From 25cc000a97a05feb3ffc06fc0eab65c7e63253ce Mon Sep 17 00:00:00 2001 From: whippyshou Date: Fri, 3 Nov 2023 06:01:48 +0900 Subject: [PATCH] add local only timeline --- .../mastodon/actions/notifications.js | 2 +- .../components/column_settings.tsx | 44 ++++ .../components/explore_prompt.tsx | 14 ++ .../features/local_timeline/index.jsx | 195 ++++++++++++++++++ .../notification_container_with_dm.js | 74 ------- .../notification_container_without_dm.js | 74 ------- .../report/components/status_check_box.jsx | 4 +- .../ui/components/navigation_panel.jsx | 2 +- .../containers/local_status_list_container.js | 66 ++++++ .../ui/containers/status_list_container.js | 3 +- app/javascript/mastodon/features/ui/index.jsx | 8 +- .../features/ui/util/async-components.js | 3 + app/javascript/mastodon/locales/en.json | 8 + app/javascript/mastodon/locales/ko.json | 8 + app/javascript/mastodon/reducers/settings.js | 13 +- .../layout-multiple-columns.scss | 11 +- .../layout-single-column.scss | 10 +- .../layout-multiple-columns.scss | 12 +- .../layout-single-column.scss | 10 +- .../preferences/appearance/show.html.haml | 3 +- config/routes.rb | 1 + 21 files changed, 397 insertions(+), 168 deletions(-) create mode 100644 app/javascript/mastodon/features/local_timeline/components/column_settings.tsx create mode 100644 app/javascript/mastodon/features/local_timeline/components/explore_prompt.tsx create mode 100644 app/javascript/mastodon/features/local_timeline/index.jsx delete mode 100644 app/javascript/mastodon/features/notifications/containers/notification_container_with_dm.js delete mode 100644 app/javascript/mastodon/features/notifications/containers/notification_container_without_dm.js create mode 100644 app/javascript/mastodon/features/ui/containers/local_status_list_container.js diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js index fc9f25b2b..f9e9cc4d1 100644 --- a/app/javascript/mastodon/actions/notifications.js +++ b/app/javascript/mastodon/actions/notifications.js @@ -142,7 +142,7 @@ const excludeTypesFromFilter = filter => { 'admin.report', ]); - return allTypes.filterNot(item => filter === 'direct'? item ==='mention' :item === filter ).toJS(); + return allTypes.filterNot(item => filter === 'direct'? item ==='mention':item === filter ).toJS(); }; const noOp = () => {}; diff --git a/app/javascript/mastodon/features/local_timeline/components/column_settings.tsx b/app/javascript/mastodon/features/local_timeline/components/column_settings.tsx new file mode 100644 index 000000000..4e45b22b9 --- /dev/null +++ b/app/javascript/mastodon/features/local_timeline/components/column_settings.tsx @@ -0,0 +1,44 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call, + @typescript-eslint/no-unsafe-return, + @typescript-eslint/no-unsafe-assignment, + @typescript-eslint/no-unsafe-member-access + -- the settings store is not yet typed */ +import { useCallback } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { useAppSelector, useAppDispatch } from 'mastodon/store'; + +import { changeSetting } from '../../../actions/settings'; +import SettingToggle from '../../notifications/components/setting_toggle'; + +export const ColumnSettings: React.FC = () => { + const settings = useAppSelector((state) => state.settings.get('local')); + + const dispatch = useAppDispatch(); + const onChange = useCallback( + (key: string, checked: boolean) => { + dispatch(changeSetting(['local', ...key], checked)); + }, + [dispatch], + ); + + return ( +
+
+ + } + /> +
+
+ ); +}; diff --git a/app/javascript/mastodon/features/local_timeline/components/explore_prompt.tsx b/app/javascript/mastodon/features/local_timeline/components/explore_prompt.tsx new file mode 100644 index 000000000..4478a156d --- /dev/null +++ b/app/javascript/mastodon/features/local_timeline/components/explore_prompt.tsx @@ -0,0 +1,14 @@ +import { FormattedMessage } from 'react-intl'; +import { DismissableBanner } from 'mastodon/components/dismissable_banner'; +import { title } from 'mastodon/initial_state'; + +export const ExplorePrompt = () => ( + +

+ +

+
+); diff --git a/app/javascript/mastodon/features/local_timeline/index.jsx b/app/javascript/mastodon/features/local_timeline/index.jsx new file mode 100644 index 000000000..0c0d816f3 --- /dev/null +++ b/app/javascript/mastodon/features/local_timeline/index.jsx @@ -0,0 +1,195 @@ +import PropTypes from 'prop-types'; +import { PureComponent } from 'react'; + +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; + +import { Helmet } from 'react-helmet'; + +import { List as ImmutableList } from 'immutable'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; + +import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator'; +import { me } from 'mastodon/initial_state'; + +import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; +import { expandHomeTimeline } from '../../actions/timelines'; +import Column from '../../components/column'; +import ColumnHeader from '../../components/column_header'; +import LocalStatusListContainer from '../ui/containers/local_status_list_container'; + +import { ColumnSettings } from './components/column_settings'; +import { ExplorePrompt } from './components/explore_prompt'; + +const messages = defineMessages({ + title: { id: 'column.local', defaultMessage: 'local' }, +}); + +const getHomeFeedSpeed = createSelector([ + state => state.getIn(['timelines', 'home', 'items'], ImmutableList()), + state => state.getIn(['timelines', 'home', 'pendingItems'], ImmutableList()), + state => state.get('statuses'), +], (statusIds, pendingStatusIds, statusMap) => { + const recentStatusIds = pendingStatusIds.size > 0 ? pendingStatusIds : statusIds; + const statuses = recentStatusIds.filter(id => id !== null).map(id => statusMap.get(id)).filter(status => status?.get('account') !== me).take(20); + + if (statuses.isEmpty()) { + return { + gap: 0, + newest: new Date(0), + }; + } + + const datetimes = statuses.map(status => status.get('created_at', 0)); + const oldest = new Date(datetimes.min()); + const newest = new Date(datetimes.max()); + const averageGap = (newest - oldest) / (1000 * (statuses.size + 1)); // Average gap between posts on first page in seconds + + return { + gap: averageGap, + newest, + }; +}); + +const homeTooSlow = createSelector([ + state => state.getIn(['timelines', 'home', 'isLoading']), + state => state.getIn(['timelines', 'home', 'isPartial']), + getHomeFeedSpeed, +], (isLoading, isPartial, speed) => + !isLoading && !isPartial // Only if the home feed has finished loading + && ( + (speed.gap > (30 * 60) // If the average gap between posts is more than 30 minutes + || (Date.now() - speed.newest) > (1000 * 3600)) // If the most recent post is from over an hour ago + ) +); + +const mapStateToProps = state => ({ + hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0, + isPartial: state.getIn(['timelines', 'home', 'isPartial']), + tooSlow: homeTooSlow(state), +}); + +class HomeTimeline extends PureComponent { + + static contextTypes = { + identity: PropTypes.object, + }; + + static propTypes = { + dispatch: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + hasUnread: PropTypes.bool, + isPartial: PropTypes.bool, + columnId: PropTypes.string, + multiColumn: PropTypes.bool, + tooSlow: PropTypes.bool, + }; + + handlePin = () => { + const { columnId, dispatch } = this.props; + + if (columnId) { + dispatch(removeColumn(columnId)); + } else { + dispatch(addColumn('HOME', {})); + } + }; + + handleMove = (dir) => { + const { columnId, dispatch } = this.props; + dispatch(moveColumn(columnId, dir)); + }; + + handleHeaderClick = () => { + this.column.scrollTop(); + }; + + setRef = c => { + this.column = c; + }; + + handleLoadMore = maxId => { + this.props.dispatch(expandHomeTimeline({ maxId })); + }; + + componentDidUpdate (prevProps) { + this._checkIfReloadNeeded(prevProps.isPartial, this.props.isPartial); + } + + componentWillUnmount () { + this._stopPolling(); + } + + _checkIfReloadNeeded (wasPartial, isPartial) { + const { dispatch } = this.props; + + if (wasPartial === isPartial) { + return; + } else if (!wasPartial && isPartial) { + this.polling = setInterval(() => { + dispatch(expandHomeTimeline()); + }, 3000); + } else if (wasPartial && !isPartial) { + this._stopPolling(); + } + } + + _stopPolling () { + if (this.polling) { + clearInterval(this.polling); + this.polling = null; + } + } + + + + render () { + const { intl, hasUnread, columnId, multiColumn, tooSlow } = this.props; + const pinned = !!columnId; + const { signedIn } = this.context.identity; + const banners = []; + + + if (tooSlow) { + banners.push(); + } + + return ( + + + + + + {signedIn ? ( + } + bindToDocument={!multiColumn} + /> + ) : } + + + {intl.formatMessage(messages.title)} + + + + ); + } + +} + +export default connect(mapStateToProps)(injectIntl(HomeTimeline)); diff --git a/app/javascript/mastodon/features/notifications/containers/notification_container_with_dm.js b/app/javascript/mastodon/features/notifications/containers/notification_container_with_dm.js deleted file mode 100644 index ebb4be7c8..000000000 --- a/app/javascript/mastodon/features/notifications/containers/notification_container_with_dm.js +++ /dev/null @@ -1,74 +0,0 @@ -import { connect } from 'react-redux'; - -import { initBoostModal } from '../../../actions/boosts'; -import { mentionCompose } from '../../../actions/compose'; -import { - reblog, - favourite, - unreblog, - unfavourite, -} from '../../../actions/interactions'; -import { - hideStatus, - revealStatus, -} from '../../../actions/statuses'; -import { boostModal } from '../../../initial_state'; -import { makeGetNotification, makeGetStatus, makeGetReport } from '../../../selectors'; -import Notification from '../components/notification'; - -const makeMapStateToProps = () => { - const getNotification = makeGetNotification(); - const getStatus = makeGetStatus(); - const getReport = makeGetReport(); - - const mapStateToProps = (state, props) => { - const notification = getNotification(state, props.notification, props.accountId); - return { - notification: notification , - status: notification.get('status') ? getStatus(state, { id: notification.get('status'), contextType: 'notifications' }) : null, - report: notification.get('report') ? getReport(state, notification.get('report'), notification.getIn(['report', 'target_account', 'id'])) : null, - }; - }; - - return mapStateToProps; -}; - -const mapDispatchToProps = dispatch => ({ - onMention: (account, router) => { - dispatch(mentionCompose(account, router)); - }, - - onModalReblog (status, privacy) { - dispatch(reblog(status, privacy)); - }, - - onReblog (status, e) { - if (status.get('reblogged')) { - dispatch(unreblog(status)); - } else { - if (e.shiftKey || !boostModal) { - this.onModalReblog(status); - } else { - dispatch(initBoostModal({ status, onReblog: this.onModalReblog })); - } - } - }, - - onFavourite (status) { - if (status.get('favourited')) { - dispatch(unfavourite(status)); - } else { - dispatch(favourite(status)); - } - }, - - onToggleHidden (status) { - if (status.get('hidden')) { - dispatch(revealStatus(status.get('id'))); - } else { - dispatch(hideStatus(status.get('id'))); - } - }, -}); - -export default connect(makeMapStateToProps, mapDispatchToProps)(Notification); diff --git a/app/javascript/mastodon/features/notifications/containers/notification_container_without_dm.js b/app/javascript/mastodon/features/notifications/containers/notification_container_without_dm.js deleted file mode 100644 index c59a8ebb4..000000000 --- a/app/javascript/mastodon/features/notifications/containers/notification_container_without_dm.js +++ /dev/null @@ -1,74 +0,0 @@ -import { connect } from 'react-redux'; - -import { initBoostModal } from '../../../actions/boosts'; -import { mentionCompose } from '../../../actions/compose'; -import { - reblog, - favourite, - unreblog, - unfavourite, -} from '../../../actions/interactions'; -import { - hideStatus, - revealStatus, -} from '../../../actions/statuses'; -import { boostModal } from '../../../initial_state'; -import { makeGetNotification, makeGetStatus, makeGetReport } from '../../../selectors'; -import Notification from '../components/notification'; - -const makeMapStateToProps = () => { - const getNotification = makeGetNotification(); - const getStatus = makeGetStatus(); - const getReport = makeGetReport(); - - const mapStateToProps = (state, props) => { - const notification = getNotification(state, props.notification, props.accountId); - return { - notification: (notification.get('status') && getStatus(state, { id: notification.get('status'), contextType: 'notifications' }).get('visibility')!=='direct') || notification.get('report') ? notification : null, - status: notification.get('status') ? getStatus(state, { id: notification.get('status'), contextType: 'notifications' }) : null, - report: notification.get('report') ? getReport(state, notification.get('report'), notification.getIn(['report', 'target_account', 'id'])) : null, - }; - }; - - return mapStateToProps; -}; - -const mapDispatchToProps = dispatch => ({ - onMention: (account, router) => { - dispatch(mentionCompose(account, router)); - }, - - onModalReblog (status, privacy) { - dispatch(reblog(status, privacy)); - }, - - onReblog (status, e) { - if (status.get('reblogged')) { - dispatch(unreblog(status)); - } else { - if (e.shiftKey || !boostModal) { - this.onModalReblog(status); - } else { - dispatch(initBoostModal({ status, onReblog: this.onModalReblog })); - } - } - }, - - onFavourite (status) { - if (status.get('favourited')) { - dispatch(unfavourite(status)); - } else { - dispatch(favourite(status)); - } - }, - - onToggleHidden (status) { - if (status.get('hidden')) { - dispatch(revealStatus(status.get('id'))); - } else { - dispatch(hideStatus(status.get('id'))); - } - }, -}); - -export default connect(makeMapStateToProps, mapDispatchToProps)(Notification); diff --git a/app/javascript/mastodon/features/report/components/status_check_box.jsx b/app/javascript/mastodon/features/report/components/status_check_box.jsx index 8125b5b3b..e371e9769 100644 --- a/app/javascript/mastodon/features/report/components/status_check_box.jsx +++ b/app/javascript/mastodon/features/report/components/status_check_box.jsx @@ -45,9 +45,9 @@ class StatusCheckBox extends PureComponent { const visibilityIconInfo = { 'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) }, - 'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) }, + 'unlisted': { icon: 'cloud', text: intl.formatMessage(messages.unlisted_short) }, 'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) }, - 'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) }, + 'direct': { icon: 'explore', text: intl.formatMessage(messages.direct_short) }, }; const visibilityIcon = visibilityIconInfo[status.get('visibility')]; diff --git a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx index eb8bd76c7..ee60088b2 100644 --- a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx +++ b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx @@ -76,7 +76,7 @@ class NavigationPanel extends Component { )} {(signedIn) && ( - + )} {!signedIn && ( diff --git a/app/javascript/mastodon/features/ui/containers/local_status_list_container.js b/app/javascript/mastodon/features/ui/containers/local_status_list_container.js new file mode 100644 index 000000000..559d35bf7 --- /dev/null +++ b/app/javascript/mastodon/features/ui/containers/local_status_list_container.js @@ -0,0 +1,66 @@ +import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; + +import { debounce } from 'lodash'; + +import { scrollTopTimeline, loadPending } from '../../../actions/timelines'; +import StatusList from '../../../components/status_list'; + +const makeGetStatusIds = (pending = false) => createSelector([ + (state) => state.getIn(['settings', 'local'], ImmutableMap()), + (state, { type }) => state.getIn(['timelines', type, pending ? 'pendingItems' : 'items'], ImmutableList()), + (state) => state.get('statuses'), + (type) => type +], (columnSettings, statusIds, statuses,type) => { + return statusIds.filter(id => { + if (id === null) return true; + + const statusForId = statuses.get(id); + + let showStatus = true; + + if (statusForId.get('visibility') === 'direct') + return false; + if (statusForId.get('in_reply_to_id')) { + showStatus = showStatus && statusForId.get('in_reply_to_account_id') === statusForId.get('account') + } + if (columnSettings.getIn(['shows', 'media']) === true) { + showStatus = showStatus && statusForId.get('media_attachments').size>0; + } + + return showStatus; + }); +}); + +const makeMapStateToProps = () => { + const getStatusIds = makeGetStatusIds(); + const getPendingStatusIds = makeGetStatusIds(true); + + const mapStateToProps = (state, { timelineId }) => ({ + statusIds: getStatusIds(state, { type: timelineId }), + lastId: state.getIn(['timelines', timelineId, 'items'])?.last(), + isLoading: state.getIn(['timelines', timelineId, 'isLoading'], true), + isPartial: state.getIn(['timelines', timelineId, 'isPartial'], false), + hasMore: state.getIn(['timelines', timelineId, 'hasMore']), + numPending: getPendingStatusIds(state, { type: timelineId }).size, + }); + + return mapStateToProps; +}; + +const mapDispatchToProps = (dispatch, { timelineId }) => ({ + + onScrollToTop: debounce(() => { + dispatch(scrollTopTimeline(timelineId, true)); + }, 100), + + onScroll: debounce(() => { + dispatch(scrollTopTimeline(timelineId, false)); + }, 100), + + onLoadPending: () => dispatch(loadPending(timelineId)), + +}); + +export default connect(makeMapStateToProps, mapDispatchToProps)(StatusList); diff --git a/app/javascript/mastodon/features/ui/containers/status_list_container.js b/app/javascript/mastodon/features/ui/containers/status_list_container.js index 5a0bdb8ba..1ad07afab 100644 --- a/app/javascript/mastodon/features/ui/containers/status_list_container.js +++ b/app/javascript/mastodon/features/ui/containers/status_list_container.js @@ -12,7 +12,8 @@ const makeGetStatusIds = (pending = false) => createSelector([ (state, { type }) => state.getIn(['settings', type], ImmutableMap()), (state, { type }) => state.getIn(['timelines', type, pending ? 'pendingItems' : 'items'], ImmutableList()), (state) => state.get('statuses'), -], (columnSettings, statusIds, statuses) => { + (type) => type +], (columnSettings, statusIds, statuses,type) => { return statusIds.filter(id => { if (id === null) return true; diff --git a/app/javascript/mastodon/features/ui/index.jsx b/app/javascript/mastodon/features/ui/index.jsx index 12ec756f6..c0892a702 100644 --- a/app/javascript/mastodon/features/ui/index.jsx +++ b/app/javascript/mastodon/features/ui/index.jsx @@ -40,6 +40,7 @@ import { AccountTimeline, AccountGallery, HomeTimeline, + LocalTimeline, Followers, Following, Reblogs, @@ -199,7 +200,12 @@ class SwitchingColumnsArea extends PureComponent { - + + + + + + diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index e89bdaaec..372a4d48d 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -14,6 +14,9 @@ export function HomeTimeline () { return import(/* webpackChunkName: "features/home_timeline" */'../../home_timeline'); } +export function LocalTimeline () { + return import(/* webpackChunkName: "features/home_timeline" */'../../local_timeline'); +} export function PublicTimeline () { return import(/* webpackChunkName: "features/public_timeline" */'../../public_timeline'); } diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index c3a36dbb3..edff87f67 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -117,6 +117,7 @@ "column.firehose": "Live feeds", "column.follow_requests": "Follow requests", "column.home": "Home", + "column.local": "feed", "column.lists": "Lists", "column.mutes": "Muted users", "column.notifications": "Notifications", @@ -239,6 +240,7 @@ "empty_column.followed_tags": "You have not followed any hashtags yet. When you do, they 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.", + "empty_column.local": "Your home timeline is empty! Follow more people to fill it up.", "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.", @@ -309,8 +311,14 @@ "home.column_settings.basic": "Basic", "home.column_settings.show_reblogs": "Show boosts", "home.column_settings.show_replies": "Show replies", + "local.column_settings.basic": "local", + "local.column_settings.show_media": "Show Media", "home.explore_prompt.body": "Your home feed will have a mix of posts from the hashtags you've chosen to follow, the people you've chosen to follow, and the posts they boost. If that feels too quiet, you may want to:", "home.explore_prompt.title": "This is your home base within Mastodon.", + + "local.explore_prompt.body": "Your home feed will have a mix of posts from the hashtags you've chosen to follow, the people you've chosen to follow, and the posts they boost. If that feels too quiet, you may want to:", + "local.explore_prompt.title": "This is your home base within Mastodon.", + "home.hide_announcements": "Hide announcements", "home.pending_critical_update.body": "Please update your Mastodon server as soon as possible!", "home.pending_critical_update.link": "See updates", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index e4eb030b3..9460d97f4 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -117,6 +117,7 @@ "column.firehose": "실시간 피드", "column.follow_requests": "팔로우 요청", "column.home": "홈", + "column.local": "실시간 피드", "column.lists": "리스트", "column.mutes": "뮤트한 사용자", "column.notifications": "알림", @@ -239,6 +240,7 @@ "empty_column.followed_tags": "아직 아무 해시태그도 팔로우하고 있지 않습니다. 해시태그를 팔로우하면, 여기에 표시됩니다.", "empty_column.hashtag": "이 해시태그는 아직 사용되지 않았습니다.", "empty_column.home": "당신의 홈 타임라인은 비어있습니다! 더 많은 사람들을 팔로우 하여 채워보세요.", + "empty_column.local": "당신의 실시간 피드는 비어있습니다! 더 많은 사람들을 팔로우 하여 채워보세요.", "empty_column.list": "리스트에 아직 아무것도 없습니다. 리스트의 누군가가 게시물을 올리면 여기에 나타납니다.", "empty_column.lists": "아직 리스트가 없습니다. 리스트를 만들면 여기에 나타납니다.", "empty_column.mutes": "아직 아무도 뮤트하지 않았습니다.", @@ -309,8 +311,14 @@ "home.column_settings.basic": "기본", "home.column_settings.show_reblogs": "부스트 표시", "home.column_settings.show_replies": "답글 표시", + "local.column_settings.basic": "실시간 피드", + "local.column_settings.show_media": "미디어만", "home.explore_prompt.body": "홈 피드에는 내가 팔로우한 해시태그 그리고 팔로우한 사람과 부스트가 함께 나타납니다. 너무 고요하게 느껴진다면, 다음 것들을 살펴볼 수 있습니다.", "home.explore_prompt.title": "이곳은 마스토돈의 내 본거지입니다.", + + "local.explore_prompt.body": "실시간 피드에는 {title}에 있는 사람들의 답장이 아닌 최근 게시글을 살펴볼 수 있습니다.", + "local.explore_prompt.title": "이곳은 마스토돈의 내 본거지입니다.", + "home.hide_announcements": "공지사항 숨기기", "home.pending_critical_update.body": "서둘러 마스토돈 서버를 업데이트 하세요!", "home.pending_critical_update.link": "업데이트 보기", diff --git a/app/javascript/mastodon/reducers/settings.js b/app/javascript/mastodon/reducers/settings.js index 55735afca..a2f3c31fc 100644 --- a/app/javascript/mastodon/reducers/settings.js +++ b/app/javascript/mastodon/reducers/settings.js @@ -20,7 +20,7 @@ const initialState = ImmutableMap({ home: ImmutableMap({ shows: ImmutableMap({ - reblog: true, + media: true, reply: true, }), @@ -29,6 +29,17 @@ const initialState = ImmutableMap({ }), }), + local: ImmutableMap({ + shows: ImmutableMap({ + media: false + }), + + regex: ImmutableMap({ + body: '', + }), + }), + + notifications: ImmutableMap({ alerts: ImmutableMap({ follow: false, diff --git a/app/javascript/styles/mastodon-bird-ui-vanilla/layout-multiple-columns.scss b/app/javascript/styles/mastodon-bird-ui-vanilla/layout-multiple-columns.scss index 4b16c70af..884ef8b82 100644 --- a/app/javascript/styles/mastodon-bird-ui-vanilla/layout-multiple-columns.scss +++ b/app/javascript/styles/mastodon-bird-ui-vanilla/layout-multiple-columns.scss @@ -1551,21 +1551,21 @@ body.embed .detailed-status__favorites { body.embed .detailed-status__link > .fa-reply + span::after, .layout-multiple-columns .detailed-status__link > .fa-reply + span::after { color: var(--color-dim); - content: 'Replies'; + content: '답장'; font-weight: var(--font-weight-semibold); } body.embed .detailed-status__link > .fa-retweet + span::after, .layout-multiple-columns .detailed-status__link > .fa-retweet + span::after { color: var(--color-dim); - content: 'Boosts'; + content: '부스트'; font-weight: var(--font-weight-semibold); } body.embed .detailed-status__link > .fa-star + span::after, .layout-multiple-columns .detailed-status__link > .fa-star + span::after { color: var(--color-dim); - content: 'Favourites'; + content: '좋아요'; font-weight: var(--font-weight-semibold); } @@ -2961,6 +2961,11 @@ body.embed .button.logo-button:hover, order: 5; } + + .layout-multiple-columns .column-link[href="/local"] { + order: 5; + } + .layout-multiple-columns .column-link[href="/public"] { order: 6; } diff --git a/app/javascript/styles/mastodon-bird-ui-vanilla/layout-single-column.scss b/app/javascript/styles/mastodon-bird-ui-vanilla/layout-single-column.scss index 036763fab..7fcecb789 100644 --- a/app/javascript/styles/mastodon-bird-ui-vanilla/layout-single-column.scss +++ b/app/javascript/styles/mastodon-bird-ui-vanilla/layout-single-column.scss @@ -1584,21 +1584,21 @@ body.embed > .activity-stream { body.embed .detailed-status__link > .fa-reply + span::after, .layout-single-column .detailed-status__link > .fa-reply + span::after { color: var(--color-dim); - content: 'Replies'; + content: '답장'; font-weight: var(--font-weight-semibold); } body.embed .detailed-status__link > .fa-retweet + span::after, .layout-single-column .detailed-status__link > .fa-retweet + span::after { color: var(--color-dim); - content: 'Boosts'; + content: '부스트'; font-weight: var(--font-weight-semibold); } body.embed .detailed-status__link > .fa-star + span::after, .layout-single-column .detailed-status__link > .fa-star + span::after { color: var(--color-dim); - content: 'Favourites'; + content: '좋아요'; font-weight: var(--font-weight-semibold); } @@ -2997,6 +2997,10 @@ body.embed .button.logo-button:hover, order: 5; } + .layout-single-column .column-link[href="/local"] { + order: 5; + } + .layout-single-column .column-link[href="/public"] { order: 6; } diff --git a/app/javascript/styles/mastodon-bird-ui/layout-multiple-columns.scss b/app/javascript/styles/mastodon-bird-ui/layout-multiple-columns.scss index 4b16c70af..bcf47988b 100644 --- a/app/javascript/styles/mastodon-bird-ui/layout-multiple-columns.scss +++ b/app/javascript/styles/mastodon-bird-ui/layout-multiple-columns.scss @@ -1551,21 +1551,21 @@ body.embed .detailed-status__favorites { body.embed .detailed-status__link > .fa-reply + span::after, .layout-multiple-columns .detailed-status__link > .fa-reply + span::after { color: var(--color-dim); - content: 'Replies'; + content: '답장'; font-weight: var(--font-weight-semibold); } body.embed .detailed-status__link > .fa-retweet + span::after, .layout-multiple-columns .detailed-status__link > .fa-retweet + span::after { color: var(--color-dim); - content: 'Boosts'; + content: '부스트'; font-weight: var(--font-weight-semibold); } body.embed .detailed-status__link > .fa-star + span::after, .layout-multiple-columns .detailed-status__link > .fa-star + span::after { color: var(--color-dim); - content: 'Favourites'; + content: '좋아요'; font-weight: var(--font-weight-semibold); } @@ -2961,6 +2961,12 @@ body.embed .button.logo-button:hover, order: 5; } + + .layout-multiple-columns .column-link[href="/local"] { + order: 5; + } + + .layout-multiple-columns .column-link[href="/public"] { order: 6; } diff --git a/app/javascript/styles/mastodon-bird-ui/layout-single-column.scss b/app/javascript/styles/mastodon-bird-ui/layout-single-column.scss index 898ead501..6b673d6a1 100644 --- a/app/javascript/styles/mastodon-bird-ui/layout-single-column.scss +++ b/app/javascript/styles/mastodon-bird-ui/layout-single-column.scss @@ -1590,21 +1590,21 @@ body.embed > .activity-stream { body.embed .detailed-status__link > .fa-reply + span::after, .layout-single-column .detailed-status__link > .fa-reply + span::after { color: var(--color-dim); - content: 'Replies'; + content: '답장'; font-weight: var(--font-weight-semibold); } body.embed .detailed-status__link > .fa-retweet + span::after, .layout-single-column .detailed-status__link > .fa-retweet + span::after { color: var(--color-dim); - content: 'Boosts'; + content: '부스트'; font-weight: var(--font-weight-semibold); } body.embed .detailed-status__link > .fa-star + span::after, .layout-single-column .detailed-status__link > .fa-star + span::after { color: var(--color-dim); - content: 'Favourites'; + content: '좋아요'; font-weight: var(--font-weight-semibold); } @@ -3002,6 +3002,10 @@ body.embed .button.logo-button:hover, order: 5; } + .layout-single-column .column-link[href="/local"] { + order: 5; + } + .layout-single-column .column-link[href="/public"] { order: 6; } diff --git a/app/views/settings/preferences/appearance/show.html.haml b/app/views/settings/preferences/appearance/show.html.haml index 8e8018e42..40dcebc8e 100644 --- a/app/views/settings/preferences/appearance/show.html.haml +++ b/app/views/settings/preferences/appearance/show.html.haml @@ -25,6 +25,7 @@ %p.hint= t 'appearance.advanced_web_interface_hint' .fields-group + = ff.input :'web.advanced_layout', wrapper: :with_label, hint: false, label: I18n.t('simple_form.labels.defaults.setting_advanced_layout') %h4= t 'appearance.animations_and_accessibility' .fields-group @@ -56,4 +57,4 @@ = ff.input :'web.expand_content_warnings', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_expand_spoilers') .actions - = f.button :button, t('generic.save_changes'), type: :submit + = f.button :button, t('generic.save_changes'), type: :submit \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index fb4d5a9ec..8623075fc 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -23,6 +23,7 @@ Rails.application.routes.draw do /publish /follow_requests /direct_messages + /local /blocks /domain_blocks /mutes