diff options
Diffstat (limited to 'app/javascript/themes/glitch/features/status')
5 files changed, 0 insertions, 733 deletions
diff --git a/app/javascript/themes/glitch/features/status/components/action_bar.js b/app/javascript/themes/glitch/features/status/components/action_bar.js deleted file mode 100644 index 6cda988d1..000000000 --- a/app/javascript/themes/glitch/features/status/components/action_bar.js +++ /dev/null @@ -1,129 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import IconButton from 'themes/glitch/components/icon_button'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import DropdownMenuContainer from 'themes/glitch/containers/dropdown_menu_container'; -import { defineMessages, injectIntl } from 'react-intl'; -import { me } from 'themes/glitch/util/initial_state'; - -const messages = defineMessages({ - delete: { id: 'status.delete', defaultMessage: 'Delete' }, - mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' }, - reply: { id: 'status.reply', defaultMessage: 'Reply' }, - reblog: { id: 'status.reblog', defaultMessage: 'Boost' }, - cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' }, - favourite: { id: 'status.favourite', defaultMessage: 'Favourite' }, - report: { id: 'status.report', defaultMessage: 'Report @{name}' }, - share: { id: 'status.share', defaultMessage: 'Share' }, - pin: { id: 'status.pin', defaultMessage: 'Pin on profile' }, - unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' }, - embed: { id: 'status.embed', defaultMessage: 'Embed' }, -}); - -@injectIntl -export default class ActionBar extends React.PureComponent { - - static contextTypes = { - router: PropTypes.object, - }; - - static propTypes = { - status: ImmutablePropTypes.map.isRequired, - onReply: PropTypes.func.isRequired, - onReblog: PropTypes.func.isRequired, - onFavourite: PropTypes.func.isRequired, - onDelete: PropTypes.func.isRequired, - onMention: PropTypes.func.isRequired, - onReport: PropTypes.func, - onPin: PropTypes.func, - onEmbed: PropTypes.func, - intl: PropTypes.object.isRequired, - }; - - handleReplyClick = () => { - this.props.onReply(this.props.status); - } - - handleReblogClick = (e) => { - this.props.onReblog(this.props.status, e); - } - - handleFavouriteClick = () => { - this.props.onFavourite(this.props.status); - } - - handleDeleteClick = () => { - this.props.onDelete(this.props.status); - } - - handleMentionClick = () => { - this.props.onMention(this.props.status.get('account'), this.context.router.history); - } - - handleReport = () => { - this.props.onReport(this.props.status); - } - - handlePinClick = () => { - this.props.onPin(this.props.status); - } - - handleShare = () => { - navigator.share({ - text: this.props.status.get('search_index'), - url: this.props.status.get('url'), - }); - } - - handleEmbed = () => { - this.props.onEmbed(this.props.status); - } - - render () { - const { status, intl } = this.props; - - const publicStatus = ['public', 'unlisted'].includes(status.get('visibility')); - - let menu = []; - - if (publicStatus) { - menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed }); - } - - if (me === status.getIn(['account', 'id'])) { - if (publicStatus) { - menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick }); - } - - menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick }); - } else { - menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick }); - menu.push(null); - menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport }); - } - - const shareButton = ('share' in navigator) && status.get('visibility') === 'public' && ( - <div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.share)} icon='share-alt' onClick={this.handleShare} /></div> - ); - - let reblogIcon = 'retweet'; - //if (status.get('visibility') === 'direct') reblogIcon = 'envelope'; - // else if (status.get('visibility') === 'private') reblogIcon = 'lock'; - - let reblog_disabled = (status.get('visibility') === 'direct' || status.get('visibility') === 'private'); - - return ( - <div className='detailed-status__action-bar'> - <div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_id', null) === null ? 'reply' : 'reply-all'} onClick={this.handleReplyClick} /></div> - <div className='detailed-status__button'><IconButton disabled={reblog_disabled} active={status.get('reblogged')} title={reblog_disabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} /></div> - <div className='detailed-status__button'><IconButton animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} activeStyle={{ color: '#ca8f04' }} /></div> - {shareButton} - - <div className='detailed-status__action-bar-dropdown'> - <DropdownMenuContainer size={18} icon='ellipsis-h' items={menu} direction='left' ariaLabel='More' /> - </div> - </div> - ); - } - -} diff --git a/app/javascript/themes/glitch/features/status/components/card.js b/app/javascript/themes/glitch/features/status/components/card.js deleted file mode 100644 index bb83374b9..000000000 --- a/app/javascript/themes/glitch/features/status/components/card.js +++ /dev/null @@ -1,125 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import punycode from 'punycode'; -import classnames from 'classnames'; - -const IDNA_PREFIX = 'xn--'; - -const decodeIDNA = domain => { - return domain - .split('.') - .map(part => part.indexOf(IDNA_PREFIX) === 0 ? punycode.decode(part.slice(IDNA_PREFIX.length)) : part) - .join('.'); -}; - -const getHostname = url => { - const parser = document.createElement('a'); - parser.href = url; - return parser.hostname; -}; - -export default class Card extends React.PureComponent { - - static propTypes = { - card: ImmutablePropTypes.map, - maxDescription: PropTypes.number, - }; - - static defaultProps = { - maxDescription: 50, - }; - - state = { - width: 0, - }; - - renderLink () { - const { card, maxDescription } = this.props; - - let image = ''; - let provider = card.get('provider_name'); - - if (card.get('image')) { - image = ( - <div className='status-card__image'> - <img src={card.get('image')} alt={card.get('title')} className='status-card__image-image' width={card.get('width')} height={card.get('height')} /> - </div> - ); - } - - if (provider.length < 1) { - provider = decodeIDNA(getHostname(card.get('url'))); - } - - const className = classnames('status-card', { - 'horizontal': card.get('width') > card.get('height'), - }); - - return ( - <a href={card.get('url')} className={className} target='_blank' rel='noopener'> - {image} - - <div className='status-card__content'> - <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong> - <p className='status-card__description'>{(card.get('description') || '').substring(0, maxDescription)}</p> - <span className='status-card__host'>{provider}</span> - </div> - </a> - ); - } - - renderPhoto () { - const { card } = this.props; - - return ( - <a href={card.get('url')} className='status-card-photo' target='_blank' rel='noopener'> - <img src={card.get('url')} alt={card.get('title')} width={card.get('width')} height={card.get('height')} /> - </a> - ); - } - - setRef = c => { - if (c) { - this.setState({ width: c.offsetWidth }); - } - } - - renderVideo () { - const { card } = this.props; - const content = { __html: card.get('html') }; - const { width } = this.state; - const ratio = card.get('width') / card.get('height'); - const height = card.get('width') > card.get('height') ? (width / ratio) : (width * ratio); - - return ( - <div - ref={this.setRef} - className='status-card-video' - dangerouslySetInnerHTML={content} - style={{ height }} - /> - ); - } - - render () { - const { card } = this.props; - - if (card === null) { - return null; - } - - switch(card.get('type')) { - case 'link': - return this.renderLink(); - case 'photo': - return this.renderPhoto(); - case 'video': - return this.renderVideo(); - case 'rich': - default: - return null; - } - } - -} diff --git a/app/javascript/themes/glitch/features/status/components/detailed_status.js b/app/javascript/themes/glitch/features/status/components/detailed_status.js deleted file mode 100644 index 7606bfbf3..000000000 --- a/app/javascript/themes/glitch/features/status/components/detailed_status.js +++ /dev/null @@ -1,128 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import Avatar from 'themes/glitch/components/avatar'; -import DisplayName from 'themes/glitch/components/display_name'; -import StatusContent from 'themes/glitch/components/status_content'; -import StatusGallery from 'themes/glitch/components/media_gallery'; -import AttachmentList from 'themes/glitch/components/attachment_list'; -import { Link } from 'react-router-dom'; -import { FormattedDate, FormattedNumber } from 'react-intl'; -import CardContainer from '../containers/card_container'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import Video from 'themes/glitch/features/video'; -import VisibilityIcon from 'themes/glitch/components/status_visibility_icon'; - -export default class DetailedStatus extends ImmutablePureComponent { - - static contextTypes = { - router: PropTypes.object, - }; - - static propTypes = { - status: ImmutablePropTypes.map.isRequired, - settings: ImmutablePropTypes.map.isRequired, - onOpenMedia: PropTypes.func.isRequired, - onOpenVideo: PropTypes.func.isRequired, - }; - - handleAccountClick = (e) => { - if (e.button === 0) { - e.preventDefault(); - this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); - } - - e.stopPropagation(); - } - - // handleOpenVideo = startTime => { - // this.props.onOpenVideo(this.props.status.getIn(['media_attachments', 0]), startTime); - // } - - render () { - const status = this.props.status.get('reblog') ? this.props.status.get('reblog') : this.props.status; - const { settings } = this.props; - - let media = ''; - let mediaIcon = null; - let applicationLink = ''; - let reblogLink = ''; - let reblogIcon = 'retweet'; - - if (status.get('media_attachments').size > 0) { - if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) { - media = <AttachmentList media={status.get('media_attachments')} />; - } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') { - media = ( - <Video - sensitive={status.get('sensitive')} - media={status.getIn(['media_attachments', 0])} - letterbox={settings.getIn(['media', 'letterbox'])} - fullwidth={settings.getIn(['media', 'fullwidth'])} - onOpenVideo={this.props.onOpenVideo} - autoplay - /> - ); - mediaIcon = 'video-camera'; - } else { - media = ( - <StatusGallery - sensitive={status.get('sensitive')} - media={status.get('media_attachments')} - letterbox={settings.getIn(['media', 'letterbox'])} - onOpenMedia={this.props.onOpenMedia} - /> - ); - mediaIcon = 'picture-o'; - } - } else media = <CardContainer statusId={status.get('id')} />; - - if (status.get('application')) { - applicationLink = <span> · <a className='detailed-status__application' href={status.getIn(['application', 'website'])} target='_blank' rel='noopener'>{status.getIn(['application', 'name'])}</a></span>; - } - - if (status.get('visibility') === 'direct') { - reblogIcon = 'envelope'; - } else if (status.get('visibility') === 'private') { - reblogIcon = 'lock'; - } - - if (status.get('visibility') === 'private') { - reblogLink = <i className={`fa fa-${reblogIcon}`} />; - } else { - reblogLink = (<Link to={`/statuses/${status.get('id')}/reblogs`} className='detailed-status__link'> - <i className={`fa fa-${reblogIcon}`} /> - <span className='detailed-status__reblogs'> - <FormattedNumber value={status.get('reblogs_count')} /> - </span> - </Link>); - } - - return ( - <div className='detailed-status'> - <a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='detailed-status__display-name'> - <div className='detailed-status__display-avatar'><Avatar account={status.get('account')} size={48} /></div> - <DisplayName account={status.get('account')} /> - </a> - - <StatusContent - status={status} - media={media} - mediaIcon={mediaIcon} - /> - - <div className='detailed-status__meta'> - <a className='detailed-status__datetime' href={status.get('url')} target='_blank' rel='noopener'> - <FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' /> - </a>{applicationLink} · {reblogLink} · <Link to={`/statuses/${status.get('id')}/favourites`} className='detailed-status__link'> - <i className='fa fa-star' /> - <span className='detailed-status__favorites'> - <FormattedNumber value={status.get('favourites_count')} /> - </span> - </Link> · <VisibilityIcon visibility={status.get('visibility')} /> - </div> - </div> - ); - } - -} diff --git a/app/javascript/themes/glitch/features/status/containers/card_container.js b/app/javascript/themes/glitch/features/status/containers/card_container.js deleted file mode 100644 index a97404de1..000000000 --- a/app/javascript/themes/glitch/features/status/containers/card_container.js +++ /dev/null @@ -1,8 +0,0 @@ -import { connect } from 'react-redux'; -import Card from '../components/card'; - -const mapStateToProps = (state, { statusId }) => ({ - card: state.getIn(['cards', statusId], null), -}); - -export default connect(mapStateToProps)(Card); diff --git a/app/javascript/themes/glitch/features/status/index.js b/app/javascript/themes/glitch/features/status/index.js deleted file mode 100644 index 57af94a9a..000000000 --- a/app/javascript/themes/glitch/features/status/index.js +++ /dev/null @@ -1,343 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { fetchStatus } from 'themes/glitch/actions/statuses'; -import MissingIndicator from 'themes/glitch/components/missing_indicator'; -import DetailedStatus from './components/detailed_status'; -import ActionBar from './components/action_bar'; -import Column from 'themes/glitch/features/ui/components/column'; -import { - favourite, - unfavourite, - reblog, - unreblog, - pin, - unpin, -} from 'themes/glitch/actions/interactions'; -import { - replyCompose, - mentionCompose, -} from 'themes/glitch/actions/compose'; -import { deleteStatus } from 'themes/glitch/actions/statuses'; -import { initReport } from 'themes/glitch/actions/reports'; -import { makeGetStatus } from 'themes/glitch/selectors'; -import { ScrollContainer } from 'react-router-scroll-4'; -import ColumnBackButton from 'themes/glitch/components/column_back_button'; -import StatusContainer from 'themes/glitch/containers/status_container'; -import { openModal } from 'themes/glitch/actions/modal'; -import { defineMessages, injectIntl } from 'react-intl'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { HotKeys } from 'react-hotkeys'; -import { boostModal, deleteModal } from 'themes/glitch/util/initial_state'; -import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from 'themes/glitch/util/fullscreen'; - -const messages = defineMessages({ - deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, - deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' }, -}); - -const makeMapStateToProps = () => { - const getStatus = makeGetStatus(); - - const mapStateToProps = (state, props) => ({ - status: getStatus(state, props.params.statusId), - settings: state.get('local_settings'), - ancestorsIds: state.getIn(['contexts', 'ancestors', props.params.statusId]), - descendantsIds: state.getIn(['contexts', 'descendants', props.params.statusId]), - }); - - return mapStateToProps; -}; - -@injectIntl -@connect(makeMapStateToProps) -export default class Status extends ImmutablePureComponent { - - static contextTypes = { - router: PropTypes.object, - }; - - static propTypes = { - params: PropTypes.object.isRequired, - dispatch: PropTypes.func.isRequired, - status: ImmutablePropTypes.map, - settings: ImmutablePropTypes.map.isRequired, - ancestorsIds: ImmutablePropTypes.list, - descendantsIds: ImmutablePropTypes.list, - intl: PropTypes.object.isRequired, - }; - - state = { - fullscreen: false, - }; - - componentWillMount () { - this.props.dispatch(fetchStatus(this.props.params.statusId)); - } - - componentDidMount () { - attachFullscreenListener(this.onFullScreenChange); - } - - componentWillReceiveProps (nextProps) { - if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { - this._scrolledIntoView = false; - this.props.dispatch(fetchStatus(nextProps.params.statusId)); - } - } - - handleFavouriteClick = (status) => { - if (status.get('favourited')) { - this.props.dispatch(unfavourite(status)); - } else { - this.props.dispatch(favourite(status)); - } - } - - handlePin = (status) => { - if (status.get('pinned')) { - this.props.dispatch(unpin(status)); - } else { - this.props.dispatch(pin(status)); - } - } - - handleReplyClick = (status) => { - this.props.dispatch(replyCompose(status, this.context.router.history)); - } - - handleModalReblog = (status) => { - this.props.dispatch(reblog(status)); - } - - handleReblogClick = (status, e) => { - if (status.get('reblogged')) { - this.props.dispatch(unreblog(status)); - } else { - if (e.shiftKey || !boostModal) { - this.handleModalReblog(status); - } else { - this.props.dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog })); - } - } - } - - handleDeleteClick = (status) => { - const { dispatch, intl } = this.props; - - if (!deleteModal) { - dispatch(deleteStatus(status.get('id'))); - } else { - dispatch(openModal('CONFIRM', { - message: intl.formatMessage(messages.deleteMessage), - confirm: intl.formatMessage(messages.deleteConfirm), - onConfirm: () => dispatch(deleteStatus(status.get('id'))), - })); - } - } - - handleMentionClick = (account, router) => { - this.props.dispatch(mentionCompose(account, router)); - } - - handleOpenMedia = (media, index) => { - this.props.dispatch(openModal('MEDIA', { media, index })); - } - - handleOpenVideo = (media, time) => { - this.props.dispatch(openModal('VIDEO', { media, time })); - } - - handleReport = (status) => { - this.props.dispatch(initReport(status.get('account'), status)); - } - - handleEmbed = (status) => { - this.props.dispatch(openModal('EMBED', { url: status.get('url') })); - } - - handleHotkeyMoveUp = () => { - this.handleMoveUp(this.props.status.get('id')); - } - - handleHotkeyMoveDown = () => { - this.handleMoveDown(this.props.status.get('id')); - } - - handleHotkeyReply = e => { - e.preventDefault(); - this.handleReplyClick(this.props.status); - } - - handleHotkeyFavourite = () => { - this.handleFavouriteClick(this.props.status); - } - - handleHotkeyBoost = () => { - this.handleReblogClick(this.props.status); - } - - handleHotkeyMention = e => { - e.preventDefault(); - this.handleMentionClick(this.props.status); - } - - handleHotkeyOpenProfile = () => { - this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); - } - - handleMoveUp = id => { - const { status, ancestorsIds, descendantsIds } = this.props; - - if (id === status.get('id')) { - this._selectChild(ancestorsIds.size - 1); - } else { - let index = ancestorsIds.indexOf(id); - - if (index === -1) { - index = descendantsIds.indexOf(id); - this._selectChild(ancestorsIds.size + index); - } else { - this._selectChild(index - 1); - } - } - } - - handleMoveDown = id => { - const { status, ancestorsIds, descendantsIds } = this.props; - - if (id === status.get('id')) { - this._selectChild(ancestorsIds.size + 1); - } else { - let index = ancestorsIds.indexOf(id); - - if (index === -1) { - index = descendantsIds.indexOf(id); - this._selectChild(ancestorsIds.size + index + 2); - } else { - this._selectChild(index + 1); - } - } - } - - _selectChild (index) { - const element = this.node.querySelectorAll('.focusable')[index]; - - if (element) { - element.focus(); - } - } - - renderChildren (list) { - return list.map(id => ( - <StatusContainer - key={id} - id={id} - onMoveUp={this.handleMoveUp} - onMoveDown={this.handleMoveDown} - /> - )); - } - - setRef = c => { - this.node = c; - } - - componentDidUpdate () { - if (this._scrolledIntoView) { - return; - } - - const { status, ancestorsIds } = this.props; - - if (status && ancestorsIds && ancestorsIds.size > 0) { - const element = this.node.querySelectorAll('.focusable')[ancestorsIds.size - 1]; - - if (element) { - element.scrollIntoView(true); - this._scrolledIntoView = true; - } - } - } - - componentWillUnmount () { - detachFullscreenListener(this.onFullScreenChange); - } - - onFullScreenChange = () => { - this.setState({ fullscreen: isFullscreen() }); - } - - render () { - let ancestors, descendants; - const { status, settings, ancestorsIds, descendantsIds } = this.props; - const { fullscreen } = this.state; - - if (status === null) { - return ( - <Column> - <ColumnBackButton /> - <MissingIndicator /> - </Column> - ); - } - - if (ancestorsIds && ancestorsIds.size > 0) { - ancestors = <div>{this.renderChildren(ancestorsIds)}</div>; - } - - if (descendantsIds && descendantsIds.size > 0) { - descendants = <div>{this.renderChildren(descendantsIds)}</div>; - } - - const handlers = { - moveUp: this.handleHotkeyMoveUp, - moveDown: this.handleHotkeyMoveDown, - reply: this.handleHotkeyReply, - favourite: this.handleHotkeyFavourite, - boost: this.handleHotkeyBoost, - mention: this.handleHotkeyMention, - openProfile: this.handleHotkeyOpenProfile, - }; - - return ( - <Column> - <ColumnBackButton /> - - <ScrollContainer scrollKey='thread'> - <div className={classNames('scrollable', 'detailed-status__wrapper', { fullscreen })} ref={this.setRef}> - {ancestors} - - <HotKeys handlers={handlers}> - <div className='focusable' tabIndex='0'> - <DetailedStatus - status={status} - settings={settings} - onOpenVideo={this.handleOpenVideo} - onOpenMedia={this.handleOpenMedia} - /> - - <ActionBar - status={status} - onReply={this.handleReplyClick} - onFavourite={this.handleFavouriteClick} - onReblog={this.handleReblogClick} - onDelete={this.handleDeleteClick} - onMention={this.handleMentionClick} - onReport={this.handleReport} - onPin={this.handlePin} - onEmbed={this.handleEmbed} - /> - </div> - </HotKeys> - - {descendants} - </div> - </ScrollContainer> - </Column> - ); - } - -} |