+++ /dev/null
-/*
-
-`actions/local_settings`
-========================
-
-> For more information on the contents of this file, please contact:
->
-> - kibigo! [@kibi@glitch.social]
-
-This file provides our Redux actions related to local settings. It
-consists of the following:
-
- - __`changesLocalSetting(key, value)` :__
- Changes the local setting with the given `key` to the given
- `value`. `key` **MUST** be an array of strings, as required by
- `Immutable.Map.prototype.getIn()`.
-
- - __`saveLocalSettings()` :__
- Saves the local settings to `localStorage` as a JSON object. We
- shouldn't ever need to call this ourselves.
-
-*/
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-
-/*
-
-Constants:
-----------
-
-We provide the following constants:
-
- - __`LOCAL_SETTING_CHANGE` :__
- This string constant is used to dispatch a setting change to our
- reducer in `reducers/local_settings`, where the setting is
- actually changed.
-
-*/
-
-export const LOCAL_SETTING_CHANGE = 'LOCAL_SETTING_CHANGE';
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-
-/*
-
-`changeLocalSetting(key, value)`:
----------------------------------
-
-Changes the local setting with the given `key` to the given `value`.
-`key` **MUST** be an array of strings, as required by
-`Immutable.Map.prototype.getIn()`.
-
-To accomplish this, we just dispatch a `LOCAL_SETTING_CHANGE` to our
-reducer in `reducers/local_settings`.
-
-*/
-
-export function changeLocalSetting(key, value) {
- return dispatch => {
- dispatch({
- type: LOCAL_SETTING_CHANGE,
- key,
- value,
- });
-
- dispatch(saveLocalSettings());
- };
-};
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-
-/*
-
-`saveLocalSettings()`:
-----------------------
-
-Saves the local settings to `localStorage` as a JSON object.
-`changeLocalSetting()` calls this whenever it changes a setting. We
-shouldn't ever need to call this ourselves.
-
-> __TODO :__
-> Right now `saveLocalSettings()` doesn't keep track of which user
-> is currently signed in, but it might be better to give each user
-> their *own* local settings.
-
-*/
-
-export function saveLocalSettings() {
- return (_, getState) => {
- const localSettings = getState().get('local_settings').toJS();
- localStorage.setItem('mastodon-settings', JSON.stringify(localSettings));
- };
-};
+++ /dev/null
-/*
-
-`<AccountHeader>`
-=================
-
-> For more information on the contents of this file, please contact:
->
-> - kibigo! [@kibi@glitch.social]
-
-Original file by @gargron@mastodon.social et al as part of
-tootsuite/mastodon. We've expanded it in order to handle user bio
-frontmatter.
-
-The `<AccountHeader>` component provides the header for account
-timelines. It is a fairly simple component which mostly just consists
-of a `render()` method.
-
-__Props:__
-
- - __`account` (`ImmutablePropTypes.map`) :__
- The account to render a header for.
-
- - __`me` (`PropTypes.number.isRequired`) :__
- The id of the currently-signed-in account.
-
- - __`onFollow` (`PropTypes.func.isRequired`) :__
- The function to call when the user clicks the "follow" button.
-
- - __`intl` (`PropTypes.object.isRequired`) :__
- Our internationalization object, inserted by `@injectIntl`.
-
-*/
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-
-/*
-
-Imports:
---------
-
-*/
-
-// Package imports //
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-
-// Mastodon imports //
-import emojify from '../../../mastodon/features/emoji/emoji';
-import IconButton from '../../../mastodon/components/icon_button';
-import Avatar from '../../../mastodon/components/avatar';
-import { me } from '../../../mastodon/initial_state';
-
-// Our imports //
-import { processBio } from '../../util/bio_metadata';
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-
-/*
-
-Inital setup:
--------------
-
-The `messages` constant is used to define any messages that we need
-from inside props. In our case, these are the `unfollow`, `follow`, and
-`requested` messages used in the `title` of our buttons.
-
-*/
-
-const messages = defineMessages({
- unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
- follow: { id: 'account.follow', defaultMessage: 'Follow' },
- requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' },
-});
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-
-/*
-
-Implementation:
----------------
-
-*/
-
-@injectIntl
-export default class AccountHeader extends ImmutablePureComponent {
-
- static propTypes = {
- account : ImmutablePropTypes.map,
- onFollow : PropTypes.func.isRequired,
- intl : PropTypes.object.isRequired,
- };
-
-/*
-
-### `render()`
-
-The `render()` function is used to render our component.
-
-*/
-
- render () {
- const { account, intl } = this.props;
-
-/*
-
-If no `account` is provided, then we can't render a header. Otherwise,
-we get the `displayName` for the account, if available. If it's blank,
-then we set the `displayName` to just be the `username` of the account.
-
-*/
-
- if (!account) {
- return null;
- }
-
- let displayName = account.get('display_name_html');
- let info = '';
- let actionBtn = '';
- let following = false;
-
-/*
-
-Next, we handle the account relationships. If the account follows the
-user, then we add an `info` message. If the user has requested a
-follow, then we disable the `actionBtn` and display an hourglass.
-Otherwise, if the account isn't blocked, we set the `actionBtn` to the
-appropriate icon.
-
-*/
-
- if (me !== account.get('id')) {
- if (account.getIn(['relationship', 'followed_by'])) {
- info = (
- <span className='account--follows-info'>
- <FormattedMessage id='account.follows_you' defaultMessage='Follows you' />
- </span>
- );
- }
- if (account.getIn(['relationship', 'requested'])) {
- actionBtn = (
- <div className='account--action-button'>
- <IconButton size={26} disabled icon='hourglass' title={intl.formatMessage(messages.requested)} />
- </div>
- );
- } else if (!account.getIn(['relationship', 'blocking'])) {
- following = account.getIn(['relationship', 'following']);
- actionBtn = (
- <div className='account--action-button'>
- <IconButton
- size={26}
- icon={following ? 'user-times' : 'user-plus'}
- active={following ? true : false}
- title={intl.formatMessage(following ? messages.unfollow : messages.follow)}
- onClick={this.props.onFollow}
- />
- </div>
- );
- }
- }
-
-/*
- we extract the `text` and
-`metadata` from our account's `note` using `processBio()`.
-
-*/
-
- const { text, metadata } = processBio(account.get('note'));
-
-/*
-
-Here, we render our component using all the things we've defined above.
-
-*/
-
- return (
- <div className='account__header__wrapper'>
- <div
- className='account__header'
- style={{ backgroundImage: `url(${account.get('header')})` }}
- >
- <div>
- <a href={account.get('url')} target='_blank' rel='noopener'>
- <span className='account__header__avatar'>
- <Avatar account={account} size={90} />
- </span>
- <span
- className='account__header__display-name'
- dangerouslySetInnerHTML={{ __html: displayName }}
- />
- </a>
- <span className='account__header__username'>
- @{account.get('acct')}
- {account.get('locked') ? <i className='fa fa-lock' /> : null}
- </span>
- <div className='account__header__content' dangerouslySetInnerHTML={{ __html: emojify(text) }} />
-
- {info}
- {actionBtn}
- </div>
- </div>
-
- {metadata.length && (
- <table className='account__metadata'>
- <tbody>
- {(() => {
- let data = [];
- for (let i = 0; i < metadata.length; i++) {
- data.push(
- <tr key={i}>
- <th scope='row'><div dangerouslySetInnerHTML={{ __html: emojify(metadata[i][0]) }} /></th>
- <td><div dangerouslySetInnerHTML={{ __html: emojify(metadata[i][1]) }} /></td>
- </tr>
- );
- }
- return data;
- })()}
- </tbody>
- </table>
- ) || null}
- </div>
- );
- }
-
-}
+++ /dev/null
-/*
-
-`<ComposeAdvancedOptionsContainer>`
-===================================
-
-This container connects `<ComposeAdvancedOptions>` to the Redux store.
-
-*/
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-
-/*
-
-Imports:
---------
-
-*/
-
-// Package imports //
-import { connect } from 'react-redux';
-
-// Mastodon imports //
-import { toggleComposeAdvancedOption } from '../../../../mastodon/actions/compose';
-
-// Our imports //
-import ComposeAdvancedOptions from '.';
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-
-/*
-
-State mapping:
---------------
-
-The `mapStateToProps()` function maps various state properties to the
-props of our component. The only property we care about is
-`compose.advanced_options`.
-
-*/
-
-const mapStateToProps = state => ({
- values: state.getIn(['compose', 'advanced_options']),
-});
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-
-/*
-
-Dispatch mapping:
------------------
-
-The `mapDispatchToProps()` function maps dispatches to our store to the
-various props of our component. We just need to provide a dispatch for
-when an advanced option toggle changes.
-
-*/
-
-const mapDispatchToProps = dispatch => ({
-
- onChange (option) {
- dispatch(toggleComposeAdvancedOption(option));
- },
-
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(ComposeAdvancedOptions);
+++ /dev/null
-/*
-
-`<ComposeAdvancedOptions>`
-==========================
-
-> For more information on the contents of this file, please contact:
->
-> - surinna [@srn@dev.glitch.social]
-
-This adds an advanced options dropdown to the toot compose box, for
-toggles that don't necessarily fit elsewhere.
-
-__Props:__
-
- - __`values` (`ImmutablePropTypes.contains(…).isRequired`) :__
- An Immutable map with the following values:
-
- - __`do_not_federate` (`PropTypes.bool.isRequired`) :__
- Specifies whether or not to federate the status.
-
- - __`onChange` (`PropTypes.func.isRequired`) :__
- The function to call when a toggle is changed. We pass this from
- our container to the toggle.
-
- - __`intl` (`PropTypes.object.isRequired`) :__
- Our internationalization object, inserted by `@injectIntl`.
-
-__State:__
-
- - __`open` :__
- This tells whether the dropdown is currently open or closed.
-
-*/
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-
-/*
-
-Imports:
---------
-
-*/
-
-// Package imports //
-import React from 'react';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { injectIntl, defineMessages } from 'react-intl';
-
-// Our imports //
-import ComposeAdvancedOptionsToggle from './toggle';
-import ComposeDropdown from '../dropdown/index';
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-
-/*
-
-Inital setup:
--------------
-
-The `messages` constant is used to define any messages that we need
-from inside props. These are the various titles and labels on our
-toggles.
-
-`iconStyle` styles the icon used for the dropdown button.
-
-*/
-
-const messages = defineMessages({
- local_only_short :
- { id: 'advanced-options.local-only.short', defaultMessage: 'Local-only' },
- local_only_long :
- { id: 'advanced-options.local-only.long', defaultMessage: 'Do not post to other instances' },
- advanced_options_icon_title :
- { id: 'advanced_options.icon_title', defaultMessage: 'Advanced options' },
-});
-
-/*
-
-Implementation:
----------------
-
-*/
-
-@injectIntl
-export default class ComposeAdvancedOptions extends React.PureComponent {
-
- static propTypes = {
- values : ImmutablePropTypes.contains({
- do_not_federate : PropTypes.bool.isRequired,
- }).isRequired,
- onChange : PropTypes.func.isRequired,
- intl : PropTypes.object.isRequired,
- };
-
-
-/*
-
-### `render()`
-
-`render()` actually puts our component on the screen.
-
-*/
-
- render () {
- const { intl, values } = this.props;
-
-/*
-
-The `options` array provides all of the available advanced options
-alongside their icon, text, and name.
-
-*/
- const options = [
- { icon: 'wifi', shortText: messages.local_only_short, longText: messages.local_only_long, name: 'do_not_federate' },
- ];
-
-/*
-
-`anyEnabled` tells us if any of our advanced options have been enabled.
-
-*/
-
- const anyEnabled = values.some((enabled) => enabled);
-
-/*
-
-`optionElems` takes our `options` and creates
-`<ComposeAdvancedOptionsToggle>`s out of them. We use the `name` of the
-toggle as its `key` so that React can keep track of it.
-
-*/
-
- const optionElems = options.map((option) => {
- return (
- <ComposeAdvancedOptionsToggle
- onChange={this.props.onChange}
- active={values.get(option.name)}
- key={option.name}
- name={option.name}
- shortText={intl.formatMessage(option.shortText)}
- longText={intl.formatMessage(option.longText)}
- />
- );
- });
-
-/*
-
-Finally, we can render our component.
-
-*/
- return (
- <ComposeDropdown
- title={intl.formatMessage(messages.advanced_options_icon_title)}
- icon='home'
- highlight={anyEnabled}
- >
- {optionElems}
- </ComposeDropdown>
- );
- }
-
-}
+++ /dev/null
-/*
-
-`<ComposeAdvancedOptionsToggle>`
-================================
-
-> For more information on the contents of this file, please contact:
->
-> - surinna [@srn@dev.glitch.social]
-
-This creates the toggle used by `<ComposeAdvancedOptions>`.
-
-__Props:__
-
- - __`onChange` (`PropTypes.func`) :__
- This provides the function to call when the toggle is
- (de-?)activated.
-
- - __`active` (`PropTypes.bool`) :__
- This prop controls whether the toggle is currently active or not.
-
- - __`name` (`PropTypes.string`) :__
- This identifies the toggle, and is sent to `onChange()` when it is
- called.
-
- - __`shortText` (`PropTypes.string`) :__
- This is a short string used as the title of the toggle.
-
- - __`longText` (`PropTypes.string`) :__
- This is a longer string used as a subtitle for the toggle.
-
-*/
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-
-/*
-
-Imports:
---------
-
-*/
-
-// Package imports //
-import React from 'react';
-import PropTypes from 'prop-types';
-import Toggle from 'react-toggle';
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-
-/*
-
-Implementation:
----------------
-
-*/
-
-export default class ComposeAdvancedOptionsToggle extends React.PureComponent {
-
- static propTypes = {
- onChange: PropTypes.func.isRequired,
- active: PropTypes.bool.isRequired,
- name: PropTypes.string.isRequired,
- shortText: PropTypes.string.isRequired,
- longText: PropTypes.string.isRequired,
- }
-
-/*
-
-### `onToggle()`
-
-The `onToggle()` function simply calls the `onChange()` prop with the
-toggle's `name`.
-
-*/
-
- onToggle = () => {
- this.props.onChange(this.props.name);
- }
-
-/*
-
-### `render()`
-
-The `render()` function is used to render our component. We just render
-a `<Toggle>` and place next to it our text.
-
-*/
-
- render() {
- const { active, shortText, longText } = this.props;
- return (
- <div role='button' tabIndex='0' className='advanced-options-dropdown__option' onClick={this.onToggle}>
- <div className='advanced-options-dropdown__option__toggle'>
- <Toggle checked={active} onChange={this.onToggle} />
- </div>
- <div className='advanced-options-dropdown__option__content'>
- <strong>{shortText}</strong>
- {longText}
- </div>
- </div>
- );
- }
-
-}
+++ /dev/null
-// Package imports //
-import { connect } from 'react-redux';
-
-// Mastodon imports //
-import { closeModal } from '../../../mastodon/actions/modal';
-
-// Our imports //
-import { changeLocalSetting } from '../../../glitch/actions/local_settings';
-import LocalSettings from '.';
-
-const mapStateToProps = state => ({
- settings: state.get('local_settings'),
-});
-
-const mapDispatchToProps = dispatch => ({
- onChange (setting, value) {
- dispatch(changeLocalSetting(setting, value));
- },
- onClose () {
- dispatch(closeModal());
- },
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(LocalSettings);
+++ /dev/null
-/*
-
-`<NotificationContainer>`
-=========================
-
-This container connects `<Notification>`s to the Redux store.
-
-*/
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-
-/*
-
-Imports:
---------
-
-*/
-
-// Package imports //
-import { connect } from 'react-redux';
-
-// Our imports //
-import Notification from '.';
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-
-const mapStateToProps = (state, props) => {
- // replace account id with object
- let leNotif = props.notification.set('account', state.getIn(['accounts', props.notification.get('account')]));
-
- // populate markedForDelete from state - is mysteriously lost somewhere
- for (let n of state.getIn(['notifications', 'items'])) {
- if (n.get('id') === props.notification.get('id')) {
- leNotif = leNotif.set('markedForDelete', n.get('markedForDelete'));
- break;
- }
- }
-
- return ({
- notification: leNotif,
- settings: state.get('local_settings'),
- notifCleaning: state.getIn(['notifications', 'cleaningMode']),
- });
-};
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-
-export default connect(mapStateToProps)(Notification);
+++ /dev/null
-// `<NotificationFollow>`
-// ======================
-
-// * * * * * * * //
-
-// Imports
-// -------
-
-// Package imports.
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import { FormattedMessage } from 'react-intl';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-
-// Mastodon imports.
-import Permalink from '../../../mastodon/components/permalink';
-import AccountContainer from '../../../mastodon/containers/account_container';
-
-// Our imports.
-import NotificationOverlayContainer from '../notification/overlay/container';
-
-// * * * * * * * //
-
-// Implementation
-// --------------
-
-export default class NotificationFollow extends ImmutablePureComponent {
-
- static propTypes = {
- id : PropTypes.string.isRequired,
- account : ImmutablePropTypes.map.isRequired,
- notification : ImmutablePropTypes.map.isRequired,
- };
-
- render () {
- const { account, notification } = this.props;
-
- // Links to the display name.
- const displayName = account.get('display_name_html') || account.get('username');
- const link = (
- <Permalink
- className='notification__display-name'
- href={account.get('url')}
- title={account.get('acct')}
- to={`/accounts/${account.get('id')}`}
- dangerouslySetInnerHTML={{ __html: displayName }}
- />
- );
-
- // Renders.
- return (
- <div className='notification notification-follow'>
- <div className='notification__message'>
- <div className='notification__favourite-icon-wrapper'>
- <i className='fa fa-fw fa-user-plus' />
- </div>
-
- <FormattedMessage
- id='notification.follow'
- defaultMessage='{name} followed you'
- values={{ name: link }}
- />
- </div>
-
- <AccountContainer id={account.get('id')} withNote={false} />
- <NotificationOverlayContainer notification={notification} />
- </div>
- );
- }
-
-}
+++ /dev/null
-/*
-
-`<NotificationOverlayContainer>`
-=========================
-
-This container connects `<NotificationOverlay>`s to the Redux store.
-
-*/
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-
-/*
-
-Imports:
---------
-
-*/
-
-// Package imports //
-import { connect } from 'react-redux';
-
-// Our imports //
-import NotificationOverlay from './notification_overlay';
-import { markNotificationForDelete } from '../../../../mastodon/actions/notifications';
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-
-/*
-
-Dispatch mapping:
------------------
-
-The `mapDispatchToProps()` function maps dispatches to our store to the
-various props of our component. We only need to provide a dispatch for
-deleting notifications.
-
-*/
-
-const mapDispatchToProps = dispatch => ({
- onMarkForDelete(id, yes) {
- dispatch(markNotificationForDelete(id, yes));
- },
-});
-
-const mapStateToProps = state => ({
- show: state.getIn(['notifications', 'cleaningMode']),
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(NotificationOverlay);
+++ /dev/null
-// Package imports //
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import { defineMessages, injectIntl } from 'react-intl';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-
-// Mastodon imports //
-import RelativeTimestamp from '../../../mastodon/components/relative_timestamp';
-import IconButton from '../../../mastodon/components/icon_button';
-import DropdownMenuContainer from '../../../mastodon/containers/dropdown_menu_container';
-import { me } from '../../../mastodon/initial_state';
-
-const messages = defineMessages({
- delete: { id: 'status.delete', defaultMessage: 'Delete' },
- mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' },
- mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
- block: { id: 'account.block', defaultMessage: 'Block @{name}' },
- reply: { id: 'status.reply', defaultMessage: 'Reply' },
- share: { id: 'status.share', defaultMessage: 'Share' },
- replyAll: { id: 'status.replyAll', defaultMessage: 'Reply to thread' },
- reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
- cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
- favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
- open: { id: 'status.open', defaultMessage: 'Expand this status' },
- report: { id: 'status.report', defaultMessage: 'Report @{name}' },
- muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' },
- unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' },
- 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 StatusActionBar extends ImmutablePureComponent {
-
- static contextTypes = {
- router: PropTypes.object,
- };
-
- static propTypes = {
- status: ImmutablePropTypes.map.isRequired,
- onReply: PropTypes.func,
- onFavourite: PropTypes.func,
- onReblog: PropTypes.func,
- onDelete: PropTypes.func,
- onMention: PropTypes.func,
- onMute: PropTypes.func,
- onBlock: PropTypes.func,
- onReport: PropTypes.func,
- onEmbed: PropTypes.func,
- onMuteConversation: PropTypes.func,
- onPin: PropTypes.func,
- withDismiss: PropTypes.bool,
- intl: PropTypes.object.isRequired,
- };
-
- // Avoid checking props that are functions (and whose equality will always
- // evaluate to false. See react-immutable-pure-component for usage.
- updateOnProps = [
- 'status',
- 'withDismiss',
- ]
-
- handleReplyClick = () => {
- this.props.onReply(this.props.status, this.context.router.history);
- }
-
- handleShareClick = () => {
- navigator.share({
- text: this.props.status.get('search_index'),
- url: this.props.status.get('url'),
- });
- }
-
- handleFavouriteClick = () => {
- this.props.onFavourite(this.props.status);
- }
-
- handleReblogClick = (e) => {
- this.props.onReblog(this.props.status, e);
- }
-
- handleDeleteClick = () => {
- this.props.onDelete(this.props.status);
- }
-
- handlePinClick = () => {
- this.props.onPin(this.props.status);
- }
-
- handleMentionClick = () => {
- this.props.onMention(this.props.status.get('account'), this.context.router.history);
- }
-
- handleMuteClick = () => {
- this.props.onMute(this.props.status.get('account'));
- }
-
- handleBlockClick = () => {
- this.props.onBlock(this.props.status.get('account'));
- }
-
- handleOpen = () => {
- this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
- }
-
- handleEmbed = () => {
- this.props.onEmbed(this.props.status);
- }
-
- handleReport = () => {
- this.props.onReport(this.props.status);
- }
-
- handleConversationMuteClick = () => {
- this.props.onMuteConversation(this.props.status);
- }
-
- render () {
- const { status, intl, withDismiss } = this.props;
-
- const mutingConversation = status.get('muted');
- const anonymousAccess = !me;
- const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
-
- let menu = [];
- let reblogIcon = 'retweet';
- let replyIcon;
- let replyTitle;
-
- menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen });
-
- if (publicStatus) {
- menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
- }
-
- menu.push(null);
-
- if (status.getIn(['account', 'id']) === me || withDismiss) {
- menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
- menu.push(null);
- }
-
- if (status.getIn(['account', 'id']) === me) {
- 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.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick });
- menu.push({ text: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick });
- menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
- }
-
- if (status.get('in_reply_to_id', null) === null) {
- replyIcon = 'reply';
- replyTitle = intl.formatMessage(messages.reply);
- } else {
- replyIcon = 'reply-all';
- replyTitle = intl.formatMessage(messages.replyAll);
- }
-
- const shareButton = ('share' in navigator) && status.get('visibility') === 'public' && (
- <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.share)} icon='share-alt' onClick={this.handleShareClick} />
- );
-
- return (
- <div className='status__action-bar'>
- <IconButton className='status__action-bar-button' disabled={anonymousAccess} title={replyTitle} icon={replyIcon} onClick={this.handleReplyClick} />
- <IconButton className='status__action-bar-button' disabled={anonymousAccess || !publicStatus} active={status.get('reblogged')} pressed={status.get('reblogged')} title={!publicStatus ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} />
- <IconButton className='status__action-bar-button star-icon' disabled={anonymousAccess} animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />
- {shareButton}
-
- <div className='status__action-bar-dropdown'>
- <DropdownMenuContainer disabled={anonymousAccess} status={status} items={menu} icon='ellipsis-h' size={18} direction='right' ariaLabel='More' />
- </div>
-
- <a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
- </div>
- );
- }
-
-}
+++ /dev/null
-/*
-
-`<StatusContainer>`
-===================
-
-Original file by @gargron@mastodon.social et al as part of
-tootsuite/mastodon. Documentation by @kibi@glitch.social. The code
-detecting reblogs has been moved here from <Status>.
-
-*/
-
- /* * * * */
-
-/*
-
-Imports:
---------
-
-*/
-
-// Package imports //
-import React from 'react';
-import { connect } from 'react-redux';
-import {
- defineMessages,
- injectIntl,
- FormattedMessage,
-} from 'react-intl';
-
-// Mastodon imports //
-import { makeGetStatus } from '../../../mastodon/selectors';
-import {
- replyCompose,
- mentionCompose,
-} from '../../../mastodon/actions/compose';
-import {
- reblog,
- favourite,
- unreblog,
- unfavourite,
- pin,
- unpin,
-} from '../../../mastodon/actions/interactions';
-import { blockAccount } from '../../../mastodon/actions/accounts';
-import { initMuteModal } from '../../../mastodon/actions/mutes';
-import {
- muteStatus,
- unmuteStatus,
- deleteStatus,
-} from '../../../mastodon/actions/statuses';
-import { initReport } from '../../../mastodon/actions/reports';
-import { openModal } from '../../../mastodon/actions/modal';
-
-// Our imports //
-import Status from '.';
-
- /* * * * */
-
-/*
-
-Inital setup:
--------------
-
-The `messages` constant is used to define any messages that we will
-need in our component. In our case, these are the various confirmation
-messages used with statuses.
-
-*/
-
-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?',
- },
- blockConfirm : {
- id : 'confirmations.block.confirm',
- defaultMessage : 'Block',
- },
-});
-
- /* * * * */
-
-/*
-
-State mapping:
---------------
-
-The `mapStateToProps()` function maps various state properties to the
-props of our component. We wrap this in a `makeMapStateToProps()`
-function to give us closure and preserve `getStatus()` across function
-calls.
-
-*/
-
-const makeMapStateToProps = () => {
- const getStatus = makeGetStatus();
-
- const mapStateToProps = (state, ownProps) => {
-
- let status = getStatus(state, ownProps.id);
-
- if(status === null) {
- console.error(`ERROR! NULL STATUS! ${ownProps.id}`);
- // work-around: find first good status
- for (let k of state.get('statuses').keys()) {
- status = getStatus(state, k);
- if (status !== null) break;
- }
- }
-
- let reblogStatus = status.get('reblog', null);
- let account = undefined;
- let prepend = undefined;
-
-/*
-
-Here we process reblogs. If our status is a reblog, then we create a
-`prependMessage` to pass along to our `<Status>` along with the
-reblogger's `account`, and set `coreStatus` (the one we will actually
-render) to the status which has been reblogged.
-
-*/
-
- if (reblogStatus !== null && typeof reblogStatus === 'object') {
- account = status.get('account');
- status = reblogStatus;
- prepend = 'reblogged_by';
- }
-
-/*
-
-Here are the props we pass to `<Status>`.
-
-*/
-
- return {
- status : status,
- account : account || ownProps.account,
- settings : state.get('local_settings'),
- prepend : prepend || ownProps.prepend,
- reblogModal : state.getIn(['meta', 'boost_modal']),
- deleteModal : state.getIn(['meta', 'delete_modal']),
- };
- };
-
- return mapStateToProps;
-};
-
- /* * * * */
-
-/*
-
-Dispatch mapping:
------------------
-
-The `mapDispatchToProps()` function maps dispatches to our store to the
-various props of our component. We need to provide dispatches for all
-of the things you can do with a status: reply, reblog, favourite, et
-cetera.
-
-For a few of these dispatches, we open up confirmation modals; the rest
-just immediately execute their corresponding actions.
-
-*/
-
-const mapDispatchToProps = (dispatch, { intl }) => ({
-
- onReply (status, router) {
- dispatch(replyCompose(status, router));
- },
-
- onModalReblog (status) {
- dispatch(reblog(status));
- },
-
- onReblog (status, e) {
- if (status.get('reblogged')) {
- dispatch(unreblog(status));
- } else {
- if (e.shiftKey || !this.reblogModal) {
- this.onModalReblog(status);
- } else {
- dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));
- }
- }
- },
-
- onFavourite (status) {
- if (status.get('favourited')) {
- dispatch(unfavourite(status));
- } else {
- dispatch(favourite(status));
- }
- },
-
- onPin (status) {
- if (status.get('pinned')) {
- dispatch(unpin(status));
- } else {
- dispatch(pin(status));
- }
- },
-
- onEmbed (status) {
- dispatch(openModal('EMBED', { url: status.get('url') }));
- },
-
- onDelete (status) {
- if (!this.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'))),
- }));
- }
- },
-
- onMention (account, router) {
- dispatch(mentionCompose(account, router));
- },
-
- onOpenMedia (media, index) {
- dispatch(openModal('MEDIA', { media, index }));
- },
-
- onOpenVideo (media, time) {
- dispatch(openModal('VIDEO', { media, time }));
- },
-
- onBlock (account) {
- dispatch(openModal('CONFIRM', {
- message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
- confirm: intl.formatMessage(messages.blockConfirm),
- onConfirm: () => dispatch(blockAccount(account.get('id'))),
- }));
- },
-
- onReport (status) {
- dispatch(initReport(status.get('account'), status));
- },
-
- onMute (account) {
- dispatch(initMuteModal(account));
- },
-
- onMuteConversation (status) {
- if (status.get('muted')) {
- dispatch(unmuteStatus(status.get('id')));
- } else {
- dispatch(muteStatus(status.get('id')));
- }
- },
-});
-
-export default injectIntl(
- connect(makeMapStateToProps, mapDispatchToProps)(Status)
-);
+++ /dev/null
-// Package imports //
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-
-// Mastodon imports //
-import IconButton from '../../../../mastodon/components/icon_button';
-
-// Our imports //
-import StatusGalleryItem from './item';
-
-const messages = defineMessages({
- toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' },
-});
-
-@injectIntl
-export default class StatusGallery extends React.PureComponent {
-
- static propTypes = {
- sensitive: PropTypes.bool,
- media: ImmutablePropTypes.list.isRequired,
- letterbox: PropTypes.bool,
- fullwidth: PropTypes.bool,
- height: PropTypes.number.isRequired,
- onOpenMedia: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired,
- autoPlayGif: PropTypes.bool.isRequired,
- };
-
- state = {
- visible: !this.props.sensitive,
- };
-
- handleOpen = () => {
- this.setState({ visible: !this.state.visible });
- }
-
- handleClick = (index) => {
- this.props.onOpenMedia(this.props.media, index);
- }
-
- render () {
- const { media, intl, sensitive, letterbox, fullwidth } = this.props;
-
- let children;
-
- if (!this.state.visible) {
- let warning;
-
- if (sensitive) {
- warning = <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />;
- } else {
- warning = <FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' />;
- }
-
- children = (
- <div role='button' tabIndex='0' className='media-spoiler' onClick={this.handleOpen}>
- <span className='media-spoiler__warning'>{warning}</span>
- <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
- </div>
- );
- } else {
- const size = media.take(4).size;
- children = media.take(4).map((attachment, i) => <StatusGalleryItem key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} autoPlayGif={this.props.autoPlayGif} index={i} size={size} letterbox={letterbox} />);
- }
-
- return (
- <div className={`media-gallery ${fullwidth ? 'full-width' : ''}`} style={{ height: `${this.props.height}px` }}>
- <div className={`spoiler-button ${this.state.visible ? 'spoiler-button--visible' : ''}`}>
- <IconButton title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} overlay onClick={this.handleOpen} />
- </div>
-
- {children}
- </div>
- );
- }
-
-}
+++ /dev/null
-// Package imports //
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-
-// Mastodon imports //
-import { isIOS } from '../../../../mastodon/is_mobile';
-
-export default class StatusGalleryItem extends React.PureComponent {
-
- static propTypes = {
- attachment: ImmutablePropTypes.map.isRequired,
- index: PropTypes.number.isRequired,
- size: PropTypes.number.isRequired,
- letterbox: PropTypes.bool,
- onClick: PropTypes.func.isRequired,
- autoPlayGif: PropTypes.bool.isRequired,
- };
-
- handleMouseEnter = (e) => {
- if (this.hoverToPlay()) {
- e.target.play();
- }
- }
-
- handleMouseLeave = (e) => {
- if (this.hoverToPlay()) {
- e.target.pause();
- e.target.currentTime = 0;
- }
- }
-
- hoverToPlay () {
- const { attachment, autoPlayGif } = this.props;
- return !autoPlayGif && attachment.get('type') === 'gifv';
- }
-
- handleClick = (e) => {
- const { index, onClick } = this.props;
-
- if (e.button === 0) {
- e.preventDefault();
- onClick(index);
- }
-
- e.stopPropagation();
- }
-
- render () {
- const { attachment, index, size, letterbox } = this.props;
-
- let width = 50;
- let height = 100;
- let top = 'auto';
- let left = 'auto';
- let bottom = 'auto';
- let right = 'auto';
-
- if (size === 1) {
- width = 100;
- }
-
- if (size === 4 || (size === 3 && index > 0)) {
- height = 50;
- }
-
- if (size === 2) {
- if (index === 0) {
- right = '2px';
- } else {
- left = '2px';
- }
- } else if (size === 3) {
- if (index === 0) {
- right = '2px';
- } else if (index > 0) {
- left = '2px';
- }
-
- if (index === 1) {
- bottom = '2px';
- } else if (index > 1) {
- top = '2px';
- }
- } else if (size === 4) {
- if (index === 0 || index === 2) {
- right = '2px';
- }
-
- if (index === 1 || index === 3) {
- left = '2px';
- }
-
- if (index < 2) {
- bottom = '2px';
- } else {
- top = '2px';
- }
- }
-
- let thumbnail = '';
-
- if (attachment.get('type') === 'image') {
- const previewUrl = attachment.get('preview_url');
- const previewWidth = attachment.getIn(['meta', 'small', 'width']);
-
- const originalUrl = attachment.get('url');
- const originalWidth = attachment.getIn(['meta', 'original', 'width']);
-
- const srcSet = `${originalUrl} ${originalWidth}w, ${previewUrl} ${previewWidth}w`;
- const sizes = `(min-width: 1025px) ${320 * (width / 100)}px, ${width}vw`;
-
- thumbnail = (
- <a
- className='media-gallery__item-thumbnail'
- href={attachment.get('remote_url') || originalUrl}
- onClick={this.handleClick}
- target='_blank'
- >
- <img
- className={letterbox ? 'letterbox' : ''}
- src={previewUrl} srcSet={srcSet}
- sizes={sizes}
- alt={attachment.get('description')}
- title={attachment.get('description')}
- />
- </a>
- );
- } else if (attachment.get('type') === 'gifv') {
- const autoPlay = !isIOS() && this.props.autoPlayGif;
-
- thumbnail = (
- <div className={`media-gallery__gifv ${autoPlay ? 'autoplay' : ''}`}>
- <video
- className={`media-gallery__item-gifv-thumbnail${letterbox ? ' letterbox' : ''}`}
- role='application'
- src={attachment.get('url')}
- onClick={this.handleClick}
- onMouseEnter={this.handleMouseEnter}
- onMouseLeave={this.handleMouseLeave}
- autoPlay={autoPlay}
- loop
- muted
- />
-
- <span className='media-gallery__gifv__label'>GIF</span>
- </div>
- );
- }
-
- return (
- <div className='media-gallery__item' key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}>
- {thumbnail}
- </div>
- );
- }
-
-}
+++ /dev/null
-/*
-
-`<Status>`
-==========
-
-Original file by @gargron@mastodon.social et al as part of
-tootsuite/mastodon. *Heavily* rewritten (and documented!) by
-@kibi@glitch.social as a part of glitch-soc/mastodon. The following
-features have been added:
-
- - Better separating the "guts" of statuses from their wrapper(s)
- - Collapsing statuses
- - Moving images inside of CWs
-
-A number of aspects of this original file have been split off into
-their own components for better maintainance; for these, see:
-
- - <StatusHeader>
- - <StatusPrepend>
-
-…And, of course, the other <Status>-related components as well.
-
-*/
-
- /* * * * */
-
-/*
-
-Imports:
---------
-
-*/
-
-// Package imports //
-import React from 'react';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-
-// Mastodon imports //
-import scheduleIdleTask from '../../../mastodon/features/ui/util/schedule_idle_task';
-import { autoPlayGif } from '../../../mastodon/initial_state';
-
-// Our imports //
-import StatusPrepend from './prepend';
-import StatusHeader from './header';
-import StatusContent from './content';
-import StatusActionBar from './action_bar';
-import StatusGallery from './gallery';
-import StatusPlayer from './player';
-import NotificationOverlayContainer from '../notification/overlay/container';
-
- /* * * * */
-
-/*
-
-The `<Status>` component:
--------------------------
-
-The `<Status>` component is a container for statuses. It consists of a
-few parts:
-
- - The `<StatusPrepend>`, which contains tangential information about
- the status, such as who reblogged it.
- - The `<StatusHeader>`, which contains the avatar and username of the
- status author, as well as a media icon and the "collapse" toggle.
- - The `<StatusContent>`, which contains the content of the status.
- - The `<StatusActionBar>`, which provides actions to be performed
- on statuses, like reblogging or sending a reply.
-
-### Context
-
- - __`router` (`PropTypes.object`) :__
- We need to get our router from the surrounding React context.
-
-### Props
-
- - __`id` (`PropTypes.number`) :__
- The id of the status.
-
- - __`status` (`ImmutablePropTypes.map`) :__
- The status object, straight from the store.
-
- - __`account` (`ImmutablePropTypes.map`) :__
- Don't be confused by this one! This is **not** the account which
- posted the status, but the associated account with any further
- action (eg, a reblog or a favourite).
-
- - __`settings` (`ImmutablePropTypes.map`) :__
- These are our local settings, fetched from our store. We need this
- to determine how best to collapse our statuses, among other things.
-
- - __`onFavourite`, `onReblog`, `onModalReblog`, `onDelete`,
- `onMention`, `onMute`, `onMuteConversation`, onBlock`, `onReport`,
- `onOpenMedia`, `onOpenVideo` (`PropTypes.func`) :__
- These are all functions passed through from the
- `<StatusContainer>`. We don't deal with them directly here.
-
- - __`reblogModal`, `deleteModal` (`PropTypes.bool`) :__
- These tell whether or not the user has modals activated for
- reblogging and deleting statuses. They are used by the `onReblog`
- and `onDelete` functions, but we don't deal with them here.
-
- - __`muted` (`PropTypes.bool`) :__
- This has nothing to do with a user or conversation mute! "Muted" is
- what Mastodon internally calls the subdued look of statuses in the
- notifications column. This should be `true` for notifications, and
- `false` otherwise.
-
- - __`collapse` (`PropTypes.bool`) :__
- This prop signals a directive from a higher power to (un)collapse
- a status. Most of the time it should be `undefined`, in which case
- we do nothing.
-
- - __`prepend` (`PropTypes.string`) :__
- The type of prepend: `'reblogged_by'`, `'reblog'`, or
- `'favourite'`.
-
- - __`withDismiss` (`PropTypes.bool`) :__
- Whether or not the status can be dismissed. Used for notifications.
-
- - __`intersectionObserverWrapper` (`PropTypes.object`) :__
- This holds our intersection observer. In Mastodon parlance,
- an "intersection" is just when the status is viewable onscreen.
-
-### State
-
- - __`isExpanded` :__
- Should be either `true`, `false`, or `null`. The meanings of
- these values are as follows:
-
- - __`true` :__ The status contains a CW and the CW is expanded.
- - __`false` :__ The status is collapsed.
- - __`null` :__ The status is not collapsed or expanded.
-
- - __`isIntersecting` :__
- This boolean tells us whether or not the status is currently
- onscreen.
-
- - __`isHidden` :__
- This boolean tells us if the status has been unrendered to save
- CPUs.
-
-*/
-
-export default class Status extends ImmutablePureComponent {
-
- static contextTypes = {
- router : PropTypes.object,
- };
-
- static propTypes = {
- id : PropTypes.string,
- status : ImmutablePropTypes.map,
- account : ImmutablePropTypes.map,
- settings : ImmutablePropTypes.map,
- notification : ImmutablePropTypes.map,
- onFavourite : PropTypes.func,
- onReblog : PropTypes.func,
- onModalReblog : PropTypes.func,
- onDelete : PropTypes.func,
- onPin : PropTypes.func,
- onMention : PropTypes.func,
- onMute : PropTypes.func,
- onMuteConversation : PropTypes.func,
- onBlock : PropTypes.func,
- onEmbed : PropTypes.func,
- onHeightChange : PropTypes.func,
- onReport : PropTypes.func,
- onOpenMedia : PropTypes.func,
- onOpenVideo : PropTypes.func,
- reblogModal : PropTypes.bool,
- deleteModal : PropTypes.bool,
- muted : PropTypes.bool,
- collapse : PropTypes.bool,
- prepend : PropTypes.string,
- withDismiss : PropTypes.bool,
- intersectionObserverWrapper : PropTypes.object,
- };
-
- state = {
- isExpanded : null,
- isIntersecting : true,
- isHidden : false,
- markedForDelete : false,
- }
-
-/*
-
-### Implementation
-
-#### `updateOnProps` and `updateOnStates`.
-
-`updateOnProps` and `updateOnStates` tell the component when to update.
-We specify them explicitly because some of our props are dynamically=
-generated functions, which would otherwise always trigger an update.
-Of course, this means that if we add an important prop, we will need
-to remember to specify it here.
-
-*/
-
- updateOnProps = [
- 'status',
- 'account',
- 'settings',
- 'prepend',
- 'boostModal',
- 'muted',
- 'collapse',
- 'notification',
- ]
-
- updateOnStates = [
- 'isExpanded',
- 'markedForDelete',
- ]
-
-/*
-
-#### `componentWillReceiveProps()`.
-
-If our settings have changed to disable collapsed statuses, then we
-need to make sure that we uncollapse every one. We do that by watching
-for changes to `settings.collapsed.enabled` in
-`componentWillReceiveProps()`.
-
-We also need to watch for changes on the `collapse` prop---if this
-changes to anything other than `undefined`, then we need to collapse or
-uncollapse our status accordingly.
-
-*/
-
- componentWillReceiveProps (nextProps) {
- if (!nextProps.settings.getIn(['collapsed', 'enabled'])) {
- if (this.state.isExpanded === false) {
- this.setExpansion(null);
- }
- } else if (
- nextProps.collapse !== this.props.collapse &&
- nextProps.collapse !== undefined
- ) this.setExpansion(nextProps.collapse ? false : null);
- }
-
-/*
-
-#### `componentDidMount()`.
-
-When mounting, we just check to see if our status should be collapsed,
-and collapse it if so. We don't need to worry about whether collapsing
-is enabled here, because `setExpansion()` already takes that into
-account.
-
-The cases where a status should be collapsed are:
-
- - The `collapse` prop has been set to `true`
- - The user has decided in local settings to collapse all statuses.
- - The user has decided to collapse all notifications ('muted'
- statuses).
- - The user has decided to collapse long statuses and the status is
- over 400px (without media, or 650px with).
- - The status is a reply and the user has decided to collapse all
- replies.
- - The status contains media and the user has decided to collapse all
- statuses with media.
-
-We also start up our intersection observer to monitor our statuses.
-`componentMounted` lets us know that everything has been set up
-properly and our intersection observer is good to go.
-
-*/
-
- componentDidMount () {
- const { node, handleIntersection } = this;
- const {
- status,
- settings,
- collapse,
- muted,
- id,
- intersectionObserverWrapper,
- prepend,
- } = this.props;
- const autoCollapseSettings = settings.getIn(['collapsed', 'auto']);
-
- if (
- collapse ||
- autoCollapseSettings.get('all') || (
- autoCollapseSettings.get('notifications') && muted
- ) || (
- autoCollapseSettings.get('lengthy') &&
- node.clientHeight > (
- status.get('media_attachments').size && !muted ? 650 : 400
- )
- ) || (
- autoCollapseSettings.get('reblogs') &&
- prepend === 'reblogged_by'
- ) || (
- autoCollapseSettings.get('replies') &&
- status.get('in_reply_to_id', null) !== null
- ) || (
- autoCollapseSettings.get('media') &&
- !(status.get('spoiler_text').length) &&
- status.get('media_attachments').size
- )
- ) this.setExpansion(false);
-
- if (!intersectionObserverWrapper) return;
- else intersectionObserverWrapper.observe(
- id,
- node,
- handleIntersection
- );
-
- this.componentMounted = true;
- }
-
-/*
-
-#### `shouldComponentUpdate()`.
-
-If the status is about to be both offscreen (not intersecting) and
-hidden, then we only need to update it if it's not that way currently.
-If the status is moving from offscreen to onscreen, then we *have* to
-re-render, so that we can unhide the element if necessary.
-
-If neither of these cases are true, we can leave it up to our
-`updateOnProps` and `updateOnStates` arrays.
-
-*/
-
- shouldComponentUpdate (nextProps, nextState) {
- switch (true) {
- case !nextState.isIntersecting && nextState.isHidden:
- return this.state.isIntersecting || !this.state.isHidden;
- case nextState.isIntersecting && !this.state.isIntersecting:
- return true;
- default:
- return super.shouldComponentUpdate(nextProps, nextState);
- }
- }
-
-/*
-
-#### `componentDidUpdate()`.
-
-If our component is being rendered for any reason and an update has
-triggered, this will save its height.
-
-This is, frankly, a bit overkill, as the only instance when we
-actually *need* to update the height right now should be when the
-value of `isExpanded` has changed. But it makes for more readable
-code and prevents bugs in the future where the height isn't set
-properly after some change.
-
-*/
-
- componentDidUpdate () {
- if (
- this.state.isIntersecting || !this.state.isHidden
- ) this.saveHeight();
- }
-
-/*
-
-#### `componentWillUnmount()`.
-
-If our component is about to unmount, then we'd better unset
-`this.componentMounted`.
-
-*/
-
- componentWillUnmount () {
- this.componentMounted = false;
- }
-
-/*
-
-#### `handleIntersection()`.
-
-`handleIntersection()` either hides the status (if it is offscreen) or
-unhides it (if it is onscreen). It's called by
-`intersectionObserverWrapper.observe()`.
-
-If our status isn't intersecting, we schedule an idle task (using the
-aptly-named `scheduleIdleTask()`) to hide the status at the next
-available opportunity.
-
-tootsuite/mastodon left us with the following enlightening comment
-regarding this function:
-
-> Edge 15 doesn't support isIntersecting, but we can infer it
-
-It then implements a polyfill (intersectionRect.height > 0) which isn't
-actually sufficient. The short answer is, this behaviour isn't really
-supported on Edge but we can get kinda close.
-
-*/
-
- handleIntersection = (entry) => {
- const isIntersecting = (
- typeof entry.isIntersecting === 'boolean' ?
- entry.isIntersecting :
- entry.intersectionRect.height > 0
- );
- this.setState(
- (prevState) => {
- if (prevState.isIntersecting && !isIntersecting) {
- scheduleIdleTask(this.hideIfNotIntersecting);
- }
- return {
- isIntersecting : isIntersecting,
- isHidden : false,
- };
- }
- );
- }
-
-/*
-
-#### `hideIfNotIntersecting()`.
-
-This function will hide the status if we're still not intersecting.
-Hiding the status means that it will just render an empty div instead
-of actual content, which saves RAMS and CPUs or some such.
-
-*/
-
- hideIfNotIntersecting = () => {
- if (!this.componentMounted) return;
- this.setState(
- (prevState) => ({ isHidden: !prevState.isIntersecting })
- );
- }
-
-/*
-
-#### `saveHeight()`.
-
-`saveHeight()` saves the height of our status so that when whe hide it
-we preserve its dimensions. We only want to store our height, though,
-if our status has content (otherwise, it would imply that it is
-already hidden).
-
-*/
-
- saveHeight = () => {
- if (this.node && this.node.children.length) {
- this.height = this.node.getBoundingClientRect().height;
- }
- }
-
-/*
-
-#### `setExpansion()`.
-
-`setExpansion()` sets the value of `isExpanded` in our state. It takes
-one argument, `value`, which gives the desired value for `isExpanded`.
-The default for this argument is `null`.
-
-`setExpansion()` automatically checks for us whether toot collapsing
-is enabled, so we don't have to.
-
-We use a `switch` statement to simplify our code.
-
-*/
-
- setExpansion = (value) => {
- switch (true) {
- case value === undefined || value === null:
- this.setState({ isExpanded: null });
- break;
- case !value && this.props.settings.getIn(['collapsed', 'enabled']):
- this.setState({ isExpanded: false });
- break;
- case !!value:
- this.setState({ isExpanded: true });
- break;
- }
- }
-
-/*
-
-#### `handleRef()`.
-
-`handleRef()` just saves a reference to our status node to `this.node`.
-It also saves our height, in case the height of our node has changed.
-
-*/
-
- handleRef = (node) => {
- this.node = node;
- this.saveHeight();
- }
-
-/*
-
-#### `parseClick()`.
-
-`parseClick()` takes a click event and responds appropriately.
-If our status is collapsed, then clicking on it should uncollapse it.
-If `Shift` is held, then clicking on it should collapse it.
-Otherwise, we open the url handed to us in `destination`, if
-applicable.
-
-*/
-
- parseClick = (e, destination) => {
- const { router } = this.context;
- const { status } = this.props;
- const { isExpanded } = this.state;
- if (!router) return;
- if (destination === undefined) {
- destination = `/statuses/${
- status.getIn(['reblog', 'id'], status.get('id'))
- }`;
- }
- if (e.button === 0) {
- if (isExpanded === false) this.setExpansion(null);
- else if (e.shiftKey) {
- this.setExpansion(false);
- document.getSelection().removeAllRanges();
- } else router.history.push(destination);
- e.preventDefault();
- }
- }
-
-/*
-
-#### `render()`.
-
-`render()` actually puts our element on the screen. The particulars of
-this operation are further explained in the code below.
-
-*/
-
- render () {
- const {
- parseClick,
- setExpansion,
- saveHeight,
- handleRef,
- } = this;
- const { router } = this.context;
- const {
- status,
- account,
- settings,
- collapsed,
- muted,
- prepend,
- intersectionObserverWrapper,
- onOpenVideo,
- onOpenMedia,
- notification,
- ...other
- } = this.props;
- const { isExpanded, isIntersecting, isHidden } = this.state;
- let background = null;
- let attachments = null;
- let media = null;
- let mediaIcon = null;
-
-/*
-
-If we don't have a status, then we don't render anything.
-
-*/
-
- if (status === null) {
- return null;
- }
-
-/*
-
-If our status is offscreen and hidden, then we render an empty <div> in
-its place. We fill it with "content" but note that opacity is set to 0.
-
-*/
-
- if (!isIntersecting && isHidden) {
- return (
- <div
- ref={this.handleRef}
- data-id={status.get('id')}
- style={{
- height : `${this.height}px`,
- opacity : 0,
- overflow : 'hidden',
- }}
- >
- {
- status.getIn(['account', 'display_name']) ||
- status.getIn(['account', 'username'])
- }
- {status.get('content')}
- </div>
- );
- }
-
-/*
-
-If user backgrounds for collapsed statuses are enabled, then we
-initialize our background accordingly. This will only be rendered if
-the status is collapsed.
-
-*/
-
- if (
- settings.getIn(['collapsed', 'backgrounds', 'user_backgrounds'])
- ) background = status.getIn(['account', 'header']);
-
-/*
-
-This handles our media attachments. Note that we don't show media on
-muted (notification) statuses. If the media type is unknown, then we
-simply ignore it.
-
-After we have generated our appropriate media element and stored it in
-`media`, we snatch the thumbnail to use as our `background` if media
-backgrounds for collapsed statuses are enabled.
-
-*/
-
- attachments = status.get('media_attachments');
- if (attachments.size && !muted) {
- if (attachments.some((item) => item.get('type') === 'unknown')) {
-
- } else if (
- attachments.getIn([0, 'type']) === 'video'
- ) {
- media = ( // Media type is 'video'
- <StatusPlayer
- media={attachments.get(0)}
- sensitive={status.get('sensitive')}
- letterbox={settings.getIn(['media', 'letterbox'])}
- fullwidth={settings.getIn(['media', 'fullwidth'])}
- height={250}
- onOpenVideo={onOpenVideo}
- />
- );
- mediaIcon = 'video-camera';
- } else { // Media type is 'image' or 'gifv'
- media = (
- <StatusGallery
- media={attachments}
- sensitive={status.get('sensitive')}
- letterbox={settings.getIn(['media', 'letterbox'])}
- fullwidth={settings.getIn(['media', 'fullwidth'])}
- height={250}
- onOpenMedia={onOpenMedia}
- autoPlayGif={autoPlayGif}
- />
- );
- mediaIcon = 'picture-o';
- }
-
- if (
- !status.get('sensitive') &&
- !(status.get('spoiler_text').length > 0) &&
- settings.getIn(['collapsed', 'backgrounds', 'preview_images'])
- ) background = attachments.getIn([0, 'preview_url']);
- }
-
-/*
-
-Here we prepare extra data-* attributes for CSS selectors.
-Users can use those for theming, hiding avatars etc via UserStyle
-
-*/
-
- const selectorAttribs = {
- 'data-status-by': `@${status.getIn(['account', 'acct'])}`,
- };
-
- if (prepend && account) {
- const notifKind = {
- favourite: 'favourited',
- reblog: 'boosted',
- reblogged_by: 'boosted',
- }[prepend];
-
- selectorAttribs[`data-${notifKind}-by`] = `@${account.get('acct')}`;
- }
-
-/*
-
-Finally, we can render our status. We just put the pieces together
-from above. We only render the action bar if the status isn't
-collapsed.
-
-*/
-
- return (
- <article
- className={
- `status${
- muted ? ' muted' : ''
- } status-${status.get('visibility')}${
- isExpanded === false ? ' collapsed' : ''
- }${
- isExpanded === false && background ? ' has-background' : ''
- }${
- this.state.markedForDelete ? ' marked-for-delete' : ''
- }`
- }
- style={{
- backgroundImage: (
- isExpanded === false && background ?
- `url(${background})` :
- 'none'
- ),
- }}
- ref={handleRef}
- {...selectorAttribs}
- >
- {prepend && account ? (
- <StatusPrepend
- type={prepend}
- account={account}
- parseClick={parseClick}
- notificationId={this.props.notificationId}
- />
- ) : null}
- <StatusHeader
- status={status}
- friend={account}
- mediaIcon={mediaIcon}
- collapsible={settings.getIn(['collapsed', 'enabled'])}
- collapsed={isExpanded === false}
- parseClick={parseClick}
- setExpansion={setExpansion}
- />
- <StatusContent
- status={status}
- media={media}
- mediaIcon={mediaIcon}
- expanded={isExpanded}
- setExpansion={setExpansion}
- onHeightUpdate={saveHeight}
- parseClick={parseClick}
- disabled={!router}
- />
- {isExpanded !== false ? (
- <StatusActionBar
- {...other}
- status={status}
- account={status.get('account')}
- />
- ) : null}
- {notification ? (
- <NotificationOverlayContainer
- notification={notification}
- />
- ) : null}
- </article>
- );
-
- }
-
-}
+++ /dev/null
-// Package imports //
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-
-// Mastodon imports //
-import IconButton from '../../../mastodon/components/icon_button';
-import { isIOS } from '../../../mastodon/is_mobile';
-
-const messages = defineMessages({
- toggle_sound: { id: 'video_player.toggle_sound', defaultMessage: 'Toggle sound' },
- toggle_visible: { id: 'video_player.toggle_visible', defaultMessage: 'Toggle visibility' },
- expand_video: { id: 'video_player.expand', defaultMessage: 'Expand video' },
-});
-
-@injectIntl
-export default class StatusPlayer extends React.PureComponent {
-
- static contextTypes = {
- router: PropTypes.object,
- };
-
- static propTypes = {
- media: ImmutablePropTypes.map.isRequired,
- letterbox: PropTypes.bool,
- fullwidth: PropTypes.bool,
- height: PropTypes.number,
- sensitive: PropTypes.bool,
- intl: PropTypes.object.isRequired,
- autoplay: PropTypes.bool,
- onOpenVideo: PropTypes.func.isRequired,
- };
-
- static defaultProps = {
- height: 110,
- };
-
- state = {
- visible: !this.props.sensitive,
- preview: true,
- muted: true,
- hasAudio: true,
- videoError: false,
- };
-
- handleClick = () => {
- this.setState({ muted: !this.state.muted });
- }
-
- handleVideoClick = (e) => {
- e.stopPropagation();
-
- const node = this.video;
-
- if (node.paused) {
- node.play();
- } else {
- node.pause();
- }
- }
-
- handleOpen = () => {
- this.setState({ preview: !this.state.preview });
- }
-
- handleVisibility = () => {
- this.setState({
- visible: !this.state.visible,
- preview: true,
- });
- }
-
- handleExpand = () => {
- this.video.pause();
- this.props.onOpenVideo(this.props.media, this.video.currentTime);
- }
-
- setRef = (c) => {
- this.video = c;
- }
-
- handleLoadedData = () => {
- if (('WebkitAppearance' in document.documentElement.style && this.video.audioTracks.length === 0) || this.video.mozHasAudio === false) {
- this.setState({ hasAudio: false });
- }
- }
-
- handleVideoError = () => {
- this.setState({ videoError: true });
- }
-
- componentDidMount () {
- if (!this.video) {
- return;
- }
-
- this.video.addEventListener('loadeddata', this.handleLoadedData);
- this.video.addEventListener('error', this.handleVideoError);
- }
-
- componentDidUpdate () {
- if (!this.video) {
- return;
- }
-
- this.video.addEventListener('loadeddata', this.handleLoadedData);
- this.video.addEventListener('error', this.handleVideoError);
- }
-
- componentWillUnmount () {
- if (!this.video) {
- return;
- }
-
- this.video.removeEventListener('loadeddata', this.handleLoadedData);
- this.video.removeEventListener('error', this.handleVideoError);
- }
-
- render () {
- const { media, intl, letterbox, fullwidth, height, sensitive, autoplay } = this.props;
-
- let spoilerButton = (
- <div className={`status__video-player-spoiler ${this.state.visible ? 'status__video-player-spoiler--visible' : ''}`}>
- <IconButton overlay title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} onClick={this.handleVisibility} />
- </div>
- );
-
- let expandButton = !this.context.router ? '' : (
- <div className='status__video-player-expand'>
- <IconButton overlay title={intl.formatMessage(messages.expand_video)} icon='expand' onClick={this.handleExpand} />
- </div>
- );
-
- let muteButton = '';
-
- if (this.state.hasAudio) {
- muteButton = (
- <div className='status__video-player-mute'>
- <IconButton overlay title={intl.formatMessage(messages.toggle_sound)} icon={this.state.muted ? 'volume-off' : 'volume-up'} onClick={this.handleClick} />
- </div>
- );
- }
-
- if (!this.state.visible) {
- if (sensitive) {
- return (
- <div role='button' tabIndex='0' style={{ height: `${height}px` }} className={`media-spoiler ${fullwidth ? 'full-width' : ''}`} onClick={this.handleVisibility}>
- {spoilerButton}
- <span className='media-spoiler__warning'><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span>
- <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
- </div>
- );
- } else {
- return (
- <div role='button' tabIndex='0' style={{ height: `${height}px` }} className={`media-spoiler ${fullwidth ? 'full-width' : ''}`} onClick={this.handleVisibility}>
- {spoilerButton}
- <span className='media-spoiler__warning'><FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' /></span>
- <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
- </div>
- );
- }
- }
-
- if (this.state.preview && !autoplay) {
- return (
- <div role='button' tabIndex='0' className={`media-spoiler-video ${fullwidth ? 'full-width' : ''}`} style={{ height: `${height}px`, backgroundImage: `url(${media.get('preview_url')})` }} onClick={this.handleOpen}>
- {spoilerButton}
- <div className='media-spoiler-video-play-icon'><i className='fa fa-play' /></div>
- </div>
- );
- }
-
- if (this.state.videoError) {
- return (
- <div style={{ height: `${height}px` }} className='video-error-cover' >
- <span className='media-spoiler__warning'><FormattedMessage id='video_player.video_error' defaultMessage='Video could not be played' /></span>
- </div>
- );
- }
-
- return (
- <div className={`status__video-player ${fullwidth ? 'full-width' : ''}`} style={{ height: `${height}px` }}>
- {spoilerButton}
- {muteButton}
- {expandButton}
-
- <video
- className={`status__video-player-video${letterbox ? ' letterbox' : ''}`}
- role='button'
- tabIndex='0'
- ref={this.setRef}
- src={media.get('url')}
- autoPlay={!isIOS()}
- loop
- muted={this.state.muted}
- onClick={this.handleVideoClick}
- />
- </div>
- );
- }
-
-}
+++ /dev/null
-/*
-
-`reducers/local_settings`
-========================
-
-> For more information on the contents of this file, please contact:
->
-> - kibigo! [@kibi@glitch.social]
-
-This file provides our Redux reducers related to local settings. The
-associated actions are:
-
- - __`STORE_HYDRATE` :__
- Used to hydrate the store with its initial values.
-
- - __`LOCAL_SETTING_CHANGE` :__
- Used to change the value of a local setting in the store.
-
-*/
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-
-/*
-
-Imports:
---------
-
-*/
-
-// Package imports //
-import { Map as ImmutableMap } from 'immutable';
-
-// Mastodon imports //
-import { STORE_HYDRATE } from '../../mastodon/actions/store';
-
-// Our imports //
-import { LOCAL_SETTING_CHANGE } from '../actions/local_settings';
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-
-/*
-
-initialState:
--------------
-
-You can see the default values for all of our local settings here.
-These are only used if no previously-saved values exist.
-
-*/
-
-const initialState = ImmutableMap({
- layout : 'auto',
- stretch : true,
- navbar_under : false,
- side_arm : 'none',
- collapsed : ImmutableMap({
- enabled : true,
- auto : ImmutableMap({
- all : false,
- notifications : true,
- lengthy : true,
- reblogs : false,
- replies : false,
- media : false,
- }),
- backgrounds : ImmutableMap({
- user_backgrounds : false,
- preview_images : false,
- }),
- }),
- media : ImmutableMap({
- letterbox : true,
- fullwidth : true,
- }),
-});
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-
-/*
-
-Helper functions:
------------------
-
-### `hydrate(state, localSettings)`
-
-`hydrate()` is used to hydrate the `local_settings` part of our store
-with its initial values. The `state` will probably just be the
-`initialState`, and the `localSettings` should be whatever we pulled
-from `localStorage`.
-
-*/
-
-const hydrate = (state, localSettings) => state.mergeDeep(localSettings);
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-
-/*
-
-`localSettings(state = initialState, action)`:
-----------------------------------------------
-
-This function holds our actual reducer.
-
-If our action is `STORE_HYDRATE`, then we call `hydrate()` with the
-`local_settings` property of the provided `action.state`.
-
-If our action is `LOCAL_SETTING_CHANGE`, then we set `action.key` in
-our state to the provided `action.value`. Note that `action.key` MUST
-be an array, since we use `setIn()`.
-
-> __Note :__
-> We call this function `localSettings`, but its associated object
-> in the store is `local_settings`.
-
-*/
-
-export default function localSettings(state = initialState, action) {
- switch(action.type) {
- case STORE_HYDRATE:
- return hydrate(state, action.state.get('local_settings'));
- case LOCAL_SETTING_CHANGE:
- return state.setIn(action.key, action.value);
- default:
- return state;
- }
-};
+++ /dev/null
-// THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !!
-// SEE INSTEAD : glitch/components/status
-
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import Avatar from './avatar';
-import AvatarOverlay from './avatar_overlay';
-import RelativeTimestamp from './relative_timestamp';
-import DisplayName from './display_name';
-import StatusContent from './status_content';
-import StatusActionBar from './status_action_bar';
-import { FormattedMessage } from 'react-intl';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { MediaGallery, Video } from '../features/ui/util/async-components';
-import { HotKeys } from 'react-hotkeys';
-import classNames from 'classnames';
-
-// We use the component (and not the container) since we do not want
-// to use the progress bar to show download progress
-import Bundle from '../features/ui/components/bundle';
-
-export default class Status extends ImmutablePureComponent {
-
- static contextTypes = {
- router: PropTypes.object,
- };
-
- static propTypes = {
- status: ImmutablePropTypes.map,
- account: ImmutablePropTypes.map,
- onReply: PropTypes.func,
- onFavourite: PropTypes.func,
- onReblog: PropTypes.func,
- onDelete: PropTypes.func,
- onPin: PropTypes.func,
- onOpenMedia: PropTypes.func,
- onOpenVideo: PropTypes.func,
- onBlock: PropTypes.func,
- onEmbed: PropTypes.func,
- onHeightChange: PropTypes.func,
- muted: PropTypes.bool,
- hidden: PropTypes.bool,
- onMoveUp: PropTypes.func,
- onMoveDown: PropTypes.func,
- };
-
- state = {
- isExpanded: false,
- }
-
- // Avoid checking props that are functions (and whose equality will always
- // evaluate to false. See react-immutable-pure-component for usage.
- updateOnProps = [
- 'status',
- 'account',
- 'muted',
- 'hidden',
- ]
-
- updateOnStates = ['isExpanded']
-
- handleClick = () => {
- if (!this.context.router) {
- return;
- }
-
- const { status } = this.props;
- this.context.router.history.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`);
- }
-
- handleAccountClick = (e) => {
- if (this.context.router && e.button === 0) {
- const id = e.currentTarget.getAttribute('data-id');
- e.preventDefault();
- this.context.router.history.push(`/accounts/${id}`);
- }
- }
-
- handleExpandedToggle = () => {
- this.setState({ isExpanded: !this.state.isExpanded });
- };
-
- renderLoadingMediaGallery () {
- return <div className='media_gallery' style={{ height: '110px' }} />;
- }
-
- renderLoadingVideoPlayer () {
- return <div className='media-spoiler-video' style={{ height: '110px' }} />;
- }
-
- handleOpenVideo = startTime => {
- this.props.onOpenVideo(this._properStatus().getIn(['media_attachments', 0]), startTime);
- }
-
- handleHotkeyReply = e => {
- e.preventDefault();
- this.props.onReply(this._properStatus(), this.context.router.history);
- }
-
- handleHotkeyFavourite = () => {
- this.props.onFavourite(this._properStatus());
- }
-
- handleHotkeyBoost = e => {
- this.props.onReblog(this._properStatus(), e);
- }
-
- handleHotkeyMention = e => {
- e.preventDefault();
- this.props.onMention(this._properStatus().get('account'), this.context.router.history);
- }
-
- handleHotkeyOpen = () => {
- this.context.router.history.push(`/statuses/${this._properStatus().get('id')}`);
- }
-
- handleHotkeyOpenProfile = () => {
- this.context.router.history.push(`/accounts/${this._properStatus().getIn(['account', 'id'])}`);
- }
-
- handleHotkeyMoveUp = () => {
- this.props.onMoveUp(this.props.status.get('id'));
- }
-
- handleHotkeyMoveDown = () => {
- this.props.onMoveDown(this.props.status.get('id'));
- }
-
- _properStatus () {
- const { status } = this.props;
-
- if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
- return status.get('reblog');
- } else {
- return status;
- }
- }
-
- render () {
- let media = null;
- let statusAvatar, prepend;
-
- const { hidden } = this.props;
- const { isExpanded } = this.state;
-
- let { status, account, ...other } = this.props;
-
- if (status === null) {
- return null;
- }
-
- if (hidden) {
- return (
- <div>
- {status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}
- {status.get('content')}
- </div>
- );
- }
-
- if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
- const display_name_html = { __html: status.getIn(['account', 'display_name_html']) };
-
- prepend = (
- <div className='status__prepend'>
- <div className='status__prepend-icon-wrapper'><i className='fa fa-fw fa-retweet status__prepend-icon' /></div>
- <FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} className='status__display-name muted'><strong dangerouslySetInnerHTML={display_name_html} /></a> }} />
- </div>
- );
-
- account = status.get('account');
- status = status.get('reblog');
- }
-
- if (status.get('media_attachments').size > 0 && !this.props.muted) {
- if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
-
- } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
- const video = status.getIn(['media_attachments', 0]);
-
- media = (
- <Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
- {Component => <Component
- preview={video.get('preview_url')}
- src={video.get('url')}
- width={239}
- height={110}
- sensitive={status.get('sensitive')}
- onOpenVideo={this.handleOpenVideo}
- />}
- </Bundle>
- );
- } else {
- media = (
- <Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery} >
- {Component => <Component media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={this.props.onOpenMedia} />}
- </Bundle>
- );
- }
- }
-
- if (account === undefined || account === null) {
- statusAvatar = <Avatar account={status.get('account')} size={48} />;
- }else{
- statusAvatar = <AvatarOverlay account={status.get('account')} friend={account} />;
- }
-
- const handlers = this.props.muted ? {} : {
- reply: this.handleHotkeyReply,
- favourite: this.handleHotkeyFavourite,
- boost: this.handleHotkeyBoost,
- mention: this.handleHotkeyMention,
- open: this.handleHotkeyOpen,
- openProfile: this.handleHotkeyOpenProfile,
- moveUp: this.handleHotkeyMoveUp,
- moveDown: this.handleHotkeyMoveDown,
- };
-
- return (
- <HotKeys handlers={handlers}>
- <div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0}>
- {prepend}
-
- <div className={classNames('status', `status-${status.get('visibility')}`, { muted: this.props.muted })} data-id={status.get('id')}>
- <div className='status__info'>
- <a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
-
- <a onClick={this.handleAccountClick} target='_blank' data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} title={status.getIn(['account', 'acct'])} className='status__display-name'>
- <div className='status__avatar'>
- {statusAvatar}
- </div>
-
- <DisplayName account={status.get('account')} />
- </a>
- </div>
-
- <StatusContent status={status} onClick={this.handleClick} expanded={isExpanded} onExpandedToggle={this.handleExpandedToggle} />
-
- {media}
-
- <StatusActionBar status={status} account={account} {...other} />
- </div>
- </div>
- </HotKeys>
- );
- }
-
-}
+++ /dev/null
-// THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !!
-// SEE INSTEAD : glitch/components/status/content
-
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import { isRtl } from '../rtl';
-import { FormattedMessage } from 'react-intl';
-import Permalink from './permalink';
-import classnames from 'classnames';
-
-export default class StatusContent extends React.PureComponent {
-
- static contextTypes = {
- router: PropTypes.object,
- };
-
- static propTypes = {
- status: ImmutablePropTypes.map.isRequired,
- expanded: PropTypes.bool,
- onExpandedToggle: PropTypes.func,
- onClick: PropTypes.func,
- };
-
- state = {
- hidden: true,
- };
-
- _updateStatusLinks () {
- const node = this.node;
- const links = node.querySelectorAll('a');
-
- for (var i = 0; i < links.length; ++i) {
- let link = links[i];
- if (link.classList.contains('status-link')) {
- continue;
- }
- link.classList.add('status-link');
-
- let mention = this.props.status.get('mentions').find(item => link.href === item.get('url'));
-
- if (mention) {
- link.addEventListener('click', this.onMentionClick.bind(this, mention), false);
- link.setAttribute('title', mention.get('acct'));
- } else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
- link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
- } else {
- link.setAttribute('title', link.href);
- }
-
- link.setAttribute('target', '_blank');
- link.setAttribute('rel', 'noopener');
- }
- }
-
- componentDidMount () {
- this._updateStatusLinks();
- }
-
- componentDidUpdate () {
- this._updateStatusLinks();
- }
-
- onMentionClick = (mention, e) => {
- if (this.context.router && e.button === 0) {
- e.preventDefault();
- this.context.router.history.push(`/accounts/${mention.get('id')}`);
- }
- }
-
- onHashtagClick = (hashtag, e) => {
- hashtag = hashtag.replace(/^#/, '').toLowerCase();
-
- if (this.context.router && e.button === 0) {
- e.preventDefault();
- this.context.router.history.push(`/timelines/tag/${hashtag}`);
- }
- }
-
- handleMouseDown = (e) => {
- this.startXY = [e.clientX, e.clientY];
- }
-
- handleMouseUp = (e) => {
- if (!this.startXY) {
- return;
- }
-
- const [ startX, startY ] = this.startXY;
- const [ deltaX, deltaY ] = [Math.abs(e.clientX - startX), Math.abs(e.clientY - startY)];
-
- if (e.target.localName === 'button' || e.target.localName === 'a' || (e.target.parentNode && (e.target.parentNode.localName === 'button' || e.target.parentNode.localName === 'a'))) {
- return;
- }
-
- if (deltaX + deltaY < 5 && e.button === 0 && this.props.onClick) {
- this.props.onClick();
- }
-
- this.startXY = null;
- }
-
- handleSpoilerClick = (e) => {
- e.preventDefault();
-
- if (this.props.onExpandedToggle) {
- // The parent manages the state
- this.props.onExpandedToggle();
- } else {
- this.setState({ hidden: !this.state.hidden });
- }
- }
-
- setRef = (c) => {
- this.node = c;
- }
-
- render () {
- const { status } = this.props;
-
- const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
-
- const content = { __html: status.get('contentHtml') };
- const spoilerContent = { __html: status.get('spoilerHtml') };
- const directionStyle = { direction: 'ltr' };
- const classNames = classnames('status__content', {
- 'status__content--with-action': this.props.onClick && this.context.router,
- 'status__content--with-spoiler': status.get('spoiler_text').length > 0,
- });
-
- if (isRtl(status.get('search_index'))) {
- directionStyle.direction = 'rtl';
- }
-
- if (status.get('spoiler_text').length > 0) {
- let mentionsPlaceholder = '';
-
- const mentionLinks = status.get('mentions').map(item => (
- <Permalink to={`/accounts/${item.get('id')}`} href={item.get('url')} key={item.get('id')} className='mention'>
- @<span>{item.get('username')}</span>
- </Permalink>
- )).reduce((aggregate, item) => [...aggregate, item, ' '], []);
-
- const toggleText = hidden ? <FormattedMessage id='status.show_more' defaultMessage='Show more' /> : <FormattedMessage id='status.show_less' defaultMessage='Show less' />;
-
- if (hidden) {
- mentionsPlaceholder = <div>{mentionLinks}</div>;
- }
-
- return (
- <div className={classNames} ref={this.setRef} tabIndex='0' onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
- <p style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}>
- <span dangerouslySetInnerHTML={spoilerContent} />
- {' '}
- <button tabIndex='0' className='status__content__spoiler-link' onClick={this.handleSpoilerClick}>{toggleText}</button>
- </p>
-
- {mentionsPlaceholder}
-
- <div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} style={directionStyle} dangerouslySetInnerHTML={content} />
- </div>
- );
- } else if (this.props.onClick) {
- return (
- <div
- ref={this.setRef}
- tabIndex='0'
- className={classNames}
- style={directionStyle}
- onMouseDown={this.handleMouseDown}
- onMouseUp={this.handleMouseUp}
- dangerouslySetInnerHTML={content}
- />
- );
- } else {
- return (
- <div
- tabIndex='0'
- ref={this.setRef}
- className='status__content'
- style={directionStyle}
- dangerouslySetInnerHTML={content}
- />
- );
- }
- }
-
-}
+++ /dev/null
-// THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !!
-// SEE INSTEAD : glitch/components/account/header
-
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import IconButton from '../../../components/icon_button';
-import Motion from '../../ui/util/optional_motion';
-import spring from 'react-motion/lib/spring';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { autoPlayGif, me } from '../../../initial_state';
-
-const messages = defineMessages({
- unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
- follow: { id: 'account.follow', defaultMessage: 'Follow' },
- requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
-});
-
-class Avatar extends ImmutablePureComponent {
-
- static propTypes = {
- account: ImmutablePropTypes.map.isRequired,
- };
-
- state = {
- isHovered: false,
- };
-
- handleMouseOver = () => {
- if (this.state.isHovered) return;
- this.setState({ isHovered: true });
- }
-
- handleMouseOut = () => {
- if (!this.state.isHovered) return;
- this.setState({ isHovered: false });
- }
-
- render () {
- const { account } = this.props;
- const { isHovered } = this.state;
-
- return (
- <Motion defaultStyle={{ radius: 90 }} style={{ radius: spring(isHovered ? 30 : 90, { stiffness: 180, damping: 12 }) }}>
- {({ radius }) =>
- <a
- href={account.get('url')}
- className='account__header__avatar'
- role='presentation'
- target='_blank'
- rel='noopener'
- style={{ borderRadius: `${radius}px`, backgroundImage: `url(${autoPlayGif || isHovered ? account.get('avatar') : account.get('avatar_static')})` }}
- onMouseOver={this.handleMouseOver}
- onMouseOut={this.handleMouseOut}
- onFocus={this.handleMouseOver}
- onBlur={this.handleMouseOut}
- >
- <span style={{ display: 'none' }}>{account.get('acct')}</span>
- </a>
- }
- </Motion>
- );
- }
-
-}
-
-@injectIntl
-export default class Header extends ImmutablePureComponent {
-
- static propTypes = {
- account: ImmutablePropTypes.map,
- onFollow: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired,
- };
-
- render () {
- const { account, intl } = this.props;
-
- if (!account) {
- return null;
- }
-
- let info = '';
- let actionBtn = '';
- let lockedIcon = '';
-
- if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) {
- info = <span className='account--follows-info'><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span>;
- }
-
- if (me !== account.get('id')) {
- if (account.getIn(['relationship', 'requested'])) {
- actionBtn = (
- <div className='account--action-button'>
- <IconButton size={26} active icon='hourglass' title={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />
- </div>
- );
- } else if (!account.getIn(['relationship', 'blocking'])) {
- actionBtn = (
- <div className='account--action-button'>
- <IconButton size={26} icon={account.getIn(['relationship', 'following']) ? 'user-times' : 'user-plus'} active={account.getIn(['relationship', 'following'])} title={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />
- </div>
- );
- }
- }
-
- if (account.get('locked')) {
- lockedIcon = <i className='fa fa-lock' />;
- }
-
- const content = { __html: account.get('note_emojified') };
- const displayNameHtml = { __html: account.get('display_name_html') };
-
- return (
- <div className='account__header' style={{ backgroundImage: `url(${account.get('header')})` }}>
- <div>
- <Avatar account={account} />
-
- <span className='account__header__display-name' dangerouslySetInnerHTML={displayNameHtml} />
- <span className='account__header__username'>@{account.get('acct')} {lockedIcon}</span>
- <div className='account__header__content' dangerouslySetInnerHTML={content} />
-
- {info}
- {actionBtn}
- </div>
- </div>
- );
- }
-
-}
+++ /dev/null
-// THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !!
-// SEE INSTEAD : glitch/components/notification
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import StatusContainer from '../../../containers/status_container';
-import AccountContainer from '../../../containers/account_container';
-import { FormattedMessage } from 'react-intl';
-import Permalink from '../../../components/permalink';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { HotKeys } from 'react-hotkeys';
-
-export default class Notification extends ImmutablePureComponent {
-
- static contextTypes = {
- router: PropTypes.object,
- };
-
- static propTypes = {
- notification: ImmutablePropTypes.map.isRequired,
- hidden: PropTypes.bool,
- onMoveUp: PropTypes.func.isRequired,
- onMoveDown: PropTypes.func.isRequired,
- onMention: PropTypes.func.isRequired,
- };
-
- handleMoveUp = () => {
- const { notification, onMoveUp } = this.props;
- onMoveUp(notification.get('id'));
- }
-
- handleMoveDown = () => {
- const { notification, onMoveDown } = this.props;
- onMoveDown(notification.get('id'));
- }
-
- handleOpen = () => {
- const { notification } = this.props;
-
- if (notification.get('status')) {
- this.context.router.history.push(`/statuses/${notification.get('status')}`);
- } else {
- this.handleOpenProfile();
- }
- }
-
- handleOpenProfile = () => {
- const { notification } = this.props;
- this.context.router.history.push(`/accounts/${notification.getIn(['account', 'id'])}`);
- }
-
- handleMention = e => {
- e.preventDefault();
-
- const { notification, onMention } = this.props;
- onMention(notification.get('account'), this.context.router.history);
- }
-
- getHandlers () {
- return {
- moveUp: this.handleMoveUp,
- moveDown: this.handleMoveDown,
- open: this.handleOpen,
- openProfile: this.handleOpenProfile,
- mention: this.handleMention,
- reply: this.handleMention,
- };
- }
-
- renderFollow (account, link) {
- return (
- <HotKeys handlers={this.getHandlers()}>
- <div className='notification notification-follow focusable' tabIndex='0'>
- <div className='notification__message'>
- <div className='notification__favourite-icon-wrapper'>
- <i className='fa fa-fw fa-user-plus' />
- </div>
-
- <FormattedMessage id='notification.follow' defaultMessage='{name} followed you' values={{ name: link }} />
- </div>
-
- <AccountContainer id={account.get('id')} withNote={false} hidden={this.props.hidden} />
- </div>
- </HotKeys>
- );
- }
-
- renderMention (notification) {
- return (
- <StatusContainer
- id={notification.get('status')}
- withDismiss
- hidden={this.props.hidden}
- onMoveDown={this.handleMoveDown}
- onMoveUp={this.handleMoveUp}
- />
- );
- }
-
- renderFavourite (notification, link) {
- return (
- <HotKeys handlers={this.getHandlers()}>
- <div className='notification notification-favourite focusable' tabIndex='0'>
- <div className='notification__message'>
- <div className='notification__favourite-icon-wrapper'>
- <i className='fa fa-fw fa-star star-icon' />
- </div>
- <FormattedMessage id='notification.favourite' defaultMessage='{name} favourited your status' values={{ name: link }} />
- </div>
-
- <StatusContainer id={notification.get('status')} account={notification.get('account')} muted withDismiss hidden={!!this.props.hidden} />
- </div>
- </HotKeys>
- );
- }
-
- renderReblog (notification, link) {
- return (
- <HotKeys handlers={this.getHandlers()}>
- <div className='notification notification-reblog focusable' tabIndex='0'>
- <div className='notification__message'>
- <div className='notification__favourite-icon-wrapper'>
- <i className='fa fa-fw fa-retweet' />
- </div>
- <FormattedMessage id='notification.reblog' defaultMessage='{name} boosted your status' values={{ name: link }} />
- </div>
-
- <StatusContainer id={notification.get('status')} account={notification.get('account')} muted withDismiss hidden={this.props.hidden} />
- </div>
- </HotKeys>
- );
- }
-
- render () {
- const { notification } = this.props;
- const account = notification.get('account');
- const displayNameHtml = { __html: account.get('display_name_html') };
- const link = <Permalink className='notification__display-name' href={account.get('url')} title={account.get('acct')} to={`/accounts/${account.get('id')}`} dangerouslySetInnerHTML={displayNameHtml} />;
-
- switch(notification.get('type')) {
- case 'follow':
- return this.renderFollow(account, link);
- case 'mention':
- return this.renderMention(notification);
- case 'favourite':
- return this.renderFavourite(notification, link);
- case 'reblog':
- return this.renderReblog(notification, link);
- }
-
- return null;
- }
-
-}
+++ /dev/null
-import React from 'react';
-import ComposeFormContainer from '../../compose/containers/compose_form_container';
-import NotificationsContainer from '../../ui/containers/notifications_container';
-import LoadingBarContainer from '../../ui/containers/loading_bar_container';
-import ModalContainer from '../../ui/containers/modal_container';
-
-export default class Compose extends React.PureComponent {
-
- render () {
- return (
- <div>
- <ComposeFormContainer />
- <NotificationsContainer />
- <ModalContainer />
- <LoadingBarContainer className='loading-bar' />
- </div>
- );
- }
-
-}
+++ /dev/null
-export function EmojiPicker () {
- return import(/* webpackChunkName: "emoji_picker" */'../../emoji/emoji_picker');
-}
-
-export function Compose () {
- return import(/* webpackChunkName: "features/compose" */'../../compose');
-}
-
-export function Notifications () {
- return import(/* webpackChunkName: "features/notifications" */'../../notifications');
-}
-
-export function HomeTimeline () {
- return import(/* webpackChunkName: "features/home_timeline" */'../../home_timeline');
-}
-
-export function PublicTimeline () {
- return import(/* webpackChunkName: "features/public_timeline" */'../../public_timeline');
-}
-
-export function CommunityTimeline () {
- return import(/* webpackChunkName: "features/community_timeline" */'../../community_timeline');
-}
-
-export function HashtagTimeline () {
- return import(/* webpackChunkName: "features/hashtag_timeline" */'../../hashtag_timeline');
-}
-
-export function DirectTimeline() {
- return import(/* webpackChunkName: "features/direct_timeline" */'../../direct_timeline');
-}
-
-export function Status () {
- return import(/* webpackChunkName: "features/status" */'../../status');
-}
-
-export function GettingStarted () {
- return import(/* webpackChunkName: "features/getting_started" */'../../getting_started');
-}
-
-export function PinnedStatuses () {
- return import(/* webpackChunkName: "features/pinned_statuses" */'../../pinned_statuses');
-}
-
-export function AccountTimeline () {
- return import(/* webpackChunkName: "features/account_timeline" */'../../account_timeline');
-}
-
-export function AccountGallery () {
- return import(/* webpackChunkName: "features/account_gallery" */'../../account_gallery');
-}
-
-export function Followers () {
- return import(/* webpackChunkName: "features/followers" */'../../followers');
-}
-
-export function Following () {
- return import(/* webpackChunkName: "features/following" */'../../following');
-}
-
-export function Reblogs () {
- return import(/* webpackChunkName: "features/reblogs" */'../../reblogs');
-}
-
-export function Favourites () {
- return import(/* webpackChunkName: "features/favourites" */'../../favourites');
-}
-
-export function FollowRequests () {
- return import(/* webpackChunkName: "features/follow_requests" */'../../follow_requests');
-}
-
-export function GenericNotFound () {
- return import(/* webpackChunkName: "features/generic_not_found" */'../../generic_not_found');
-}
-
-export function FavouritedStatuses () {
- return import(/* webpackChunkName: "features/favourited_statuses" */'../../favourited_statuses');
-}
-
-export function Blocks () {
- return import(/* webpackChunkName: "features/blocks" */'../../blocks');
-}
-
-export function Mutes () {
- return import(/* webpackChunkName: "features/mutes" */'../../mutes');
-}
-
-export function OnboardingModal () {
- return import(/* webpackChunkName: "modals/onboarding_modal" */'../components/onboarding_modal');
-}
-
-export function MuteModal () {
- return import(/* webpackChunkName: "modals/mute_modal" */'../components/mute_modal');
-}
-
-export function ReportModal () {
- return import(/* webpackChunkName: "modals/report_modal" */'../components/report_modal');
-}
-
-export function SettingsModal () {
- return import(/* webpackChunkName: "modals/settings_modal" */'glitch/components/local_settings/container');
-}
-
-// THESE AREN'T USED BY US; SEE `glitch/components/status` AND `mastodon/features/status`. //
-// IF MASTODON EVER CHANGES DETAILED STATUSES TO REQUIRE THEM, WE'LL NEED TO UPDATE THE URLS OR SOMETHING LOL. //
-
-export function MediaGallery () {
- return import(/* webpackChunkName: "status/media_gallery" */'../../../components/media_gallery');
-}
-
-export function Video () {
- return import(/* webpackChunkName: "features/video" */'../../video');
-}
-
-export function EmbedModal () {
- return import(/* webpackChunkName: "modals/embed_modal" */'../components/embed_modal');
-}
+++ /dev/null
-import { configure } from 'enzyme';
-import Adapter from 'enzyme-adapter-react-16';
-
-const adapter = new Adapter();
-configure({ adapter });
-import loadPolyfills from '../mastodon/load_polyfills';
+import loadPolyfills from 'themes/glitch/util/load_polyfills';
require.context('../images/', true);
function loaded() {
- const TimelineContainer = require('../mastodon/containers/timeline_container').default;
+ const TimelineContainer = require('themes/glitch/containers/timeline_container').default;
const React = require('react');
const ReactDOM = require('react-dom');
const mountNode = document.getElementById('mastodon-timeline');
}
function main() {
- const ready = require('../mastodon/ready').default;
+ const ready = require('themes/glitch/util/ready').default;
ready(loaded);
}
+// THIS IS THE `vanilla` THEME PACK FILE!!
+// IT'S HERE FOR UPSTREAM COMPATIBILITY!!
+// THE `glitch` PACK FILE IS IN `themes/glitch/index.js`!!
+
import loadPolyfills from '../mastodon/load_polyfills';
// import default stylesheet with variables
import { start } from 'rails-ujs';
import 'font-awesome/css/font-awesome.css';
-// import common styling
-require('../styles/common.scss');
-
require.context('../images/', true);
start();
-import loadPolyfills from '../mastodon/load_polyfills';
-import { processBio } from '../glitch/util/bio_metadata';
-import ready from '../mastodon/ready';
+import loadPolyfills from 'themes/glitch/util/load_polyfills';
+import { processBio } from 'themes/glitch/util/bio_metadata';
+import ready from 'themes/glitch/util/ready';
window.addEventListener('message', e => {
const data = e.data || {};
const { length } = require('stringz');
const IntlRelativeFormat = require('intl-relativeformat').default;
const { delegate } = require('rails-ujs');
- const emojify = require('../mastodon/features/emoji/emoji').default;
- const { getLocale } = require('../mastodon/locales');
+ const emojify = require('../themes/glitch/features/emoji/emoji').default;
+ const { getLocale } = require('mastodon/locales');
const { localeData } = getLocale();
- const VideoContainer = require('../mastodon/containers/video_container').default;
- const MediaGalleryContainer = require('../mastodon/containers/media_gallery_container').default;
- const CardContainer = require('../mastodon/containers/card_container').default;
+ const VideoContainer = require('../themes/glitch/containers/video_container').default;
+ const MediaGalleryContainer = require('../themes/glitch/containers/media_gallery_container').default;
+ const CardContainer = require('../themes/glitch/containers/card_container').default;
const React = require('react');
const ReactDOM = require('react-dom');
-import loadPolyfills from '../mastodon/load_polyfills';
+import loadPolyfills from 'themes/glitch/util/load_polyfills';
require.context('../images/', true);
function loaded() {
- const ComposeContainer = require('../mastodon/containers/compose_container').default;
+ const ComposeContainer = require('themes/glitch/containers/compose_container').default;
const React = require('react');
const ReactDOM = require('react-dom');
const mountNode = document.getElementById('mastodon-compose');
}
function main() {
- const ready = require('../mastodon/ready').default;
+ const ready = require('themes/glitch/util/ready').default;
ready(loaded);
}
+++ /dev/null
-@import 'mastodon/mixins';
-@import 'mastodon/variables';
-@import 'variables-glitch';
-@import 'fonts/roboto';
-@import 'fonts/roboto-mono';
-@import 'fonts/montserrat';
-
-@import 'mastodon/reset';
-@import 'mastodon/basics';
-@import 'mastodon/containers';
-@import 'mastodon/lists';
-@import 'mastodon/footer';
-@import 'mastodon/compact_header';
-@import 'mastodon/landing_strip';
-@import 'mastodon/forms';
-@import 'mastodon/accounts';
-@import 'mastodon/stream_entries';
-@import 'mastodon/components';
-@import 'mastodon/emoji_picker';
-@import 'mastodon/about';
-@import 'mastodon/tables';
-@import 'mastodon/admin';
-@import 'mastodon/rtl';
+++ /dev/null
-// glitch-soc added variables
-
-$dismiss-overlay-width: 4rem;
-import api, { getLinks } from '../api';
+import api, { getLinks } from 'themes/glitch/util/api';
export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST';
export const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS';
-import api, { getLinks } from '../api';
+import api, { getLinks } from 'themes/glitch/util/api';
import { fetchRelationships } from './accounts';
export const BLOCKS_FETCH_REQUEST = 'BLOCKS_FETCH_REQUEST';
-import api from '../api';
+import api from 'themes/glitch/util/api';
export const STATUS_CARD_FETCH_REQUEST = 'STATUS_CARD_FETCH_REQUEST';
export const STATUS_CARD_FETCH_SUCCESS = 'STATUS_CARD_FETCH_SUCCESS';
-import api from '../api';
+import api from 'themes/glitch/util/api';
import { throttle } from 'lodash';
-import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light';
+import { search as emojiSearch } from 'themes/glitch/util/emoji/emoji_mart_search_light';
import { useEmoji } from './emojis';
import {
-import api, { getLinks } from '../api';
+import api, { getLinks } from 'themes/glitch/util/api';
export const DOMAIN_BLOCK_REQUEST = 'DOMAIN_BLOCK_REQUEST';
export const DOMAIN_BLOCK_SUCCESS = 'DOMAIN_BLOCK_SUCCESS';
-import api, { getLinks } from '../api';
+import api, { getLinks } from 'themes/glitch/util/api';
export const FAVOURITED_STATUSES_FETCH_REQUEST = 'FAVOURITED_STATUSES_FETCH_REQUEST';
export const FAVOURITED_STATUSES_FETCH_SUCCESS = 'FAVOURITED_STATUSES_FETCH_SUCCESS';
-import api from '../api';
+import api from 'themes/glitch/util/api';
export const REBLOG_REQUEST = 'REBLOG_REQUEST';
export const REBLOG_SUCCESS = 'REBLOG_SUCCESS';
--- /dev/null
+export const LOCAL_SETTING_CHANGE = 'LOCAL_SETTING_CHANGE';
+
+export function changeLocalSetting(key, value) {
+ return dispatch => {
+ dispatch({
+ type: LOCAL_SETTING_CHANGE,
+ key,
+ value,
+ });
+
+ dispatch(saveLocalSettings());
+ };
+};
+
+// __TODO :__
+// Right now `saveLocalSettings()` doesn't keep track of which user
+// is currently signed in, but it might be better to give each user
+// their *own* local settings.
+export function saveLocalSettings() {
+ return (_, getState) => {
+ const localSettings = getState().get('local_settings').toJS();
+ localStorage.setItem('mastodon-settings', JSON.stringify(localSettings));
+ };
+};
-import api, { getLinks } from '../api';
+import api, { getLinks } from 'themes/glitch/util/api';
import { fetchRelationships } from './accounts';
-import { openModal } from '../../mastodon/actions/modal';
+import { openModal } from 'themes/glitch/actions/modal';
export const MUTES_FETCH_REQUEST = 'MUTES_FETCH_REQUEST';
export const MUTES_FETCH_SUCCESS = 'MUTES_FETCH_SUCCESS';
return dispatch => {
dispatch({ type: MUTES_TOGGLE_HIDE_NOTIFICATIONS });
};
-}
\ No newline at end of file
+}
-import api, { getLinks } from '../api';
+import api, { getLinks } from 'themes/glitch/util/api';
import { List as ImmutableList } from 'immutable';
import IntlMessageFormat from 'intl-messageformat';
import { fetchRelationships } from './accounts';
-import api from '../api';
+import api from 'themes/glitch/util/api';
export const PINNED_STATUSES_FETCH_REQUEST = 'PINNED_STATUSES_FETCH_REQUEST';
export const PINNED_STATUSES_FETCH_SUCCESS = 'PINNED_STATUSES_FETCH_SUCCESS';
export const PINNED_STATUSES_FETCH_FAIL = 'PINNED_STATUSES_FETCH_FAIL';
-import { me } from '../initial_state';
+import { me } from 'themes/glitch/util/initial_state';
export function fetchPinnedStatuses() {
return (dispatch, getState) => {
-import api from '../api';
+import api from 'themes/glitch/util/api';
import { openModal, closeModal } from './modal';
export const REPORT_INIT = 'REPORT_INIT';
-import api from '../api';
+import api from 'themes/glitch/util/api';
export const SEARCH_CHANGE = 'SEARCH_CHANGE';
export const SEARCH_CLEAR = 'SEARCH_CLEAR';
-import api from '../api';
+import api from 'themes/glitch/util/api';
import { deleteFromTimelines } from './timelines';
import { fetchStatusCard } from './cards';
-import { connectStream } from '../stream';
+import { connectStream } from 'themes/glitch/util/stream';
import {
updateTimeline,
deleteFromTimelines,
disconnectTimeline,
} from './timelines';
import { updateNotifications, refreshNotifications } from './notifications';
-import { getLocale } from '../locales';
+import { getLocale } from 'mastodon/locales';
const { messages } = getLocale();
-import api, { getLinks } from '../api';
+import api, { getLinks } from 'themes/glitch/util/api';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
export const TIMELINE_UPDATE = 'TIMELINE_UPDATE';
import IconButton from './icon_button';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
-import { me } from '../initial_state';
+import { me } from 'themes/glitch/util/initial_state';
const messages = defineMessages({
follow: { id: 'account.follow', defaultMessage: 'Follow' },
import React from 'react';
import PropTypes from 'prop-types';
-import unicodeMapping from '../features/emoji/emoji_unicode_mapping_light';
+import unicodeMapping from 'themes/glitch/util/emoji/emoji_unicode_mapping_light';
const assetHost = process.env.CDN_HOST || '';
import React from 'react';
-import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container';
+import AutosuggestAccountContainer from 'themes/glitch/features/compose/containers/autosuggest_account_container';
import AutosuggestEmoji from './autosuggest_emoji';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
-import { isRtl } from '../rtl';
+import { isRtl } from 'themes/glitch/util/rtl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Textarea from 'react-textarea-autosize';
import classNames from 'classnames';
import React from 'react';
-import Motion from '../features/ui/util/optional_motion';
+import Motion from 'themes/glitch/util/optional_motion';
import spring from 'react-motion/lib/spring';
import PropTypes from 'prop-types';
import React from 'react';
import PropTypes from 'prop-types';
import detectPassiveEvents from 'detect-passive-events';
-import { scrollTop } from '../scroll';
+import { scrollTop } from 'themes/glitch/util/scroll';
export default class Column extends React.PureComponent {
import ImmutablePropTypes from 'react-immutable-proptypes';
// Glitch imports
-import NotificationPurgeButtonsContainer from '../../glitch/components/column/notif_cleaning_widget/container';
+import NotificationPurgeButtonsContainer from 'themes/glitch/containers/notification_purge_buttons_container';
const messages = defineMessages({
show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
import ImmutablePropTypes from 'react-immutable-proptypes';
import IconButton from './icon_button';
import Overlay from 'react-overlays/lib/Overlay';
-import Motion from '../features/ui/util/optional_motion';
+import Motion from 'themes/glitch/util/optional_motion';
import spring from 'react-motion/lib/spring';
import detectPassiveEvents from 'detect-passive-events';
import React from 'react';
-import Motion from '../features/ui/util/optional_motion';
+import Motion from 'themes/glitch/util/optional_motion';
import spring from 'react-motion/lib/spring';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import React from 'react';
import PropTypes from 'prop-types';
-import scheduleIdleTask from '../features/ui/util/schedule_idle_task';
-import getRectFromEntry from '../features/ui/util/get_rect_from_entry';
+import scheduleIdleTask from 'themes/glitch/util/schedule_idle_task';
+import getRectFromEntry from 'themes/glitch/util/get_rect_from_entry';
import { is } from 'immutable';
// Diff these props in the "rendered" state
-// THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !!
-// SEE INSTEAD : glitch/components/status/gallery
-
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import { is } from 'immutable';
import IconButton from './icon_button';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import { isIOS } from '../is_mobile';
+import { isIOS } from 'themes/glitch/util/is_mobile';
import classNames from 'classnames';
-import { autoPlayGif } from '../initial_state';
+import { autoPlayGif } from 'themes/glitch/util/initial_state';
const messages = defineMessages({
toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' },
standalone: PropTypes.bool,
index: PropTypes.number.isRequired,
size: PropTypes.number.isRequired,
+ letterbox: PropTypes.bool,
onClick: PropTypes.func.isRequired,
};
}
render () {
- const { attachment, index, size, standalone } = this.props;
+ const { attachment, index, size, standalone, letterbox } = this.props;
let width = 50;
let height = 100;
onClick={this.handleClick}
target='_blank'
>
- <img src={previewUrl} srcSet={srcSet} sizes={sizes} alt={attachment.get('description')} title={attachment.get('description')} />
+ <img className={letterbox ? 'letterbox' : null} src={previewUrl} srcSet={srcSet} sizes={sizes} alt={attachment.get('description')} title={attachment.get('description')} />
</a>
);
} else if (attachment.get('type') === 'gifv') {
thumbnail = (
<div className={classNames('media-gallery__gifv', { autoplay: autoPlay })}>
<video
- className='media-gallery__item-gifv-thumbnail'
+ className={`media-gallery__item-gifv-thumbnail${letterbox ? ' letterbox' : ''}`}
aria-label={attachment.get('description')}
role='application'
src={attachment.get('url')}
static propTypes = {
sensitive: PropTypes.bool,
standalone: PropTypes.bool,
+ letterbox: PropTypes.bool,
+ fullwidth: PropTypes.bool,
media: ImmutablePropTypes.list.isRequired,
size: PropTypes.object,
- height: PropTypes.number.isRequired,
onOpenMedia: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};
this.props.onOpenMedia(this.props.media, index);
}
- handleRef = (node) => {
- if (node && this.isStandaloneEligible()) {
- // offsetWidth triggers a layout, so only calculate when we need to
- this.setState({
- width: node.offsetWidth,
- });
- }
- }
-
isStandaloneEligible() {
const { media, standalone } = this.props;
return standalone && media.size === 1 && media.getIn([0, 'meta', 'small', 'aspect']);
}
render () {
- const { media, intl, sensitive, height } = this.props;
- const { width, visible } = this.state;
+ const { media, intl, sensitive, letterbox, fullwidth } = this.props;
+ const { visible } = this.state;
let children;
- const style = {};
-
- if (this.isStandaloneEligible()) {
- if (!visible && width) {
- // only need to forcibly set the height in "sensitive" mode
- style.height = width / this.props.media.getIn([0, 'meta', 'small', 'aspect']);
- } else {
- // layout automatically, using image's natural aspect ratio
- style.height = '';
- }
- } else {
- // crop the image
- style.height = height;
- }
-
if (!visible) {
let warning;
}
children = (
- <button className='media-spoiler' onClick={this.handleOpen} style={style} ref={this.handleRef}>
+ <button className='media-spoiler' onClick={this.handleOpen}>
<span className='media-spoiler__warning'>{warning}</span>
<span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
</button>
if (this.isStandaloneEligible()) {
children = <Item standalone onClick={this.handleClick} attachment={media.get(0)} />;
} else {
- children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} />);
+ children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} letterbox={letterbox} />);
}
}
return (
- <div className='media-gallery' style={style}>
+ <div className={`media-gallery ${fullwidth ? 'full-width' : ''}`}>
<div className={classNames('spoiler-button', { 'spoiler-button--visible': visible })}>
<IconButton title={intl.formatMessage(messages.toggle_visible)} icon={visible ? 'eye' : 'eye-slash'} overlay onClick={this.handleOpen} />
</div>
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
-// Mastodon imports //
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-
const messages = defineMessages({
btnAll : { id: 'notification_purge.btn_all', defaultMessage: 'Select\nall' },
btnNone : { id: 'notification_purge.btn_none', defaultMessage: 'Select\nnone' },
import React, { PureComponent } from 'react';
import { ScrollContainer } from 'react-router-scroll-4';
import PropTypes from 'prop-types';
-import IntersectionObserverArticleContainer from '../containers/intersection_observer_article_container';
+import IntersectionObserverArticleContainer from 'themes/glitch/containers/intersection_observer_article_container';
import LoadMore from './load_more';
-import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper';
+import IntersectionObserverWrapper from 'themes/glitch/util/intersection_observer_wrapper';
import { throttle } from 'lodash';
import { List as ImmutableList } from 'immutable';
import classNames from 'classnames';
-import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../features/ui/util/fullscreen';
+import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from 'themes/glitch/util/fullscreen';
export default class ScrollableList extends PureComponent {
--- /dev/null
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import StatusPrepend from './status_prepend';
+import StatusHeader from './status_header';
+import StatusContent from './status_content';
+import StatusActionBar from './status_action_bar';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { MediaGallery, Video } from 'themes/glitch/util/async-components';
+import { HotKeys } from 'react-hotkeys';
+import NotificationOverlayContainer from 'themes/glitch/features/notifications/containers/overlay_container';
+
+// We use the component (and not the container) since we do not want
+// to use the progress bar to show download progress
+import Bundle from '../features/ui/components/bundle';
+
+export default class Status extends ImmutablePureComponent {
+
+ static contextTypes = {
+ router: PropTypes.object,
+ };
+
+ static propTypes = {
+ id: PropTypes.string,
+ status: ImmutablePropTypes.map,
+ account: ImmutablePropTypes.map,
+ onReply: PropTypes.func,
+ onFavourite: PropTypes.func,
+ onReblog: PropTypes.func,
+ onDelete: PropTypes.func,
+ onPin: PropTypes.func,
+ onOpenMedia: PropTypes.func,
+ onOpenVideo: PropTypes.func,
+ onBlock: PropTypes.func,
+ onEmbed: PropTypes.func,
+ onHeightChange: PropTypes.func,
+ muted: PropTypes.bool,
+ collapse: PropTypes.bool,
+ hidden: PropTypes.bool,
+ prepend: PropTypes.string,
+ withDismiss: PropTypes.bool,
+ onMoveUp: PropTypes.func,
+ onMoveDown: PropTypes.func,
+ };
+
+ state = {
+ isExpanded: null,
+ markedForDelete: false,
+ }
+
+ // Avoid checking props that are functions (and whose equality will always
+ // evaluate to false. See react-immutable-pure-component for usage.
+ updateOnProps = [
+ 'status',
+ 'account',
+ 'settings',
+ 'prepend',
+ 'boostModal',
+ 'muted',
+ 'collapse',
+ 'notification',
+ ]
+
+ updateOnStates = [
+ 'isExpanded',
+ 'markedForDelete',
+ ]
+
+ // If our settings have changed to disable collapsed statuses, then we
+ // need to make sure that we uncollapse every one. We do that by watching
+ // for changes to `settings.collapsed.enabled` in
+ // `componentWillReceiveProps()`.
+
+ // We also need to watch for changes on the `collapse` prop---if this
+ // changes to anything other than `undefined`, then we need to collapse or
+ // uncollapse our status accordingly.
+ componentWillReceiveProps (nextProps) {
+ if (!nextProps.settings.getIn(['collapsed', 'enabled'])) {
+ if (this.state.isExpanded === false) {
+ this.setExpansion(null);
+ }
+ } else if (
+ nextProps.collapse !== this.props.collapse &&
+ nextProps.collapse !== undefined
+ ) this.setExpansion(nextProps.collapse ? false : null);
+ }
+
+ // When mounting, we just check to see if our status should be collapsed,
+ // and collapse it if so. We don't need to worry about whether collapsing
+ // is enabled here, because `setExpansion()` already takes that into
+ // account.
+
+ // The cases where a status should be collapsed are:
+ //
+ // - The `collapse` prop has been set to `true`
+ // - The user has decided in local settings to collapse all statuses.
+ // - The user has decided to collapse all notifications ('muted'
+ // statuses).
+ // - The user has decided to collapse long statuses and the status is
+ // over 400px (without media, or 650px with).
+ // - The status is a reply and the user has decided to collapse all
+ // replies.
+ // - The status contains media and the user has decided to collapse all
+ // statuses with media.
+ // - The status is a reblog the user has decided to collapse all
+ // statuses which are reblogs.
+ componentDidMount () {
+ const { node } = this;
+ const {
+ status,
+ settings,
+ collapse,
+ muted,
+ prepend,
+ } = this.props;
+ const autoCollapseSettings = settings.getIn(['collapsed', 'auto']);
+
+ if (function () {
+ switch (true) {
+ case collapse:
+ case autoCollapseSettings.get('all'):
+ case autoCollapseSettings.get('notifications') && muted:
+ case autoCollapseSettings.get('lengthy') && node.clientHeight > (
+ status.get('media_attachments').size && !muted ? 650 : 400
+ ):
+ case autoCollapseSettings.get('reblogs') && prepend === 'reblogged_by':
+ case autoCollapseSettings.get('replies') && status.get('in_reply_to_id', null) !== null:
+ case autoCollapseSettings.get('media') && !(status.get('spoiler_text').length) && status.get('media_attachments').size:
+ return true;
+ default:
+ return false;
+ }
+ }()) this.setExpansion(false);
+ }
+
+ // `setExpansion()` sets the value of `isExpanded` in our state. It takes
+ // one argument, `value`, which gives the desired value for `isExpanded`.
+ // The default for this argument is `null`.
+
+ // `setExpansion()` automatically checks for us whether toot collapsing
+ // is enabled, so we don't have to.
+ setExpansion = (value) => {
+ switch (true) {
+ case value === undefined || value === null:
+ this.setState({ isExpanded: null });
+ break;
+ case !value && this.props.settings.getIn(['collapsed', 'enabled']):
+ this.setState({ isExpanded: false });
+ break;
+ case !!value:
+ this.setState({ isExpanded: true });
+ break;
+ }
+ }
+
+ // `parseClick()` takes a click event and responds appropriately.
+ // If our status is collapsed, then clicking on it should uncollapse it.
+ // If `Shift` is held, then clicking on it should collapse it.
+ // Otherwise, we open the url handed to us in `destination`, if
+ // applicable.
+ parseClick = (e, destination) => {
+ const { router } = this.context;
+ const { status } = this.props;
+ const { isExpanded } = this.state;
+ if (!router) return;
+ if (destination === undefined) {
+ destination = `/statuses/${
+ status.getIn(['reblog', 'id'], status.get('id'))
+ }`;
+ }
+ if (e.button === 0) {
+ if (isExpanded === false) this.setExpansion(null);
+ else if (e.shiftKey) {
+ this.setExpansion(false);
+ document.getSelection().removeAllRanges();
+ } else router.history.push(destination);
+ e.preventDefault();
+ }
+ }
+
+ handleAccountClick = (e) => {
+ if (this.context.router && e.button === 0) {
+ const id = e.currentTarget.getAttribute('data-id');
+ e.preventDefault();
+ this.context.router.history.push(`/accounts/${id}`);
+ }
+ }
+
+ handleExpandedToggle = () => {
+ this.setExpansion(this.state.isExpanded || !this.props.status.get('spoiler') ? null : true);
+ };
+
+ handleOpenVideo = startTime => {
+ this.props.onOpenVideo(this.props.status.getIn(['media_attachments', 0]), startTime);
+ }
+
+ handleHotkeyReply = e => {
+ e.preventDefault();
+ this.props.onReply(this.props.status, this.context.router.history);
+ }
+
+ handleHotkeyFavourite = () => {
+ this.props.onFavourite(this.props.status);
+ }
+
+ handleHotkeyBoost = e => {
+ this.props.onReblog(this.props.status, e);
+ }
+
+ handleHotkeyMention = e => {
+ e.preventDefault();
+ this.props.onMention(this.props.status.get('account'), this.context.router.history);
+ }
+
+ handleHotkeyOpen = () => {
+ this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
+ }
+
+ handleHotkeyOpenProfile = () => {
+ this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
+ }
+
+ handleHotkeyMoveUp = () => {
+ this.props.onMoveUp(this.props.status.get('id'));
+ }
+
+ handleHotkeyMoveDown = () => {
+ this.props.onMoveDown(this.props.status.get('id'));
+ }
+
+ renderLoadingMediaGallery () {
+ return <div className='media_gallery' style={{ height: '110px' }} />;
+ }
+
+ renderLoadingVideoPlayer () {
+ return <div className='media-spoiler-video' style={{ height: '110px' }} />;
+ }
+
+ render () {
+ const {
+ parseClick,
+ setExpansion,
+ } = this;
+ const { router } = this.context;
+ const {
+ status,
+ account,
+ settings,
+ collapsed,
+ muted,
+ prepend,
+ intersectionObserverWrapper,
+ onOpenVideo,
+ onOpenMedia,
+ notification,
+ hidden,
+ ...other
+ } = this.props;
+ const { isExpanded } = this.state;
+ let background = null;
+ let attachments = null;
+ let media = null;
+ let mediaIcon = null;
+
+ if (status === null) {
+ return null;
+ }
+
+ if (hidden) {
+ return (
+ <div
+ ref={this.handleRef}
+ data-id={status.get('id')}
+ style={{
+ height: `${this.height}px`,
+ opacity: 0,
+ overflow: 'hidden',
+ }}
+ >
+ {status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}
+ {' '}
+ {status.get('content')}
+ </div>
+ );
+ }
+
+ // If user backgrounds for collapsed statuses are enabled, then we
+ // initialize our background accordingly. This will only be rendered if
+ // the status is collapsed.
+ if (settings.getIn(['collapsed', 'backgrounds', 'user_backgrounds'])) {
+ background = status.getIn(['account', 'header']);
+ }
+
+ // This handles our media attachments. Note that we don't show media on
+ // muted (notification) statuses. If the media type is unknown, then we
+ // simply ignore it.
+
+ // After we have generated our appropriate media element and stored it in
+ // `media`, we snatch the thumbnail to use as our `background` if media
+ // backgrounds for collapsed statuses are enabled.
+ attachments = status.get('media_attachments');
+ if (attachments.size > 0 && !muted) {
+ if (attachments.some(item => item.get('type') === 'unknown')) { // Media type is 'unknown'
+ /* Do nothing */
+ } else if (attachments.getIn([0, 'type']) === 'video') { // Media type is 'video'
+ const video = status.getIn(['media_attachments', 0]);
+
+ media = (
+ <Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
+ {Component => <Component
+ preview={video.get('preview_url')}
+ src={video.get('url')}
+ sensitive={status.get('sensitive')}
+ letterbox={settings.getIn(['media', 'letterbox'])}
+ fullwidth={settings.getIn(['media', 'fullwidth'])}
+ onOpenVideo={this.handleOpenVideo}
+ />}
+ </Bundle>
+ );
+ mediaIcon = 'video-camera';
+ } else { // Media type is 'image' or 'gifv'
+ media = (
+ <Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery} >
+ {Component => (
+ <Component
+ media={attachments}
+ sensitive={status.get('sensitive')}
+ letterbox={settings.getIn(['media', 'letterbox'])}
+ fullwidth={settings.getIn(['media', 'fullwidth'])}
+ onOpenMedia={this.props.onOpenMedia}
+ />
+ )}
+ </Bundle>
+ );
+ mediaIcon = 'picture-o';
+ }
+
+ if (!status.get('sensitive') && !(status.get('spoiler_text').length > 0) && settings.getIn(['collapsed', 'backgrounds', 'preview_images'])) {
+ background = attachments.getIn([0, 'preview_url']);
+ }
+ }
+
+ // Here we prepare extra data-* attributes for CSS selectors.
+ // Users can use those for theming, hiding avatars etc via UserStyle
+ const selectorAttribs = {
+ 'data-status-by': `@${status.getIn(['account', 'acct'])}`,
+ };
+
+ if (prepend && account) {
+ const notifKind = {
+ favourite: 'favourited',
+ reblog: 'boosted',
+ reblogged_by: 'boosted',
+ }[prepend];
+
+ selectorAttribs[`data-${notifKind}-by`] = `@${account.get('acct')}`;
+ }
+
+ const handlers = {
+ reply: this.handleHotkeyReply,
+ favourite: this.handleHotkeyFavourite,
+ boost: this.handleHotkeyBoost,
+ mention: this.handleHotkeyMention,
+ open: this.handleHotkeyOpen,
+ openProfile: this.handleHotkeyOpenProfile,
+ moveUp: this.handleHotkeyMoveUp,
+ moveDown: this.handleHotkeyMoveDown,
+ };
+
+ return (
+ <HotKeys handlers={handlers}>
+ <div
+ className={
+ `status${
+ muted ? ' muted' : ''
+ } status-${status.get('visibility')}${
+ isExpanded === false ? ' collapsed' : ''
+ }${
+ isExpanded === false && background ? ' has-background' : ''
+ }${
+ this.state.markedForDelete ? ' marked-for-delete' : ''
+ }`
+ }
+ style={{
+ backgroundImage: (
+ isExpanded === false && background ?
+ `url(${background})` :
+ 'none'
+ ),
+ }}
+ {...selectorAttribs}
+ >
+ {prepend && account ? (
+ <StatusPrepend
+ type={prepend}
+ account={account}
+ parseClick={parseClick}
+ notificationId={this.props.notificationId}
+ />
+ ) : null}
+ <StatusHeader
+ status={status}
+ friend={account}
+ mediaIcon={mediaIcon}
+ collapsible={settings.getIn(['collapsed', 'enabled'])}
+ collapsed={isExpanded === false}
+ parseClick={parseClick}
+ setExpansion={setExpansion}
+ />
+ <StatusContent
+ status={status}
+ media={media}
+ mediaIcon={mediaIcon}
+ expanded={isExpanded}
+ setExpansion={setExpansion}
+ parseClick={parseClick}
+ disabled={!router}
+ />
+ {isExpanded !== false ? (
+ <StatusActionBar
+ {...other}
+ status={status}
+ account={status.get('account')}
+ />
+ ) : null}
+ {notification ? (
+ <NotificationOverlayContainer
+ notification={notification}
+ />
+ ) : null}
+ </div>
+ </HotKeys>
+ );
+ }
+
+}
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import IconButton from './icon_button';
-import DropdownMenuContainer from '../containers/dropdown_menu_container';
+import DropdownMenuContainer from 'themes/glitch/containers/dropdown_menu_container';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
-import { me } from '../initial_state';
+import { me } from 'themes/glitch/util/initial_state';
+import RelativeTimestamp from './relative_timestamp';
const messages = defineMessages({
delete: { id: 'status.delete', defaultMessage: 'Delete' },
menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
}
- if (status.get('visibility') === 'direct') {
- reblogIcon = 'envelope';
- } else if (status.get('visibility') === 'private') {
- reblogIcon = 'lock';
- }
-
if (status.get('in_reply_to_id', null) === null) {
replyIcon = 'reply';
replyTitle = intl.formatMessage(messages.reply);
<div className='status__action-bar-dropdown'>
<DropdownMenuContainer disabled={anonymousAccess} status={status} items={menu} icon='ellipsis-h' size={18} direction='right' ariaLabel={intl.formatMessage(messages.more)} />
</div>
+
+ <a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
</div>
);
}
-// Package imports //
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
+import { isRtl } from 'themes/glitch/util/rtl';
import { FormattedMessage } from 'react-intl';
+import Permalink from './permalink';
import classnames from 'classnames';
-// Mastodon imports //
-import { isRtl } from '../../../mastodon/rtl';
-import Permalink from '../../../mastodon/components/permalink';
-
export default class StatusContent extends React.PureComponent {
static propTypes = {
status: ImmutablePropTypes.map.isRequired,
- expanded: PropTypes.oneOf([true, false, null]),
+ expanded: PropTypes.bool,
setExpansion: PropTypes.func,
- onHeightUpdate: PropTypes.func,
media: PropTypes.element,
mediaIcon: PropTypes.string,
parseClick: PropTypes.func,
hidden: true,
};
- componentDidMount () {
+ _updateStatusLinks () {
const node = this.node;
const links = node.querySelectorAll('a');
- for (let i = 0; i < links.length; ++i) {
- let link = links[i];
+ for (var i = 0; i < links.length; ++i) {
+ let link = links[i];
+ if (link.classList.contains('status-link')) {
+ continue;
+ }
+ link.classList.add('status-link');
+
let mention = this.props.status.get('mentions').find(item => link.href === item.get('url'));
if (mention) {
}
}
+ componentDidMount () {
+ this._updateStatusLinks();
+ }
+
componentDidUpdate () {
- if (this.props.onHeightUpdate) {
- this.props.onHeightUpdate();
- }
+ this._updateStatusLinks();
}
onLinkClick = (e) => {
disabled,
} = this.props;
- const hidden = (
- this.props.setExpansion ?
- !this.props.expanded :
- this.state.hidden
- );
+ const hidden = this.props.setExpansion ? !this.props.expanded : this.state.hidden;
const content = { __html: status.get('contentHtml') };
const spoilerContent = { __html: status.get('spoilerHtml') };
const directionStyle = { direction: 'ltr' };
const classNames = classnames('status__content', {
'status__content--with-action': parseClick && !disabled,
+ 'status__content--with-spoiler': status.get('spoiler_text').length > 0,
});
if (isRtl(status.get('search_index'))) {
}
return (
- <div className={classNames}>
+ <div className={classNames} tabIndex='0'>
<p
style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}
onMouseDown={this.handleMouseDown}
<div
ref={this.setRef}
style={directionStyle}
+ tabIndex={!hidden ? 0 : null}
onMouseDown={this.handleMouseDown}
onMouseUp={this.handleMouseUp}
dangerouslySetInnerHTML={content}
<div
className={classNames}
style={directionStyle}
+ tabIndex='0'
>
<div
ref={this.setRef}
onMouseDown={this.handleMouseDown}
onMouseUp={this.handleMouseUp}
dangerouslySetInnerHTML={content}
+ tabIndex='0'
/>
{media}
</div>
<div
className='status__content'
style={directionStyle}
+ tabIndex='0'
>
- <div ref={this.setRef} dangerouslySetInnerHTML={content} />
+ <div ref={this.setRef} dangerouslySetInnerHTML={content} tabIndex='0' />
{media}
</div>
);
-/*
-
-`<StatusHeader>`
-================
-
-Originally a part of `<Status>`, but extracted into a separate
-component for better documentation and maintainance by
-@kibi@glitch.social as a part of glitch-soc/mastodon.
-
-*/
-
-// * * * * * * * //
-
-// Imports
-// -------
-
// Package imports.
import React from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
// Mastodon imports.
-import Avatar from '../../../mastodon/components/avatar';
-import AvatarOverlay from '../../../mastodon/components/avatar_overlay';
-import DisplayName from '../../../mastodon/components/display_name';
-import IconButton from '../../../mastodon/components/icon_button';
-import VisibilityIcon from './visibility_icon';
-
-// * * * * * * * //
-
-// Initial setup
-// -------------
+import Avatar from './avatar';
+import AvatarOverlay from './avatar_overlay';
+import DisplayName from './display_name';
+import IconButton from './icon_button';
+import VisibilityIcon from './status_visibility_icon';
// Messages for use with internationalization stuff.
const messages = defineMessages({
direct: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
});
-// * * * * * * * //
-
-// The component
-// -------------
-
@injectIntl
export default class StatusHeader extends React.PureComponent {
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
-import StatusContainer from '../../glitch/components/status/container';
+import StatusContainer from 'themes/glitch/containers/status_container';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ScrollableList from './scrollable_list';
-/*
-
-`<StatusPrepend>`
-=================
-
-Originally a part of `<Status>`, but extracted into a separate
-component for better documentation and maintainance by
-@kibi@glitch.social as a part of glitch-soc/mastodon.
-
-*/
-
- /* * * * */
-
-/*
-
-Imports:
---------
-
-*/
-
// Package imports //
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { FormattedMessage } from 'react-intl';
- /* * * * */
-
-/*
-
-The `<StatusPrepend>` component:
---------------------------------
-
-The `<StatusPrepend>` component holds a status's prepend, ie the text
-that says “X reblogged this,” etc. It is represented by an `<aside>`
-element.
-
-### Props
-
- - __`type` (`PropTypes.string`) :__
- The type of prepend. One of `'reblogged_by'`, `'reblog'`,
- `'favourite'`.
-
- - __`account` (`ImmutablePropTypes.map`) :__
- The account associated with the prepend.
-
- - __`parseClick` (`PropTypes.func.isRequired`) :__
- Our click parsing function.
-
-*/
-
export default class StatusPrepend extends React.PureComponent {
static propTypes = {
notificationId: PropTypes.number,
};
-/*
-
-### Implementation
-
-#### `handleClick()`.
-
-This is just a small wrapper for `parseClick()` that gets fired when
-an account link is clicked.
-
-*/
-
handleClick = (e) => {
const { account, parseClick } = this.props;
parseClick(e, `/accounts/${+account.get('id')}`);
}
-/*
-
-#### `<Message>`.
-
-`<Message>` is a quick functional React component which renders the
-actual prepend message based on our provided `type`. First we create a
-`link` for the account's name, and then use `<FormattedMessage>` to
-generate the message.
-
-*/
-
Message = () => {
const { type, account } = this.props;
let link = (
return null;
}
-/*
-
-#### `render()`.
-
-Our `render()` is incredibly simple; we just render the icon and then
-the `<Message>` inside of an <aside>.
-
-*/
-
render () {
const { Message } = this;
const { type } = this.props;
import React from 'react';
import { connect } from 'react-redux';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import { makeGetAccount } from '../selectors';
-import Account from '../components/account';
+import { makeGetAccount } from 'themes/glitch/selectors';
+import Account from 'themes/glitch/components/account';
import {
followAccount,
unfollowAccount,
unblockAccount,
muteAccount,
unmuteAccount,
-} from '../actions/accounts';
-import { openModal } from '../actions/modal';
-import { initMuteModal } from '../actions/mutes';
-import { unfollowModal } from '../initial_state';
+} from 'themes/glitch/actions/accounts';
+import { openModal } from 'themes/glitch/actions/modal';
+import { initMuteModal } from 'themes/glitch/actions/mutes';
+import { unfollowModal } from 'themes/glitch/util/initial_state';
const messages = defineMessages({
unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
import React from 'react';
import PropTypes from 'prop-types';
-import Card from '../features/status/components/card';
+import Card from 'themes/glitch/features/status/components/card';
import { fromJS } from 'immutable';
export default class CardContainer extends React.PureComponent {
import React from 'react';
import { Provider } from 'react-redux';
import PropTypes from 'prop-types';
-import configureStore from '../store/configureStore';
-import { hydrateStore } from '../actions/store';
+import configureStore from 'themes/glitch/store/configureStore';
+import { hydrateStore } from 'themes/glitch/actions/store';
import { IntlProvider, addLocaleData } from 'react-intl';
-import { getLocale } from '../locales';
-import Compose from '../features/standalone/compose';
-import initialState from '../initial_state';
+import { getLocale } from 'mastodon/locales';
+import Compose from 'themes/glitch/features/standalone/compose';
+import initialState from 'themes/glitch/util/initial_state';
const { localeData, messages } = getLocale();
addLocaleData(localeData);
-import { openModal, closeModal } from '../actions/modal';
+import { openModal, closeModal } from 'themes/glitch/actions/modal';
import { connect } from 'react-redux';
-import DropdownMenu from '../components/dropdown_menu';
-import { isUserTouching } from '../is_mobile';
+import DropdownMenu from 'themes/glitch/components/dropdown_menu';
+import { isUserTouching } from 'themes/glitch/util/is_mobile';
const mapStateToProps = state => ({
isModalOpen: state.get('modal').modalType === 'ACTIONS',
import { connect } from 'react-redux';
-import IntersectionObserverArticle from '../components/intersection_observer_article';
-import { setHeight } from '../actions/height_cache';
+import IntersectionObserverArticle from 'themes/glitch/components/intersection_observer_article';
+import { setHeight } from 'themes/glitch/actions/height_cache';
const makeMapStateToProps = (state, props) => ({
cachedHeight: state.getIn(['height_cache', props.saveHeightKey, props.id]),
import React from 'react';
import { Provider } from 'react-redux';
import PropTypes from 'prop-types';
-import configureStore from '../store/configureStore';
-import { showOnboardingOnce } from '../actions/onboarding';
+import configureStore from 'themes/glitch/store/configureStore';
+import { showOnboardingOnce } from 'themes/glitch/actions/onboarding';
import { BrowserRouter, Route } from 'react-router-dom';
import { ScrollContext } from 'react-router-scroll-4';
-import UI from '../features/ui';
-import { hydrateStore } from '../actions/store';
-import { connectUserStream } from '../actions/streaming';
+import UI from 'themes/glitch/features/ui';
+import { hydrateStore } from 'themes/glitch/actions/store';
+import { connectUserStream } from 'themes/glitch/actions/streaming';
import { IntlProvider, addLocaleData } from 'react-intl';
-import { getLocale } from '../locales';
-import initialState from '../initial_state';
+import { getLocale } from 'mastodon/locales';
+import initialState from 'themes/glitch/util/initial_state';
const { localeData, messages } = getLocale();
addLocaleData(localeData);
import React from 'react';
import PropTypes from 'prop-types';
import { IntlProvider, addLocaleData } from 'react-intl';
-import { getLocale } from '../locales';
-import MediaGallery from '../components/media_gallery';
+import { getLocale } from 'mastodon/locales';
+import MediaGallery from 'themes/glitch/components/media_gallery';
import { fromJS } from 'immutable';
const { localeData, messages } = getLocale();
-/*
-
-`<NotificationPurgeButtonsContainer>`
-=========================
-
-This container connects `<NotificationPurgeButtons>`s to the Redux store.
-
-*/
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-
-/*
-
-Imports:
---------
-
-*/
-
-// Package imports //
+// Package imports.
import { connect } from 'react-redux';
+import { defineMessages, injectIntl } from 'react-intl';
-// Our imports //
-import NotificationPurgeButtons from './notification_purge_buttons';
+// Our imports.
+import NotificationPurgeButtons from 'themes/glitch/components/notification_purge_buttons';
import {
deleteMarkedNotifications,
enterNotificationClearingMode,
markAllNotifications,
-} from '../../../../mastodon/actions/notifications';
-import { defineMessages, injectIntl } from 'react-intl';
-import { openModal } from '../../../../mastodon/actions/modal';
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-
-/*
-
-Dispatch mapping:
------------------
-
-The `mapDispatchToProps()` function maps dispatches to our store to the
-various props of our component. We only need to provide a dispatch for
-deleting notifications.
-
-*/
+} from 'themes/glitch/actions/notifications';
+import { openModal } from 'themes/glitch/actions/modal';
const messages = defineMessages({
clearMessage: { id: 'notifications.marked_clear_confirmation', defaultMessage: 'Are you sure you want to permanently clear all selected notifications?' },
-// THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !!
-// SEE INSTEAD : glitch/components/status/container
-
import React from 'react';
import { connect } from 'react-redux';
-import Status from '../components/status';
-import { makeGetStatus } from '../selectors';
+import Status from 'themes/glitch/components/status';
+import { makeGetStatus } from 'themes/glitch/selectors';
import {
replyCompose,
mentionCompose,
-} from '../actions/compose';
+} from 'themes/glitch/actions/compose';
import {
reblog,
favourite,
unfavourite,
pin,
unpin,
-} from '../actions/interactions';
-import { blockAccount } from '../actions/accounts';
-import { muteStatus, unmuteStatus, deleteStatus } from '../actions/statuses';
-import { initMuteModal } from '../actions/mutes';
-import { initReport } from '../actions/reports';
-import { openModal } from '../actions/modal';
+} from 'themes/glitch/actions/interactions';
+import { blockAccount } from 'themes/glitch/actions/accounts';
+import { muteStatus, unmuteStatus, deleteStatus } from 'themes/glitch/actions/statuses';
+import { initMuteModal } from 'themes/glitch/actions/mutes';
+import { initReport } from 'themes/glitch/actions/reports';
+import { openModal } from 'themes/glitch/actions/modal';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import { boostModal, deleteModal } from '../initial_state';
+import { boostModal, deleteModal } from 'themes/glitch/util/initial_state';
const messages = defineMessages({
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
const makeMapStateToProps = () => {
const getStatus = makeGetStatus();
- const mapStateToProps = (state, props) => ({
- status: getStatus(state, props.id),
- });
+ const mapStateToProps = (state, props) => {
+
+ let status = getStatus(state, props.id);
+ let reblogStatus = status ? status.get('reblog', null) : null;
+ let account = undefined;
+ let prepend = undefined;
+
+ if (reblogStatus !== null && typeof reblogStatus === 'object') {
+ account = status.get('account');
+ status = reblogStatus;
+ prepend = 'reblogged_by';
+ }
+
+ return {
+ status : status,
+ account : account || props.account,
+ settings : state.get('local_settings'),
+ prepend : prepend || props.prepend,
+ };
+ };
return mapStateToProps;
};
import React from 'react';
import { Provider } from 'react-redux';
import PropTypes from 'prop-types';
-import configureStore from '../store/configureStore';
-import { hydrateStore } from '../actions/store';
+import configureStore from 'themes/glitch/store/configureStore';
+import { hydrateStore } from 'themes/glitch/actions/store';
import { IntlProvider, addLocaleData } from 'react-intl';
-import { getLocale } from '../locales';
-import PublicTimeline from '../features/standalone/public_timeline';
-import HashtagTimeline from '../features/standalone/hashtag_timeline';
-import initialState from '../initial_state';
+import { getLocale } from 'mastodon/locales';
+import PublicTimeline from 'themes/glitch/features/standalone/public_timeline';
+import HashtagTimeline from 'themes/glitch/features/standalone/hashtag_timeline';
+import initialState from 'themes/glitch/util/initial_state';
const { localeData, messages } = getLocale();
addLocaleData(localeData);
import React from 'react';
import PropTypes from 'prop-types';
import { IntlProvider, addLocaleData } from 'react-intl';
-import { getLocale } from '../locales';
-import Video from '../features/video';
+import { getLocale } from 'mastodon/locales';
+import Video from 'themes/glitch/features/video';
const { localeData, messages } = getLocale();
addLocaleData(localeData);
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
-import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
+import DropdownMenuContainer from 'themes/glitch/containers/dropdown_menu_container';
import { Link } from 'react-router-dom';
import { defineMessages, injectIntl, FormattedMessage, FormattedNumber } from 'react-intl';
-import { me } from '../../../initial_state';
+import { me } from 'themes/glitch/util/initial_state';
const messages = defineMessages({
mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' },
--- /dev/null
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+import Avatar from 'themes/glitch/components/avatar';
+import IconButton from 'themes/glitch/components/icon_button';
+
+import emojify from 'themes/glitch/util/emoji';
+import { me } from 'themes/glitch/util/initial_state';
+import { processBio } from 'themes/glitch/util/bio_metadata';
+
+const messages = defineMessages({
+ unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
+ follow: { id: 'account.follow', defaultMessage: 'Follow' },
+ requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
+});
+
+@injectIntl
+export default class Header extends ImmutablePureComponent {
+
+ static propTypes = {
+ account: ImmutablePropTypes.map,
+ onFollow: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired,
+ };
+
+ render () {
+ const { account, intl } = this.props;
+
+ if (!account) {
+ return null;
+ }
+
+ let displayName = account.get('display_name_html');
+ let info = '';
+ let actionBtn = '';
+
+ if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) {
+ info = <span className='account--follows-info'><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span>;
+ }
+
+ if (me !== account.get('id')) {
+ if (account.getIn(['relationship', 'requested'])) {
+ actionBtn = (
+ <div className='account--action-button'>
+ <IconButton size={26} active icon='hourglass' title={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />
+ </div>
+ );
+ } else if (!account.getIn(['relationship', 'blocking'])) {
+ actionBtn = (
+ <div className='account--action-button'>
+ <IconButton size={26} icon={account.getIn(['relationship', 'following']) ? 'user-times' : 'user-plus'} active={account.getIn(['relationship', 'following'])} title={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />
+ </div>
+ );
+ }
+ }
+
+ const { text, metadata } = processBio(account.get('note'));
+
+ return (
+ <div className='account__header__wrapper'>
+ <div className='account__header' style={{ backgroundImage: `url(${account.get('header')})` }}>
+ <div>
+ <Avatar account={account} size={90} />
+
+ <span className='account__header__display-name' dangerouslySetInnerHTML={{ __html: displayName }} />
+ <span className='account__header__username'>@{account.get('acct')} {account.get('locked') ? <i className='fa fa-lock' /> : null}</span>
+ <div className='account__header__content' dangerouslySetInnerHTML={{ __html: emojify(text) }} />
+
+ {info}
+ {actionBtn}
+ </div>
+ </div>
+
+ {metadata.length && (
+ <table className='account__metadata'>
+ <tbody>
+ {(() => {
+ let data = [];
+ for (let i = 0; i < metadata.length; i++) {
+ data.push(
+ <tr key={i}>
+ <th scope='row'><div dangerouslySetInnerHTML={{ __html: emojify(metadata[i][0]) }} /></th>
+ <td><div dangerouslySetInnerHTML={{ __html: emojify(metadata[i][1]) }} /></td>
+ </tr>
+ );
+ }
+ return data;
+ })()}
+ </tbody>
+ </table>
+ ) || null}
+ </div>
+ );
+ }
+
+}
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
-import Permalink from '../../../components/permalink';
+import Permalink from 'themes/glitch/components/permalink';
export default class MediaItem extends ImmutablePureComponent {
import { connect } from 'react-redux';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
-import { fetchAccount } from '../../actions/accounts';
-import { refreshAccountMediaTimeline, expandAccountMediaTimeline } from '../../actions/timelines';
-import LoadingIndicator from '../../components/loading_indicator';
-import Column from '../ui/components/column';
-import ColumnBackButton from '../../components/column_back_button';
+import { fetchAccount } from 'themes/glitch/actions/accounts';
+import { refreshAccountMediaTimeline, expandAccountMediaTimeline } from 'themes/glitch/actions/timelines';
+import LoadingIndicator from 'themes/glitch/components/loading_indicator';
+import Column from 'themes/glitch/features/ui/components/column';
+import ColumnBackButton from 'themes/glitch/components/column_back_button';
import ImmutablePureComponent from 'react-immutable-pure-component';
-import { getAccountGallery } from '../../selectors';
+import { getAccountGallery } from 'themes/glitch/selectors';
import MediaItem from './components/media_item';
-import HeaderContainer from '../account_timeline/containers/header_container';
+import HeaderContainer from 'themes/glitch/features/account_timeline/containers/header_container';
import { FormattedMessage } from 'react-intl';
import { ScrollContainer } from 'react-router-scroll-4';
-import LoadMore from '../../components/load_more';
+import LoadMore from 'themes/glitch/components/load_more';
const mapStateToProps = (state, props) => ({
medias: getAccountGallery(state, props.params.accountId),
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
-import InnerHeader from '../../../../glitch/components/account/header';
-import ActionBar from '../../account/components/action_bar';
-import MissingIndicator from '../../../components/missing_indicator';
+import InnerHeader from 'themes/glitch/features/account/components/header';
+import ActionBar from 'themes/glitch/features/account/components/action_bar';
+import MissingIndicator from 'themes/glitch/components/missing_indicator';
import ImmutablePureComponent from 'react-immutable-pure-component';
export default class Header extends ImmutablePureComponent {
import React from 'react';
import { connect } from 'react-redux';
-import { makeGetAccount } from '../../../selectors';
+import { makeGetAccount } from 'themes/glitch/selectors';
import Header from '../components/header';
import {
followAccount,
blockAccount,
unblockAccount,
unmuteAccount,
-} from '../../../actions/accounts';
-import { mentionCompose } from '../../../actions/compose';
-import { initMuteModal } from '../../../actions/mutes';
-import { initReport } from '../../../actions/reports';
-import { openModal } from '../../../actions/modal';
-import { blockDomain, unblockDomain } from '../../../actions/domain_blocks';
+} from 'themes/glitch/actions/accounts';
+import { mentionCompose } from 'themes/glitch/actions/compose';
+import { initMuteModal } from 'themes/glitch/actions/mutes';
+import { initReport } from 'themes/glitch/actions/reports';
+import { openModal } from 'themes/glitch/actions/modal';
+import { blockDomain, unblockDomain } from 'themes/glitch/actions/domain_blocks';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import { unfollowModal } from '../../../initial_state';
+import { unfollowModal } from 'themes/glitch/util/initial_state';
const messages = defineMessages({
unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
import { connect } from 'react-redux';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
-import { fetchAccount } from '../../actions/accounts';
-import { refreshAccountTimeline, expandAccountTimeline } from '../../actions/timelines';
+import { fetchAccount } from 'themes/glitch/actions/accounts';
+import { refreshAccountTimeline, expandAccountTimeline } from 'themes/glitch/actions/timelines';
import StatusList from '../../components/status_list';
import LoadingIndicator from '../../components/loading_indicator';
import Column from '../ui/components/column';
import { connect } from 'react-redux';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
-import LoadingIndicator from '../../components/loading_indicator';
+import LoadingIndicator from 'themes/glitch/components/loading_indicator';
import { ScrollContainer } from 'react-router-scroll-4';
-import Column from '../ui/components/column';
-import ColumnBackButtonSlim from '../../components/column_back_button_slim';
-import AccountContainer from '../../containers/account_container';
-import { fetchBlocks, expandBlocks } from '../../actions/blocks';
+import Column from 'themes/glitch/features/ui/components/column';
+import ColumnBackButtonSlim from 'themes/glitch/components/column_back_button_slim';
+import AccountContainer from 'themes/glitch/containers/account_container';
+import { fetchBlocks, expandBlocks } from 'themes/glitch/actions/blocks';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import SettingText from '../../../components/setting_text';
+import SettingText from 'themes/glitch/components/setting_text';
const messages = defineMessages({
filter_regex: { id: 'home.column_settings.filter_regex', defaultMessage: 'Filter out by regular expressions' },
import { connect } from 'react-redux';
import ColumnSettings from '../components/column_settings';
-import { changeSetting } from '../../../actions/settings';
+import { changeSetting } from 'themes/glitch/actions/settings';
const mapStateToProps = state => ({
settings: state.getIn(['settings', 'community']),
import React from 'react';
import { connect } from 'react-redux';
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 StatusListContainer from 'themes/glitch/features/ui/containers/status_list_container';
+import Column from 'themes/glitch/components/column';
+import ColumnHeader from 'themes/glitch/components/column_header';
import {
refreshCommunityTimeline,
expandCommunityTimeline,
-} from '../../actions/timelines';
-import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
+} from 'themes/glitch/actions/timelines';
+import { addColumn, removeColumn, moveColumn } from 'themes/glitch/actions/columns';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ColumnSettingsContainer from './containers/column_settings_container';
-import { connectCommunityStream } from '../../actions/streaming';
+import { connectCommunityStream } from 'themes/glitch/actions/streaming';
const messages = defineMessages({
title: { id: 'column.community', defaultMessage: 'Local timeline' },
--- /dev/null
+// Package imports.
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { injectIntl, defineMessages } from 'react-intl';
+
+// Our imports.
+import ComposeAdvancedOptionsToggle from './advanced_options_toggle';
+import ComposeDropdown from './dropdown';
+
+const messages = defineMessages({
+ local_only_short :
+ { id: 'advanced-options.local-only.short', defaultMessage: 'Local-only' },
+ local_only_long :
+ { id: 'advanced-options.local-only.long', defaultMessage: 'Do not post to other instances' },
+ advanced_options_icon_title :
+ { id: 'advanced_options.icon_title', defaultMessage: 'Advanced options' },
+});
+
+@injectIntl
+export default class ComposeAdvancedOptions extends React.PureComponent {
+
+ static propTypes = {
+ values : ImmutablePropTypes.contains({
+ do_not_federate : PropTypes.bool.isRequired,
+ }).isRequired,
+ onChange : PropTypes.func.isRequired,
+ intl : PropTypes.object.isRequired,
+ };
+
+ render () {
+ const { intl, values } = this.props;
+ const options = [
+ { icon: 'wifi', shortText: messages.local_only_short, longText: messages.local_only_long, name: 'do_not_federate' },
+ ];
+ const anyEnabled = values.some((enabled) => enabled);
+
+ const optionElems = options.map((option) => {
+ return (
+ <ComposeAdvancedOptionsToggle
+ onChange={this.props.onChange}
+ active={values.get(option.name)}
+ key={option.name}
+ name={option.name}
+ shortText={intl.formatMessage(option.shortText)}
+ longText={intl.formatMessage(option.longText)}
+ />
+ );
+ });
+
+ return (
+ <ComposeDropdown
+ title={intl.formatMessage(messages.advanced_options_icon_title)}
+ icon='home'
+ highlight={anyEnabled}
+ >
+ {optionElems}
+ </ComposeDropdown>
+ );
+ }
+
+}
--- /dev/null
+// Package imports.
+import React from 'react';
+import PropTypes from 'prop-types';
+import Toggle from 'react-toggle';
+
+export default class ComposeAdvancedOptionsToggle extends React.PureComponent {
+
+ static propTypes = {
+ onChange: PropTypes.func.isRequired,
+ active: PropTypes.bool.isRequired,
+ name: PropTypes.string.isRequired,
+ shortText: PropTypes.string.isRequired,
+ longText: PropTypes.string.isRequired,
+ }
+
+ onToggle = () => {
+ this.props.onChange(this.props.name);
+ }
+
+ render() {
+ const { active, shortText, longText } = this.props;
+ return (
+ <div role='button' tabIndex='0' className='advanced-options-dropdown__option' onClick={this.onToggle}>
+ <div className='advanced-options-dropdown__option__toggle'>
+ <Toggle checked={active} onChange={this.onToggle} />
+ </div>
+ <div className='advanced-options-dropdown__option__content'>
+ <strong>{shortText}</strong>
+ {longText}
+ </div>
+ </div>
+ );
+ }
+
+}
import { injectIntl, defineMessages } from 'react-intl';
// Our imports //
-import ComposeDropdown from '../dropdown/index';
-import { uploadCompose } from '../../../../mastodon/actions/compose';
+import ComposeDropdown from './dropdown';
+import { uploadCompose } from 'themes/glitch/actions/compose';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
-import { openModal } from '../../../../mastodon/actions/modal';
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+import { openModal } from 'themes/glitch/actions/modal';
const messages = defineMessages({
upload :
import React from 'react';
-import Avatar from '../../../components/avatar';
-import DisplayName from '../../../components/display_name';
+import Avatar from 'themes/glitch/components/avatar';
+import DisplayName from 'themes/glitch/components/display_name';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import React from 'react';
import CharacterCounter from './character_counter';
-import Button from '../../../components/button';
+import Button from 'themes/glitch/components/button';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import ReplyIndicatorContainer from '../containers/reply_indicator_container';
-import AutosuggestTextarea from '../../../components/autosuggest_textarea';
+import AutosuggestTextarea from 'themes/glitch/components/autosuggest_textarea';
import { defineMessages, injectIntl } from 'react-intl';
-import Collapsable from '../../../components/collapsable';
+import Collapsable from 'themes/glitch/components/collapsable';
import SpoilerButtonContainer from '../containers/spoiler_button_container';
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
-import ComposeAdvancedOptionsContainer from '../../../../glitch/components/compose/advanced_options/container';
+import ComposeAdvancedOptionsContainer from '../containers/advanced_options_container';
import SensitiveButtonContainer from '../containers/sensitive_button_container';
import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container';
import UploadFormContainer from '../containers/upload_form_container';
import WarningContainer from '../containers/warning_container';
-import { isMobile } from '../../../is_mobile';
+import { isMobile } from 'themes/glitch/util/is_mobile';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { length } from 'stringz';
-import { countableText } from '../util/counter';
-import ComposeAttachOptions from '../../../../glitch/components/compose/attach_options/index';
-import initialState from '../../../initial_state';
+import { countableText } from 'themes/glitch/util/counter';
+import ComposeAttachOptions from './attach_options';
+import initialState from 'themes/glitch/util/initial_state';
const maxChars = initialState.max_toot_chars;
-// Package imports //
+// Package imports.
import React from 'react';
import PropTypes from 'prop-types';
-// Mastodon imports //
-import IconButton from '../../../../mastodon/components/icon_button';
+// Our imports.
+import IconButton from 'themes/glitch/components/icon_button';
const iconStyle = {
height : null,
import React from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
-import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components';
+import { EmojiPicker as EmojiPickerAsync } from 'themes/glitch/util/async-components';
import Overlay from 'react-overlays/lib/Overlay';
import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes';
import detectPassiveEvents from 'detect-passive-events';
-import { buildCustomEmojis } from '../../emoji/emoji';
+import { buildCustomEmojis } from 'themes/glitch/util/emoji';
const messages = defineMessages({
emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
-import Avatar from '../../../components/avatar';
-import IconButton from '../../../components/icon_button';
-import Permalink from '../../../components/permalink';
+import Avatar from 'themes/glitch/components/avatar';
+import IconButton from 'themes/glitch/components/icon_button';
+import Permalink from 'themes/glitch/components/permalink';
import { FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, defineMessages } from 'react-intl';
-import IconButton from '../../../components/icon_button';
+import IconButton from 'themes/glitch/components/icon_button';
import Overlay from 'react-overlays/lib/Overlay';
-import Motion from '../../ui/util/optional_motion';
+import Motion from 'themes/glitch/util/optional_motion';
import spring from 'react-motion/lib/spring';
import detectPassiveEvents from 'detect-passive-events';
import classNames from 'classnames';
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
-import Avatar from '../../../components/avatar';
-import IconButton from '../../../components/icon_button';
-import DisplayName from '../../../components/display_name';
+import Avatar from 'themes/glitch/components/avatar';
+import IconButton from 'themes/glitch/components/icon_button';
+import DisplayName from 'themes/glitch/components/display_name';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import Overlay from 'react-overlays/lib/Overlay';
-import Motion from '../../ui/util/optional_motion';
+import Motion from 'themes/glitch/util/optional_motion';
import spring from 'react-motion/lib/spring';
const messages = defineMessages({
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { FormattedMessage } from 'react-intl';
-import AccountContainer from '../../../containers/account_container';
-import StatusContainer from '../../../../glitch/components/status/container';
+import AccountContainer from 'themes/glitch/containers/account_container';
+import StatusContainer from 'themes/glitch/containers/status_container';
import { Link } from 'react-router-dom';
import ImmutablePureComponent from 'react-immutable-pure-component';
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
-import IconButton from '../../../components/icon_button';
-import Motion from '../../ui/util/optional_motion';
+import IconButton from 'themes/glitch/components/icon_button';
+import Motion from 'themes/glitch/util/optional_motion';
import spring from 'react-motion/lib/spring';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { defineMessages, injectIntl } from 'react-intl';
import React from 'react';
-import IconButton from '../../../components/icon_button';
+import IconButton from 'themes/glitch/components/icon_button';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import { connect } from 'react-redux';
import React from 'react';
import PropTypes from 'prop-types';
-import Motion from '../../ui/util/optional_motion';
+import Motion from 'themes/glitch/util/optional_motion';
import spring from 'react-motion/lib/spring';
import { FormattedMessage } from 'react-intl';
import React from 'react';
import PropTypes from 'prop-types';
-import Motion from '../../ui/util/optional_motion';
+import Motion from 'themes/glitch/util/optional_motion';
import spring from 'react-motion/lib/spring';
export default class Warning extends React.PureComponent {
--- /dev/null
+// Package imports.
+import { connect } from 'react-redux';
+
+// Our imports.
+import { toggleComposeAdvancedOption } from 'themes/glitch/actions/compose';
+import ComposeAdvancedOptions from '../components/advanced_options';
+
+const mapStateToProps = state => ({
+ values: state.getIn(['compose', 'advanced_options']),
+});
+
+const mapDispatchToProps = dispatch => ({
+
+ onChange (option) {
+ dispatch(toggleComposeAdvancedOption(option));
+ },
+
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(ComposeAdvancedOptions);
import { connect } from 'react-redux';
import AutosuggestAccount from '../components/autosuggest_account';
-import { makeGetAccount } from '../../../selectors';
+import { makeGetAccount } from 'themes/glitch/selectors';
const makeMapStateToProps = () => {
const getAccount = makeGetAccount();
import { connect } from 'react-redux';
import ComposeForm from '../components/compose_form';
-import { changeComposeVisibility, uploadCompose } from '../../../actions/compose';
+import { changeComposeVisibility, uploadCompose } from 'themes/glitch/actions/compose';
import {
changeCompose,
submitCompose,
selectComposeSuggestion,
changeComposeSpoilerText,
insertEmojiCompose,
-} from '../../../actions/compose';
+} from 'themes/glitch/actions/compose';
const mapStateToProps = state => ({
text: state.getIn(['compose', 'text']),
import { connect } from 'react-redux';
import EmojiPickerDropdown from '../components/emoji_picker_dropdown';
-import { changeSetting } from '../../../actions/settings';
+import { changeSetting } from 'themes/glitch/actions/settings';
import { createSelector } from 'reselect';
import { Map as ImmutableMap } from 'immutable';
-import { useEmoji } from '../../../actions/emojis';
+import { useEmoji } from 'themes/glitch/actions/emojis';
const perLine = 8;
const lines = 2;
import { connect } from 'react-redux';
import NavigationBar from '../components/navigation_bar';
-import { me } from '../../../initial_state';
+import { me } from 'themes/glitch/util/initial_state';
const mapStateToProps = state => {
return {
import { connect } from 'react-redux';
import PrivacyDropdown from '../components/privacy_dropdown';
-import { changeComposeVisibility } from '../../../actions/compose';
-import { openModal, closeModal } from '../../../actions/modal';
-import { isUserTouching } from '../../../is_mobile';
+import { changeComposeVisibility } from 'themes/glitch/actions/compose';
+import { openModal, closeModal } from 'themes/glitch/actions/modal';
+import { isUserTouching } from 'themes/glitch/util/is_mobile';
const mapStateToProps = state => ({
isModalOpen: state.get('modal').modalType === 'ACTIONS',
import { connect } from 'react-redux';
-import { cancelReplyCompose } from '../../../actions/compose';
-import { makeGetStatus } from '../../../selectors';
+import { cancelReplyCompose } from 'themes/glitch/actions/compose';
+import { makeGetStatus } from 'themes/glitch/selectors';
import ReplyIndicator from '../components/reply_indicator';
const makeMapStateToProps = () => {
clearSearch,
submitSearch,
showSearch,
-} from '../../../actions/search';
+} from 'themes/glitch/actions/search';
import Search from '../components/search';
const mapStateToProps = state => ({
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import classNames from 'classnames';
-import IconButton from '../../../components/icon_button';
-import { changeComposeSensitivity } from '../../../actions/compose';
-import Motion from '../../ui/util/optional_motion';
+import IconButton from 'themes/glitch/components/icon_button';
+import { changeComposeSensitivity } from 'themes/glitch/actions/compose';
+import Motion from 'themes/glitch/util/optional_motion';
import spring from 'react-motion/lib/spring';
import { injectIntl, defineMessages } from 'react-intl';
import { connect } from 'react-redux';
import TextIconButton from '../components/text_icon_button';
-import { changeComposeSpoilerness } from '../../../actions/compose';
+import { changeComposeSpoilerness } from 'themes/glitch/actions/compose';
import { injectIntl, defineMessages } from 'react-intl';
const messages = defineMessages({
import { connect } from 'react-redux';
import UploadButton from '../components/upload_button';
-import { uploadCompose } from '../../../actions/compose';
+import { uploadCompose } from 'themes/glitch/actions/compose';
const mapStateToProps = state => ({
disabled: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size > 3 || state.getIn(['compose', 'media_attachments']).some(m => m.get('type') === 'video')),
import { connect } from 'react-redux';
import Upload from '../components/upload';
-import { undoUploadCompose, changeUploadCompose } from '../../../actions/compose';
+import { undoUploadCompose, changeUploadCompose } from 'themes/glitch/actions/compose';
const mapStateToProps = (state, { id }) => ({
media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
import Warning from '../components/warning';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
-import { me } from '../../../initial_state';
+import { me } from 'themes/glitch/util/initial_state';
const mapStateToProps = state => ({
needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', me, 'locked']),
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
-import { mountCompose, unmountCompose } from '../../actions/compose';
-import { openModal } from '../../actions/modal';
-import { changeLocalSetting } from '../../../glitch/actions/local_settings';
+import { mountCompose, unmountCompose } from 'themes/glitch/actions/compose';
+import { openModal } from 'themes/glitch/actions/modal';
+import { changeLocalSetting } from 'themes/glitch/actions/local_settings';
import { Link } from 'react-router-dom';
import { injectIntl, defineMessages } from 'react-intl';
import SearchContainer from './containers/search_container';
-import Motion from '../ui/util/optional_motion';
+import Motion from 'themes/glitch/util/optional_motion';
import spring from 'react-motion/lib/spring';
import SearchResultsContainer from './containers/search_results_container';
-import { changeComposing } from '../../actions/compose';
+import { changeComposing } from 'themes/glitch/actions/compose';
const messages = defineMessages({
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
import { connect } from 'react-redux';
-import ColumnSettings from '../../community_timeline/components/column_settings';
-import { changeSetting } from '../../../actions/settings';
+import ColumnSettings from 'themes/glitch/features/community_timeline/components/column_settings';
+import { changeSetting } from 'themes/glitch/actions/settings';
const mapStateToProps = state => ({
settings: state.getIn(['settings', 'direct']),
import React from 'react';
import { connect } from 'react-redux';
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 StatusListContainer from 'themes/glitch/features/ui/containers/status_list_container';
+import Column from 'themes/glitch/components/column';
+import ColumnHeader from 'themes/glitch/components/column_header';
import {
refreshDirectTimeline,
expandDirectTimeline,
-} from '../../actions/timelines';
-import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
+} from 'themes/glitch/actions/timelines';
+import { addColumn, removeColumn, moveColumn } from 'themes/glitch/actions/columns';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ColumnSettingsContainer from './containers/column_settings_container';
-import { connectDirectStream } from '../../actions/streaming';
+import { connectDirectStream } from 'themes/glitch/actions/streaming';
const messages = defineMessages({
title: { id: 'column.direct', defaultMessage: 'Direct messages' },
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
-import { fetchFavouritedStatuses, expandFavouritedStatuses } from '../../actions/favourites';
-import Column from '../ui/components/column';
-import ColumnHeader from '../../components/column_header';
-import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
-import StatusList from '../../components/status_list';
+import { fetchFavouritedStatuses, expandFavouritedStatuses } from 'themes/glitch/actions/favourites';
+import Column from 'themes/glitch/features/ui/components/column';
+import ColumnHeader from 'themes/glitch/components/column_header';
+import { addColumn, removeColumn, moveColumn } from 'themes/glitch/actions/columns';
+import StatusList from 'themes/glitch/components/status_list';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
-import LoadingIndicator from '../../components/loading_indicator';
-import { fetchFavourites } from '../../actions/interactions';
+import LoadingIndicator from 'themes/glitch/components/loading_indicator';
+import { fetchFavourites } from 'themes/glitch/actions/interactions';
import { ScrollContainer } from 'react-router-scroll-4';
-import AccountContainer from '../../containers/account_container';
-import Column from '../ui/components/column';
-import ColumnBackButton from '../../components/column_back_button';
+import AccountContainer from 'themes/glitch/containers/account_container';
+import Column from 'themes/glitch/features/ui/components/column';
+import ColumnBackButton from 'themes/glitch/components/column_back_button';
import ImmutablePureComponent from 'react-immutable-pure-component';
const mapStateToProps = (state, props) => ({
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
-import Permalink from '../../../components/permalink';
-import Avatar from '../../../components/avatar';
-import DisplayName from '../../../components/display_name';
-import IconButton from '../../../components/icon_button';
+import Permalink from 'themes/glitch/components/permalink';
+import Avatar from 'themes/glitch/components/avatar';
+import DisplayName from 'themes/glitch/components/display_name';
+import IconButton from 'themes/glitch/components/icon_button';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
-import { makeGetAccount } from '../../../selectors';
+import { makeGetAccount } from 'themes/glitch/selectors';
import AccountAuthorize from '../components/account_authorize';
-import { authorizeFollowRequest, rejectFollowRequest } from '../../../actions/accounts';
+import { authorizeFollowRequest, rejectFollowRequest } from 'themes/glitch/actions/accounts';
const makeMapStateToProps = () => {
const getAccount = makeGetAccount();
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
-import LoadingIndicator from '../../components/loading_indicator';
+import LoadingIndicator from 'themes/glitch/components/loading_indicator';
import { ScrollContainer } from 'react-router-scroll-4';
-import Column from '../ui/components/column';
-import ColumnBackButtonSlim from '../../components/column_back_button_slim';
+import Column from 'themes/glitch/features/ui/components/column';
+import ColumnBackButtonSlim from 'themes/glitch/components/column_back_button_slim';
import AccountAuthorizeContainer from './containers/account_authorize_container';
-import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts';
+import { fetchFollowRequests, expandFollowRequests } from 'themes/glitch/actions/accounts';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
-import LoadingIndicator from '../../components/loading_indicator';
+import LoadingIndicator from 'themes/glitch/components/loading_indicator';
import {
fetchAccount,
fetchFollowers,
expandFollowers,
-} from '../../actions/accounts';
+} from 'themes/glitch/actions/accounts';
import { ScrollContainer } from 'react-router-scroll-4';
-import AccountContainer from '../../containers/account_container';
-import Column from '../ui/components/column';
-import HeaderContainer from '../account_timeline/containers/header_container';
-import LoadMore from '../../components/load_more';
-import ColumnBackButton from '../../components/column_back_button';
+import AccountContainer from 'themes/glitch/containers/account_container';
+import Column from 'themes/glitch/features/ui/components/column';
+import HeaderContainer from 'themes/glitch/features/account_timeline/containers/header_container';
+import LoadMore from 'themes/glitch/components/load_more';
+import ColumnBackButton from 'themes/glitch/components/column_back_button';
import ImmutablePureComponent from 'react-immutable-pure-component';
const mapStateToProps = (state, props) => ({
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
-import LoadingIndicator from '../../components/loading_indicator';
+import LoadingIndicator from 'themes/glitch/components/loading_indicator';
import {
fetchAccount,
fetchFollowing,
expandFollowing,
-} from '../../actions/accounts';
+} from 'themes/glitch/actions/accounts';
import { ScrollContainer } from 'react-router-scroll-4';
-import AccountContainer from '../../containers/account_container';
-import Column from '../ui/components/column';
-import HeaderContainer from '../account_timeline/containers/header_container';
-import LoadMore from '../../components/load_more';
-import ColumnBackButton from '../../components/column_back_button';
+import AccountContainer from 'themes/glitch/containers/account_container';
+import Column from 'themes/glitch/features/ui/components/column';
+import HeaderContainer from 'themes/glitch/features/account_timeline/containers/header_container';
+import LoadMore from 'themes/glitch/components/load_more';
+import ColumnBackButton from 'themes/glitch/components/column_back_button';
import ImmutablePureComponent from 'react-immutable-pure-component';
const mapStateToProps = (state, props) => ({
import React from 'react';
-import Column from '../ui/components/column';
-import MissingIndicator from '../../components/missing_indicator';
+import Column from 'themes/glitch/features/ui/components/column';
+import MissingIndicator from 'themes/glitch/components/missing_indicator';
const GenericNotFound = () => (
<Column>
import React from 'react';
-import Column from '../ui/components/column';
-import ColumnLink from '../ui/components/column_link';
-import ColumnSubheading from '../ui/components/column_subheading';
+import Column from 'themes/glitch/features/ui/components/column';
+import ColumnLink from 'themes/glitch/features/ui/components/column_link';
+import ColumnSubheading from 'themes/glitch/features/ui/components/column_subheading';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
-import { openModal } from '../../actions/modal';
+import { openModal } from 'themes/glitch/actions/modal';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
-import { me } from '../../initial_state';
+import { me } from 'themes/glitch/util/initial_state';
const messages = defineMessages({
heading: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
import React from 'react';
import { connect } from 'react-redux';
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 StatusListContainer from 'themes/glitch/features/ui/containers/status_list_container';
+import Column from 'themes/glitch/components/column';
+import ColumnHeader from 'themes/glitch/components/column_header';
import {
refreshHashtagTimeline,
expandHashtagTimeline,
-} from '../../actions/timelines';
-import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
+} from 'themes/glitch/actions/timelines';
+import { addColumn, removeColumn, moveColumn } from 'themes/glitch/actions/columns';
import { FormattedMessage } from 'react-intl';
-import { connectHashtagStream } from '../../actions/streaming';
+import { connectHashtagStream } from 'themes/glitch/actions/streaming';
const mapStateToProps = (state, props) => ({
hasUnread: state.getIn(['timelines', `hashtag:${props.params.id}`, 'unread']) > 0,
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import SettingToggle from '../../notifications/components/setting_toggle';
-import SettingText from '../../../components/setting_text';
+import SettingToggle from 'themes/glitch/features/notifications/components/setting_toggle';
+import SettingText from 'themes/glitch/components/setting_text';
const messages = defineMessages({
filter_regex: { id: 'home.column_settings.filter_regex', defaultMessage: 'Filter out by regular expressions' },
import { connect } from 'react-redux';
import ColumnSettings from '../components/column_settings';
-import { changeSetting, saveSettings } from '../../../actions/settings';
+import { changeSetting, saveSettings } from 'themes/glitch/actions/settings';
const mapStateToProps = state => ({
settings: state.getIn(['settings', 'home']),
import React from 'react';
import { connect } from 'react-redux';
-import { expandHomeTimeline } from '../../actions/timelines';
+import { expandHomeTimeline } from 'themes/glitch/actions/timelines';
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 { addColumn, removeColumn, moveColumn } from '../../actions/columns';
+import StatusListContainer from 'themes/glitch/features/ui/containers/status_list_container';
+import Column from 'themes/glitch/components/column';
+import ColumnHeader from 'themes/glitch/components/column_header';
+import { addColumn, removeColumn, moveColumn } from 'themes/glitch/actions/columns';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ColumnSettingsContainer from './containers/column_settings_container';
import { Link } from 'react-router-dom';
-// Package imports
+// Package imports.
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
+import { connect } from 'react-redux';
// Our imports
import LocalSettingsPage from './page';
import LocalSettingsNavigation from './navigation';
+import { closeModal } from 'themes/glitch/actions/modal';
+import { changeLocalSetting } from 'themes/glitch/actions/local_settings';
// Stylesheet imports
import './style.scss';
-export default class LocalSettings extends React.PureComponent {
+const mapStateToProps = state => ({
+ settings: state.get('local_settings'),
+});
+
+const mapDispatchToProps = dispatch => ({
+ onChange (setting, value) {
+ dispatch(changeLocalSetting(setting, value));
+ },
+ onClose () {
+ dispatch(closeModal());
+ },
+});
+
+class LocalSettings extends React.PureComponent {
static propTypes = {
onChange: PropTypes.func.isRequired,
}
}
+
+export default connect(mapStateToProps, mapDispatchToProps)(LocalSettings);
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
-import LoadingIndicator from '../../components/loading_indicator';
+import LoadingIndicator from 'themes/glitch/components/loading_indicator';
import { ScrollContainer } from 'react-router-scroll-4';
-import Column from '../ui/components/column';
-import ColumnBackButtonSlim from '../../components/column_back_button_slim';
-import AccountContainer from '../../containers/account_container';
-import { fetchMutes, expandMutes } from '../../actions/mutes';
+import Column from 'themes/glitch/features/ui/components/column';
+import ColumnBackButtonSlim from 'themes/glitch/components/column_back_button_slim';
+import AccountContainer from 'themes/glitch/containers/account_container';
+import { fetchMutes, expandMutes } from 'themes/glitch/actions/mutes';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
--- /dev/null
+// Package imports.
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import { FormattedMessage } from 'react-intl';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { HotKeys } from 'react-hotkeys';
+
+// Our imports.
+import Permalink from 'themes/glitch/components/permalink';
+import AccountContainer from 'themes/glitch/containers/account_container';
+import NotificationOverlayContainer from '../containers/overlay_container';
+
+export default class NotificationFollow extends ImmutablePureComponent {
+
+ static propTypes = {
+ id: PropTypes.string.isRequired,
+ account: ImmutablePropTypes.map.isRequired,
+ notification: ImmutablePropTypes.map.isRequired,
+ };
+
+ handleMoveUp = () => {
+ const { notification, onMoveUp } = this.props;
+ onMoveUp(notification.get('id'));
+ }
+
+ handleMoveDown = () => {
+ const { notification, onMoveDown } = this.props;
+ onMoveDown(notification.get('id'));
+ }
+
+ handleOpen = () => {
+ this.handleOpenProfile();
+ }
+
+ handleOpenProfile = () => {
+ const { notification } = this.props;
+ this.context.router.history.push(`/accounts/${notification.getIn(['account', 'id'])}`);
+ }
+
+ handleMention = e => {
+ e.preventDefault();
+
+ const { notification, onMention } = this.props;
+ onMention(notification.get('account'), this.context.router.history);
+ }
+
+ getHandlers () {
+ return {
+ moveUp: this.handleMoveUp,
+ moveDown: this.handleMoveDown,
+ open: this.handleOpen,
+ openProfile: this.handleOpenProfile,
+ mention: this.handleMention,
+ reply: this.handleMention,
+ };
+ }
+
+ render () {
+ const { account, notification } = this.props;
+
+ // Links to the display name.
+ const displayName = account.get('display_name_html') || account.get('username');
+ const link = (
+ <Permalink
+ className='notification__display-name'
+ href={account.get('url')}
+ title={account.get('acct')}
+ to={`/accounts/${account.get('id')}`}
+ dangerouslySetInnerHTML={{ __html: displayName }}
+ />
+ );
+
+ // Renders.
+ return (
+ <HotKeys handlers={this.getHandlers()}>
+ <div className='notification notification-follow focusable' tabIndex='0'>
+ <div className='notification__message'>
+ <div className='notification__favourite-icon-wrapper'>
+ <i className='fa fa-fw fa-user-plus' />
+ </div>
+
+ <FormattedMessage
+ id='notification.follow'
+ defaultMessage='{name} followed you'
+ values={{ name: link }}
+ />
+ </div>
+
+ <AccountContainer id={account.get('id')} withNote={false} />
+ <NotificationOverlayContainer notification={notification} />
+ </div>
+ </HotKeys>
+ );
+ }
+
+}
-// Package imports //
+// Package imports.
import React from 'react';
+import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
-// Mastodon imports //
-
-// Our imports //
-import StatusContainer from '../status/container';
+// Our imports,
+import StatusContainer from 'themes/glitch/containers/status_container';
import NotificationFollow from './follow';
export default class Notification extends ImmutablePureComponent {
static propTypes = {
notification: ImmutablePropTypes.map.isRequired,
+ hidden: PropTypes.bool,
+ onMoveUp: PropTypes.func.isRequired,
+ onMoveDown: PropTypes.func.isRequired,
+ onMention: PropTypes.func.isRequired,
settings: ImmutablePropTypes.map.isRequired,
};
- renderFollow (notification) {
+ renderFollow () {
+ const { notification } = this.props;
return (
<NotificationFollow
id={notification.get('id')}
);
}
- renderMention (notification) {
+ renderMention () {
+ const { notification } = this.props;
return (
<StatusContainer
id={notification.get('status')}
);
}
- renderFavourite (notification) {
+ renderFavourite () {
+ const { notification } = this.props;
return (
<StatusContainer
id={notification.get('status')}
);
}
- renderReblog (notification) {
+ renderReblog () {
+ const { notification } = this.props;
return (
<StatusContainer
id={notification.get('status')}
render () {
const { notification } = this.props;
-
switch(notification.get('type')) {
case 'follow':
- return this.renderFollow(notification);
+ return this.renderFollow();
case 'mention':
- return this.renderMention(notification);
+ return this.renderMention();
case 'favourite':
- return this.renderFavourite(notification);
+ return this.renderFavourite();
case 'reblog':
- return this.renderReblog(notification);
+ return this.renderReblog();
+ default:
+ return null;
}
-
- return null;
}
}
*/
-// Package imports //
+// Package imports.
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { defineMessages, injectIntl } from 'react-intl';
-// Mastodon imports //
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-
const messages = defineMessages({
markForDeletion: { id: 'notification.markForDeletion', defaultMessage: 'Mark for deletion' },
});
import { connect } from 'react-redux';
import { defineMessages, injectIntl } from 'react-intl';
import ColumnSettings from '../components/column_settings';
-import { changeSetting, saveSettings } from '../../../actions/settings';
-import { clearNotifications } from '../../../actions/notifications';
-import { changeAlerts as changePushNotifications, saveSettings as savePushNotificationSettings } from '../../../actions/push_notifications';
-import { openModal } from '../../../actions/modal';
+import { changeSetting, saveSettings } from 'themes/glitch/actions/settings';
+import { clearNotifications } from 'themes/glitch/actions/notifications';
+import { changeAlerts as changePushNotifications, saveSettings as savePushNotificationSettings } from 'themes/glitch/actions/push_notifications';
+import { openModal } from 'themes/glitch/actions/modal';
const messages = defineMessages({
clearMessage: { id: 'notifications.clear_confirmation', defaultMessage: 'Are you sure you want to permanently clear all your notifications?' },
-// THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !!
-// SEE INSTEAD : glitch/components/notification/container
-
+// Package imports.
import { connect } from 'react-redux';
-import { makeGetNotification } from '../../../selectors';
+
+// Our imports.
+import { makeGetNotification } from 'themes/glitch/selectors';
import Notification from '../components/notification';
-import { mentionCompose } from '../../../actions/compose';
+import { mentionCompose } from 'themes/glitch/actions/compose';
const makeMapStateToProps = () => {
const getNotification = makeGetNotification();
const mapStateToProps = (state, props) => ({
notification: getNotification(state, props.notification, props.accountId),
+ settings: state.get('local_settings'),
+ notifCleaning: state.getIn(['notifications', 'cleaningMode']),
});
return mapStateToProps;
--- /dev/null
+// Package imports.
+import { connect } from 'react-redux';
+
+// Our imports.
+import NotificationOverlay from '../components/overlay';
+import { markNotificationForDelete } from 'themes/glitch/actions/notifications';
+
+const mapDispatchToProps = dispatch => ({
+ onMarkForDelete(id, yes) {
+ dispatch(markNotificationForDelete(id, yes));
+ },
+});
+
+const mapStateToProps = state => ({
+ show: state.getIn(['notifications', 'cleaningMode']),
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(NotificationOverlay);
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
-import Column from '../../components/column';
-import ColumnHeader from '../../components/column_header';
+import Column from 'themes/glitch/components/column';
+import ColumnHeader from 'themes/glitch/components/column_header';
import {
enterNotificationClearingMode,
expandNotifications,
scrollTopNotifications,
-} from '../../actions/notifications';
-import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
-import NotificationContainer from '../../../glitch/components/notification/container';
+} from 'themes/glitch/actions/notifications';
+import { addColumn, removeColumn, moveColumn } from 'themes/glitch/actions/columns';
+import NotificationContainer from './containers/notification_container';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ColumnSettingsContainer from './containers/column_settings_container';
import { createSelector } from 'reselect';
import { List as ImmutableList } from 'immutable';
import { debounce } from 'lodash';
-import ScrollableList from '../../components/scrollable_list';
+import ScrollableList from 'themes/glitch/components/scrollable_list';
const messages = defineMessages({
title: { id: 'column.notifications', defaultMessage: 'Notifications' },
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
-import { fetchPinnedStatuses } from '../../actions/pin_statuses';
-import Column from '../ui/components/column';
-import ColumnBackButtonSlim from '../../components/column_back_button_slim';
-import StatusList from '../../components/status_list';
+import { fetchPinnedStatuses } from 'themes/glitch/actions/pin_statuses';
+import Column from 'themes/glitch/features/ui/components/column';
+import ColumnBackButtonSlim from 'themes/glitch/components/column_back_button_slim';
+import StatusList from 'themes/glitch/components/status_list';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
-import ColumnSettings from '../../community_timeline/components/column_settings';
-import { changeSetting } from '../../../actions/settings';
+import ColumnSettings from 'themes/glitch/features/community_timeline/components/column_settings';
+import { changeSetting } from 'themes/glitch/actions/settings';
const mapStateToProps = state => ({
settings: state.getIn(['settings', 'public']),
import React from 'react';
import { connect } from 'react-redux';
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 StatusListContainer from 'themes/glitch/features/ui/containers/status_list_container';
+import Column from 'themes/glitch/components/column';
+import ColumnHeader from 'themes/glitch/components/column_header';
import {
refreshPublicTimeline,
expandPublicTimeline,
-} from '../../actions/timelines';
-import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
+} from 'themes/glitch/actions/timelines';
+import { addColumn, removeColumn, moveColumn } from 'themes/glitch/actions/columns';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ColumnSettingsContainer from './containers/column_settings_container';
-import { connectPublicStream } from '../../actions/streaming';
+import { connectPublicStream } from 'themes/glitch/actions/streaming';
const messages = defineMessages({
title: { id: 'column.public', defaultMessage: 'Federated timeline' },
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
-import LoadingIndicator from '../../components/loading_indicator';
-import { fetchReblogs } from '../../actions/interactions';
+import LoadingIndicator from 'themes/glitch/components/loading_indicator';
+import { fetchReblogs } from 'themes/glitch/actions/interactions';
import { ScrollContainer } from 'react-router-scroll-4';
-import AccountContainer from '../../containers/account_container';
-import Column from '../ui/components/column';
-import ColumnBackButton from '../../components/column_back_button';
+import AccountContainer from 'themes/glitch/containers/account_container';
+import Column from 'themes/glitch/features/ui/components/column';
+import ColumnBackButton from 'themes/glitch/components/column_back_button';
import ImmutablePureComponent from 'react-immutable-pure-component';
const mapStateToProps = (state, props) => ({
import { connect } from 'react-redux';
import StatusCheckBox from '../components/status_check_box';
-import { toggleStatusReport } from '../../../actions/reports';
+import { toggleStatusReport } from 'themes/glitch/actions/reports';
import { Set as ImmutableSet } from 'immutable';
const mapStateToProps = (state, { id }) => ({
--- /dev/null
+import React from 'react';
+import ComposeFormContainer from 'themes/glitch/features/compose/containers/compose_form_container';
+import NotificationsContainer from 'themes/glitch/features/ui/containers/notifications_container';
+import LoadingBarContainer from 'themes/glitch/features/ui/containers/loading_bar_container';
+import ModalContainer from 'themes/glitch/features/ui/containers/modal_container';
+
+export default class Compose extends React.PureComponent {
+
+ render () {
+ return (
+ <div>
+ <ComposeFormContainer />
+ <NotificationsContainer />
+ <ModalContainer />
+ <LoadingBarContainer className='loading-bar' />
+ </div>
+ );
+ }
+
+}
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
-import StatusListContainer from '../../ui/containers/status_list_container';
+import StatusListContainer from 'themes/glitch/features/ui/containers/status_list_container';
import {
refreshHashtagTimeline,
expandHashtagTimeline,
-} from '../../../actions/timelines';
-import Column from '../../../components/column';
-import ColumnHeader from '../../../components/column_header';
+} from 'themes/glitch/actions/timelines';
+import Column from 'themes/glitch/components/column';
+import ColumnHeader from 'themes/glitch/components/column_header';
@connect()
export default class HashtagTimeline extends React.PureComponent {
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
-import StatusListContainer from '../../ui/containers/status_list_container';
+import StatusListContainer from 'themes/glitch/features/ui/containers/status_list_container';
import {
refreshPublicTimeline,
expandPublicTimeline,
-} from '../../../actions/timelines';
-import Column from '../../../components/column';
-import ColumnHeader from '../../../components/column_header';
+} from 'themes/glitch/actions/timelines';
+import Column from 'themes/glitch/components/column';
+import ColumnHeader from 'themes/glitch/components/column_header';
import { defineMessages, injectIntl } from 'react-intl';
const messages = defineMessages({
import React from 'react';
import PropTypes from 'prop-types';
-import IconButton from '../../../components/icon_button';
+import IconButton from 'themes/glitch/components/icon_button';
import ImmutablePropTypes from 'react-immutable-proptypes';
-import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
+import DropdownMenuContainer from 'themes/glitch/containers/dropdown_menu_container';
import { defineMessages, injectIntl } from 'react-intl';
-import { me } from '../../../initial_state';
+import { me } from 'themes/glitch/util/initial_state';
const messages = defineMessages({
delete: { id: 'status.delete', defaultMessage: 'Delete' },
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
-import Avatar from '../../../components/avatar';
-import DisplayName from '../../../components/display_name';
-import StatusContent from '../../../../glitch/components/status/content';
-import StatusGallery from '../../../../glitch/components/status/gallery';
-import StatusPlayer from '../../../../glitch/components/status/player';
-import AttachmentList from '../../../components/attachment_list';
+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 '../../video';
-import VisibilityIcon from '../../../../glitch/components/status/visibility_icon';
+import Video from 'themes/glitch/features/video';
+import VisibilityIcon from 'themes/glitch/components/status_visibility_icon';
export default class DetailedStatus extends ImmutablePureComponent {
media = <AttachmentList media={status.get('media_attachments')} />;
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
media = (
- <StatusPlayer
+ <Video
sensitive={status.get('sensitive')}
media={status.getIn(['media_attachments', 0])}
letterbox={settings.getIn(['media', 'letterbox'])}
fullwidth={settings.getIn(['media', 'fullwidth'])}
- height={250}
onOpenVideo={this.props.onOpenVideo}
autoplay
/>
sensitive={status.get('sensitive')}
media={status.get('media_attachments')}
letterbox={settings.getIn(['media', 'letterbox'])}
- fullwidth={settings.getIn(['media', 'fullwidth'])}
- height={250}
onOpenMedia={this.props.onOpenMedia}
/>
);
import PropTypes from 'prop-types';
import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes';
-import { fetchStatus } from '../../actions/statuses';
-import MissingIndicator from '../../components/missing_indicator';
+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 '../ui/components/column';
+import Column from 'themes/glitch/features/ui/components/column';
import {
favourite,
unfavourite,
unreblog,
pin,
unpin,
-} from '../../actions/interactions';
+} from 'themes/glitch/actions/interactions';
import {
replyCompose,
mentionCompose,
-} from '../../actions/compose';
-import { deleteStatus } from '../../actions/statuses';
-import { initReport } from '../../actions/reports';
-import { makeGetStatus } from '../../selectors';
+} 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 '../../components/column_back_button';
-import StatusContainer from '../../../glitch/components/status/container';
-import { openModal } from '../../actions/modal';
+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 '../../initial_state';
-import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../../features/ui/util/fullscreen';
+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' },
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
-import StatusContent from '../../../components/status_content';
-import Avatar from '../../../components/avatar';
-import RelativeTimestamp from '../../../components/relative_timestamp';
-import DisplayName from '../../../components/display_name';
-import IconButton from '../../../components/icon_button';
+import StatusContent from 'themes/glitch/components/status_content';
+import Avatar from 'themes/glitch/components/avatar';
+import RelativeTimestamp from 'themes/glitch/components/relative_timestamp';
+import DisplayName from 'themes/glitch/components/display_name';
+import IconButton from 'themes/glitch/components/icon_button';
import classNames from 'classnames';
export default class ActionsModal extends ImmutablePureComponent {
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import Button from '../../../components/button';
-import StatusContent from '../../../../glitch/components/status/content';
-import Avatar from '../../../components/avatar';
-import RelativeTimestamp from '../../../components/relative_timestamp';
-import DisplayName from '../../../components/display_name';
+import Button from 'themes/glitch/components/button';
+import StatusContent from 'themes/glitch/components/status_content';
+import Avatar from 'themes/glitch/components/avatar';
+import RelativeTimestamp from 'themes/glitch/components/relative_timestamp';
+import DisplayName from 'themes/glitch/components/display_name';
import ImmutablePureComponent from 'react-immutable-pure-component';
const messages = defineMessages({
import Column from './column';
import ColumnHeader from './column_header';
-import ColumnBackButtonSlim from '../../../components/column_back_button_slim';
-import IconButton from '../../../components/icon_button';
+import ColumnBackButtonSlim from 'themes/glitch/components/column_back_button_slim';
+import IconButton from 'themes/glitch/components/icon_button';
const messages = defineMessages({
title: { id: 'bundle_column_error.title', defaultMessage: 'Network error' },
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
-import IconButton from '../../../components/icon_button';
+import IconButton from 'themes/glitch/components/icon_button';
const messages = defineMessages({
error: { id: 'bundle_modal_error.message', defaultMessage: 'Something went wrong while loading this component.' },
import ColumnHeader from './column_header';
import PropTypes from 'prop-types';
import { debounce } from 'lodash';
-import { scrollTop } from '../../../scroll';
-import { isMobile } from '../../../is_mobile';
+import { scrollTop } from 'themes/glitch/util/scroll';
+import { isMobile } from 'themes/glitch/util/is_mobile';
export default class Column extends React.PureComponent {
import React from 'react';
import PropTypes from 'prop-types';
-import Column from '../../../components/column';
-import ColumnHeader from '../../../components/column_header';
+import Column from 'themes/glitch/components/column';
+import ColumnHeader from 'themes/glitch/components/column_header';
import ImmutablePureComponent from 'react-immutable-pure-component';
export default class ColumnLoading extends ImmutablePureComponent {
import ColumnLoading from './column_loading';
import DrawerLoading from './drawer_loading';
import BundleColumnError from './bundle_column_error';
-import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, DirectTimeline, FavouritedStatuses } from '../../ui/util/async-components';
+import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, DirectTimeline, FavouritedStatuses } from 'themes/glitch/util/async-components';
import detectPassiveEvents from 'detect-passive-events';
-import { scrollRight } from '../../../scroll';
+import { scrollRight } from 'themes/glitch/util/scroll';
const componentMap = {
'COMPOSE': Compose,
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, FormattedMessage } from 'react-intl';
-import Button from '../../../components/button';
+import Button from 'themes/glitch/components/button';
@injectIntl
export default class ConfirmationModal extends React.PureComponent {
import React from 'react';
import PropTypes from 'prop-types';
-import Button from '../../../components/button';
+import Button from 'themes/glitch/components/button';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Atrament from 'atrament'; // the doodling library
import { connect } from 'react-redux';
import ImmutablePropTypes from 'react-immutable-proptypes';
-import { doodleSet, uploadCompose } from '../../../actions/compose';
-import IconButton from '../../../components/icon_button';
+import { doodleSet, uploadCompose } from 'themes/glitch/actions/compose';
+import IconButton from 'themes/glitch/components/icon_button';
import { debounce, mapValues } from 'lodash';
import classNames from 'classnames';
import ReactSwipeableViews from 'react-swipeable-views';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
-import ExtendedVideoPlayer from '../../../components/extended_video_player';
+import ExtendedVideoPlayer from 'themes/glitch/components/extended_video_player';
import { defineMessages, injectIntl } from 'react-intl';
-import IconButton from '../../../components/icon_button';
+import IconButton from 'themes/glitch/components/icon_button';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImageLoader from './image_loader';
import React from 'react';
-import LoadingIndicator from '../../../components/loading_indicator';
+import LoadingIndicator from 'themes/glitch/components/loading_indicator';
// Keep the markup in sync with <BundleModalError />
// (make sure they have the same dimensions)
ReportModal,
SettingsModal,
EmbedModal,
-} from '../../../features/ui/util/async-components';
+} from 'themes/glitch/util/async-components';
const MODAL_COMPONENTS = {
'MEDIA': () => Promise.resolve({ default: MediaModal }),
import PropTypes from 'prop-types';
import { injectIntl, FormattedMessage } from 'react-intl';
import Toggle from 'react-toggle';
-import Button from '../../../components/button';
-import { closeModal } from '../../../actions/modal';
-import { muteAccount } from '../../../actions/accounts';
-import { toggleHideNotifications } from '../../../actions/mutes';
+import Button from 'themes/glitch/components/button';
+import { closeModal } from 'themes/glitch/actions/modal';
+import { muteAccount } from 'themes/glitch/actions/accounts';
+import { toggleHideNotifications } from 'themes/glitch/actions/mutes';
const mapStateToProps = state => {
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ReactSwipeableViews from 'react-swipeable-views';
import classNames from 'classnames';
-import Permalink from '../../../components/permalink';
-import ComposeForm from '../../compose/components/compose_form';
-import Search from '../../compose/components/search';
-import NavigationBar from '../../compose/components/navigation_bar';
+import Permalink from 'themes/glitch/components/permalink';
+import ComposeForm from 'themes/glitch/features/compose/components/compose_form';
+import Search from 'themes/glitch/features/compose/components/search';
+import NavigationBar from 'themes/glitch/features/compose/components/navigation_bar';
import ColumnHeader from './column_header';
import {
List as ImmutableList,
Map as ImmutableMap,
} from 'immutable';
-import { me } from '../../../initial_state';
+import { me } from 'themes/glitch/util/initial_state';
const noop = () => { };
import React from 'react';
import { connect } from 'react-redux';
-import { changeReportComment, submitReport } from '../../../actions/reports';
-import { refreshAccountTimeline } from '../../../actions/timelines';
+import { changeReportComment, submitReport } from 'themes/glitch/actions/reports';
+import { refreshAccountTimeline } from 'themes/glitch/actions/timelines';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
-import { makeGetAccount } from '../../../selectors';
+import { makeGetAccount } from 'themes/glitch/selectors';
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
-import StatusCheckBox from '../../report/containers/status_check_box_container';
+import StatusCheckBox from 'themes/glitch/features/report/containers/status_check_box_container';
import { OrderedSet } from 'immutable';
import ImmutablePureComponent from 'react-immutable-pure-component';
-import Button from '../../../components/button';
+import Button from 'themes/glitch/components/button';
const messages = defineMessages({
placeholder: { id: 'report.placeholder', defaultMessage: 'Additional comments' },
import { NavLink } from 'react-router-dom';
import { FormattedMessage, injectIntl } from 'react-intl';
import { debounce } from 'lodash';
-import { isUserTouching } from '../../../is_mobile';
+import { isUserTouching } from 'themes/glitch/util/is_mobile';
export const links = [
<NavLink className='tabs-bar__link primary' to='/statuses/new' data-preview-title-id='tabs_bar.compose' data-preview-icon='pencil' ><i className='fa fa-fw fa-pencil' /><FormattedMessage id='tabs_bar.compose' defaultMessage='Compose' /></NavLink>,
import React from 'react';
import PropTypes from 'prop-types';
-import Motion from '../../ui/util/optional_motion';
+import Motion from 'themes/glitch/util/optional_motion';
import spring from 'react-motion/lib/spring';
import { FormattedMessage } from 'react-intl';
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
-import Video from '../../video';
+import Video from 'themes/glitch/features/video';
import ImmutablePureComponent from 'react-immutable-pure-component';
export default class VideoModal extends ImmutablePureComponent {
import Bundle from '../components/bundle';
-import { fetchBundleRequest, fetchBundleSuccess, fetchBundleFail } from '../../../actions/bundles';
+import { fetchBundleRequest, fetchBundleSuccess, fetchBundleFail } from 'themes/glitch/actions/bundles';
const mapDispatchToProps = dispatch => ({
onFetch () {
import { connect } from 'react-redux';
-import { closeModal } from '../../../actions/modal';
+import { closeModal } from 'themes/glitch/actions/modal';
import ModalRoot from '../components/modal_root';
const mapStateToProps = state => ({
import { connect } from 'react-redux';
import { NotificationStack } from 'react-notification';
-import { dismissAlert } from '../../../actions/alerts';
-import { getAlerts } from '../../../selectors';
+import { dismissAlert } from 'themes/glitch/actions/alerts';
+import { getAlerts } from 'themes/glitch/selectors';
const mapStateToProps = state => ({
notifications: getAlerts(state),
import { connect } from 'react-redux';
-import StatusList from '../../../components/status_list';
-import { scrollTopTimeline } from '../../../actions/timelines';
+import StatusList from 'themes/glitch/components/status_list';
+import { scrollTopTimeline } from 'themes/glitch/actions/timelines';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
import { createSelector } from 'reselect';
import { debounce } from 'lodash';
-import { me } from '../../../initial_state';
+import { me } from 'themes/glitch/util/initial_state';
const makeGetStatusIds = () => createSelector([
(state, { type }) => state.getIn(['settings', type], ImmutableMap()),
import ModalContainer from './containers/modal_container';
import { connect } from 'react-redux';
import { Redirect, withRouter } from 'react-router-dom';
-import { isMobile } from '../../is_mobile';
+import { isMobile } from 'themes/glitch/util/is_mobile';
import { debounce } from 'lodash';
-import { uploadCompose, resetCompose } from '../../actions/compose';
-import { refreshHomeTimeline } from '../../actions/timelines';
-import { refreshNotifications } from '../../actions/notifications';
-import { clearHeight } from '../../actions/height_cache';
-import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers';
+import { uploadCompose, resetCompose } from 'themes/glitch/actions/compose';
+import { refreshHomeTimeline } from 'themes/glitch/actions/timelines';
+import { refreshNotifications } from 'themes/glitch/actions/notifications';
+import { clearHeight } from 'themes/glitch/actions/height_cache';
+import { WrappedSwitch, WrappedRoute } from 'themes/glitch/util/react_router_helpers';
import UploadArea from './components/upload_area';
import ColumnsAreaContainer from './containers/columns_area_container';
import classNames from 'classnames';
Blocks,
Mutes,
PinnedStatuses,
-} from './util/async-components';
+} from 'themes/glitch/util/async-components';
import { HotKeys } from 'react-hotkeys';
-import { me } from '../../initial_state';
+import { me } from 'themes/glitch/util/initial_state';
import { defineMessages, injectIntl } from 'react-intl';
// Dummy import, to make sure that <Status /> ends up in the application bundle.
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { throttle } from 'lodash';
import classNames from 'classnames';
-import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
+import { isFullscreen, requestFullscreen, exitFullscreen } from 'themes/glitch/util/fullscreen';
const messages = defineMessages({
play: { id: 'video.play', defaultMessage: 'Play' },
startTime: PropTypes.number,
onOpenVideo: PropTypes.func,
onCloseVideo: PropTypes.func,
+ letterbox: PropTypes.bool,
+ fullwidth: PropTypes.bool,
intl: PropTypes.object.isRequired,
};
}
render () {
- const { preview, src, width, height, startTime, onOpenVideo, onCloseVideo, intl, alt } = this.props;
+ const { preview, src, width, height, startTime, onOpenVideo, onCloseVideo, intl, alt, letterbox, fullwidth } = this.props;
const { progress, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
return (
- <div className={classNames('video-player', { inactive: !revealed, inline: width && height && !fullscreen, fullscreen })} style={{ width, height }} ref={this.setPlayerRef} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
+ <div className={classNames('video-player', { inactive: !revealed, inline: !fullscreen, fullscreen, letterbox, 'full-width': fullwidth })} ref={this.setPlayerRef} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
<video
ref={this.setVideoRef}
src={src}
--- /dev/null
+import loadPolyfills from './util/load_polyfills';
+
+// import default stylesheet with variables
+require('font-awesome/css/font-awesome.css');
+
+import './styles/index.scss';
+
+require.context('../../images/', true);
+
+loadPolyfills().then(() => {
+ require('./util/main').default();
+}).catch(e => {
+ console.error(e);
+});
-import { showAlert } from '../actions/alerts';
+import { showAlert } from 'themes/glitch/actions/alerts';
const defaultFailSuffix = 'FAIL';
FOLLOWING_EXPAND_SUCCESS,
FOLLOW_REQUESTS_FETCH_SUCCESS,
FOLLOW_REQUESTS_EXPAND_SUCCESS,
-} from '../actions/accounts';
+} from 'themes/glitch/actions/accounts';
import {
BLOCKS_FETCH_SUCCESS,
BLOCKS_EXPAND_SUCCESS,
-} from '../actions/blocks';
+} from 'themes/glitch/actions/blocks';
import {
MUTES_FETCH_SUCCESS,
MUTES_EXPAND_SUCCESS,
-} from '../actions/mutes';
-import { COMPOSE_SUGGESTIONS_READY } from '../actions/compose';
+} from 'themes/glitch/actions/mutes';
+import { COMPOSE_SUGGESTIONS_READY } from 'themes/glitch/actions/compose';
import {
REBLOG_SUCCESS,
UNREBLOG_SUCCESS,
UNFAVOURITE_SUCCESS,
REBLOGS_FETCH_SUCCESS,
FAVOURITES_FETCH_SUCCESS,
-} from '../actions/interactions';
+} from 'themes/glitch/actions/interactions';
import {
TIMELINE_REFRESH_SUCCESS,
TIMELINE_UPDATE,
TIMELINE_EXPAND_SUCCESS,
-} from '../actions/timelines';
+} from 'themes/glitch/actions/timelines';
import {
STATUS_FETCH_SUCCESS,
CONTEXT_FETCH_SUCCESS,
-} from '../actions/statuses';
-import { SEARCH_FETCH_SUCCESS } from '../actions/search';
+} from 'themes/glitch/actions/statuses';
+import { SEARCH_FETCH_SUCCESS } from 'themes/glitch/actions/search';
import {
NOTIFICATIONS_UPDATE,
NOTIFICATIONS_REFRESH_SUCCESS,
NOTIFICATIONS_EXPAND_SUCCESS,
-} from '../actions/notifications';
+} from 'themes/glitch/actions/notifications';
import {
FAVOURITED_STATUSES_FETCH_SUCCESS,
FAVOURITED_STATUSES_EXPAND_SUCCESS,
-} from '../actions/favourites';
-import { STORE_HYDRATE } from '../actions/store';
-import emojify from '../features/emoji/emoji';
+} from 'themes/glitch/actions/favourites';
+import { STORE_HYDRATE } from 'themes/glitch/actions/store';
+import emojify from 'themes/glitch/util/emoji';
import { Map as ImmutableMap, fromJS } from 'immutable';
import escapeTextContentForBrowser from 'escape-html';
FOLLOW_REQUESTS_EXPAND_SUCCESS,
ACCOUNT_FOLLOW_SUCCESS,
ACCOUNT_UNFOLLOW_SUCCESS,
-} from '../actions/accounts';
+} from 'themes/glitch/actions/accounts';
import {
BLOCKS_FETCH_SUCCESS,
BLOCKS_EXPAND_SUCCESS,
-} from '../actions/blocks';
+} from 'themes/glitch/actions/blocks';
import {
MUTES_FETCH_SUCCESS,
MUTES_EXPAND_SUCCESS,
-} from '../actions/mutes';
-import { COMPOSE_SUGGESTIONS_READY } from '../actions/compose';
+} from 'themes/glitch/actions/mutes';
+import { COMPOSE_SUGGESTIONS_READY } from 'themes/glitch/actions/compose';
import {
REBLOG_SUCCESS,
UNREBLOG_SUCCESS,
UNFAVOURITE_SUCCESS,
REBLOGS_FETCH_SUCCESS,
FAVOURITES_FETCH_SUCCESS,
-} from '../actions/interactions';
+} from 'themes/glitch/actions/interactions';
import {
TIMELINE_REFRESH_SUCCESS,
TIMELINE_UPDATE,
TIMELINE_EXPAND_SUCCESS,
-} from '../actions/timelines';
+} from 'themes/glitch/actions/timelines';
import {
STATUS_FETCH_SUCCESS,
CONTEXT_FETCH_SUCCESS,
-} from '../actions/statuses';
-import { SEARCH_FETCH_SUCCESS } from '../actions/search';
+} from 'themes/glitch/actions/statuses';
+import { SEARCH_FETCH_SUCCESS } from 'themes/glitch/actions/search';
import {
NOTIFICATIONS_UPDATE,
NOTIFICATIONS_REFRESH_SUCCESS,
NOTIFICATIONS_EXPAND_SUCCESS,
-} from '../actions/notifications';
+} from 'themes/glitch/actions/notifications';
import {
FAVOURITED_STATUSES_FETCH_SUCCESS,
FAVOURITED_STATUSES_EXPAND_SUCCESS,
-} from '../actions/favourites';
-import { STORE_HYDRATE } from '../actions/store';
+} from 'themes/glitch/actions/favourites';
+import { STORE_HYDRATE } from 'themes/glitch/actions/store';
import { Map as ImmutableMap, fromJS } from 'immutable';
const normalizeAccount = (state, account) => state.set(account.id, fromJS({
case STATUS_FETCH_SUCCESS:
return normalizeAccountFromStatus(state, action.status);
case ACCOUNT_FOLLOW_SUCCESS:
- if (action.alreadyFollowing) { return state; }
+ if (action.alreadyFollowing) {
+ return state;
+ }
return state.updateIn([action.relationship.id, 'followers_count'], num => num + 1);
case ACCOUNT_UNFOLLOW_SUCCESS:
return state.updateIn([action.relationship.id, 'followers_count'], num => Math.max(0, num - 1));
ALERT_SHOW,
ALERT_DISMISS,
ALERT_CLEAR,
-} from '../actions/alerts';
+} from 'themes/glitch/actions/alerts';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
const initialState = ImmutableList([]);
-import { STATUS_CARD_FETCH_SUCCESS } from '../actions/cards';
+import { STATUS_CARD_FETCH_SUCCESS } from 'themes/glitch/actions/cards';
import { Map as ImmutableMap, fromJS } from 'immutable';
COMPOSE_UPLOAD_CHANGE_FAIL,
COMPOSE_DOODLE_SET,
COMPOSE_RESET,
-} from '../actions/compose';
-import { TIMELINE_DELETE } from '../actions/timelines';
-import { STORE_HYDRATE } from '../actions/store';
+} from 'themes/glitch/actions/compose';
+import { TIMELINE_DELETE } from 'themes/glitch/actions/timelines';
+import { STORE_HYDRATE } from 'themes/glitch/actions/store';
import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
-import uuid from '../uuid';
-import { me } from '../initial_state';
+import uuid from 'themes/glitch/util/uuid';
+import { me } from 'themes/glitch/util/initial_state';
const initialState = ImmutableMap({
mounted: false,
-import { CONTEXT_FETCH_SUCCESS } from '../actions/statuses';
-import { TIMELINE_DELETE, TIMELINE_CONTEXT_UPDATE } from '../actions/timelines';
+import { CONTEXT_FETCH_SUCCESS } from 'themes/glitch/actions/statuses';
+import { TIMELINE_DELETE, TIMELINE_CONTEXT_UPDATE } from 'themes/glitch/actions/timelines';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
const initialState = ImmutableMap({
import { List as ImmutableList } from 'immutable';
-import { STORE_HYDRATE } from '../actions/store';
-import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light';
-import { buildCustomEmojis } from '../features/emoji/emoji';
+import { STORE_HYDRATE } from 'themes/glitch/actions/store';
+import { search as emojiSearch } from 'themes/glitch/util/emoji/emoji_mart_search_light';
+import { buildCustomEmojis } from 'themes/glitch/util/emoji';
const initialState = ImmutableList();
import { Map as ImmutableMap } from 'immutable';
-import { HEIGHT_CACHE_SET, HEIGHT_CACHE_CLEAR } from '../actions/height_cache';
+import { HEIGHT_CACHE_SET, HEIGHT_CACHE_CLEAR } from 'themes/glitch/actions/height_cache';
const initialState = ImmutableMap();
import statuses from './statuses';
import relationships from './relationships';
import settings from './settings';
-import local_settings from '../../glitch/reducers/local_settings';
+import local_settings from './local_settings';
import push_notifications from './push_notifications';
import status_lists from './status_lists';
import cards from './cards';
--- /dev/null
+// Package imports.
+import { Map as ImmutableMap } from 'immutable';
+
+// Our imports.
+import { STORE_HYDRATE } from 'themes/glitch/actions/store';
+import { LOCAL_SETTING_CHANGE } from 'themes/glitch/actions/local_settings';
+
+const initialState = ImmutableMap({
+ layout : 'auto',
+ stretch : true,
+ navbar_under : false,
+ side_arm : 'none',
+ collapsed : ImmutableMap({
+ enabled : true,
+ auto : ImmutableMap({
+ all : false,
+ notifications : true,
+ lengthy : true,
+ reblogs : false,
+ replies : false,
+ media : false,
+ }),
+ backgrounds : ImmutableMap({
+ user_backgrounds : false,
+ preview_images : false,
+ }),
+ }),
+ media : ImmutableMap({
+ letterbox : true,
+ fullwidth : true,
+ }),
+});
+
+const hydrate = (state, localSettings) => state.mergeDeep(localSettings);
+
+export default function localSettings(state = initialState, action) {
+ switch(action.type) {
+ case STORE_HYDRATE:
+ return hydrate(state, action.state.get('local_settings'));
+ case LOCAL_SETTING_CHANGE:
+ return state.setIn(action.key, action.value);
+ default:
+ return state;
+ }
+};
-import { STORE_HYDRATE } from '../actions/store';
+import { STORE_HYDRATE } from 'themes/glitch/actions/store';
import { Map as ImmutableMap } from 'immutable';
const initialState = ImmutableMap({
-import { STORE_HYDRATE } from '../actions/store';
+import { STORE_HYDRATE } from 'themes/glitch/actions/store';
import { Map as ImmutableMap } from 'immutable';
const initialState = ImmutableMap({
-import { MODAL_OPEN, MODAL_CLOSE } from '../actions/modal';
+import { MODAL_OPEN, MODAL_CLOSE } from 'themes/glitch/actions/modal';
const initialState = {
modalType: null,
import {
MUTES_INIT_MODAL,
MUTES_TOGGLE_HIDE_NOTIFICATIONS,
-} from '../actions/mutes';
+} from 'themes/glitch/actions/mutes';
const initialState = Immutable.Map({
new: Immutable.Map({
NOTIFICATIONS_DELETE_MARKED_FAIL,
NOTIFICATIONS_ENTER_CLEARING_MODE,
NOTIFICATIONS_MARK_ALL_FOR_DELETE,
-} from '../actions/notifications';
+} from 'themes/glitch/actions/notifications';
import {
ACCOUNT_BLOCK_SUCCESS,
ACCOUNT_MUTE_SUCCESS,
-} from '../actions/accounts';
-import { TIMELINE_DELETE } from '../actions/timelines';
+} from 'themes/glitch/actions/accounts';
+import { TIMELINE_DELETE } from 'themes/glitch/actions/timelines';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
const initialState = ImmutableMap({
-import { STORE_HYDRATE } from '../actions/store';
-import { SET_BROWSER_SUPPORT, SET_SUBSCRIPTION, CLEAR_SUBSCRIPTION, ALERTS_CHANGE } from '../actions/push_notifications';
+import { STORE_HYDRATE } from 'themes/glitch/actions/store';
+import { SET_BROWSER_SUPPORT, SET_SUBSCRIPTION, CLEAR_SUBSCRIPTION, ALERTS_CHANGE } from 'themes/glitch/actions/push_notifications';
import Immutable from 'immutable';
const initialState = Immutable.Map({
ACCOUNT_MUTE_SUCCESS,
ACCOUNT_UNMUTE_SUCCESS,
RELATIONSHIPS_FETCH_SUCCESS,
-} from '../actions/accounts';
+} from 'themes/glitch/actions/accounts';
import {
DOMAIN_BLOCK_SUCCESS,
DOMAIN_UNBLOCK_SUCCESS,
-} from '../actions/domain_blocks';
+} from 'themes/glitch/actions/domain_blocks';
import { Map as ImmutableMap, fromJS } from 'immutable';
const normalizeRelationship = (state, relationship) => state.set(relationship.id, fromJS(relationship));
REPORT_CANCEL,
REPORT_STATUS_TOGGLE,
REPORT_COMMENT_CHANGE,
-} from '../actions/reports';
+} from 'themes/glitch/actions/reports';
import { Map as ImmutableMap, Set as ImmutableSet } from 'immutable';
const initialState = ImmutableMap({
SEARCH_CLEAR,
SEARCH_FETCH_SUCCESS,
SEARCH_SHOW,
-} from '../actions/search';
-import { COMPOSE_MENTION, COMPOSE_REPLY } from '../actions/compose';
+} from 'themes/glitch/actions/search';
+import { COMPOSE_MENTION, COMPOSE_REPLY } from 'themes/glitch/actions/compose';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
const initialState = ImmutableMap({
-import { SETTING_CHANGE, SETTING_SAVE } from '../actions/settings';
-import { COLUMN_ADD, COLUMN_REMOVE, COLUMN_MOVE } from '../actions/columns';
-import { STORE_HYDRATE } from '../actions/store';
-import { EMOJI_USE } from '../actions/emojis';
+import { SETTING_CHANGE, SETTING_SAVE } from 'themes/glitch/actions/settings';
+import { COLUMN_ADD, COLUMN_REMOVE, COLUMN_MOVE } from 'themes/glitch/actions/columns';
+import { STORE_HYDRATE } from 'themes/glitch/actions/store';
+import { EMOJI_USE } from 'themes/glitch/actions/emojis';
import { Map as ImmutableMap, fromJS } from 'immutable';
-import uuid from '../uuid';
+import uuid from 'themes/glitch/util/uuid';
const initialState = ImmutableMap({
saved: true,
import {
FAVOURITED_STATUSES_FETCH_SUCCESS,
FAVOURITED_STATUSES_EXPAND_SUCCESS,
-} from '../actions/favourites';
+} from 'themes/glitch/actions/favourites';
import {
PINNED_STATUSES_FETCH_SUCCESS,
-} from '../actions/pin_statuses';
+} from 'themes/glitch/actions/pin_statuses';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
import {
FAVOURITE_SUCCESS,
UNFAVOURITE_SUCCESS,
PIN_SUCCESS,
UNPIN_SUCCESS,
-} from '../actions/interactions';
+} from 'themes/glitch/actions/interactions';
const initialState = ImmutableMap({
favourites: ImmutableMap({
UNFAVOURITE_SUCCESS,
PIN_SUCCESS,
UNPIN_SUCCESS,
-} from '../actions/interactions';
+} from 'themes/glitch/actions/interactions';
import {
STATUS_FETCH_SUCCESS,
CONTEXT_FETCH_SUCCESS,
STATUS_MUTE_SUCCESS,
STATUS_UNMUTE_SUCCESS,
-} from '../actions/statuses';
+} from 'themes/glitch/actions/statuses';
import {
TIMELINE_REFRESH_SUCCESS,
TIMELINE_UPDATE,
TIMELINE_DELETE,
TIMELINE_EXPAND_SUCCESS,
-} from '../actions/timelines';
+} from 'themes/glitch/actions/timelines';
import {
ACCOUNT_BLOCK_SUCCESS,
ACCOUNT_MUTE_SUCCESS,
-} from '../actions/accounts';
+} from 'themes/glitch/actions/accounts';
import {
NOTIFICATIONS_UPDATE,
NOTIFICATIONS_REFRESH_SUCCESS,
NOTIFICATIONS_EXPAND_SUCCESS,
-} from '../actions/notifications';
+} from 'themes/glitch/actions/notifications';
import {
FAVOURITED_STATUSES_FETCH_SUCCESS,
FAVOURITED_STATUSES_EXPAND_SUCCESS,
-} from '../actions/favourites';
+} from 'themes/glitch/actions/favourites';
import {
PINNED_STATUSES_FETCH_SUCCESS,
-} from '../actions/pin_statuses';
-import { SEARCH_FETCH_SUCCESS } from '../actions/search';
-import emojify from '../features/emoji/emoji';
+} from 'themes/glitch/actions/pin_statuses';
+import { SEARCH_FETCH_SUCCESS } from 'themes/glitch/actions/search';
+import emojify from 'themes/glitch/util/emoji';
import { Map as ImmutableMap, fromJS } from 'immutable';
import escapeTextContentForBrowser from 'escape-html';
TIMELINE_SCROLL_TOP,
TIMELINE_CONNECT,
TIMELINE_DISCONNECT,
-} from '../actions/timelines';
+} from 'themes/glitch/actions/timelines';
import {
ACCOUNT_BLOCK_SUCCESS,
ACCOUNT_MUTE_SUCCESS,
ACCOUNT_UNFOLLOW_SUCCESS,
-} from '../actions/accounts';
+} from 'themes/glitch/actions/accounts';
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
const initialState = ImmutableMap();
FOLLOW_REQUESTS_EXPAND_SUCCESS,
FOLLOW_REQUEST_AUTHORIZE_SUCCESS,
FOLLOW_REQUEST_REJECT_SUCCESS,
-} from '../actions/accounts';
+} from 'themes/glitch/actions/accounts';
import {
REBLOGS_FETCH_SUCCESS,
FAVOURITES_FETCH_SUCCESS,
-} from '../actions/interactions';
+} from 'themes/glitch/actions/interactions';
import {
BLOCKS_FETCH_SUCCESS,
BLOCKS_EXPAND_SUCCESS,
-} from '../actions/blocks';
+} from 'themes/glitch/actions/blocks';
import {
MUTES_FETCH_SUCCESS,
MUTES_EXPAND_SUCCESS,
-} from '../actions/mutes';
+} from 'themes/glitch/actions/mutes';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
const initialState = ImmutableMap({
@import 'variables';
-@import 'variables-glitch';
@mixin fullwidth-gallery {
&.full-width {
--- /dev/null
+@import 'mixins';
+@import 'variables';
+@import 'fonts/roboto';
+@import 'fonts/roboto-mono';
+@import 'fonts/montserrat';
+
+@import 'reset';
+@import 'basics';
+@import 'containers';
+@import 'lists';
+@import 'footer';
+@import 'compact_header';
+@import 'landing_strip';
+@import 'forms';
+@import 'accounts';
+@import 'stream_entries';
+@import 'components';
+@import 'emoji_picker';
+@import 'about';
+@import 'tables';
+@import 'admin';
+@import 'rtl';
--- /dev/null
+/* http://meyerweb.com/eric/tools/css/reset/
+ v2.0 | 20110126
+ License: none (public domain)
+*/
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed,
+figure, figcaption, footer, header, hgroup,
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline;
+}
+
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section {
+ display: block;
+}
+
+body {
+ line-height: 1;
+}
+
+ol, ul {
+ list-style: none;
+}
+
+blockquote, q {
+ quotes: none;
+}
+
+blockquote:before, blockquote:after,
+q:before, q:after {
+ content: '';
+ content: none;
+}
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+::-webkit-scrollbar-thumb {
+ background: lighten($ui-base-color, 4%);
+ border: 0px none $base-border-color;
+ border-radius: 50px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: lighten($ui-base-color, 6%);
+}
+
+::-webkit-scrollbar-thumb:active {
+ background: lighten($ui-base-color, 4%);
+}
+
+::-webkit-scrollbar-track {
+ border: 0px none $base-border-color;
+ border-radius: 0;
+ background: rgba($base-overlay-background, 0.1);
+}
+
+::-webkit-scrollbar-track:hover {
+ background: $ui-base-color;
+}
+
+::-webkit-scrollbar-track:active {
+ background: $ui-base-color;
+}
+
+::-webkit-scrollbar-corner {
+ background: transparent;
+}
// Avatar border size (8% default, 100% for rounded avatars)
$ui-avatar-border-size: 8%;
+
+// More variables
+$dismiss-overlay-width: 4rem;
--- /dev/null
+# (REQUIRED) The location of the pack file inside `pack_directory`.
+pack: index.js
+
+# (OPTIONAL) The directory which contains the pack file.
+# Defaults to the theme directory (`app/javascript/themes/[theme]`),
+# but in the case of the vanilla Mastodon theme the pack file is
+# somewhere else.
+# pack_directory: app/javascript/packs
+
+# (OPTIONAL) Additional javascript resources to preload, for use with
+# lazy-loaded components. It is **STRONGLY RECOMMENDED** that you
+# derive these pathnames from `themes/[your-theme]` to ensure that
+# they stay unique. (Of course, vanilla doesn't do this ^^;;)
+preload:
+- themes/glitch/async/getting_started
+- themes/glitch/async/compose
+- themes/glitch/async/home_timeline
+- themes/glitch/async/notifications
--- /dev/null
+export function EmojiPicker () {
+ return import(/* webpackChunkName: "themes/glitch/async/emoji_picker" */'themes/glitch/util/emoji/emoji_picker');
+}
+
+export function Compose () {
+ return import(/* webpackChunkName: "themes/glitch/async/compose" */'themes/glitch/features/compose');
+}
+
+export function Notifications () {
+ return import(/* webpackChunkName: "themes/glitch/async/notifications" */'themes/glitch/features/notifications');
+}
+
+export function HomeTimeline () {
+ return import(/* webpackChunkName: "themes/glitch/async/home_timeline" */'themes/glitch/features/home_timeline');
+}
+
+export function PublicTimeline () {
+ return import(/* webpackChunkName: "themes/glitch/async/public_timeline" */'themes/glitch/features/public_timeline');
+}
+
+export function CommunityTimeline () {
+ return import(/* webpackChunkName: "themes/glitch/async/community_timeline" */'themes/glitch/features/community_timeline');
+}
+
+export function HashtagTimeline () {
+ return import(/* webpackChunkName: "themes/glitch/async/hashtag_timeline" */'themes/glitch/features/hashtag_timeline');
+}
+
+export function DirectTimeline() {
+ return import(/* webpackChunkName: "themes/glitch/async/direct_timeline" */'themes/glitch/features/direct_timeline');
+}
+
+export function Status () {
+ return import(/* webpackChunkName: "themes/glitch/async/status" */'themes/glitch/features/status');
+}
+
+export function GettingStarted () {
+ return import(/* webpackChunkName: "themes/glitch/async/getting_started" */'themes/glitch/features/getting_started');
+}
+
+export function PinnedStatuses () {
+ return import(/* webpackChunkName: "themes/glitch/async/pinned_statuses" */'themes/glitch/features/pinned_statuses');
+}
+
+export function AccountTimeline () {
+ return import(/* webpackChunkName: "themes/glitch/async/account_timeline" */'themes/glitch/features/account_timeline');
+}
+
+export function AccountGallery () {
+ return import(/* webpackChunkName: "themes/glitch/async/account_gallery" */'themes/glitch/features/account_gallery');
+}
+
+export function Followers () {
+ return import(/* webpackChunkName: "themes/glitch/async/followers" */'themes/glitch/features/followers');
+}
+
+export function Following () {
+ return import(/* webpackChunkName: "themes/glitch/async/following" */'themes/glitch/features/following');
+}
+
+export function Reblogs () {
+ return import(/* webpackChunkName: "themes/glitch/async/reblogs" */'themes/glitch/features/reblogs');
+}
+
+export function Favourites () {
+ return import(/* webpackChunkName: "themes/glitch/async/favourites" */'themes/glitch/features/favourites');
+}
+
+export function FollowRequests () {
+ return import(/* webpackChunkName: "themes/glitch/async/follow_requests" */'themes/glitch/features/follow_requests');
+}
+
+export function GenericNotFound () {
+ return import(/* webpackChunkName: "themes/glitch/async/generic_not_found" */'themes/glitch/features/generic_not_found');
+}
+
+export function FavouritedStatuses () {
+ return import(/* webpackChunkName: "themes/glitch/async/favourited_statuses" */'themes/glitch/features/favourited_statuses');
+}
+
+export function Blocks () {
+ return import(/* webpackChunkName: "themes/glitch/async/blocks" */'themes/glitch/features/blocks');
+}
+
+export function Mutes () {
+ return import(/* webpackChunkName: "themes/glitch/async/mutes" */'themes/glitch/features/mutes');
+}
+
+export function OnboardingModal () {
+ return import(/* webpackChunkName: "themes/glitch/async/onboarding_modal" */'themes/glitch/features/ui/components/onboarding_modal');
+}
+
+export function MuteModal () {
+ return import(/* webpackChunkName: "themes/glitch/async/mute_modal" */'themes/glitch/features/ui/components/mute_modal');
+}
+
+export function ReportModal () {
+ return import(/* webpackChunkName: "themes/glitch/async/report_modal" */'themes/glitch/features/ui/components/report_modal');
+}
+
+export function SettingsModal () {
+ return import(/* webpackChunkName: "themes/glitch/async/settings_modal" */'themes/glitch/features/local_settings');
+}
+
+export function MediaGallery () {
+ return import(/* webpackChunkName: "themes/glitch/async/media_gallery" */'themes/glitch/components/media_gallery');
+}
+
+export function Video () {
+ return import(/* webpackChunkName: "themes/glitch/async/video" */'themes/glitch/features/video');
+}
+
+export function EmbedModal () {
+ return import(/* webpackChunkName: "themes/glitch/async/embed_modal" */'themes/glitch/features/ui/components/embed_modal');
+}
-import emojify from '../emoji';
+import emojify from '..';
describe('emoji', () => {
describe('.emojify', () => {
-import { autoPlayGif } from '../../initial_state';
+import { autoPlayGif } from 'themes/glitch/util/initial_state';
import unicodeMapping from './emoji_unicode_mapping_light';
import Trie from 'substring-trie';
import * as WebPushSubscription from './web_push_subscription';
-import Mastodon from './containers/mastodon';
+import Mastodon from 'themes/glitch/containers/mastodon';
import React from 'react';
import ReactDOM from 'react-dom';
import ready from './ready';
-import { reduceMotion } from '../../../initial_state';
+import { reduceMotion } from 'themes/glitch/util/initial_state';
import ReducedMotion from './reduced_motion';
import Motion from 'react-motion/lib/Motion';
import PropTypes from 'prop-types';
import { Switch, Route } from 'react-router-dom';
-import ColumnLoading from '../components/column_loading';
-import BundleColumnError from '../components/bundle_column_error';
-import BundleContainer from '../containers/bundle_container';
+import ColumnLoading from 'themes/glitch/features/ui/components/column_loading';
+import BundleColumnError from 'themes/glitch/features/ui/components/bundle_column_error';
+import BundleContainer from 'themes/glitch/features/ui/containers/bundle_container';
// Small wrapper to pass multiColumn to the route components
export class WrappedSwitch extends React.PureComponent {
import axios from 'axios';
-import { store } from './containers/mastodon';
-import { setBrowserSupport, setSubscription, clearSubscription } from './actions/push_notifications';
+import { store } from 'themes/glitch/containers/mastodon';
+import { setBrowserSupport, setSubscription, clearSubscription } from 'themes/glitch/actions/push_notifications';
// Taken from https://www.npmjs.com/package/web-push
const urlBase64ToUint8Array = (base64String) => {
# (REQUIRED) The location of the pack file inside `pack_directory`.
-pack: application.js
+#pack: application.js
# (OPTIONAL) The directory which contains the pack file.
# Defaults to the theme directory (`app/javascript/themes/[theme]`),