Merge remote-tracking branch 'origin' into a11y--

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
v1.x.x
marcin mikołajczak 2021-09-14 14:10:23 +02:00
commit 6152ca40a7
54 zmienionych plików z 701 dodań i 262 usunięć

Wyświetl plik

@ -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 => {

Wyświetl plik

@ -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 => {

Wyświetl plik

@ -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,
};
}

Wyświetl plik

@ -50,6 +50,7 @@ export const makeDefaultConfig = features => {
limit: 1,
}),
aboutPages: ImmutableMap(),
authenticatedProfile: true,
});
};

Wyświetl plik

@ -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,
});

Wyświetl plik

@ -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>

Wyświetl plik

@ -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]}

Wyświetl plik

@ -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 (

Wyświetl plik

@ -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'

Wyświetl plik

@ -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;

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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();
}

Wyświetl plik

@ -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);
}

Wyświetl plik

@ -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}>

Wyświetl plik

@ -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>
);
}
}

Wyświetl plik

@ -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'>

Wyświetl plik

@ -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;

Wyświetl plik

@ -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" />');
});
});
});

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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>

Wyświetl plik

@ -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']),

Wyświetl plik

@ -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}>
{

Wyświetl plik

@ -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'>

Wyświetl plik

@ -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'),

Wyświetl plik

@ -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,

Wyświetl plik

@ -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 }),
};

Wyświetl plik

@ -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')} />

Wyświetl plik

@ -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>

Wyświetl plik

@ -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');
}

Wyświetl plik

@ -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.",

Wyświetl plik

@ -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');

Wyświetl plik

@ -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',
},
]),

Wyświetl plik

@ -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);
};

Wyświetl plik

@ -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>

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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,

Wyświetl plik

@ -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);

Wyświetl plik

@ -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);

Wyświetl plik

@ -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',
};
});

Wyświetl plik

@ -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 => {

Wyświetl plik

@ -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);
};

Wyświetl plik

@ -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);
}

Wyświetl plik

@ -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;

Wyświetl plik

@ -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: `""`

Wyświetl plik

@ -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',
},
};

Wyświetl plik

@ -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",

Wyświetl plik

@ -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' ],
};

Wyświetl plik

@ -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,
}),
],
});

Wyświetl plik

@ -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]',
},
}];

Wyświetl plik

@ -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),
},
},
],
};

Wyświetl plik

@ -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,

Wyświetl plik

@ -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'),

Wyświetl plik

@ -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"