Added self-destructing/expiring statuses

• Added:
- self-destructing/expiring statuses for GabPRO members only
- ExpiringStatusWorker
- stopwatch icon
- expires_at redux values
- expires_at button in composer
- expires at selection popover

• Updated:
- Schedule status button to not show if expiring status active
This commit is contained in:
mgabdev 2020-07-24 19:05:31 -05:00
parent 5f4e7aad31
commit 043fc01cea
16 changed files with 341 additions and 4 deletions

View File

@ -63,6 +63,7 @@ class Api::V1::StatusesController < Api::BaseController
spoiler_text: status_params[:spoiler_text],
visibility: status_params[:visibility],
scheduled_at: status_params[:scheduled_at],
expires_at: status_params[:expires_at],
application: doorkeeper_token.application,
poll: status_params[:poll],
idempotency: request.headers['Idempotency-Key'],
@ -79,6 +80,7 @@ class Api::V1::StatusesController < Api::BaseController
text: status_params[:status],
markdown: markdown,
media_ids: status_params[:media_ids],
expires_at: status_params[:expires_at],
sensitive: status_params[:sensitive],
spoiler_text: status_params[:spoiler_text],
visibility: status_params[:visibility],
@ -117,6 +119,7 @@ class Api::V1::StatusesController < Api::BaseController
:spoiler_text,
:visibility,
:scheduled_at,
:expires_at,
:group_id,
media_ids: [],
poll: [

View File

@ -15,6 +15,14 @@ import { updateTimeline, dequeueTimeline } from './timelines';
// import { showAlert, showAlertForError } from './alerts';
import { defineMessages } from 'react-intl';
import { openModal, closeModal } from './modal';
import {
STATUS_EXPIRATION_OPTION_5_MINUTES,
STATUS_EXPIRATION_OPTION_60_MINUTES,
STATUS_EXPIRATION_OPTION_6_HOURS,
STATUS_EXPIRATION_OPTION_24_HOURS,
STATUS_EXPIRATION_OPTION_3_DAYS,
STATUS_EXPIRATION_OPTION_7_DAYS,
} from '../constants'
import { me } from '../initial_state';
import { makeGetStatus } from '../selectors'
@ -67,6 +75,8 @@ export const COMPOSE_POLL_SETTINGS_CHANGE = 'COMPOSE_POLL_SETTINGS_CHANGE';
export const COMPOSE_SCHEDULED_AT_CHANGE = 'COMPOSE_SCHEDULED_AT_CHANGE';
export const COMPOSE_EXPIRES_AT_CHANGE = 'COMPOSE_EXPIRES_AT_CHANGE'
export const COMPOSE_RICH_TEXT_EDITOR_CONTROLS_VISIBILITY = 'COMPOSE_RICH_TEXT_EDITOR_CONTROLS_VISIBILITY'
export const COMPOSE_CLEAR = 'COMPOSE_CLEAR'
@ -299,6 +309,24 @@ export function submitCompose(groupId, replyToId = null, router, isStandalone, a
let scheduled_at = getState().getIn(['compose', 'scheduled_at'], null);
if (scheduled_at !== null) scheduled_at = moment.utc(scheduled_at).toDate();
let expires_at = getState().getIn(['compose', 'expires_at'], null);
if (expires_at) {
if (expires_at === STATUS_EXPIRATION_OPTION_5_MINUTES) {
expires_at = moment.utc().add('5', 'minute').toDate()
} else if (expires_at === STATUS_EXPIRATION_OPTION_60_MINUTES) {
expires_at = moment.utc().add('60', 'minute').toDate()
} else if (expires_at === STATUS_EXPIRATION_OPTION_6_HOURS) {
expires_at = moment.utc().add('6', 'hour').toDate()
} else if (expires_at === STATUS_EXPIRATION_OPTION_24_HOURS) {
expires_at = moment.utc().add('24', 'hour').toDate()
} else if (expires_at === STATUS_EXPIRATION_OPTION_3_DAYS) {
expires_at = moment.utc().add('3', 'day').toDate()
} else if (expires_at === STATUS_EXPIRATION_OPTION_7_DAYS) {
expires_at = moment.utc().add('7', 'day').toDate()
}
}
if (isMobile(window.innerWidth) && router && isStandalone) {
router.history.goBack()
}
@ -306,6 +334,7 @@ export function submitCompose(groupId, replyToId = null, router, isStandalone, a
api(getState)[method](endpoint, {
status,
markdown,
expires_at,
scheduled_at,
autoJoinGroup,
in_reply_to_id: inReplyToId,
@ -715,6 +744,13 @@ export function changeScheduledAt(date) {
};
};
export function changeExpiresAt(value) {
return {
type: COMPOSE_EXPIRES_AT_CHANGE,
value,
}
}
export function changeRichTextEditorControlsVisibility(open) {
return {
type: COMPOSE_RICH_TEXT_EDITOR_CONTROLS_VISIBILITY,

View File

@ -0,0 +1,25 @@
const StopwatchIcon = ({
className = '',
size = '16px',
title = '',
}) => (
<svg
className={className}
version='1.1'
xmlns='http://www.w3.org/2000/svg'
x='0px'
y='0px'
width={size}
height={size}
viewBox='0 0 48 48'
xmlSpace='preserve'
aria-label={title}
>
<g>
<path d='M 18.01 0 L 18.01 4 L 22.02 4 L 22.02 6.01 C 23.42 5.8 24.62 5.8 26.02 6.01 L 26.02 4 L 30.02 4 L 30.02 0 Z M 18.01 0' />
<path d='M 37.63 13.21 L 39.04 11.81 L 40.64 13.41 L 43.44 10.61 L 37.43 4.61 L 34.63 7.41 L 36.23 9.01 L 34.43 10.81 C 24.82 5 12.41 8.01 6.8 17.62 C 1.2 27.23 4.2 39.24 13.61 45.05 C 23.02 50.85 35.43 47.85 41.04 38.24 C 46.04 30.03 44.64 19.62 37.63 13.21 Z M 24.02 41.84 C 16.21 41.84 10 35.64 10 27.83 C 10 20.02 16.21 13.81 24.02 13.81 L 24.02 27.83 L 38.03 27.83 C 38.03 35.64 31.83 41.84 24.02 41.84 Z M 24.02 41.84' />
</g>
</svg>
)
export default StopwatchIcon

View File

@ -68,6 +68,7 @@ import ShareIcon from '../assets/share_icon'
import ShopIcon from '../assets/shop_icon'
import SortIcon from '../assets/sort_icon'
import StarIcon from '../assets/star_icon'
import StopWatchIcon from '../assets/stopwatch_icon'
import StrikethroughIcon from '../assets/strikethrough_icon'
import SubtractIcon from '../assets/subtract_icon'
import TextSizeIcon from '../assets/text_size_icon'
@ -150,6 +151,7 @@ const ICONS = {
'shop': ShopIcon,
'sort': SortIcon,
'star': StarIcon,
'stopwatch': StopWatchIcon,
'strikethrough': StrikethroughIcon,
'subtract': SubtractIcon,
'text-size': TextSizeIcon,

View File

@ -10,6 +10,7 @@ import {
POPOVER_SEARCH,
POPOVER_SIDEBAR_MORE,
POPOVER_STATUS_OPTIONS,
POPOVER_STATUS_EXPIRATION_OPTIONS,
POPOVER_STATUS_VISIBILITY,
POPOVER_USER_INFO,
POPOVER_VIDEO_STATS,
@ -24,6 +25,7 @@ import {
ProfileOptionsPopover,
SearchPopover,
SidebarMorePopover,
StatusExpirationOptionsPopover,
StatusOptionsPopover,
StatusVisibilityPopover,
UserInfoPopover,
@ -51,6 +53,7 @@ POPOVER_COMPONENTS[POPOVER_PROFILE_OPTIONS] = ProfileOptionsPopover
POPOVER_COMPONENTS[POPOVER_SEARCH] = SearchPopover
POPOVER_COMPONENTS[POPOVER_SIDEBAR_MORE] = SidebarMorePopover
POPOVER_COMPONENTS[POPOVER_STATUS_OPTIONS] = StatusOptionsPopover
POPOVER_COMPONENTS[POPOVER_STATUS_EXPIRATION_OPTIONS] = StatusExpirationOptionsPopover
POPOVER_COMPONENTS[POPOVER_STATUS_VISIBILITY] = StatusVisibilityPopover
POPOVER_COMPONENTS[POPOVER_USER_INFO] = UserInfoPopover
POPOVER_COMPONENTS[POPOVER_VIDEO_STATS] = VideoStatsPopover

View File

@ -0,0 +1,126 @@
import { defineMessages, injectIntl } from 'react-intl'
import { closePopover } from '../../actions/popover'
import { changeExpiresAt } from '../../actions/compose'
import {
STATUS_EXPIRATION_OPTION_5_MINUTES,
STATUS_EXPIRATION_OPTION_60_MINUTES,
STATUS_EXPIRATION_OPTION_6_HOURS,
STATUS_EXPIRATION_OPTION_24_HOURS,
STATUS_EXPIRATION_OPTION_3_DAYS,
STATUS_EXPIRATION_OPTION_7_DAYS,
} from '../../constants'
import PopoverLayout from './popover_layout'
import List from '../list'
const messages = defineMessages({
minutes: { id: 'intervals.full.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}}' },
hours: { id: 'intervals.full.hours', defaultMessage: '{number, plural, one {# hour} other {# hours}}' },
days: { id: 'intervals.full.days', defaultMessage: '{number, plural, one {# day} other {# days}}' },
})
const mapStateToProps = (state) => ({
expiresAtValue: state.getIn(['compose', 'expires_at']),
})
const mapDispatchToProps = (dispatch) => ({
onChangeExpiresAt(expiresAt) {
dispatch(changeExpiresAt(expiresAt))
},
onClosePopover() {
dispatch(closePopover())
},
})
export default
@injectIntl
@connect(mapStateToProps, mapDispatchToProps)
class StatusExpirationOptionsPopover extends PureComponent {
static defaultProps = {
expiresAtValue: PropTypes.string.isRequired,
intl: PropTypes.object.isRequired,
isXS: PropTypes.bool,
onChangeExpiresAt: PropTypes.func.isRequired,
}
handleOnSetStatusExpiration = (expiresAt) => {
this.props.onChangeExpiresAt(expiresAt)
this.handleOnClosePopover()
}
handleOnClosePopover = () => {
this.props.onClosePopover()
}
render() {
const {
expiresAtValue,
intl,
isXS,
} = this.props
const listItems = [
{
hideArrow: true,
title: intl.formatMessage(messages.minutes, { number: 5 }),
onClick: () => this.handleOnSetStatusExpiration(STATUS_EXPIRATION_OPTION_5_MINUTES),
isActive: expiresAtValue === STATUS_EXPIRATION_OPTION_5_MINUTES,
},
{
hideArrow: true,
title: intl.formatMessage(messages.minutes, { number: 60 }),
onClick: () => this.handleOnSetStatusExpiration(STATUS_EXPIRATION_OPTION_60_MINUTES),
isActive: expiresAtValue === STATUS_EXPIRATION_OPTION_60_MINUTES,
},
{
hideArrow: true,
title: '6 hours',
title: intl.formatMessage(messages.hours, { number: 6 }),
onClick: () => this.handleOnSetStatusExpiration(STATUS_EXPIRATION_OPTION_6_HOURS),
isActive: expiresAtValue === STATUS_EXPIRATION_OPTION_6_HOURS,
},
{
hideArrow: true,
title: intl.formatMessage(messages.hours, { number: 24 }),
onClick: () => this.handleOnSetStatusExpiration(STATUS_EXPIRATION_OPTION_24_HOURS),
isActive: expiresAtValue === STATUS_EXPIRATION_OPTION_24_HOURS,
},
{
hideArrow: true,
title: '3 days',
title: intl.formatMessage(messages.days, { number: 3 }),
onClick: () => this.handleOnSetStatusExpiration(STATUS_EXPIRATION_OPTION_3_DAYS),
isActive: expiresAtValue === STATUS_EXPIRATION_OPTION_3_DAYS,
},
{
hideArrow: true,
title: intl.formatMessage(messages.days, { number: 7 }),
onClick: () => this.handleOnSetStatusExpiration(STATUS_EXPIRATION_OPTION_7_DAYS),
isActive: expiresAtValue === STATUS_EXPIRATION_OPTION_7_DAYS,
},
]
if (expiresAtValue) {
listItems.unshift({
hideArrow: true,
title: 'Remove expiration',
onClick: () => this.handleOnSetStatusExpiration(null),
},)
}
return (
<PopoverLayout
width={210}
isXS={isXS}
onClose={this.handleOnClosePopover}
>
<List
scrollKey='group_list_sort_options'
items={listItems}
size={isXS ? 'large' : 'small'}
/>
</PopoverLayout>
)
}
}

View File

@ -4,6 +4,7 @@ import { NavLink } from 'react-router-dom'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import classNames from 'classnames/bind'
import moment from 'moment-mini'
import { openPopover } from '../actions/popover'
import { openModal } from '../actions/modal'
import { me } from '../initial_state'
@ -17,6 +18,7 @@ import Avatar from './avatar'
const messages = defineMessages({
edited: { id: 'status.edited', defaultMessage: 'Edited' },
expirationMessage: { id: 'status.expiration_message', defaultMessage: 'This status expires {time}' },
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
public_long: { id: 'privacy.public.long', defaultMessage: 'Visible for anyone on or off Gab' },
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
@ -101,6 +103,12 @@ class StatusHeader extends ImmutablePureComponent {
visibilityText = `${intl.formatMessage(messages.public_short)} - ${intl.formatMessage(messages.public_long)}`
}
const expirationDate = status.get('expires_at')
let timeUntilExpiration
if (!!expirationDate) {
timeUntilExpiration = moment(expirationDate).fromNow()
}
return (
<div className={containerClasses}>
<div className={[_s.default, _s.flexRow, _s.mt5].join(' ')}>
@ -162,6 +170,18 @@ class StatusHeader extends ImmutablePureComponent {
<Icon id={visibilityIcon} size='12px' className={[_s.default, _s.fillSecondary].join(' ')} />
</span>
{
!!status.get('expires_at') &&
<Fragment>
<DotTextSeperator />
<span title={intl.formatMessage(messages.expirationMessage, {
time: timeUntilExpiration,
})} className={[_s.default, _s.displayInline, _s.ml5].join(' ')}>
<Icon id='stopwatch' size='13px' className={[_s.default, _s.fillSecondary].join(' ')} />
</span>
</Fragment>
}
{
!!status.get('group') &&
<Fragment>

View File

@ -30,6 +30,7 @@ export const POPOVER_PROFILE_OPTIONS = 'PROFILE_OPTIONS'
export const POPOVER_SEARCH = 'SEARCH'
export const POPOVER_SIDEBAR_MORE = 'SIDEBAR_MORE'
export const POPOVER_STATUS_OPTIONS = 'STATUS_OPTIONS'
export const POPOVER_STATUS_EXPIRATION_OPTIONS = 'STATUS_EXPIRATION_OPTIONS'
export const POPOVER_STATUS_VISIBILITY = 'STATUS_VISIBILITY'
export const POPOVER_USER_INFO = 'USER_INFO'
export const POPOVER_VIDEO_STATS = 'VIDEO_STATS'
@ -111,4 +112,11 @@ export const NOTIFICATION_FILTERS = [
export const GAB_COM_INTRODUCE_YOURSELF_GROUP_ID = '12'
export const MIN_ACCOUNT_CREATED_AT_ONBOARDING = 1594789200000 // 2020-07-15
export const MIN_ACCOUNT_CREATED_AT_ONBOARDING = 1594789200000 // 2020-07-15
export const STATUS_EXPIRATION_OPTION_5_MINUTES = '5-minutes'
export const STATUS_EXPIRATION_OPTION_60_MINUTES = '60-minutes'
export const STATUS_EXPIRATION_OPTION_6_HOURS = '6-hours'
export const STATUS_EXPIRATION_OPTION_24_HOURS = '24-hours'
export const STATUS_EXPIRATION_OPTION_3_DAYS = '3-days'
export const STATUS_EXPIRATION_OPTION_7_DAYS = '7-days'

View File

@ -21,6 +21,7 @@ import PollButton from './poll_button'
import PollForm from './poll_form'
import SchedulePostButton from './schedule_post_button'
import SpoilerButton from './spoiler_button'
import ExpiresPostButton from './expires_post_button'
import RichTextEditorButton from './rich_text_editor_button'
import StatusContainer from '../../../containers/status_container'
import StatusVisibilityButton from './status_visibility_button'
@ -484,10 +485,15 @@ class ComposeForm extends ImmutablePureComponent {
<StatusVisibilityButton />
<SpoilerButton />
{
!hidePro &&
!hidePro && !edit &&
<SchedulePostButton />
}
{
!hidePro && !edit &&
<ExpiresPostButton />
}
{
!hidePro &&
<Responsive min={BREAKPOINT_EXTRA_SMALL}>

View File

@ -0,0 +1,78 @@
import { injectIntl, defineMessages } from 'react-intl'
import { openModal } from '../../../actions/modal'
import { openPopover } from '../../../actions/popover'
import { me } from '../../../initial_state'
import { POPOVER_STATUS_EXPIRATION_OPTIONS } from '../../../constants'
import ComposeExtraButton from './compose_extra_button'
const messages = defineMessages({
expires: { id: 'expiration.title', defaultMessage: 'Add status expiration' },
})
const mapStateToProps = (state) => ({
hasScheduledAt: !!state.getIn(['compose', 'scheduled_at']),
active: !!state.getIn(['compose', 'expires_at']) || state.getIn(['popover', 'popoverType']) === POPOVER_STATUS_EXPIRATION_OPTIONS,
isPro: state.getIn(['accounts', me, 'is_pro']),
})
const mapDispatchToProps = (dispatch) => ({
onOpenExpirationPopover(targetRef) {
dispatch(openPopover(POPOVER_STATUS_EXPIRATION_OPTIONS, {
targetRef,
}))
},
onOpenProUpgradeModal() {
dispatch(openModal('PRO_UPGRADE'))
},
})
export default
@injectIntl
@connect(mapStateToProps, mapDispatchToProps)
class ExpiresPostButton extends PureComponent {
static propTypes = {
active: PropTypes.bool.isRequired,
intl: PropTypes.object.isRequired,
isPro: PropTypes.bool,
hasScheduledAt: PropTypes.bool,
onOpenProUpgradeModal: PropTypes.func.isRequired,
onOpenExpirationPopover: PropTypes.func.isRequired,
small: PropTypes.bool,
}
handleToggle = () => {
if (!this.props.isPro) {
this.props.onOpenProUpgradeModal()
} else {
this.props.onOpenExpirationPopover(this.button)
}
}
setButton = (n) => {
this.button = n
}
render () {
const {
active,
intl,
hasScheduledAt,
small,
} = this.props
if (hasScheduledAt) return null
return (
<ComposeExtraButton
active={active}
buttonRef={this.setButton}
icon='stopwatch'
onClick={this.handleToggle}
small={small}
title={intl.formatMessage(messages.expires)}
/>
)
}
}

View File

@ -11,6 +11,7 @@ const messages = defineMessages({
})
const mapStateToProps = (state) => ({
hasExpiresAt: !!state.getIn(['compose', 'expires_at']),
active: !!state.getIn(['compose', 'scheduled_at']) || state.getIn(['popover', 'popoverType']) === 'DATE_PICKER',
isPro: state.getIn(['accounts', me, 'is_pro']),
})
@ -34,12 +35,13 @@ const mapDispatchToProps = (dispatch) => ({
export default
@injectIntl
@connect(mapStateToProps, mapDispatchToProps)
class SchedulePostDropdown extends PureComponent {
class SchedulePostButton extends PureComponent {
static propTypes = {
active: PropTypes.bool.isRequired,
intl: PropTypes.object.isRequired,
isPro: PropTypes.bool,
hasExpiresAt: PropTypes.bool,
onOpenProUpgradeModal: PropTypes.func.isRequired,
onOpenDatePickerPopover: PropTypes.func.isRequired,
onCloseDatePickerPopover: PropTypes.func.isRequired,
@ -62,9 +64,12 @@ class SchedulePostDropdown extends PureComponent {
const {
active,
intl,
hasExpiresAt,
small,
} = this.props
if (hasExpiresAt) return null
return (
<ComposeExtraButton
active={active}

View File

@ -71,6 +71,7 @@ export function Status() { return import(/* webpackChunkName: "components/status
export function StatusFeature() { return import(/* webpackChunkName: "features/status" */'../../status') }
export function SearchPopover() { return import(/* webpackChunkName: "components/search_popover" */'../../../components/popover/search_popover') }
export function SidebarMorePopover() { return import(/* webpackChunkName: "components/sidebar_more_popover" */'../../../components/popover/sidebar_more_popover') }
export function StatusExpirationOptionsPopover() { return import(/* webpackChunkName: "components/status_expiration_options_popover" */'../../../components/popover/status_expiration_options_popover') }
export function StatusLikes() { return import(/* webpackChunkName: "features/status_likes" */'../../status_likes') }
export function StatusOptionsPopover() { return import(/* webpackChunkName: "components/status_options_popover" */'../../../components/popover/status_options_popover') }
export function StatusReposts() { return import(/* webpackChunkName: "features/status_reposts" */'../../status_reposts') }

View File

@ -36,6 +36,7 @@ import {
COMPOSE_POLL_OPTION_REMOVE,
COMPOSE_POLL_SETTINGS_CHANGE,
COMPOSE_SCHEDULED_AT_CHANGE,
COMPOSE_EXPIRES_AT_CHANGE,
COMPOSE_RICH_TEXT_EDITOR_CONTROLS_VISIBILITY,
COMPOSE_CLEAR,
} from '../actions/compose';
@ -77,6 +78,7 @@ const initialState = ImmutableMap({
idempotencyKey: null,
tagHistory: ImmutableList(),
scheduled_at: null,
expires_at: null,
rte_controls_visible: false,
gif: null,
});
@ -114,6 +116,7 @@ function clearAll(state) {
map.set('poll', null);
map.set('idempotencyKey', uuid());
map.set('scheduled_at', null);
map.set('expires_at', null);
map.set('rte_controls_visible', false);
map.set('gif', false);
});
@ -309,6 +312,7 @@ export default function compose(state = initialState, action) {
map.set('poll', null);
map.set('idempotencyKey', uuid());
map.set('scheduled_at', null);
map.set('expires_at', null);
map.set('rte_controls_visible', false);
});
case COMPOSE_SUBMIT_REQUEST:
@ -404,6 +408,8 @@ export default function compose(state = initialState, action) {
return state.update('poll', poll => poll.set('expires_in', action.expiresIn).set('multiple', action.isMultiple));
case COMPOSE_SCHEDULED_AT_CHANGE:
return state.set('scheduled_at', action.date);
case COMPOSE_EXPIRES_AT_CHANGE:
return state.set('expires_at', action.value);
case COMPOSE_RICH_TEXT_EDITOR_CONTROLS_VISIBILITY:
return state.withMutations(map => {
map.set('rte_controls_visible', action.open || !state.get('rte_controls_visible'));

View File

@ -4,7 +4,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
attributes :id, :created_at, :revised_at, :in_reply_to_id, :in_reply_to_account_id,
:sensitive, :spoiler_text, :visibility, :language,
:uri, :url, :replies_count, :reblogs_count,
:favourites_count, :quote_of_id
:favourites_count, :quote_of_id, :expires_at
attribute :favourited, if: :current_user?
attribute :reblogged, if: :current_user?

View File

@ -16,6 +16,7 @@ class PostStatusService < BaseService
# @option [String] :spoiler_text
# @option [String] :language
# @option [String] :scheduled_at
# @option [String] :expires_at
# @option [Hash] :poll Optional poll to attach
# @option [Enumerable] :media_ids Optional array of media IDs to attach
# @option [Doorkeeper::Application] :application
@ -55,6 +56,7 @@ class PostStatusService < BaseService
@text = @options.delete(:spoiler_text) if @text.blank? && @options[:spoiler_text].present?
@visibility = @options[:visibility] || @account.user&.setting_default_privacy
@visibility = :unlisted if @visibility == :public && @account.silenced?
@expires_at = @options[:expires_at]&.to_datetime if @account.is_pro
@scheduled_at = @options[:scheduled_at]&.to_datetime
@scheduled_at = nil if scheduled_in_the_past?
rescue ArgumentError
@ -98,6 +100,7 @@ class PostStatusService < BaseService
DistributionWorker.perform_async(@status.id)
# Pubsubhubbub::DistributionWorker.perform_async(@status.stream_entry.id)
# ActivityPub::DistributionWorker.perform_async(@status.id)
ExpiringStatusWorker.perform_at(@status.expires_at, @status.id) if @status.expires_at && @account.is_pro
PollExpirationNotifyWorker.perform_at(@status.poll.expires_at, @status.poll.id) if @status.poll
end
@ -177,6 +180,7 @@ class PostStatusService < BaseService
{
text: @text,
markdown: @markdown,
expires_at: @expires_at,
group_id: @options[:group_id],
quote_of_id: @options[:quote_of_id],
media_attachments: @media || [],

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
class ExpiringStatusWorker
include Sidekiq::Worker
sidekiq_options unique: :until_executed
def perform(status_id)
status = Status.find(status_id)
RemovalWorker.perform_async(status.id)
rescue ActiveRecord::RecordNotFound
true
end
end