Add personal visibility feature
This commit is contained in:
parent
d73a197f61
commit
ec075c3ccb
|
@ -31,10 +31,11 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
|
|||
def cached_account_statuses
|
||||
statuses = truthy_param?(:pinned) ? pinned_scope : permitted_account_statuses
|
||||
|
||||
statuses.merge!(only_media_scope) if truthy_param?(:only_media)
|
||||
statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies)
|
||||
statuses.merge!(no_reblogs_scope) if truthy_param?(:exclude_reblogs)
|
||||
statuses.merge!(hashtag_scope) if params[:tagged].present?
|
||||
statuses.merge!(only_media_scope) if truthy_param?(:only_media)
|
||||
statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies)
|
||||
statuses.merge!(no_reblogs_scope) if truthy_param?(:exclude_reblogs)
|
||||
statuses.merge!(hashtag_scope) if params[:tagged].present?
|
||||
statuses.merge!(no_personal_scope) if current_user&.setting_hide_personal_from_account
|
||||
|
||||
cache_collection_paginated_by_id(
|
||||
statuses,
|
||||
|
@ -78,6 +79,10 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
|
|||
end
|
||||
end
|
||||
|
||||
def no_personal_scope
|
||||
Status.without_personal_visibility
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit, :only_media, :exclude_replies, :compact).permit(:limit, :only_media, :exclude_replies, :compact).merge(core_params)
|
||||
end
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Timelines::PersonalController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: [:show]
|
||||
before_action :require_user!, only: [:show]
|
||||
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
||||
|
||||
def show
|
||||
@statuses = load_statuses
|
||||
|
||||
if compact?
|
||||
render json: CompactStatusesPresenter.new(statuses: @statuses), serializer: REST::CompactStatusesSerializer
|
||||
else
|
||||
account_ids = @statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq
|
||||
|
||||
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), account_relationships: AccountRelationshipsPresenter.new(account_ids, current_user&.account_id), status: account_home_feed.regenerating? ? 206 : 200
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_statuses
|
||||
cached_personal_statuses
|
||||
end
|
||||
|
||||
def cached_personal_statuses
|
||||
cache_collection personal_statuses, Status
|
||||
end
|
||||
|
||||
def personal_statuses
|
||||
personal_feed.get(
|
||||
limit_param(DEFAULT_STATUSES_LIMIT),
|
||||
params[:max_id],
|
||||
params[:since_id],
|
||||
params[:min_id],
|
||||
)
|
||||
end
|
||||
|
||||
def personal_feed
|
||||
PersonalFeed.new(current_account)
|
||||
end
|
||||
|
||||
def compact?
|
||||
truthy_param?(:compact)
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:local, :limit).permit(:local, :limit).merge(core_params)
|
||||
end
|
||||
|
||||
def next_path
|
||||
api_v1_timelines_home_url pagination_params(max_id: pagination_max_id)
|
||||
end
|
||||
|
||||
def prev_path
|
||||
api_v1_timelines_home_url pagination_params(min_id: pagination_since_id)
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@statuses.last.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@statuses.first.id
|
||||
end
|
||||
end
|
|
@ -72,6 +72,7 @@ class Settings::PreferencesController < Settings::BaseController
|
|||
:setting_show_target,
|
||||
:setting_enable_federated_timeline,
|
||||
:setting_enable_limited_timeline,
|
||||
:setting_enable_personal_timeline,
|
||||
:setting_enable_local_timeline,
|
||||
:setting_enable_reaction,
|
||||
:setting_compact_reaction,
|
||||
|
@ -113,6 +114,9 @@ class Settings::PreferencesController < Settings::BaseController
|
|||
:setting_disable_account_delete,
|
||||
:setting_prohibited_words,
|
||||
:setting_disable_relative_time,
|
||||
:setting_hide_direct_from_timeline,
|
||||
:setting_hide_personal_from_timeline,
|
||||
:setting_hide_personal_from_account,
|
||||
setting_prohibited_visibilities: [],
|
||||
notification_emails: %i(follow follow_request reblog favourite emoji_reaction status_reference mention digest report pending_account trending_tag),
|
||||
interactions: %i(must_be_follower must_be_following must_be_following_dm must_be_dm_to_send_email must_be_following_reference)
|
||||
|
|
|
@ -106,6 +106,8 @@ module ApplicationHelper
|
|||
fa_icon('user-circle', title: I18n.t('statuses.visibilities.limited'))
|
||||
elsif status.direct_visibility?
|
||||
fa_icon('envelope', title: I18n.t('statuses.visibilities.direct'))
|
||||
elsif status.personal_visibility?
|
||||
fa_icon('book', title: I18n.t('statuses.visibilities.personal'))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -205,7 +207,7 @@ module ApplicationHelper
|
|||
text: [params[:title], params[:text], params[:url]].compact.join(' '),
|
||||
}
|
||||
|
||||
permit_visibilities = %w(public unlisted private mutual direct)
|
||||
permit_visibilities = %w(public unlisted private mutual direct personal)
|
||||
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]
|
||||
|
|
|
@ -207,14 +207,14 @@ export const getDateTimeFromText = (value, origin = new Date()) => {
|
|||
if (value.length >= 7) {
|
||||
const isoDateTime = parseISO(value);
|
||||
|
||||
if (isoDateTime.toString() === "Invalid Date") {
|
||||
if (isoDateTime.toString() === 'Invalid Date') {
|
||||
return null;
|
||||
} else {
|
||||
return isoDateTime;
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
return null;
|
||||
})();
|
||||
|
||||
return {
|
||||
|
@ -304,7 +304,7 @@ export function submitCompose(routerHistory) {
|
|||
}
|
||||
};
|
||||
|
||||
if (homeVisibilities.length == 0 || homeVisibilities.includes(response.data.visibility)) {
|
||||
if (homeVisibilities.length || homeVisibilities.includes(response.data.visibility)) {
|
||||
insertIfOnline('home');
|
||||
}
|
||||
|
||||
|
@ -312,6 +312,10 @@ export function submitCompose(routerHistory) {
|
|||
insertIfOnline('limited');
|
||||
}
|
||||
|
||||
if (['personal'].includes(response.data.visibility)) {
|
||||
insertIfOnline('personal');
|
||||
}
|
||||
|
||||
if (response.data.in_reply_to_id === null && response.data.visibility === 'public') {
|
||||
if (enableFederatedTimeline) {
|
||||
insertIfOnline('public');
|
||||
|
|
|
@ -63,7 +63,7 @@ export function updateTimeline(timeline, status, accept) {
|
|||
const limitedVisibilities = getLimitedVisibilities(getState());
|
||||
|
||||
if (timeline === 'home') {
|
||||
if (homeVisibilities.length == 0 || homeVisibilities.includes(visibility)) {
|
||||
if (homeVisibilities.length || homeVisibilities.includes(visibility)) {
|
||||
insertTimeline('home');
|
||||
dispatch(submitMarkers());
|
||||
}
|
||||
|
@ -71,6 +71,10 @@ export function updateTimeline(timeline, status, accept) {
|
|||
if (limitedVisibilities.includes(visibility)) {
|
||||
insertTimeline('limited');
|
||||
}
|
||||
|
||||
if (visibility === 'personal') {
|
||||
insertTimeline('personal');
|
||||
}
|
||||
} else {
|
||||
insertTimeline(timeline);
|
||||
}
|
||||
|
@ -177,8 +181,9 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
|
|||
};
|
||||
};
|
||||
|
||||
export const expandHomeTimeline = ({ maxId, visibilities } = {}, done = noOp) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId, visibilities: visibilities}, done);
|
||||
export const expandLimitedTimeline = ({ maxId, visibilities } = {}, done = noOp) => expandTimeline('limited', '/api/v1/timelines/home', { max_id: maxId, visibilities: visibilities}, done);
|
||||
export const expandHomeTimeline = ({ maxId, visibilities } = {}, done = noOp) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId, visibilities: visibilities }, done);
|
||||
export const expandLimitedTimeline = ({ maxId, visibilities } = {}, done = noOp) => expandTimeline('limited', '/api/v1/timelines/home', { max_id: maxId, visibilities: visibilities }, done);
|
||||
export const expandPersonalTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('personal', '/api/v1/timelines/personal', { max_id: maxId}, done);
|
||||
export const expandPublicTimeline = ({ maxId, onlyMedia, withoutMedia, withoutBot, onlyRemote } = {}, done = noOp) => expandTimeline(`public${onlyRemote ? ':remote' : ''}${withoutBot ? ':nobot' : ':bot'}${withoutMedia ? ':nomedia' : ''}${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { remote: !!onlyRemote, max_id: maxId, only_media: !!onlyMedia, without_media: !!withoutMedia, without_bot: !!withoutBot }, done);
|
||||
export const expandDomainTimeline = (domain, { maxId, onlyMedia, withoutMedia, withoutBot } = {}, done = noOp) => expandTimeline(`domain${withoutBot ? ':nobot' : ':bot'}${withoutMedia ? ':nomedia' : ''}${onlyMedia ? ':media' : ''}:${domain}`, '/api/v1/timelines/public', { local: false, domain: domain, max_id: maxId, only_media: !!onlyMedia, without_media: !!withoutMedia, without_bot: !!withoutBot }, done);
|
||||
export const expandGroupTimeline = (id, { maxId, onlyMedia, withoutMedia, tagged } = {}, done = noOp) => expandTimeline(`group:${id}${withoutMedia ? ':nomedia' : ''}${onlyMedia ? ':media' : ''}${tagged ? `:${tagged}` : ''}`, `/api/v1/timelines/group/${id}`, { max_id: maxId, only_media: !!onlyMedia, without_media: !!withoutMedia, tagged: tagged }, done);
|
||||
|
|
|
@ -84,6 +84,7 @@ const messages = defineMessages({
|
|||
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
|
||||
mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual-followers-only' },
|
||||
personal_short: { id: 'privacy.personal.short', defaultMessage: 'Personal' },
|
||||
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Circle' },
|
||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
|
||||
mark_ancestor: { id: 'thread_mark.ancestor', defaultMessage: 'Has reference' },
|
||||
|
@ -420,6 +421,7 @@ class Status extends ImmutablePureComponent {
|
|||
'mutual': { icon: 'exchange', text: intl.formatMessage(messages.mutual_short) },
|
||||
'limited': { icon: 'user-circle', text: intl.formatMessage(messages.limited_short) },
|
||||
'direct': { icon: 'envelope', text: intl.formatMessage(messages.direct_short) },
|
||||
'personal': { icon: 'book', text: intl.formatMessage(messages.personal_short) },
|
||||
};
|
||||
|
||||
if (hidden) {
|
||||
|
|
|
@ -473,9 +473,9 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
<IconButton className='status__action-bar-button' disabled={expired} title={intl.formatMessage(messages.share)} icon='share-alt' onClick={this.handleShareClick} />
|
||||
);
|
||||
|
||||
const referenceDisabled = expired || !referenced && referenceCountLimit || ['limited', 'direct'].includes(status.get('visibility'));
|
||||
const referenceDisabled = expired || !referenced && referenceCountLimit || ['limited', 'direct', 'personal'].includes(status.get('visibility'));
|
||||
|
||||
const reactionsCounter = compactReaction && contextType != 'thread' && status.get('emoji_reactions_count') > 0 ? status.get('emoji_reactions_count') : undefined;
|
||||
const reactionsCounter = compactReaction && contextType !== 'thread' && status.get('emoji_reactions_count') > 0 ? status.get('emoji_reactions_count') : undefined;
|
||||
|
||||
return (
|
||||
<div className='status__action-bar'>
|
||||
|
|
|
@ -19,6 +19,8 @@ const messages = defineMessages({
|
|||
private_long: { id: 'privacy.private.long', defaultMessage: 'Visible for followers only' },
|
||||
mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutuals-followers-only' },
|
||||
mutual_long: { id: 'privacy.mutual.long', defaultMessage: 'Visible for mutual followers only (Supported servers only)' },
|
||||
personal_short: { id: 'privacy.personal.short', defaultMessage: 'Personal' },
|
||||
personal_long: { id: 'privacy.personal.long', defaultMessage: 'Visible for personal only' },
|
||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
|
||||
direct_long: { id: 'privacy.direct.long', defaultMessage: 'Visible for mentioned users only' },
|
||||
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Circle' },
|
||||
|
@ -249,8 +251,9 @@ class PrivacyDropdown extends React.PureComponent {
|
|||
...!this.props.noDirect && [
|
||||
{ icon: 'user-circle', value: 'limited', text: formatMessage(messages.limited_short), meta: formatMessage(messages.limited_long) },
|
||||
{ icon: 'envelope', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
|
||||
{ icon: 'book', value: 'personal', text: formatMessage(messages.personal_short), meta: formatMessage(messages.personal_long) },
|
||||
],
|
||||
].filter(option => !prohibitedVisibilities?.includes(option.value));
|
||||
].filter(option => option && !prohibitedVisibilities?.includes(option.value));
|
||||
}
|
||||
|
||||
render () {
|
||||
|
|
|
@ -7,7 +7,7 @@ import { connect } from 'react-redux';
|
|||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { me, profile_directory, showTrends, enableLimitedTimeline, enableFederatedTimeline, enableLocalTimeline, enableEmptyColumn, defaultColumnWidth } from '../../initial_state';
|
||||
import { me, profile_directory, showTrends, enableLimitedTimeline, enablePersonalTimeline, enableFederatedTimeline, enableLocalTimeline, enableEmptyColumn, defaultColumnWidth } from '../../initial_state';
|
||||
import { fetchFollowRequests } from 'mastodon/actions/accounts';
|
||||
import { fetchFavouriteDomains } from 'mastodon/actions/favourite_domains';
|
||||
import { fetchFavouriteTags } from 'mastodon/actions/favourite_tags';
|
||||
|
@ -38,6 +38,7 @@ const messages = defineMessages({
|
|||
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
|
||||
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' },
|
||||
limited_timeline: { id: 'navigation_bar.limited_timeline', defaultMessage: 'Limited home' },
|
||||
personal_timeline: { id: 'navigation_bar.personal_timeline', defaultMessage: 'Personal' },
|
||||
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
|
||||
circles: { id: 'navigation_bar.circles', defaultMessage: 'Circles' },
|
||||
discover: { id: 'navigation_bar.discover', defaultMessage: 'Discover' },
|
||||
|
@ -122,7 +123,7 @@ class GettingStarted extends ImmutablePureComponent {
|
|||
|
||||
render () {
|
||||
const { intl, myAccount, columns, multiColumn, unreadFollowRequests, lists, favourite_domains, favourite_tags, columnWidth } = this.props;
|
||||
|
||||
|
||||
const navItems = [];
|
||||
let height = (multiColumn) ? 0 : 60;
|
||||
|
||||
|
@ -178,7 +179,7 @@ class GettingStarted extends ImmutablePureComponent {
|
|||
height += 34 + 48*2;
|
||||
|
||||
navItems.push(
|
||||
<ColumnSubheading key='header-personal' text={intl.formatMessage(messages.personal)} />
|
||||
<ColumnSubheading key='header-personal' text={intl.formatMessage(messages.personal)} />,
|
||||
);
|
||||
|
||||
height += 34;
|
||||
|
@ -226,6 +227,13 @@ class GettingStarted extends ImmutablePureComponent {
|
|||
height += 48;
|
||||
}
|
||||
|
||||
if (enablePersonalTimeline && multiColumn && !columns.find(item => item.get('id') === 'PERSONAL')) {
|
||||
navItems.push(
|
||||
<ColumnLink key='personal_timeline' icon='lock' text={intl.formatMessage(messages.personal_timeline)} to='/timelines/personal' />,
|
||||
);
|
||||
height += 48;
|
||||
}
|
||||
|
||||
navItems.push(
|
||||
<ColumnLink key='direct' icon='envelope' text={intl.formatMessage(messages.direct)} to='/timelines/direct' />,
|
||||
<ColumnLink key='bookmark' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} to='/bookmarks' />,
|
||||
|
|
|
@ -44,6 +44,10 @@ class ColumnSettings extends React.PureComponent {
|
|||
<div className='column-settings__row'>
|
||||
<SettingToggle prefix='home_timeline' settings={settings} settingPath={['shows', 'direct']} onChange={onChangeClear} label={<FormattedMessage id='home.column_settings.show_direct' defaultMessage='Show direct' />} />
|
||||
</div>
|
||||
|
||||
<div className='column-settings__row'>
|
||||
<SettingToggle prefix='home_timeline' settings={settings} settingPath={['shows', 'personal']} onChange={onChangeClear} label={<FormattedMessage id='home.column_settings.show_personal' defaultMessage='Show personal' />} />
|
||||
</div>
|
||||
</Fragment>}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -42,6 +42,10 @@ class ColumnSettings extends React.PureComponent {
|
|||
<div className='column-settings__row'>
|
||||
<SettingToggle prefix='limited_timeline' settings={settings} settingPath={['shows', 'direct']} onChange={onChangeClear} label={<FormattedMessage id='limited.column_settings.show_direct' defaultMessage='Show direct' />} />
|
||||
</div>
|
||||
|
||||
<div className='column-settings__row'>
|
||||
<SettingToggle prefix='limited_timeline' settings={settings} settingPath={['shows', 'personal']} onChange={onChangeClear} label={<FormattedMessage id='limited.column_settings.show_personal' defaultMessage='Show personal' />} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { injectIntl, FormattedMessage } from 'react-intl';
|
||||
import SettingToggle from '../../notifications/components/setting_toggle';
|
||||
|
||||
export default @injectIntl
|
||||
class ColumnSettings extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
settings: ImmutablePropTypes.map.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onChangeClear: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { settings, onChange } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span className='column-settings__section'><FormattedMessage id='personal.column_settings.basic' defaultMessage='Basic' /></span>
|
||||
|
||||
<div className='column-settings__row'>
|
||||
<SettingToggle prefix='personal_timeline' settings={settings} settingPath={['shows', 'reply']} onChange={onChange} label={<FormattedMessage id='personal.column_settings.show_replies' defaultMessage='Show replies' />} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import { connect } from 'react-redux';
|
||||
import ColumnSettings from '../components/column_settings';
|
||||
import { changeSetting, saveSettings } from '../../../actions/settings';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
settings: state.getIn(['settings', 'personal']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
||||
onChange (key, checked) {
|
||||
dispatch(changeSetting(['personal', ...key], checked));
|
||||
},
|
||||
|
||||
onSave () {
|
||||
dispatch(saveSettings());
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings);
|
|
@ -0,0 +1,120 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { expandPersonalTimeline } from '../../actions/timelines';
|
||||
import PropTypes from 'prop-types';
|
||||
import StatusListContainer from '../ui/containers/status_list_container';
|
||||
import Column from '../../components/column';
|
||||
import ColumnHeader from '../../components/column_header';
|
||||
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import ColumnSettingsContainer from './containers/column_settings_container';
|
||||
import { defaultColumnWidth } from 'mastodon/initial_state';
|
||||
import { changeSetting } from '../../actions/settings';
|
||||
import { changeColumnParams } from '../../actions/columns';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'column.personal', defaultMessage: 'Personal' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, { columnId }) => {
|
||||
const uuid = columnId;
|
||||
const columns = state.getIn(['settings', 'columns']);
|
||||
const index = columns.findIndex(c => c.get('uuid') === uuid);
|
||||
const columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'personal', 'columnWidth']);
|
||||
|
||||
return {
|
||||
hasUnread: state.getIn(['timelines', 'personal', 'unread']) > 0,
|
||||
columnWidth: columnWidth ?? defaultColumnWidth,
|
||||
};
|
||||
};
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class PersonalTimeline extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
hasUnread: PropTypes.bool,
|
||||
columnId: PropTypes.string,
|
||||
multiColumn: PropTypes.bool,
|
||||
columnWidth: PropTypes.string,
|
||||
};
|
||||
|
||||
handlePin = () => {
|
||||
const { columnId, dispatch } = this.props;
|
||||
|
||||
if (columnId) {
|
||||
dispatch(removeColumn(columnId));
|
||||
} else {
|
||||
dispatch(addColumn('PERSONAL', {}));
|
||||
}
|
||||
}
|
||||
|
||||
handleMove = (dir) => {
|
||||
const { columnId, dispatch } = this.props;
|
||||
dispatch(moveColumn(columnId, dir));
|
||||
}
|
||||
|
||||
handleHeaderClick = () => {
|
||||
this.column.scrollTop();
|
||||
}
|
||||
|
||||
setRef = c => {
|
||||
this.column = c;
|
||||
}
|
||||
|
||||
handleLoadMore = maxId => {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(expandPersonalTimeline({ maxId }));
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(expandPersonalTimeline({}));
|
||||
}
|
||||
|
||||
handleWidthChange = (value) => {
|
||||
const { columnId, dispatch } = this.props;
|
||||
|
||||
if (columnId) {
|
||||
dispatch(changeColumnParams(columnId, 'columnWidth', value));
|
||||
} else {
|
||||
dispatch(changeSetting(['personal', 'columnWidth'], value));
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { intl, hasUnread, columnId, multiColumn, columnWidth } = this.props;
|
||||
const pinned = !!columnId;
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)} columnWidth={columnWidth}>
|
||||
<ColumnHeader
|
||||
icon='lock'
|
||||
active={hasUnread}
|
||||
title={intl.formatMessage(messages.title)}
|
||||
onPin={this.handlePin}
|
||||
onMove={this.handleMove}
|
||||
onClick={this.handleHeaderClick}
|
||||
pinned={pinned}
|
||||
multiColumn={multiColumn}
|
||||
columnWidth={columnWidth}
|
||||
onWidthChange={this.handleWidthChange}
|
||||
/>
|
||||
|
||||
<StatusListContainer
|
||||
trackScroll={!pinned}
|
||||
scrollKey={`personal_timeline-${columnId}`}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
timelineId='personal'
|
||||
emptyMessage={<FormattedMessage id='empty_column.personal' defaultMessage='Personal posts unavailable' />}
|
||||
bindToDocument={!multiColumn}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -247,7 +247,7 @@ class Footer extends ImmutablePureComponent {
|
|||
reblogTitle = intl.formatMessage(messages.cannot_reblog);
|
||||
}
|
||||
|
||||
const referenceDisabled = expired || !referenced && referenceCountLimit || ['limited', 'direct'].includes(status.get('visibility'));
|
||||
const referenceDisabled = expired || !referenced && referenceCountLimit || ['limited', 'direct', 'personal'].includes(status.get('visibility'));
|
||||
|
||||
return (
|
||||
<div className='picture-in-picture__footer'>
|
||||
|
|
|
@ -419,7 +419,7 @@ class ActionBar extends React.PureComponent {
|
|||
reblogTitle = intl.formatMessage(messages.cannot_reblog);
|
||||
}
|
||||
|
||||
const referenceDisabled = expired || !referenced && referenceCountLimit || ['limited', 'direct'].includes(status.get('visibility'));
|
||||
const referenceDisabled = expired || !referenced && referenceCountLimit || ['limited', 'direct', 'personal'].includes(status.get('visibility'));
|
||||
|
||||
return (
|
||||
<div className='detailed-status__action-bar'>
|
||||
|
|
|
@ -25,6 +25,7 @@ const messages = defineMessages({
|
|||
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
|
||||
mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual-followers-only' },
|
||||
personal_short: { id: 'privacy.personal.short', defaultMessage: 'Personal' },
|
||||
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Circle' },
|
||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
|
||||
});
|
||||
|
@ -357,6 +358,7 @@ class DetailedStatus extends ImmutablePureComponent {
|
|||
'mutual': { icon: 'exchange', text: intl.formatMessage(messages.mutual_short) },
|
||||
'limited': { icon: 'user-circle', text: intl.formatMessage(messages.limited_short) },
|
||||
'direct': { icon: 'envelope', text: intl.formatMessage(messages.direct_short) },
|
||||
'personal': { icon: 'book', text: intl.formatMessage(messages.personal_short) },
|
||||
};
|
||||
|
||||
const visibilityIcon = visibilityIconInfo[status.get('visibility')];
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
|
@ -25,6 +25,7 @@ import {
|
|||
HashtagTimeline,
|
||||
DirectTimeline,
|
||||
LimitedTimeline,
|
||||
PersonalTimeline,
|
||||
FavouritedStatuses,
|
||||
BookmarkedStatuses,
|
||||
ListTimeline,
|
||||
|
@ -56,6 +57,7 @@ const componentMap = {
|
|||
'HASHTAG': HashtagTimeline,
|
||||
'DIRECT': DirectTimeline,
|
||||
'LIMITED': LimitedTimeline,
|
||||
'PERSONAL': PersonalTimeline,
|
||||
'FAVOURITES': FavouritedStatuses,
|
||||
'BOOKMARKS': BookmarkedStatuses,
|
||||
'LIST': ListTimeline,
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import { NavLink, withRouter } from 'react-router-dom';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import { profile_directory, showTrends, enableLimitedTimeline, enableFederatedTimeline, enableLocalTimeline } from 'mastodon/initial_state';
|
||||
import { profile_directory, showTrends, enableLimitedTimeline, enableFederatedTimeline, enableLocalTimeline, enablePersonalTimeline } from 'mastodon/initial_state';
|
||||
import NotificationsCounterIcon from './notifications_counter_icon';
|
||||
import FollowRequestsNavLink from './follow_requests_nav_link';
|
||||
import ListPanel from './list_panel';
|
||||
|
@ -14,6 +14,7 @@ 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>
|
||||
{enableLimitedTimeline && <NavLink className='column-link column-link--transparent' to='/timelines/limited' data-preview-title-id='column.limited' data-preview-icon='lock' ><Icon className='column-link__icon' id='lock' fixedWidth /><FormattedMessage id='navigation_bar.limited_timeline' defaultMessage='Limited home' /></NavLink>}
|
||||
{enablePersonalTimeline && <NavLink className='column-link column-link--transparent' to='/timelines/personal' data-preview-title-id='column.personal' data-preview-icon='book' ><Icon className='column-link__icon' id='book' fixedWidth /><FormattedMessage id='navigation_bar.personal_timeline' defaultMessage='Personal' /></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 />
|
||||
{enableLocalTimeline && <NavLink className='column-link column-link--transparent' exact 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>}
|
||||
|
|
|
@ -6,7 +6,7 @@ import { debounce, memoize } from 'lodash';
|
|||
import { isUserTouching } from '../../../is_mobile';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import NotificationsCounterIcon from './notifications_counter_icon';
|
||||
import { place_tab_bar_at_bottom, show_tab_bar_label, enableLimitedTimeline, enableFederatedTimeline, enableLocalTimeline } from 'mastodon/initial_state';
|
||||
import { place_tab_bar_at_bottom, show_tab_bar_label, enableLimitedTimeline, enableFederatedTimeline, enableLocalTimeline, enablePersonalTimeline } from 'mastodon/initial_state';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
|
@ -14,6 +14,7 @@ import classNames from 'classnames';
|
|||
|
||||
const link_home = <NavLink className='tabs-bar__link' to='/timelines/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon id='home' fixedWidth /><span className='tabs-bar__link__full-label'><FormattedMessage id='tabs_bar.home' defaultMessage='Home' children={msg=> <>{msg}</>} /></span><span className='tabs-bar__link__short-label'><FormattedMessage id='navigation_bar.short.home' defaultMessage='Home' children={msg=> <>{msg}</>} /></span></NavLink>;
|
||||
const link_limited = <NavLink className='tabs-bar__link tabs-bar__limited' to='/timelines/limited' data-preview-title-id='column.limited' data-preview-icon='lock' ><Icon id='lock' fixedWidth /><span className='tabs-bar__link__full-label'><FormattedMessage id='tabs_bar.limited_timeline' defaultMessage='Limited' children={msg=> <>{msg}</>} /></span><span className='tabs-bar__link__short-label'><FormattedMessage id='navigation_bar.short.limited_timeline' defaultMessage='Ltd.' children={msg=> <>{msg}</>} /></span></NavLink>;
|
||||
const link_personal = <NavLink className='tabs-bar__link tabs-bar__personal' to='/timelines/personal' data-preview-title-id='column.personal' data-preview-icon='book' ><Icon id='book' fixedWidth /><span className='tabs-bar__link__full-label'><FormattedMessage id='tabs_bar.personal_timeline' defaultMessage='Personal' children={msg=> <>{msg}</>} /></span><span className='tabs-bar__link__short-label'><FormattedMessage id='navigation_bar.short.personal_timeline' defaultMessage='Per.' children={msg=> <>{msg}</>} /></span></NavLink>;
|
||||
const link_notifications = <NavLink className='tabs-bar__link' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><NotificationsCounterIcon /><span className='tabs-bar__link__full-label'><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' children={msg=> <>{msg}</>} /></span><span className='tabs-bar__link__short-label'><FormattedMessage id='navigation_bar.short.notifications' defaultMessage='Notif.' children={msg=> <>{msg}</>} /></span></NavLink>;
|
||||
const link_local = <NavLink className='tabs-bar__link' exact to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon id='users' fixedWidth /><span className='tabs-bar__link__full-label'><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' children={msg=> <>{msg}</>} /></span><span className='tabs-bar__link__short-label'><FormattedMessage id='navigation_bar.short.local_timeline' defaultMessage='LTL' children={msg=> <>{msg}</>} /></span></NavLink>;
|
||||
const link_public = <NavLink className='tabs-bar__link' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon id='globe' fixedWidth /><span className='tabs-bar__link__full-label'><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' children={msg=> <>{msg}</>} /></span><span className='tabs-bar__link__short-label'><FormattedMessage id='navigation_bar.short.public_timeline' defaultMessage='FTL' children={msg=> <>{msg}</>} /></span></NavLink>;
|
||||
|
@ -40,6 +41,7 @@ export const getLinks = memoize((favouriteLists = null) => {
|
|||
return [
|
||||
link_home,
|
||||
enableLimitedTimeline ? link_limited : null,
|
||||
enablePersonalTimeline ? link_personal : null,
|
||||
link_favourite_lists,
|
||||
link_notifications,
|
||||
enableLocalTimeline ? link_local : null,
|
||||
|
|
|
@ -7,9 +7,19 @@ import { createSelector } from 'reselect';
|
|||
import { debounce } from 'lodash';
|
||||
import { me } from '../../../initial_state';
|
||||
|
||||
const visibilitiesByType = (state, type) => {
|
||||
if (type === 'home') {
|
||||
return getHomeVisibilities(state);
|
||||
} else if (type === 'limited') {
|
||||
return getLimitedVisibilities(state);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const makeGetStatusIds = (pending = false) => createSelector([
|
||||
(state, { type }) => state.getIn(['settings', type], ImmutableMap()),
|
||||
(state, { type }) => type === 'home' ? getHomeVisibilities(state) : type === 'limited' ? getLimitedVisibilities(state) : [],
|
||||
(state, { type }) => visibilitiesByType(state, type),
|
||||
(state, { type }) => state.getIn(['timelines', type, pending ? 'pendingItems' : 'items'], ImmutableList()),
|
||||
(state) => state.get('statuses'),
|
||||
], (columnSettings, visibilities, statusIds, statuses) => {
|
||||
|
|
|
@ -46,6 +46,7 @@ import {
|
|||
Mentions,
|
||||
DirectTimeline,
|
||||
LimitedTimeline,
|
||||
PersonalTimeline,
|
||||
HashtagTimeline,
|
||||
Notifications,
|
||||
FollowRequests,
|
||||
|
@ -182,6 +183,7 @@ class SwitchingColumnsArea extends React.PureComponent {
|
|||
<WrappedRoute path='/timelines/groups/:id/:tagged?' exact component={GroupTimeline} content={children} />
|
||||
<WrappedRoute path='/timelines/direct' component={DirectTimeline} content={children} />
|
||||
<WrappedRoute path='/timelines/limited' component={LimitedTimeline} content={children} />
|
||||
<WrappedRoute path='/timelines/personal' component={PersonalTimeline} content={children} />
|
||||
<WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
|
||||
<WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} />
|
||||
|
||||
|
|
|
@ -42,6 +42,10 @@ export function LimitedTimeline() {
|
|||
return import(/* webpackChunkName: "features/limited_timeline" */'../../limited_timeline');
|
||||
}
|
||||
|
||||
export function PersonalTimeline() {
|
||||
return import(/* webpackChunkName: "features/personal_timeline" */'../../personal_timeline');
|
||||
}
|
||||
|
||||
export function ListTimeline () {
|
||||
return import(/* webpackChunkName: "features/list_timeline" */'../../list_timeline');
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ export const show_target = getMeta('show_target');
|
|||
export const place_tab_bar_at_bottom = getMeta('place_tab_bar_at_bottom');
|
||||
export const show_tab_bar_label = getMeta('show_tab_bar_label');
|
||||
export const enableLimitedTimeline = getMeta('enable_limited_timeline');
|
||||
export const enablePersonalTimeline = getMeta('enable_personal_timeline');
|
||||
export const enableFederatedTimeline = getMeta('enable_federated_timeline') ?? true;
|
||||
export const enableLocalTimeline = getMeta('enable_local_timeline') ?? true;
|
||||
export const enableReaction = getMeta('enable_reaction');
|
||||
|
@ -66,6 +67,9 @@ export const disableDomainBlock = getMeta('disable_domain_block');
|
|||
export const disableClearAllNotifications = getMeta('disable_clear_all_notifications');
|
||||
export const disableAccountDelete = getMeta('disable_account_delete');
|
||||
export const disableRelativeTime = getMeta('disable_relative_time');
|
||||
export const hideDirectFromTimeline = getMeta('hide_direct_from_timeline');
|
||||
export const hidePersonalFromTimeline = getMeta('hide_personal_from_timeline');
|
||||
export const hidePersonalFromAccount = getMeta('hide_personal_from_account');
|
||||
|
||||
export const maxChars = initialState?.max_toot_chars ?? 500;
|
||||
|
||||
|
|
|
@ -125,6 +125,7 @@
|
|||
"column.lists": "Lists",
|
||||
"column.mutes": "Muted users",
|
||||
"column.notifications": "Notifications",
|
||||
"column.personal": "Personal home",
|
||||
"column.pins": "Pinned posts",
|
||||
"column.public": "Federated timeline",
|
||||
"column_back_button.label": "Back",
|
||||
|
@ -265,6 +266,7 @@
|
|||
"empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
|
||||
"empty_column.mutes": "You haven't muted any users yet.",
|
||||
"empty_column.notifications": "You don't have any notifications yet. When other people interact with you, you will see it here.",
|
||||
"empty_column.personal": "Personal posts unavailable",
|
||||
"empty_column.pinned_unavailable": "Pinned posts unavailable",
|
||||
"empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other servers to fill it up",
|
||||
"empty_column.referred_by_statuses": "There are no referred by posts yet. When someone refers a post, it will appear here.",
|
||||
|
@ -310,6 +312,7 @@
|
|||
"home.column_settings.basic": "Basic",
|
||||
"home.column_settings.show_direct": "Show direct",
|
||||
"home.column_settings.show_limited": "Show limited",
|
||||
"home.column_settings.show_personal": "Show personal",
|
||||
"home.column_settings.show_private": "Show private",
|
||||
"home.column_settings.show_reblogs": "Show boosts",
|
||||
"home.column_settings.show_replies": "Show replies",
|
||||
|
@ -367,6 +370,7 @@
|
|||
"limited.column_settings.basic": "Basic",
|
||||
"limited.column_settings.show_direct": "Show direct",
|
||||
"limited.column_settings.show_limited": "Show limited",
|
||||
"limited.column_settings.show_personal": "Show personal",
|
||||
"limited.column_settings.show_private": "Show private",
|
||||
"limited.column_settings.show_reblogs": "Show boosts",
|
||||
"limited.column_settings.show_replies": "Show replies",
|
||||
|
@ -418,6 +422,7 @@
|
|||
"navigation_bar.logout": "Logout",
|
||||
"navigation_bar.mutes": "Muted users",
|
||||
"navigation_bar.personal": "Personal",
|
||||
"navigation_bar.personal_timeline": "Personal home",
|
||||
"navigation_bar.pins": "Pinned posts",
|
||||
"navigation_bar.preferences": "Preferences",
|
||||
"navigation_bar.public_timeline": "Federated timeline",
|
||||
|
@ -429,6 +434,7 @@
|
|||
"navigation_bar.short.lists": "Lists",
|
||||
"navigation_bar.short.logout": "Logout",
|
||||
"navigation_bar.short.notifications": "Notif.",
|
||||
"navigation_bar.short.personal_timeline": "Per.",
|
||||
"navigation_bar.short.preferences": "Pref.",
|
||||
"navigation_bar.short.public_timeline": "FTL",
|
||||
"navigation_bar.short.search": "Search",
|
||||
|
@ -481,6 +487,8 @@
|
|||
"notifications_permission_banner.enable": "Enable desktop notifications",
|
||||
"notifications_permission_banner.how_to_control": "To receive notifications when Mastodon isn't open, enable desktop notifications. You can control precisely which types of interactions generate desktop notifications through the {icon} button above once they're enabled.",
|
||||
"notifications_permission_banner.title": "Never miss a thing",
|
||||
"personal.column_settings.basic": "Basic",
|
||||
"personal.column_settings.show_replies": "Show replies",
|
||||
"picture_in_picture.restore": "Put it back",
|
||||
"poll.closed": "Closed",
|
||||
"poll.refresh": "Refresh",
|
||||
|
@ -499,6 +507,8 @@
|
|||
"privacy.mutual.short": "Mutual-followers-only",
|
||||
"privacy.none.long": "No visibility allowed",
|
||||
"privacy.none.short": "None",
|
||||
"privacy.personal.long": "Visible for personal only",
|
||||
"privacy.personal.short": "Personal",
|
||||
"privacy.private.long": "Visible for followers only",
|
||||
"privacy.private.short": "Followers-only",
|
||||
"privacy.public.long": "Visible for all, shown in public timelines",
|
||||
|
@ -614,6 +624,7 @@
|
|||
"tabs_bar.lists": "List",
|
||||
"tabs_bar.local_timeline": "Local",
|
||||
"tabs_bar.notifications": "Notifications",
|
||||
"tabs_bar.personal_timeline": "Personal",
|
||||
"tabs_bar.search": "Search",
|
||||
"thread_mark.ancestor": "Has reference",
|
||||
"thread_mark.both": "Has reference and reply",
|
||||
|
|
|
@ -125,6 +125,7 @@
|
|||
"column.lists": "リスト",
|
||||
"column.mutes": "ミュートしたユーザー",
|
||||
"column.notifications": "通知",
|
||||
"column.personal": "自分限定ホーム",
|
||||
"column.pins": "固定された投稿",
|
||||
"column.public": "連合タイムライン",
|
||||
"column_back_button.label": "戻る",
|
||||
|
@ -265,6 +266,7 @@
|
|||
"empty_column.lists": "まだリストがありません。リストを作るとここに表示されます。",
|
||||
"empty_column.mutes": "まだ誰もミュートしていません。",
|
||||
"empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。",
|
||||
"empty_column.personal": "自分限定投稿はありません。",
|
||||
"empty_column.pinned_unavailable": "固定された投稿はありません。",
|
||||
"empty_column.referred_by_statuses": "まだ、参照している投稿はありません。誰かが投稿を参照すると、ここに表示されます。",
|
||||
"empty_column.suggestions": "まだおすすめできるユーザーがいません。",
|
||||
|
@ -310,6 +312,7 @@
|
|||
"home.column_settings.basic": "基本設定",
|
||||
"home.column_settings.show_direct": "ダイレクトメッセージを表示",
|
||||
"home.column_settings.show_limited": "サークルを表示",
|
||||
"home.column_settings.show_personal": "自分限定を表示",
|
||||
"home.column_settings.show_private": "フォロワー限定を表示",
|
||||
"home.column_settings.show_reblogs": "ブースト表示",
|
||||
"home.column_settings.show_replies": "返信表示",
|
||||
|
@ -367,6 +370,7 @@
|
|||
"limited.column_settings.basic": "基本設定",
|
||||
"limited.column_settings.show_direct": "ダイレクトメッセージを表示",
|
||||
"limited.column_settings.show_limited": "サークルを表示",
|
||||
"limited.column_settings.show_personal": "自分限定を表示",
|
||||
"limited.column_settings.show_private": "フォロワー限定を表示",
|
||||
"limited.column_settings.show_reblogs": "ブースト表示",
|
||||
"limited.column_settings.show_replies": "返信表示",
|
||||
|
@ -418,6 +422,7 @@
|
|||
"navigation_bar.logout": "ログアウト",
|
||||
"navigation_bar.mutes": "ミュートしたユーザー",
|
||||
"navigation_bar.personal": "個人用",
|
||||
"navigation_bar.personal_timeline": "自分限定ホーム",
|
||||
"navigation_bar.pins": "固定した投稿",
|
||||
"navigation_bar.preferences": "ユーザー設定",
|
||||
"navigation_bar.public_timeline": "連合タイムライン",
|
||||
|
@ -429,6 +434,7 @@
|
|||
"navigation_bar.short.lists": "リスト",
|
||||
"navigation_bar.short.logout": "ログアウト",
|
||||
"navigation_bar.short.notifications": "通知",
|
||||
"navigation_bar.short.personal_timeline": "自分",
|
||||
"navigation_bar.short.preferences": "設定",
|
||||
"navigation_bar.short.public_timeline": "連合",
|
||||
"navigation_bar.short.search": "検索",
|
||||
|
@ -481,6 +487,8 @@
|
|||
"notifications_permission_banner.enable": "デスクトップ通知を有効にする",
|
||||
"notifications_permission_banner.how_to_control": "Mastodon を閉じている間でも通知を受信するにはデスクトップ通知を有効にしてください。有効にすると上の {icon} ボタンから通知の内容を細かくカスタマイズできます。",
|
||||
"notifications_permission_banner.title": "お見逃しなく",
|
||||
"personal.column_settings.basic": "基本設定",
|
||||
"personal.column_settings.show_replies": "返信表示",
|
||||
"picture_in_picture.restore": "元に戻す",
|
||||
"poll.closed": "終了",
|
||||
"poll.refresh": "更新",
|
||||
|
@ -499,6 +507,8 @@
|
|||
"privacy.mutual.short": "相互フォロー限定",
|
||||
"privacy.none.long": "許可された公開範囲なし",
|
||||
"privacy.none.short": "なし",
|
||||
"privacy.personal.long": "自分のみ閲覧可",
|
||||
"privacy.personal.short": "自分限定",
|
||||
"privacy.private.long": "フォロワーのみ閲覧可",
|
||||
"privacy.private.short": "フォロワー限定",
|
||||
"privacy.public.long": "誰でも閲覧可、公開TLに表示",
|
||||
|
@ -614,6 +624,7 @@
|
|||
"tabs_bar.lists": "リスト",
|
||||
"tabs_bar.local_timeline": "ローカル",
|
||||
"tabs_bar.notifications": "通知",
|
||||
"tabs_bar.personal_timeline": "自分限定ホーム",
|
||||
"tabs_bar.search": "検索",
|
||||
"thread_mark.ancestor": "参照あり",
|
||||
"thread_mark.both": "参照・返信あり",
|
||||
|
|
|
@ -248,18 +248,18 @@ const insertEmoji = (state, position, emojiData, needsSpace) => {
|
|||
};
|
||||
|
||||
const privacyExpand = (a, b) => {
|
||||
const order = ['public', 'unlisted', 'private', 'mutual', 'limited', 'direct'];
|
||||
const order = ['public', 'unlisted', 'private', 'mutual', 'limited', 'direct', 'personal'];
|
||||
return order[Math.min(order.indexOf(a), order.indexOf(b), order.length - 1)];
|
||||
};
|
||||
|
||||
const privacyCap = (a, b) => {
|
||||
const order = ['public', 'unlisted', 'private', 'mutual', 'limited', 'direct'];
|
||||
const order = ['public', 'unlisted', 'private', 'mutual', 'limited', 'direct', 'personal'];
|
||||
return order[Math.max(order.indexOf(a), order.indexOf(b), 0)];
|
||||
};
|
||||
|
||||
const searchabilityCap = (a, b) => {
|
||||
const order = ['public', 'unlisted', 'private', 'mutual', 'limited', 'direct'];
|
||||
const to = ['public', 'private', 'private', 'direct', 'direct', 'direct'];
|
||||
const order = ['public', 'unlisted', 'private', 'mutual', 'limited', 'direct', 'personal'];
|
||||
const to = ['public', 'private', 'private', 'direct', 'direct', 'direct', 'direct'];
|
||||
return to[Math.max(order.indexOf(a), order.indexOf(b), 0)];
|
||||
};
|
||||
|
||||
|
|
|
@ -31,9 +31,10 @@ const initialState = ImmutableMap({
|
|||
shows: ImmutableMap({
|
||||
reblog: true,
|
||||
reply: true,
|
||||
private: false,
|
||||
limited: false,
|
||||
direct: false,
|
||||
private: true,
|
||||
limited: true,
|
||||
direct: true,
|
||||
personal: true,
|
||||
}),
|
||||
|
||||
regex: ImmutableMap({
|
||||
|
@ -48,6 +49,17 @@ const initialState = ImmutableMap({
|
|||
private: true,
|
||||
limited: true,
|
||||
direct: true,
|
||||
personal: true,
|
||||
}),
|
||||
|
||||
regex: ImmutableMap({
|
||||
body: '',
|
||||
}),
|
||||
}),
|
||||
|
||||
personal: ImmutableMap({
|
||||
shows: ImmutableMap({
|
||||
reply: true,
|
||||
}),
|
||||
|
||||
regex: ImmutableMap({
|
||||
|
|
|
@ -69,7 +69,7 @@ const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, is
|
|||
const updateTimeline = (state, timeline, status, usePendingItems) => {
|
||||
const top = state.getIn([timeline, 'top']);
|
||||
|
||||
if (usePendingItems || !state.getIn([timeline, 'pendingItems']).isEmpty()) {
|
||||
if (usePendingItems || !state.getIn([timeline, 'pendingItems'], ImmutableList()).isEmpty()) {
|
||||
if (state.getIn([timeline, 'pendingItems'], ImmutableList()).includes(status.get('id')) || state.getIn([timeline, 'items'], ImmutableList()).includes(status.get('id'))) {
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { createSelector } from 'reselect';
|
||||
import { List as ImmutableList, Map as ImmutableMap, is } from 'immutable';
|
||||
import { me, enableLimitedTimeline } from '../initial_state';
|
||||
import { me, enableLimitedTimeline, hideDirectFromTimeline, hidePersonalFromTimeline, enablePersonalTimeline } from '../initial_state';
|
||||
|
||||
const getAccountBase = (state, id) => state.getIn(['accounts', id], null);
|
||||
const getAccountCounters = (state, id) => state.getIn(['accounts_counters', id], null);
|
||||
|
@ -249,13 +249,21 @@ export const getAccountGallery = createSelector([
|
|||
|
||||
export const getHomeVisibilities = createSelector(
|
||||
state => state.getIn(['settings', 'home', 'shows']),
|
||||
shows => !enableLimitedTimeline ? [] : [
|
||||
shows => (!enableLimitedTimeline ? [
|
||||
'public',
|
||||
'unlisted',
|
||||
'private',
|
||||
'limited',
|
||||
!hideDirectFromTimeline ? 'direct' : null,
|
||||
!hidePersonalFromTimeline ? 'personal' : null,
|
||||
] : [
|
||||
'public',
|
||||
'unlisted',
|
||||
shows.get('private') ? 'private' : null,
|
||||
shows.get('limited') ? 'limited' : null,
|
||||
shows.get('direct') ? 'direct' : null,
|
||||
].filter(x => !!x),
|
||||
shows.get('direct') && !hideDirectFromTimeline ? 'direct' : null,
|
||||
shows.get('personal') && !hidePersonalFromTimeline ? 'personal' : null,
|
||||
]).filter(x => !!x),
|
||||
);
|
||||
|
||||
export const getLimitedVisibilities = createSelector(
|
||||
|
@ -264,5 +272,10 @@ export const getLimitedVisibilities = createSelector(
|
|||
shows.get('private') ? 'private' : null,
|
||||
shows.get('limited') ? 'limited' : null,
|
||||
shows.get('direct') ? 'direct' : null,
|
||||
shows.get('personal') ? 'personal' : null,
|
||||
].filter(x => !!x),
|
||||
);
|
||||
|
||||
export const getPersonalVisibilities = createSelector(
|
||||
state => state.getIn(['settings', 'personal', 'shows']),
|
||||
);
|
||||
|
|
|
@ -65,6 +65,7 @@ class UserSettingsDecorator
|
|||
show_tab_bar_label
|
||||
enable_federated_timeline
|
||||
enable_limited_timeline
|
||||
enable_personal_timeline
|
||||
enable_local_timeline
|
||||
enable_reaction
|
||||
compact_reaction
|
||||
|
@ -90,6 +91,9 @@ class UserSettingsDecorator
|
|||
disable_clear_all_notifications
|
||||
disable_account_delete
|
||||
disable_relative_time
|
||||
hide_direct_from_timeline
|
||||
hide_personal_from_timeline
|
||||
hide_personal_from_account
|
||||
).freeze
|
||||
|
||||
STRING_KEYS = %w(
|
||||
|
|
|
@ -89,7 +89,7 @@ class Account < ApplicationRecord
|
|||
enum protocol: [:ostatus, :activitypub]
|
||||
enum suspension_origin: [:local, :remote], _prefix: true
|
||||
enum silence_mode: { soft: 0, hard: 1 }, _suffix: :silence_mode
|
||||
enum searchability: [:public, :unlisted, :private, :direct, :limited, :mutual], _suffix: :searchability
|
||||
enum searchability: { public: 0, unlisted: 1, private: 2, direct: 3, limited: 4, mutual: 100, personal: 200 }, _suffix: :searchability
|
||||
|
||||
validates :username, presence: true
|
||||
validates_with UniqueUsernameValidator, if: -> { will_save_change_to_username? }
|
||||
|
|
|
@ -243,6 +243,10 @@ module AccountInteractions
|
|||
.where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago)
|
||||
end
|
||||
|
||||
def self_included_lists
|
||||
owned_lists.joins(:list_accounts).where(list_accounts: {account_id: id})
|
||||
end
|
||||
|
||||
def remote_followers_hash(url)
|
||||
url_prefix = url[Account::URL_PREFIX_RE]
|
||||
return if url_prefix.blank?
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class PersonalFeed
|
||||
# @param [Account] account
|
||||
# @param [Hash] options
|
||||
# @option [Boolean] :with_replies
|
||||
def initialize(account, options = {})
|
||||
@account = account
|
||||
@options = options
|
||||
end
|
||||
|
||||
# @param [Integer] limit
|
||||
# @param [Integer] max_id
|
||||
# @param [Integer] since_id
|
||||
# @param [Integer] min_id
|
||||
# @return [Array<Status>]
|
||||
def get(limit, max_id = nil, since_id = nil, min_id = nil)
|
||||
scope = personal_scope
|
||||
|
||||
scope.cache_ids.to_a_paginated_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :account, :options
|
||||
|
||||
def personal_scope
|
||||
Status.include_expired.where(account_id: account.id).without_reblogs.with_personal_visibility
|
||||
end
|
||||
end
|
|
@ -48,8 +48,13 @@ class Status < ApplicationRecord
|
|||
|
||||
update_index('statuses', :proper)
|
||||
|
||||
enum visibility: [:public, :unlisted, :private, :direct, :limited, :mutual], _suffix: :visibility
|
||||
enum searchability: [:public, :unlisted, :private, :direct, :limited, :mutual], _suffix: :searchability
|
||||
enum visibility: { public: 0, unlisted: 1, private: 2, direct: 3, limited: 4, mutual: 100, personal: 200 }, _suffix: :visibility
|
||||
enum searchability: { public: 0, unlisted: 1, private: 2, direct: 3, limited: 4, mutual: 100, personal: 200 }, _suffix: :searchability
|
||||
|
||||
STANDARD_VISIBILITY = %w(public unlisted private direct)
|
||||
EXTRA_VISIBILITY = %w(limited personal)
|
||||
PSEUDO_VISIBILITY = %w(mutual)
|
||||
UNCOUNT_VISIBILITY = %w(direct personal)
|
||||
|
||||
belongs_to :application, class_name: 'Doorkeeper::Application', optional: true
|
||||
|
||||
|
@ -111,6 +116,8 @@ class Status < ApplicationRecord
|
|||
scope :without_replies, -> { where('statuses.reply = FALSE OR statuses.in_reply_to_account_id = statuses.account_id') }
|
||||
scope :without_reblogs, -> { where('statuses.reblog_of_id IS NULL') }
|
||||
scope :with_public_visibility, -> { where(visibility: :public) }
|
||||
scope :with_personal_visibility, -> { where(visibility: :personal) }
|
||||
scope :without_personal_visibility, -> { where.not(visibility: :personal) }
|
||||
scope :tagged_with, ->(tag_ids) { joins(:statuses_tags).where(statuses_tags: { tag_id: tag_ids }) }
|
||||
scope :in_chosen_languages, ->(account) { where(language: nil).or where(language: account.chosen_languages) }
|
||||
scope :mentioned_with, ->(account) { joins(:mentions).where(mentions: { account_id: account }) }
|
||||
|
@ -189,6 +196,22 @@ class Status < ApplicationRecord
|
|||
searchability || Status.searchabilities.invert.fetch([Account.searchabilities[account.searchability], Status.visibilities[visibility] || 0].max, nil) || 'direct'
|
||||
end
|
||||
|
||||
def standard_visibility?
|
||||
STANDARD_VISIBILITY.include?(visibility)
|
||||
end
|
||||
|
||||
def extra_visibility?
|
||||
EXTRA_VISIBILITY.include?(visibility)
|
||||
end
|
||||
|
||||
def pseudo_visibility?
|
||||
PSEUDO_VISIBILITY.include?(visibility)
|
||||
end
|
||||
|
||||
def uncount_visibility?
|
||||
UNCOUNT_VISIBILITY.include?(visibility)
|
||||
end
|
||||
|
||||
def reply?
|
||||
!in_reply_to_id.nil? || attributes['reply']
|
||||
end
|
||||
|
@ -412,7 +435,7 @@ class Status < ApplicationRecord
|
|||
end
|
||||
|
||||
def selectable_searchabilities
|
||||
searchabilities.keys - %w(unlisted limited mutual)
|
||||
searchabilities.keys - %w(unlisted limited mutual personal)
|
||||
end
|
||||
|
||||
def favourites_map(status_ids, account_id)
|
||||
|
@ -628,7 +651,7 @@ class Status < ApplicationRecord
|
|||
end
|
||||
|
||||
def increment_counter_caches
|
||||
return if direct_visibility?
|
||||
return if uncount_visibility?
|
||||
|
||||
account&.increment_count!(:statuses_count)
|
||||
reblog&.increment_count!(:reblogs_count) if reblog?
|
||||
|
@ -636,7 +659,7 @@ class Status < ApplicationRecord
|
|||
end
|
||||
|
||||
def decrement_counter_caches
|
||||
return if direct_visibility?
|
||||
return if uncount_visibility?
|
||||
|
||||
account&.decrement_count!(:statuses_count)
|
||||
reblog&.decrement_count!(:reblogs_count) if reblog?
|
||||
|
@ -644,7 +667,7 @@ class Status < ApplicationRecord
|
|||
end
|
||||
|
||||
def unlink_from_conversations
|
||||
return unless direct_visibility?
|
||||
return if uncount_visibility?
|
||||
|
||||
mentioned_accounts = (association(:mentions).loaded? ? mentions : mentions.includes(:account)).map(&:account)
|
||||
inbox_owners = mentioned_accounts.select(&:local?) + (account.local? ? [account] : [])
|
||||
|
|
|
@ -130,7 +130,7 @@ class User < ApplicationRecord
|
|||
:show_follow_button_on_timeline, :show_subscribe_button_on_timeline, :show_followed_by, :show_target,
|
||||
:follow_button_to_list_adder, :show_navigation_panel, :show_quote_button, :show_bookmark_button,
|
||||
:place_tab_bar_at_bottom,:show_tab_bar_label,
|
||||
:enable_local_timeline, :enable_federated_timeline, :enable_limited_timeline,
|
||||
:enable_local_timeline, :enable_federated_timeline, :enable_limited_timeline, :enable_personal_timeline,
|
||||
:enable_reaction, :compact_reaction,
|
||||
:show_reply_tree_button,
|
||||
:hide_statuses_count, :hide_following_count, :hide_followers_count, :disable_joke_appearance,
|
||||
|
@ -145,7 +145,7 @@ class User < ApplicationRecord
|
|||
:show_reload_button, :default_column_width,
|
||||
:disable_post, :disable_reactions, :disable_follow, :disable_unfollow, :disable_block, :disable_domain_block, :disable_clear_all_notifications, :disable_account_delete,
|
||||
:prohibited_visibilities, :prohibited_words,
|
||||
:disable_relative_time,
|
||||
:disable_relative_time, :hide_direct_from_timeline, :hide_personal_from_timeline, :hide_personal_from_account,
|
||||
|
||||
to: :settings, prefix: :setting, allow_nil: false
|
||||
|
||||
|
|
|
@ -18,7 +18,9 @@ class StatusPolicy < ApplicationPolicy
|
|||
return false if author.suspended?
|
||||
return false unless expired_show?
|
||||
|
||||
if requires_mention?
|
||||
if personal?
|
||||
owned?
|
||||
elsif requires_mention?
|
||||
owned? || mention_exists?
|
||||
elsif private?
|
||||
owned? || following_author? || mention_exists?
|
||||
|
@ -85,6 +87,10 @@ class StatusPolicy < ApplicationPolicy
|
|||
record.limited_visibility?
|
||||
end
|
||||
|
||||
def personal?
|
||||
record.personal_visibility?
|
||||
end
|
||||
|
||||
def mention_exists?
|
||||
return false if current_account.nil?
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ class InitialStateSerializer < ActiveModel::Serializer
|
|||
store[:show_tab_bar_label] = object.current_account.user.setting_show_tab_bar_label
|
||||
store[:enable_federated_timeline] = object.current_account.user.setting_enable_federated_timeline
|
||||
store[:enable_limited_timeline] = object.current_account.user.setting_enable_limited_timeline
|
||||
store[:enable_personal_timeline] = object.current_account.user.setting_enable_personal_timeline
|
||||
store[:enable_local_timeline] = false #object.current_account.user.setting_enable_local_timeline
|
||||
store[:enable_reaction] = object.current_account.user.setting_enable_reaction
|
||||
store[:compact_reaction] = object.current_account.user.setting_compact_reaction
|
||||
|
@ -87,6 +88,9 @@ class InitialStateSerializer < ActiveModel::Serializer
|
|||
store[:disable_clear_all_notifications] = object.current_account.user.setting_disable_clear_all_notifications
|
||||
store[:disable_account_delete] = object.current_account.user.setting_disable_account_delete
|
||||
store[:disable_relative_time] = object.current_account.user.setting_disable_relative_time
|
||||
store[:hide_direct_from_timeline] = object.current_account.user.setting_hide_direct_from_timeline
|
||||
store[:hide_personal_from_timeline] = object.current_account.user.setting_hide_personal_from_timeline
|
||||
store[:hide_personal_from_account] = object.current_account.user.setting_hide_personal_from_account
|
||||
else
|
||||
store[:auto_play_gif] = Setting.auto_play_gif
|
||||
store[:display_media] = Setting.display_media
|
||||
|
|
|
@ -139,6 +139,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer
|
|||
:timeline_group_directory,
|
||||
:visibility_mutual,
|
||||
:visibility_limited,
|
||||
:visibility_personal,
|
||||
:emoji_reaction,
|
||||
:misskey_birthday,
|
||||
:misskey_location,
|
||||
|
|
|
@ -102,7 +102,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
|||
end
|
||||
|
||||
def visibility_ex?
|
||||
object.limited_visibility?
|
||||
!object.standard_visibility?
|
||||
end
|
||||
|
||||
def visibility
|
||||
|
@ -111,6 +111,8 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
|||
# UX differences
|
||||
if object.limited_visibility?
|
||||
'private'
|
||||
elsif object.personal_visibility?
|
||||
'direct'
|
||||
else
|
||||
object.visibility
|
||||
end
|
||||
|
|
|
@ -6,9 +6,12 @@ class FanOutOnWriteService < BaseService
|
|||
def call(status)
|
||||
raise Mastodon::RaceConditionError if status.visibility.nil?
|
||||
|
||||
deliver_to_self(status) if status.account.local?
|
||||
deliver_to_self(status) if status.account.local? && !(status.direct_visibility? && status.account.user.setting_hide_direct_from_timeline)
|
||||
|
||||
if status.direct_visibility?
|
||||
if status.personal_visibility?
|
||||
deliver_to_self_included_lists(status) if status.account.local? && !status.account.user.setting_hide_personal_from_timeline
|
||||
return
|
||||
elsif status.direct_visibility?
|
||||
deliver_to_mentioned_followers(status)
|
||||
deliver_to_own_conversation(status)
|
||||
elsif status.limited_visibility?
|
||||
|
@ -283,4 +286,10 @@ class FanOutOnWriteService < BaseService
|
|||
def deliver_to_own_conversation(status)
|
||||
AccountConversation.add_status(status.account, status)
|
||||
end
|
||||
|
||||
def deliver_to_self_included_lists(status)
|
||||
FeedInsertWorker.push_bulk(status.account.self_included_lists.pluck(:id)) do |list_id|
|
||||
[status.id, list_id, :list]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -120,7 +120,7 @@ class PostStatusService < BaseService
|
|||
end
|
||||
|
||||
ProcessHashtagsService.new.call(@status)
|
||||
ProcessMentionsService.new.call(@status, @circle)
|
||||
ProcessMentionsService.new.call(@status, @circle) unless @status.personal_visibility?
|
||||
ProcessStatusReferenceService.new.call(@status, status_reference_ids: (@options[:status_reference_ids] || []) + [@quote_id], urls: @options[:status_reference_urls])
|
||||
end
|
||||
|
||||
|
@ -144,7 +144,7 @@ class PostStatusService < BaseService
|
|||
def postprocess_status!
|
||||
LinkCrawlWorker.perform_async(@status.id) unless @status.spoiler_text?
|
||||
DistributionWorker.perform_async(@status.id)
|
||||
ActivityPub::DistributionWorker.perform_async(@status.id)
|
||||
ActivityPub::DistributionWorker.perform_async(@status.id) unless @status.personal_visibility?
|
||||
PollExpirationNotifyWorker.perform_at(@status.poll.expires_at, @status.poll.id) if @status.poll
|
||||
@status.status_expire.queue_action if expires_soon?
|
||||
end
|
||||
|
|
|
@ -37,7 +37,7 @@ class ReblogService < BaseService
|
|||
end
|
||||
end
|
||||
|
||||
raise Mastodon::ValidationError, I18n.t('status_prohibit.validations.prohibited_visibilities') if account.user&.setting_prohibited_visibilities&.filter(&:present?)&.include?(visibility)
|
||||
raise Mastodon::ValidationError, I18n.t('status_prohibit.validations.prohibited_visibilities') if account.user&.setting_prohibited_visibilities&.filter(&:present?)&.include?(visibility) || visibility == 'personal'
|
||||
|
||||
ApplicationRecord.transaction do
|
||||
reblog = account.statuses.create!(reblog: reblogged_status, text: '', visibility: visibility, rate_limit: options[:with_rate_limit])
|
||||
|
|
|
@ -95,6 +95,9 @@
|
|||
.fields-group
|
||||
= f.input :setting_enable_limited_timeline, as: :boolean, wrapper: :with_label, fedibird_features: true
|
||||
|
||||
.fields-group
|
||||
= f.input :setting_enable_personal_timeline, as: :boolean, wrapper: :with_label, fedibird_features: true
|
||||
|
||||
.fields-group
|
||||
= f.input :setting_enable_reaction, as: :boolean, wrapper: :with_label, fedibird_features: true
|
||||
|
||||
|
@ -119,6 +122,15 @@
|
|||
.fields-group
|
||||
= f.input :setting_disable_relative_time, as: :boolean, wrapper: :with_label, fedibird_features: true
|
||||
|
||||
.fields-group
|
||||
= f.input :setting_hide_direct_from_timeline, as: :boolean, wrapper: :with_label, fedibird_features: true
|
||||
|
||||
.fields-group
|
||||
= f.input :setting_hide_personal_from_timeline, as: :boolean, wrapper: :with_label, fedibird_features: true
|
||||
|
||||
.fields-group
|
||||
= f.input :setting_hide_personal_from_account, as: :boolean, wrapper: :with_label, fedibird_features: true
|
||||
|
||||
%h4= t 'preferences.public_timelines'
|
||||
|
||||
.fields-group
|
||||
|
|
|
@ -1464,6 +1464,8 @@ en:
|
|||
limited_long: Only show to circle users
|
||||
mutual: Mutual
|
||||
mutual_long: Only show to mutual followers
|
||||
personal: Personal
|
||||
personal_long: Only show to personal
|
||||
private: Followers-only
|
||||
private_long: Only show to followers
|
||||
public: Public
|
||||
|
|
|
@ -1401,6 +1401,8 @@ ja:
|
|||
limited_long: サークルで指定したユーザーのみ閲覧可
|
||||
mutual: 相互フォロー限定
|
||||
mutual_long: 相互フォロー相手にのみ表示されます
|
||||
personal: 自分限定
|
||||
personal_long: 自分にのみ表示されます
|
||||
private: フォロワー限定
|
||||
private_long: フォロワーにのみ表示されます
|
||||
public: 公開
|
||||
|
|
|
@ -87,14 +87,18 @@ en:
|
|||
setting_enable_federated_timeline: 'Enable a federated timeline (default: enable)'
|
||||
setting_enable_limited_timeline: Enable a limited home to display private and circle and direct message
|
||||
setting_enable_local_timeline: 'Enable a local timeline (default: enable)'
|
||||
setting_enable_personal_timeline: Enable a personal home to display personal post
|
||||
setting_enable_reaction: Enable the reaction display on the timeline and display the reaction button
|
||||
setting_enable_status_reference: Enable the feature where a post references another post
|
||||
setting_follow_button_to_list_adder: Change the behavior of the Follow / Subscribe button, open a dialog where you can select a list to follow / subscribe, or opt out of receiving at home
|
||||
setting_hexagon_avatar: Display everyone's avatar icon as a hollowed out hexagon (joke feature)
|
||||
setting_hide_bot_on_public_timeline: Disable Bot accounts from appearing on federation & hashtag & domain & group timelines (overridden by column setting)
|
||||
setting_hide_direct_from_timeline: Hide direct messages from the home and list timelines
|
||||
setting_hide_followers_count: The number of followers will be hidden in your profile
|
||||
setting_hide_following_count: The number of following will be hidden in your profile
|
||||
setting_hide_network: Who you follow and who follows you will be hidden on your profile
|
||||
setting_hide_personal_from_account: Hide personal posts from the account post lists
|
||||
setting_hide_personal_from_timeline: Hide personal posts from the home and list timelines
|
||||
setting_hide_statuses_count: The number of post will be hidden in your profile
|
||||
setting_match_visibility_of_references: If the referenced post is private, default the visibility of the post to private behavior accordingly
|
||||
setting_new_features_policy: Set the acceptance policy when new features are added to Fedibird. The recommended setting will enable many new features, so set it to disabled if it is not desirable
|
||||
|
@ -274,15 +278,19 @@ en:
|
|||
setting_enable_federated_timeline: Enable federated timeline
|
||||
setting_enable_limited_timeline: Enable limited timeline
|
||||
setting_enable_local_timeline: Enable local timeline
|
||||
setting_enable_personal_timeline: Enable personal timeline
|
||||
setting_enable_reaction: Enable reaction
|
||||
setting_enable_status_reference: Enable reference
|
||||
setting_expand_spoilers: Always expand posts marked with content warnings
|
||||
setting_follow_button_to_list_adder: Open list add dialog with follow button
|
||||
setting_hexagon_avatar: Experience NFT Avatar
|
||||
setting_hide_bot_on_public_timeline: Hide bot account on public timeline
|
||||
setting_hide_direct_from_timeline: Hide direct messages from the timeline
|
||||
setting_hide_followers_count: Hide your followers count
|
||||
setting_hide_following_count: Hide your following count
|
||||
setting_hide_network: Hide your social graph
|
||||
setting_hide_personal_from_account: Hide personal posts from account posts
|
||||
setting_hide_personal_from_timeline: Hide personal posts from the timeline
|
||||
setting_hide_statuses_count: Hide your post count
|
||||
setting_info_font_size: Information header font size
|
||||
setting_match_visibility_of_references: Match the visibility of the post to the references
|
||||
|
|
|
@ -83,14 +83,18 @@ ja:
|
|||
setting_enable_federated_timeline: 連合タイムラインを有効にします(デフォルト)
|
||||
setting_enable_limited_timeline: フォロワー限定・サークル・ダイレクトメッセージを表示する限定ホームを有効にします
|
||||
setting_enable_local_timeline: ローカルタイムラインを有効にします(デフォルト)
|
||||
setting_enable_personal_timeline: 自分限定を表示する自分限定ホームを有効にします
|
||||
setting_enable_reaction: タイムラインでリアクションの表示を有効にし、リアクションボタンを表示する
|
||||
setting_enable_status_reference: 投稿が別の投稿を参照する機能を有効にします
|
||||
setting_follow_button_to_list_adder: フォロー・購読ボタンの動作を変更し、フォロー・購読するリストを選択したり、ホームで受け取らないよう設定するダイアログを開きます
|
||||
setting_hexagon_avatar: 全員のアバターアイコンを6角形にくりぬいて表示します(ジョーク機能)
|
||||
setting_hide_bot_on_public_timeline: 連合・ハッシュタグ・ドメイン・グループタイムライン上にBotアカウントが表示されないようにします(※カラム設定を優先)
|
||||
setting_hide_direct_from_timeline: ホームとリストタイムラインからダイレクトメッセージを隠します
|
||||
setting_hide_followers_count: フォロワー数をプロフィールページで見られないようにします
|
||||
setting_hide_following_count: フォロー数をプロフィールページで見られないようにします
|
||||
setting_hide_network: フォローとフォロワーの情報がプロフィールページで見られないようにします
|
||||
setting_hide_personal_from_account: アカウントの投稿一覧から個人限定投稿を隠します
|
||||
setting_hide_personal_from_timeline: ホームとリストタイムラインから個人限定投稿を隠します
|
||||
setting_hide_statuses_count: 投稿数をプロフィールページで見られないようにします
|
||||
setting_match_visibility_of_references: 参照先の投稿がフォロワー限定の場合、投稿の公開範囲をそれに合わせてフォロワー限定とする動作をデフォルトにします
|
||||
setting_new_features_policy: Fedibirdに新しい機能が追加された時の受け入れポリシーを設定します。推奨設定は多くの新機能を有効にするので、望ましくない場合は無効に設定してください
|
||||
|
@ -270,15 +274,19 @@ ja:
|
|||
setting_enable_federated_timeline: 連合タイムラインを有効にする
|
||||
setting_enable_limited_timeline: 限定ホームを有効にする
|
||||
setting_enable_local_timeline: ローカルタイムラインを有効にする
|
||||
setting_enable_personal_timeline: 自分限定ホームを有効にする
|
||||
setting_enable_reaction: リアクションを有効にする
|
||||
setting_enable_status_reference: 参照を有効にする
|
||||
setting_expand_spoilers: 閲覧注意としてマークされた投稿を常に展開する
|
||||
setting_follow_button_to_list_adder: フォローボタンでリスト追加ダイアログを開く
|
||||
setting_hexagon_avatar: NFTアイコンを体験する
|
||||
setting_hide_bot_on_public_timeline: 公開タイムラインのBotアカウントを非表示
|
||||
setting_hide_direct_from_timeline: ダイレクトメッセージをタイムラインから隠す
|
||||
setting_hide_followers_count: フォロワー数を隠す
|
||||
setting_hide_following_count: フォロー数を隠す
|
||||
setting_hide_network: 繋がりを隠す
|
||||
setting_hide_personal_from_account: 自分限定投稿をアカウントの投稿一覧から隠す
|
||||
setting_hide_personal_from_timeline: 自分限定投稿をタイムラインから隠す
|
||||
setting_hide_statuses_count: 投稿数を隠す
|
||||
setting_info_font_size: 情報ヘッダのフォントサイズ
|
||||
setting_match_visibility_of_references: 投稿の公開範囲を参照先に合わせる
|
||||
|
|
|
@ -379,6 +379,7 @@ Rails.application.routes.draw do
|
|||
resources :tag, only: :show
|
||||
resources :list, only: :show
|
||||
resources :group, only: :show
|
||||
resource :personal, only: :show, controller: :personal
|
||||
end
|
||||
|
||||
resources :streaming, only: [:index]
|
||||
|
|
|
@ -55,6 +55,8 @@ defaults: &defaults
|
|||
show_tab_bar_label: false
|
||||
enable_federated_timeline: true
|
||||
enable_limited_timeline: false
|
||||
enable_personal_timeline: false
|
||||
enable_personal_account: false
|
||||
enable_local_timeline: true
|
||||
enable_reaction: true
|
||||
compact_reaction: false
|
||||
|
@ -129,6 +131,8 @@ defaults: &defaults
|
|||
prohibited_visibilities: []
|
||||
prohibited_words: ''
|
||||
disable_relative_time: false
|
||||
hide_direct_from_timeline: false
|
||||
hide_personal_from_timeline: false
|
||||
|
||||
development:
|
||||
<<: *defaults
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
class AddPersonalTimelineIndex < ActiveRecord::Migration[6.1]
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
safety_assured { add_index :statuses, [:account_id, :id], where: 'visibility = 200 AND deleted_at IS NULL AND reblog_of_id IS NULL', order: { id: :desc }, algorithm: :concurrently, name: :index_statuses_personal_timeline }
|
||||
end
|
||||
|
||||
def down
|
||||
remove_index :statuses, name: :index_statuses_personal_timeline
|
||||
end
|
||||
end
|
|
@ -1007,6 +1007,7 @@ ActiveRecord::Schema.define(version: 2023_01_29_193248) do
|
|||
t.index ["account_id", "id"], name: "index_statuses_private_searchable", order: { id: :desc }, where: "((deleted_at IS NULL) AND (expired_at IS NULL) AND (reblog_of_id IS NULL) AND (searchability = ANY (ARRAY[0, 1, 2])))"
|
||||
t.index ["id", "account_id"], name: "index_statuses_local_20190824", order: { id: :desc }, where: "((local OR (uri IS NULL)) AND (deleted_at IS NULL) AND (visibility = 0) AND (reblog_of_id IS NULL) AND ((NOT reply) OR (in_reply_to_account_id = account_id)))"
|
||||
t.index ["id", "account_id"], name: "index_statuses_public_20200119", order: { id: :desc }, where: "((deleted_at IS NULL) AND (visibility = 0) AND (reblog_of_id IS NULL) AND ((NOT reply) OR (in_reply_to_account_id = account_id)))"
|
||||
t.index ["account_id", "id"], name: "index_statuses_personal_timeline", order: :desc, where: "((visibility = 200) AND (deleted_at IS NULL) AND (reblog_of_id IS NULL))"
|
||||
t.index ["in_reply_to_account_id"], name: "index_statuses_on_in_reply_to_account_id"
|
||||
t.index ["in_reply_to_id"], name: "index_statuses_on_in_reply_to_id"
|
||||
t.index ["quote_id"], name: "index_statuses_on_quote_id"
|
||||
|
|
Loading…
Reference in New Issue