Merge remote-tracking branch 'origin' into a11y--
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
commit
6152ca40a7
54 changed files with 701 additions and 262 deletions
|
@ -2,6 +2,9 @@ import loadPolyfills from './soapbox/load_polyfills';
|
|||
|
||||
require.context('./images/', true);
|
||||
|
||||
// Load stylesheet
|
||||
require('./styles/application.scss');
|
||||
|
||||
loadPolyfills().then(() => {
|
||||
require('./soapbox/main').default();
|
||||
}).catch(e => {
|
||||
|
|
|
@ -471,8 +471,6 @@ export function unsubscribeAccountFail(error) {
|
|||
|
||||
export function fetchFollowers(id) {
|
||||
return (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(fetchFollowersRequest(id));
|
||||
|
||||
api(getState).get(`/api/v1/accounts/${id}/followers`).then(response => {
|
||||
|
@ -561,8 +559,6 @@ export function expandFollowersFail(id, error) {
|
|||
|
||||
export function fetchFollowing(id) {
|
||||
return (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(fetchFollowingRequest(id));
|
||||
|
||||
api(getState).get(`/api/v1/accounts/${id}/following`).then(response => {
|
||||
|
|
|
@ -10,6 +10,14 @@ export const FAVOURITED_STATUSES_EXPAND_REQUEST = 'FAVOURITED_STATUSES_EXPAND_RE
|
|||
export const FAVOURITED_STATUSES_EXPAND_SUCCESS = 'FAVOURITED_STATUSES_EXPAND_SUCCESS';
|
||||
export const FAVOURITED_STATUSES_EXPAND_FAIL = 'FAVOURITED_STATUSES_EXPAND_FAIL';
|
||||
|
||||
export const ACCOUNT_FAVOURITED_STATUSES_FETCH_REQUEST = 'ACCOUNT_FAVOURITED_STATUSES_FETCH_REQUEST';
|
||||
export const ACCOUNT_FAVOURITED_STATUSES_FETCH_SUCCESS = 'ACCOUNT_FAVOURITED_STATUSES_FETCH_SUCCESS';
|
||||
export const ACCOUNT_FAVOURITED_STATUSES_FETCH_FAIL = 'ACCOUNT_FAVOURITED_STATUSES_FETCH_FAIL';
|
||||
|
||||
export const ACCOUNT_FAVOURITED_STATUSES_EXPAND_REQUEST = 'ACCOUNT_FAVOURITED_STATUSES_EXPAND_REQUEST';
|
||||
export const ACCOUNT_FAVOURITED_STATUSES_EXPAND_SUCCESS = 'ACCOUNT_FAVOURITED_STATUSES_EXPAND_SUCCESS';
|
||||
export const ACCOUNT_FAVOURITED_STATUSES_EXPAND_FAIL = 'ACCOUNT_FAVOURITED_STATUSES_EXPAND_FAIL';
|
||||
|
||||
export function fetchFavouritedStatuses() {
|
||||
return (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
@ -96,3 +104,96 @@ export function expandFavouritedStatusesFail(error) {
|
|||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchAccountFavouritedStatuses(accountId) {
|
||||
return (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
if (getState().getIn(['status_lists', `favourites:${accountId}`, 'isLoading'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(fetchAccountFavouritedStatusesRequest(accountId));
|
||||
|
||||
api(getState).get(`/api/v1/pleroma/accounts/${accountId}/favourites`).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(importFetchedStatuses(response.data));
|
||||
dispatch(fetchAccountFavouritedStatusesSuccess(accountId, response.data, next ? next.uri : null));
|
||||
}).catch(error => {
|
||||
dispatch(fetchAccountFavouritedStatusesFail(accountId, error));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchAccountFavouritedStatusesRequest(accountId) {
|
||||
return {
|
||||
type: ACCOUNT_FAVOURITED_STATUSES_FETCH_REQUEST,
|
||||
accountId,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchAccountFavouritedStatusesSuccess(accountId, statuses, next) {
|
||||
return {
|
||||
type: ACCOUNT_FAVOURITED_STATUSES_FETCH_SUCCESS,
|
||||
accountId,
|
||||
statuses,
|
||||
next,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchAccountFavouritedStatusesFail(accountId, error) {
|
||||
return {
|
||||
type: ACCOUNT_FAVOURITED_STATUSES_FETCH_FAIL,
|
||||
accountId,
|
||||
error,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function expandAccountFavouritedStatuses(accountId) {
|
||||
return (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
const url = getState().getIn(['status_lists', `favourites:${accountId}`, 'next'], null);
|
||||
|
||||
if (url === null || getState().getIn(['status_lists', `favourites:${accountId}`, 'isLoading'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(expandAccountFavouritedStatusesRequest(accountId));
|
||||
|
||||
api(getState).get(url).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(importFetchedStatuses(response.data));
|
||||
dispatch(expandAccountFavouritedStatusesSuccess(accountId, response.data, next ? next.uri : null));
|
||||
}).catch(error => {
|
||||
dispatch(expandAccountFavouritedStatusesFail(accountId, error));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function expandAccountFavouritedStatusesRequest(accountId) {
|
||||
return {
|
||||
type: ACCOUNT_FAVOURITED_STATUSES_EXPAND_REQUEST,
|
||||
accountId,
|
||||
};
|
||||
}
|
||||
|
||||
export function expandAccountFavouritedStatusesSuccess(accountId, statuses, next) {
|
||||
return {
|
||||
type: ACCOUNT_FAVOURITED_STATUSES_EXPAND_SUCCESS,
|
||||
accountId,
|
||||
statuses,
|
||||
next,
|
||||
};
|
||||
}
|
||||
|
||||
export function expandAccountFavouritedStatusesFail(accountId, error) {
|
||||
return {
|
||||
type: ACCOUNT_FAVOURITED_STATUSES_EXPAND_FAIL,
|
||||
accountId,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ export const makeDefaultConfig = features => {
|
|||
limit: 1,
|
||||
}),
|
||||
aboutPages: ImmutableMap(),
|
||||
authenticatedProfile: true,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ const {
|
|||
BACKEND_URL,
|
||||
FE_SUBDIRECTORY,
|
||||
FE_BUILD_DIR,
|
||||
SENTRY_DSN,
|
||||
} = process.env;
|
||||
|
||||
const sanitizeURL = url => {
|
||||
|
@ -38,4 +39,5 @@ module.exports = sanitize({
|
|||
BACKEND_URL: sanitizeURL(BACKEND_URL),
|
||||
FE_SUBDIRECTORY: sanitizeBasename(FE_SUBDIRECTORY),
|
||||
FE_BUILD_DIR: sanitizePath(FE_BUILD_DIR) || 'static',
|
||||
SENTRY_DSN,
|
||||
});
|
||||
|
|
|
@ -20,7 +20,7 @@ exports[`<AutosuggestEmoji /> renders native emoji 1`] = `
|
|||
<img
|
||||
alt="💙"
|
||||
className="emojione"
|
||||
src="/emoji/1f499.svg"
|
||||
src="/packs/emoji/1f499.svg"
|
||||
/>
|
||||
:foobar:
|
||||
</div>
|
||||
|
|
|
@ -15,7 +15,7 @@ exports[`<EmojiSelector /> renders correctly 1`] = `
|
|||
className="emoji-react-selector__emoji"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"👍\\" title=\\":+1:\\" src=\\"/emoji/1f44d.svg\\" />",
|
||||
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"👍\\" title=\\":+1:\\" src=\\"/packs/emoji/1f44d.svg\\" />",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
|
@ -26,7 +26,7 @@ exports[`<EmojiSelector /> renders correctly 1`] = `
|
|||
className="emoji-react-selector__emoji"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"❤\\" title=\\":heart:\\" src=\\"/emoji/2764.svg\\" />",
|
||||
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"❤\\" title=\\":heart:\\" src=\\"/packs/emoji/2764.svg\\" />",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
|
@ -37,7 +37,7 @@ exports[`<EmojiSelector /> renders correctly 1`] = `
|
|||
className="emoji-react-selector__emoji"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"😆\\" title=\\":laughing:\\" src=\\"/emoji/1f606.svg\\" />",
|
||||
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"😆\\" title=\\":laughing:\\" src=\\"/packs/emoji/1f606.svg\\" />",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
|
@ -48,7 +48,7 @@ exports[`<EmojiSelector /> renders correctly 1`] = `
|
|||
className="emoji-react-selector__emoji"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"😮\\" title=\\":open_mouth:\\" src=\\"/emoji/1f62e.svg\\" />",
|
||||
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"😮\\" title=\\":open_mouth:\\" src=\\"/packs/emoji/1f62e.svg\\" />",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
|
@ -59,7 +59,7 @@ exports[`<EmojiSelector /> renders correctly 1`] = `
|
|||
className="emoji-react-selector__emoji"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"😢\\" title=\\":cry:\\" src=\\"/emoji/1f622.svg\\" />",
|
||||
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"😢\\" title=\\":cry:\\" src=\\"/packs/emoji/1f622.svg\\" />",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
|
@ -70,7 +70,7 @@ exports[`<EmojiSelector /> renders correctly 1`] = `
|
|||
className="emoji-react-selector__emoji"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"😩\\" title=\\":weary:\\" src=\\"/emoji/1f629.svg\\" />",
|
||||
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"😩\\" title=\\":weary:\\" src=\\"/packs/emoji/1f629.svg\\" />",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import unicodeMapping from '../features/emoji/emoji_unicode_mapping_light';
|
||||
import { join } from 'path';
|
||||
import { FE_SUBDIRECTORY } from 'soapbox/build_config';
|
||||
import { joinPublicPath } from 'soapbox/utils/static';
|
||||
|
||||
export default class AutosuggestEmoji extends React.PureComponent {
|
||||
|
||||
|
@ -23,7 +22,7 @@ export default class AutosuggestEmoji extends React.PureComponent {
|
|||
return null;
|
||||
}
|
||||
|
||||
url = join(FE_SUBDIRECTORY, 'emoji', `${mapping.filename}.svg`);
|
||||
url = joinPublicPath(`packs/emoji/${mapping.filename}.svg`);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import Bowser from 'bowser';
|
||||
import { captureException } from 'soapbox/monitoring';
|
||||
|
||||
export default class ErrorBoundary extends React.PureComponent {
|
||||
|
||||
|
@ -15,11 +15,21 @@ export default class ErrorBoundary extends React.PureComponent {
|
|||
}
|
||||
|
||||
componentDidCatch(error, info) {
|
||||
captureException(error);
|
||||
|
||||
this.setState({
|
||||
hasError: true,
|
||||
error,
|
||||
componentStack: info && info.componentStack,
|
||||
});
|
||||
|
||||
import(/* webpackChunkName: "error" */'bowser')
|
||||
.then(({ default: Bowser }) => {
|
||||
this.setState({
|
||||
browser: Bowser.getParser(window.navigator.userAgent),
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
setTextareaRef = c => {
|
||||
|
@ -46,9 +56,7 @@ export default class ErrorBoundary extends React.PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const browser = Bowser.getParser(window.navigator.userAgent);
|
||||
|
||||
const { hasError } = this.state;
|
||||
const { browser, hasError } = this.state;
|
||||
|
||||
if (!hasError) {
|
||||
return this.props.children;
|
||||
|
@ -72,9 +80,9 @@ export default class ErrorBoundary extends React.PureComponent {
|
|||
onClick={this.handleCopy}
|
||||
readOnly
|
||||
/>}
|
||||
<p className='error-boundary__browser'>
|
||||
{browser && <p className='error-boundary__browser'>
|
||||
{browser.getBrowserName()} {browser.getBrowserVersion()}
|
||||
</p>
|
||||
</p>}
|
||||
<p className='help-text'>
|
||||
<FormattedMessage
|
||||
id='alert.unexpected.help_text'
|
||||
|
|
|
@ -265,15 +265,16 @@ class StatusContent extends React.PureComponent {
|
|||
}
|
||||
|
||||
if (status.get('poll')) {
|
||||
output.push(<PollContainer pollId={status.get('poll')} />);
|
||||
output.push(<PollContainer pollId={status.get('poll')} key='poll' />);
|
||||
}
|
||||
|
||||
return output;
|
||||
} else {
|
||||
const output = [
|
||||
<div
|
||||
tabIndex='0'
|
||||
ref={this.setRef}
|
||||
tabIndex='0'
|
||||
key='content'
|
||||
className={classnames('status__content', {
|
||||
'status__content--big': onlyEmoji,
|
||||
})}
|
||||
|
@ -284,7 +285,7 @@ class StatusContent extends React.PureComponent {
|
|||
];
|
||||
|
||||
if (status.get('poll')) {
|
||||
output.push(<PollContainer pollId={status.get('poll')} />);
|
||||
output.push(<PollContainer pollId={status.get('poll')} key='poll' />);
|
||||
}
|
||||
|
||||
return output;
|
||||
|
|
|
@ -17,7 +17,6 @@ import {
|
|||
isRemote,
|
||||
getDomain,
|
||||
} from 'soapbox/utils/accounts';
|
||||
import { parseVersion } from 'soapbox/utils/features';
|
||||
import classNames from 'classnames';
|
||||
import Avatar from 'soapbox/components/avatar';
|
||||
import { shortNumberFormat } from 'soapbox/utils/numbers';
|
||||
|
@ -30,6 +29,7 @@ import ActionButton from 'soapbox/features/ui/components/action_button';
|
|||
import SubscriptionButton from 'soapbox/features/ui/components/subscription_button';
|
||||
import { openModal } from 'soapbox/actions/modal';
|
||||
import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
|
||||
const messages = defineMessages({
|
||||
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
|
||||
|
@ -72,11 +72,13 @@ const messages = defineMessages({
|
|||
const mapStateToProps = state => {
|
||||
const me = state.get('me');
|
||||
const account = state.getIn(['accounts', me]);
|
||||
const instance = state.get('instance');
|
||||
const features = getFeatures(instance);
|
||||
|
||||
return {
|
||||
me,
|
||||
meAccount: account,
|
||||
version: parseVersion(state.getIn(['instance', 'version'])),
|
||||
features,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -90,7 +92,7 @@ class Header extends ImmutablePureComponent {
|
|||
identity_props: ImmutablePropTypes.list,
|
||||
intl: PropTypes.object.isRequired,
|
||||
username: PropTypes.string,
|
||||
version: PropTypes.object,
|
||||
features: PropTypes.object,
|
||||
};
|
||||
|
||||
state = {
|
||||
|
@ -156,7 +158,7 @@ class Header extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
makeMenu() {
|
||||
const { account, intl, me, meAccount, version } = this.props;
|
||||
const { account, intl, me, meAccount, features } = this.props;
|
||||
|
||||
const menu = [];
|
||||
|
||||
|
@ -196,7 +198,7 @@ class Header extends ImmutablePureComponent {
|
|||
menu.push({ text: intl.formatMessage(messages.add_or_remove_from_list), action: this.props.onAddToList });
|
||||
// menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle });
|
||||
menu.push(null);
|
||||
} else if (version.software === 'Pleroma') {
|
||||
} else if (features.unrestrictedLists) {
|
||||
menu.push({ text: intl.formatMessage(messages.add_or_remove_from_list), action: this.props.onAddToList });
|
||||
}
|
||||
|
||||
|
@ -285,7 +287,7 @@ class Header extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { account, intl, username, me } = this.props;
|
||||
const { account, intl, username, me, features } = this.props;
|
||||
const { isSmallScreen } = this.state;
|
||||
|
||||
if (!account) {
|
||||
|
@ -327,9 +329,9 @@ class Header extends ImmutablePureComponent {
|
|||
<StillImage src={account.get('header')} alt='' className='parallax' />
|
||||
</a>}
|
||||
|
||||
<div className='account__header__subscribe'>
|
||||
{features.accountSubscriptions && <div className='account__header__subscribe'>
|
||||
<SubscriptionButton account={account} />
|
||||
</div>
|
||||
</div>}
|
||||
</div>
|
||||
|
||||
<div className='account__header__bar'>
|
||||
|
@ -356,24 +358,20 @@ class Header extends ImmutablePureComponent {
|
|||
<span><FormattedMessage id='account.followers' defaultMessage='Followers' /></span>
|
||||
</NavLink>}
|
||||
|
||||
{
|
||||
ownAccount &&
|
||||
<div>
|
||||
<NavLink
|
||||
exact activeClassName='active' to={`/@${account.get('acct')}/favorites`}
|
||||
>
|
||||
{ /* : TODO : shortNumberFormat(account.get('favourite_count')) */ }
|
||||
<span>•</span>
|
||||
<span><FormattedMessage id='navigation_bar.favourites' defaultMessage='Likes' /></span>
|
||||
</NavLink>
|
||||
<NavLink
|
||||
exact activeClassName='active' to={`/@${account.get('acct')}/pins`}
|
||||
>
|
||||
{ /* : TODO : shortNumberFormat(account.get('pinned_count')) */ }
|
||||
<span>•</span>
|
||||
<span><FormattedMessage id='navigation_bar.pins' defaultMessage='Pins' /></span>
|
||||
</NavLink>
|
||||
</div>
|
||||
{(ownAccount || !account.getIn(['pleroma', 'hide_favorites'], true)) && <NavLink exact activeClassName='active' to={`/@${account.get('acct')}/favorites`}>
|
||||
{ /* : TODO : shortNumberFormat(account.get('favourite_count')) */ }
|
||||
<span>•</span>
|
||||
<span><FormattedMessage id='navigation_bar.favourites' defaultMessage='Likes' /></span>
|
||||
</NavLink>}
|
||||
|
||||
{ownAccount &&
|
||||
<NavLink
|
||||
exact activeClassName='active' to={`/@${account.get('acct')}/pins`}
|
||||
>
|
||||
{ /* : TODO : shortNumberFormat(account.get('pinned_count')) */ }
|
||||
<span>•</span>
|
||||
<span><FormattedMessage id='navigation_bar.pins' defaultMessage='Pins' /></span>
|
||||
</NavLink>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -7,8 +7,7 @@ import classNames from 'classnames';
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||
import { buildCustomEmojis } from '../../emoji/emoji';
|
||||
import { join } from 'path';
|
||||
import { FE_SUBDIRECTORY } from 'soapbox/build_config';
|
||||
import { joinPublicPath } from 'soapbox/utils/static';
|
||||
|
||||
const messages = defineMessages({
|
||||
emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },
|
||||
|
@ -29,7 +28,7 @@ const messages = defineMessages({
|
|||
|
||||
let EmojiPicker, Emoji; // load asynchronously
|
||||
|
||||
const backgroundImageFn = () => join(FE_SUBDIRECTORY, 'emoji', 'sheet_13.png');
|
||||
const backgroundImageFn = () => require('emoji-datasource/img/twitter/sheets/32.png');
|
||||
const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
|
||||
|
||||
const categoriesSort = [
|
||||
|
@ -358,8 +357,8 @@ class EmojiPickerDropdown extends React.PureComponent {
|
|||
<div ref={this.setTargetRef} className='emoji-button' title={title} aria-label={title} aria-expanded={active} role='button' onClick={this.onToggle} onKeyDown={this.onToggle} tabIndex={0}>
|
||||
<img
|
||||
className={classNames('emojione', { 'pulse-loading': active && loading })}
|
||||
alt='🙂'
|
||||
src={join(FE_SUBDIRECTORY, 'emoji', '1f602.svg')}
|
||||
alt='😂'
|
||||
src={joinPublicPath('packs/emoji/1f602.svg')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -4,10 +4,10 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import { setSchedule, removeSchedule } from '../../../actions/compose';
|
||||
import DatePicker from 'react-datepicker';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import IconButton from 'soapbox/components/icon_button';
|
||||
import { removeSchedule } from 'soapbox/actions/compose';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -15,11 +15,22 @@ const messages = defineMessages({
|
|||
remove: { id: 'schedule.remove', defaultMessage: 'Remove schedule' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, ownProps) => ({
|
||||
const mapStateToProps = state => ({
|
||||
active: state.getIn(['compose', 'schedule']) ? true : false,
|
||||
scheduledAt: state.getIn(['compose', 'schedule']),
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onSchedule(date) {
|
||||
dispatch(setSchedule(date));
|
||||
},
|
||||
|
||||
onRemoveSchedule(date) {
|
||||
dispatch(removeSchedule());
|
||||
},
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps, mapDispatchToProps)
|
||||
@injectIntl
|
||||
class ScheduleForm extends React.Component {
|
||||
|
||||
|
@ -27,6 +38,7 @@ class ScheduleForm extends React.Component {
|
|||
scheduledAt: PropTypes.instanceOf(Date),
|
||||
intl: PropTypes.object.isRequired,
|
||||
onSchedule: PropTypes.func.isRequired,
|
||||
onRemoveSchedule: PropTypes.func.isRequired,
|
||||
dispatch: PropTypes.func,
|
||||
active: PropTypes.bool,
|
||||
};
|
||||
|
@ -60,7 +72,7 @@ class ScheduleForm extends React.Component {
|
|||
}
|
||||
|
||||
handleRemove = e => {
|
||||
this.props.dispatch(removeSchedule());
|
||||
this.props.onRemoveSchedule();
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
import { connect } from 'react-redux';
|
||||
import ScheduleForm from '../components/schedule_form';
|
||||
import { setSchedule } from '../../../actions/compose';
|
||||
import React from 'react';
|
||||
import BundleContainer from 'soapbox/features/ui/containers/bundle_container';
|
||||
import { ScheduleForm } from 'soapbox/features/ui/util/async-components';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
schedule: state.getIn(['compose', 'schedule']),
|
||||
active: state.getIn(['compose', 'schedule']) ? true : false,
|
||||
});
|
||||
export default class ScheduleFormContainer extends React.PureComponent {
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onSchedule(date) {
|
||||
dispatch(setSchedule(date));
|
||||
},
|
||||
});
|
||||
render() {
|
||||
return (
|
||||
<BundleContainer fetchComponent={ScheduleForm}>
|
||||
{Component => <Component {...this.props} />}
|
||||
</BundleContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ScheduleForm);
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
|
|||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import Icon from 'soapbox/components/icon';
|
||||
import CoinDB from '../utils/coin_db';
|
||||
import { getCoinIcon } from '../utils/coin_icons';
|
||||
import CryptoIcon from './crypto_icon';
|
||||
import { openModal } from 'soapbox/actions/modal';
|
||||
import { CopyableInput } from 'soapbox/features/forms';
|
||||
import { getExplorerUrl } from '../utils/block_explorer';
|
||||
|
@ -31,9 +31,11 @@ class CryptoAddress extends ImmutablePureComponent {
|
|||
return (
|
||||
<div className='crypto-address'>
|
||||
<div className='crypto-address__head'>
|
||||
<div className='crypto-address__icon'>
|
||||
<img src={getCoinIcon(ticker)} alt={title} />
|
||||
</div>
|
||||
<CryptoIcon
|
||||
className='crypto-address__icon'
|
||||
ticker={ticker}
|
||||
title={title}
|
||||
/>
|
||||
<div className='crypto-address__title'>{title || ticker.toUpperCase()}</div>
|
||||
<div className='crypto-address__actions'>
|
||||
<a href='' onClick={this.handleModalClick}>
|
||||
|
|
26
app/soapbox/features/crypto_donate/components/crypto_icon.js
Normal file
26
app/soapbox/features/crypto_donate/components/crypto_icon.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export default class CryptoIcon extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
ticker: PropTypes.string.isRequired,
|
||||
title: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
}
|
||||
|
||||
render() {
|
||||
const { ticker, title, className } = this.props;
|
||||
|
||||
return (
|
||||
<div className={classNames('crypto-icon', className)}>
|
||||
<img
|
||||
src={require(`cryptocurrency-icons/svg/color/${ticker.toLowerCase()}.svg`)}
|
||||
alt={title || ticker}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,16 +1,14 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import Icon from 'soapbox/components/icon';
|
||||
import QRCode from 'qrcode.react';
|
||||
import CoinDB from '../utils/coin_db';
|
||||
import { getCoinIcon } from '../utils/coin_icons';
|
||||
import CryptoIcon from './crypto_icon';
|
||||
import { CopyableInput } from 'soapbox/features/forms';
|
||||
import { getExplorerUrl } from '../utils/block_explorer';
|
||||
|
||||
export default @connect()
|
||||
class DetailedCryptoAddress extends ImmutablePureComponent {
|
||||
export default class DetailedCryptoAddress extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
address: PropTypes.string.isRequired,
|
||||
|
@ -26,9 +24,11 @@ class DetailedCryptoAddress extends ImmutablePureComponent {
|
|||
return (
|
||||
<div className='crypto-address'>
|
||||
<div className='crypto-address__head'>
|
||||
<div className='crypto-address__icon'>
|
||||
<img src={getCoinIcon(ticker)} alt={title} />
|
||||
</div>
|
||||
<CryptoIcon
|
||||
className='crypto-address__icon'
|
||||
ticker={ticker}
|
||||
title={title}
|
||||
/>
|
||||
<div className='crypto-address__title'>{title || ticker.toUpperCase()}</div>
|
||||
<div className='crypto-address__actions'>
|
||||
{explorerUrl && <a href={explorerUrl} target='_blank'>
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
// Does some trickery to import all the icons into the project
|
||||
// See: https://stackoverflow.com/questions/42118296/dynamically-import-images-from-a-directory-using-webpack
|
||||
|
||||
const icons = {};
|
||||
|
||||
function importAll(r) {
|
||||
const pathRegex = /\.\/(.*)\.svg/i;
|
||||
|
||||
r.keys().forEach((key) => {
|
||||
const ticker = pathRegex.exec(key)[1];
|
||||
return icons[ticker] = r(key).default;
|
||||
});
|
||||
}
|
||||
|
||||
importAll(require.context('cryptocurrency-icons/svg/color/', true, /\.svg$/));
|
||||
|
||||
export default icons;
|
||||
|
||||
// For getting the icon
|
||||
export const getCoinIcon = ticker => icons[ticker] || icons.generic || null;
|
|
@ -22,23 +22,23 @@ describe('emoji', () => {
|
|||
|
||||
it('does unicode', () => {
|
||||
expect(emojify('\uD83D\uDC69\u200D\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66')).toEqual(
|
||||
'<img draggable="false" class="emojione" alt="👩👩👦👦" title=":woman-woman-boy-boy:" src="/emoji/1f469-200d-1f469-200d-1f466-200d-1f466.svg" />');
|
||||
'<img draggable="false" class="emojione" alt="👩👩👦👦" title=":woman-woman-boy-boy:" src="/packs/emoji/1f469-200d-1f469-200d-1f466-200d-1f466.svg" />');
|
||||
expect(emojify('👨👩👧👧')).toEqual(
|
||||
'<img draggable="false" class="emojione" alt="👨👩👧👧" title=":man-woman-girl-girl:" src="/emoji/1f468-200d-1f469-200d-1f467-200d-1f467.svg" />');
|
||||
expect(emojify('👩👩👦')).toEqual('<img draggable="false" class="emojione" alt="👩👩👦" title=":woman-woman-boy:" src="/emoji/1f469-200d-1f469-200d-1f466.svg" />');
|
||||
'<img draggable="false" class="emojione" alt="👨👩👧👧" title=":man-woman-girl-girl:" src="/packs/emoji/1f468-200d-1f469-200d-1f467-200d-1f467.svg" />');
|
||||
expect(emojify('👩👩👦')).toEqual('<img draggable="false" class="emojione" alt="👩👩👦" title=":woman-woman-boy:" src="/packs/emoji/1f469-200d-1f469-200d-1f466.svg" />');
|
||||
expect(emojify('\u2757')).toEqual(
|
||||
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" />');
|
||||
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/packs/emoji/2757.svg" />');
|
||||
});
|
||||
|
||||
it('does multiple unicode', () => {
|
||||
expect(emojify('\u2757 #\uFE0F\u20E3')).toEqual(
|
||||
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" /> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg" />');
|
||||
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/packs/emoji/2757.svg" /> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/packs/emoji/23-20e3.svg" />');
|
||||
expect(emojify('\u2757#\uFE0F\u20E3')).toEqual(
|
||||
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" /><img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg" />');
|
||||
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/packs/emoji/2757.svg" /><img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/packs/emoji/23-20e3.svg" />');
|
||||
expect(emojify('\u2757 #\uFE0F\u20E3 \u2757')).toEqual(
|
||||
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" /> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg" /> <img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" />');
|
||||
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/packs/emoji/2757.svg" /> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/packs/emoji/23-20e3.svg" /> <img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/packs/emoji/2757.svg" />');
|
||||
expect(emojify('foo \u2757 #\uFE0F\u20E3 bar')).toEqual(
|
||||
'foo <img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" /> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg" /> bar');
|
||||
'foo <img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/packs/emoji/2757.svg" /> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/packs/emoji/23-20e3.svg" /> bar');
|
||||
});
|
||||
|
||||
it('ignores unicode inside of tags', () => {
|
||||
|
@ -46,16 +46,16 @@ describe('emoji', () => {
|
|||
});
|
||||
|
||||
it('does multiple emoji properly (issue 5188)', () => {
|
||||
expect(emojify('👌🌈💕')).toEqual('<img draggable="false" class="emojione" alt="👌" title=":ok_hand:" src="/emoji/1f44c.svg" /><img draggable="false" class="emojione" alt="🌈" title=":rainbow:" src="/emoji/1f308.svg" /><img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/emoji/1f495.svg" />');
|
||||
expect(emojify('👌 🌈 💕')).toEqual('<img draggable="false" class="emojione" alt="👌" title=":ok_hand:" src="/emoji/1f44c.svg" /> <img draggable="false" class="emojione" alt="🌈" title=":rainbow:" src="/emoji/1f308.svg" /> <img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/emoji/1f495.svg" />');
|
||||
expect(emojify('👌🌈💕')).toEqual('<img draggable="false" class="emojione" alt="👌" title=":ok_hand:" src="/packs/emoji/1f44c.svg" /><img draggable="false" class="emojione" alt="🌈" title=":rainbow:" src="/packs/emoji/1f308.svg" /><img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/packs/emoji/1f495.svg" />');
|
||||
expect(emojify('👌 🌈 💕')).toEqual('<img draggable="false" class="emojione" alt="👌" title=":ok_hand:" src="/packs/emoji/1f44c.svg" /> <img draggable="false" class="emojione" alt="🌈" title=":rainbow:" src="/packs/emoji/1f308.svg" /> <img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/packs/emoji/1f495.svg" />');
|
||||
});
|
||||
|
||||
it('does an emoji that has no shortcode', () => {
|
||||
expect(emojify('👁🗨')).toEqual('<img draggable="false" class="emojione" alt="👁🗨" title="" src="/emoji/1f441-200d-1f5e8.svg" />');
|
||||
expect(emojify('👁🗨')).toEqual('<img draggable="false" class="emojione" alt="👁🗨" title="" src="/packs/emoji/1f441-200d-1f5e8.svg" />');
|
||||
});
|
||||
|
||||
it('does an emoji whose filename is irregular', () => {
|
||||
expect(emojify('↙️')).toEqual('<img draggable="false" class="emojione" alt="↙️" title=":arrow_lower_left:" src="/emoji/2199.svg" />');
|
||||
expect(emojify('↙️')).toEqual('<img draggable="false" class="emojione" alt="↙️" title=":arrow_lower_left:" src="/packs/emoji/2199.svg" />');
|
||||
});
|
||||
|
||||
it('avoid emojifying on invisible text', () => {
|
||||
|
@ -67,16 +67,16 @@ describe('emoji', () => {
|
|||
|
||||
it('avoid emojifying on invisible text with nested tags', () => {
|
||||
expect(emojify('<span class="invisible">😄<span class="foo">bar</span>😴</span>😇'))
|
||||
.toEqual('<span class="invisible">😄<span class="foo">bar</span>😴</span><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg" />');
|
||||
.toEqual('<span class="invisible">😄<span class="foo">bar</span>😴</span><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/packs/emoji/1f607.svg" />');
|
||||
expect(emojify('<span class="invisible">😄<span class="invisible">😕</span>😴</span>😇'))
|
||||
.toEqual('<span class="invisible">😄<span class="invisible">😕</span>😴</span><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg" />');
|
||||
.toEqual('<span class="invisible">😄<span class="invisible">😕</span>😴</span><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/packs/emoji/1f607.svg" />');
|
||||
expect(emojify('<span class="invisible">😄<br/>😴</span>😇'))
|
||||
.toEqual('<span class="invisible">😄<br/>😴</span><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg" />');
|
||||
.toEqual('<span class="invisible">😄<br/>😴</span><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/packs/emoji/1f607.svg" />');
|
||||
});
|
||||
|
||||
it('skips the textual presentation VS15 character', () => {
|
||||
expect(emojify('✴︎')) // This is U+2734 EIGHT POINTED BLACK STAR then U+FE0E VARIATION SELECTOR-15
|
||||
.toEqual('<img draggable="false" class="emojione" alt="✴" title=":eight_pointed_black_star:" src="/emoji/2734.svg" />');
|
||||
.toEqual('<img draggable="false" class="emojione" alt="✴" title=":eight_pointed_black_star:" src="/packs/emoji/2734.svg" />');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import unicodeMapping from './emoji_unicode_mapping_light';
|
||||
import Trie from 'substring-trie';
|
||||
import { join } from 'path';
|
||||
import { FE_SUBDIRECTORY } from 'soapbox/build_config';
|
||||
import { joinPublicPath } from 'soapbox/utils/static';
|
||||
|
||||
const trie = new Trie(Object.keys(unicodeMapping));
|
||||
|
||||
|
@ -62,7 +61,8 @@ const emojify = (str, customEmojis = {}, autoplay = false) => {
|
|||
} else { // matched to unicode emoji
|
||||
const { filename, shortCode } = unicodeMapping[match];
|
||||
const title = shortCode ? `:${shortCode}:` : '';
|
||||
replacement = `<img draggable="false" class="emojione" alt="${match}" title="${title}" src="${join(FE_SUBDIRECTORY, 'emoji', `${filename}.svg`)}" />`;
|
||||
const src = joinPublicPath(`packs/emoji/${filename}.svg`);
|
||||
replacement = `<img draggable="false" class="emojione" alt="${match}" title="${title}" src="${src}" />`;
|
||||
rend = i + match.length;
|
||||
// If the matched character was followed by VS15 (for selecting text presentation), skip it.
|
||||
if (str.codePointAt(rend) === 65038) {
|
||||
|
|
|
@ -2,23 +2,55 @@ import React from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { fetchFavouritedStatuses, expandFavouritedStatuses } from '../../actions/favourites';
|
||||
import { fetchFavouritedStatuses, expandFavouritedStatuses, fetchAccountFavouritedStatuses, expandAccountFavouritedStatuses } from '../../actions/favourites';
|
||||
import Column from '../ui/components/column';
|
||||
import StatusList from '../../components/status_list';
|
||||
import { injectIntl, FormattedMessage } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { debounce } from 'lodash';
|
||||
import MissingIndicator from 'soapbox/components/missing_indicator';
|
||||
import { fetchAccount, fetchAccountByUsername } from '../../actions/accounts';
|
||||
import LoadingIndicator from '../../components/loading_indicator';
|
||||
|
||||
const mapStateToProps = (state, { params }) => {
|
||||
const username = params.username || '';
|
||||
const me = state.get('me');
|
||||
const meUsername = state.getIn(['accounts', me, 'username']);
|
||||
const meUsername = state.getIn(['accounts', me, 'username'], '');
|
||||
|
||||
const isMyAccount = (username.toLowerCase() === meUsername.toLowerCase());
|
||||
|
||||
if (isMyAccount) {
|
||||
return {
|
||||
isMyAccount,
|
||||
statusIds: state.getIn(['status_lists', 'favourites', 'items']),
|
||||
isLoading: state.getIn(['status_lists', 'favourites', 'isLoading'], true),
|
||||
hasMore: !!state.getIn(['status_lists', 'favourites', 'next']),
|
||||
};
|
||||
}
|
||||
|
||||
const accounts = state.getIn(['accounts']);
|
||||
const accountFetchError = (state.getIn(['accounts', -1, 'username'], '').toLowerCase() === username.toLowerCase());
|
||||
|
||||
let accountId = -1;
|
||||
if (accountFetchError) {
|
||||
accountId = null;
|
||||
} else {
|
||||
const account = accounts.find(acct => username.toLowerCase() === acct.getIn(['acct'], '').toLowerCase());
|
||||
accountId = account ? account.getIn(['id'], null) : -1;
|
||||
}
|
||||
|
||||
const isBlocked = state.getIn(['relationships', accountId, 'blocked_by'], false);
|
||||
const unavailable = (me === accountId) ? false : isBlocked;
|
||||
|
||||
return {
|
||||
isMyAccount: (username.toLowerCase() === meUsername.toLowerCase()),
|
||||
statusIds: state.getIn(['status_lists', 'favourites', 'items']),
|
||||
isLoading: state.getIn(['status_lists', 'favourites', 'isLoading'], true),
|
||||
hasMore: !!state.getIn(['status_lists', 'favourites', 'next']),
|
||||
isMyAccount,
|
||||
accountId,
|
||||
unavailable,
|
||||
username,
|
||||
isAccount: !!state.getIn(['accounts', accountId]),
|
||||
statusIds: state.getIn(['status_lists', `favourites:${accountId}`, 'items'], []),
|
||||
isLoading: state.getIn(['status_lists', `favourites:${accountId}`, 'isLoading'], true),
|
||||
hasMore: !!state.getIn(['status_lists', `favourites:${accountId}`, 'next']),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -36,17 +68,43 @@ class Favourites extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.props.dispatch(fetchFavouritedStatuses());
|
||||
const { accountId, isMyAccount, username } = this.props;
|
||||
|
||||
if (isMyAccount)
|
||||
this.props.dispatch(fetchFavouritedStatuses());
|
||||
else {
|
||||
if (accountId && accountId !== -1) {
|
||||
this.props.dispatch(fetchAccount(accountId));
|
||||
this.props.dispatch(fetchAccountFavouritedStatuses(accountId));
|
||||
} else {
|
||||
this.props.dispatch(fetchAccountByUsername(username));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { accountId, isMyAccount } = this.props;
|
||||
|
||||
if (!isMyAccount && accountId && accountId !== -1 && (accountId !== prevProps.accountId && accountId)) {
|
||||
this.props.dispatch(fetchAccount(accountId));
|
||||
this.props.dispatch(fetchAccountFavouritedStatuses(accountId));
|
||||
}
|
||||
}
|
||||
|
||||
handleLoadMore = debounce(() => {
|
||||
this.props.dispatch(expandFavouritedStatuses());
|
||||
const { accountId, isMyAccount } = this.props;
|
||||
|
||||
if (isMyAccount) {
|
||||
this.props.dispatch(expandFavouritedStatuses());
|
||||
} else {
|
||||
this.props.dispatch(expandAccountFavouritedStatuses(accountId));
|
||||
}
|
||||
}, 300, { leading: true })
|
||||
|
||||
render() {
|
||||
const { statusIds, hasMore, isLoading, isMyAccount } = this.props;
|
||||
const { statusIds, isLoading, hasMore, isMyAccount, isAccount, accountId, unavailable } = this.props;
|
||||
|
||||
if (!isMyAccount) {
|
||||
if (!isMyAccount && !isAccount && accountId !== -1) {
|
||||
return (
|
||||
<Column>
|
||||
<MissingIndicator />
|
||||
|
@ -54,7 +112,27 @@ class Favourites extends ImmutablePureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
const emptyMessage = <FormattedMessage id='empty_column.favourited_statuses' defaultMessage="You don't have any liked posts yet. When you like one, it will show up here." />;
|
||||
if (accountId === -1) {
|
||||
return (
|
||||
<Column>
|
||||
<LoadingIndicator />
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
if (unavailable) {
|
||||
return (
|
||||
<Column>
|
||||
<div className='empty-column-indicator'>
|
||||
<FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />
|
||||
</div>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
const emptyMessage = isMyAccount
|
||||
? <FormattedMessage id='empty_column.favourited_statuses' defaultMessage="You don't have any liked posts yet. When you like one, it will show up here." />
|
||||
: <FormattedMessage id='empty_column.account_favourited_statuses' defaultMessage="This user doesn't have any liked posts yet." />;
|
||||
|
||||
return (
|
||||
<Column>
|
||||
|
|
|
@ -12,7 +12,7 @@ import MissingIndicator from 'soapbox/components/missing_indicator';
|
|||
const mapStateToProps = (state, { params }) => {
|
||||
const username = params.username || '';
|
||||
const me = state.get('me');
|
||||
const meUsername = state.getIn(['accounts', me, 'username']);
|
||||
const meUsername = state.getIn(['accounts', me, 'username'], '');
|
||||
return {
|
||||
isMyAccount: (username.toLowerCase() === meUsername.toLowerCase()),
|
||||
statusIds: state.getIn(['status_lists', 'pins', 'items']),
|
||||
|
|
|
@ -73,7 +73,7 @@ class Reactions extends ImmutablePureComponent {
|
|||
|
||||
render() {
|
||||
const { params, reactions, accounts, status } = this.props;
|
||||
const { username, statusId, reaction } = params;
|
||||
const { username, statusId } = params;
|
||||
|
||||
const back = `/@${username}/posts/${statusId}`;
|
||||
|
||||
|
@ -95,7 +95,6 @@ class Reactions extends ImmutablePureComponent {
|
|||
|
||||
const emptyMessage = <FormattedMessage id='status.reactions.empty' defaultMessage='No one has reacted to this post yet. When someone does, they will show up here.' />;
|
||||
|
||||
console.log(params.reaction);
|
||||
return (
|
||||
<Column back={back}>
|
||||
{
|
||||
|
|
|
@ -51,6 +51,8 @@ const messages = defineMessages({
|
|||
displayFqnLabel: { id: 'soapbox_config.display_fqn_label', defaultMessage: 'Display domain (eg @user@domain) for local accounts.' },
|
||||
greentextLabel: { id: 'soapbox_config.greentext_label', defaultMessage: 'Enable greentext support' },
|
||||
promoPanelIconsLink: { id: 'soapbox_config.hints.promo_panel_icons.link', defaultMessage: 'Soapbox Icons List' },
|
||||
authenticatedProfileLabel: { id: 'soapbox_config.authenticated_profile_label', defaultMessage: 'Profiles require authentication' },
|
||||
authenticatedProfileHint: { id: 'soapbox_config.authenticated_profile_hint', defaultMessage: 'Users must be logged-in to view replies and media on user profiles.' },
|
||||
});
|
||||
|
||||
const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
|
||||
|
@ -279,6 +281,13 @@ class SoapboxConfig extends ImmutablePureComponent {
|
|||
checked={soapbox.get('greentext') === true}
|
||||
onChange={this.handleChange(['greentext'], (e) => e.target.checked)}
|
||||
/>
|
||||
<Checkbox
|
||||
name='authenticatedProfile'
|
||||
label={intl.formatMessage(messages.authenticatedProfileLabel)}
|
||||
hint={intl.formatMessage(messages.authenticatedProfileHint)}
|
||||
checked={soapbox.get('authenticatedProfile') === true}
|
||||
onChange={this.handleChange(['authenticatedProfile'], (e) => e.target.checked)}
|
||||
/>
|
||||
</FieldsGroup>
|
||||
<FieldsGroup>
|
||||
<div className='input with_block_label popup'>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Immutable from 'immutable';
|
||||
import { is, fromJS } from 'immutable';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import punycode from 'punycode';
|
||||
import classnames from 'classnames';
|
||||
|
@ -77,7 +77,7 @@ export default class Card extends React.PureComponent {
|
|||
};
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (!Immutable.is(prevProps.card, this.props.card)) {
|
||||
if (!is(prevProps.card, this.props.card)) {
|
||||
this.setState({ embedded: false });
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ export default class Card extends React.PureComponent {
|
|||
const { card, onOpenMedia } = this.props;
|
||||
|
||||
onOpenMedia(
|
||||
Immutable.fromJS([
|
||||
fromJS([
|
||||
{
|
||||
type: 'image',
|
||||
url: card.get('embed_url'),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Immutable from 'immutable';
|
||||
import { OrderedSet as ImmutableOrderedSet } from 'immutable';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
|
@ -71,11 +71,11 @@ const makeMapStateToProps = () => {
|
|||
(_, { id }) => id,
|
||||
state => state.getIn(['contexts', 'inReplyTos']),
|
||||
], (statusId, inReplyTos) => {
|
||||
let ancestorsIds = Immutable.OrderedSet();
|
||||
let ancestorsIds = ImmutableOrderedSet();
|
||||
let id = statusId;
|
||||
|
||||
while (id) {
|
||||
ancestorsIds = Immutable.OrderedSet([id]).union(ancestorsIds);
|
||||
ancestorsIds = ImmutableOrderedSet([id]).union(ancestorsIds);
|
||||
id = inReplyTos.get(id);
|
||||
}
|
||||
|
||||
|
@ -86,7 +86,7 @@ const makeMapStateToProps = () => {
|
|||
(_, { id }) => id,
|
||||
state => state.getIn(['contexts', 'replies']),
|
||||
], (statusId, contextReplies) => {
|
||||
let descendantsIds = Immutable.OrderedSet();
|
||||
let descendantsIds = ImmutableOrderedSet();
|
||||
const ids = [statusId];
|
||||
|
||||
while (ids.length > 0) {
|
||||
|
@ -109,8 +109,8 @@ const makeMapStateToProps = () => {
|
|||
|
||||
const mapStateToProps = (state, props) => {
|
||||
const status = getStatus(state, { id: props.params.statusId });
|
||||
let ancestorsIds = Immutable.List();
|
||||
let descendantsIds = Immutable.List();
|
||||
let ancestorsIds = ImmutableOrderedSet();
|
||||
let descendantsIds = ImmutableOrderedSet();
|
||||
|
||||
if (status) {
|
||||
ancestorsIds = getAncestorsIds(state, { id: state.getIn(['contexts', 'inReplyTos', status.get('id')]) });
|
||||
|
@ -146,8 +146,8 @@ class Status extends ImmutablePureComponent {
|
|||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
status: ImmutablePropTypes.map,
|
||||
ancestorsIds: ImmutablePropTypes.list,
|
||||
descendantsIds: ImmutablePropTypes.list,
|
||||
ancestorsIds: ImmutablePropTypes.orderedSet,
|
||||
descendantsIds: ImmutablePropTypes.orderedSet,
|
||||
intl: PropTypes.object.isRequired,
|
||||
askReplyConfirmation: PropTypes.bool,
|
||||
domain: PropTypes.string,
|
||||
|
|
|
@ -14,13 +14,13 @@ import FocalPointModal from './focal_point_modal';
|
|||
import HotkeysModal from './hotkeys_modal';
|
||||
import ComposeModal from './compose_modal';
|
||||
import UnauthorizedModal from './unauthorized_modal';
|
||||
import CryptoDonateModal from './crypto_donate_modal';
|
||||
import EditFederationModal from './edit_federation_modal';
|
||||
|
||||
import {
|
||||
MuteModal,
|
||||
ReportModal,
|
||||
EmbedModal,
|
||||
CryptoDonateModal,
|
||||
ListEditor,
|
||||
ListAdder,
|
||||
} from '../../../features/ui/util/async-components';
|
||||
|
@ -41,7 +41,7 @@ const MODAL_COMPONENTS = {
|
|||
'HOTKEYS': () => Promise.resolve({ default: HotkeysModal }),
|
||||
'COMPOSE': () => Promise.resolve({ default: ComposeModal }),
|
||||
'UNAUTHORIZED': () => Promise.resolve({ default: UnauthorizedModal }),
|
||||
'CRYPTO_DONATE': () => Promise.resolve({ default: CryptoDonateModal }),
|
||||
'CRYPTO_DONATE': CryptoDonateModal,
|
||||
'EDIT_FEDERATION': () => Promise.resolve({ default: EditFederationModal }),
|
||||
};
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import BundleContainer from 'soapbox/features/ui/containers/bundle_container';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import Icon from 'soapbox/components/icon';
|
||||
import VerificationBadge from 'soapbox/components/verification_badge';
|
||||
|
@ -13,7 +14,7 @@ import { List as ImmutableList } from 'immutable';
|
|||
import { getAcct, isAdmin, isModerator, isLocal } from 'soapbox/utils/accounts';
|
||||
import { displayFqn } from 'soapbox/utils/state';
|
||||
import classNames from 'classnames';
|
||||
import CryptoAddress from 'soapbox/features/crypto_donate/components/crypto_address';
|
||||
import { CryptoAddress } from 'soapbox/features/ui/util/async-components';
|
||||
|
||||
const TICKER_REGEX = /\$([a-zA-Z]*)/i;
|
||||
|
||||
|
@ -143,7 +144,15 @@ class ProfileInfoPanel extends ImmutablePureComponent {
|
|||
|
||||
{fields.map((pair, i) =>
|
||||
isTicker(pair.get('name', '')) ? (
|
||||
<CryptoAddress key={i} ticker={getTicker(pair.get('name')).toLowerCase()} address={pair.get('value_plain')} />
|
||||
<BundleContainer fetchComponent={CryptoAddress}>
|
||||
{Component => (
|
||||
<Component
|
||||
key={i}
|
||||
ticker={getTicker(pair.get('name')).toLowerCase()}
|
||||
address={pair.get('value_plain')}
|
||||
/>
|
||||
)}
|
||||
</BundleContainer>
|
||||
) : (
|
||||
<dl className='profile-info-panel-content__fields__item' key={i}>
|
||||
<dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} />
|
||||
|
|
|
@ -7,6 +7,7 @@ import { defineMessages, injectIntl } from 'react-intl';
|
|||
import { connect } from 'react-redux';
|
||||
import { Switch, withRouter } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import SoapboxPropTypes from 'soapbox/utils/soapbox_prop_types';
|
||||
import NotificationsContainer from './containers/notifications_container';
|
||||
import LoadingBarContainer from './containers/loading_bar_container';
|
||||
|
@ -44,6 +45,7 @@ import ProfileHoverCard from 'soapbox/components/profile_hover_card';
|
|||
import { getAccessToken } from 'soapbox/utils/auth';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
import { fetchCustomEmojis } from 'soapbox/actions/custom_emojis';
|
||||
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
|
||||
|
||||
import {
|
||||
Status,
|
||||
|
@ -121,6 +123,7 @@ const mapStateToProps = state => {
|
|||
const me = state.get('me');
|
||||
const account = state.getIn(['accounts', me]);
|
||||
const instance = state.get('instance');
|
||||
const soapbox = getSoapboxConfig(state);
|
||||
|
||||
return {
|
||||
dropdownMenuIsOpen: state.getIn(['dropdown_menu', 'openId']) !== null,
|
||||
|
@ -129,6 +132,7 @@ const mapStateToProps = state => {
|
|||
me,
|
||||
account,
|
||||
features: getFeatures(instance),
|
||||
soapbox,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -166,6 +170,7 @@ class SwitchingColumnsArea extends React.PureComponent {
|
|||
children: PropTypes.node,
|
||||
location: PropTypes.object,
|
||||
onLayoutChange: PropTypes.func.isRequired,
|
||||
soapbox: ImmutablePropTypes.map.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
|
@ -194,7 +199,8 @@ class SwitchingColumnsArea extends React.PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
const { children, soapbox } = this.props;
|
||||
const authenticatedProfile = soapbox.get('authenticatedProfile');
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
|
@ -254,10 +260,10 @@ class SwitchingColumnsArea extends React.PureComponent {
|
|||
<WrappedRoute path='/mutes' page={DefaultPage} component={Mutes} content={children} />
|
||||
<WrappedRoute path='/filters' page={DefaultPage} component={Filters} content={children} />
|
||||
<WrappedRoute path='/@:username' publicRoute exact component={AccountTimeline} page={ProfilePage} content={children} />
|
||||
<WrappedRoute path='/@:username/with_replies' component={AccountTimeline} page={ProfilePage} content={children} componentParams={{ withReplies: true }} />
|
||||
<WrappedRoute path='/@:username/followers' component={Followers} page={ProfilePage} content={children} />
|
||||
<WrappedRoute path='/@:username/following' component={Following} page={ProfilePage} content={children} />
|
||||
<WrappedRoute path='/@:username/media' component={AccountGallery} page={ProfilePage} content={children} />
|
||||
<WrappedRoute path='/@:username/with_replies' publicRoute={!authenticatedProfile} component={AccountTimeline} page={ProfilePage} content={children} componentParams={{ withReplies: true }} />
|
||||
<WrappedRoute path='/@:username/followers' publicRoute={!authenticatedProfile} component={Followers} page={ProfilePage} content={children} />
|
||||
<WrappedRoute path='/@:username/following' publicRoute={!authenticatedProfile} component={Following} page={ProfilePage} content={children} />
|
||||
<WrappedRoute path='/@:username/media' publicRoute={!authenticatedProfile} component={AccountGallery} page={ProfilePage} content={children} />
|
||||
<WrappedRoute path='/@:username/tagged/:tag' exact component={AccountTimeline} page={ProfilePage} content={children} />
|
||||
<WrappedRoute path='/@:username/favorites' component={FavouritedStatuses} page={ProfilePage} content={children} />
|
||||
<WrappedRoute path='/@:username/pins' component={PinnedStatuses} page={ProfilePage} content={children} />
|
||||
|
@ -314,6 +320,7 @@ class UI extends React.PureComponent {
|
|||
streamingUrl: PropTypes.string,
|
||||
account: PropTypes.object,
|
||||
features: PropTypes.object.isRequired,
|
||||
soapbox: ImmutablePropTypes.map.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
|
@ -594,7 +601,7 @@ class UI extends React.PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { streamingUrl, features } = this.props;
|
||||
const { streamingUrl, features, soapbox } = this.props;
|
||||
const { draggingOver, mobile } = this.state;
|
||||
const { intl, children, location, dropdownMenuIsOpen, me } = this.props;
|
||||
|
||||
|
@ -644,7 +651,7 @@ class UI extends React.PureComponent {
|
|||
<HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef} attach={window} focused>
|
||||
<div className={classnames} ref={this.setRef} style={style}>
|
||||
<TabsBar />
|
||||
<SwitchingColumnsArea location={location} onLayoutChange={this.handleLayoutChange}>
|
||||
<SwitchingColumnsArea location={location} onLayoutChange={this.handleLayoutChange} soapbox={soapbox}>
|
||||
{children}
|
||||
</SwitchingColumnsArea>
|
||||
|
||||
|
|
|
@ -250,6 +250,18 @@ export function CryptoDonate() {
|
|||
return import(/* webpackChunkName: "features/crypto_donate" */'../../crypto_donate');
|
||||
}
|
||||
|
||||
export function CryptoDonatePanel() {
|
||||
return import(/* webpackChunkName: "features/crypto_donate" */'../../crypto_donate/components/crypto_donate_panel');
|
||||
}
|
||||
|
||||
export function CryptoAddress() {
|
||||
return import(/* webpackChunkName: "features/crypto_donate" */'../../crypto_donate/components/crypto_address');
|
||||
}
|
||||
|
||||
export function CryptoDonateModal() {
|
||||
return import(/* webpackChunkName: "features/crypto_donate" */'../components/crypto_donate_modal');
|
||||
}
|
||||
|
||||
export function ScheduledStatuses() {
|
||||
return import(/* webpackChunkName: "features/scheduled_statuses" */'../../scheduled_statuses');
|
||||
}
|
||||
|
@ -265,3 +277,7 @@ export function FederationRestrictions() {
|
|||
export function Aliases() {
|
||||
return import(/* webpackChunkName: "features/aliases" */'../../aliases');
|
||||
}
|
||||
|
||||
export function ScheduleForm() {
|
||||
return import(/* webpackChunkName: "features/compose" */'../../compose/components/schedule_form');
|
||||
}
|
||||
|
|
|
@ -312,6 +312,7 @@
|
|||
"emoji_button.search_results": "Wyniki wyszukiwania",
|
||||
"emoji_button.symbols": "Symbole",
|
||||
"emoji_button.travel": "Podróże i miejsca",
|
||||
"empty_column.account_favourited_statuses": "Ten użytkownik nie polubił jeszcze żadnego wpisu.",
|
||||
"empty_column.account_timeline": "Brak wpisów tutaj!",
|
||||
"empty_column.account_unavailable": "Profil niedostępny",
|
||||
"empty_column.aliases": "Nie utworzyłeś(-aś) jeszcze żadnego aliasu konta.",
|
||||
|
@ -321,7 +322,7 @@
|
|||
"empty_column.community": "Lokalna oś czasu jest pusta. Napisz coś publicznie, aby zagaić!",
|
||||
"empty_column.direct": "Nie masz żadnych wiadomości bezpośrednich. Kiedy dostaniesz lub wyślesz jakąś, pojawi się ona tutaj.",
|
||||
"empty_column.domain_blocks": "Brak ukrytych domen.",
|
||||
"empty_column.favourited_statuses": "Nie dodałeś(-aś) żadnego wpisu do ulubionych. Kiedy to zrobisz, pojawi się on tutaj.",
|
||||
"empty_column.favourited_statuses": "Nie polubiłeś(-aś) żadnego wpisu. Kiedy to zrobisz, pojawi się on tutaj.",
|
||||
"empty_column.favourites": "Nikt nie dodał tego wpisu do ulubionych. Gdy ktoś to zrobi, pojawi się tutaj.",
|
||||
"empty_column.filters": "Nie wyciszyłeś(-aś) jeszcze żadnego słowa.",
|
||||
"empty_column.follow_requests": "Nie masz żadnych próśb o możliwość śledzenia. Kiedy ktoś utworzy ją, pojawi się tutaj.",
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
import './wdyr';
|
||||
import './precheck';
|
||||
// FIXME: Push notifications are temporarily removed
|
||||
// import * as registerPushNotifications from './actions/push_notifications';
|
||||
|
@ -10,12 +9,16 @@ import React from 'react';
|
|||
import ReactDOM from 'react-dom';
|
||||
import * as OfflinePluginRuntime from '@lcdp/offline-plugin/runtime';
|
||||
import * as perf from './performance';
|
||||
import * as monitoring from './monitoring';
|
||||
import ready from './ready';
|
||||
import { NODE_ENV } from 'soapbox/build_config';
|
||||
|
||||
function main() {
|
||||
perf.start('main()');
|
||||
|
||||
// Sentry
|
||||
monitoring.start();
|
||||
|
||||
ready(() => {
|
||||
const mountNode = document.getElementById('soapbox');
|
||||
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
import { join } from 'path';
|
||||
import { FE_SUBDIRECTORY } from 'soapbox/build_config';
|
||||
|
||||
const createAudio = sources => {
|
||||
const audio = new Audio();
|
||||
sources.forEach(({ type, src }) => {
|
||||
|
@ -31,21 +28,21 @@ export default function soundsMiddleware() {
|
|||
const soundCache = {
|
||||
boop: createAudio([
|
||||
{
|
||||
src: join(FE_SUBDIRECTORY, '/sounds/boop.ogg'),
|
||||
src: require('../../sounds/boop.ogg'),
|
||||
type: 'audio/ogg',
|
||||
},
|
||||
{
|
||||
src: join(FE_SUBDIRECTORY, '/sounds/boop.mp3'),
|
||||
src: require('../../sounds/boop.mp3'),
|
||||
type: 'audio/mpeg',
|
||||
},
|
||||
]),
|
||||
chat: createAudio([
|
||||
{
|
||||
src: join(FE_SUBDIRECTORY, '/sounds/chat.oga'),
|
||||
src: require('../../sounds/chat.oga'),
|
||||
type: 'audio/ogg',
|
||||
},
|
||||
{
|
||||
src: join(FE_SUBDIRECTORY, '/sounds/chat.mp3'),
|
||||
src: require('../../sounds/chat.mp3'),
|
||||
type: 'audio/mpeg',
|
||||
},
|
||||
]),
|
||||
|
|
27
app/soapbox/monitoring.js
Normal file
27
app/soapbox/monitoring.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { NODE_ENV, SENTRY_DSN } from 'soapbox/build_config';
|
||||
|
||||
export const start = () => {
|
||||
Promise.all([
|
||||
import(/* webpackChunkName: "error" */'@sentry/react'),
|
||||
import(/* webpackChunkName: "error" */'@sentry/tracing'),
|
||||
]).then(([Sentry, { Integrations: Integrations }]) => {
|
||||
Sentry.init({
|
||||
dsn: SENTRY_DSN,
|
||||
environment: NODE_ENV,
|
||||
debug: false,
|
||||
integrations: [new Integrations.BrowserTracing()],
|
||||
|
||||
// We recommend adjusting this value in production, or using tracesSampler
|
||||
// for finer control
|
||||
tracesSampleRate: 1.0,
|
||||
});
|
||||
}).catch(console.error);
|
||||
};
|
||||
|
||||
export const captureException = error => {
|
||||
import(/* webpackChunkName: "error" */'@sentry/react')
|
||||
.then(Sentry => {
|
||||
Sentry.captureException(error);
|
||||
})
|
||||
.catch(console.error);
|
||||
};
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import { Link } from 'react-router-dom';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import BundleContainer from '../features/ui/containers/bundle_container';
|
||||
import ComposeFormContainer from '../features/compose/containers/compose_form_container';
|
||||
import Avatar from '../components/avatar';
|
||||
import UserPanel from 'soapbox/features/ui/components/user_panel';
|
||||
|
@ -9,7 +10,7 @@ import WhoToFollowPanel from 'soapbox/features/ui/components/who_to_follow_panel
|
|||
import TrendsPanel from 'soapbox/features/ui/components/trends_panel';
|
||||
import PromoPanel from 'soapbox/features/ui/components/promo_panel';
|
||||
import FundingPanel from 'soapbox/features/ui/components/funding_panel';
|
||||
import CryptoDonatePanel from 'soapbox/features/crypto_donate/components/crypto_donate_panel';
|
||||
import { CryptoDonatePanel } from 'soapbox/features/ui/util/async-components';
|
||||
// import GroupSidebarPanel from '../features/groups/sidebar_panel';
|
||||
import FeaturesPanel from 'soapbox/features/ui/components/features_panel';
|
||||
import SignUpPanel from 'soapbox/features/ui/components/sign_up_panel';
|
||||
|
@ -58,7 +59,11 @@ class HomePage extends ImmutablePureComponent {
|
|||
<div className='columns-area__panels__pane__inner'>
|
||||
<UserPanel accountId={me} key='user-panel' />
|
||||
{showFundingPanel && <FundingPanel key='funding-panel' />}
|
||||
{showCryptoDonatePanel && <CryptoDonatePanel limit={cryptoLimit} key='crypto-panel' />}
|
||||
{showCryptoDonatePanel && (
|
||||
<BundleContainer fetchComponent={CryptoDonatePanel}>
|
||||
{Component => <Component limit={cryptoLimit} key='crypto-panel' />}
|
||||
</BundleContainer>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import Immutable from 'immutable';
|
||||
import { Map as ImmutableMap } from 'immutable';
|
||||
import {
|
||||
DROPDOWN_MENU_OPEN,
|
||||
DROPDOWN_MENU_CLOSE,
|
||||
} from '../actions/dropdown_menu';
|
||||
|
||||
const initialState = Immutable.Map({ openId: null, placement: null, keyboard: false });
|
||||
const initialState = ImmutableMap({ openId: null, placement: null, keyboard: false });
|
||||
|
||||
export default function dropdownMenu(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import Immutable from 'immutable';
|
||||
import { Map as ImmutableMap } from 'immutable';
|
||||
|
||||
import {
|
||||
MUTES_INIT_MODAL,
|
||||
MUTES_TOGGLE_HIDE_NOTIFICATIONS,
|
||||
} from '../actions/mutes';
|
||||
|
||||
const initialState = Immutable.Map({
|
||||
new: Immutable.Map({
|
||||
const initialState = ImmutableMap({
|
||||
new: ImmutableMap({
|
||||
isSubmitting: false,
|
||||
account: null,
|
||||
notifications: true,
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { SET_BROWSER_SUPPORT, SET_SUBSCRIPTION, CLEAR_SUBSCRIPTION, SET_ALERTS } from '../actions/push_notifications';
|
||||
import Immutable from 'immutable';
|
||||
import { Map as ImmutableMap } from 'immutable';
|
||||
|
||||
const initialState = Immutable.Map({
|
||||
const initialState = ImmutableMap({
|
||||
subscription: null,
|
||||
alerts: new Immutable.Map({
|
||||
alerts: new ImmutableMap({
|
||||
follow: false,
|
||||
follow_request: false,
|
||||
favourite: false,
|
||||
|
@ -19,11 +19,11 @@ export default function push_subscriptions(state = initialState, action) {
|
|||
switch(action.type) {
|
||||
case SET_SUBSCRIPTION:
|
||||
return state
|
||||
.set('subscription', new Immutable.Map({
|
||||
.set('subscription', new ImmutableMap({
|
||||
id: action.subscription.id,
|
||||
endpoint: action.subscription.endpoint,
|
||||
}))
|
||||
.set('alerts', new Immutable.Map(action.subscription.alerts))
|
||||
.set('alerts', new ImmutableMap(action.subscription.alerts))
|
||||
.set('isSubscribed', true);
|
||||
case SET_BROWSER_SUPPORT:
|
||||
return state.set('browserSupport', action.value);
|
||||
|
|
|
@ -5,6 +5,12 @@ import {
|
|||
FAVOURITED_STATUSES_EXPAND_REQUEST,
|
||||
FAVOURITED_STATUSES_EXPAND_SUCCESS,
|
||||
FAVOURITED_STATUSES_EXPAND_FAIL,
|
||||
ACCOUNT_FAVOURITED_STATUSES_FETCH_REQUEST,
|
||||
ACCOUNT_FAVOURITED_STATUSES_FETCH_SUCCESS,
|
||||
ACCOUNT_FAVOURITED_STATUSES_FETCH_FAIL,
|
||||
ACCOUNT_FAVOURITED_STATUSES_EXPAND_REQUEST,
|
||||
ACCOUNT_FAVOURITED_STATUSES_EXPAND_SUCCESS,
|
||||
ACCOUNT_FAVOURITED_STATUSES_EXPAND_FAIL,
|
||||
} from '../actions/favourites';
|
||||
import {
|
||||
BOOKMARKED_STATUSES_FETCH_REQUEST,
|
||||
|
@ -101,6 +107,16 @@ export default function statusLists(state = initialState, action) {
|
|||
return normalizeList(state, 'favourites', action.statuses, action.next);
|
||||
case FAVOURITED_STATUSES_EXPAND_SUCCESS:
|
||||
return appendToList(state, 'favourites', action.statuses, action.next);
|
||||
case ACCOUNT_FAVOURITED_STATUSES_FETCH_REQUEST:
|
||||
case ACCOUNT_FAVOURITED_STATUSES_EXPAND_REQUEST:
|
||||
return setLoading(state, `favourites:${action.accountId}`, true);
|
||||
case ACCOUNT_FAVOURITED_STATUSES_FETCH_FAIL:
|
||||
case ACCOUNT_FAVOURITED_STATUSES_EXPAND_FAIL:
|
||||
return setLoading(state, `favourites:${action.accountId}`, false);
|
||||
case ACCOUNT_FAVOURITED_STATUSES_FETCH_SUCCESS:
|
||||
return normalizeList(state, `favourites:${action.accountId}`, action.statuses, action.next);
|
||||
case ACCOUNT_FAVOURITED_STATUSES_EXPAND_SUCCESS:
|
||||
return appendToList(state, `favourites:${action.accountId}`, action.statuses, action.next);
|
||||
case BOOKMARKED_STATUSES_FETCH_REQUEST:
|
||||
case BOOKMARKED_STATUSES_EXPAND_REQUEST:
|
||||
return setLoading(state, 'bookmarks', true);
|
||||
|
|
|
@ -26,6 +26,8 @@ export const getFeatures = createSelector([
|
|||
accountAliasesAPI: v.software === 'Pleroma',
|
||||
resetPasswordAPI: v.software === 'Pleroma',
|
||||
exposableReactions: features.includes('exposable_reactions'),
|
||||
accountSubscriptions: v.software === 'Pleroma' && gte(v.version, '1.0.0'),
|
||||
unrestrictedLists: v.software === 'Pleroma',
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
/* eslint-disable no-case-declarations */
|
||||
import EXIF from 'exif-js';
|
||||
|
||||
const MAX_IMAGE_PIXELS = 2073600; // 1920x1080px
|
||||
|
||||
const _browser_quirks = {};
|
||||
|
@ -115,14 +113,16 @@ const getOrientation = (img, type = 'image/png') => new Promise(resolve => {
|
|||
return;
|
||||
}
|
||||
|
||||
EXIF.getData(img, () => {
|
||||
const orientation = EXIF.getTag(img, 'Orientation');
|
||||
if (orientation !== 1) {
|
||||
dropOrientationIfNeeded(orientation).then(resolve).catch(() => resolve(orientation));
|
||||
} else {
|
||||
resolve(orientation);
|
||||
}
|
||||
});
|
||||
import(/* webpackChunkName: "features/compose" */'exif-js').then(({ default: EXIF }) => {
|
||||
EXIF.getData(img, () => {
|
||||
const orientation = EXIF.getTag(img, 'Orientation');
|
||||
if (orientation !== 1) {
|
||||
dropOrientationIfNeeded(orientation).then(resolve).catch(() => resolve(orientation));
|
||||
} else {
|
||||
resolve(orientation);
|
||||
}
|
||||
});
|
||||
}).catch(() => {});
|
||||
});
|
||||
|
||||
const processImage = (img, { width, height, orientation, type = 'image/png', name = 'resized.png' }) => new Promise(resolve => {
|
||||
|
|
11
app/soapbox/utils/static.js
Normal file
11
app/soapbox/utils/static.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
/**
|
||||
* Static: functions related to static files.
|
||||
* @module soapbox/utils/static
|
||||
*/
|
||||
|
||||
import { join } from 'path';
|
||||
import { FE_SUBDIRECTORY } from 'soapbox/build_config';
|
||||
|
||||
export const joinPublicPath = (...paths) => {
|
||||
return join(FE_SUBDIRECTORY, ...paths);
|
||||
};
|
|
@ -1,7 +0,0 @@
|
|||
import React from 'react';
|
||||
import { NODE_ENV } from 'soapbox/build_config';
|
||||
|
||||
if (NODE_ENV === 'development') {
|
||||
const whyDidYouRender = require('@welldone-software/why-did-you-render');
|
||||
whyDidYouRender(React);
|
||||
}
|
|
@ -156,6 +156,7 @@
|
|||
display: flex !important;
|
||||
align-items: center !important;
|
||||
transition: 0.2s !important;
|
||||
background: var(--foreground-color);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--background-color) !important;
|
||||
|
|
|
@ -60,3 +60,18 @@ For example, if you want to host the build on `https://gleasonator.com/soapbox`,
|
|||
```sh
|
||||
NODE_ENV="production" FE_SUBDIRECTORY="/soapbox" yarn build
|
||||
```
|
||||
|
||||
### `SENTRY_DSN`
|
||||
|
||||
[Sentry](https://sentry.io/) endpoint for this custom build.
|
||||
|
||||
Sentry is an error monitoring service that may be optionally included.
|
||||
When an endpoint is not configured, it does nothing.
|
||||
|
||||
Sentry's backend was FOSS until 2019 when it moved to source-available, but a BSD-3 fork called [GlitchTip](https://glitchtip.com/) may also be used.
|
||||
|
||||
Options:
|
||||
|
||||
- Endpoint URL, eg `"https://abcdefg@app.glitchtip.com/123"`
|
||||
|
||||
Default: `""`
|
||||
|
|
|
@ -31,4 +31,11 @@ module.exports = {
|
|||
'<rootDir>/app',
|
||||
],
|
||||
'testEnvironment': 'jsdom',
|
||||
'moduleNameMapper': {
|
||||
'^.+.(css|styl|less|sass|scss|png|jpg|svg|ttf|woff|woff2)$': 'jest-transform-stub',
|
||||
},
|
||||
'transform': {
|
||||
'\\.[jt]sx?$': 'babel-jest',
|
||||
'.+\\.(css|styl|less|sass|scss|png|jpg|svg|ttf|woff|woff2)$': 'jest-transform-stub',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
"browserslist": [
|
||||
"> 0.5%",
|
||||
"last 2 versions",
|
||||
"Firefox ESR",
|
||||
"not IE 11",
|
||||
"not dead"
|
||||
],
|
||||
"dependencies": {
|
||||
|
@ -51,7 +51,9 @@
|
|||
"@fontsource/roboto": "^4.5.0",
|
||||
"@lcdp/offline-plugin": "^5.1.0",
|
||||
"@popperjs/core": "^2.4.4",
|
||||
"@welldone-software/why-did-you-render": "^6.2.0",
|
||||
"@sentry/browser": "^6.12.0",
|
||||
"@sentry/react": "^6.12.0",
|
||||
"@sentry/tracing": "^6.12.0",
|
||||
"array-includes": "^3.0.3",
|
||||
"autoprefixer": "^10.0.0",
|
||||
"axios": "^0.21.0",
|
||||
|
@ -92,6 +94,7 @@
|
|||
"intl-messageformat-parser": "^6.0.0",
|
||||
"intl-pluralrules": "^1.3.0",
|
||||
"is-nan": "^1.2.1",
|
||||
"jest-transform-stub": "^2.0.0",
|
||||
"jsdoc": "~3.6.7",
|
||||
"lodash": "^4.7.11",
|
||||
"mark-loader": "^0.1.6",
|
||||
|
|
|
@ -12,7 +12,6 @@ const settings = {
|
|||
test_root_path: `${FE_BUILD_DIR}-test`,
|
||||
cache_path: 'tmp/cache',
|
||||
resolved_paths: [],
|
||||
static_assets_extensions: [ '.jpg', '.jpeg', '.png', '.tiff', '.ico', '.svg', '.gif', '.eot', '.otf', '.ttf', '.woff', '.woff2', '.mp3', '.ogg', '.oga' ],
|
||||
extensions: [ '.mjs', '.js', '.sass', '.scss', '.css', '.module.sass', '.module.scss', '.module.css', '.png', '.svg', '.gif', '.jpeg', '.jpg' ],
|
||||
};
|
||||
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
// Note: You must restart bin/webpack-dev-server for changes to take effect
|
||||
console.log('Running in production mode'); // eslint-disable-line no-console
|
||||
|
||||
const { join } = require('path');
|
||||
const { merge } = require('webpack-merge');
|
||||
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
||||
const OfflinePlugin = require('@lcdp/offline-plugin');
|
||||
const sharedConfig = require('./shared');
|
||||
|
||||
const { FE_SUBDIRECTORY } = require(join(__dirname, '..', 'app', 'soapbox', 'build_config'));
|
||||
const joinPublicPath = (...paths) => join(FE_SUBDIRECTORY, ...paths);
|
||||
|
||||
module.exports = merge(sharedConfig, {
|
||||
mode: 'production',
|
||||
devtool: 'source-map',
|
||||
|
@ -25,37 +29,37 @@ module.exports = merge(sharedConfig, {
|
|||
new OfflinePlugin({
|
||||
caches: {
|
||||
main: [':rest:'],
|
||||
additional: [':externals:'],
|
||||
additional: [
|
||||
':externals:',
|
||||
'packs/images/32-*.png', // used in emoji-mart
|
||||
],
|
||||
optional: [
|
||||
'**/locale_*.js', // don't fetch every locale; the user only needs one
|
||||
'**/*_polyfills-*.js', // the user may not need polyfills
|
||||
'**/*.chunk.js', // only cache chunks when needed
|
||||
'**/*.chunk.css',
|
||||
'**/*.woff2', // the user may have system-fonts enabled
|
||||
// images/audio can be cached on-demand
|
||||
// images can be cached on-demand
|
||||
'**/*.png',
|
||||
'**/*.jpg',
|
||||
'**/*.jpeg',
|
||||
'**/*.svg',
|
||||
'**/*.mp3',
|
||||
'**/*.ogg',
|
||||
],
|
||||
},
|
||||
externals: [
|
||||
'/emoji/1f602.svg', // used for emoji picker dropdown
|
||||
'/emoji/sheet_13.png', // used in emoji-mart
|
||||
joinPublicPath('packs/emoji/1f602.svg'), // used for emoji picker dropdown
|
||||
|
||||
// Default emoji reacts
|
||||
'/emoji/1f44d.svg', // Thumbs up
|
||||
'/emoji/2764.svg', // Heart
|
||||
'/emoji/1f606.svg', // Laughing
|
||||
'/emoji/1f62e.svg', // Surprised
|
||||
'/emoji/1f622.svg', // Crying
|
||||
'/emoji/1f629.svg', // Weary
|
||||
'/emoji/1f621.svg', // Angry (Spinster)
|
||||
joinPublicPath('packs/emoji/1f44d.svg'), // Thumbs up
|
||||
joinPublicPath('packs/emoji/2764.svg'), // Heart
|
||||
joinPublicPath('packs/emoji/1f606.svg'), // Laughing
|
||||
joinPublicPath('packs/emoji/1f62e.svg'), // Surprised
|
||||
joinPublicPath('packs/emoji/1f622.svg'), // Crying
|
||||
joinPublicPath('packs/emoji/1f629.svg'), // Weary
|
||||
joinPublicPath('packs/emoji/1f621.svg'), // Angry (Spinster)
|
||||
],
|
||||
excludes: [
|
||||
'**/*.gz',
|
||||
'**/*.map',
|
||||
'**/*.LICENSE.txt',
|
||||
'stats.json',
|
||||
'report.html',
|
||||
'instance/**/*',
|
||||
|
@ -66,15 +70,24 @@ module.exports = merge(sharedConfig, {
|
|||
'**/*.woff',
|
||||
// Sounds return a 206 causing sw.js to crash
|
||||
// https://stackoverflow.com/a/66335638
|
||||
'sounds/**/*',
|
||||
// Don't cache index.html
|
||||
'**/*.ogg',
|
||||
'**/*.oga',
|
||||
'**/*.mp3',
|
||||
// Don't serve index.html
|
||||
// https://github.com/bromite/bromite/issues/1294
|
||||
'index.html',
|
||||
'404.html',
|
||||
'assets-manifest.json',
|
||||
// It would be nice to serve these, but they bloat up sw.js
|
||||
'packs/images/crypto/**/*',
|
||||
'packs/emoji/**/*',
|
||||
],
|
||||
// ServiceWorker: {
|
||||
// entry: join(__dirname, '../app/soapbox/service_worker/entry.js'),
|
||||
// cacheName: 'soapbox',
|
||||
// minify: true,
|
||||
// },
|
||||
ServiceWorker: {
|
||||
// entry: join(__dirname, '../app/soapbox/service_worker/entry.js'),
|
||||
// cacheName: 'soapbox',
|
||||
minify: true,
|
||||
},
|
||||
safeToUseOptionalCaches: true,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
|
50
webpack/rules/assets.js
Normal file
50
webpack/rules/assets.js
Normal file
|
@ -0,0 +1,50 @@
|
|||
// Asset modules
|
||||
// https://webpack.js.org/guides/asset-modules/
|
||||
|
||||
const { resolve } = require('path');
|
||||
|
||||
// These are processed in reverse-order
|
||||
// We use the name 'packs' instead of 'assets' for legacy reasons
|
||||
module.exports = [{
|
||||
test: /\.(png|svg)/,
|
||||
type: 'asset/resource',
|
||||
include: [
|
||||
resolve('app', 'images'),
|
||||
resolve('node_modules', 'emoji-datasource'),
|
||||
],
|
||||
generator: {
|
||||
filename: 'packs/images/[name]-[contenthash:8][ext]',
|
||||
},
|
||||
}, {
|
||||
test: /\.(ttf|eot|svg|woff|woff2)/,
|
||||
type: 'asset/resource',
|
||||
include: [
|
||||
resolve('app', 'fonts'),
|
||||
resolve('node_modules', 'fork-awesome'),
|
||||
resolve('node_modules', '@fontsource'),
|
||||
],
|
||||
generator: {
|
||||
filename: 'packs/fonts/[name]-[contenthash:8][ext]',
|
||||
},
|
||||
}, {
|
||||
test: /\.(ogg|oga|mp3)/,
|
||||
type: 'asset/resource',
|
||||
include: resolve('app', 'sounds'),
|
||||
generator: {
|
||||
filename: 'packs/sounds/[name]-[contenthash:8][ext]',
|
||||
},
|
||||
}, {
|
||||
test: /\.svg$/,
|
||||
type: 'asset/resource',
|
||||
include: resolve('node_modules', 'twemoji'),
|
||||
generator: {
|
||||
filename: 'packs/emoji/[name]-[contenthash:8][ext]',
|
||||
},
|
||||
}, {
|
||||
test: /\.svg$/,
|
||||
type: 'asset/resource',
|
||||
include: resolve('node_modules', 'cryptocurrency-icons'),
|
||||
generator: {
|
||||
filename: 'packs/images/crypto/[name]-[contenthash:8][ext]',
|
||||
},
|
||||
}];
|
|
@ -1,20 +0,0 @@
|
|||
const { join } = require('path');
|
||||
const { settings } = require('../configuration');
|
||||
|
||||
module.exports = {
|
||||
test: new RegExp(`(${settings.static_assets_extensions.join('|')})$`, 'i'),
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name(file) {
|
||||
if (file.includes(settings.source_path)) {
|
||||
return 'packs/media/[path][name]-[contenthash].[ext]';
|
||||
}
|
||||
return 'packs/media/[folder]/[name]-[contenthash:8].[ext]';
|
||||
},
|
||||
context: join(settings.source_path),
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
|
@ -3,14 +3,14 @@ const git = require('./babel-git');
|
|||
const gitRefresh = require('./git-refresh');
|
||||
const buildConfig = require('./babel-build-config');
|
||||
const css = require('./css');
|
||||
const file = require('./file');
|
||||
const assets = require('./assets');
|
||||
const nodeModules = require('./node_modules');
|
||||
|
||||
// Webpack loaders are processed in reverse order
|
||||
// https://webpack.js.org/concepts/loaders/#loader-features
|
||||
// Lastly, process static files using file loader
|
||||
module.exports = [
|
||||
file,
|
||||
...assets,
|
||||
css,
|
||||
nodeModules,
|
||||
babel,
|
||||
|
|
|
@ -30,10 +30,9 @@ const makeHtmlConfig = (params = {}) => {
|
|||
};
|
||||
|
||||
module.exports = {
|
||||
entry: Object.assign(
|
||||
{ application: resolve('app/application.js') },
|
||||
{ styles: resolve(join(settings.source_path, 'styles/application.scss')) },
|
||||
),
|
||||
entry: {
|
||||
application: resolve('app/application.js'),
|
||||
},
|
||||
|
||||
output: {
|
||||
filename: 'packs/js/[name]-[chunkhash].js',
|
||||
|
@ -65,7 +64,7 @@ module.exports = {
|
|||
},
|
||||
|
||||
module: {
|
||||
rules: Object.keys(rules).map(key => rules[key]),
|
||||
rules,
|
||||
},
|
||||
|
||||
plugins: [
|
||||
|
@ -90,13 +89,7 @@ module.exports = {
|
|||
new CopyPlugin({
|
||||
patterns: [{
|
||||
from: join(__dirname, '../node_modules/twemoji/assets/svg'),
|
||||
to: join(output.path, 'emoji'),
|
||||
}, {
|
||||
from: join(__dirname, '../node_modules/emoji-datasource/img/twitter/sheets/32.png'),
|
||||
to: join(output.path, 'emoji/sheet_13.png'),
|
||||
}, {
|
||||
from: join(__dirname, '../app/sounds'),
|
||||
to: join(output.path, 'sounds'),
|
||||
to: join(output.path, 'packs/emoji'),
|
||||
}, {
|
||||
from: join(__dirname, '../app/instance'),
|
||||
to: join(output.path, 'instance'),
|
||||
|
|
94
yarn.lock
94
yarn.lock
|
@ -2039,6 +2039,81 @@
|
|||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.2.tgz#adea7b6953cbb34651766b0548468e743c6a2353"
|
||||
integrity sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q==
|
||||
|
||||
"@sentry/browser@6.12.0", "@sentry/browser@^6.12.0":
|
||||
version "6.12.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.12.0.tgz#970cd68fa117a1e1336fdb373e3b1fa76cd63e2d"
|
||||
integrity sha512-wsJi1NLOmfwtPNYxEC50dpDcVY7sdYckzwfqz1/zHrede1mtxpqSw+7iP4bHADOJXuF+ObYYTHND0v38GSXznQ==
|
||||
dependencies:
|
||||
"@sentry/core" "6.12.0"
|
||||
"@sentry/types" "6.12.0"
|
||||
"@sentry/utils" "6.12.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/core@6.12.0":
|
||||
version "6.12.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-6.12.0.tgz#bc7c5f0785b6a392d9ad47bd9b1fae3f5389996c"
|
||||
integrity sha512-mU/zdjlzFHzdXDZCPZm8OeCw7c9xsbL49Mq0TrY0KJjLt4CJBkiq5SDTGfRsenBLgTedYhe5Z/J8Z+xVVq+MfQ==
|
||||
dependencies:
|
||||
"@sentry/hub" "6.12.0"
|
||||
"@sentry/minimal" "6.12.0"
|
||||
"@sentry/types" "6.12.0"
|
||||
"@sentry/utils" "6.12.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/hub@6.12.0":
|
||||
version "6.12.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-6.12.0.tgz#29e323ab6a95e178fb14fffb684aa0e09707197f"
|
||||
integrity sha512-yR/UQVU+ukr42bSYpeqvb989SowIXlKBanU0cqLFDmv5LPCnaQB8PGeXwJAwWhQgx44PARhmB82S6Xor8gYNxg==
|
||||
dependencies:
|
||||
"@sentry/types" "6.12.0"
|
||||
"@sentry/utils" "6.12.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/minimal@6.12.0":
|
||||
version "6.12.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-6.12.0.tgz#cbe20e95056cedb9709d7d5b2119ef95206a9f8c"
|
||||
integrity sha512-r3C54Q1KN+xIqUvcgX9DlcoWE7ezWvFk2pSu1Ojx9De81hVqR9u5T3sdSAP2Xma+um0zr6coOtDJG4WtYlOtsw==
|
||||
dependencies:
|
||||
"@sentry/hub" "6.12.0"
|
||||
"@sentry/types" "6.12.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/react@^6.12.0":
|
||||
version "6.12.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-6.12.0.tgz#8ae2680d226fafb0da0f3d8366bb285004ba6c2e"
|
||||
integrity sha512-E8Nw9PPzP/EyMy64ksr9xcyYYlBmUA5ROnkPQp7o5wF0xf5/J+nMS1tQdyPnLQe2KUgHlN4kVs2HHft1m7mSYQ==
|
||||
dependencies:
|
||||
"@sentry/browser" "6.12.0"
|
||||
"@sentry/minimal" "6.12.0"
|
||||
"@sentry/types" "6.12.0"
|
||||
"@sentry/utils" "6.12.0"
|
||||
hoist-non-react-statics "^3.3.2"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/tracing@^6.12.0":
|
||||
version "6.12.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-6.12.0.tgz#a05c8985ee7fed7310b029b147d8f9f14f2a2e67"
|
||||
integrity sha512-u10QHNknPBzbWSUUNMkvuH53sQd5NaBo6YdNPj4p5b7sE7445Sh0PwBpRbY3ZiUUiwyxV59fx9UQ4yVnPGxZQA==
|
||||
dependencies:
|
||||
"@sentry/hub" "6.12.0"
|
||||
"@sentry/minimal" "6.12.0"
|
||||
"@sentry/types" "6.12.0"
|
||||
"@sentry/utils" "6.12.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/types@6.12.0":
|
||||
version "6.12.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.12.0.tgz#b7395688a79403c6df8d8bb8d81deb8222519853"
|
||||
integrity sha512-urtgLzE4EDMAYQHYdkgC0Ei9QvLajodK1ntg71bGn0Pm84QUpaqpPDfHRU+i6jLeteyC7kWwa5O5W1m/jrjGXA==
|
||||
|
||||
"@sentry/utils@6.12.0":
|
||||
version "6.12.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.12.0.tgz#3de261e8d11bdfdc7add64a3065d43517802e975"
|
||||
integrity sha512-oRHQ7TH5TSsJqoP9Gqq25Jvn9LKexXfAh/OoKwjMhYCGKGhqpDNUIZVgl9DWsGw5A5N5xnQyLOxDfyRV5RshdA==
|
||||
dependencies:
|
||||
"@sentry/types" "6.12.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sinonjs/commons@^1.7.0":
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.0.tgz#c8d68821a854c555bba172f3b06959a0039b236d"
|
||||
|
@ -2454,13 +2529,6 @@
|
|||
resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.5.2.tgz#ea584b637ff63c5a477f6f21604b5a205b72c9ec"
|
||||
integrity sha512-vgJ5OLWadI8aKjDlOH3rb+dYyPd2GTZuQC/Tihjct6F9GpXGZINo3Y/IVuZVTM1eDQB+/AOsjPUWH/WySDaXvw==
|
||||
|
||||
"@welldone-software/why-did-you-render@^6.2.0":
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@welldone-software/why-did-you-render/-/why-did-you-render-6.2.0.tgz#a053e63f45adb57161c723dee4b005769ea1b64f"
|
||||
integrity sha512-ViwaE09Vgb0yXzyZuGTWCmWy/nBRAEGyztMdFYuxIgmL8yoXX5TVMCfieiJGdRQQPiDUznlYmcu0lu8kN1lwtQ==
|
||||
dependencies:
|
||||
lodash "^4"
|
||||
|
||||
"@xtuc/ieee754@^1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
|
||||
|
@ -7239,6 +7307,11 @@ jest-snapshot@^27.1.0:
|
|||
pretty-format "^27.1.0"
|
||||
semver "^7.3.2"
|
||||
|
||||
jest-transform-stub@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-transform-stub/-/jest-transform-stub-2.0.0.tgz#19018b0851f7568972147a5d60074b55f0225a7d"
|
||||
integrity sha512-lspHaCRx/mBbnm3h4uMMS3R5aZzMwyNpNIJLXj4cEsV0mIUtS4IjYJLSoyjRCtnxb6RIGJ4NL2quZzfIeNhbkg==
|
||||
|
||||
jest-util@^27.0.0:
|
||||
version "27.0.6"
|
||||
resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.0.6.tgz#e8e04eec159de2f4d5f57f795df9cdc091e50297"
|
||||
|
@ -7731,7 +7804,7 @@ lodash.uniq@^4.5.0:
|
|||
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
||||
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
|
||||
|
||||
lodash@4.x, lodash@^4, lodash@^4.17.21, lodash@^4.7.0:
|
||||
lodash@4.x, lodash@^4.17.21, lodash@^4.7.0:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
@ -11665,6 +11738,11 @@ tslib@^1.9.0:
|
|||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
|
||||
integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==
|
||||
|
||||
tslib@^1.9.3:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tslib@^2.0.1:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.2.tgz#462295631185db44b21b1ea3615b63cd1c038242"
|
||||
|
|
Loading…
Reference in a new issue