set url to repo use same image as target we have to sudo on this image copy masto file use our builder bundle install include rake install rake just use their conifg re-lock deps update service worker build prod ignore ruby deps make sure we have zip yeet SW stream out to the right places fix URL in reactions add sounds to build fix serviceworker stop circles erroring us out fix onclick fix lookup revert changes to gemfile fix notification display don't fetch unsupported stuff yeet most of SW fix reply references fixes #2 fix emoji_reaction type fix public timeline
This commit is contained in:
parent
eb03a78993
commit
bad0b9ac7f
25 changed files with 134 additions and 143 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -7,6 +7,7 @@
|
||||||
# Ignore bundler config and downloaded libraries.
|
# Ignore bundler config and downloaded libraries.
|
||||||
/.bundle
|
/.bundle
|
||||||
/vendor/bundle
|
/vendor/bundle
|
||||||
|
distribution
|
||||||
|
|
||||||
# Ignore the default SQLite database.
|
# Ignore the default SQLite database.
|
||||||
/db/*.sqlite3
|
/db/*.sqlite3
|
||||||
|
|
28
.woodpecker.yml
Normal file
28
.woodpecker.yml
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
pipeline:
|
||||||
|
build:
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- tag
|
||||||
|
- push
|
||||||
|
image: node:16
|
||||||
|
commands:
|
||||||
|
- apt-get update && apt-get install -y zip
|
||||||
|
- yarn install --frozen-lockfile
|
||||||
|
- TARGET=distribution ./build.sh
|
||||||
|
- zip mastofe.zip -r distribution
|
||||||
|
|
||||||
|
release:
|
||||||
|
image: akkoma/releaser
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- tag
|
||||||
|
- push
|
||||||
|
secrets:
|
||||||
|
- SCW_ACCESS_KEY
|
||||||
|
- SCW_SECRET_KEY
|
||||||
|
- SCW_DEFAULT_ORGANIZATION_ID
|
||||||
|
commands:
|
||||||
|
- export SOURCE=mastofe.zip
|
||||||
|
- export DEST=scaleway:akkoma-updates/frontend/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/fedibird-fe.zip
|
||||||
|
- /bin/sh /entrypoint.sh
|
||||||
|
|
|
@ -50,6 +50,7 @@ export const CIRCLE_ADDER_CIRCLES_FETCH_SUCCESS = 'CIRCLE_ADDER_CIRCLES_FETCH_SU
|
||||||
export const CIRCLE_ADDER_CIRCLES_FETCH_FAIL = 'CIRCLE_ADDER_CIRCLES_FETCH_FAIL';
|
export const CIRCLE_ADDER_CIRCLES_FETCH_FAIL = 'CIRCLE_ADDER_CIRCLES_FETCH_FAIL';
|
||||||
|
|
||||||
export const fetchCircle = id => (dispatch, getState) => {
|
export const fetchCircle = id => (dispatch, getState) => {
|
||||||
|
return;
|
||||||
if (getState().getIn(['circles', id])) {
|
if (getState().getIn(['circles', id])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -77,12 +78,8 @@ export const fetchCircleFail = (id, error) => ({
|
||||||
error,
|
error,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const fetchCircles = () => (dispatch, getState) => {
|
export const fetchCircles = () => () => {
|
||||||
dispatch(fetchCirclesRequest());
|
return;
|
||||||
|
|
||||||
api(getState).get('/api/v1/circles')
|
|
||||||
.then(({ data }) => dispatch(fetchCirclesSuccess(data)))
|
|
||||||
.catch(err => dispatch(fetchCirclesFail(err)));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchCirclesRequest = () => ({
|
export const fetchCirclesRequest = () => ({
|
||||||
|
|
|
@ -112,9 +112,8 @@ export const getContextReference = (getState, status) => {
|
||||||
return ImmutableSet();
|
return ImmutableSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
const references = status.get('status_reference_ids').toSet();
|
|
||||||
const replyStatus = status.get('in_reply_to_id') ? getState().getIn(['statuses', status.get('in_reply_to_id')]) : null;
|
const replyStatus = status.get('in_reply_to_id') ? getState().getIn(['statuses', status.get('in_reply_to_id')]) : null;
|
||||||
return references.concat(getContextReference(getState, replyStatus));
|
return getContextReference(getState, replyStatus);
|
||||||
};
|
};
|
||||||
|
|
||||||
export function changeCompose(text) {
|
export function changeCompose(text) {
|
||||||
|
|
|
@ -8,16 +8,8 @@ export const FAVOURITE_DOMAINS_FETCH_REQUEST = 'FAVOURITE_DOMAINS_FETCH_REQUEST'
|
||||||
export const FAVOURITE_DOMAINS_FETCH_SUCCESS = 'FAVOURITE_DOMAINS_FETCH_SUCCESS';
|
export const FAVOURITE_DOMAINS_FETCH_SUCCESS = 'FAVOURITE_DOMAINS_FETCH_SUCCESS';
|
||||||
export const FAVOURITE_DOMAINS_FETCH_FAIL = 'FAVOURITE_DOMAINS_FETCH_FAIL';
|
export const FAVOURITE_DOMAINS_FETCH_FAIL = 'FAVOURITE_DOMAINS_FETCH_FAIL';
|
||||||
|
|
||||||
export const fetchFavouriteDomain = id => (dispatch, getState) => {
|
export const fetchFavouriteDomain = () => () => {
|
||||||
if (getState().getIn(['favourite_domains', id])) {
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(fetchFavouriteDomainRequest(id));
|
|
||||||
|
|
||||||
api(getState).get(`/api/v1/favourite_domains/${id}`)
|
|
||||||
.then(({ data }) => dispatch(fetchFavouriteDomainSuccess(data)))
|
|
||||||
.catch(err => dispatch(fetchFavouriteDomainFail(id, err)));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchFavouriteDomainRequest = id => ({
|
export const fetchFavouriteDomainRequest = id => ({
|
||||||
|
@ -37,6 +29,7 @@ export const fetchFavouriteDomainFail = (id, error) => ({
|
||||||
});
|
});
|
||||||
|
|
||||||
export const fetchFavouriteDomains = () => (dispatch, getState) => {
|
export const fetchFavouriteDomains = () => (dispatch, getState) => {
|
||||||
|
return;
|
||||||
dispatch(fetchFavouriteDomainsRequest());
|
dispatch(fetchFavouriteDomainsRequest());
|
||||||
|
|
||||||
api(getState).get('/api/v1/favourite_domains')
|
api(getState).get('/api/v1/favourite_domains')
|
||||||
|
|
|
@ -8,16 +8,8 @@ export const FAVOURITE_TAGS_FETCH_REQUEST = 'FAVOURITE_TAGS_FETCH_REQUEST';
|
||||||
export const FAVOURITE_TAGS_FETCH_SUCCESS = 'FAVOURITE_TAGS_FETCH_SUCCESS';
|
export const FAVOURITE_TAGS_FETCH_SUCCESS = 'FAVOURITE_TAGS_FETCH_SUCCESS';
|
||||||
export const FAVOURITE_TAGS_FETCH_FAIL = 'FAVOURITE_TAGS_FETCH_FAIL';
|
export const FAVOURITE_TAGS_FETCH_FAIL = 'FAVOURITE_TAGS_FETCH_FAIL';
|
||||||
|
|
||||||
export const fetchFavouriteTag = id => (dispatch, getState) => {
|
export const fetchFavouriteTag = () => () => {
|
||||||
if (getState().getIn(['favourite_tags', id])) {
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(fetchFavouriteTagRequest(id));
|
|
||||||
|
|
||||||
api(getState).get(`/api/v1/favourite_tags/${id}`)
|
|
||||||
.then(({ data }) => dispatch(fetchFavouriteTagSuccess(data)))
|
|
||||||
.catch(err => dispatch(fetchFavouriteTagFail(id, err)));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchFavouriteTagRequest = id => ({
|
export const fetchFavouriteTagRequest = id => ({
|
||||||
|
@ -37,6 +29,7 @@ export const fetchFavouriteTagFail = (id, error) => ({
|
||||||
});
|
});
|
||||||
|
|
||||||
export const fetchFavouriteTags = () => (dispatch, getState) => {
|
export const fetchFavouriteTags = () => (dispatch, getState) => {
|
||||||
|
return;
|
||||||
dispatch(fetchFavouriteTagsRequest());
|
dispatch(fetchFavouriteTagsRequest());
|
||||||
|
|
||||||
api(getState).get('/api/v1/favourite_tags')
|
api(getState).get('/api/v1/favourite_tags')
|
||||||
|
|
|
@ -120,7 +120,7 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
|
||||||
|
|
||||||
const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS();
|
const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS();
|
||||||
|
|
||||||
const allTypes = ImmutableList(['follow', 'follow_request', 'favourite', 'reblog', 'mention', 'poll', enableReaction ? 'emoji_reaction' : null, enableStatusReference ? 'status_reference' : null].filter(x => !!x))
|
const allTypes = ImmutableList(['follow', 'follow_request', 'favourite', 'reblog', 'mention', 'poll', enableReaction ? 'pleroma:emoji_reaction' : null, enableStatusReference ? 'status_reference' : null].filter(x => !!x))
|
||||||
|
|
||||||
const excludeTypesFromFilter = filter => {
|
const excludeTypesFromFilter = filter => {
|
||||||
return allTypes.filterNot(item => item === filter).toJS();
|
return allTypes.filterNot(item => item === filter).toJS();
|
||||||
|
@ -141,9 +141,9 @@ export function expandNotifications({ maxId } = {}, done = noOp) {
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
max_id: maxId,
|
max_id: maxId,
|
||||||
exclude_types: activeFilter === 'all'
|
exclude_types: (activeFilter === 'all'
|
||||||
? excludeTypesFromSettings(getState())
|
? excludeTypesFromSettings(getState())
|
||||||
: excludeTypesFromFilter(activeFilter),
|
: excludeTypesFromFilter(activeFilter)).filter(x => x !== 'status_reference'),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!params.max_id && (notifications.get('items', ImmutableList()).size + notifications.get('pendingItems', ImmutableList()).size) > 0) {
|
if (!params.max_id && (notifications.get('items', ImmutableList()).size + notifications.get('pendingItems', ImmutableList()).size) > 0) {
|
||||||
|
|
|
@ -148,8 +148,6 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
params.compact = true;
|
|
||||||
|
|
||||||
const isLoadingRecent = !!params.since_id;
|
const isLoadingRecent = !!params.since_id;
|
||||||
|
|
||||||
dispatch(expandTimelineRequest(timelineId, isLoadingMore));
|
dispatch(expandTimelineRequest(timelineId, isLoadingMore));
|
||||||
|
@ -181,11 +179,11 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const expandHomeTimeline = ({ maxId, visibilities } = {}, done = noOp) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId, visibilities: visibilities }, done);
|
export const expandHomeTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId }, done);
|
||||||
export const expandLimitedTimeline = ({ maxId, visibilities } = {}, done = noOp) => expandTimeline('limited', '/api/v1/timelines/home', { max_id: maxId, visibilities: visibilities }, done);
|
export const expandLimitedTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('limited', '/api/v1/timelines/home', { max_id: maxId }, done);
|
||||||
export const expandPersonalTimeline = ({ maxId, onlyMedia, withoutMedia } = {}, done = noOp) => expandTimeline(`personal${withoutMedia ? ':nomedia' : ''}${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/personal', { max_id: maxId, only_media: !!onlyMedia, without_media: !!withoutMedia }, done);
|
export const expandPersonalTimeline = ({ maxId, onlyMedia, withoutMedia } = {}, done = noOp) => expandTimeline(`personal${withoutMedia ? ':nomedia' : ''}${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/personal', { max_id: maxId, only_media: !!onlyMedia }, done);
|
||||||
export const expandPublicTimeline = ({ maxId, onlyMedia, withoutMedia, withoutBot, onlyRemote } = {}, done = noOp) => expandTimeline(`public${onlyRemote ? ':remote' : ''}${withoutBot ? ':nobot' : ':bot'}${withoutMedia ? ':nomedia' : ''}${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { remote: !!onlyRemote, max_id: maxId, only_media: !!onlyMedia, without_media: !!withoutMedia, without_bot: !!withoutBot }, done);
|
export const expandPublicTimeline = ({ maxId, onlyMedia, withoutMedia, onlyRemote } = {}, done = noOp) => expandTimeline('public', '/api/v1/timelines/public', { remote: !!onlyRemote, max_id: maxId, only_media: !!onlyMedia }, done);
|
||||||
export const expandDomainTimeline = (domain, { maxId, onlyMedia, withoutMedia, withoutBot } = {}, done = noOp) => expandTimeline(`domain${withoutBot ? ':nobot' : ':bot'}${withoutMedia ? ':nomedia' : ''}${onlyMedia ? ':media' : ''}:${domain}`, '/api/v1/timelines/public', { local: false, domain: domain, max_id: maxId, only_media: !!onlyMedia, without_media: !!withoutMedia, without_bot: !!withoutBot }, done);
|
export const expandDomainTimeline = (domain, { maxId, onlyMedia, withoutMedia, withoutBot } = {}, done = noOp) => expandTimeline(`domain${withoutBot ? ':nobot' : ':bot'}${withoutMedia ? ':nomedia' : ''}${onlyMedia ? ':media' : ''}:${domain}`, '/api/v1/timelines/public', { local: false, domain: domain, max_id: maxId, only_media: !!onlyMedia, without_media: !!withoutMedia }, done);
|
||||||
export const expandGroupTimeline = (id, { maxId, onlyMedia, withoutMedia, tagged } = {}, done = noOp) => expandTimeline(`group:${id}${withoutMedia ? ':nomedia' : ''}${onlyMedia ? ':media' : ''}${tagged ? `:${tagged}` : ''}`, `/api/v1/timelines/group/${id}`, { max_id: maxId, only_media: !!onlyMedia, without_media: !!withoutMedia, tagged: tagged }, done);
|
export const expandGroupTimeline = (id, { maxId, onlyMedia, withoutMedia, tagged } = {}, done = noOp) => expandTimeline(`group:${id}${withoutMedia ? ':nomedia' : ''}${onlyMedia ? ':media' : ''}${tagged ? `:${tagged}` : ''}`, `/api/v1/timelines/group/${id}`, { max_id: maxId, only_media: !!onlyMedia, without_media: !!withoutMedia, tagged: tagged }, done);
|
||||||
export const expandAccountTimeline = (accountId, { maxId, withReplies, withoutReblogs, tagged } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}${withoutReblogs ? ':without_reblogs' : ''}${tagged ? `:${tagged}` : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, exclude_reblogs: withoutReblogs, tagged, max_id: maxId });
|
export const expandAccountTimeline = (accountId, { maxId, withReplies, withoutReblogs, tagged } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}${withoutReblogs ? ':without_reblogs' : ''}${tagged ? `:${tagged}` : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, exclude_reblogs: withoutReblogs, tagged, max_id: maxId });
|
||||||
export const expandAccountCoversations = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:conversations`, `/api/v1/accounts/${accountId}/conversations`, { max_id: maxId });
|
export const expandAccountCoversations = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:conversations`, `/api/v1/accounts/${accountId}/conversations`, { max_id: maxId });
|
||||||
|
|
|
@ -164,7 +164,7 @@ class Account extends ImmutablePureComponent {
|
||||||
return (
|
return (
|
||||||
<div className='account'>
|
<div className='account'>
|
||||||
<div className='account__wrapper'>
|
<div className='account__wrapper'>
|
||||||
<Permalink key={account.get('id')} className='account__display-name' title={account.get('acct')} href={account.get('url')} to={`${(account.get('group', false)) ? '/timelines/groups/' : '/accounts/'}${account.get('id')}`}>
|
<Permalink key={account.get('id')} className='account__display-name' title={account.get('acct')} href={account.get('url')} to={`/accounts/${account.get('id')}`}>
|
||||||
<div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div>
|
<div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div>
|
||||||
{mute_expires_at}
|
{mute_expires_at}
|
||||||
<DisplayName account={account} />
|
<DisplayName account={account} />
|
||||||
|
|
|
@ -56,39 +56,6 @@ const Hashtag = ({ hashtag }) => (
|
||||||
>
|
>
|
||||||
#<span>{hashtag.get('name')}</span>
|
#<span>{hashtag.get('name')}</span>
|
||||||
</Permalink>
|
</Permalink>
|
||||||
|
|
||||||
<ShortNumber
|
|
||||||
value={
|
|
||||||
hashtag.getIn(['history', 0, 'accounts']) * 1 +
|
|
||||||
hashtag.getIn(['history', 1, 'accounts']) * 1
|
|
||||||
}
|
|
||||||
renderer={accountsCountRenderer}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='trends__item__current'>
|
|
||||||
<ShortNumber
|
|
||||||
value={
|
|
||||||
hashtag.getIn(['history', 0, 'uses']) * 1 +
|
|
||||||
hashtag.getIn(['history', 1, 'uses']) * 1
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='trends__item__sparkline'>
|
|
||||||
<SilentErrorBoundary>
|
|
||||||
<Sparklines
|
|
||||||
width={50}
|
|
||||||
height={28}
|
|
||||||
data={hashtag
|
|
||||||
.get('history')
|
|
||||||
.reverse()
|
|
||||||
.map((day) => day.get('uses'))
|
|
||||||
.toArray()}
|
|
||||||
>
|
|
||||||
<SparklinesCurve style={{ fill: 'none' }} />
|
|
||||||
</Sparklines>
|
|
||||||
</SilentErrorBoundary>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -255,14 +255,10 @@ class Status extends ImmutablePureComponent {
|
||||||
|
|
||||||
handleAccountClick = (e) => {
|
handleAccountClick = (e) => {
|
||||||
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||||
const id = e.currentTarget.getAttribute('data-id');
|
|
||||||
const group = e.currentTarget.getAttribute('data-group') !== 'false';
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (group) {
|
const { status } = this.props;
|
||||||
this.context.router.history.push(`/timelines/groups/${id}`);
|
const id = status.getIn(['account', 'id']);
|
||||||
} else {
|
this.context.router.history.push(`/accounts/${id}`);
|
||||||
this.context.router.history.push(`/accounts/${id}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -434,6 +430,7 @@ class Status extends ImmutablePureComponent {
|
||||||
'limited': { icon: 'user-circle', text: intl.formatMessage(messages.limited_short) },
|
'limited': { icon: 'user-circle', text: intl.formatMessage(messages.limited_short) },
|
||||||
'direct': { icon: 'envelope', text: intl.formatMessage(messages.direct_short) },
|
'direct': { icon: 'envelope', text: intl.formatMessage(messages.direct_short) },
|
||||||
'personal': { icon: 'book', text: intl.formatMessage(messages.personal_short) },
|
'personal': { icon: 'book', text: intl.formatMessage(messages.personal_short) },
|
||||||
|
'local': { icon: 'lock', text: 'local' },
|
||||||
};
|
};
|
||||||
|
|
||||||
if (hidden) {
|
if (hidden) {
|
||||||
|
|
|
@ -304,7 +304,7 @@ class AccountCard extends ImmutablePureComponent {
|
||||||
<Permalink
|
<Permalink
|
||||||
className='directory__card__bar__name'
|
className='directory__card__bar__name'
|
||||||
href={account.get('url')}
|
href={account.get('url')}
|
||||||
to={`/timelines/groups/${account.get('id')}`}
|
to={`/accounts/${account.get('id')}`}
|
||||||
>
|
>
|
||||||
<Avatar account={account} size={48} />
|
<Avatar account={account} size={48} />
|
||||||
<DisplayName account={account} />
|
<DisplayName account={account} />
|
||||||
|
|
|
@ -23,16 +23,14 @@ const mapStateToProps = (state, { columnId }) => {
|
||||||
const index = columns.findIndex(c => c.get('uuid') === uuid);
|
const index = columns.findIndex(c => c.get('uuid') === uuid);
|
||||||
const onlyMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'public', 'other', 'onlyMedia']);
|
const onlyMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'public', 'other', 'onlyMedia']);
|
||||||
const withoutMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'withoutMedia']) : state.getIn(['settings', 'public', 'other', 'withoutMedia']);
|
const withoutMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'withoutMedia']) : state.getIn(['settings', 'public', 'other', 'withoutMedia']);
|
||||||
const withoutBot = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'withoutBot']) : state.getIn(['settings', 'public', 'other', 'withoutBot']);
|
|
||||||
const onlyRemote = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyRemote']) : state.getIn(['settings', 'public', 'other', 'onlyRemote']);
|
const onlyRemote = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyRemote']) : state.getIn(['settings', 'public', 'other', 'onlyRemote']);
|
||||||
const columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'public', 'columnWidth']);
|
const columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'public', 'columnWidth']);
|
||||||
const timelineState = state.getIn(['timelines', `public${onlyRemote ? ':remote' : ''}${withoutBot ? ':nobot' : ':bot'}${withoutMedia ? ':nomedia' : ''}${onlyMedia ? ':media' : ''}`]);
|
const timelineState = state.getIn(['timelines', 'public']);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hasUnread: !!timelineState && timelineState.get('unread') > 0,
|
hasUnread: !!timelineState && timelineState.get('unread') > 0,
|
||||||
onlyMedia,
|
onlyMedia,
|
||||||
withoutMedia,
|
withoutMedia,
|
||||||
withoutBot,
|
|
||||||
onlyRemote,
|
onlyRemote,
|
||||||
columnWidth: columnWidth ?? defaultColumnWidth,
|
columnWidth: columnWidth ?? defaultColumnWidth,
|
||||||
};
|
};
|
||||||
|
@ -49,7 +47,6 @@ class PublicTimeline extends React.PureComponent {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
onlyMedia: false,
|
onlyMedia: false,
|
||||||
withoutMedia: false,
|
withoutMedia: false,
|
||||||
withoutBot: false,
|
|
||||||
onlyRemote: false,
|
onlyRemote: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -62,17 +59,16 @@ class PublicTimeline extends React.PureComponent {
|
||||||
hasUnread: PropTypes.bool,
|
hasUnread: PropTypes.bool,
|
||||||
onlyMedia: PropTypes.bool,
|
onlyMedia: PropTypes.bool,
|
||||||
withoutMedia: PropTypes.bool,
|
withoutMedia: PropTypes.bool,
|
||||||
withoutBot: PropTypes.bool,
|
|
||||||
onlyRemote: PropTypes.bool,
|
onlyRemote: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
handlePin = () => {
|
handlePin = () => {
|
||||||
const { columnId, dispatch, onlyMedia, withoutMedia, withoutBot, onlyRemote } = this.props;
|
const { columnId, dispatch, onlyMedia, withoutMedia, onlyRemote } = this.props;
|
||||||
|
|
||||||
if (columnId) {
|
if (columnId) {
|
||||||
dispatch(removeColumn(columnId));
|
dispatch(removeColumn(columnId));
|
||||||
} else {
|
} else {
|
||||||
dispatch(addColumn(onlyRemote ? 'REMOTE' : 'PUBLIC', { other: { onlyMedia, withoutMedia, withoutBot, onlyRemote } }));
|
dispatch(addColumn(onlyRemote ? 'REMOTE' : 'PUBLIC', { other: { onlyMedia, withoutMedia, onlyRemote } }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,19 +92,19 @@ class PublicTimeline extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
const { dispatch, onlyMedia, withoutMedia, withoutBot, onlyRemote } = this.props;
|
const { dispatch, onlyMedia, withoutMedia, onlyRemote } = this.props;
|
||||||
|
|
||||||
dispatch(expandPublicTimeline({ onlyMedia, withoutMedia, withoutBot, onlyRemote }));
|
dispatch(expandPublicTimeline({ onlyMedia, withoutMedia, onlyRemote }));
|
||||||
this.disconnect = dispatch(connectPublicStream({ onlyMedia, withoutMedia, withoutBot, onlyRemote }));
|
this.disconnect = dispatch(connectPublicStream({ onlyMedia, withoutMedia, onlyRemote }));
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate (prevProps) {
|
||||||
if (prevProps.onlyMedia !== this.props.onlyMedia || prevProps.withoutMedia !== this.props.withoutMedia || prevProps.withoutBot !== this.props.withoutBot || prevProps.onlyRemote !== this.props.onlyRemote) {
|
if (prevProps.onlyMedia !== this.props.onlyMedia || prevProps.withoutMedia !== this.props.withoutMedia || prevProps.onlyRemote !== this.props.onlyRemote) {
|
||||||
const { dispatch, onlyMedia, withoutMedia, withoutBot, onlyRemote } = this.props;
|
const { dispatch, onlyMedia, withoutMedia, onlyRemote } = this.props;
|
||||||
|
|
||||||
this.disconnect();
|
this.disconnect();
|
||||||
dispatch(expandPublicTimeline({ onlyMedia, withoutMedia, withoutBot, onlyRemote }));
|
dispatch(expandPublicTimeline({ onlyMedia, withoutMedia, onlyRemote }));
|
||||||
this.disconnect = dispatch(connectPublicStream({ onlyMedia, withoutMedia, withoutBot, onlyRemote }));
|
this.disconnect = dispatch(connectPublicStream({ onlyMedia, withoutMedia, onlyRemote }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,13 +120,13 @@ class PublicTimeline extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLoadMore = maxId => {
|
handleLoadMore = maxId => {
|
||||||
const { dispatch, onlyMedia, withoutMedia, withoutBot, onlyRemote } = this.props;
|
const { dispatch, onlyMedia, withoutMedia, onlyRemote } = this.props;
|
||||||
|
|
||||||
dispatch(expandPublicTimeline({ maxId, onlyMedia, withoutMedia, withoutBot, onlyRemote }));
|
dispatch(expandPublicTimeline({ maxId, onlyMedia, withoutMedia, onlyRemote }));
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, columnId, hasUnread, multiColumn, onlyMedia, withoutMedia, withoutBot, onlyRemote, columnWidth } = this.props;
|
const { intl, columnId, hasUnread, multiColumn, onlyMedia, withoutMedia, onlyRemote, columnWidth } = this.props;
|
||||||
const pinned = !!columnId;
|
const pinned = !!columnId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -151,7 +147,7 @@ class PublicTimeline extends React.PureComponent {
|
||||||
</ColumnHeader>
|
</ColumnHeader>
|
||||||
|
|
||||||
<StatusListContainer
|
<StatusListContainer
|
||||||
timelineId={`public${onlyRemote ? ':remote' : ''}${withoutBot ? ':nobot' : ':bot'}${withoutMedia ? ':nomedia' : ''}${onlyMedia ? ':media' : ''}`}
|
timelineId={'public'}
|
||||||
onLoadMore={this.handleLoadMore}
|
onLoadMore={this.handleLoadMore}
|
||||||
trackScroll={!pinned}
|
trackScroll={!pinned}
|
||||||
scrollKey={`public_timeline-${columnId}`}
|
scrollKey={`public_timeline-${columnId}`}
|
||||||
|
|
|
@ -29,6 +29,7 @@ const messages = defineMessages({
|
||||||
personal_short: { id: 'privacy.personal.short', defaultMessage: 'Personal' },
|
personal_short: { id: 'privacy.personal.short', defaultMessage: 'Personal' },
|
||||||
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Circle' },
|
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Circle' },
|
||||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
|
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
|
||||||
|
local_short: { id: 'privacy.local.short', defaultMessage: 'Local' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => {
|
const mapStateToProps = (state, props) => {
|
||||||
|
@ -359,6 +360,7 @@ class DetailedStatus extends ImmutablePureComponent {
|
||||||
'limited': { icon: 'user-circle', text: intl.formatMessage(messages.limited_short) },
|
'limited': { icon: 'user-circle', text: intl.formatMessage(messages.limited_short) },
|
||||||
'direct': { icon: 'envelope', text: intl.formatMessage(messages.direct_short) },
|
'direct': { icon: 'envelope', text: intl.formatMessage(messages.direct_short) },
|
||||||
'personal': { icon: 'book', text: intl.formatMessage(messages.personal_short) },
|
'personal': { icon: 'book', text: intl.formatMessage(messages.personal_short) },
|
||||||
|
'local': { icon: 'local', text: 'local' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const visibilityIcon = visibilityIconInfo[status.get('visibility')];
|
const visibilityIcon = visibilityIconInfo[status.get('visibility')];
|
||||||
|
|
|
@ -40,8 +40,8 @@ export const getLinks = memoize((favouriteLists = null) => {
|
||||||
|
|
||||||
return [
|
return [
|
||||||
link_home,
|
link_home,
|
||||||
enableLimitedTimeline ? link_limited : null,
|
null, // enableLimitedTimeline ? link_limited : null,
|
||||||
enablePersonalTimeline ? link_personal : null,
|
null, // enablePersonalTimeline ? link_personal : null,
|
||||||
link_favourite_lists,
|
link_favourite_lists,
|
||||||
link_notifications,
|
link_notifications,
|
||||||
enableLocalTimeline ? link_local : null,
|
enableLocalTimeline ? link_local : null,
|
||||||
|
|
|
@ -178,15 +178,12 @@ class SwitchingColumnsArea extends React.PureComponent {
|
||||||
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
|
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
|
||||||
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
|
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
|
||||||
<WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
|
<WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
|
||||||
<WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
|
|
||||||
<WrappedRoute path='/timelines/public/local' exact component={CommunityTimeline} content={children} />
|
|
||||||
<WrappedRoute path='/timelines/public/domain/:domain' exact component={DomainTimeline} content={children} />
|
|
||||||
<WrappedRoute path='/timelines/groups/:id/:tagged?' exact component={GroupTimeline} content={children} />
|
|
||||||
<WrappedRoute path='/timelines/direct' component={DirectTimeline} content={children} />
|
<WrappedRoute path='/timelines/direct' component={DirectTimeline} content={children} />
|
||||||
<WrappedRoute path='/timelines/limited' component={LimitedTimeline} content={children} />
|
<WrappedRoute path='/timelines/limited' component={LimitedTimeline} content={children} />
|
||||||
<WrappedRoute path='/timelines/personal' component={PersonalTimeline} content={children} />
|
<WrappedRoute path='/timelines/personal' component={PersonalTimeline} content={children} />
|
||||||
<WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
|
<WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
|
||||||
<WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} />
|
<WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} />
|
||||||
|
<WrappedRoute path='/timelines/public' component={PublicTimeline} content={children} />
|
||||||
|
|
||||||
<WrappedRoute path='/notifications' component={Notifications} content={children} />
|
<WrappedRoute path='/notifications' component={Notifications} content={children} />
|
||||||
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
|
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
|
||||||
|
|
|
@ -10,7 +10,7 @@ function openWebCache() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchRoot() {
|
function fetchRoot() {
|
||||||
return fetch('/', { credentials: 'include', redirect: 'manual' });
|
return fetch('/web', { credentials: 'include', redirect: 'manual' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// const firefox = navigator.userAgent.match(/Firefox\/(\d+)/);
|
// const firefox = navigator.userAgent.match(/Firefox\/(\d+)/);
|
||||||
|
@ -19,7 +19,7 @@ function fetchRoot() {
|
||||||
// Cause a new version of a registered Service Worker to replace an existing one
|
// Cause a new version of a registered Service Worker to replace an existing one
|
||||||
// that is already installed, and replace the currently active worker on open pages.
|
// that is already installed, and replace the currently active worker on open pages.
|
||||||
self.addEventListener('install', function(event) {
|
self.addEventListener('install', function(event) {
|
||||||
event.waitUntil(Promise.all([openWebCache(), fetchRoot()]).then(([cache, root]) => cache.put('/', root)));
|
event.waitUntil(Promise.all([openWebCache(), fetchRoot()]).then(([cache, root]) => cache.put('/web', root)));
|
||||||
});
|
});
|
||||||
self.addEventListener('activate', function(event) {
|
self.addEventListener('activate', function(event) {
|
||||||
event.waitUntil(self.clients.claim());
|
event.waitUntil(self.clients.claim());
|
||||||
|
@ -27,31 +27,8 @@ self.addEventListener('activate', function(event) {
|
||||||
self.addEventListener('fetch', function(event) {
|
self.addEventListener('fetch', function(event) {
|
||||||
const url = new URL(event.request.url);
|
const url = new URL(event.request.url);
|
||||||
|
|
||||||
if (url.pathname.startsWith('/web/')) {
|
if (url.pathname.startsWith('/web')) {
|
||||||
const asyncResponse = fetchRoot();
|
return;
|
||||||
const asyncCache = openWebCache();
|
|
||||||
|
|
||||||
event.respondWith(asyncResponse.then(
|
|
||||||
response => {
|
|
||||||
const clonedResponse = response.clone();
|
|
||||||
asyncCache.then(cache => cache.put('/', clonedResponse)).catch();
|
|
||||||
return response;
|
|
||||||
},
|
|
||||||
() => asyncCache.then(cache => cache.match('/'))));
|
|
||||||
} else if (url.pathname === '/auth/sign_out') {
|
|
||||||
const asyncResponse = fetch(event.request);
|
|
||||||
const asyncCache = openWebCache();
|
|
||||||
|
|
||||||
event.respondWith(asyncResponse.then(response => {
|
|
||||||
if (response.ok || response.type === 'opaqueredirect') {
|
|
||||||
return Promise.all([
|
|
||||||
asyncCache.then(cache => cache.delete('/')),
|
|
||||||
indexedDB.deleteDatabase('mastodon'),
|
|
||||||
]).then(() => response);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}));
|
|
||||||
} /* else if (storageFreeable && (ATTACHMENT_HOST ? url.host === ATTACHMENT_HOST : url.pathname.startsWith('/system/'))) {
|
} /* else if (storageFreeable && (ATTACHMENT_HOST ? url.host === ATTACHMENT_HOST : url.pathname.startsWith('/system/'))) {
|
||||||
event.respondWith(openSystemCache().then(cache => {
|
event.respondWith(openSystemCache().then(cache => {
|
||||||
return cache.match(event.request.url).then(cached => {
|
return cache.match(event.request.url).then(cached => {
|
||||||
|
|
|
@ -88,7 +88,8 @@ const sharedCallbacks = {
|
||||||
},
|
},
|
||||||
|
|
||||||
received (data) {
|
received (data) {
|
||||||
const { stream } = data;
|
const { stream: streamFull } = data;
|
||||||
|
const stream = streamFull.map((name) => name.split(':')[0]);
|
||||||
|
|
||||||
subscriptions.filter(({ channelName, params }) => {
|
subscriptions.filter(({ channelName, params }) => {
|
||||||
const streamChannelName = stream[0];
|
const streamChannelName = stream[0];
|
||||||
|
|
20
build.sh
Executable file
20
build.sh
Executable file
|
@ -0,0 +1,20 @@
|
||||||
|
TARGET="${TARGET:-./distribution}" # Where pleroma’s repository is sitting
|
||||||
|
mkdir -p $TARGET/emoji
|
||||||
|
|
||||||
|
die() {
|
||||||
|
echo "Die: $@"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[ -d "${TARGET}" ] || die "${TARGET} directory is missing, are you sure TARGET is set to a pleroma repository? (Info: TARGET=${TARGET} )"
|
||||||
|
|
||||||
|
yarn install -D || die "Installing dependencies via yarn failed"
|
||||||
|
|
||||||
|
rm -rf public/packs public/assets
|
||||||
|
env -i "PATH=$PATH" yarn build:production || die "Building the frontend failed"
|
||||||
|
cp public/assets/sw.js "${TARGET}/sw.js" || die "installing sw.js (service-worker) failed"
|
||||||
|
rm -rf "${TARGET}/packs" || die "Removing old assets in priv/static/packs failed"
|
||||||
|
cp -r public/packs "${TARGET}/packs" || die "Copying new assets in priv/static/packs failed"
|
||||||
|
cp -r public/sounds "${TARGET}/sounds" || die "Copying sounds failed"
|
||||||
|
rm -rf "${TARGET}/emoji/*.svg" || die "Removing the old emoji assets failed"
|
||||||
|
cp -r public/emoji/* "${TARGET}/emoji" || die "Installing the new emoji assets failed"
|
2
ci-builder/Dockerfile
Normal file
2
ci-builder/Dockerfile
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
FROM circleci/ruby:2.7-buster-node
|
||||||
|
USER root
|
|
@ -34,9 +34,7 @@ module.exports = {
|
||||||
),
|
),
|
||||||
|
|
||||||
output: {
|
output: {
|
||||||
filename: 'js/[name]-[chunkhash].js',
|
filename: 'js/[name].js',
|
||||||
chunkFilename: 'js/[name]-[chunkhash].chunk.js',
|
|
||||||
hotUpdateChunkFilename: 'js/[id]-[hash].hot-update.js',
|
|
||||||
path: output.path,
|
path: output.path,
|
||||||
publicPath: output.publicPath,
|
publicPath: output.publicPath,
|
||||||
},
|
},
|
||||||
|
@ -75,8 +73,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
new MiniCssExtractPlugin({
|
new MiniCssExtractPlugin({
|
||||||
filename: 'css/[name]-[contenthash:8].css',
|
filename: 'css/[name].css',
|
||||||
chunkFilename: 'css/[name]-[contenthash:8].chunk.css',
|
|
||||||
}),
|
}),
|
||||||
new AssetsManifestPlugin({
|
new AssetsManifestPlugin({
|
||||||
integrity: true,
|
integrity: true,
|
||||||
|
|
18
dev.sh
Executable file
18
dev.sh
Executable file
|
@ -0,0 +1,18 @@
|
||||||
|
TARGET="${TARGET:-./distribution}" # Where pleroma’s repository is sitting
|
||||||
|
mkdir -p $TARGET/emoji
|
||||||
|
|
||||||
|
die() {
|
||||||
|
echo "Die: $@"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[ -d "${TARGET}" ] || die "${TARGET} directory is missing, are you sure TARGET is set to a pleroma repository? (Info: TARGET=${TARGET} )"
|
||||||
|
|
||||||
|
yarn install -D || die "Installing dependencies via yarn failed"
|
||||||
|
|
||||||
|
rm -rf public/packs public/assets
|
||||||
|
env -i "PATH=$PATH" bundle exec yarn build:development || die "Building the frontend failed"
|
||||||
|
rm -rf "${TARGET}/packs" || die "Removing old assets in priv/static/packs failed"
|
||||||
|
cp -r public/packs "${TARGET}/packs" || die "Copying new assets in priv/static/packs failed"
|
||||||
|
rm -rf "${TARGET}/emoji/*.svg" || die "Removing the old emoji assets failed"
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postversion": "git push --tags",
|
"postversion": "git push --tags",
|
||||||
"build:development": "cross-env RAILS_ENV=development NODE_ENV=development ./bin/webpack",
|
"build:development": "cross-env NODE_ENV=development webpack --config config/webpack/development.js",
|
||||||
"build:production": "cross-env RAILS_ENV=production NODE_ENV=production ./bin/webpack",
|
"build:production": "cross-env NODE_ENV=production webpack --config config/webpack/production.js",
|
||||||
"manage:translations": "node ./config/webpack/translationRunner.js",
|
"manage:translations": "node ./config/webpack/translationRunner.js",
|
||||||
"start": "node ./streaming/index.js",
|
"start": "node ./streaming/index.js",
|
||||||
"test": "${npm_execpath} run test:lint:js && ${npm_execpath} run test:jest",
|
"test": "${npm_execpath} run test:lint:js && ${npm_execpath} run test:jest",
|
||||||
|
@ -18,7 +18,7 @@
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/mastodon/mastodon.git"
|
"url": "https://akkoma.dev/AkkomaGang/fedibird-fe"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"last 2 versions",
|
"last 2 versions",
|
||||||
|
|
8
package.sh
Executable file
8
package.sh
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
TARGET="${TARGET:-./distribution}"
|
||||||
|
|
||||||
|
rm -rf "${TARGET}/packs" || die "Removing old assets in priv/static/packs failed"
|
||||||
|
cp -r public/packs "${TARGET}/packs" || die "Copying new assets in priv/static/packs failed"
|
||||||
|
rm -rf "${TARGET}/emoji/*.svg" || die "Removing the old emoji assets failed"
|
||||||
|
cp -r public/emoji/* "${TARGET}/emoji" || die "Installing the new emoji assets failed"
|
|
@ -2946,9 +2946,9 @@ caniuse-api@^3.0.0:
|
||||||
lodash.uniq "^4.5.0"
|
lodash.uniq "^4.5.0"
|
||||||
|
|
||||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001219:
|
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001219:
|
||||||
version "1.0.30001228"
|
version "1.0.30001462"
|
||||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001228.tgz#bfdc5942cd3326fa51ee0b42fbef4da9d492a7fa"
|
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001462.tgz"
|
||||||
integrity sha512-QQmLOGJ3DEgokHbMSA8cj2a+geXqmnpyOFT0lhQV6P3/YOJvGDEwoedcwxEQ30gJIwIIunHIicunJ2rzK5gB2A==
|
integrity sha512-PDd20WuOBPiasZ7KbFnmQRyuLE7cFXW2PVd7dmALzbkUXEP46upAuCDm9eY9vho8fgNMGmbAX92QBZHzcnWIqw==
|
||||||
|
|
||||||
capture-exit@^2.0.0:
|
capture-exit@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
|
|
Loading…
Reference in a new issue