From ccc240b59c8833bc8cb1c00c97350b2d9a7fd7db Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Mon, 21 May 2018 19:43:38 +0900 Subject: [PATCH] Add media timeline (#6631) --- app/javascript/mastodon/actions/streaming.js | 13 ++++--- app/javascript/mastodon/actions/timelines.js | 16 ++++----- .../features/community_timeline/index.js | 34 ++++++++++++++----- .../features/public_timeline/index.js | 34 ++++++++++++++----- app/javascript/mastodon/features/ui/index.js | 2 ++ app/javascript/mastodon/locales/ar.json | 2 ++ app/javascript/mastodon/locales/bg.json | 2 ++ app/javascript/mastodon/locales/ca.json | 2 ++ app/javascript/mastodon/locales/co.json | 2 ++ app/javascript/mastodon/locales/de.json | 2 ++ .../mastodon/locales/defaultMessages.json | 16 +++++++++ app/javascript/mastodon/locales/el.json | 2 ++ app/javascript/mastodon/locales/en.json | 2 ++ app/javascript/mastodon/locales/eo.json | 2 ++ app/javascript/mastodon/locales/es.json | 2 ++ app/javascript/mastodon/locales/eu.json | 2 ++ app/javascript/mastodon/locales/fa.json | 2 ++ app/javascript/mastodon/locales/fi.json | 2 ++ app/javascript/mastodon/locales/fr.json | 2 ++ app/javascript/mastodon/locales/gl.json | 2 ++ app/javascript/mastodon/locales/he.json | 2 ++ app/javascript/mastodon/locales/hr.json | 2 ++ app/javascript/mastodon/locales/hu.json | 2 ++ app/javascript/mastodon/locales/hy.json | 2 ++ app/javascript/mastodon/locales/id.json | 2 ++ app/javascript/mastodon/locales/io.json | 2 ++ app/javascript/mastodon/locales/it.json | 2 ++ app/javascript/mastodon/locales/ja.json | 2 ++ app/javascript/mastodon/locales/ko.json | 2 ++ app/javascript/mastodon/locales/nl.json | 2 ++ app/javascript/mastodon/locales/no.json | 2 ++ app/javascript/mastodon/locales/oc.json | 2 ++ app/javascript/mastodon/locales/pl.json | 2 ++ app/javascript/mastodon/locales/pt-BR.json | 2 ++ app/javascript/mastodon/locales/pt.json | 2 ++ app/javascript/mastodon/locales/ru.json | 2 ++ app/javascript/mastodon/locales/sk.json | 2 ++ app/javascript/mastodon/locales/sl.json | 2 ++ app/javascript/mastodon/locales/sr-Latn.json | 2 ++ app/javascript/mastodon/locales/sr.json | 2 ++ app/javascript/mastodon/locales/sv.json | 2 ++ app/javascript/mastodon/locales/te.json | 2 ++ app/javascript/mastodon/locales/th.json | 2 ++ app/javascript/mastodon/locales/tr.json | 2 ++ app/javascript/mastodon/locales/uk.json | 2 ++ app/javascript/mastodon/locales/zh-CN.json | 2 ++ app/javascript/mastodon/locales/zh-HK.json | 2 ++ app/javascript/mastodon/locales/zh-TW.json | 2 ++ .../styles/mastodon/components.scss | 2 ++ app/services/batched_remove_status_service.rb | 5 +++ app/services/fan_out_on_write_service.rb | 8 +++++ app/services/remove_status_service.rb | 8 +++++ streaming/index.js | 18 ++++++++-- 53 files changed, 205 insertions(+), 35 deletions(-) diff --git a/app/javascript/mastodon/actions/streaming.js b/app/javascript/mastodon/actions/streaming.js index 10e68bf3aa..f56853bffb 100644 --- a/app/javascript/mastodon/actions/streaming.js +++ b/app/javascript/mastodon/actions/streaming.js @@ -40,10 +40,9 @@ const refreshHomeTimelineAndNotification = (dispatch, done) => { dispatch(expandHomeTimeline({}, () => dispatch(expandNotifications({}, done)))); }; -export const connectUserStream = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification); -export const connectCommunityStream = () => connectTimelineStream('community', 'public:local'); -export const connectMediaStream = () => connectTimelineStream('community', 'public:local'); -export const connectPublicStream = () => connectTimelineStream('public', 'public'); -export const connectHashtagStream = (tag) => connectTimelineStream(`hashtag:${tag}`, `hashtag&tag=${tag}`); -export const connectDirectStream = () => connectTimelineStream('direct', 'direct'); -export const connectListStream = (id) => connectTimelineStream(`list:${id}`, `list&list=${id}`); +export const connectUserStream = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification); +export const connectCommunityStream = ({ onlyMedia } = {}) => connectTimelineStream(`community${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`); +export const connectPublicStream = ({ onlyMedia } = {}) => connectTimelineStream(`public${onlyMedia ? ':media' : ''}`, `public${onlyMedia ? ':media' : ''}`); +export const connectHashtagStream = tag => connectTimelineStream(`hashtag:${tag}`, `hashtag&tag=${tag}`); +export const connectDirectStream = () => connectTimelineStream('direct', 'direct'); +export const connectListStream = id => connectTimelineStream(`list:${id}`, `list&list=${id}`); diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js index 8bcfe4db9c..8f54dfd8a0 100644 --- a/app/javascript/mastodon/actions/timelines.js +++ b/app/javascript/mastodon/actions/timelines.js @@ -93,15 +93,15 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) { }; }; -export const expandHomeTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId }, done); -export const expandPublicTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('public', '/api/v1/timelines/public', { max_id: maxId }, done); -export const expandCommunityTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('community', '/api/v1/timelines/public', { local: true, max_id: maxId }, done); -export const expandDirectTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('direct', '/api/v1/timelines/direct', { max_id: maxId }, done); -export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId }); +export const expandHomeTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId }, done); +export const expandPublicTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`public${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { max_id: maxId, only_media: !!onlyMedia }, done); +export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { local: true, max_id: maxId, only_media: !!onlyMedia }, done); +export const expandDirectTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('direct', '/api/v1/timelines/direct', { max_id: maxId }, done); +export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId }); export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true }); -export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true }); -export const expandHashtagTimeline = (hashtag, { maxId } = {}, done = noOp) => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`, { max_id: maxId }, done); -export const expandListTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done); +export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true }); +export const expandHashtagTimeline = (hashtag, { maxId } = {}, done = noOp) => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`, { max_id: maxId }, done); +export const expandListTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done); export function expandTimelineRequest(timeline) { return { diff --git a/app/javascript/mastodon/features/community_timeline/index.js b/app/javascript/mastodon/features/community_timeline/index.js index 870474ed58..8ad1144071 100644 --- a/app/javascript/mastodon/features/community_timeline/index.js +++ b/app/javascript/mastodon/features/community_timeline/index.js @@ -1,12 +1,13 @@ import React from 'react'; import { connect } from 'react-redux'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { NavLink } from 'react-router-dom'; 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 { expandCommunityTimeline } from '../../actions/timelines'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ColumnSettingsContainer from './containers/column_settings_container'; import { connectCommunityStream } from '../../actions/streaming'; @@ -14,20 +15,25 @@ const messages = defineMessages({ title: { id: 'column.community', defaultMessage: 'Local timeline' }, }); -const mapStateToProps = state => ({ - hasUnread: state.getIn(['timelines', 'community', 'unread']) > 0, +const mapStateToProps = (state, { onlyMedia }) => ({ + hasUnread: state.getIn(['timelines', `community${onlyMedia ? ':media' : ''}`, 'unread']) > 0, }); @connect(mapStateToProps) @injectIntl export default class CommunityTimeline extends React.PureComponent { + static defaultProps = { + onlyMedia: false, + }; + static propTypes = { dispatch: PropTypes.func.isRequired, columnId: PropTypes.string, intl: PropTypes.object.isRequired, hasUnread: PropTypes.bool, multiColumn: PropTypes.bool, + onlyMedia: PropTypes.bool, }; handlePin = () => { @@ -50,10 +56,10 @@ export default class CommunityTimeline extends React.PureComponent { } componentDidMount () { - const { dispatch } = this.props; + const { dispatch, onlyMedia } = this.props; - dispatch(expandCommunityTimeline()); - this.disconnect = dispatch(connectCommunityStream()); + dispatch(expandCommunityTimeline({ onlyMedia })); + this.disconnect = dispatch(connectCommunityStream({ onlyMedia })); } componentWillUnmount () { @@ -68,13 +74,22 @@ export default class CommunityTimeline extends React.PureComponent { } handleLoadMore = maxId => { - this.props.dispatch(expandCommunityTimeline({ maxId })); + const { dispatch, onlyMedia } = this.props; + + dispatch(expandCommunityTimeline({ maxId, onlyMedia })); } render () { - const { intl, hasUnread, columnId, multiColumn } = this.props; + const { intl, hasUnread, columnId, multiColumn, onlyMedia } = this.props; const pinned = !!columnId; + const headline = ( +
+ + +
+ ); + return ( } /> diff --git a/app/javascript/mastodon/features/public_timeline/index.js b/app/javascript/mastodon/features/public_timeline/index.js index 5a88f76017..54904c5cfa 100644 --- a/app/javascript/mastodon/features/public_timeline/index.js +++ b/app/javascript/mastodon/features/public_timeline/index.js @@ -1,12 +1,13 @@ import React from 'react'; import { connect } from 'react-redux'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { NavLink } from 'react-router-dom'; 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 { expandPublicTimeline } from '../../actions/timelines'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ColumnSettingsContainer from './containers/column_settings_container'; import { connectPublicStream } from '../../actions/streaming'; @@ -14,20 +15,25 @@ const messages = defineMessages({ title: { id: 'column.public', defaultMessage: 'Federated timeline' }, }); -const mapStateToProps = state => ({ - hasUnread: state.getIn(['timelines', 'public', 'unread']) > 0, +const mapStateToProps = (state, { onlyMedia }) => ({ + hasUnread: state.getIn(['timelines', `public${onlyMedia ? ':media' : ''}`, 'unread']) > 0, }); @connect(mapStateToProps) @injectIntl export default class PublicTimeline extends React.PureComponent { + static defaultProps = { + onlyMedia: false, + }; + static propTypes = { dispatch: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, columnId: PropTypes.string, multiColumn: PropTypes.bool, hasUnread: PropTypes.bool, + onlyMedia: PropTypes.bool, }; handlePin = () => { @@ -50,10 +56,10 @@ export default class PublicTimeline extends React.PureComponent { } componentDidMount () { - const { dispatch } = this.props; + const { dispatch, onlyMedia } = this.props; - dispatch(expandPublicTimeline()); - this.disconnect = dispatch(connectPublicStream()); + dispatch(expandPublicTimeline({ onlyMedia })); + this.disconnect = dispatch(connectPublicStream({ onlyMedia })); } componentWillUnmount () { @@ -68,13 +74,22 @@ export default class PublicTimeline extends React.PureComponent { } handleLoadMore = maxId => { - this.props.dispatch(expandPublicTimeline({ maxId })); + const { dispatch, onlyMedia } = this.props; + + dispatch(expandPublicTimeline({ maxId, onlyMedia })); } render () { - const { intl, columnId, hasUnread, multiColumn } = this.props; + const { intl, columnId, hasUnread, multiColumn, onlyMedia } = this.props; const pinned = !!columnId; + const headline = ( +
+ + +
+ ); + return ( + + diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index 9827ac5f0e..c8e37a3665 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "المحلي", "tabs_bar.notifications": "الإخطارات", "tabs_bar.search": "البحث", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "سوف تفقد مسودتك إن تركت ماستدون.", "upload_area.title": "إسحب ثم أفلت للرفع", "upload_button.label": "إضافة وسائط", diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index caba7703d9..9ac5aad9e7 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Известия", "tabs_bar.search": "Search", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Drag & drop to upload", "upload_button.label": "Добави медия", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 7181589ec2..8fd42ca34a 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notificacions", "tabs_bar.search": "Cerca", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "El vostre esborrany es perdrà si sortiu de Mastodon.", "upload_area.title": "Arrossega i deixa anar per carregar", "upload_button.label": "Afegir multimèdia", diff --git a/app/javascript/mastodon/locales/co.json b/app/javascript/mastodon/locales/co.json index c6de9d016b..bc75813ea1 100644 --- a/app/javascript/mastodon/locales/co.json +++ b/app/javascript/mastodon/locales/co.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Lucale", "tabs_bar.notifications": "Nutificazione", "tabs_bar.search": "Cercà", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "A bruttacopia sarà persa s'ellu hè chjosu Mastodon.", "upload_area.title": "Drag & drop per caricà un fugliale", "upload_button.label": "Aghjunghje un media", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 4d89ea7041..29756aff0b 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Lokal", "tabs_bar.notifications": "Mitteilungen", "tabs_bar.search": "Suchen", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "Dein Entwurf geht verloren, wenn du Mastodon verlässt.", "upload_area.title": "Zum Hochladen hereinziehen", "upload_button.label": "Mediendatei hinzufügen", diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index d2ec8e7100..1431346fe5 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -596,6 +596,14 @@ "defaultMessage": "Local timeline", "id": "column.community" }, + { + "defaultMessage": "Toots", + "id": "timeline.posts" + }, + { + "defaultMessage": "Media", + "id": "timeline.media" + }, { "defaultMessage": "The local timeline is empty. Write something publicly to get the ball rolling!", "id": "empty_column.community" @@ -1393,6 +1401,14 @@ "defaultMessage": "Federated timeline", "id": "column.public" }, + { + "defaultMessage": "Toots", + "id": "timeline.posts" + }, + { + "defaultMessage": "Media", + "id": "timeline.media" + }, { "defaultMessage": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up", "id": "empty_column.public" diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index c547b2e284..1e7707b4a0 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notifications", "tabs_bar.search": "Search", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Drag & drop to upload", "upload_button.label": "Add media", diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index caec0cbd31..7a977673ea 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notifications", "tabs_bar.search": "Search", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Drag & drop to upload", "upload_button.label": "Add media", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index 2715b80c9b..04d9dc8d51 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Loka tempolinio", "tabs_bar.notifications": "Sciigoj", "tabs_bar.search": "Serĉi", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "Via malneto perdiĝos se vi eliras de Mastodon.", "upload_area.title": "Altreni kaj lasi por alŝuti", "upload_button.label": "Aldoni aŭdovidaĵon", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 668f437b9d..6092630c48 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notificaciones", "tabs_bar.search": "Search", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "Tu borrador se perderá si sales de Mastodon.", "upload_area.title": "Arrastra y suelta para subir", "upload_button.label": "Subir multimedia", diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json index 8ec1407532..a6b1f8e836 100644 --- a/app/javascript/mastodon/locales/eu.json +++ b/app/javascript/mastodon/locales/eu.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notifications", "tabs_bar.search": "Search", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Drag & drop to upload", "upload_button.label": "Add media", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index 605961a018..c45277d82f 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "محلی", "tabs_bar.notifications": "اعلان‌ها", "tabs_bar.search": "Search", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "اگر از ماستدون خارج شوید پیش‌نویس شما پاک خواهد شد.", "upload_area.title": "برای بارگذاری به این‌جا بکشید", "upload_button.label": "افزودن تصویر", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index ead1eb8c4b..d04e0230a5 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Paikallinen", "tabs_bar.notifications": "Ilmoitukset", "tabs_bar.search": "Hae", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "Luonnos häviää, jos poistut Mastodonista.", "upload_area.title": "Lataa raahaamalla ja pudottamalla tähän", "upload_button.label": "Lisää mediaa", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index c05cb40c12..4c2bd39cac 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Fil public local", "tabs_bar.notifications": "Notifications", "tabs_bar.search": "Chercher", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "Votre brouillon sera perdu si vous quittez Mastodon.", "upload_area.title": "Glissez et déposez pour envoyer", "upload_button.label": "Joindre un média", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index 90447ad4c5..f2f0fc260c 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notificacións", "tabs_bar.search": "Buscar", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "O borrador perderase se sae de Mastodon.", "upload_area.title": "Arrastre e solte para subir", "upload_button.label": "Engadir medios", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index 5da3e9ebbc..361beebcca 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "ציר זמן מקומי", "tabs_bar.notifications": "התראות", "tabs_bar.search": "Search", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "הטיוטא תאבד אם תעזבו את מסטודון.", "upload_area.title": "ניתן להעלות על ידי Drag & drop", "upload_button.label": "הוספת מדיה", diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json index 6486284ec4..93dc1c17d2 100644 --- a/app/javascript/mastodon/locales/hr.json +++ b/app/javascript/mastodon/locales/hr.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Lokalno", "tabs_bar.notifications": "Notifikacije", "tabs_bar.search": "Search", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Povuci i spusti kako bi uploadao", "upload_button.label": "Dodaj media", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 6c04eced63..158c0fe414 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Értesítések", "tabs_bar.search": "Search", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "A piszkozata el fog vesztődni ha elhagyja Mastodon-t.", "upload_area.title": "Húzza ide a feltöltéshez", "upload_button.label": "Média hozzáadása", diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json index f1f4df9688..76b3a9c124 100644 --- a/app/javascript/mastodon/locales/hy.json +++ b/app/javascript/mastodon/locales/hy.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Տեղական", "tabs_bar.notifications": "Ծանուցումներ", "tabs_bar.search": "Search", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "Քո սեւագիրը կկորի, եթե լքես Մաստոդոնը։", "upload_area.title": "Քաշիր ու նետիր՝ վերբեռնելու համար", "upload_button.label": "Ավելացնել մեդիա", diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json index e0fa959b07..00240530d0 100644 --- a/app/javascript/mastodon/locales/id.json +++ b/app/javascript/mastodon/locales/id.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Lokal", "tabs_bar.notifications": "Notifikasi", "tabs_bar.search": "Search", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "Naskah anda akan hilang jika anda keluar dari Mastodon.", "upload_area.title": "Seret & lepaskan untuk mengunggah", "upload_button.label": "Tambahkan media", diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json index 616232c3ec..d20ee0f622 100644 --- a/app/javascript/mastodon/locales/io.json +++ b/app/javascript/mastodon/locales/io.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Lokala", "tabs_bar.notifications": "Savigi", "tabs_bar.search": "Search", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Tranar faligar por kargar", "upload_button.label": "Adjuntar kontenajo", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 6bf9c73a60..485fe0fad8 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Locale", "tabs_bar.notifications": "Notifiche", "tabs_bar.search": "Cerca", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "La bozza andrà persa se esci da Mastodon.", "upload_area.title": "Trascina per caricare", "upload_button.label": "Aggiungi file multimediale", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 09dc624326..614e526e35 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "ローカル", "tabs_bar.notifications": "通知", "tabs_bar.search": "検索", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "Mastodonから離れると送信前の投稿は失われます。", "upload_area.title": "ドラッグ&ドロップでアップロード", "upload_button.label": "メディアを追加", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index 66657abcdb..edcf5f8176 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "로컬", "tabs_bar.notifications": "알림", "tabs_bar.search": "검색", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "지금 나가면 저장되지 않은 항목을 잃게 됩니다.", "upload_area.title": "드래그 & 드롭으로 업로드", "upload_button.label": "미디어 추가", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 8ddf47f3f5..1ead2011d7 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Lokaal", "tabs_bar.notifications": "Meldingen", "tabs_bar.search": "Zoeken", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "Je concept zal verloren gaan als je Mastodon verlaat.", "upload_area.title": "Hierin slepen om te uploaden", "upload_button.label": "Media toevoegen", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index ab755a3e8c..d8de2ad4be 100644 --- a/app/javascript/mastodon/locales/no.json +++ b/app/javascript/mastodon/locales/no.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Lokal", "tabs_bar.notifications": "Varslinger", "tabs_bar.search": "Search", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "Din kladd vil bli forkastet om du forlater Mastodon.", "upload_area.title": "Dra og slipp for å laste opp", "upload_button.label": "Legg til media", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index 44bb44e977..e7bc148fa3 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Flux public local", "tabs_bar.notifications": "Notificacions", "tabs_bar.search": "Recèrcas", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "Vòstre brolhon serà perdut se quitatz Mastodon.", "upload_area.title": "Lisatz e depausatz per mandar", "upload_button.label": "Ajustar un mèdia", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index 7bfb7643c6..92c17556f2 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Lokalne", "tabs_bar.notifications": "Powiadomienia", "tabs_bar.search": "Szukaj", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "Utracisz tworzony wpis, jeżeli opuścisz Mastodona.", "upload_area.title": "Przeciągnij i upuść aby wysłać", "upload_button.label": "Dodaj zawartość multimedialną", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index 82bbd17a35..c69a90e620 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notificações", "tabs_bar.search": "Buscar", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "Seu rascunho será perdido se você sair do Mastodon.", "upload_area.title": "Arraste e solte para enviar", "upload_button.label": "Adicionar mídia", diff --git a/app/javascript/mastodon/locales/pt.json b/app/javascript/mastodon/locales/pt.json index 6463d001dd..18d66be7f2 100644 --- a/app/javascript/mastodon/locales/pt.json +++ b/app/javascript/mastodon/locales/pt.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notificações", "tabs_bar.search": "Search", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "O teu rascunho vai ser perdido se abandonares o Mastodon.", "upload_area.title": "Arraste e solte para enviar", "upload_button.label": "Adicionar media", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index 6c7926ffe6..577ec99212 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Локальная", "tabs_bar.notifications": "Уведомления", "tabs_bar.search": "Поиск", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "Ваш черновик будет утерян, если вы покинете Mastodon.", "upload_area.title": "Перетащите сюда, чтобы загрузить", "upload_button.label": "Добавить медиаконтент", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index 6ac646bee1..b85ba13181 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Lokálna", "tabs_bar.notifications": "Notifikácie", "tabs_bar.search": "Hľadaj", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "Čo máte rozpísané sa stratí, ak opustíte Mastodon.", "upload_area.title": "Ťahaj a pusti pre nahratie", "upload_button.label": "Pridať médiá", diff --git a/app/javascript/mastodon/locales/sl.json b/app/javascript/mastodon/locales/sl.json index edd5ab62b4..3cc7389ae3 100644 --- a/app/javascript/mastodon/locales/sl.json +++ b/app/javascript/mastodon/locales/sl.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Lokalno", "tabs_bar.notifications": "Obvestila", "tabs_bar.search": "Poišči", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "Vaš osnutek bo izgubljen, če zapustite Mastodona.", "upload_area.title": "Povlecite in spustite za pošiljanje", "upload_button.label": "Dodaj medij", diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json index 2a9d3737d6..e2b55c1794 100644 --- a/app/javascript/mastodon/locales/sr-Latn.json +++ b/app/javascript/mastodon/locales/sr-Latn.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Lokalno", "tabs_bar.notifications": "Obaveštenja", "tabs_bar.search": "Search", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "Ako napustite Mastodont, izgubićete napisani nacrt.", "upload_area.title": "Prevucite ovde da otpremite", "upload_button.label": "Dodaj multimediju", diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json index b53f1ecc98..09efb985cb 100644 --- a/app/javascript/mastodon/locales/sr.json +++ b/app/javascript/mastodon/locales/sr.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Локално", "tabs_bar.notifications": "Обавештења", "tabs_bar.search": "Search", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "Ако напустите Мастодонт, изгубићете написани нацрт.", "upload_area.title": "Превуците овде да отпремите", "upload_button.label": "Додај мултимедију", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index dbe2c182ed..6c396e5250 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Lokal", "tabs_bar.notifications": "Meddelanden", "tabs_bar.search": "Sök", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "Ditt utkast kommer att förloras om du lämnar Mastodon.", "upload_area.title": "Dra & släpp för att ladda upp", "upload_button.label": "Lägg till media", diff --git a/app/javascript/mastodon/locales/te.json b/app/javascript/mastodon/locales/te.json index 52bd41935b..ce0d4b9cc2 100644 --- a/app/javascript/mastodon/locales/te.json +++ b/app/javascript/mastodon/locales/te.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notifications", "tabs_bar.search": "Search", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Drag & drop to upload", "upload_button.label": "Add media", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index 32aa240804..f0ba25ec37 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notifications", "tabs_bar.search": "Search", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Drag & drop to upload", "upload_button.label": "Add media", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index b73157e27d..89f00fd3b8 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Yerel", "tabs_bar.notifications": "Bildirimler", "tabs_bar.search": "Search", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Upload için sürükle bırak yapınız", "upload_button.label": "Görsel ekle", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index b301c6827c..e81476541b 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "Локальна", "tabs_bar.notifications": "Сповіщення", "tabs_bar.search": "Search", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Перетягніть сюди, щоб завантажити", "upload_button.label": "Додати медіаконтент", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index 455f1f828b..05194c9094 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "本站", "tabs_bar.notifications": "通知", "tabs_bar.search": "Search", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "如果你现在离开 Mastodon,你的草稿内容将会被丢弃。", "upload_area.title": "将文件拖放到此处开始上传", "upload_button.label": "上传媒体文件", diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json index 18be177924..cad53c69ac 100644 --- a/app/javascript/mastodon/locales/zh-HK.json +++ b/app/javascript/mastodon/locales/zh-HK.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "本站", "tabs_bar.notifications": "通知", "tabs_bar.search": "搜尋", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "如果你現在離開 Mastodon,你的草稿內容將會被丟棄。", "upload_area.title": "將檔案拖放至此上載", "upload_button.label": "上載媒體檔案", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 3222c035a5..91b44012f0 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -280,6 +280,8 @@ "tabs_bar.local_timeline": "本地", "tabs_bar.notifications": "通知", "tabs_bar.search": "Search", + "timeline.media": "Media", + "timeline.posts": "Toots", "ui.beforeunload": "如果離開 Mastodon,你的草稿將會不見。", "upload_area.title": "拖放來上傳", "upload_button.label": "增加媒體", diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 90325be9c4..0765b3ffc5 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -4737,6 +4737,8 @@ a.status-card { } } +.community-timeline__section-headline, +.public-timeline__section-headline, .account__section-headline { background: darken($ui-base-color, 4%); border-bottom: 1px solid lighten($ui-base-color, 8%); diff --git a/app/services/batched_remove_status_service.rb b/app/services/batched_remove_status_service.rb index cb65a22565..425a11d012 100644 --- a/app/services/batched_remove_status_service.rb +++ b/app/services/batched_remove_status_service.rb @@ -81,6 +81,11 @@ class BatchedRemoveStatusService < BaseService redis.publish('timeline:public', payload) redis.publish('timeline:public:local', payload) if status.local? + if status.media_attachments.exists? + redis.publish('timeline:public:media', payload) + redis.publish('timeline:public:local:media', payload) if status.local? + end + @tags[status.id].each do |hashtag| redis.publish("timeline:hashtag:#{hashtag}", payload) redis.publish("timeline:hashtag:#{hashtag}:local", payload) if status.local? diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb index cb82a79ed3..b4b9986f3b 100644 --- a/app/services/fan_out_on_write_service.rb +++ b/app/services/fan_out_on_write_service.rb @@ -25,6 +25,7 @@ class FanOutOnWriteService < BaseService return if status.reply? && status.in_reply_to_account_id != status.account_id deliver_to_public(status) + deliver_to_media(status) if status.media_attachments.exists? end private @@ -85,6 +86,13 @@ class FanOutOnWriteService < BaseService Redis.current.publish('timeline:public:local', @payload) if status.local? end + def deliver_to_media(status) + Rails.logger.debug "Delivering status #{status.id} to media timeline" + + Redis.current.publish('timeline:public:media', @payload) + Redis.current.publish('timeline:public:local:media', @payload) if status.local? + end + def deliver_to_direct_timelines(status) Rails.logger.debug "Delivering status #{status.id} to direct timelines" diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb index e164c03ab1..53f2be6f86 100644 --- a/app/services/remove_status_service.rb +++ b/app/services/remove_status_service.rb @@ -20,6 +20,7 @@ class RemoveStatusService < BaseService remove_reblogs remove_from_hashtags remove_from_public + remove_from_media if status.media_attachments.exists? remove_from_direct if status.direct_visibility? @status.destroy! @@ -131,6 +132,13 @@ class RemoveStatusService < BaseService Redis.current.publish('timeline:public:local', @payload) if @status.local? end + def remove_from_media + return unless @status.public_visibility? + + Redis.current.publish('timeline:public:media', @payload) + Redis.current.publish('timeline:public:local:media', @payload) if @status.local? + end + def remove_from_direct @mentions.each do |mention| Redis.current.publish("timeline:direct:#{mention.account.id}", @payload) if mention.account.local? diff --git a/streaming/index.js b/streaming/index.js index 48bab8078e..4eaf668658 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -241,7 +241,9 @@ const startWorker = (workerId) => { const PUBLIC_STREAMS = [ 'public', + 'public:media', 'public:local', + 'public:local:media', 'hashtag', 'hashtag:local', ]; @@ -459,11 +461,17 @@ const startWorker = (workerId) => { }); app.get('/api/v1/streaming/public', (req, res) => { - streamFrom('timeline:public', req, streamToHttp(req, res), streamHttpEnd(req), true); + const onlyMedia = req.query.only_media === '1' || req.query.only_media === 'true'; + const channel = onlyMedia ? 'timeline:public:media' : 'timeline:public'; + + streamFrom(channel, req, streamToHttp(req, res), streamHttpEnd(req), true); }); app.get('/api/v1/streaming/public/local', (req, res) => { - streamFrom('timeline:public:local', req, streamToHttp(req, res), streamHttpEnd(req), true); + const onlyMedia = req.query.only_media === '1' || req.query.only_media === 'true'; + const channel = onlyMedia ? 'timeline:public:local:media' : 'timeline:public:local'; + + streamFrom(channel, req, streamToHttp(req, res), streamHttpEnd(req), true); }); app.get('/api/v1/streaming/direct', (req, res) => { @@ -521,6 +529,12 @@ const startWorker = (workerId) => { case 'public:local': streamFrom('timeline:public:local', req, streamToWs(req, ws), streamWsEnd(req, ws), true); break; + case 'public:media': + streamFrom('timeline:public:media', req, streamToWs(req, ws), streamWsEnd(req, ws), true); + break; + case 'public:local:media': + streamFrom('timeline:public:local:media', req, streamToWs(req, ws), streamWsEnd(req, ws), true); + break; case 'direct': streamFrom(`timeline:direct:${req.accountId}`, req, streamToWs(req, ws), streamWsEnd(req, ws), true); break;