Compare commits
1 commit
main
...
feature-un
Author | SHA1 | Date | |
---|---|---|---|
|
dd512c140b |
81 changed files with 524 additions and 561 deletions
|
@ -1,12 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AccountFollowController < ApplicationController
|
||||
include AccountControllerConcern
|
||||
|
||||
before_action :authenticate_user!
|
||||
|
||||
def create
|
||||
FollowService.new.call(current_user.account, @account, with_rate_limit: true)
|
||||
redirect_to account_path(@account)
|
||||
end
|
||||
end
|
|
@ -1,12 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AccountUnfollowController < ApplicationController
|
||||
include AccountControllerConcern
|
||||
|
||||
before_action :authenticate_user!
|
||||
|
||||
def create
|
||||
UnfollowService.new.call(current_user.account, @account)
|
||||
redirect_to account_path(@account)
|
||||
end
|
||||
end
|
|
@ -5,11 +5,11 @@ class AccountsController < ApplicationController
|
|||
PAGE_SIZE_MAX = 200
|
||||
|
||||
include AccountControllerConcern
|
||||
include WebAppControllerConcern
|
||||
include SignatureAuthentication
|
||||
|
||||
before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :set_cache_headers
|
||||
before_action :set_body_classes
|
||||
|
||||
skip_around_action :set_locale, if: -> { [:json, :rss].include?(request.format&.to_sym) }
|
||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
||||
|
@ -18,24 +18,6 @@ class AccountsController < ApplicationController
|
|||
respond_to do |format|
|
||||
format.html do
|
||||
expires_in 0, public: true unless user_signed_in?
|
||||
|
||||
@pinned_statuses = []
|
||||
@endorsed_accounts = @account.endorsed_accounts.to_a.sample(4)
|
||||
@featured_hashtags = @account.featured_tags.order(statuses_count: :desc)
|
||||
|
||||
if current_account && @account.blocking?(current_account)
|
||||
@statuses = []
|
||||
return
|
||||
end
|
||||
|
||||
@pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses?
|
||||
@statuses = cached_filtered_status_page
|
||||
@rss_url = rss_url
|
||||
|
||||
unless @statuses.empty?
|
||||
@older_url = older_url if @statuses.last.id > filtered_statuses.last.id
|
||||
@newer_url = newer_url if @statuses.first.id < filtered_statuses.first.id
|
||||
end
|
||||
end
|
||||
|
||||
format.rss do
|
||||
|
@ -56,10 +38,6 @@ class AccountsController < ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'with-modals'
|
||||
end
|
||||
|
||||
def show_pinned_statuses?
|
||||
[replies_requested?, media_requested?, tag_requested?, params[:max_id].present?, params[:min_id].present?].none?
|
||||
end
|
||||
|
|
16
app/controllers/api/v1/accounts/lookup_controller.rb
Normal file
16
app/controllers/api/v1/accounts/lookup_controller.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Accounts::LookupController < Api::BaseController
|
||||
before_action -> { authorize_if_got_token! :read, :'read:accounts' }
|
||||
before_action :set_account
|
||||
|
||||
def show
|
||||
render json: @account, serializer: REST::AccountSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = ResolveAccountService.new.call(params[:acct], skip_webfinger: true) || raise(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
|
@ -13,7 +13,7 @@ class AuthorizeInteractionsController < ApplicationController
|
|||
if @resource.is_a?(Account)
|
||||
render :show
|
||||
elsif @resource.is_a?(Status)
|
||||
redirect_to web_url("statuses/#{@resource.id}")
|
||||
redirect_to short_account_status_path(@resource.account.acct, @resource.id)
|
||||
else
|
||||
render :error
|
||||
end
|
||||
|
|
|
@ -8,18 +8,11 @@ module AccountControllerConcern
|
|||
FOLLOW_PER_PAGE = 12
|
||||
|
||||
included do
|
||||
layout 'public'
|
||||
|
||||
before_action :set_instance_presenter
|
||||
before_action :set_link_headers, if: -> { request.format.nil? || request.format == :html }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_instance_presenter
|
||||
@instance_presenter = InstancePresenter.new
|
||||
end
|
||||
|
||||
def set_link_headers
|
||||
response.headers['Link'] = LinkHeader.new(
|
||||
[
|
||||
|
|
18
app/controllers/concerns/web_app_controller_concern.rb
Normal file
18
app/controllers/concerns/web_app_controller_concern.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WebAppControllerConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :set_body_classes
|
||||
before_action :set_referrer_policy_header
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'app-body'
|
||||
end
|
||||
|
||||
def set_referrer_policy_header
|
||||
response.headers['Referrer-Policy'] = 'origin'
|
||||
end
|
||||
end
|
|
@ -1,42 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class DirectoriesController < ApplicationController
|
||||
layout 'public'
|
||||
include WebAppControllerConcern
|
||||
|
||||
before_action :authenticate_user!, if: :whitelist_mode?
|
||||
before_action :require_enabled!
|
||||
before_action :set_instance_presenter
|
||||
before_action :set_tag, only: :show
|
||||
before_action :set_accounts
|
||||
|
||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
||||
|
||||
def index
|
||||
render :index
|
||||
end
|
||||
|
||||
def show
|
||||
render :index
|
||||
expires_in 0, public: true if current_account.nil?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def require_enabled!
|
||||
return not_found unless Setting.profile_directory
|
||||
end
|
||||
|
||||
def set_tag
|
||||
@tag = Tag.discoverable.find_normalized!(params[:id])
|
||||
end
|
||||
|
||||
def set_accounts
|
||||
@accounts = Account.local.discoverable.by_recent_status.page(params[:page]).per(20).tap do |query|
|
||||
query.merge!(Account.tagged_with(@tag.id)) if @tag
|
||||
query.merge!(Account.not_excluded_by_account(current_account)) if current_account
|
||||
end
|
||||
end
|
||||
|
||||
def set_instance_presenter
|
||||
@instance_presenter = InstancePresenter.new
|
||||
not_found unless Setting.profile_directory
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
class FollowerAccountsController < ApplicationController
|
||||
include AccountControllerConcern
|
||||
include WebAppControllerConcern
|
||||
include SignatureVerification
|
||||
|
||||
before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
|
@ -14,10 +15,6 @@ class FollowerAccountsController < ApplicationController
|
|||
respond_to do |format|
|
||||
format.html do
|
||||
expires_in 0, public: true unless user_signed_in?
|
||||
|
||||
next if @account.user_hides_network?
|
||||
|
||||
follows
|
||||
end
|
||||
|
||||
format.json do
|
||||
|
@ -36,6 +33,10 @@ class FollowerAccountsController < ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def username_param
|
||||
params[:username] || params[:account_username]
|
||||
end
|
||||
|
||||
def follows
|
||||
return @follows if defined?(@follows)
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
class FollowingAccountsController < ApplicationController
|
||||
include AccountControllerConcern
|
||||
include WebAppControllerConcern
|
||||
include SignatureVerification
|
||||
|
||||
before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
|
@ -14,10 +15,6 @@ class FollowingAccountsController < ApplicationController
|
|||
respond_to do |format|
|
||||
format.html do
|
||||
expires_in 0, public: true unless user_signed_in?
|
||||
|
||||
next if @account.user_hides_network?
|
||||
|
||||
follows
|
||||
end
|
||||
|
||||
format.json do
|
||||
|
@ -36,6 +33,10 @@ class FollowingAccountsController < ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def username_param
|
||||
params[:username] || params[:account_username]
|
||||
end
|
||||
|
||||
def follows
|
||||
return @follows if defined?(@follows)
|
||||
|
||||
|
|
|
@ -1,47 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class HomeController < ApplicationController
|
||||
before_action :redirect_unauthenticated_to_permalinks!
|
||||
before_action :authenticate_user!
|
||||
before_action :set_referrer_policy_header
|
||||
|
||||
def index
|
||||
@body_classes = 'app-body'
|
||||
end
|
||||
include WebAppControllerConcern
|
||||
|
||||
def index; end
|
||||
|
||||
private
|
||||
|
||||
def redirect_unauthenticated_to_permalinks!
|
||||
return if user_signed_in?
|
||||
|
||||
matches = request.path.match(/\A\/web\/(statuses|accounts)\/([\d]+)\z/)
|
||||
|
||||
if matches
|
||||
case matches[1]
|
||||
when 'statuses'
|
||||
status = Status.find_by(id: matches[2])
|
||||
|
||||
if status&.distributable?
|
||||
redirect_to(ActivityPub::TagManager.instance.url_for(status))
|
||||
return
|
||||
end
|
||||
when 'accounts'
|
||||
account = Account.find_by(id: matches[2])
|
||||
|
||||
if account
|
||||
redirect_to(ActivityPub::TagManager.instance.url_for(account))
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
matches = request.path.match(%r{\A/web/timelines/tag/(?<tag>.+)\z})
|
||||
|
||||
redirect_to(matches ? tag_path(CGI.unescape(matches[:tag])) : default_redirect_path)
|
||||
end
|
||||
|
||||
def default_redirect_path
|
||||
if request.path.start_with?('/web') || whitelist_mode?
|
||||
if whitelist_mode?
|
||||
new_user_session_path
|
||||
elsif single_user_mode?
|
||||
short_account_path(Account.local.without_suspended.where('id > 0').first)
|
||||
|
@ -49,8 +18,4 @@ class HomeController < ApplicationController
|
|||
about_path
|
||||
end
|
||||
end
|
||||
|
||||
def set_referrer_policy_header
|
||||
response.headers['Referrer-Policy'] = 'origin'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,26 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class PublicTimelinesController < ApplicationController
|
||||
layout 'public'
|
||||
include WebAppControllerConcern
|
||||
|
||||
before_action :authenticate_user!, if: :whitelist_mode?
|
||||
before_action :require_enabled!
|
||||
before_action :set_body_classes
|
||||
before_action :set_instance_presenter
|
||||
|
||||
def show; end
|
||||
def show
|
||||
expires_in 0, public: true if current_account.nil?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def require_enabled!
|
||||
not_found unless Setting.timeline_preview
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'with-modals'
|
||||
end
|
||||
|
||||
def set_instance_presenter
|
||||
@instance_presenter = InstancePresenter.new
|
||||
not_found unless user_signed_in? || Setting.timeline_preview
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,21 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class StatusesController < ApplicationController
|
||||
include StatusControllerConcern
|
||||
include SignatureAuthentication
|
||||
include Authorization
|
||||
include AccountOwnedConcern
|
||||
|
||||
layout 'public'
|
||||
include WebAppControllerConcern
|
||||
|
||||
before_action :require_signature!, only: [:show, :activity], if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :set_status
|
||||
before_action :set_instance_presenter
|
||||
before_action :set_link_headers
|
||||
before_action :redirect_to_original, only: :show
|
||||
before_action :set_referrer_policy_header, only: :show
|
||||
before_action :set_cache_headers
|
||||
before_action :set_body_classes
|
||||
before_action :set_autoplay, only: :embed
|
||||
|
||||
skip_around_action :set_locale, if: -> { request.format == :json }
|
||||
|
@ -29,8 +25,6 @@ class StatusesController < ApplicationController
|
|||
respond_to do |format|
|
||||
format.html do
|
||||
expires_in 10.seconds, public: true if current_account.nil?
|
||||
set_ancestors
|
||||
set_descendants
|
||||
end
|
||||
|
||||
format.json do
|
||||
|
@ -56,10 +50,6 @@ class StatusesController < ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'with-modals'
|
||||
end
|
||||
|
||||
def set_link_headers
|
||||
response.headers['Link'] = LinkHeader.new([[ActivityPub::TagManager.instance.uri_for(@status), [%w(rel alternate), %w(type application/activity+json)]]])
|
||||
end
|
||||
|
@ -71,16 +61,12 @@ class StatusesController < ApplicationController
|
|||
not_found
|
||||
end
|
||||
|
||||
def set_instance_presenter
|
||||
@instance_presenter = InstancePresenter.new
|
||||
end
|
||||
|
||||
def redirect_to_original
|
||||
redirect_to ActivityPub::TagManager.instance.url_for(@status.reblog) if @status.reblog?
|
||||
end
|
||||
|
||||
def set_referrer_policy_header
|
||||
response.headers['Referrer-Policy'] = 'origin' unless @status.distributable?
|
||||
response.headers['Referrer-Policy'] = 'origin'
|
||||
end
|
||||
|
||||
def set_autoplay
|
||||
|
|
|
@ -2,26 +2,23 @@
|
|||
|
||||
class TagsController < ApplicationController
|
||||
include SignatureVerification
|
||||
include WebAppControllerConcern
|
||||
|
||||
PAGE_SIZE = 20
|
||||
PAGE_SIZE_MAX = 200
|
||||
|
||||
layout 'public'
|
||||
|
||||
before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :authenticate_user!, if: :whitelist_mode?
|
||||
before_action :set_local
|
||||
before_action :set_tag
|
||||
before_action :set_statuses
|
||||
before_action :set_body_classes
|
||||
before_action :set_instance_presenter
|
||||
|
||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
||||
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
expires_in 0, public: true
|
||||
expires_in 0, public: true if current_account.nil?
|
||||
end
|
||||
|
||||
format.rss do
|
||||
|
@ -55,14 +52,6 @@ class TagsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'with-modals'
|
||||
end
|
||||
|
||||
def set_instance_presenter
|
||||
@instance_presenter = InstancePresenter.new
|
||||
end
|
||||
|
||||
def limit_param
|
||||
params[:limit].present? ? [params[:limit].to_i, PAGE_SIZE_MAX].min : PAGE_SIZE
|
||||
end
|
||||
|
|
|
@ -158,19 +158,15 @@ module ApplicationHelper
|
|||
|
||||
def render_initial_state
|
||||
state_params = {
|
||||
settings: {
|
||||
known_fediverse: Setting.show_known_fediverse_at_about_page,
|
||||
},
|
||||
|
||||
text: [params[:title], params[:text], params[:url]].compact.join(' '),
|
||||
}
|
||||
|
||||
permit_visibilities = %w(public unlisted private direct)
|
||||
default_privacy = current_account&.user&.setting_default_privacy
|
||||
permit_visibilities.shift(permit_visibilities.index(default_privacy) + 1) if default_privacy.present?
|
||||
state_params[:visibility] = params[:visibility] if permit_visibilities.include? params[:visibility]
|
||||
|
||||
if user_signed_in?
|
||||
permit_visibilities = %w(public unlisted private direct)
|
||||
default_privacy = current_account&.user&.setting_default_privacy
|
||||
permit_visibilities.shift(permit_visibilities.index(default_privacy) + 1) if default_privacy.present?
|
||||
|
||||
state_params[:visibility] = params[:visibility] if permit_visibilities.include? params[:visibility]
|
||||
state_params[:settings] = state_params[:settings].merge(Web::Setting.find_by(user: current_user)&.data || {})
|
||||
state_params[:push_subscription] = current_account.user.web_push_subscription(current_session)
|
||||
state_params[:current_account] = current_account
|
||||
|
|
|
@ -5,6 +5,10 @@ export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST';
|
|||
export const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS';
|
||||
export const ACCOUNT_FETCH_FAIL = 'ACCOUNT_FETCH_FAIL';
|
||||
|
||||
export const ACCOUNT_LOOKUP_REQUEST = 'ACCOUNT_LOOKUP_REQUEST';
|
||||
export const ACCOUNT_LOOKUP_SUCCESS = 'ACCOUNT_LOOKUP_SUCCESS';
|
||||
export const ACCOUNT_LOOKUP_FAIL = 'ACCOUNT_LOOKUP_FAIL';
|
||||
|
||||
export const ACCOUNT_FOLLOW_REQUEST = 'ACCOUNT_FOLLOW_REQUEST';
|
||||
export const ACCOUNT_FOLLOW_SUCCESS = 'ACCOUNT_FOLLOW_SUCCESS';
|
||||
export const ACCOUNT_FOLLOW_FAIL = 'ACCOUNT_FOLLOW_FAIL';
|
||||
|
@ -87,6 +91,34 @@ export function fetchAccount(id) {
|
|||
};
|
||||
};
|
||||
|
||||
export const lookupAccount = acct => (dispatch, getState) => {
|
||||
dispatch(lookupAccountRequest(acct));
|
||||
|
||||
api(getState).get('/api/v1/accounts/lookup', { params: { acct } }).then(response => {
|
||||
dispatch(fetchRelationships([response.data.id]));
|
||||
dispatch(importFetchedAccount(response.data));
|
||||
dispatch(lookupAccountSuccess());
|
||||
}).catch(error => {
|
||||
dispatch(lookupAccountFail(acct, error));
|
||||
});
|
||||
};
|
||||
|
||||
export const lookupAccountRequest = (acct) => ({
|
||||
type: ACCOUNT_LOOKUP_REQUEST,
|
||||
acct,
|
||||
});
|
||||
|
||||
export const lookupAccountSuccess = () => ({
|
||||
type: ACCOUNT_LOOKUP_SUCCESS,
|
||||
});
|
||||
|
||||
export const lookupAccountFail = (acct, error) => ({
|
||||
type: ACCOUNT_LOOKUP_FAIL,
|
||||
acct,
|
||||
error,
|
||||
skipAlert: true,
|
||||
});
|
||||
|
||||
export function fetchAccountRequest(id) {
|
||||
return {
|
||||
type: ACCOUNT_FETCH_REQUEST,
|
||||
|
|
|
@ -72,7 +72,7 @@ const COMPOSE_PANEL_BREAKPOINT = 600 + (285 * 1) + (10 * 1);
|
|||
|
||||
export const ensureComposeIsVisible = (getState, routerHistory) => {
|
||||
if (!getState().getIn(['compose', 'mounted']) && window.innerWidth < COMPOSE_PANEL_BREAKPOINT) {
|
||||
routerHistory.push('/statuses/new');
|
||||
routerHistory.push('/publish');
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -152,7 +152,7 @@ export function submitCompose(routerHistory) {
|
|||
'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
|
||||
},
|
||||
}).then(function (response) {
|
||||
if (routerHistory && routerHistory.location.pathname === '/statuses/new' && window.history.state) {
|
||||
if (routerHistory && routerHistory.location.pathname === '/publish' && window.history.state) {
|
||||
routerHistory.goBack();
|
||||
}
|
||||
|
||||
|
|
|
@ -116,7 +116,7 @@ class Account extends ImmutablePureComponent {
|
|||
return (
|
||||
<div className='account'>
|
||||
<div className='account__wrapper'>
|
||||
<Permalink key={account.get('id')} className='account__display-name' title={account.get('acct')} href={account.get('url')} to={`/accounts/${account.get('id')}`}>
|
||||
<Permalink key={account.get('id')} className='account__display-name' title={account.get('acct')} href={account.get('url')} to={`/@${account.get('acct')}`}>
|
||||
<div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div>
|
||||
{mute_expires_at}
|
||||
<DisplayName account={account} />
|
||||
|
|
|
@ -27,7 +27,7 @@ const Hashtag = ({ hashtag }) => (
|
|||
<div className='trends__item__name'>
|
||||
<Permalink
|
||||
href={hashtag.get('url')}
|
||||
to={`/timelines/tag/${hashtag.get('name')}`}
|
||||
to={`/tags/${hashtag.get('name')}`}
|
||||
>
|
||||
#<span>{hashtag.get('name')}</span>
|
||||
</Permalink>
|
||||
|
|
|
@ -134,42 +134,28 @@ class Status extends ImmutablePureComponent {
|
|||
this.setState({ showMedia: !this.state.showMedia });
|
||||
}
|
||||
|
||||
handleClick = () => {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick();
|
||||
handleClick = e => {
|
||||
if (e && (e.button !== 0 || e.ctrlKey || e.metaKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.context.router) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { status } = this.props;
|
||||
this.context.router.history.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`);
|
||||
}
|
||||
|
||||
handleExpandClick = (e) => {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.button === 0) {
|
||||
if (!this.context.router) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { status } = this.props;
|
||||
this.context.router.history.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`);
|
||||
}
|
||||
}
|
||||
|
||||
handleAccountClick = (e) => {
|
||||
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
const id = e.currentTarget.getAttribute('data-id');
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(`/accounts/${id}`);
|
||||
}
|
||||
|
||||
this.handleHotkeyOpen();
|
||||
}
|
||||
|
||||
handleAccountClick = e => {
|
||||
if (e && (e.button !== 0 || e.ctrlKey || e.metaKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
this.handleHotkeyOpenProfile();
|
||||
}
|
||||
|
||||
handleExpandedToggle = () => {
|
||||
|
@ -242,11 +228,30 @@ class Status extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
handleHotkeyOpen = () => {
|
||||
this.context.router.history.push(`/statuses/${this._properStatus().get('id')}`);
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick();
|
||||
return;
|
||||
}
|
||||
|
||||
const { router } = this.context;
|
||||
const status = this._properStatus();
|
||||
|
||||
if (!router) {
|
||||
return;
|
||||
}
|
||||
|
||||
router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
|
||||
}
|
||||
|
||||
handleHotkeyOpenProfile = () => {
|
||||
this.context.router.history.push(`/accounts/${this._properStatus().getIn(['account', 'id'])}`);
|
||||
const { router } = this.context;
|
||||
const status = this._properStatus();
|
||||
|
||||
if (!router) {
|
||||
return;
|
||||
}
|
||||
|
||||
router.history.push(`/@${status.getIn(['account', 'acct'])}`);
|
||||
}
|
||||
|
||||
handleHotkeyMoveUp = e => {
|
||||
|
@ -465,14 +470,15 @@ class Status extends ImmutablePureComponent {
|
|||
{prepend}
|
||||
|
||||
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), muted: this.props.muted })} data-id={status.get('id')}>
|
||||
<div className='status__expand' onClick={this.handleExpandClick} role='presentation' />
|
||||
<div className='status__expand' onClick={this.handleClick} role='presentation' />
|
||||
|
||||
<div className='status__info'>
|
||||
<a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener noreferrer'>
|
||||
<a onClick={this.handleClick} href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener noreferrer'>
|
||||
<span className='status__visibility-icon'><Icon id={visibilityIcon.icon} title={visibilityIcon.text} /></span>
|
||||
<RelativeTimestamp timestamp={status.get('created_at')} />
|
||||
</a>
|
||||
|
||||
<a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} title={status.getIn(['account', 'acct'])} className='status__display-name' target='_blank' rel='noopener noreferrer'>
|
||||
<a onClick={this.handleAccountClick} href={status.getIn(['account', 'url'])} title={status.getIn(['account', 'acct'])} className='status__display-name' target='_blank' rel='noopener noreferrer'>
|
||||
<div className='status__avatar'>
|
||||
{statusAvatar}
|
||||
</div>
|
||||
|
|
|
@ -186,7 +186,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
handleOpen = () => {
|
||||
this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
|
||||
this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}/${this.props.status.get('id')}`);
|
||||
}
|
||||
|
||||
handleEmbed = () => {
|
||||
|
|
|
@ -112,7 +112,7 @@ export default class StatusContent extends React.PureComponent {
|
|||
onMentionClick = (mention, e) => {
|
||||
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(`/accounts/${mention.get('id')}`);
|
||||
this.context.router.history.push(`/@${mention.get('acct')}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -121,7 +121,7 @@ export default class StatusContent extends React.PureComponent {
|
|||
|
||||
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(`/timelines/tag/${hashtag}`);
|
||||
this.context.router.history.push(`/tags/${hashtag}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -202,7 +202,7 @@ export default class StatusContent extends React.PureComponent {
|
|||
let mentionsPlaceholder = '';
|
||||
|
||||
const mentionLinks = status.get('mentions').map(item => (
|
||||
<Permalink to={`/accounts/${item.get('id')}`} href={item.get('url')} key={item.get('id')} className='mention'>
|
||||
<Permalink to={`/@${item.get('acct')}`} href={item.get('url')} key={item.get('id')} className='mention'>
|
||||
@<span>{item.get('username')}</span>
|
||||
</Permalink>
|
||||
)).reduce((aggregate, item) => [...aggregate, item, ' '], []);
|
||||
|
|
|
@ -14,7 +14,7 @@ import { IntlProvider, addLocaleData } from 'react-intl';
|
|||
import { getLocale } from '../locales';
|
||||
import { previewState as previewMediaState } from 'mastodon/features/ui/components/media_modal';
|
||||
import { previewState as previewVideoState } from 'mastodon/features/ui/components/video_modal';
|
||||
import initialState from '../initial_state';
|
||||
import initialState, { me } from '../initial_state';
|
||||
import ErrorBoundary from '../components/error_boundary';
|
||||
|
||||
const { localeData, messages } = getLocale();
|
||||
|
@ -27,7 +27,7 @@ store.dispatch(hydrateAction);
|
|||
store.dispatch(fetchCustomEmojis());
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
showIntroduction: state.getIn(['settings', 'introductionVersion'], 0) < INTRODUCTION_VERSION,
|
||||
showIntroduction: me && state.getIn(['settings', 'introductionVersion'], 0) < INTRODUCTION_VERSION,
|
||||
});
|
||||
|
||||
@connect(mapStateToProps)
|
||||
|
@ -49,7 +49,7 @@ class MastodonMount extends React.PureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<BrowserRouter basename='/web'>
|
||||
<BrowserRouter>
|
||||
<ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
|
||||
<Route path='/' component={UI} />
|
||||
</ScrollContext>
|
||||
|
|
|
@ -330,21 +330,21 @@ class Header extends ImmutablePureComponent {
|
|||
|
||||
{!suspended && (
|
||||
<div className='account__header__extra__links'>
|
||||
<NavLink isActive={this.isStatusesPageActive} activeClassName='active' to={`/accounts/${account.get('id')}`} title={intl.formatNumber(account.get('statuses_count'))}>
|
||||
<NavLink isActive={this.isStatusesPageActive} activeClassName='active' to={`/@${account.get('acct')}`} title={intl.formatNumber(account.get('statuses_count'))}>
|
||||
<ShortNumber
|
||||
value={account.get('statuses_count')}
|
||||
renderer={counterRenderer('statuses')}
|
||||
/>
|
||||
</NavLink>
|
||||
|
||||
<NavLink exact activeClassName='active' to={`/accounts/${account.get('id')}/following`} title={intl.formatNumber(account.get('following_count'))}>
|
||||
<NavLink exact activeClassName='active' to={`/@${account.get('acct')}/following`} title={intl.formatNumber(account.get('following_count'))}>
|
||||
<ShortNumber
|
||||
value={account.get('following_count')}
|
||||
renderer={counterRenderer('following')}
|
||||
/>
|
||||
</NavLink>
|
||||
|
||||
<NavLink exact activeClassName='active' to={`/accounts/${account.get('id')}/followers`} title={intl.formatNumber(account.get('followers_count'))}>
|
||||
<NavLink exact activeClassName='active' to={`/@${account.get('acct')}/followers`} title={intl.formatNumber(account.get('followers_count'))}>
|
||||
<ShortNumber
|
||||
value={account.get('followers_count')}
|
||||
renderer={counterRenderer('followers')}
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import { fetchAccount } from 'mastodon/actions/accounts';
|
||||
import { lookupAccount } from 'mastodon/actions/accounts';
|
||||
import { expandAccountMediaTimeline } from '../../actions/timelines';
|
||||
import LoadingIndicator from 'mastodon/components/loading_indicator';
|
||||
import Column from '../ui/components/column';
|
||||
|
@ -17,14 +17,25 @@ import MissingIndicator from 'mastodon/components/missing_indicator';
|
|||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
isAccount: !!state.getIn(['accounts', props.params.accountId]),
|
||||
attachments: getAccountGallery(state, props.params.accountId),
|
||||
isLoading: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'isLoading']),
|
||||
hasMore: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'hasMore']),
|
||||
suspended: state.getIn(['accounts', props.params.accountId, 'suspended'], false),
|
||||
blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false),
|
||||
});
|
||||
const mapStateToProps = (state, { params: { acct } }) => {
|
||||
const accountId = state.getIn(['accounts_map', acct]);
|
||||
|
||||
if (!accountId) {
|
||||
return {
|
||||
isLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
accountId,
|
||||
isAccount: !!state.getIn(['accounts', accountId]),
|
||||
attachments: getAccountGallery(state, accountId),
|
||||
isLoading: state.getIn(['timelines', `account:${accountId}:media`, 'isLoading']),
|
||||
hasMore: state.getIn(['timelines', `account:${accountId}:media`, 'hasMore']),
|
||||
suspended: state.getIn(['accounts', accountId, 'suspended'], false),
|
||||
blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
|
||||
};
|
||||
};
|
||||
|
||||
class LoadMoreMedia extends ImmutablePureComponent {
|
||||
|
||||
|
@ -53,7 +64,10 @@ export default @connect(mapStateToProps)
|
|||
class AccountGallery extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
params: PropTypes.shape({
|
||||
acct: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
accountId: PropTypes.string,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
attachments: ImmutablePropTypes.list.isRequired,
|
||||
isLoading: PropTypes.bool,
|
||||
|
@ -68,15 +82,29 @@ class AccountGallery extends ImmutablePureComponent {
|
|||
width: 323,
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
this.props.dispatch(fetchAccount(this.props.params.accountId));
|
||||
this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId));
|
||||
_load () {
|
||||
const { accountId, dispatch } = this.props;
|
||||
|
||||
dispatch(expandAccountMediaTimeline(accountId));
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) {
|
||||
this.props.dispatch(fetchAccount(nextProps.params.accountId));
|
||||
this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId));
|
||||
componentDidMount () {
|
||||
const { params: { acct }, accountId, dispatch } = this.props;
|
||||
|
||||
if (accountId) {
|
||||
this._load();
|
||||
} else {
|
||||
dispatch(lookupAccount(acct));
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
const { params: { acct }, accountId, dispatch } = this.props;
|
||||
|
||||
if (prevProps.accountId !== accountId && accountId) {
|
||||
this._load();
|
||||
} else if (prevProps.params.acct !== acct) {
|
||||
dispatch(lookupAccount(acct));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,7 +124,7 @@ class AccountGallery extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
handleLoadMore = maxId => {
|
||||
this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId, { maxId }));
|
||||
this.props.dispatch(expandAccountMediaTimeline(this.props.accountId, { maxId }));
|
||||
};
|
||||
|
||||
handleLoadOlder = e => {
|
||||
|
@ -166,7 +194,7 @@ class AccountGallery extends ImmutablePureComponent {
|
|||
|
||||
<ScrollContainer scrollKey='account_gallery' shouldUpdateScroll={shouldUpdateScroll}>
|
||||
<div className='scrollable scrollable--flex' onScroll={this.handleScroll}>
|
||||
<HeaderContainer accountId={this.props.params.accountId} />
|
||||
<HeaderContainer accountId={this.props.accountId} />
|
||||
|
||||
{(suspended || blockedBy) ? (
|
||||
<div className='empty-column-indicator'>
|
||||
|
|
|
@ -123,9 +123,9 @@ export default class Header extends ImmutablePureComponent {
|
|||
|
||||
{!hideTabs && (
|
||||
<div className='account__section-headline'>
|
||||
<NavLink exact to={`/accounts/${account.get('id')}`}><FormattedMessage id='account.posts' defaultMessage='Toots' /></NavLink>
|
||||
<NavLink exact to={`/accounts/${account.get('id')}/with_replies`}><FormattedMessage id='account.posts_with_replies' defaultMessage='Toots and replies' /></NavLink>
|
||||
<NavLink exact to={`/accounts/${account.get('id')}/media`}><FormattedMessage id='account.media' defaultMessage='Media' /></NavLink>
|
||||
<NavLink exact to={`/@${account.get('acct')}`}><FormattedMessage id='account.posts' defaultMessage='Toots' /></NavLink>
|
||||
<NavLink exact to={`/@${account.get('acct')}/with_replies`}><FormattedMessage id='account.posts_with_replies' defaultMessage='Toots and replies' /></NavLink>
|
||||
<NavLink exact to={`/@${account.get('acct')}/media`}><FormattedMessage id='account.media' defaultMessage='Media' /></NavLink>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -21,7 +21,7 @@ export default class MovedNote extends ImmutablePureComponent {
|
|||
handleAccountClick = e => {
|
||||
if (e.button === 0) {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(`/accounts/${this.props.to.get('id')}`);
|
||||
this.context.router.history.push(`/@${this.props.to.get('acct')}`);
|
||||
}
|
||||
|
||||
e.stopPropagation();
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import { fetchAccount } from '../../actions/accounts';
|
||||
import { lookupAccount, fetchAccount } from '../../actions/accounts';
|
||||
import { expandAccountFeaturedTimeline, expandAccountTimeline } from '../../actions/timelines';
|
||||
import StatusList from '../../components/status_list';
|
||||
import LoadingIndicator from '../../components/loading_indicator';
|
||||
|
@ -20,10 +20,19 @@ import { connectTimeline, disconnectTimeline } from 'mastodon/actions/timelines'
|
|||
|
||||
const emptyList = ImmutableList();
|
||||
|
||||
const mapStateToProps = (state, { params: { accountId }, withReplies = false }) => {
|
||||
const mapStateToProps = (state, { params: { acct }, withReplies = false }) => {
|
||||
const accountId = state.getIn(['accounts_map', acct]);
|
||||
|
||||
if (!accountId) {
|
||||
return {
|
||||
isLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
const path = withReplies ? `${accountId}:with_replies` : accountId;
|
||||
|
||||
return {
|
||||
accountId,
|
||||
remote: !!(state.getIn(['accounts', accountId, 'acct']) !== state.getIn(['accounts', accountId, 'username'])),
|
||||
remoteUrl: state.getIn(['accounts', accountId, 'url']),
|
||||
isAccount: !!state.getIn(['accounts', accountId]),
|
||||
|
@ -48,7 +57,10 @@ export default @connect(mapStateToProps)
|
|||
class AccountTimeline extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
params: PropTypes.shape({
|
||||
acct: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
accountId: PropTypes.string,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
shouldUpdateScroll: PropTypes.func,
|
||||
statusIds: ImmutablePropTypes.list,
|
||||
|
@ -64,8 +76,8 @@ class AccountTimeline extends ImmutablePureComponent {
|
|||
multiColumn: PropTypes.bool,
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
const { params: { accountId }, withReplies, dispatch } = this.props;
|
||||
_load () {
|
||||
const { accountId, withReplies, dispatch } = this.props;
|
||||
|
||||
dispatch(fetchAccount(accountId));
|
||||
dispatch(fetchAccountIdentityProofs(accountId));
|
||||
|
@ -81,29 +93,32 @@ class AccountTimeline extends ImmutablePureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
const { dispatch } = this.props;
|
||||
componentDidMount () {
|
||||
const { params: { acct }, accountId, dispatch } = this.props;
|
||||
|
||||
if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) {
|
||||
dispatch(fetchAccount(nextProps.params.accountId));
|
||||
dispatch(fetchAccountIdentityProofs(nextProps.params.accountId));
|
||||
if (accountId) {
|
||||
this._load();
|
||||
} else {
|
||||
dispatch(lookupAccount(acct));
|
||||
}
|
||||
}
|
||||
|
||||
if (!nextProps.withReplies) {
|
||||
dispatch(expandAccountFeaturedTimeline(nextProps.params.accountId));
|
||||
}
|
||||
componentDidUpdate (prevProps) {
|
||||
const { params: { acct }, accountId, dispatch } = this.props;
|
||||
|
||||
dispatch(expandAccountTimeline(nextProps.params.accountId, { withReplies: nextProps.params.withReplies }));
|
||||
if (prevProps.accountId !== accountId && accountId) {
|
||||
this._load();
|
||||
} else if (prevProps.params.acct !== acct) {
|
||||
dispatch(lookupAccount(acct));
|
||||
}
|
||||
|
||||
if (nextProps.params.accountId === me && this.props.params.accountId !== me) {
|
||||
dispatch(connectTimeline(`account:${me}`));
|
||||
} else if (this.props.params.accountId === me && nextProps.params.accountId !== me) {
|
||||
if (prevProps.accountId === me && accountId !== me) {
|
||||
dispatch(disconnectTimeline(`account:${me}`));
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
const { dispatch, params: { accountId } } = this.props;
|
||||
const { dispatch, accountId } = this.props;
|
||||
|
||||
if (accountId === me) {
|
||||
dispatch(disconnectTimeline(`account:${me}`));
|
||||
|
@ -111,7 +126,7 @@ class AccountTimeline extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
handleLoadMore = maxId => {
|
||||
this.props.dispatch(expandAccountTimeline(this.props.params.accountId, { maxId, withReplies: this.props.withReplies }));
|
||||
this.props.dispatch(expandAccountTimeline(this.props.accountId, { maxId, withReplies: this.props.withReplies }));
|
||||
}
|
||||
|
||||
render () {
|
||||
|
@ -153,7 +168,7 @@ class AccountTimeline extends ImmutablePureComponent {
|
|||
<ColumnBackButton multiColumn={multiColumn} />
|
||||
|
||||
<StatusList
|
||||
prepend={<HeaderContainer accountId={this.props.params.accountId} />}
|
||||
prepend={<HeaderContainer accountId={this.props.accountId} />}
|
||||
alwaysPrepend
|
||||
append={remoteMessage}
|
||||
scrollKey='account_timeline'
|
||||
|
|
|
@ -19,13 +19,13 @@ export default class NavigationBar extends ImmutablePureComponent {
|
|||
render () {
|
||||
return (
|
||||
<div className='navigation-bar'>
|
||||
<Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`}>
|
||||
<Permalink href={this.props.account.get('url')} to={`/@${this.props.account.get('acct')}`}>
|
||||
<span style={{ display: 'none' }}>{this.props.account.get('acct')}</span>
|
||||
<Avatar account={this.props.account} size={48} />
|
||||
</Permalink>
|
||||
|
||||
<div className='navigation-bar__profile'>
|
||||
<Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`}>
|
||||
<Permalink href={this.props.account.get('url')} to={`/@${this.props.account.get('acct')}`}>
|
||||
<strong className='navigation-bar__profile-account'>@{this.props.account.get('acct')}</strong>
|
||||
</Permalink>
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ class ReplyIndicator extends ImmutablePureComponent {
|
|||
handleAccountClick = (e) => {
|
||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
|
||||
this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -99,16 +99,16 @@ class Compose extends React.PureComponent {
|
|||
<nav className='drawer__header'>
|
||||
<Link to='/getting-started' className='drawer__tab' title={intl.formatMessage(messages.start)} aria-label={intl.formatMessage(messages.start)}><Icon id='bars' fixedWidth /></Link>
|
||||
{!columns.some(column => column.get('id') === 'HOME') && (
|
||||
<Link to='/timelines/home' className='drawer__tab' title={intl.formatMessage(messages.home_timeline)} aria-label={intl.formatMessage(messages.home_timeline)}><Icon id='home' fixedWidth /></Link>
|
||||
<Link to='/home' className='drawer__tab' title={intl.formatMessage(messages.home_timeline)} aria-label={intl.formatMessage(messages.home_timeline)}><Icon id='home' fixedWidth /></Link>
|
||||
)}
|
||||
{!columns.some(column => column.get('id') === 'NOTIFICATIONS') && (
|
||||
<Link to='/notifications' className='drawer__tab' title={intl.formatMessage(messages.notifications)} aria-label={intl.formatMessage(messages.notifications)}><Icon id='bell' fixedWidth /></Link>
|
||||
)}
|
||||
{!columns.some(column => column.get('id') === 'COMMUNITY') && (
|
||||
<Link to='/timelines/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)} aria-label={intl.formatMessage(messages.community)}><Icon id='users' fixedWidth /></Link>
|
||||
<Link to='/local' className='drawer__tab' title={intl.formatMessage(messages.community)} aria-label={intl.formatMessage(messages.community)}><Icon id='users' fixedWidth /></Link>
|
||||
)}
|
||||
{!columns.some(column => column.get('id') === 'PUBLIC') && (
|
||||
<Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><Icon id='globe' fixedWidth /></Link>
|
||||
<Link to='/federated' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><Icon id='globe' fixedWidth /></Link>
|
||||
)}
|
||||
<a href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)} aria-label={intl.formatMessage(messages.preferences)}><Icon id='cog' fixedWidth /></a>
|
||||
<a href='/auth/sign_out' className='drawer__tab' title={intl.formatMessage(messages.logout)} aria-label={intl.formatMessage(messages.logout)} onClick={this.handleLogoutClick}><Icon id='sign-out' fixedWidth /></a>
|
||||
|
|
|
@ -133,7 +133,7 @@ class Conversation extends ImmutablePureComponent {
|
|||
|
||||
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDelete });
|
||||
|
||||
const names = accounts.map(a => <Permalink to={`/accounts/${a.get('id')}`} href={a.get('url')} key={a.get('id')} title={a.get('acct')}><bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }} /></bdi></Permalink>).reduce((prev, cur) => [prev, ', ', cur]);
|
||||
const names = accounts.map(a => <Permalink to={`/@${a.get('acct')}`} href={a.get('url')} key={a.get('id')} title={a.get('acct')}><bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }} /></bdi></Permalink>).reduce((prev, cur) => [prev, ', ', cur]);
|
||||
|
||||
const handlers = {
|
||||
reply: this.handleReply,
|
||||
|
|
|
@ -213,7 +213,7 @@ class AccountCard extends ImmutablePureComponent {
|
|||
<Permalink
|
||||
className='directory__card__bar__name'
|
||||
href={account.get('url')}
|
||||
to={`/accounts/${account.get('id')}`}
|
||||
to={`/@${account.get('acct')}`}
|
||||
>
|
||||
<Avatar account={account} size={48} />
|
||||
<DisplayName account={account} />
|
||||
|
|
|
@ -30,7 +30,7 @@ class AccountAuthorize extends ImmutablePureComponent {
|
|||
return (
|
||||
<div className='account-authorize__wrapper'>
|
||||
<div className='account-authorize'>
|
||||
<Permalink href={account.get('url')} to={`/accounts/${account.get('id')}`} className='detailed-status__display-name'>
|
||||
<Permalink href={account.get('url')} to={`/@${account.get('acct')}`} className='detailed-status__display-name'>
|
||||
<div className='account-authorize__avatar'><Avatar account={account} size={48} /></div>
|
||||
<DisplayName account={account} />
|
||||
</Permalink>
|
||||
|
|
|
@ -6,7 +6,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||
import { debounce } from 'lodash';
|
||||
import LoadingIndicator from '../../components/loading_indicator';
|
||||
import {
|
||||
fetchAccount,
|
||||
lookupAccount,
|
||||
fetchFollowers,
|
||||
expandFollowers,
|
||||
} from '../../actions/accounts';
|
||||
|
@ -19,15 +19,26 @@ import ScrollableList from '../../components/scrollable_list';
|
|||
import MissingIndicator from 'mastodon/components/missing_indicator';
|
||||
import TimelineHint from 'mastodon/components/timeline_hint';
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
remote: !!(state.getIn(['accounts', props.params.accountId, 'acct']) !== state.getIn(['accounts', props.params.accountId, 'username'])),
|
||||
remoteUrl: state.getIn(['accounts', props.params.accountId, 'url']),
|
||||
isAccount: !!state.getIn(['accounts', props.params.accountId]),
|
||||
accountIds: state.getIn(['user_lists', 'followers', props.params.accountId, 'items']),
|
||||
hasMore: !!state.getIn(['user_lists', 'followers', props.params.accountId, 'next']),
|
||||
isLoading: state.getIn(['user_lists', 'followers', props.params.accountId, 'isLoading'], true),
|
||||
blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false),
|
||||
});
|
||||
const mapStateToProps = (state, { params: { acct } }) => {
|
||||
const accountId = state.getIn(['accounts_map', acct]);
|
||||
|
||||
if (!accountId) {
|
||||
return {
|
||||
isLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
accountId,
|
||||
remote: !!(state.getIn(['accounts', accountId, 'acct']) !== state.getIn(['accounts', accountId, 'username'])),
|
||||
remoteUrl: state.getIn(['accounts', accountId, 'url']),
|
||||
isAccount: !!state.getIn(['accounts', accountId]),
|
||||
accountIds: state.getIn(['user_lists', 'followers', accountId, 'items']),
|
||||
hasMore: !!state.getIn(['user_lists', 'followers', accountId, 'next']),
|
||||
isLoading: state.getIn(['user_lists', 'followers', accountId, 'isLoading'], true),
|
||||
blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
|
||||
};
|
||||
};
|
||||
|
||||
const RemoteHint = ({ url }) => (
|
||||
<TimelineHint url={url} resource={<FormattedMessage id='timeline_hint.resources.followers' defaultMessage='Followers' />} />
|
||||
|
@ -41,7 +52,10 @@ export default @connect(mapStateToProps)
|
|||
class Followers extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
params: PropTypes.shape({
|
||||
acct: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
accountId: PropTypes.string,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
shouldUpdateScroll: PropTypes.func,
|
||||
accountIds: ImmutablePropTypes.list,
|
||||
|
@ -54,22 +68,34 @@ class Followers extends ImmutablePureComponent {
|
|||
multiColumn: PropTypes.bool,
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
if (!this.props.accountIds) {
|
||||
this.props.dispatch(fetchAccount(this.props.params.accountId));
|
||||
this.props.dispatch(fetchFollowers(this.props.params.accountId));
|
||||
_load () {
|
||||
const { accountId, dispatch } = this.props;
|
||||
|
||||
dispatch(fetchFollowers(accountId));
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { params: { acct }, accountId, dispatch } = this.props;
|
||||
|
||||
if (accountId) {
|
||||
this._load();
|
||||
} else {
|
||||
dispatch(lookupAccount(acct));
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) {
|
||||
this.props.dispatch(fetchAccount(nextProps.params.accountId));
|
||||
this.props.dispatch(fetchFollowers(nextProps.params.accountId));
|
||||
componentDidUpdate (prevProps) {
|
||||
const { params: { acct }, accountId, dispatch } = this.props;
|
||||
|
||||
if (prevProps.accountId !== accountId && accountId) {
|
||||
this._load();
|
||||
} else if (prevProps.params.acct !== acct) {
|
||||
dispatch(lookupAccount(acct));
|
||||
}
|
||||
}
|
||||
|
||||
handleLoadMore = debounce(() => {
|
||||
this.props.dispatch(expandFollowers(this.props.params.accountId));
|
||||
this.props.dispatch(expandFollowers(this.props.accountId));
|
||||
}, 300, { leading: true });
|
||||
|
||||
render () {
|
||||
|
|
|
@ -6,7 +6,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||
import { debounce } from 'lodash';
|
||||
import LoadingIndicator from '../../components/loading_indicator';
|
||||
import {
|
||||
fetchAccount,
|
||||
lookupAccount,
|
||||
fetchFollowing,
|
||||
expandFollowing,
|
||||
} from '../../actions/accounts';
|
||||
|
@ -19,15 +19,26 @@ import ScrollableList from '../../components/scrollable_list';
|
|||
import MissingIndicator from 'mastodon/components/missing_indicator';
|
||||
import TimelineHint from 'mastodon/components/timeline_hint';
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
remote: !!(state.getIn(['accounts', props.params.accountId, 'acct']) !== state.getIn(['accounts', props.params.accountId, 'username'])),
|
||||
remoteUrl: state.getIn(['accounts', props.params.accountId, 'url']),
|
||||
isAccount: !!state.getIn(['accounts', props.params.accountId]),
|
||||
accountIds: state.getIn(['user_lists', 'following', props.params.accountId, 'items']),
|
||||
hasMore: !!state.getIn(['user_lists', 'following', props.params.accountId, 'next']),
|
||||
isLoading: state.getIn(['user_lists', 'following', props.params.accountId, 'isLoading'], true),
|
||||
blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false),
|
||||
});
|
||||
const mapStateToProps = (state, { params: { acct } }) => {
|
||||
const accountId = state.getIn(['accounts_map', acct]);
|
||||
|
||||
if (!accountId) {
|
||||
return {
|
||||
isLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
accountId,
|
||||
remote: !!(state.getIn(['accounts', accountId, 'acct']) !== state.getIn(['accounts', accountId, 'username'])),
|
||||
remoteUrl: state.getIn(['accounts', accountId, 'url']),
|
||||
isAccount: !!state.getIn(['accounts', accountId]),
|
||||
accountIds: state.getIn(['user_lists', 'following', accountId, 'items']),
|
||||
hasMore: !!state.getIn(['user_lists', 'following', accountId, 'next']),
|
||||
isLoading: state.getIn(['user_lists', 'following', accountId, 'isLoading'], true),
|
||||
blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
|
||||
};
|
||||
};
|
||||
|
||||
const RemoteHint = ({ url }) => (
|
||||
<TimelineHint url={url} resource={<FormattedMessage id='timeline_hint.resources.follows' defaultMessage='Follows' />} />
|
||||
|
@ -41,7 +52,10 @@ export default @connect(mapStateToProps)
|
|||
class Following extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
params: PropTypes.shape({
|
||||
acct: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
accountId: PropTypes.string,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
shouldUpdateScroll: PropTypes.func,
|
||||
accountIds: ImmutablePropTypes.list,
|
||||
|
@ -54,22 +68,34 @@ class Following extends ImmutablePureComponent {
|
|||
multiColumn: PropTypes.bool,
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
if (!this.props.accountIds) {
|
||||
this.props.dispatch(fetchAccount(this.props.params.accountId));
|
||||
this.props.dispatch(fetchFollowing(this.props.params.accountId));
|
||||
_load () {
|
||||
const { accountId, dispatch } = this.props;
|
||||
|
||||
dispatch(fetchFollowing(accountId));
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { params: { acct }, accountId, dispatch } = this.props;
|
||||
|
||||
if (accountId) {
|
||||
this._load();
|
||||
} else {
|
||||
dispatch(lookupAccount(acct));
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) {
|
||||
this.props.dispatch(fetchAccount(nextProps.params.accountId));
|
||||
this.props.dispatch(fetchFollowing(nextProps.params.accountId));
|
||||
componentDidUpdate (prevProps) {
|
||||
const { params: { acct }, accountId, dispatch } = this.props;
|
||||
|
||||
if (prevProps.accountId !== accountId && accountId) {
|
||||
this._load();
|
||||
} else if (prevProps.params.acct !== acct) {
|
||||
dispatch(lookupAccount(acct));
|
||||
}
|
||||
}
|
||||
|
||||
handleLoadMore = debounce(() => {
|
||||
this.props.dispatch(expandFollowing(this.props.params.accountId));
|
||||
this.props.dispatch(expandFollowing(this.props.accountId));
|
||||
}, 300, { leading: true });
|
||||
|
||||
render () {
|
||||
|
|
|
@ -87,7 +87,7 @@ class Content extends ImmutablePureComponent {
|
|||
onMentionClick = (mention, e) => {
|
||||
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(`/accounts/${mention.get('id')}`);
|
||||
this.context.router.history.push(`/@${mention.get('acct')}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,14 +96,14 @@ class Content extends ImmutablePureComponent {
|
|||
|
||||
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(`/timelines/tag/${hashtag}`);
|
||||
this.context.router.history.push(`/tags/${hashtag}`);
|
||||
}
|
||||
}
|
||||
|
||||
onStatusClick = (status, e) => {
|
||||
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(`/statuses/${status.get('id')}`);
|
||||
this.context.router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -82,7 +82,7 @@ class GettingStarted extends ImmutablePureComponent {
|
|||
const { fetchFollowRequests, multiColumn } = this.props;
|
||||
|
||||
if (!multiColumn && window.innerWidth >= NAVIGATION_PANEL_BREAKPOINT) {
|
||||
this.context.router.history.replace('/timelines/home');
|
||||
this.context.router.history.replace('/home');
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -98,15 +98,15 @@ class GettingStarted extends ImmutablePureComponent {
|
|||
if (multiColumn) {
|
||||
navItems.push(
|
||||
<ColumnSubheading key='header-discover' text={intl.formatMessage(messages.discover)} />,
|
||||
<ColumnLink key='community_timeline' icon='users' text={intl.formatMessage(messages.community_timeline)} to='/timelines/public/local' />,
|
||||
<ColumnLink key='public_timeline' icon='globe' text={intl.formatMessage(messages.public_timeline)} to='/timelines/public' />,
|
||||
<ColumnLink key='community_timeline' icon='users' text={intl.formatMessage(messages.community_timeline)} to='/public/local' />,
|
||||
<ColumnLink key='public_timeline' icon='globe' text={intl.formatMessage(messages.public_timeline)} to='/public' />,
|
||||
);
|
||||
|
||||
height += 34 + 48*2;
|
||||
|
||||
if (profile_directory) {
|
||||
navItems.push(
|
||||
<ColumnLink key='directory' icon='address-book' text={intl.formatMessage(messages.profile_directory)} to='/directory' />,
|
||||
<ColumnLink key='directory' icon='address-book' text={intl.formatMessage(messages.profile_directory)} to='/explore' />,
|
||||
);
|
||||
|
||||
height += 48;
|
||||
|
@ -119,7 +119,7 @@ class GettingStarted extends ImmutablePureComponent {
|
|||
height += 34;
|
||||
} else if (profile_directory) {
|
||||
navItems.push(
|
||||
<ColumnLink key='directory' icon='address-book' text={intl.formatMessage(messages.profile_directory)} to='/directory' />,
|
||||
<ColumnLink key='directory' icon='address-book' text={intl.formatMessage(messages.profile_directory)} to='/explore' />,
|
||||
);
|
||||
|
||||
height += 48;
|
||||
|
@ -127,13 +127,13 @@ class GettingStarted extends ImmutablePureComponent {
|
|||
|
||||
if (multiColumn && !columns.find(item => item.get('id') === 'HOME')) {
|
||||
navItems.push(
|
||||
<ColumnLink key='home' icon='home' text={intl.formatMessage(messages.home_timeline)} to='/timelines/home' />,
|
||||
<ColumnLink key='home' icon='home' text={intl.formatMessage(messages.home_timeline)} to='/home' />,
|
||||
);
|
||||
height += 48;
|
||||
}
|
||||
|
||||
navItems.push(
|
||||
<ColumnLink key='direct' icon='envelope' text={intl.formatMessage(messages.direct)} to='/timelines/direct' />,
|
||||
<ColumnLink key='direct' icon='envelope' text={intl.formatMessage(messages.direct)} to='/conversations' />,
|
||||
<ColumnLink key='bookmark' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} to='/bookmarks' />,
|
||||
<ColumnLink key='favourites' icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />,
|
||||
<ColumnLink key='lists' icon='list-ul' text={intl.formatMessage(messages.lists)} to='/lists' />,
|
||||
|
|
|
@ -153,7 +153,7 @@ class HomeTimeline extends React.PureComponent {
|
|||
scrollKey={`home_timeline-${columnId}`}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
timelineId='home'
|
||||
emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage='Your home timeline is empty! Visit {public} or use search to get started and meet other users.' values={{ public: <Link to='/timelines/public'><FormattedMessage id='empty_column.home.public_timeline' defaultMessage='the public timeline' /></Link> }} />}
|
||||
emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage='Your home timeline is empty! Visit {public} or use search to get started and meet other users.' values={{ public: <Link to='/public'><FormattedMessage id='empty_column.home.public_timeline' defaultMessage='the public timeline' /></Link> }} />}
|
||||
shouldUpdateScroll={shouldUpdateScroll}
|
||||
bindToDocument={!multiColumn}
|
||||
/>
|
||||
|
|
|
@ -74,7 +74,7 @@ class Lists extends ImmutablePureComponent {
|
|||
bindToDocument={!multiColumn}
|
||||
>
|
||||
{lists.map(list =>
|
||||
<ColumnLink key={list.get('id')} to={`/timelines/list/${list.get('id')}`} icon='list-ul' text={list.get('title')} />,
|
||||
<ColumnLink key={list.get('id')} to={`/lists/${list.get('id')}`} icon='list-ul' text={list.get('title')} />,
|
||||
)}
|
||||
</ScrollableList>
|
||||
</Column>
|
||||
|
|
|
@ -42,7 +42,7 @@ class FollowRequest extends ImmutablePureComponent {
|
|||
return (
|
||||
<div className='account'>
|
||||
<div className='account__wrapper'>
|
||||
<Permalink key={account.get('id')} className='account__display-name' title={account.get('acct')} href={account.get('url')} to={`/accounts/${account.get('id')}`}>
|
||||
<Permalink key={account.get('id')} className='account__display-name' title={account.get('acct')} href={account.get('url')} to={`/@${account.get('acct')}`}>
|
||||
<div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div>
|
||||
<DisplayName account={account} />
|
||||
</Permalink>
|
||||
|
|
|
@ -68,7 +68,7 @@ class Notification extends ImmutablePureComponent {
|
|||
const { notification } = this.props;
|
||||
|
||||
if (notification.get('status')) {
|
||||
this.context.router.history.push(`/statuses/${notification.get('status')}`);
|
||||
this.context.router.history.push(`/@${notification.getIn(['status', 'account', 'acct'])}/${notification.get('status')}`);
|
||||
} else {
|
||||
this.handleOpenProfile();
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ class Notification extends ImmutablePureComponent {
|
|||
|
||||
handleOpenProfile = () => {
|
||||
const { notification } = this.props;
|
||||
this.context.router.history.push(`/accounts/${notification.getIn(['account', 'id'])}`);
|
||||
this.context.router.history.push(`/@${notification.getIn(['account', 'acct'])}`);
|
||||
}
|
||||
|
||||
handleMention = e => {
|
||||
|
@ -315,7 +315,7 @@ class Notification extends ImmutablePureComponent {
|
|||
const { notification } = this.props;
|
||||
const account = notification.get('account');
|
||||
const displayNameHtml = { __html: account.get('display_name_html') };
|
||||
const link = <bdi><Permalink className='notification__display-name' href={account.get('url')} title={account.get('acct')} to={`/accounts/${account.get('id')}`} dangerouslySetInnerHTML={displayNameHtml} /></bdi>;
|
||||
const link = <bdi><Permalink className='notification__display-name' href={account.get('url')} title={account.get('acct')} to={`/@${account.get('acct')}`} dangerouslySetInnerHTML={displayNameHtml} /></bdi>;
|
||||
|
||||
switch(notification.get('type')) {
|
||||
case 'follow':
|
||||
|
|
|
@ -116,7 +116,7 @@ class Footer extends ImmutablePureComponent {
|
|||
|
||||
const { status } = this.props;
|
||||
|
||||
router.history.push(`/statuses/${status.get('id')}`);
|
||||
router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
|
||||
}
|
||||
|
||||
render () {
|
||||
|
|
|
@ -34,7 +34,7 @@ class Header extends ImmutablePureComponent {
|
|||
|
||||
return (
|
||||
<div className='picture-in-picture__header'>
|
||||
<Link to={`/statuses/${statusId}`} className='picture-in-picture__header__account'>
|
||||
<Link to={`/@${account.get('acct')}/${statusId}`} className='picture-in-picture__header__account'>
|
||||
<Avatar account={account} size={36} />
|
||||
<DisplayName account={account} />
|
||||
</Link>
|
||||
|
|
|
@ -55,7 +55,7 @@ class DetailedStatus extends ImmutablePureComponent {
|
|||
handleAccountClick = (e) => {
|
||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey) && this.context.router) {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
|
||||
this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
|
||||
}
|
||||
|
||||
e.stopPropagation();
|
||||
|
@ -195,7 +195,7 @@ class DetailedStatus extends ImmutablePureComponent {
|
|||
reblogLink = (
|
||||
<React.Fragment>
|
||||
<React.Fragment> · </React.Fragment>
|
||||
<Link to={`/statuses/${status.get('id')}/reblogs`} className='detailed-status__link'>
|
||||
<Link to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/reblogs`} className='detailed-status__link'>
|
||||
<Icon id={reblogIcon} />
|
||||
<span className='detailed-status__reblogs'>
|
||||
<AnimatedNumber value={status.get('reblogs_count')} />
|
||||
|
@ -219,7 +219,7 @@ class DetailedStatus extends ImmutablePureComponent {
|
|||
|
||||
if (this.context.router) {
|
||||
favouriteLink = (
|
||||
<Link to={`/statuses/${status.get('id')}/favourites`} className='detailed-status__link'>
|
||||
<Link to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/favourites`} className='detailed-status__link'>
|
||||
<Icon id='star' />
|
||||
<span className='detailed-status__favorites'>
|
||||
<AnimatedNumber value={status.get('favourites_count')} />
|
||||
|
|
|
@ -396,7 +396,7 @@ class Status extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
handleHotkeyOpenProfile = () => {
|
||||
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
|
||||
this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
|
||||
}
|
||||
|
||||
handleHotkeyToggleHidden = () => {
|
||||
|
|
|
@ -68,7 +68,7 @@ class BoostModal extends ImmutablePureComponent {
|
|||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.props.onClose();
|
||||
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
|
||||
this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -191,7 +191,7 @@ class ColumnsArea extends ImmutablePureComponent {
|
|||
const columnIndex = getIndex(this.context.router.history.location.pathname);
|
||||
|
||||
if (singleColumn) {
|
||||
const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : <Link key='floating-action-button' to='/statuses/new' className='floating-action-button' aria-label={intl.formatMessage(messages.publish)}><Icon id='pencil' /></Link>;
|
||||
const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : <Link key='floating-action-button' to='/publish' className='floating-action-button' aria-label={intl.formatMessage(messages.publish)}><Icon id='pencil' /></Link>;
|
||||
|
||||
const content = columnIndex !== -1 ? (
|
||||
<ReactSwipeableViews key='content' hysteresis={0.2} threshold={15} index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }} disabled={disableSwiping}>
|
||||
|
|
|
@ -6,6 +6,7 @@ import ComposeFormContainer from 'mastodon/features/compose/containers/compose_f
|
|||
import NavigationContainer from 'mastodon/features/compose/containers/navigation_container';
|
||||
import LinkFooter from './link_footer';
|
||||
import { changeComposing } from 'mastodon/actions/compose';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
|
||||
export default @connect()
|
||||
class ComposePanel extends React.PureComponent {
|
||||
|
@ -26,8 +27,10 @@ class ComposePanel extends React.PureComponent {
|
|||
return (
|
||||
<div className='compose-panel' onFocus={this.onFocus}>
|
||||
<SearchContainer openInRoute />
|
||||
<NavigationContainer onClose={this.onBlur} />
|
||||
<ComposeFormContainer singleColumn />
|
||||
|
||||
{me && <NavigationContainer onClose={this.onBlur} />}
|
||||
{me && <ComposeFormContainer singleColumn />}
|
||||
|
||||
<LinkFooter withHotkeys />
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -46,7 +46,7 @@ class ListPanel extends ImmutablePureComponent {
|
|||
<hr />
|
||||
|
||||
{lists.map(list => (
|
||||
<NavLink key={list.get('id')} className='column-link column-link--transparent' strict to={`/timelines/list/${list.get('id')}`}><Icon className='column-link__icon' id='list-ul' fixedWidth />{list.get('title')}</NavLink>
|
||||
<NavLink key={list.get('id')} className='column-link column-link--transparent' strict to={`/lists/${list.get('id')}`}><Icon className='column-link__icon' id='list-ul' fixedWidth />{list.get('title')}</NavLink>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -152,13 +152,6 @@ class MediaModal extends ImmutablePureComponent {
|
|||
}));
|
||||
};
|
||||
|
||||
handleStatusClick = e => {
|
||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(`/statuses/${this.props.statusId}`);
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { media, statusId, intl, onClose } = this.props;
|
||||
const { navigationHidden } = this.state;
|
||||
|
|
|
@ -10,16 +10,16 @@ import TrendsContainer from 'mastodon/features/getting_started/containers/trends
|
|||
|
||||
const NavigationPanel = () => (
|
||||
<div className='navigation-panel'>
|
||||
<NavLink className='column-link column-link--transparent' to='/timelines/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon className='column-link__icon' id='home' fixedWidth /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>
|
||||
<NavLink className='column-link column-link--transparent' to='/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon className='column-link__icon' id='home' fixedWidth /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>
|
||||
<NavLink className='column-link column-link--transparent' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><NotificationsCounterIcon className='column-link__icon' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>
|
||||
<FollowRequestsNavLink />
|
||||
<NavLink className='column-link column-link--transparent' to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon className='column-link__icon' id='users' fixedWidth /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>
|
||||
<NavLink className='column-link column-link--transparent' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon className='column-link__icon' id='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>
|
||||
<NavLink className='column-link column-link--transparent' to='/timelines/direct'><Icon className='column-link__icon' id='envelope' fixedWidth /><FormattedMessage id='navigation_bar.direct' defaultMessage='Direct messages' /></NavLink>
|
||||
<NavLink className='column-link column-link--transparent' to='/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon className='column-link__icon' id='users' fixedWidth /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>
|
||||
<NavLink className='column-link column-link--transparent' exact to='/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon className='column-link__icon' id='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>
|
||||
<NavLink className='column-link column-link--transparent' to='/conversations'><Icon className='column-link__icon' id='envelope' fixedWidth /><FormattedMessage id='navigation_bar.direct' defaultMessage='Direct messages' /></NavLink>
|
||||
<NavLink className='column-link column-link--transparent' to='/favourites'><Icon className='column-link__icon' id='star' fixedWidth /><FormattedMessage id='navigation_bar.favourites' defaultMessage='Favourites' /></NavLink>
|
||||
<NavLink className='column-link column-link--transparent' to='/bookmarks'><Icon className='column-link__icon' id='bookmark' fixedWidth /><FormattedMessage id='navigation_bar.bookmarks' defaultMessage='Bookmarks' /></NavLink>
|
||||
<NavLink className='column-link column-link--transparent' to='/lists'><Icon className='column-link__icon' id='list-ul' fixedWidth /><FormattedMessage id='navigation_bar.lists' defaultMessage='Lists' /></NavLink>
|
||||
{profile_directory && <NavLink className='column-link column-link--transparent' to='/directory'><Icon className='column-link__icon' id='address-book-o' fixedWidth /><FormattedMessage id='getting_started.directory' defaultMessage='Profile directory' /></NavLink>}
|
||||
{profile_directory && <NavLink className='column-link column-link--transparent' to='/explore'><Icon className='column-link__icon' id='address-book-o' fixedWidth /><FormattedMessage id='getting_started.directory' defaultMessage='Profile directory' /></NavLink>}
|
||||
|
||||
<ListPanel />
|
||||
|
||||
|
|
|
@ -8,10 +8,10 @@ import Icon from 'mastodon/components/icon';
|
|||
import NotificationsCounterIcon from './notifications_counter_icon';
|
||||
|
||||
export const links = [
|
||||
<NavLink className='tabs-bar__link' to='/timelines/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon id='home' fixedWidth /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link' to='/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon id='home' fixedWidth /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><NotificationsCounterIcon /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link' to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon id='users' fixedWidth /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon id='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link' to='/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon id='users' fixedWidth /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link' exact to='/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon id='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link optional' to='/search' data-preview-title-id='tabs_bar.search' data-preview-icon='bell' ><Icon id='search' fixedWidth /><FormattedMessage id='tabs_bar.search' defaultMessage='Search' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started' data-preview-title-id='getting_started.heading' data-preview-icon='bars' ><Icon id='bars' fixedWidth /></NavLink>,
|
||||
];
|
||||
|
|
|
@ -147,7 +147,7 @@ class SwitchingColumnsArea extends React.PureComponent {
|
|||
|
||||
render () {
|
||||
const { children, mobile } = this.props;
|
||||
const redirect = mobile ? <Redirect from='/' to='/timelines/home' exact /> : <Redirect from='/' to='/getting-started' exact />;
|
||||
const redirect = mobile ? <Redirect from='/' to='/home' exact /> : <Redirect from='/' to='/getting-started' exact />;
|
||||
|
||||
return (
|
||||
<ColumnsAreaContainer ref={this.setRef} singleColumn={mobile}>
|
||||
|
@ -155,31 +155,29 @@ class SwitchingColumnsArea extends React.PureComponent {
|
|||
{redirect}
|
||||
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
|
||||
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
|
||||
<WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/timelines/public/local' exact component={CommunityTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/timelines/direct' component={DirectTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
|
||||
<WrappedRoute path='/home' component={HomeTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/public' exact component={PublicTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/public/local' exact component={CommunityTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/conversations' component={DirectTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/tags/:id' component={HashtagTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/lists/:id' component={ListTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/notifications' component={Notifications} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/bookmarks' component={BookmarkedStatuses} content={children} />
|
||||
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
|
||||
<WrappedRoute path='/search' component={Search} content={children} />
|
||||
<WrappedRoute path='/directory' component={Directory} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/explore' component={Directory} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/publish' component={Compose} content={children} />
|
||||
|
||||
<WrappedRoute path='/statuses/new' component={Compose} content={children} />
|
||||
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
|
||||
<WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll, withReplies: true }} />
|
||||
<WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/@:acct' exact component={AccountTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/@:acct/with_replies' component={AccountTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll, withReplies: true }} />
|
||||
<WrappedRoute path='/@:acct/followers' component={Followers} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/@:acct/following' component={Following} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/@:acct/media' component={AccountGallery} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/@:acct/tagged/:tag' component={AccountTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/@:acct/:statusId' exact component={Status} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/@:acct/:statusId/reblogs' component={Reblogs} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/@:acct/:statusId/favourites' component={Favourites} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
|
||||
<WrappedRoute path='/follow_requests' component={FollowRequests} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/blocks' component={Blocks} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
|
@ -447,7 +445,7 @@ class UI extends React.PureComponent {
|
|||
}
|
||||
|
||||
handleHotkeyGoToHome = () => {
|
||||
this.context.router.history.push('/timelines/home');
|
||||
this.context.router.history.push('/home');
|
||||
}
|
||||
|
||||
handleHotkeyGoToNotifications = () => {
|
||||
|
@ -455,15 +453,15 @@ class UI extends React.PureComponent {
|
|||
}
|
||||
|
||||
handleHotkeyGoToLocal = () => {
|
||||
this.context.router.history.push('/timelines/public/local');
|
||||
this.context.router.history.push('/public/local');
|
||||
}
|
||||
|
||||
handleHotkeyGoToFederated = () => {
|
||||
this.context.router.history.push('/timelines/public');
|
||||
this.context.router.history.push('/public');
|
||||
}
|
||||
|
||||
handleHotkeyGoToDirect = () => {
|
||||
this.context.router.history.push('/timelines/direct');
|
||||
this.context.router.history.push('/conversations');
|
||||
}
|
||||
|
||||
handleHotkeyGoToStart = () => {
|
||||
|
@ -479,7 +477,7 @@ class UI extends React.PureComponent {
|
|||
}
|
||||
|
||||
handleHotkeyGoToProfile = () => {
|
||||
this.context.router.history.push(`/accounts/${me}`);
|
||||
this.context.router.history.push(`/accounts/${me}`); // FIXME
|
||||
}
|
||||
|
||||
handleHotkeyGoToBlocked = () => {
|
||||
|
|
|
@ -13,9 +13,9 @@ function main() {
|
|||
if (window.history && history.replaceState) {
|
||||
const { pathname, search, hash } = window.location;
|
||||
const path = pathname + search + hash;
|
||||
if (!(/^\/web($|\/)/).test(path)) {
|
||||
history.replaceState(null, document.title, `/web${path}`);
|
||||
}
|
||||
//if (!(/^\/web($|\/)/).test(path)) {
|
||||
// history.replaceState(null, document.title, `/web${path}`);
|
||||
//}
|
||||
}
|
||||
|
||||
ready(() => {
|
||||
|
|
15
app/javascript/mastodon/reducers/accounts_map.js
Normal file
15
app/javascript/mastodon/reducers/accounts_map.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { ACCOUNT_IMPORT, ACCOUNTS_IMPORT } from '../actions/importer';
|
||||
import { Map as ImmutableMap } from 'immutable';
|
||||
|
||||
const initialState = ImmutableMap();
|
||||
|
||||
export default function accountsMap(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case ACCOUNT_IMPORT:
|
||||
return state.set(action.account.acct, action.account.id);
|
||||
case ACCOUNTS_IMPORT:
|
||||
return state.withMutations(map => action.accounts.forEach(account => map.set(account.acct, account.id)));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
|
@ -38,6 +38,7 @@ import missed_updates from './missed_updates';
|
|||
import announcements from './announcements';
|
||||
import markers from './markers';
|
||||
import picture_in_picture from './picture_in_picture';
|
||||
import accounts_map from './accounts_map';
|
||||
|
||||
const reducers = {
|
||||
announcements,
|
||||
|
@ -52,6 +53,7 @@ const reducers = {
|
|||
status_lists,
|
||||
accounts,
|
||||
accounts_counters,
|
||||
accounts_map,
|
||||
statuses,
|
||||
relationships,
|
||||
settings,
|
||||
|
|
|
@ -90,7 +90,7 @@ const handlePush = (event) => {
|
|||
options.tag = notification.id;
|
||||
options.badge = '/badge.png';
|
||||
options.image = notification.status && notification.status.media_attachments.length > 0 && notification.status.media_attachments[0].preview_url || undefined;
|
||||
options.data = { access_token, preferred_locale, id: notification.status ? notification.status.id : notification.account.id, url: notification.status ? `/web/statuses/${notification.status.id}` : `/web/accounts/${notification.account.id}` };
|
||||
options.data = { access_token, preferred_locale, id: notification.status ? notification.status.id : notification.account.id, url: notification.status ? `/@${notification.account.acct}/${notification.status.id}` : `/@${notification.account.acct}` };
|
||||
|
||||
if (notification.status && notification.status.spoiler_text || notification.status.sensitive) {
|
||||
options.data.hiddenBody = htmlToPlainText(notification.status.content);
|
||||
|
|
|
@ -16,71 +16,13 @@
|
|||
= opengraph 'og:type', 'profile'
|
||||
= render 'og', account: @account, url: short_account_url(@account, only_path: false)
|
||||
|
||||
%meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key}
|
||||
= render_initial_state
|
||||
= javascript_pack_tag 'application', crossorigin: 'anonymous'
|
||||
|
||||
= render 'header', account: @account, with_bio: true
|
||||
.app-holder#mastodon{ data: { props: Oj.dump(default_props) } }
|
||||
%noscript
|
||||
= image_pack_tag 'logo.svg', alt: 'Mastodon'
|
||||
|
||||
.grid
|
||||
.column-0
|
||||
.h-feed
|
||||
%data.p-name{ value: "#{@account.username} on #{site_hostname}" }/
|
||||
|
||||
.account__section-headline
|
||||
= active_link_to t('accounts.posts_tab_heading'), short_account_url(@account)
|
||||
= active_link_to t('accounts.posts_with_replies'), short_account_with_replies_url(@account)
|
||||
= active_link_to t('accounts.media'), short_account_media_url(@account)
|
||||
|
||||
- if user_signed_in? && @account.blocking?(current_account)
|
||||
.nothing-here.nothing-here--under-tabs= t('accounts.unavailable')
|
||||
- elsif @statuses.empty?
|
||||
= nothing_here 'nothing-here--under-tabs'
|
||||
- else
|
||||
.activity-stream.activity-stream--under-tabs
|
||||
- if params[:page].to_i.zero?
|
||||
= render partial: 'statuses/status', collection: @pinned_statuses, as: :status, locals: { pinned: true }
|
||||
|
||||
- if @newer_url
|
||||
.entry= link_to_newer @newer_url
|
||||
|
||||
= render partial: 'statuses/status', collection: @statuses, as: :status
|
||||
|
||||
- if @older_url
|
||||
.entry= link_to_older @older_url
|
||||
|
||||
.column-1
|
||||
- if @account.memorial?
|
||||
.memoriam-widget= t('in_memoriam_html')
|
||||
- elsif @account.moved?
|
||||
= render 'moved', account: @account
|
||||
|
||||
= render 'bio', account: @account
|
||||
|
||||
- if @endorsed_accounts.empty? && @account.id == current_account&.id
|
||||
.placeholder-widget= t('accounts.endorsements_hint')
|
||||
- elsif !@endorsed_accounts.empty?
|
||||
.endorsements-widget
|
||||
%h4= t 'accounts.choices_html', name: content_tag(:bdi, display_name(@account, custom_emojify: true))
|
||||
|
||||
- @endorsed_accounts.each do |account|
|
||||
= account_link_to account
|
||||
|
||||
- if @featured_hashtags.empty? && @account.id == current_account&.id
|
||||
.placeholder-widget
|
||||
= t('accounts.featured_tags_hint')
|
||||
= link_to settings_featured_tags_path do
|
||||
= t('featured_tags.add_new')
|
||||
= fa_icon 'chevron-right fw'
|
||||
- else
|
||||
- @featured_hashtags.each do |featured_tag|
|
||||
.directory__tag{ class: params[:tag] == featured_tag.name ? 'active' : nil }
|
||||
= link_to short_account_tag_path(@account, featured_tag.tag) do
|
||||
%h4
|
||||
= fa_icon 'hashtag'
|
||||
= featured_tag.name
|
||||
%small
|
||||
- if featured_tag.last_status_at.nil?
|
||||
= t('accounts.nothing_here')
|
||||
- else
|
||||
%time.formatted{ datetime: featured_tag.last_status_at.iso8601, title: l(featured_tag.last_status_at) }= l featured_tag.last_status_at
|
||||
.trends__item__current= number_to_human featured_tag.statuses_count, strip_insignificant_zeros: true
|
||||
|
||||
= render 'application/sidebar'
|
||||
%div
|
||||
= t('errors.noscript_html', apps_path: 'https://joinmastodon.org/apps')
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.post-follow-actions
|
||||
%div= link_to t('authorize_follow.post_follow.web'), web_url("accounts/#{@resource.id}"), class: 'button button--block'
|
||||
%div= link_to t('authorize_follow.post_follow.web'), short_account_path(@resource.acct), class: 'button button--block'
|
||||
%div= link_to t('authorize_follow.post_follow.return'), ActivityPub::TagManager.instance.url_for(@resource), class: 'button button--block'
|
||||
%div= t('authorize_follow.post_follow.close')
|
||||
|
|
|
@ -10,46 +10,13 @@
|
|||
= opengraph 'og:description', t('directories.explanation')
|
||||
= opengraph 'og:image', File.join(root_url, 'android-chrome-192x192.png')
|
||||
|
||||
.page-header
|
||||
%h1= t('directories.explore_mastodon', title: site_title)
|
||||
%p= t('directories.explanation')
|
||||
%meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key}
|
||||
= render_initial_state
|
||||
= javascript_pack_tag 'application', crossorigin: 'anonymous'
|
||||
|
||||
- if @accounts.empty?
|
||||
= nothing_here
|
||||
- else
|
||||
.directory__list
|
||||
- @accounts.each do |account|
|
||||
.directory__card
|
||||
.directory__card__img
|
||||
= image_tag account.header.url, alt: ''
|
||||
.directory__card__bar
|
||||
= link_to TagManager.instance.url_for(account), class: 'directory__card__bar__name' do
|
||||
.avatar
|
||||
= image_tag account.avatar.url, alt: '', class: 'u-photo'
|
||||
.app-holder#mastodon{ data: { props: Oj.dump(default_props) } }
|
||||
%noscript
|
||||
= image_pack_tag 'logo.svg', alt: 'Mastodon'
|
||||
|
||||
.display-name
|
||||
%bdi
|
||||
%strong.emojify.p-name= display_name(account, custom_emojify: true)
|
||||
%span= acct(account)
|
||||
.directory__card__bar__relationship.account__relationship
|
||||
= minimal_account_action_button(account)
|
||||
|
||||
.directory__card__extra
|
||||
.account__header__content.emojify= Formatter.instance.simplified_format(account, custom_emojify: true)
|
||||
|
||||
.directory__card__extra
|
||||
.accounts-table__count
|
||||
= number_to_human account.statuses_count, strip_insignificant_zeros: true
|
||||
%small= t('accounts.posts', count: account.statuses_count).downcase
|
||||
.accounts-table__count
|
||||
= number_to_human account.followers_count, strip_insignificant_zeros: true
|
||||
%small= t('accounts.followers', count: account.followers_count).downcase
|
||||
.accounts-table__count
|
||||
- if account.last_status_at.present?
|
||||
%time.time-ago{ datetime: account.last_status_at.to_date.iso8601, title: l(account.last_status_at.to_date) }= l account.last_status_at.to_date
|
||||
- else
|
||||
= t('accounts.never_active')
|
||||
|
||||
%small= t('accounts.last_active')
|
||||
|
||||
= paginate @accounts
|
||||
%div
|
||||
= t('errors.noscript_html', apps_path: 'https://joinmastodon.org/apps')
|
||||
|
|
|
@ -5,16 +5,13 @@
|
|||
%meta{ name: 'robots', content: 'noindex' }/
|
||||
= render 'accounts/og', account: @account, url: account_followers_url(@account, only_path: false)
|
||||
|
||||
= render 'accounts/header', account: @account
|
||||
%meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key}
|
||||
= render_initial_state
|
||||
= javascript_pack_tag 'application', crossorigin: 'anonymous'
|
||||
|
||||
- if @account.user_hides_network?
|
||||
.nothing-here= t('accounts.network_hidden')
|
||||
- elsif user_signed_in? && @account.blocking?(current_account)
|
||||
.nothing-here= t('accounts.unavailable')
|
||||
- elsif @follows.empty?
|
||||
= nothing_here
|
||||
- else
|
||||
.card-grid
|
||||
= render partial: 'application/card', collection: @follows.map(&:account), as: :account
|
||||
.app-holder#mastodon{ data: { props: Oj.dump(default_props) } }
|
||||
%noscript
|
||||
= image_pack_tag 'logo.svg', alt: 'Mastodon'
|
||||
|
||||
= paginate @follows
|
||||
%div
|
||||
= t('errors.noscript_html', apps_path: 'https://joinmastodon.org/apps')
|
||||
|
|
|
@ -5,16 +5,13 @@
|
|||
%meta{ name: 'robots', content: 'noindex' }/
|
||||
= render 'accounts/og', account: @account, url: account_followers_url(@account, only_path: false)
|
||||
|
||||
= render 'accounts/header', account: @account
|
||||
%meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key}
|
||||
= render_initial_state
|
||||
= javascript_pack_tag 'application', crossorigin: 'anonymous'
|
||||
|
||||
- if @account.user_hides_network?
|
||||
.nothing-here= t('accounts.network_hidden')
|
||||
- elsif user_signed_in? && @account.blocking?(current_account)
|
||||
.nothing-here= t('accounts.unavailable')
|
||||
- elsif @follows.empty?
|
||||
= nothing_here
|
||||
- else
|
||||
.card-grid
|
||||
= render partial: 'application/card', collection: @follows.map(&:target_account), as: :account
|
||||
.app-holder#mastodon{ data: { props: Oj.dump(default_props) } }
|
||||
%noscript
|
||||
= image_pack_tag 'logo.svg', alt: 'Mastodon'
|
||||
|
||||
= paginate @follows
|
||||
%div
|
||||
= t('errors.noscript_html', apps_path: 'https://joinmastodon.org/apps')
|
||||
|
|
|
@ -42,4 +42,4 @@
|
|||
= link_to a.remote_url, a.remote_url
|
||||
|
||||
%p.status-footer
|
||||
= link_to l(status.created_at), web_url("statuses/#{status.id}")
|
||||
= link_to l(status.created_at), short_account_status_url(status.account.acct, status.id)
|
||||
|
|
|
@ -5,4 +5,4 @@
|
|||
<% end %>
|
||||
<%= raw Formatter.instance.plaintext(status) %>
|
||||
|
||||
<%= raw t('application_mailer.view')%> <%= web_url("statuses/#{status.id}") %>
|
||||
<%= raw t('application_mailer.view')%> <%= short_account_status_url(status.account.acct, status.id) %>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
%tbody
|
||||
%tr
|
||||
%td.button-primary
|
||||
= link_to web_url do
|
||||
= link_to home_url do
|
||||
%span= t 'notification_mailer.digest.action'
|
||||
|
||||
- @notifications.each_with_index do |n, i|
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
<%= raw Formatter.instance.plaintext(notification.target_status) %>
|
||||
|
||||
<%= raw t('application_mailer.view')%> <%= web_url("statuses/#{notification.target_status.id}") %>
|
||||
<%= raw t('application_mailer.view')%> <%= short_account_status_url(notification.target_status.account.acct, notification.target_status.id) %>
|
||||
<% end %>
|
||||
<% if @follows_since > 0 %>
|
||||
|
||||
|
|
|
@ -41,5 +41,5 @@
|
|||
%tbody
|
||||
%tr
|
||||
%td.button-primary
|
||||
= link_to web_url("statuses/#{@status.id}") do
|
||||
= link_to short_account_status_url(status.account.acct, status.id) do
|
||||
%span= t 'application_mailer.view_status'
|
||||
|
|
|
@ -39,5 +39,5 @@
|
|||
%tbody
|
||||
%tr
|
||||
%td.button-primary
|
||||
= link_to web_url("accounts/#{@account.id}") do
|
||||
= link_to short_account_url(@account.acct) do
|
||||
%span= t 'application_mailer.view_profile'
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
<%= raw t('notification_mailer.follow.body', name: @account.acct) %>
|
||||
|
||||
<%= raw t('application_mailer.view')%> <%= web_url("accounts/#{@account.id}") %>
|
||||
<%= raw t('application_mailer.view')%> <%= short_account_url(@account.acct) %>
|
||||
|
|
|
@ -39,5 +39,5 @@
|
|||
%tbody
|
||||
%tr
|
||||
%td.button-primary
|
||||
= link_to web_url("follow_requests") do
|
||||
= link_to follow_requests_url do
|
||||
%span= t 'notification_mailer.follow_request.action'
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
<%= raw t('notification_mailer.follow_request.body', name: @account.acct) %>
|
||||
|
||||
<%= raw t('application_mailer.view')%> <%= web_url("follow_requests") %>
|
||||
<%= raw t('application_mailer.view')%> <%= follow_requests_url %>
|
||||
|
|
|
@ -41,5 +41,5 @@
|
|||
%tbody
|
||||
%tr
|
||||
%td.button-primary
|
||||
= link_to web_url("statuses/#{@status.id}") do
|
||||
= link_to short_account_status_url(@status.account.acct, @status.id) do
|
||||
%span= t 'notification_mailer.mention.action'
|
||||
|
|
|
@ -41,5 +41,5 @@
|
|||
%tbody
|
||||
%tr
|
||||
%td.button-primary
|
||||
= link_to web_url("statuses/#{@status.id}") do
|
||||
= link_to short_account_status_url(@status.account.acct, @status.id) do
|
||||
%span= t 'application_mailer.view_status'
|
||||
|
|
|
@ -3,15 +3,13 @@
|
|||
|
||||
- content_for :header_tags do
|
||||
%meta{ name: 'robots', content: 'noindex' }/
|
||||
= javascript_pack_tag 'about', crossorigin: 'anonymous'
|
||||
%meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key}
|
||||
= render_initial_state
|
||||
= javascript_pack_tag 'application', crossorigin: 'anonymous'
|
||||
|
||||
.page-header
|
||||
%h1= t('about.see_whats_happening')
|
||||
.app-holder.notranslate#mastodon{ data: { props: Oj.dump(default_props) } }
|
||||
%noscript
|
||||
= image_pack_tag 'logo.svg', alt: 'Mastodon'
|
||||
|
||||
- if Setting.show_known_fediverse_at_about_page
|
||||
%p= t('about.browse_public_posts')
|
||||
- else
|
||||
%p= t('about.browse_local_posts')
|
||||
|
||||
#mastodon-timeline{ data: { props: Oj.dump(default_props.merge(local: !Setting.show_known_fediverse_at_about_page)) }}
|
||||
.notranslate#modal-container
|
||||
%div
|
||||
= t('errors.noscript_html', apps_path: 'https://joinmastodon.org/apps')
|
||||
|
|
|
@ -77,4 +77,4 @@
|
|||
|
||||
- if user_signed_in?
|
||||
·
|
||||
= link_to t('statuses.open_in_web'), web_url("statuses/#{status.id}"), class: 'detailed-status__application', target: '_blank'
|
||||
= link_to t('statuses.open_in_web'), short_account_status_url(status.account.acct, status.id), class: 'detailed-status__application', target: '_blank'
|
||||
|
|
|
@ -17,9 +17,13 @@
|
|||
= render 'og_description', activity: @status
|
||||
= render 'og_image', activity: @status, account: @account
|
||||
|
||||
.grid
|
||||
.column-0
|
||||
.activity-stream.h-entry
|
||||
= render partial: 'status', locals: { status: @status, include_threads: true }
|
||||
.column-1
|
||||
= render 'application/sidebar'
|
||||
%meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key}
|
||||
= render_initial_state
|
||||
= javascript_pack_tag 'application', crossorigin: 'anonymous'
|
||||
|
||||
.app-holder#mastodon{ data: { props: Oj.dump(default_props) } }
|
||||
%noscript
|
||||
= image_pack_tag 'logo.svg', alt: 'Mastodon'
|
||||
|
||||
%div
|
||||
= t('errors.noscript_html', apps_path: 'https://joinmastodon.org/apps')
|
||||
|
|
|
@ -5,12 +5,15 @@
|
|||
%meta{ name: 'robots', content: 'noindex' }/
|
||||
%link{ rel: 'alternate', type: 'application/rss+xml', href: tag_url(@tag, format: 'rss') }/
|
||||
|
||||
= javascript_pack_tag 'about', crossorigin: 'anonymous'
|
||||
= render 'og'
|
||||
|
||||
.page-header
|
||||
%h1= "##{@tag.name}"
|
||||
%p= t('about.about_hashtag_html', hashtag: @tag.name)
|
||||
%meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key}
|
||||
= render_initial_state
|
||||
= javascript_pack_tag 'application', crossorigin: 'anonymous'
|
||||
|
||||
#mastodon-timeline{ data: { props: Oj.dump(default_props.merge(hashtag: @tag.name, local: @local)) }}
|
||||
.notranslate#modal-container
|
||||
.app-holder.notranslate#mastodon{ data: { props: Oj.dump(default_props) } }
|
||||
%noscript
|
||||
= image_pack_tag 'logo.svg', alt: 'Mastodon'
|
||||
|
||||
%div
|
||||
= t('errors.noscript_html', apps_path: 'https://joinmastodon.org/apps')
|
||||
|
|
|
@ -114,7 +114,7 @@
|
|||
%tbody
|
||||
%tr
|
||||
%td.button-primary
|
||||
= link_to web_url do
|
||||
= link_to home_url do
|
||||
%span= t 'user_mailer.welcome.final_action'
|
||||
|
||||
%table.email-table{ cellspacing: 0, cellpadding: 0 }
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
<%= t 'user_mailer.welcome.final_step' %>
|
||||
|
||||
=> <%= web_url %>
|
||||
=> <%= home_url %>
|
||||
|
||||
---
|
||||
|
||||
|
|
|
@ -5,6 +5,25 @@ require 'sidekiq-scheduler/web'
|
|||
|
||||
Sidekiq::Web.set :session_secret, Rails.application.secrets[:secret_key_base]
|
||||
|
||||
# These paths do not have special server-side controllers and must load the web app only
|
||||
APP_PATHS = %w(
|
||||
/home
|
||||
/getting-started
|
||||
/keyboard-shortcuts
|
||||
/conversations
|
||||
/lists/(*any)
|
||||
/notifications
|
||||
/favourites
|
||||
/bookmarks
|
||||
/pinned
|
||||
/search
|
||||
/publish
|
||||
/follow_requests
|
||||
/blocks
|
||||
/domain_blocks
|
||||
/mutes
|
||||
).freeze
|
||||
|
||||
Rails.application.routes.draw do
|
||||
root 'home#index'
|
||||
|
||||
|
@ -76,8 +95,6 @@ Rails.application.routes.draw do
|
|||
|
||||
resources :followers, only: [:index], controller: :follower_accounts
|
||||
resources :following, only: [:index], controller: :following_accounts
|
||||
resource :follow, only: [:create], controller: :account_follow
|
||||
resource :unfollow, only: [:create], controller: :account_unfollow
|
||||
|
||||
resource :outbox, only: [:show], module: :activitypub
|
||||
resource :inbox, only: [:create], module: :activitypub
|
||||
|
@ -92,6 +109,8 @@ Rails.application.routes.draw do
|
|||
get '/@:username/with_replies', to: 'accounts#show', as: :short_account_with_replies
|
||||
get '/@:username/media', to: 'accounts#show', as: :short_account_media
|
||||
get '/@:username/tagged/:tag', to: 'accounts#show', as: :short_account_tag
|
||||
get '/@:username/followers', to: 'follower_accounts#index', as: :short_account_followers
|
||||
get '/@:username/following', to: 'following_accounts#index', as: :short_account_following
|
||||
get '/@:account_username/:id', to: 'statuses#show', as: :short_account_status
|
||||
get '/@:account_username/:id/embed', to: 'statuses#embed', as: :embed_short_account_status
|
||||
|
||||
|
@ -99,7 +118,6 @@ Rails.application.routes.draw do
|
|||
post '/interact/:id', to: 'remote_interaction#create'
|
||||
|
||||
get '/explore', to: 'directories#index', as: :explore
|
||||
get '/explore/:id', to: 'directories#show', as: :explore_hashtag
|
||||
|
||||
get '/settings', to: redirect('/settings/profile')
|
||||
|
||||
|
@ -181,6 +199,7 @@ Rails.application.routes.draw do
|
|||
resource :relationships, only: [:show, :update]
|
||||
|
||||
get '/public', to: 'public_timelines#show', as: :public_timeline
|
||||
get '/public/local', to: 'public_timelines#show', as: :local_public_timeline
|
||||
get '/media_proxy/:id/(*any)', to: 'media_proxy#show', as: :media_proxy
|
||||
|
||||
resource :authorize_interaction, only: [:show, :create]
|
||||
|
@ -431,6 +450,7 @@ Rails.application.routes.draw do
|
|||
get :verify_credentials, to: 'credentials#show'
|
||||
patch :update_credentials, to: 'credentials#update'
|
||||
resource :search, only: :show, controller: :search
|
||||
resource :lookup, only: :show, controller: :lookup
|
||||
resources :relationships, only: :index
|
||||
end
|
||||
|
||||
|
@ -515,7 +535,9 @@ Rails.application.routes.draw do
|
|||
end
|
||||
end
|
||||
|
||||
get '/web/(*any)', to: 'home#index', as: :web
|
||||
APP_PATHS.each do |app_path|
|
||||
get app_path, to: 'home#index'
|
||||
end
|
||||
|
||||
get '/about', to: 'about#show'
|
||||
get '/about/more', to: 'about#more'
|
||||
|
|
Loading…
Reference in a new issue