+
{children}
- {layout !== 'mobile' &&
}
+ {layout !== "mobile" &&
}
-
+
@@ -554,5 +808,4 @@ class UI extends React.PureComponent {
);
}
-
}
diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js
index 76e48a52d43c50..354e97a228c51e 100644
--- a/app/javascript/mastodon/features/ui/util/async-components.js
+++ b/app/javascript/mastodon/features/ui/util/async-components.js
@@ -30,6 +30,10 @@ export function DirectTimeline() {
return import(/* webpackChunkName: "features/direct_timeline" */'../../direct_timeline');
}
+export function LimitedTimeline() {
+ return import(/* webpackChunkName: "features/limited_timeline" */'../../limited_timeline');
+}
+
export function ListTimeline () {
return import(/* webpackChunkName: "features/list_timeline" */'../../list_timeline');
}
diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js
index 1389a3c3dff2bb..7629906b909907 100644
--- a/app/javascript/mastodon/initial_state.js
+++ b/app/javascript/mastodon/initial_state.js
@@ -1,31 +1,49 @@
-const element = document.getElementById('initial-state');
+const element = document.getElementById("initial-state");
const initialState = element && JSON.parse(element.textContent);
-const getMeta = (prop) => initialState && initialState.meta && initialState.meta[prop];
+const getMeta = (prop) =>
+ initialState && initialState.meta && initialState.meta[prop];
-export const reduceMotion = getMeta('reduce_motion');
-export const autoPlayGif = getMeta('auto_play_gif');
-export const displayMedia = getMeta('display_media');
-export const expandSpoilers = getMeta('expand_spoilers');
-export const unfollowModal = getMeta('unfollow_modal');
-export const boostModal = getMeta('boost_modal');
-export const deleteModal = getMeta('delete_modal');
-export const me = getMeta('me');
-export const searchEnabled = getMeta('search_enabled');
-export const invitesEnabled = getMeta('invites_enabled');
-export const limitedFederationMode = getMeta('limited_federation_mode');
-export const repository = getMeta('repository');
-export const source_url = getMeta('source_url');
-export const version = getMeta('version');
-export const mascot = getMeta('mascot');
-export const profile_directory = getMeta('profile_directory');
-export const isStaff = getMeta('is_staff');
-export const forceSingleColumn = !getMeta('advanced_layout');
-export const useBlurhash = getMeta('use_blurhash');
-export const usePendingItems = getMeta('use_pending_items');
-export const showTrends = getMeta('trends');
-export const title = getMeta('title');
-export const cropImages = getMeta('crop_images');
-export const disableSwiping = getMeta('disable_swiping');
+export const reduceMotion = getMeta("reduce_motion");
+export const autoPlayGif = getMeta("auto_play_gif");
+export const displayMedia = getMeta("display_media");
+export const expandSpoilers = getMeta("expand_spoilers");
+export const unfollowModal = getMeta("unfollow_modal");
+export const boostModal = getMeta("boost_modal");
+export const deleteModal = getMeta("delete_modal");
+export const me = getMeta("me");
+export const searchEnabled = getMeta("search_enabled");
+export const invitesEnabled = getMeta("invites_enabled");
+export const limitedFederationMode = getMeta("limited_federation_mode");
+export const repository = getMeta("repository");
+export const source_url = getMeta("source_url");
+export const version = getMeta("version");
+export const mascot = getMeta("mascot");
+export const profile_directory = getMeta("profile_directory");
+export const isStaff = getMeta("is_staff");
+export const forceSingleColumn = !getMeta("advanced_layout");
+export const useBlurhash = getMeta("use_blurhash");
+export const usePendingItems = getMeta("use_pending_items");
+export const showTrends = getMeta("trends");
+export const title = getMeta("title");
+export const cropImages = getMeta("crop_images");
+export const disableSwiping = getMeta("disable_swiping");
+export const show_follow_button_on_timeline = getMeta(
+ "show_follow_button_on_timeline"
+);
+export const show_subscribe_button_on_timeline = getMeta(
+ "show_subscribe_button_on_timeline"
+);
+export const show_followed_by = getMeta("show_followed_by");
+export const follow_button_to_list_adder = getMeta(
+ "follow_button_to_list_adder"
+);
+export const show_navigation_panel = getMeta("show_navigation_panel");
+export const show_quote_button = getMeta("show_quote_button");
+export const show_bookmark_button = getMeta("show_bookmark_button");
+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 enable_limited_timeline = getMeta("enable_limited_timeline");
export default initialState;
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index d0a6f5af6a0b2b..00be92f7c497d8 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -88,6 +88,7 @@
"column.favourites": "Favourites",
"column.follow_requests": "Follow requests",
"column.home": "Home",
+ "column.limited": "Limited home",
"column.lists": "Lists",
"column.mutes": "Muted users",
"column.notifications": "Notifications",
@@ -188,7 +189,9 @@
"empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
"empty_column.hashtag": "There is nothing in this hashtag yet.",
"empty_column.home": "Your home timeline is empty! Follow more people to fill it up. {suggestions}",
+ "empty_column.home.public_timeline": "the public timeline",
"empty_column.home.suggestions": "See some suggestions",
+ "empty_column.limited": "Your limited timeline is empty.",
"empty_column.list": "There is nothing in this list yet. When members of this list publish new posts, they will appear here.",
"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.",
@@ -225,8 +228,12 @@
"hashtag.column_settings.tag_mode.none": "None of these",
"hashtag.column_settings.tag_toggle": "Include additional tags for this column",
"home.column_settings.basic": "Basic",
+ "home.column_settings.show_direct": "Show direct",
+ "home.column_settings.show_limited": "Show limited",
+ "home.column_settings.show_private": "Show private",
"home.column_settings.show_reblogs": "Show boosts",
"home.column_settings.show_replies": "Show replies",
+ "home.column_settings.visibility": "Visibility",
"home.hide_announcements": "Hide announcements",
"home.show_announcements": "Show announcements",
"intervals.full.days": "{number, plural, one {# day} other {# days}}",
@@ -271,6 +278,13 @@
"lightbox.expand": "Expand image view box",
"lightbox.next": "Next",
"lightbox.previous": "Previous",
+ "limited.column_settings.basic": "Basic",
+ "limited.column_settings.show_direct": "Show direct",
+ "limited.column_settings.show_limited": "Show limited",
+ "limited.column_settings.show_private": "Show private",
+ "limited.column_settings.show_reblogs": "Show boosts",
+ "limited.column_settings.show_replies": "Show replies",
+ "limited.column_settings.visibility": "Visibility",
"lists.account.add": "Add to list",
"lists.account.remove": "Remove from list",
"lists.delete": "Delete list",
@@ -308,6 +322,7 @@
"navigation_bar.follows_and_followers": "Follows and followers",
"navigation_bar.info": "About this server",
"navigation_bar.keyboard_shortcuts": "Hotkeys",
+ "navigation_bar.limited_timeline": "Limited home",
"navigation_bar.lists": "Lists",
"navigation_bar.logout": "Logout",
"navigation_bar.mutes": "Muted users",
@@ -316,6 +331,18 @@
"navigation_bar.preferences": "Preferences",
"navigation_bar.public_timeline": "Federated timeline",
"navigation_bar.security": "Security",
+ "navigation_bar.short.community_timeline": "LTL",
+ "navigation_bar.short.getting_started": "Started",
+ "navigation_bar.short.home": "Home",
+ "navigation_bar.short.limited_timeline": "Ltd.",
+ "navigation_bar.short.lists": "Lists",
+ "navigation_bar.short.logout": "Logout",
+ "navigation_bar.short.notifications": "Notif.",
+ "navigation_bar.short.preferences": "Pref.",
+ "navigation_bar.short.public_timeline": "FTL",
+ "navigation_bar.short.search": "Search",
+ "navigation_bar.suggestions": "Suggestions",
+ "navigation_bar.trends": "Trends",
"notification.favourite": "{name} favourited your post",
"notification.follow": "{name} followed you",
"notification.follow_request": "{name} has requested to follow you",
@@ -456,6 +483,11 @@
"suggestions.header": "You might be interested in…",
"tabs_bar.federated_timeline": "Federated",
"tabs_bar.home": "Home",
+<<<<<<< HEAD
+=======
+ "tabs_bar.limited_timeline": "Limited",
+ "tabs_bar.lists": "List",
+>>>>>>> 4d681b84c (Add limited timeline)
"tabs_bar.local_timeline": "Local",
"tabs_bar.notifications": "Notifications",
"tabs_bar.search": "Search",
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
index b3305cf5a1fb5d..5b0225e1777bea 100644
--- a/app/javascript/mastodon/locales/ja.json
+++ b/app/javascript/mastodon/locales/ja.json
@@ -88,6 +88,7 @@
"column.favourites": "お気に入り",
"column.follow_requests": "フォローリクエスト",
"column.home": "ホーム",
+ "column.limited": "限定ホーム",
"column.lists": "リスト",
"column.mutes": "ミュートしたユーザー",
"column.notifications": "通知",
@@ -189,6 +190,7 @@
"empty_column.hashtag": "このハッシュタグはまだ使われていません。",
"empty_column.home": "ホームタイムラインはまだ空っぽです。誰かフォローして埋めてみましょう。 {suggestions}",
"empty_column.home.suggestions": "おすすめを見る",
+ "empty_column.limited": "まだ誰からも公開範囲が限定された投稿を受け取っていません。",
"empty_column.list": "このリストにはまだなにもありません。このリストのメンバーが新しい投稿をするとここに表示されます。",
"empty_column.lists": "まだリストがありません。リストを作るとここに表示されます。",
"empty_column.mutes": "まだ誰もミュートしていません。",
@@ -225,8 +227,12 @@
"hashtag.column_settings.tag_mode.none": "これらを除く",
"hashtag.column_settings.tag_toggle": "このカラムに追加のタグを含める",
"home.column_settings.basic": "基本設定",
+ "home.column_settings.show_direct": "ダイレクトメッセージを表示",
+ "home.column_settings.show_limited": "サークルを表示",
+ "home.column_settings.show_private": "フォロワー限定を表示",
"home.column_settings.show_reblogs": "ブースト表示",
"home.column_settings.show_replies": "返信表示",
+ "home.column_settings.visibility": "公開範囲",
"home.hide_announcements": "お知らせを隠す",
"home.show_announcements": "お知らせを表示",
"intervals.full.days": "{number}日",
@@ -271,6 +277,13 @@
"lightbox.expand": "画像ビューボックスを開く",
"lightbox.next": "次",
"lightbox.previous": "前",
+ "limited.column_settings.basic": "基本設定",
+ "limited.column_settings.show_direct": "ダイレクトメッセージを表示",
+ "limited.column_settings.show_limited": "サークルを表示",
+ "limited.column_settings.show_private": "フォロワー限定を表示",
+ "limited.column_settings.show_reblogs": "ブースト表示",
+ "limited.column_settings.show_replies": "返信表示",
+ "limited.column_settings.visibility": "公開範囲",
"lists.account.add": "リストに追加",
"lists.account.remove": "リストから外す",
"lists.delete": "リストを削除",
@@ -308,6 +321,7 @@
"navigation_bar.follows_and_followers": "フォロー・フォロワー",
"navigation_bar.info": "このサーバーについて",
"navigation_bar.keyboard_shortcuts": "ホットキー",
+ "navigation_bar.limited_timeline": "限定ホーム",
"navigation_bar.lists": "リスト",
"navigation_bar.logout": "ログアウト",
"navigation_bar.mutes": "ミュートしたユーザー",
@@ -316,6 +330,18 @@
"navigation_bar.preferences": "ユーザー設定",
"navigation_bar.public_timeline": "連合タイムライン",
"navigation_bar.security": "セキュリティ",
+ "navigation_bar.short.community_timeline": "ローカル",
+ "navigation_bar.short.getting_started": "スタート",
+ "navigation_bar.short.home": "ホーム",
+ "navigation_bar.short.limited_timeline": "限定",
+ "navigation_bar.short.lists": "リスト",
+ "navigation_bar.short.logout": "ログアウト",
+ "navigation_bar.short.notifications": "通知",
+ "navigation_bar.short.preferences": "設定",
+ "navigation_bar.short.public_timeline": "連合",
+ "navigation_bar.short.search": "検索",
+ "navigation_bar.suggestions": "おすすめユーザー",
+ "navigation_bar.trends": "トレンド",
"notification.favourite": "{name}さんがあなたの投稿をお気に入りに登録しました",
"notification.follow": "{name}さんにフォローされました",
"notification.follow_request": "{name} さんがあなたにフォローリクエストしました",
@@ -455,6 +481,11 @@
"suggestions.header": "興味あるかもしれません…",
"tabs_bar.federated_timeline": "連合",
"tabs_bar.home": "ホーム",
+<<<<<<< HEAD
+=======
+ "tabs_bar.limited_timeline": "限定ホーム",
+ "tabs_bar.lists": "リスト",
+>>>>>>> 4d681b84c (Add limited timeline)
"tabs_bar.local_timeline": "ローカル",
"tabs_bar.notifications": "通知",
"tabs_bar.search": "検索",
diff --git a/app/javascript/mastodon/reducers/settings.js b/app/javascript/mastodon/reducers/settings.js
index 2a89919e1f02eb..9d772f60112fe3 100644
--- a/app/javascript/mastodon/reducers/settings.js
+++ b/app/javascript/mastodon/reducers/settings.js
@@ -20,6 +20,23 @@ const initialState = ImmutableMap({
shows: ImmutableMap({
reblog: true,
reply: true,
+ private: false,
+ limited: false,
+ direct: false,
+ }),
+
+ regex: ImmutableMap({
+ body: '',
+ }),
+ }),
+
+ limited: ImmutableMap({
+ shows: ImmutableMap({
+ reblog: true,
+ reply: true,
+ private: true,
+ limited: true,
+ direct: true,
}),
regex: ImmutableMap({
diff --git a/app/javascript/mastodon/reducers/timelines.js b/app/javascript/mastodon/reducers/timelines.js
index b66c19fd5e8378..284e1a52957bd3 100644
--- a/app/javascript/mastodon/reducers/timelines.js
+++ b/app/javascript/mastodon/reducers/timelines.js
@@ -10,14 +10,14 @@ import {
TIMELINE_DISCONNECT,
TIMELINE_LOAD_PENDING,
TIMELINE_MARK_AS_PARTIAL,
-} from '../actions/timelines';
+} from "../actions/timelines";
import {
ACCOUNT_BLOCK_SUCCESS,
ACCOUNT_MUTE_SUCCESS,
ACCOUNT_UNFOLLOW_SUCCESS,
-} from '../actions/accounts';
-import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
-import compareId from '../compare_id';
+} from "../actions/accounts";
+import { Map as ImmutableMap, List as ImmutableList, fromJS } from "immutable";
+import compareId from "../compare_id";
const initialState = ImmutableMap();
@@ -31,51 +31,94 @@ const initialTimeline = ImmutableMap({
items: ImmutableList(),
});
-const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, isLoadingRecent, usePendingItems) => {
- return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
- mMap.set('isLoading', false);
- mMap.set('isPartial', isPartial);
+const expandNormalizedTimeline = (
+ state,
+ timeline,
+ statuses,
+ next,
+ isPartial,
+ isLoadingRecent,
+ usePendingItems
+) => {
+ return state.update(timeline, initialTimeline, (map) =>
+ map.withMutations((mMap) => {
+ mMap.set("isLoading", false);
+ mMap.set("isPartial", isPartial);
- if (!next && !isLoadingRecent) mMap.set('hasMore', false);
+ if (!next && !isLoadingRecent) mMap.set("hasMore", false);
- if (timeline.endsWith(':pinned')) {
- mMap.set('items', statuses.map(status => status.get('id')));
- } else if (!statuses.isEmpty()) {
- usePendingItems = isLoadingRecent && (usePendingItems || !mMap.get('pendingItems').isEmpty());
+ if (timeline.endsWith(":pinned")) {
+ mMap.set(
+ "items",
+ statuses.map((status) => status.get("id"))
+ );
+ } else if (!statuses.isEmpty()) {
+ usePendingItems =
+ isLoadingRecent &&
+ (usePendingItems || !mMap.get("pendingItems").isEmpty());
- mMap.update(usePendingItems ? 'pendingItems' : 'items', ImmutableList(), oldIds => {
- const newIds = statuses.map(status => status.get('id'));
+ mMap.update(
+ usePendingItems ? "pendingItems" : "items",
+ ImmutableList(),
+ (oldIds) => {
+ const newIds = statuses.map((status) => status.get("id"));
- const lastIndex = oldIds.findLastIndex(id => id !== null && compareId(id, newIds.last()) >= 0) + 1;
- const firstIndex = oldIds.take(lastIndex).findLastIndex(id => id !== null && compareId(id, newIds.first()) > 0);
+ const lastIndex =
+ oldIds.findLastIndex(
+ (id) => id !== null && compareId(id, newIds.last()) >= 0
+ ) + 1;
+ const firstIndex = oldIds
+ .take(lastIndex)
+ .findLastIndex(
+ (id) => id !== null && compareId(id, newIds.first()) > 0
+ );
- if (firstIndex < 0) {
- return (isPartial ? newIds.unshift(null) : newIds).concat(oldIds.skip(lastIndex));
- }
+ if (firstIndex < 0) {
+ return (isPartial ? newIds.unshift(null) : newIds).concat(
+ oldIds.skip(lastIndex)
+ );
+ }
- return oldIds.take(firstIndex + 1).concat(
- isPartial && oldIds.get(firstIndex) !== null ? newIds.unshift(null) : newIds,
- oldIds.skip(lastIndex),
+ return oldIds
+ .take(firstIndex + 1)
+ .concat(
+ isPartial && oldIds.get(firstIndex) !== null
+ ? newIds.unshift(null)
+ : newIds,
+ oldIds.skip(lastIndex)
+ );
+ }
);
- });
- }
- }));
+ }
+ })
+ );
};
const updateTimeline = (state, timeline, status, usePendingItems) => {
- const top = state.getIn([timeline, 'top']);
+ const top = state.getIn([timeline, "top"]);
- if (usePendingItems || !state.getIn([timeline, 'pendingItems']).isEmpty()) {
- if (state.getIn([timeline, 'pendingItems'], ImmutableList()).includes(status.get('id')) || state.getIn([timeline, 'items'], ImmutableList()).includes(status.get('id'))) {
+ if (usePendingItems || !state.getIn([timeline, "pendingItems"]).isEmpty()) {
+ if (
+ state
+ .getIn([timeline, "pendingItems"], ImmutableList())
+ .includes(status.get("id")) ||
+ state
+ .getIn([timeline, "items"], ImmutableList())
+ .includes(status.get("id"))
+ ) {
return state;
}
- return state.update(timeline, initialTimeline, map => map.update('pendingItems', list => list.unshift(status.get('id'))).update('unread', unread => unread + 1));
+ return state.update(timeline, initialTimeline, (map) =>
+ map
+ .update("pendingItems", (list) => list.unshift(status.get("id")))
+ .update("unread", (unread) => unread + 1)
+ );
}
- const ids = state.getIn([timeline, 'items'], ImmutableList());
- const includesId = ids.includes(status.get('id'));
- const unread = state.getIn([timeline, 'unread'], 0);
+ const ids = state.getIn([timeline, "items"], ImmutableList());
+ const includesId = ids.includes(status.get("id"));
+ const unread = state.getIn([timeline, "unread"], 0);
if (includesId) {
return state;
@@ -83,23 +126,31 @@ const updateTimeline = (state, timeline, status, usePendingItems) => {
let newIds = ids;
- return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
- if (!top) mMap.set('unread', unread + 1);
- if (top && ids.size > 40) newIds = newIds.take(20);
- mMap.set('items', newIds.unshift(status.get('id')));
- }));
+ return state.update(timeline, initialTimeline, (map) =>
+ map.withMutations((mMap) => {
+ if (!top) mMap.set("unread", unread + 1);
+ if (top && ids.size > 40) newIds = newIds.take(20);
+ mMap.set("items", newIds.unshift(status.get("id")));
+ })
+ );
};
const deleteStatus = (state, id, references, exclude_account = null) => {
- state.keySeq().forEach(timeline => {
- if (exclude_account === null || (timeline !== `account:${exclude_account}` && !timeline.startsWith(`account:${exclude_account}:`))) {
- const helper = list => list.filterNot(item => item === id);
- state = state.updateIn([timeline, 'items'], helper).updateIn([timeline, 'pendingItems'], helper);
+ state.keySeq().forEach((timeline) => {
+ if (
+ exclude_account === null ||
+ (timeline !== `account:${exclude_account}` &&
+ !timeline.startsWith(`account:${exclude_account}:`))
+ ) {
+ const helper = (list) => list.filterNot((item) => item === id);
+ state = state
+ .updateIn([timeline, "items"], helper)
+ .updateIn([timeline, "pendingItems"], helper);
}
});
// Remove reblogs of deleted status
- references.forEach(ref => {
+ references.forEach((ref) => {
state = deleteStatus(state, ref, [], exclude_account);
});
@@ -113,69 +164,113 @@ const clearTimeline = (state, timeline) => {
const filterTimelines = (state, relationship, statuses) => {
let references;
- statuses.forEach(status => {
- if (status.get('account') !== relationship.id) {
+ statuses.forEach((status) => {
+ if (status.get("account") !== relationship.id) {
return;
}
- references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => item.get('id'));
- state = deleteStatus(state, status.get('id'), references, relationship.id);
+ references = statuses
+ .filter((item) => item.get("reblog") === status.get("id"))
+ .map((item) => item.get("id"));
+ state = deleteStatus(state, status.get("id"), references, relationship.id);
});
return state;
};
const filterTimeline = (timeline, state, relationship, statuses) => {
- const helper = list => list.filterNot(statusId => statuses.getIn([statusId, 'account']) === relationship.id);
- return state.updateIn([timeline, 'items'], ImmutableList(), helper).updateIn([timeline, 'pendingItems'], ImmutableList(), helper);
+ const helper = (list) =>
+ list.filterNot(
+ (statusId) => statuses.getIn([statusId, "account"]) === relationship.id
+ );
+ return state
+ .updateIn([timeline, "items"], ImmutableList(), helper)
+ .updateIn([timeline, "pendingItems"], ImmutableList(), helper);
};
const updateTop = (state, timeline, top) => {
- return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
- if (top) mMap.set('unread', mMap.get('pendingItems').size);
- mMap.set('top', top);
- }));
+ return state.update(timeline, initialTimeline, (map) =>
+ map.withMutations((mMap) => {
+ if (top) mMap.set("unread", mMap.get("pendingItems").size);
+ mMap.set("top", top);
+ })
+ );
};
export default function timelines(state = initialState, action) {
- switch(action.type) {
- case TIMELINE_LOAD_PENDING:
- return state.update(action.timeline, initialTimeline, map =>
- map.update('items', list => map.get('pendingItems').concat(list.take(40))).set('pendingItems', ImmutableList()).set('unread', 0));
- case TIMELINE_EXPAND_REQUEST:
- return state.update(action.timeline, initialTimeline, map => map.set('isLoading', true));
- case TIMELINE_EXPAND_FAIL:
- return state.update(action.timeline, initialTimeline, map => map.set('isLoading', false));
- case TIMELINE_EXPAND_SUCCESS:
- return expandNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial, action.isLoadingRecent, action.usePendingItems);
- case TIMELINE_UPDATE:
- return updateTimeline(state, action.timeline, fromJS(action.status), action.usePendingItems);
- case TIMELINE_DELETE:
- return deleteStatus(state, action.id, action.references, action.reblogOf);
- case TIMELINE_CLEAR:
- return clearTimeline(state, action.timeline);
- case ACCOUNT_BLOCK_SUCCESS:
- case ACCOUNT_MUTE_SUCCESS:
- return filterTimelines(state, action.relationship, action.statuses);
- case ACCOUNT_UNFOLLOW_SUCCESS:
- return filterTimeline('home', state, action.relationship, action.statuses);
- case TIMELINE_SCROLL_TOP:
- return updateTop(state, action.timeline, action.top);
- case TIMELINE_CONNECT:
- return state.update(action.timeline, initialTimeline, map => map.set('online', true));
- case TIMELINE_DISCONNECT:
- return state.update(
- action.timeline,
- initialTimeline,
- map => map.set('online', false).update(action.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(null) : items),
- );
- case TIMELINE_MARK_AS_PARTIAL:
- return state.update(
- action.timeline,
- initialTimeline,
- map => map.set('isPartial', true).set('items', ImmutableList()).set('pendingItems', ImmutableList()).set('unread', 0),
- );
- default:
- return state;
+ switch (action.type) {
+ case TIMELINE_LOAD_PENDING:
+ return state.update(action.timeline, initialTimeline, (map) =>
+ map
+ .update("items", (list) =>
+ map.get("pendingItems").concat(list.take(40))
+ )
+ .set("pendingItems", ImmutableList())
+ .set("unread", 0)
+ );
+ case TIMELINE_EXPAND_REQUEST:
+ return state.update(action.timeline, initialTimeline, (map) =>
+ map.set("isLoading", true)
+ );
+ case TIMELINE_EXPAND_FAIL:
+ return state.update(action.timeline, initialTimeline, (map) =>
+ map.set("isLoading", false)
+ );
+ case TIMELINE_EXPAND_SUCCESS:
+ return expandNormalizedTimeline(
+ state,
+ action.timeline,
+ fromJS(action.statuses),
+ action.next,
+ action.partial,
+ action.isLoadingRecent,
+ action.usePendingItems
+ );
+ case TIMELINE_UPDATE:
+ return updateTimeline(
+ state,
+ action.timeline,
+ fromJS(action.status),
+ action.usePendingItems
+ );
+ case TIMELINE_DELETE:
+ return deleteStatus(state, action.id, action.references, action.reblogOf);
+ case TIMELINE_CLEAR:
+ return clearTimeline(state, action.timeline);
+ case ACCOUNT_BLOCK_SUCCESS:
+ case ACCOUNT_MUTE_SUCCESS:
+ return filterTimelines(state, action.relationship, action.statuses);
+ case ACCOUNT_UNFOLLOW_SUCCESS:
+ case ACCOUNT_UNSUBSCRIBE_SUCCESS:
+ return filterTimeline(
+ "home",
+ state,
+ action.relationship,
+ action.statuses
+ ).filterTimeline("limited", state, action.relationship, action.statuses);
+ case TIMELINE_SCROLL_TOP:
+ return updateTop(state, action.timeline, action.top);
+ case TIMELINE_CONNECT:
+ return state.update(action.timeline, initialTimeline, (map) =>
+ map.set("online", true)
+ );
+ case TIMELINE_DISCONNECT:
+ return state.update(action.timeline, initialTimeline, (map) =>
+ map
+ .set("online", false)
+ .update(action.usePendingItems ? "pendingItems" : "items", (items) =>
+ items.first() ? items.unshift(null) : items
+ )
+ );
+ case TIMELINE_MARK_AS_PARTIAL:
+ return state.update(action.timeline, initialTimeline, (map) =>
+ map
+ .set("isPartial", true)
+ .set("items", ImmutableList())
+ .set("pendingItems", ImmutableList())
+ .set("unread", 0)
+ );
+ default:
+ return state;
}
-};
+}
diff --git a/app/javascript/mastodon/selectors/index.js b/app/javascript/mastodon/selectors/index.js
index 1e19db65d027f0..7970ee522dddc0 100644
--- a/app/javascript/mastodon/selectors/index.js
+++ b/app/javascript/mastodon/selectors/index.js
@@ -1,6 +1,6 @@
import { createSelector } from 'reselect';
import { List as ImmutableList, Map as ImmutableMap, is } from 'immutable';
-import { me } from '../initial_state';
+import { me, enable_limited_timeline } from '../initial_state';
const getAccountBase = (state, id) => state.getIn(['accounts', id], null);
const getAccountCounters = (state, id) => state.getIn(['accounts_counters', id], null);
@@ -175,3 +175,25 @@ export const getAccountGallery = createSelector([
return medias;
});
+
+export const getHomeVisibilities = createSelector(
+ state => state.getIn(['settings', 'home', 'shows']),
+ shows => {
+ return enable_limited_timeline ? (
+ ['public', 'unlisted']
+ .concat(shows.get('private') ? ['private'] : [])
+ .concat(shows.get('limited') ? ['limited'] : [])
+ .concat(shows.get('direct') ? ['direct'] : [])
+ ) : [];
+});
+
+export const getLimitedVisibilities = createSelector(
+ state => state.getIn(['settings', 'limited', 'shows']),
+ shows => {
+ return enable_limited_timeline ? (
+ []
+ .concat(shows.get('private') ? ['private'] : [])
+ .concat(shows.get('limited') ? ['limited'] : [])
+ .concat(shows.get('direct') ? ['direct'] : [])
+ ) : [];
+});
diff --git a/app/lib/user_settings_decorator.rb b/app/lib/user_settings_decorator.rb
index e37bc6d9f11437..3874ba44c33ec8 100644
--- a/app/lib/user_settings_decorator.rb
+++ b/app/lib/user_settings_decorator.rb
@@ -15,30 +15,42 @@ def update(settings)
private
def process_update
- user.settings['notification_emails'] = merged_notification_emails if change?('notification_emails')
- user.settings['interactions'] = merged_interactions if change?('interactions')
- user.settings['default_privacy'] = default_privacy_preference if change?('setting_default_privacy')
- user.settings['default_sensitive'] = default_sensitive_preference if change?('setting_default_sensitive')
- user.settings['default_language'] = default_language_preference if change?('setting_default_language')
- user.settings['unfollow_modal'] = unfollow_modal_preference if change?('setting_unfollow_modal')
- user.settings['boost_modal'] = boost_modal_preference if change?('setting_boost_modal')
- user.settings['delete_modal'] = delete_modal_preference if change?('setting_delete_modal')
- user.settings['auto_play_gif'] = auto_play_gif_preference if change?('setting_auto_play_gif')
- user.settings['display_media'] = display_media_preference if change?('setting_display_media')
- user.settings['expand_spoilers'] = expand_spoilers_preference if change?('setting_expand_spoilers')
- user.settings['reduce_motion'] = reduce_motion_preference if change?('setting_reduce_motion')
- user.settings['disable_swiping'] = disable_swiping_preference if change?('setting_disable_swiping')
- user.settings['system_font_ui'] = system_font_ui_preference if change?('setting_system_font_ui')
- user.settings['noindex'] = noindex_preference if change?('setting_noindex')
- user.settings['theme'] = theme_preference if change?('setting_theme')
- user.settings['hide_network'] = hide_network_preference if change?('setting_hide_network')
- user.settings['aggregate_reblogs'] = aggregate_reblogs_preference if change?('setting_aggregate_reblogs')
- user.settings['show_application'] = show_application_preference if change?('setting_show_application')
- user.settings['advanced_layout'] = advanced_layout_preference if change?('setting_advanced_layout')
- user.settings['use_blurhash'] = use_blurhash_preference if change?('setting_use_blurhash')
- user.settings['use_pending_items'] = use_pending_items_preference if change?('setting_use_pending_items')
- user.settings['trends'] = trends_preference if change?('setting_trends')
- user.settings['crop_images'] = crop_images_preference if change?('setting_crop_images')
+ user.settings['notification_emails'] = merged_notification_emails if change?('notification_emails')
+ user.settings['interactions'] = merged_interactions if change?('interactions')
+ user.settings['default_privacy'] = default_privacy_preference if change?('setting_default_privacy')
+ user.settings['default_sensitive'] = default_sensitive_preference if change?('setting_default_sensitive')
+ user.settings['default_language'] = default_language_preference if change?('setting_default_language')
+ user.settings['unfollow_modal'] = unfollow_modal_preference if change?('setting_unfollow_modal')
+ user.settings['unsubscribe_modal'] = unsubscribe_modal_preference if change?('setting_unsubscribe_modal')
+ user.settings['boost_modal'] = boost_modal_preference if change?('setting_boost_modal')
+ user.settings['delete_modal'] = delete_modal_preference if change?('setting_delete_modal')
+ user.settings['auto_play_gif'] = auto_play_gif_preference if change?('setting_auto_play_gif')
+ user.settings['display_media'] = display_media_preference if change?('setting_display_media')
+ user.settings['expand_spoilers'] = expand_spoilers_preference if change?('setting_expand_spoilers')
+ user.settings['reduce_motion'] = reduce_motion_preference if change?('setting_reduce_motion')
+ user.settings['disable_swiping'] = disable_swiping_preference if change?('setting_disable_swiping')
+ user.settings['system_font_ui'] = system_font_ui_preference if change?('setting_system_font_ui')
+ user.settings['noindex'] = noindex_preference if change?('setting_noindex')
+ user.settings['theme'] = theme_preference if change?('setting_theme')
+ user.settings['hide_network'] = hide_network_preference if change?('setting_hide_network')
+ user.settings['aggregate_reblogs'] = aggregate_reblogs_preference if change?('setting_aggregate_reblogs')
+ user.settings['show_application'] = show_application_preference if change?('setting_show_application')
+ user.settings['advanced_layout'] = advanced_layout_preference if change?('setting_advanced_layout')
+ user.settings['use_blurhash'] = use_blurhash_preference if change?('setting_use_blurhash')
+ user.settings['use_pending_items'] = use_pending_items_preference if change?('setting_use_pending_items')
+ user.settings['trends'] = trends_preference if change?('setting_trends')
+ user.settings['crop_images'] = crop_images_preference if change?('setting_crop_images')
+ user.settings['show_follow_button_on_timeline'] = show_follow_button_on_timeline_preference if change?('setting_show_follow_button_on_timeline')
+ user.settings['show_subscribe_button_on_timeline'] = show_subscribe_button_on_timeline_preference if change?('setting_show_subscribe_button_on_timeline')
+ user.settings['show_followed_by'] = show_followed_by_preference if change?('setting_show_followed_by')
+ user.settings['follow_button_to_list_adder'] = follow_button_to_list_adder_preference if change?('setting_follow_button_to_list_adder')
+ user.settings['show_navigation_panel'] = show_navigation_panel_preference if change?('setting_show_navigation_panel')
+ user.settings['show_quote_button'] = show_quote_button_preference if change?('setting_show_quote_button')
+ user.settings['show_bookmark_button'] = show_bookmark_button_preference if change?('setting_show_bookmark_button')
+ user.settings['show_target'] = show_target_preference if change?('setting_show_target')
+ user.settings['place_tab_bar_at_bottom'] = place_tab_bar_at_bottom_preference if change?('setting_place_tab_bar_at_bottom')
+ user.settings['show_tab_bar_label'] = show_tab_bar_label_preference if change?('setting_show_tab_bar_label')
+ user.settings['enable_limited_timeline'] = enable_limited_timeline_preference if change?('setting_enable_limited_timeline')
end
def merged_notification_emails
@@ -137,6 +149,50 @@ def crop_images_preference
boolean_cast_setting 'setting_crop_images'
end
+ def show_follow_button_on_timeline_preference
+ boolean_cast_setting 'setting_show_follow_button_on_timeline'
+ end
+
+ def show_subscribe_button_on_timeline_preference
+ boolean_cast_setting 'setting_show_subscribe_button_on_timeline'
+ end
+
+ def show_followed_by_preference
+ boolean_cast_setting 'setting_show_followed_by'
+ end
+
+ def follow_button_to_list_adder_preference
+ boolean_cast_setting 'setting_follow_button_to_list_adder'
+ end
+
+ def show_navigation_panel_preference
+ boolean_cast_setting 'setting_show_navigation_panel'
+ end
+
+ def show_quote_button_preference
+ boolean_cast_setting 'setting_show_quote_button'
+ end
+
+ def show_bookmark_button_preference
+ boolean_cast_setting 'setting_show_bookmark_button'
+ end
+
+ def show_target_preference
+ boolean_cast_setting 'setting_show_target'
+ end
+
+ def place_tab_bar_at_bottom_preference
+ boolean_cast_setting 'setting_place_tab_bar_at_bottom'
+ end
+
+ def show_tab_bar_label_preference
+ boolean_cast_setting 'setting_show_tab_bar_label'
+ end
+
+ def enable_limited_timeline_preference
+ boolean_cast_setting 'setting_enable_limited_timeline'
+ end
+
def boolean_cast_setting(key)
ActiveModel::Type::Boolean.new.cast(settings[key])
end
diff --git a/app/models/feed.rb b/app/models/feed.rb
index f51dcfab1dad66..28947cae53f2f6 100644
--- a/app/models/feed.rb
+++ b/app/models/feed.rb
@@ -8,27 +8,29 @@ def initialize(type, id)
@id = id
end
- def get(limit, max_id = nil, since_id = nil, min_id = nil)
+ def get(limit, max_id = nil, since_id = nil, min_id = nil, visibilities = [])
limit = limit.to_i
max_id = max_id.to_i if max_id.present?
since_id = since_id.to_i if since_id.present?
min_id = min_id.to_i if min_id.present?
- from_redis(limit, max_id, since_id, min_id)
+ from_redis(limit, max_id, since_id, min_id, visibilities)
end
protected
- def from_redis(limit, max_id, since_id, min_id)
+ def from_redis(limit, max_id, since_id, min_id, visibilities)
max_id = '+inf' if max_id.blank?
if min_id.blank?
since_id = '-inf' if since_id.blank?
- unhydrated = redis.zrevrangebyscore(key, "(#{max_id}", "(#{since_id}", limit: [0, limit], with_scores: true).map(&:first).map(&:to_i)
+ unhydrated = redis.zrevrangebyscore(key, "(#{max_id}", "(#{since_id}", limit: [0, visibilities.empty? ? limit : FeedManager::MAX_ITEMS], with_scores: true).map(&:first).map(&:to_i)
else
- unhydrated = redis.zrangebyscore(key, "(#{min_id}", "(#{max_id}", limit: [0, limit], with_scores: true).map(&:first).map(&:to_i)
+ unhydrated = redis.zrangebyscore(key, "(#{min_id}", "(#{max_id}", limit: [0, visibilities.empty? ? limit : FeedManager::MAX_ITEMS], with_scores: true).map(&:first).map(&:to_i)
end
- Status.where(id: unhydrated).cache_ids
+ statuses = Status.where(id: unhydrated)
+ statuses = statuses.where(visibility: visibilities).limit(limit) unless visibilities.empty?
+ statuses.cache_ids
end
def key
diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb
index b3b913946a706c..638a27aaeecefe 100644
--- a/app/serializers/initial_state_serializer.rb
+++ b/app/serializers/initial_state_serializer.rb
@@ -26,21 +26,33 @@ def meta
}
if object.current_account
- store[:me] = object.current_account.id.to_s
- store[:unfollow_modal] = object.current_account.user.setting_unfollow_modal
- store[:boost_modal] = object.current_account.user.setting_boost_modal
- store[:delete_modal] = object.current_account.user.setting_delete_modal
- store[:auto_play_gif] = object.current_account.user.setting_auto_play_gif
- store[:display_media] = object.current_account.user.setting_display_media
- store[:expand_spoilers] = object.current_account.user.setting_expand_spoilers
- store[:reduce_motion] = object.current_account.user.setting_reduce_motion
- store[:disable_swiping] = object.current_account.user.setting_disable_swiping
- store[:advanced_layout] = object.current_account.user.setting_advanced_layout
- store[:use_blurhash] = object.current_account.user.setting_use_blurhash
- store[:use_pending_items] = object.current_account.user.setting_use_pending_items
- store[:is_staff] = object.current_account.user.staff?
- store[:trends] = Setting.trends && object.current_account.user.setting_trends
- store[:crop_images] = object.current_account.user.setting_crop_images
+ store[:me] = object.current_account.id.to_s
+ store[:unfollow_modal] = object.current_account.user.setting_unfollow_modal
+ store[:unsubscribe_modal] = object.current_account.user.setting_unsubscribe_modal
+ store[:boost_modal] = object.current_account.user.setting_boost_modal
+ store[:delete_modal] = object.current_account.user.setting_delete_modal
+ store[:auto_play_gif] = object.current_account.user.setting_auto_play_gif
+ store[:display_media] = object.current_account.user.setting_display_media
+ store[:expand_spoilers] = object.current_account.user.setting_expand_spoilers
+ store[:reduce_motion] = object.current_account.user.setting_reduce_motion
+ store[:disable_swiping] = object.current_account.user.setting_disable_swiping
+ store[:advanced_layout] = object.current_account.user.setting_advanced_layout
+ store[:use_blurhash] = object.current_account.user.setting_use_blurhash
+ store[:use_pending_items] = object.current_account.user.setting_use_pending_items
+ store[:is_staff] = object.current_account.user.staff?
+ store[:trends] = Setting.trends && object.current_account.user.setting_trends
+ store[:crop_images] = object.current_account.user.setting_crop_images
+ store[:show_follow_button_on_timeline] = object.current_account.user.setting_show_follow_button_on_timeline
+ store[:show_subscribe_button_on_timeline] = object.current_account.user.setting_show_subscribe_button_on_timeline
+ store[:show_followed_by] = object.current_account.user.setting_show_followed_by
+ store[:follow_button_to_list_adder] = object.current_account.user.setting_follow_button_to_list_adder
+ store[:show_navigation_panel] = object.current_account.user.setting_show_navigation_panel
+ store[:show_quote_button] = object.current_account.user.setting_show_quote_button
+ store[:show_bookmark_button] = object.current_account.user.setting_show_bookmark_button
+ store[:show_target] = object.current_account.user.setting_show_target
+ store[:place_tab_bar_at_bottom] = object.current_account.user.setting_place_tab_bar_at_bottom
+ store[:show_tab_bar_label] = object.current_account.user.setting_show_tab_bar_label
+ store[:enable_limited_timeline] = object.current_account.user.setting_enable_limited_timeline
else
store[:auto_play_gif] = Setting.auto_play_gif
store[:display_media] = Setting.display_media
diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml
index 0157939897b995..bd1dbc4daa120b 100644
--- a/config/locales/simple_form.en.yml
+++ b/config/locales/simple_form.en.yml
@@ -52,6 +52,8 @@ en:
setting_display_media_default: Hide media marked as sensitive
setting_display_media_hide_all: Always hide media
setting_display_media_show_all: Always show media
+ setting_enable_limited_timeline: Enable a limited home to display private and circle and direct message
+ 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_hide_network: Who you follow and who follows you will be hidden on your profile
setting_noindex: Affects your public profile and post pages
setting_show_application: The application you use to post will be displayed in the detailed view of your posts
@@ -65,7 +67,7 @@ en:
domain: This can be the domain name that shows up in the e-mail address, the MX record that domain resolves to, or IP of the server that MX record resolves to. Those will be checked upon user sign-up and the sign-up will be rejected.
with_dns_records: An attempt to resolve the given domain's DNS records will be made and the results will also be blocked
featured_tag:
- name: 'You might want to use one of these:'
+ name: "You might want to use one of these:"
form_challenge:
current_password: You are entering a secure area
imports:
@@ -83,7 +85,7 @@ en:
rule:
text: Describe a rule or requirement for users on this server. Try to keep it short and simple
sessions:
- otp: 'Enter the two-factor code generated by your phone app or use one of your recovery codes:'
+ otp: "Enter the two-factor code generated by your phone app or use one of your recovery codes:"
webauthn: If it's an USB key be sure to insert it and, if necessary, tap it.
tag:
name: You can only change the casing of the letters, for example, to make it more readable
@@ -160,6 +162,7 @@ en:
setting_display_media_default: Default
setting_display_media_hide_all: Hide all
setting_display_media_show_all: Show all
+ setting_enable_limited_timeline: Enable limited timeline
setting_expand_spoilers: Always expand posts marked with content warnings
setting_hide_network: Hide your social graph
setting_noindex: Opt-out of search engine indexing
@@ -213,7 +216,7 @@ en:
name: Hashtag
trendable: Allow this hashtag to appear under trends
usable: Allow posts to use this hashtag
- 'no': 'No'
+ "no": "No"
recommended: Recommended
required:
mark: "*"
@@ -221,4 +224,4 @@ en:
title:
sessions:
webauthn: Use one of your security keys to sign in
- 'yes': 'Yes'
+ "yes": "Yes"
diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml
index 4c4133bafee398..3fd4fb75fe8f9b 100644
--- a/config/locales/simple_form.ja.yml
+++ b/config/locales/simple_form.ja.yml
@@ -51,6 +51,8 @@ ja:
setting_display_media_default: 閲覧注意としてマークされたメディアは隠す
setting_display_media_hide_all: メディアを常に隠す
setting_display_media_show_all: メディアを常に表示する
+ setting_enable_limited_timeline: フォロワー限定・サークル・ダイレクトメッセージを表示する限定ホームを有効にします
+ setting_follow_button_to_list_adder: フォロー・購読ボタンの動作を変更し、フォロー・購読するリストを選択したり、ホームで受け取らないよう設定するダイアログを開きます
setting_hide_network: フォローとフォロワーの情報がプロフィールページで見られないようにします
setting_noindex: 公開プロフィールおよび各投稿ページに影響します
setting_show_application: 投稿するのに使用したアプリが投稿の詳細ビューに表示されるようになります
@@ -64,7 +66,7 @@ ja:
domain: メールアドレスのドメイン名および、名前解決したMXレコード、IPアドレスを指定できます。ユーザー登録時にこれらをチェックし、該当する場合はユーザー登録を拒否します。
with_dns_records: 指定したドメインのDNSレコードを取得し、その結果もメールドメインブロックに登録されます
featured_tag:
- name: 'これらを使うといいかもしれません:'
+ name: "これらを使うといいかもしれません:"
form_challenge:
current_password: セキュリティ上重要なエリアにアクセスしています
imports:
@@ -82,7 +84,7 @@ ja:
rule:
text: ユーザーのためのルールや要件を記述してください。短くシンプルにしてください。
sessions:
- otp: '携帯電話のアプリで生成された二段階認証コードを入力するか、リカバリーコードを使用してください:'
+ otp: "携帯電話のアプリで生成された二段階認証コードを入力するか、リカバリーコードを使用してください:"
webauthn: USBキーの場合は、必ず挿入し、必要に応じてタップしてください。
tag:
name: 視認性向上などのためにアルファベット大文字小文字の変更のみ行うことができます
@@ -159,6 +161,7 @@ ja:
setting_display_media_default: 標準
setting_display_media_hide_all: 非表示
setting_display_media_show_all: 表示
+ setting_enable_limited_timeline: 限定ホームを有効にする
setting_expand_spoilers: 閲覧注意としてマークされた投稿を常に展開する
setting_hide_network: 繋がりを隠す
setting_noindex: 検索エンジンによるインデックスを拒否する
@@ -212,7 +215,7 @@ ja:
name: ハッシュタグ
trendable: トレンドへの表示を許可する
usable: 投稿への使用を許可する
- 'no': いいえ
+ "no": いいえ
recommended: おすすめ
required:
mark: "*"
@@ -220,4 +223,4 @@ ja:
title:
sessions:
webauthn: セキュリティキーを使用してサインインする
- 'yes': はい
+ "yes": はい
diff --git a/config/settings.yml b/config/settings.yml
index 06cee253240fac..4d2c34367d716b 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -3,17 +3,17 @@
defaults: &defaults
site_title: Mastodon
- site_short_description: ''
- site_description: ''
- site_extended_description: ''
- site_terms: ''
- site_contact_username: ''
- site_contact_email: ''
- registrations_mode: 'open'
+ site_short_description: ""
+ site_description: ""
+ site_extended_description: ""
+ site_terms: ""
+ site_contact_username: ""
+ site_contact_email: ""
+ registrations_mode: "open"
profile_directory: true
- closed_registrations_message: ''
+ closed_registrations_message: ""
open_deletion: true
- min_invite_role: 'admin'
+ min_invite_role: "admin"
timeline_preview: true
show_staff_badge: true
default_sensitive: false
@@ -22,7 +22,7 @@ defaults: &defaults
boost_modal: false
delete_modal: true
auto_play_gif: false
- display_media: 'default'
+ display_media: "default"
expand_spoilers: false
preview_sensitive_media: false
reduce_motion: false
@@ -30,7 +30,7 @@ defaults: &defaults
show_application: true
system_font_ui: false
noindex: false
- theme: 'default'
+ theme: "default"
aggregate_reblogs: true
advanced_layout: false
use_blurhash: true
@@ -38,6 +38,17 @@ defaults: &defaults
trends: true
trendable_by_default: false
crop_images: true
+ show_follow_button_on_timeline: false
+ show_subscribe_button_on_timeline: false
+ show_followed_by: false
+ follow_button_to_list_adder: false
+ show_navigation_panel: true
+ show_bookmark_button: true
+ show_quote_button: true
+ show_target: false
+ place_tab_bar_at_bottom: false
+ show_tab_bar_label: false
+ enable_limited_timeline: false
notification_emails:
follow: false
reblog: false
@@ -62,12 +73,12 @@ defaults: &defaults
- mod
- moderator
disallowed_hashtags: # space separated string or list of hashtags without the hash
- bootstrap_timeline_accounts: ''
+ bootstrap_timeline_accounts: ""
activity_api_enabled: true
peers_api_enabled: true
show_known_fediverse_at_about_page: true
- show_domain_blocks: 'disabled'
- show_domain_blocks_rationale: 'disabled'
+ show_domain_blocks: "disabled"
+ show_domain_blocks_rationale: "disabled"
require_invite_text: false
development: