Merge remote-tracking branch 'origin/main' into fix-profile-dropdown

environments/review-fix-profil-stmcg6/deployments/3932
Alex Gleason 2023-09-18 19:55:05 -05:00
commit a7eb9d4112
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 7211D1F99744FBB7
1501 zmienionych plików z 6543 dodań i 16863 usunięć

Wyświetl plik

@ -12,10 +12,11 @@
yarn-error.log*
/junit.xml
/dist/
/static/
/static-test/
/public/
/dist/
/soapbox.zip
.idea
.DS_Store

Wyświetl plik

@ -1,6 +1,6 @@
/node_modules/**
/dist/**
/static/**
/static-test/**
/public/**
/tmp/**
/coverage/**

Wyświetl plik

@ -19,8 +19,6 @@ module.exports = {
ATTACHMENT_HOST: false,
},
parser: '@babel/eslint-parser',
plugins: [
'react',
'jsdoc',
@ -50,9 +48,8 @@ module.exports = {
'\\.(css|scss|json)$',
],
'import/resolver': {
node: {
paths: ['app'],
},
typescript: true,
node: true,
},
polyfills: [
'es:all', // core-js
@ -79,6 +76,7 @@ module.exports = {
},
],
'comma-style': ['warn', 'last'],
'import/no-duplicates': 'error',
'space-before-function-paren': ['error', 'never'],
'space-infix-ops': 'error',
'space-in-parens': ['error', 'never'],
@ -260,7 +258,6 @@ module.exports = {
alphabetize: { order: 'asc' },
},
],
'@typescript-eslint/no-duplicate-imports': 'error',
'@typescript-eslint/member-delimiter-style': [
'error',
{
@ -298,7 +295,7 @@ module.exports = {
{
// Only enforce JSDoc comments on UI components for now.
// https://www.npmjs.com/package/eslint-plugin-jsdoc
files: ['app/soapbox/components/ui/**/*'],
files: ['src/components/ui/**/*'],
rules: {
'jsdoc/require-jsdoc': ['error', {
publicOnly: true,

5
.gitignore vendored
Wyświetl plik

@ -9,11 +9,14 @@
/.vs/
yarn-error.log*
/junit.xml
*.timestamp-*
*.bundled_*
/dist/
/static/
/static-test/
/public/
/dist/
/soapbox.zip
.idea
.DS_Store

Wyświetl plik

@ -2,6 +2,7 @@ image: node:20
variables:
NODE_ENV: test
DS_EXCLUDED_ANALYZERS: gemnasium-python
default:
interruptible: true
@ -30,20 +31,9 @@ deps:
<<: *cache
policy: push
danger:
lint:
stage: test
script:
# https://github.com/danger/danger-js/issues/1029#issuecomment-998915436
- export CI_MERGE_REQUEST_IID=${CI_OPEN_MERGE_REQUESTS#*!}
- npx danger ci
except:
variables:
- $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
allow_failure: true
lint-js:
stage: test
script: yarn lint:js
script: yarn lint
only:
changes:
- "**/*.js"
@ -52,63 +42,25 @@ lint-js:
- "**/*.mjs"
- "**/*.ts"
- "**/*.tsx"
- ".eslintignore"
- ".eslintrc.cjs"
lint-sass:
stage: test
script: yarn lint:sass
only:
changes:
- "**/*.scss"
- "**/*.css"
- ".eslintignore"
- ".eslintrc.cjs"
- ".stylelintrc.json"
jest:
build:
stage: test
script: yarn test:coverage --runInBand
only:
changes:
- "**/*.js"
- "**/*.json"
- "app/soapbox/**/*"
- "webpack/**/*"
- "custom/**/*"
- "jest.config.cjs"
- "package.json"
- "yarn.lock"
- ".gitlab-ci.yml"
coverage: /All files[^|]*\|[^|]*\s+([\d\.]+)/
artifacts:
reports:
junit: junit.xml
coverage_report:
coverage_format: cobertura
path: .coverage/cobertura-coverage.xml
nginx-test:
stage: test
image: nginx:latest
before_script:
- cp installation/mastodon.conf /etc/nginx/conf.d/default.conf
script: nginx -t
only:
changes:
- "installation/mastodon.conf"
build-production:
stage: test
- apt-get update -y && apt-get install -y zip
script:
- yarn build
- yarn manage:translations en
# Fail if files got changed.
# https://stackoverflow.com/a/9066385
- git diff --quiet
- cp dist/index.html dist/404.html
- cd dist && zip -r ../soapbox.zip . && cd ..
variables:
NODE_ENV: production
artifacts:
paths:
- static
- soapbox.zip
docs-deploy:
stage: deploy
@ -128,16 +80,20 @@ review:
environment:
name: review/$CI_COMMIT_REF_NAME
url: https://$CI_COMMIT_REF_SLUG.git.soapbox.pub
before_script:
- apt-get update -y && apt-get install -y unzip
script:
- npx -y surge static $CI_COMMIT_REF_SLUG.git.soapbox.pub
- unzip soapbox.zip -d dist
- npx -y surge dist $CI_COMMIT_REF_SLUG.git.soapbox.pub
allow_failure: true
pages:
stage: deploy
before_script: []
before_script:
- apt-get update -y && apt-get install -y unzip
script:
# artifacts are kept between jobs
- mv static public
- unzip soapbox.zip -d public
variables:
NODE_ENV: production
artifacts:
@ -149,9 +105,9 @@ pages:
docker:
stage: deploy
image: docker:23.0.0
image: docker:24.0.6
services:
- docker:23.0.0-dind
- docker:24.0.6-dind
tags:
- dind
# https://medium.com/devops-with-valentine/how-to-build-a-docker-image-and-push-it-to-the-gitlab-container-registry-from-a-gitlab-ci-pipeline-acac0d1f26df
@ -173,4 +129,3 @@ release:
include:
- template: Jobs/Dependency-Scanning.gitlab-ci.yml
- template: Security/License-Scanning.gitlab-ci.yml

Wyświetl plik

@ -4,5 +4,5 @@
"*.mjs": "eslint --cache",
"*.ts": "eslint --cache",
"*.tsx": "eslint --cache",
"app/styles/**/*.scss": "stylelint"
"src/styles/**/*.scss": "stylelint"
}

Wyświetl plik

@ -1,43 +0,0 @@
import sharedConfig from '../webpack/shared';
import type { StorybookConfig } from '@storybook/core-common';
const config: StorybookConfig = {
stories: [
'../stories/**/*.stories.mdx',
'../stories/**/*.stories.@(js|jsx|ts|tsx)'
],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'storybook-react-intl',
{
name: '@storybook/addon-postcss',
options: {
postcssLoaderOptions: {
implementation: require('postcss'),
},
},
},
],
framework: '@storybook/react',
core: {
builder: '@storybook/builder-webpack5',
},
webpackFinal: async (config) => {
config.resolve!.alias = {
...sharedConfig.resolve!.alias,
...config.resolve!.alias,
};
config.resolve!.modules = [
...sharedConfig.resolve!.modules!,
...config.resolve!.modules!,
];
return config;
},
};
export default config;

Wyświetl plik

@ -1,22 +0,0 @@
import '../app/styles/tailwind.css';
import '../stories/theme.css';
import { addDecorator, Story } from '@storybook/react';
import { IntlProvider } from 'react-intl';
import React from 'react';
const withProvider = (Story: Story) => (
<IntlProvider locale='en'><Story /></IntlProvider>
);
addDecorator(withProvider);
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
};

Wyświetl plik

@ -16,7 +16,6 @@
"no-invalid-position-at-import-rule": null,
"scss/at-rule-no-unknown": [true, { "ignoreAtRules": ["tailwind", "apply", "layer", "config"]}],
"scss/operator-no-unspaced": null,
"selector-class-pattern": null,
"string-quotes": "single"
"selector-class-pattern": null
}
}

Wyświetl plik

@ -12,9 +12,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Reactions: Support custom emoji reactions
- Compatbility: Support Mastodon v2 timeline filters.
- Compatbility: Preliminary support for Ditto backend.
- Compatibility: Support Firefish.
- Posts: Support dislikes on Friendica.
- UI: added a character counter to some textareas.
- UI: added new experience for viewing Media
- Hotkeys: Added `/` as a hotkey for search field.
### Changed
- Posts: truncate Nostr pubkeys in reply mentions.
@ -23,10 +25,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- UI: unified design of "approve" and "reject" buttons in follow requests and waitlist.
- UI: added sticky column header.
- UI: add specific zones the user can drag-and-drop files.
- UI: disable toast notifications for API errors.
- Chats: Display year for older messages creation date.
### Fixed
- Posts: fixed emojis being cut off in reactions modal.
- Posts: fix audio player progress bar visibility.
- Posts: fix audio player avatar aspect ratio for non-square avatars.
- Posts: added missing gap in pending status.
- Compatibility: fixed quote posting compatibility with custom Pleroma forks.
- Profile: fix "load more" button height on account gallery page.
@ -36,6 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- UI: fixed various overflow issues related to long usernames.
- UI: fixed display of Markdown code blocks in the reply indicator.
- Auth: fixed too many API requests when the server has an error.
- Auth: Don't display "username or e-mail" if username is not allowed.
## [3.2.0] - 2023-02-15

Wyświetl plik

@ -14,4 +14,4 @@ ENV FALLBACK_PORT=4444
ENV BACKEND_URL=http://localhost:4444
ENV CSP=
COPY installation/docker.conf.template /etc/nginx/templates/default.conf.template
COPY --from=build /app/static /usr/share/nginx/html
COPY --from=build /app/dist /usr/share/nginx/html

Wyświetl plik

@ -1,109 +0,0 @@
import { Map as ImmutableMap } from 'immutable';
import { __stub } from 'soapbox/api';
import { buildAccount, buildRelationship } from 'soapbox/jest/factory';
import { mockStore, rootState } from 'soapbox/jest/test-helpers';
import { ReducerRecord, EditRecord } from 'soapbox/reducers/account-notes';
import { changeAccountNoteComment, initAccountNoteModal, submitAccountNote } from '../account-notes';
describe('submitAccountNote()', () => {
let store: ReturnType<typeof mockStore>;
beforeEach(() => {
const state = rootState
.set('account_notes', ReducerRecord({ edit: EditRecord({ account: '1', comment: 'hello' }) }));
store = mockStore(state);
});
describe('with a successful API request', () => {
beforeEach(() => {
__stub((mock) => {
mock.onPost('/api/v1/accounts/1/note').reply(200, {});
});
});
it('post the note to the API', async() => {
const expectedActions = [
{ type: 'ACCOUNT_NOTE_SUBMIT_REQUEST' },
{ type: 'MODAL_CLOSE', modalType: undefined },
{ type: 'ACCOUNT_NOTE_SUBMIT_SUCCESS', relationship: {} },
];
await store.dispatch(submitAccountNote());
const actions = store.getActions();
expect(actions).toEqual(expectedActions);
});
});
describe('with an unsuccessful API request', () => {
beforeEach(() => {
__stub((mock) => {
mock.onPost('/api/v1/accounts/1/note').networkError();
});
});
it('should dispatch failed action', async() => {
const expectedActions = [
{ type: 'ACCOUNT_NOTE_SUBMIT_REQUEST' },
{
type: 'ACCOUNT_NOTE_SUBMIT_FAIL',
error: new Error('Network Error'),
},
];
await store.dispatch(submitAccountNote());
const actions = store.getActions();
expect(actions).toEqual(expectedActions);
});
});
});
describe('initAccountNoteModal()', () => {
let store: ReturnType<typeof mockStore>;
beforeEach(() => {
const state = rootState
.set('relationships', ImmutableMap({ '1': buildRelationship({ note: 'hello' }) }));
store = mockStore(state);
});
it('dispatches the proper actions', async() => {
const account = buildAccount({
id: '1',
acct: 'justin-username',
display_name: 'Justin L',
avatar: 'test.jpg',
verified: true,
});
const expectedActions = [
{ type: 'ACCOUNT_NOTE_INIT_MODAL', account, comment: 'hello' },
{ type: 'MODAL_CLOSE', modalType: 'ACCOUNT_NOTE' },
{ type: 'MODAL_OPEN', modalType: 'ACCOUNT_NOTE' },
];
await store.dispatch(initAccountNoteModal(account));
const actions = store.getActions();
expect(actions).toEqual(expectedActions);
});
});
describe('changeAccountNoteComment()', () => {
let store: ReturnType<typeof mockStore>;
beforeEach(() => {
const state = rootState;
store = mockStore(state);
});
it('dispatches the proper actions', async() => {
const comment = 'hello world';
const expectedActions = [
{ type: 'ACCOUNT_NOTE_CHANGE_COMMENT', comment },
];
await store.dispatch(changeAccountNoteComment(comment));
const actions = store.getActions();
expect(actions).toEqual(expectedActions);
});
});

Wyświetl plik

@ -1,82 +0,0 @@
import api from '../api';
import { openModal, closeModal } from './modals';
import type { AxiosError } from 'axios';
import type { AnyAction } from 'redux';
import type { Account } from 'soapbox/schemas';
import type { AppDispatch, RootState } from 'soapbox/store';
const ACCOUNT_NOTE_SUBMIT_REQUEST = 'ACCOUNT_NOTE_SUBMIT_REQUEST';
const ACCOUNT_NOTE_SUBMIT_SUCCESS = 'ACCOUNT_NOTE_SUBMIT_SUCCESS';
const ACCOUNT_NOTE_SUBMIT_FAIL = 'ACCOUNT_NOTE_SUBMIT_FAIL';
const ACCOUNT_NOTE_INIT_MODAL = 'ACCOUNT_NOTE_INIT_MODAL';
const ACCOUNT_NOTE_CHANGE_COMMENT = 'ACCOUNT_NOTE_CHANGE_COMMENT';
const submitAccountNote = () => (dispatch: React.Dispatch<AnyAction>, getState: () => RootState) => {
dispatch(submitAccountNoteRequest());
const id = getState().account_notes.edit.account;
return api(getState)
.post(`/api/v1/accounts/${id}/note`, {
comment: getState().account_notes.edit.comment,
})
.then(response => {
dispatch(closeModal());
dispatch(submitAccountNoteSuccess(response.data));
})
.catch(error => dispatch(submitAccountNoteFail(error)));
};
function submitAccountNoteRequest() {
return {
type: ACCOUNT_NOTE_SUBMIT_REQUEST,
};
}
function submitAccountNoteSuccess(relationship: any) {
return {
type: ACCOUNT_NOTE_SUBMIT_SUCCESS,
relationship,
};
}
function submitAccountNoteFail(error: AxiosError) {
return {
type: ACCOUNT_NOTE_SUBMIT_FAIL,
error,
};
}
const initAccountNoteModal = (account: Account) => (dispatch: AppDispatch, getState: () => RootState) => {
const comment = getState().relationships.get(account.id)!.note;
dispatch({
type: ACCOUNT_NOTE_INIT_MODAL,
account,
comment,
});
dispatch(openModal('ACCOUNT_NOTE'));
};
function changeAccountNoteComment(comment: string) {
return {
type: ACCOUNT_NOTE_CHANGE_COMMENT,
comment,
};
}
export {
submitAccountNote,
initAccountNoteModal,
changeAccountNoteComment,
ACCOUNT_NOTE_SUBMIT_REQUEST,
ACCOUNT_NOTE_SUBMIT_SUCCESS,
ACCOUNT_NOTE_SUBMIT_FAIL,
ACCOUNT_NOTE_INIT_MODAL,
ACCOUNT_NOTE_CHANGE_COMMENT,
};

Wyświetl plik

@ -1,46 +0,0 @@
// @preval
/**
* Build config: configuration set at build time.
* @module soapbox/build-config
*/
const trim = require('lodash/trim');
const trimEnd = require('lodash/trimEnd');
const {
NODE_ENV,
BACKEND_URL,
FE_SUBDIRECTORY,
FE_BUILD_DIR,
FE_INSTANCE_SOURCE_DIR,
SENTRY_DSN,
} = process.env;
const sanitizeURL = url => {
try {
return trimEnd(new URL(url).toString(), '/');
} catch {
return '';
}
};
const sanitizeBasename = path => {
return `/${trim(path, '/')}`;
};
const sanitizePath = path => {
return trim(path, '/');
};
// JSON.parse/stringify is to emulate what @preval is doing and avoid any
// inconsistent behavior in dev mode
const sanitize = obj => JSON.parse(JSON.stringify(obj));
module.exports = sanitize({
NODE_ENV: NODE_ENV || 'development',
BACKEND_URL: sanitizeURL(BACKEND_URL),
FE_SUBDIRECTORY: sanitizeBasename(FE_SUBDIRECTORY),
FE_BUILD_DIR: sanitizePath(FE_BUILD_DIR) || 'static',
FE_INSTANCE_SOURCE_DIR: FE_INSTANCE_SOURCE_DIR || 'instance',
SENTRY_DSN,
});

Wyświetl plik

@ -1,46 +0,0 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import { useAppSelector, useCompose } from 'soapbox/hooks';
import Warning from '../components/warning';
const APPROX_HASHTAG_RE = /(?:^|[^\/\)\w])#(\w*[a-zA-Z·]\w*)/i;
interface IWarningWrapper {
composeId: string
}
const WarningWrapper: React.FC<IWarningWrapper> = ({ composeId }) => {
const compose = useCompose(composeId);
const me = useAppSelector((state) => state.me);
const needsLockWarning = useAppSelector((state) => compose.privacy === 'private' && !state.accounts.get(me)!.locked);
const hashtagWarning = (compose.privacy !== 'public' && compose.privacy !== 'group') && APPROX_HASHTAG_RE.test(compose.text);
const directMessageWarning = compose.privacy === 'direct';
if (needsLockWarning) {
return <Warning message={<FormattedMessage id='compose_form.lock_disclaimer' defaultMessage='Your account is not {locked}. Anyone can follow you to view your follower-only posts.' values={{ locked: <Link to='/settings/profile'><FormattedMessage id='compose_form.lock_disclaimer.lock' defaultMessage='locked' /></Link> }} />} />;
}
if (hashtagWarning) {
return <Warning message={<FormattedMessage id='compose_form.hashtag_warning' defaultMessage="This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag." />} />;
}
if (directMessageWarning) {
const message = (
<span>
<FormattedMessage id='compose_form.direct_message_warning' defaultMessage='This post will only be sent to the mentioned users.' />
{/* <a href='/about/tos' target='_blank'><FormattedMessage id='compose_form.direct_message_warning_learn_more' defaultMessage='Learn more' /></a> */}
</span>
);
return <Warning message={message} />;
}
return null;
};
export default WarningWrapper;

Wyświetl plik

@ -1,146 +0,0 @@
import React, { useEffect, useRef } from 'react';
import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
import { connectHashtagStream } from 'soapbox/actions/streaming';
import { fetchHashtag, followHashtag, unfollowHashtag } from 'soapbox/actions/tags';
import { expandHashtagTimeline, clearTimeline } from 'soapbox/actions/timelines';
import List, { ListItem } from 'soapbox/components/list';
import { Column, Toggle } from 'soapbox/components/ui';
import Timeline from 'soapbox/features/ui/components/timeline';
import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks';
import type { Tag as TagEntity } from 'soapbox/types/entities';
type Mode = 'any' | 'all' | 'none';
type Tag = { value: string };
type Tags = { [k in Mode]: Tag[] };
const messages = defineMessages({
any: { id: 'hashtag.column_header.tag_mode.any', defaultMessage: 'or {additional}' },
all: { id: 'hashtag.column_header.tag_mode.all', defaultMessage: 'and {additional}' },
none: { id: 'hashtag.column_header.tag_mode.none', defaultMessage: 'without {additional}' },
empty: { id: 'empty_column.hashtag', defaultMessage: 'There is nothing in this hashtag yet.' },
});
interface IHashtagTimeline {
params?: {
id?: string
tags?: Tags
}
}
export const HashtagTimeline: React.FC<IHashtagTimeline> = ({ params }) => {
const intl = useIntl();
const id = params?.id || '';
const tags = params?.tags || { any: [], all: [], none: [] };
const features = useFeatures();
const dispatch = useAppDispatch();
const disconnects = useRef<(() => void)[]>([]);
const tag = useAppSelector((state) => state.tags.get(id));
const next = useAppSelector(state => state.timelines.get(`hashtag:${id}`)?.next);
// Mastodon supports displaying results from multiple hashtags.
// https://github.com/mastodon/mastodon/issues/6359
const title = (): string => {
const title: string[] = [`#${id}`];
if (additionalFor('any')) {
title.push(' ', intl.formatMessage(messages.any, { additional: additionalFor('any') }));
}
if (additionalFor('all')) {
title.push(' ', intl.formatMessage(messages.any, { additional: additionalFor('all') }));
}
if (additionalFor('none')) {
title.push(' ', intl.formatMessage(messages.any, { additional: additionalFor('none') }));
}
return title.join('');
};
const additionalFor = (mode: Mode) => {
if (tags && (tags[mode] || []).length > 0) {
return tags[mode].map(tag => tag.value).join('/');
} else {
return '';
}
};
const subscribe = () => {
const any = tags.any.map(tag => tag.value);
const all = tags.all.map(tag => tag.value);
const none = tags.none.map(tag => tag.value);
[id, ...any].map(tag => {
disconnects.current.push(dispatch(connectHashtagStream(id, tag, status => {
const tags = status.tags.map((tag: TagEntity) => tag.name);
return all.filter(tag => tags.includes(tag)).length === all.length &&
none.filter(tag => tags.includes(tag)).length === 0;
})));
});
};
const unsubscribe = () => {
disconnects.current.map(disconnect => disconnect());
disconnects.current = [];
};
const handleLoadMore = (maxId: string) => {
dispatch(expandHashtagTimeline(id, { url: next, maxId, tags }));
};
const handleFollow = () => {
if (tag?.following) {
dispatch(unfollowHashtag(id));
} else {
dispatch(followHashtag(id));
}
};
useEffect(() => {
subscribe();
dispatch(expandHashtagTimeline(id, { tags }));
dispatch(fetchHashtag(id));
return () => {
unsubscribe();
};
}, []);
useEffect(() => {
unsubscribe();
subscribe();
dispatch(clearTimeline(`hashtag:${id}`));
dispatch(expandHashtagTimeline(id, { tags }));
}, [id]);
return (
<Column bodyClassName='space-y-3' label={title()} transparent>
{features.followHashtags && (
<List>
<ListItem
label={<FormattedMessage id='hashtag.follow' defaultMessage='Follow hashtag' />}
>
<Toggle
checked={tag?.following}
onChange={handleFollow}
/>
</ListItem>
</List>
)}
<Timeline
scrollKey='hashtag_timeline'
timelineId={`hashtag:${id}`}
onLoadMore={handleLoadMore}
emptyMessage={intl.formatMessage(messages.empty)}
divideType='space'
/>
</Column>
);
};
export default HashtagTimeline;

Wyświetl plik

@ -1,89 +0,0 @@
import React from 'react';
import { Route, Switch } from 'react-router-dom';
import { buildAccount } from 'soapbox/jest/factory';
import { render, screen, waitFor } from '../../../jest/test-helpers';
import { normalizeInstance } from '../../../normalizers';
import UI from '../index';
import { WrappedRoute } from '../util/react-router-helpers';
const TestableComponent = () => (
<Switch>
<Route path='/@:username/posts/:statusId' exact><UI /></Route>
<Route path='/@:username/media' exact><UI /></Route>
<Route path='/@:username' exact><UI /></Route>
<Route path='/login' exact><span data-testid='sign-in'>Sign in</span></Route>
{/* WrappedRount will redirect to /login for logged out users... which will resolve to the route above! */}
<WrappedRoute path='/notifications' component={() => null} />
</Switch>
);
describe('<UI />', () => {
let store: any;
beforeEach(() => {
store = {
me: false,
accounts: {
'1': buildAccount({
id: '1',
acct: 'username',
display_name: 'My name',
avatar: 'test.jpg',
}),
},
instance: normalizeInstance({ registrations: true }),
};
});
describe('when logged out', () => {
describe('when viewing a Profile Page', () => {
it('should render the Profile page', async() => {
render(
<TestableComponent />,
{},
store,
{ initialEntries: ['/@username'] },
);
await waitFor(() => {
expect(screen.getByTestId('cta-banner')).toHaveTextContent('Sign up now to discuss');
}, {
timeout: 5000,
});
});
});
describe('when viewing a Status Page', () => {
it('should render the Status page', async() => {
render(
<TestableComponent />,
{},
store,
{ initialEntries: ['/@username/posts/12'] },
);
await waitFor(() => {
expect(screen.getByTestId('cta-banner')).toHaveTextContent('Sign up now to discuss');
});
});
});
describe('when viewing Notifications', () => {
it('should redirect to the login page', async() => {
render(
<TestableComponent />,
{},
store,
{ initialEntries: ['/notifications'] },
);
await waitFor(() => {
expect(screen.getByTestId('sign-in')).toHaveTextContent('Sign in');
});
});
});
});
});

Wyświetl plik

@ -1,67 +0,0 @@
import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { changeAccountNoteComment, submitAccountNote } from 'soapbox/actions/account-notes';
import { closeModal } from 'soapbox/actions/modals';
import { useAccount } from 'soapbox/api/hooks';
import { Modal, Text } from 'soapbox/components/ui';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
const messages = defineMessages({
placeholder: { id: 'account_note.placeholder', defaultMessage: 'No comment provided' },
save: { id: 'account_note.save', defaultMessage: 'Save' },
});
const AccountNoteModal = () => {
const intl = useIntl();
const dispatch = useAppDispatch();
const isSubmitting = useAppSelector((state) => state.account_notes.edit.isSubmitting);
const accountId = useAppSelector((state) => state.account_notes.edit.account);
const { account } = useAccount(accountId || undefined);
const comment = useAppSelector((state) => state.account_notes.edit.comment);
const onClose = () => {
dispatch(closeModal('ACCOUNT_NOTE'));
};
const handleCommentChange: React.ChangeEventHandler<HTMLTextAreaElement> = e => {
dispatch(changeAccountNoteComment(e.target.value));
};
const handleSubmit = () => {
dispatch(submitAccountNote());
};
const handleKeyDown: React.KeyboardEventHandler<HTMLTextAreaElement> = e => {
if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
handleSubmit();
}
};
return (
<Modal
title={<FormattedMessage id='account_note.target' defaultMessage='Note for @{target}' values={{ target: account!.acct }} />}
onClose={onClose}
confirmationAction={handleSubmit}
confirmationText={intl.formatMessage(messages.save)}
confirmationDisabled={isSubmitting}
>
<Text theme='muted'>
<FormattedMessage id='account_note.hint' defaultMessage='You can keep notes about this user for yourself (this will not be shared with them):' />
</Text>
<textarea
className='setting-text light'
placeholder={intl.formatMessage(messages.placeholder)}
value={comment}
onChange={handleCommentChange}
onKeyDown={handleKeyDown}
disabled={isSubmitting}
autoFocus
/>
</Modal>
);
};
export default AccountNoteModal;

Wyświetl plik

@ -1,643 +0,0 @@
export function EmojiPicker() {
return import(/* webpackChunkName: "emoji_picker" */'../../emoji/components/emoji-picker');
}
export function Notifications() {
return import(/* webpackChunkName: "features/notifications" */'../../notifications');
}
export function HomeTimeline() {
return import(/* webpackChunkName: "features/home_timeline" */'../../home-timeline');
}
export function PublicTimeline() {
return import(/* webpackChunkName: "features/public_timeline" */'../../public-timeline');
}
export function RemoteTimeline() {
return import(/* webpackChunkName: "features/remote_timeline" */'../../remote-timeline');
}
export function CommunityTimeline() {
return import(/* webpackChunkName: "features/community_timeline" */'../../community-timeline');
}
export function HashtagTimeline() {
return import(/* webpackChunkName: "features/hashtag_timeline" */'../../hashtag-timeline');
}
export function DirectTimeline() {
return import(/* webpackChunkName: "features/direct_timeline" */'../../direct-timeline');
}
export function Conversations() {
return import(/* webpackChunkName: "features/conversations" */'../../conversations');
}
export function ListTimeline() {
return import(/* webpackChunkName: "features/list_timeline" */'../../list-timeline');
}
export function Lists() {
return import(/* webpackChunkName: "features/lists" */'../../lists');
}
export function Bookmarks() {
return import(/* webpackChunkName: "features/bookmarks" */'../../bookmarks');
}
export function Status() {
return import(/* webpackChunkName: "features/status" */'../../status');
}
export function PinnedStatuses() {
return import(/* webpackChunkName: "features/pinned_statuses" */'../../pinned-statuses');
}
export function AccountTimeline() {
return import(/* webpackChunkName: "features/account_timeline" */'../../account-timeline');
}
export function AccountGallery() {
return import(/* webpackChunkName: "features/account_gallery" */'../../account-gallery');
}
export function Followers() {
return import(/* webpackChunkName: "features/followers" */'../../followers');
}
export function Following() {
return import(/* webpackChunkName: "features/following" */'../../following');
}
export function FollowRequests() {
return import(/* webpackChunkName: "features/follow_requests" */'../../follow-requests');
}
export function GenericNotFound() {
return import(/* webpackChunkName: "features/generic_not_found" */'../../generic-not-found');
}
export function FavouritedStatuses() {
return import(/* webpackChunkName: "features/favourited_statuses" */'../../favourited-statuses');
}
export function Blocks() {
return import(/* webpackChunkName: "features/blocks" */'../../blocks');
}
export function DomainBlocks() {
return import(/* webpackChunkName: "features/domain_blocks" */'../../domain-blocks');
}
export function Mutes() {
return import(/* webpackChunkName: "features/mutes" */'../../mutes');
}
export function MuteModal() {
return import(/* webpackChunkName: "modals/mute_modal" */'../components/modals/mute-modal');
}
export function Filters() {
return import(/* webpackChunkName: "features/filters" */'../../filters');
}
export function EditFilter() {
return import(/* webpackChunkName: "features/filters" */'../../filters/edit-filter');
}
export function ReportModal() {
return import(/* webpackChunkName: "modals/report-modal/report-modal" */'../components/modals/report-modal/report-modal');
}
export function AccountModerationModal() {
return import(/* webpackChunkName: "modals/account-moderation-modal" */'../components/modals/account-moderation-modal/account-moderation-modal');
}
export function PolicyModal() {
return import(/* webpackChunkName: "modals/policy-modal" */'../components/modals/policy-modal');
}
export function MediaGallery() {
return import(/* webpackChunkName: "status/media_gallery" */'../../../components/media-gallery');
}
export function Video() {
return import(/* webpackChunkName: "features/video" */'../../video');
}
export function Audio() {
return import(/* webpackChunkName: "features/audio" */'../../audio');
}
export function MediaModal() {
return import(/* webpackChunkName: "features/ui" */'../components/modals/media-modal');
}
export function VideoModal() {
return import(/* webpackChunkName: "features/ui" */'../components/modals/video-modal');
}
export function BoostModal() {
return import(/* webpackChunkName: "features/ui" */'../components/modals/boost-modal');
}
export function ConfirmationModal() {
return import(/* webpackChunkName: "features/ui" */'../components/modals/confirmation-modal');
}
export function MissingDescriptionModal() {
return import(/* webpackChunkName: "features/ui" */'../components/modals/missing-description-modal');
}
export function ActionsModal() {
return import(/* webpackChunkName: "features/ui" */'../components/modals/actions-modal');
}
export function HotkeysModal() {
return import(/* webpackChunkName: "features/ui" */'../components/modals/hotkeys-modal');
}
export function ComposeModal() {
return import(/* webpackChunkName: "features/ui" */'../components/modals/compose-modal');
}
export function ReplyMentionsModal() {
return import(/* webpackChunkName: "features/ui" */'../components/modals/reply-mentions-modal');
}
export function UnauthorizedModal() {
return import(/* webpackChunkName: "features/ui" */'../components/modals/unauthorized-modal');
}
export function EditFederationModal() {
return import(/* webpackChunkName: "features/ui" */'../components/modals/edit-federation-modal');
}
export function EmbedModal() {
return import(/* webpackChunkName: "modals/embed_modal" */'../components/modals/embed-modal');
}
export function ComponentModal() {
return import(/* webpackChunkName: "features/ui" */'../components/modals/component-modal');
}
export function ReblogsModal() {
return import(/* webpackChunkName: "features/ui" */'../components/modals/reblogs-modal');
}
export function FavouritesModal() {
return import(/* webpackChunkName: "features/ui" */'../components/modals/favourites-modal');
}
export function DislikesModal() {
return import(/* webpackChunkName: "features/ui" */'../components/modals/dislikes-modal');
}
export function ReactionsModal() {
return import(/* webpackChunkName: "features/ui" */'../components/modals/reactions-modal');
}
export function MentionsModal() {
return import(/* webpackChunkName: "features/ui" */'../components/modals/mentions-modal');
}
export function LandingPageModal() {
return import(/* webpackChunkName: "features/ui/modals/landing-page-modal" */'../components/modals/landing-page-modal');
}
export function BirthdaysModal() {
return import(/* webpackChunkName: "features/ui" */'../components/modals/birthdays-modal');
}
export function BirthdayPanel() {
return import(/* webpackChunkName: "features/ui" */'../../../components/birthday-panel');
}
export function AccountNoteModal() {
return import(/* webpackChunkName: "features/ui" */'../components/modals/account-note-modal');
}
export function ListEditor() {
return import(/* webpackChunkName: "features/list_editor" */'../../list-editor');
}
export function ListAdder() {
return import(/*webpackChunkName: "features/list_adder" */'../../list-adder');
}
export function Search() {
return import(/*webpackChunkName: "features/search" */'../../search');
}
export function LoginPage() {
return import(/* webpackChunkName: "features/auth_login" */'../../auth-login/components/login-page');
}
export function ExternalLogin() {
return import(/* webpackChunkName: "features/external_login" */'../../external-login');
}
export function LogoutPage() {
return import(/* webpackChunkName: "features/auth_login" */'../../auth-login/components/logout');
}
export function Settings() {
return import(/* webpackChunkName: "features/settings" */'../../settings');
}
export function EditProfile() {
return import(/* webpackChunkName: "features/edit_profile" */'../../edit-profile');
}
export function EditEmail() {
return import(/* webpackChunkName: "features/edit_email" */'../../edit-email');
}
export function EmailConfirmation() {
return import(/* webpackChunkName: "features/email_confirmation" */'../../email-confirmation');
}
export function EditPassword() {
return import(/* webpackChunkName: "features/edit_password" */'../../edit-password');
}
export function DeleteAccount() {
return import(/* webpackChunkName: "features/delete_account" */'../../delete-account');
}
export function SoapboxConfig() {
return import(/* webpackChunkName: "features/soapbox_config" */'../../soapbox-config');
}
export function ExportData() {
return import(/* webpackChunkName: "features/export_data" */ '../../export-data');
}
export function ImportData() {
return import(/* webpackChunkName: "features/import_data" */'../../import-data');
}
export function Backups() {
return import(/* webpackChunkName: "features/backups" */'../../backups');
}
export function PasswordReset() {
return import(/* webpackChunkName: "features/auth_login" */'../../auth-login/components/password-reset');
}
export function PasswordResetConfirm() {
return import(/* webpackChunkName: "features/auth_login/password_reset_confirm" */'../../auth-login/components/password-reset-confirm');
}
export function MfaForm() {
return import(/* webpackChunkName: "features/security/mfa_form" */'../../security/mfa-form');
}
export function ChatIndex() {
return import(/* webpackChunkName: "features/chats" */'../../chats');
}
export function ChatWidget() {
return import(/* webpackChunkName: "features/chats/components/chat-widget" */'../../chats/components/chat-widget/chat-widget');
}
export function ServerInfo() {
return import(/* webpackChunkName: "features/server_info" */'../../server-info');
}
export function Dashboard() {
return import(/* webpackChunkName: "features/admin" */'../../admin');
}
export function ModerationLog() {
return import(/* webpackChunkName: "features/admin/moderation_log" */'../../admin/moderation-log');
}
export function ThemeEditor() {
return import(/* webpackChunkName: "features/theme-editor" */'../../theme-editor');
}
export function UserPanel() {
return import(/* webpackChunkName: "features/ui" */'../components/user-panel');
}
export function PromoPanel() {
return import(/* webpackChunkName: "features/ui" */'../components/promo-panel');
}
export function SignUpPanel() {
return import(/* webpackChunkName: "features/ui" */'../components/panels/sign-up-panel');
}
export function CtaBanner() {
return import(/* webpackChunkName: "features/ui" */'../components/cta-banner');
}
export function FundingPanel() {
return import(/* webpackChunkName: "features/ui" */'../components/funding-panel');
}
export function TrendsPanel() {
return import(/* webpackChunkName: "features/trends" */'../components/trends-panel');
}
export function ProfileInfoPanel() {
return import(/* webpackChunkName: "features/account_timeline" */'../components/profile-info-panel');
}
export function ProfileMediaPanel() {
return import(/* webpackChunkName: "features/account_gallery" */'../components/profile-media-panel');
}
export function ProfileFieldsPanel() {
return import(/* webpackChunkName: "features/account_timeline" */'../components/profile-fields-panel');
}
export function PinnedAccountsPanel() {
return import(/* webpackChunkName: "features/pinned_accounts" */'../components/pinned-accounts-panel');
}
export function InstanceInfoPanel() {
return import(/* webpackChunkName: "features/remote_timeline" */'../components/instance-info-panel');
}
export function InstanceModerationPanel() {
return import(/* webpackChunkName: "features/remote_timeline" */'../components/instance-moderation-panel');
}
export function LatestAccountsPanel() {
return import(/* webpackChunkName: "features/admin" */'../../admin/components/latest-accounts-panel');
}
export function SidebarMenu() {
return import(/* webpackChunkName: "features/ui" */'../../../components/sidebar-menu');
}
export function ModalContainer() {
return import(/* webpackChunkName: "features/ui" */'../containers/modal-container');
}
export function ProfileHoverCard() {
return import(/* webpackChunkName: "features/ui" */'soapbox/components/profile-hover-card');
}
export function StatusHoverCard() {
return import(/* webpackChunkName: "features/ui" */'soapbox/components/status-hover-card');
}
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/modals/crypto-donate-modal');
}
export function ScheduledStatuses() {
return import(/* webpackChunkName: "features/scheduled_statuses" */'../../scheduled-statuses');
}
export function UserIndex() {
return import(/* webpackChunkName: "features/admin/user_index" */'../../admin/user-index');
}
export function FederationRestrictions() {
return import(/* webpackChunkName: "features/federation_restrictions" */'../../federation-restrictions');
}
export function Aliases() {
return import(/* webpackChunkName: "features/aliases" */'../../aliases');
}
export function Migration() {
return import(/* webpackChunkName: "features/migration" */'../../migration');
}
export function ScheduleForm() {
return import(/* webpackChunkName: "features/compose" */'../../compose/components/schedule-form');
}
export function WhoToFollowPanel() {
return import(/* webpackChunkName: "features/follow_recommendations" */'../components/who-to-follow-panel');
}
export function FollowRecommendations() {
return import(/* webpackChunkName: "features/follow-recommendations" */'../../follow-recommendations');
}
export function Directory() {
return import(/* webpackChunkName: "features/directory" */'../../directory');
}
export function RegisterInvite() {
return import(/* webpackChunkName: "features/register_invite" */'../../register-invite');
}
export function Share() {
return import(/* webpackChunkName: "features/share" */'../../share');
}
export function NewStatus() {
return import(/* webpackChunkName: "features/new_status" */'../../new-status');
}
export function IntentionalError() {
return import(/* webpackChunkName: "error" */'../../intentional-error');
}
export function Developers() {
return import(/* webpackChunkName: "features/developers" */'../../developers');
}
export function CreateApp() {
return import(/* webpackChunkName: "features/developers" */'../../developers/apps/create');
}
export function SettingsStore() {
return import(/* webpackChunkName: "features/developers" */'../../developers/settings-store');
}
export function TestTimeline() {
return import(/* webpackChunkName: "features/test_timeline" */'../../test-timeline');
}
export function ServiceWorkerInfo() {
return import(/* webpackChunkName: "features/developers" */'../../developers/service-worker-info');
}
export function DatePicker() {
return import(/* webpackChunkName: "date_picker" */'../../birthdays/date-picker');
}
export function OnboardingWizard() {
return import(/* webpackChunkName: "features/onboarding" */'../../onboarding/onboarding-wizard');
}
export function WaitlistPage() {
return import(/* webpackChunkName: "features/verification" */'../../verification/waitlist-page');
}
export function CompareHistoryModal() {
return import(/*webpackChunkName: "modals/compare_history_modal" */'../components/modals/compare-history-modal');
}
export function AuthTokenList() {
return import(/* webpackChunkName: "features/auth_token_list" */'../../auth-token-list');
}
export function VerifySmsModal() {
return import(/* webpackChunkName: "features/ui" */'../components/modals/verify-sms-modal');
}
export function FamiliarFollowersModal() {
return import(/*webpackChunkName: "modals/familiar_followers_modal" */'../components/modals/familiar-followers-modal');
}
export function AnnouncementsPanel() {
return import(/* webpackChunkName: "features/announcements" */'../../../components/announcements/announcements-panel');
}
export function Quotes() {
return import(/*webpackChunkName: "features/quotes" */'../../quotes');
}
export function ComposeEventModal() {
return import(/* webpackChunkName: "features/compose_event_modal" */'../components/modals/compose-event-modal/compose-event-modal');
}
export function JoinEventModal() {
return import(/* webpackChunkName: "features/join_event_modal" */'../components/modals/join-event-modal');
}
export function EventHeader() {
return import(/* webpackChunkName: "features/event" */'../../event/components/event-header');
}
export function EventInformation() {
return import(/* webpackChunkName: "features/event" */'../../event/event-information');
}
export function EventDiscussion() {
return import(/* webpackChunkName: "features/event" */'../../event/event-discussion');
}
export function EventMapModal() {
return import(/* webpackChunkName: "modals/event-map-modal" */'../components/modals/event-map-modal');
}
export function EventParticipantsModal() {
return import(/* webpackChunkName: "modals/event-participants-modal" */'../components/modals/event-participants-modal');
}
export function Events() {
return import(/* webpackChunkName: "features/events" */'../../events');
}
export function Groups() {
return import(/* webpackChunkName: "features/groups" */'../../groups');
}
export function GroupsDiscover() {
return import(/* webpackChunkName: "features/groups" */'../../groups/discover');
}
export function GroupsPopular() {
return import(/* webpackChunkName: "features/groups" */'../../groups/popular');
}
export function GroupsSuggested() {
return import(/* webpackChunkName: "features/groups" */'../../groups/suggested');
}
export function GroupsTag() {
return import(/* webpackChunkName: "features/groups" */'../../groups/tag');
}
export function GroupsTags() {
return import(/* webpackChunkName: "features/groups" */'../../groups/tags');
}
export function PendingGroupRequests() {
return import(/* webpackChunkName: "features/groups" */'../../groups/pending-requests');
}
export function GroupMembers() {
return import(/* webpackChunkName: "features/groups" */'../../group/group-members');
}
export function GroupTags() {
return import(/* webpackChunkName: "features/groups" */'../../group/group-tags');
}
export function GroupTagTimeline() {
return import(/* webpackChunkName: "features/groups" */'../../group/group-tag-timeline');
}
export function GroupTimeline() {
return import(/* webpackChunkName: "features/groups" */'../../group/group-timeline');
}
export function ManageGroup() {
return import(/* webpackChunkName: "features/groups" */'../../group/manage-group');
}
export function EditGroup() {
return import(/* webpackChunkName: "features/groups" */'../../group/edit-group');
}
export function GroupBlockedMembers() {
return import(/* webpackChunkName: "features/groups" */'../../group/group-blocked-members');
}
export function GroupMembershipRequests() {
return import(/* webpackChunkName: "features/groups" */'../../group/group-membership-requests');
}
export function GroupGallery() {
return import(/* webpackChunkName: "features/groups" */'../../group/group-gallery');
}
export function CreateGroupModal() {
return import(/* webpackChunkName: "features/groups" */'../components/modals/manage-group-modal/create-group-modal');
}
export function NewGroupPanel() {
return import(/* webpackChunkName: "features/groups" */'../components/panels/new-group-panel');
}
export function MyGroupsPanel() {
return import(/* webpackChunkName: "features/groups" */'../components/panels/my-groups-panel');
}
export function SuggestedGroupsPanel() {
return import(/* webpackChunkName: "features/groups" */'../components/panels/suggested-groups-panel');
}
export function GroupMediaPanel() {
return import(/* webpackChunkName: "features/groups" */'../components/group-media-panel');
}
export function NewEventPanel() {
return import(/* webpackChunkName: "features/events" */'../components/panels/new-event-panel');
}
export function Announcements() {
return import(/* webpackChunkName: "features/admin/announcements" */'../../admin/announcements');
}
export function EditAnnouncementModal() {
return import(/* webpackChunkName: "features/admin/announcements" */'../components/modals/edit-announcement-modal');
}
export function FollowedTags() {
return import(/* webpackChunkName: "features/followed-tags" */'../../followed-tags');
}

Wyświetl plik

@ -1,29 +0,0 @@
import { Map as ImmutableMap, Record as ImmutableRecord } from 'immutable';
import { ACCOUNT_IMPORT } from 'soapbox/actions/importer';
import reducer from '../accounts';
describe('accounts reducer', () => {
it('should return the initial state', () => {
expect(reducer(undefined, {} as any)).toEqual(ImmutableMap());
});
describe('ACCOUNT_IMPORT', () => {
it('parses the account as a Record', () => {
const account = require('soapbox/__fixtures__/pleroma-account.json');
const action = { type: ACCOUNT_IMPORT, account };
const result = reducer(undefined, action).get('9v5bmRalQvjOy0ECcC');
expect(ImmutableRecord.isRecord(result)).toBe(true);
});
it('minifies a moved account', () => {
const account = require('soapbox/__fixtures__/account-moved.json');
const action = { type: ACCOUNT_IMPORT, account };
const result = reducer(undefined, action).get('106801667066418367');
expect(result?.moved).toBe('107945464165013501');
});
});
});

Wyświetl plik

@ -1,43 +0,0 @@
import { Record as ImmutableRecord } from 'immutable';
import {
ACCOUNT_NOTE_INIT_MODAL,
ACCOUNT_NOTE_CHANGE_COMMENT,
ACCOUNT_NOTE_SUBMIT_REQUEST,
ACCOUNT_NOTE_SUBMIT_FAIL,
ACCOUNT_NOTE_SUBMIT_SUCCESS,
} from '../actions/account-notes';
import type { AnyAction } from 'redux';
export const EditRecord = ImmutableRecord({
isSubmitting: false,
account: null as string | null,
comment: '',
});
export const ReducerRecord = ImmutableRecord({
edit: EditRecord(),
});
type State = ReturnType<typeof ReducerRecord>;
export default function account_notes(state: State = ReducerRecord(), action: AnyAction) {
switch (action.type) {
case ACCOUNT_NOTE_INIT_MODAL:
return state.withMutations((state) => {
state.setIn(['edit', 'isSubmitting'], false);
state.setIn(['edit', 'account'], action.account.get('id'));
state.setIn(['edit', 'comment'], action.comment);
});
case ACCOUNT_NOTE_CHANGE_COMMENT:
return state.setIn(['edit', 'comment'], action.comment);
case ACCOUNT_NOTE_SUBMIT_REQUEST:
return state.setIn(['edit', 'isSubmitting'], true);
case ACCOUNT_NOTE_SUBMIT_FAIL:
case ACCOUNT_NOTE_SUBMIT_SUCCESS:
return state.setIn(['edit', 'isSubmitting'], false);
default:
return state;
}
}

Wyświetl plik

@ -1 +0,0 @@
import './web-push-notifications';

Wyświetl plik

@ -1,8 +0,0 @@
import type { Event, EventTemplate } from 'nostr-tools';
interface Nostr {
getPublicKey(): Promise<string>
signEvent(event: EventTemplate): Promise<Event>
}
export default Nostr;

Wyświetl plik

@ -1,42 +0,0 @@
import { danger, warn, message } from 'danger';
// App changes
const app = danger.git.fileMatch('app/soapbox/**');
// Docs changes
const docs = danger.git.fileMatch('docs/**/*.md');
if (docs.edited) {
message('Thanks - We :heart: our [documentarians](http://www.writethedocs.org/)!');
}
// Enforce CHANGELOG.md additions
const changelog = danger.git.fileMatch('CHANGELOG.md');
if (app.edited && !changelog.edited) {
warn('You have not updated `CHANGELOG.md`. If this change directly impacts admins or users, please update the changelog. Otherwise you can ignore this message. See: https://keepachangelog.com');
}
// UI components
const uiCode = danger.git.fileMatch('app/soapbox/components/ui/**');
const uiTests = danger.git.fileMatch('app/soapbox/components/ui/**/__tests__/**');
if (uiCode.edited && !uiTests.edited) {
warn('You have UI changes (`soapbox/components/ui`) without tests.');
}
// Actions
const actionsCode = danger.git.fileMatch('app/soapbox/actions/**');
const actionsTests = danger.git.fileMatch('app/soapbox/actions/**__tests__/**');
if (actionsCode.edited && !actionsTests.edited) {
warn('You have actions changes (`soapbox/actions`) without tests.');
}
// Reducers
const reducersCode = danger.git.fileMatch('app/soapbox/reducers/**');
const reducersTests = danger.git.fileMatch('app/soapbox/reducers/**__tests__/**');
if (reducersCode.edited && !reducersTests.edited) {
warn('You have reducer changes (`soapbox/reducers`) without tests.');
}

Wyświetl plik

@ -11,7 +11,7 @@ The best way to get Soapbox builds is from a GitLab CI job.
The official build URL is here:
```
https://gitlab.com/soapbox-pub/soapbox/-/jobs/artifacts/develop/download?job=build-production
https://dl.soapbox.pub/main/soapbox.zip
```
(Note that `develop` in that URL can be replaced with any git ref, eg `v2.0.0`, and thus will be updated with the latest zip whenever a new commit is pushed to `develop`.)
@ -44,7 +44,7 @@ location ~ ^/(api|oauth|admin) {
}
```
We recommend trying [`mastodon.conf`](https://gitlab.com/soapbox-pub/soapbox/-/blob/develop/installation/mastodon.conf) as a starting point.
We recommend trying [`mastodon.conf`](https://gitlab.com/soapbox-pub/soapbox/-/blob/main/installation/mastodon.conf) as a starting point.
It is fine-tuned, includes support for federation, and should work with any backend.
## The ServiceWorker

Wyświetl plik

@ -7,7 +7,7 @@ If you want to install Soapbox to a Pleroma instance installed using [YunoHost](
First, download the latest build of Soapbox from GitLab.
```sh
curl -L https://gitlab.com/soapbox-pub/soapbox/-/jobs/artifacts/develop/download?job=build-production -o soapbox-fe.zip
curl -O https://dl.soapbox.pub/main/soapbox.zip
```
## 2. Unzip the build
@ -15,7 +15,7 @@ curl -L https://gitlab.com/soapbox-pub/soapbox/-/jobs/artifacts/develop/download
Then, unzip the build to the Pleroma directory under YunoHost's directory:
```sh
busybox unzip soapbox-fe.zip -o -d /home/yunohost.app/pleroma/
busybox unzip soapbox.zip -o -d /home/yunohost.app/pleroma/static
```
## 3. Change YunoHost Admin Static directory

Wyświetl plik

@ -8,16 +8,16 @@ To do so, shell into your server and unpack Soapbox:
```sh
mkdir -p /opt/soapbox
curl -L https://gitlab.com/soapbox-pub/soapbox/-/jobs/artifacts/develop/download?job=build-production -o soapbox-fe.zip
curl -O https://dl.soapbox.pub/main/soapbox.zip
busybox unzip soapbox-fe.zip -o -d /opt/soapbox
busybox unzip soapbox.zip -o -d /opt/soapbox
```
Now create an Nginx file for Soapbox with Mastodon.
If you already have one, replace it:
```sh
curl https://gitlab.com/soapbox-pub/soapbox/-/raw/develop/installation/mastodon.conf > /etc/nginx/sites-available/mastodon
curl https://gitlab.com/soapbox-pub/soapbox/-/raw/main/installation/mastodon.conf > /etc/nginx/sites-available/mastodon
```
Edit this file and replace all occurrences of `example.com` with your domain name.

Wyświetl plik

@ -1,6 +1,6 @@
# Updating Soapbox
You should always check the [release notes/changelog](https://gitlab.com/soapbox-pub/soapbox/-/blob/develop/CHANGELOG.md) in case there are deprecations, special update changes, etc.
You should always check the [release notes/changelog](https://gitlab.com/soapbox-pub/soapbox/-/blob/main/CHANGELOG.md) in case there are deprecations, special update changes, etc.
Besides that, it's relatively pretty easy to update Soapbox. There's two ways to go about it: with the command line or with an unofficial script.
@ -10,15 +10,10 @@ To update Soapbox via the command line, do the following:
```
# Download the build.
curl -L https://gitlab.com/soapbox-pub/soapbox/-/jobs/artifacts/develop/download?job=build-production -o soapbox-fe.zip
# Remove all the current Soapbox build in Pleroma's instance directory.
rm -R /opt/pleroma/instance/static/packs
rm /opt/pleroma/instance/static/index.html
rm -R /opt/pleroma/instance/static/sounds
curl -O https://dl.soapbox.pub/main/soapbox.zip
# Unzip the new build to Pleroma's instance directory.
busybox unzip soapbox-fe.zip -o -d /opt/pleroma/instance
busybox unzip soapbox.zip -o -d /opt/pleroma/instance/static
```
## After updating Soapbox

Wyświetl plik

@ -15,7 +15,7 @@ When contributing to Soapbox, please first discuss the change you wish to make b
When you push to a branch, the CI pipeline will run.
[Soapbox uses GitLab CI](https://gitlab.com/soapbox-pub/soapbox/-/blob/develop/.gitlab-ci.yml) to lint, run tests, and verify changes.
[Soapbox uses GitLab CI](https://gitlab.com/soapbox-pub/soapbox/-/blob/main/.gitlab-ci.yml) to lint, run tests, and verify changes.
It's important this pipeline passes, otherwise we cannot merge the change.
New users of gitlab.com may see a "detatched pipeline" error.
@ -31,4 +31,4 @@ We recommend developing Soapbox with [VSCodium](https://vscodium.com/) (or its p
This will help give you feedback about your changes _in the editor itself_ before GitLab CI performs linting, etc.
When this project is opened in Code it will automatically recommend extensions.
See [`.vscode/extensions.json`](https://gitlab.com/soapbox-pub/soapbox/-/blob/develop/.vscode/extensions.json) for the full list.
See [`.vscode/extensions.json`](https://gitlab.com/soapbox-pub/soapbox/-/blob/main/.vscode/extensions.json) for the full list.

Wyświetl plik

@ -71,7 +71,7 @@ For example:
}
```
See `app/soapbox/utils/features.js` for the full list of features.
See `src/utils/features.js` for the full list of features.
### Embedded app (`custom/app.json`)
@ -118,7 +118,7 @@ When compiling Soapbox, environment variables may be passed to change the build
For example:
```sh
NODE_ENV="production" FE_BUILD_DIR="public" FE_SUBDIRECTORY="/soapbox" yarn build
NODE_ENV="production" FE_SUBDIRECTORY="/soapbox" yarn build
```
### `NODE_ENV`
@ -147,16 +147,6 @@ Options:
Default: `""`
### `FE_BUILD_DIR`
The folder to put build files in. This is mostly useful for CI tasks like GitLab Pages.
Options:
- Any directory name, eg `"public"`
Default: `"static"`
### `FE_SUBDIRECTORY`
Subdirectory to host Soapbox out of.

Wyświetl plik

@ -48,7 +48,7 @@ Typically checks are done against `BACKEND_NAME` and `VERSION`.
The version string is similar in purpose to a [User-Agent](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent) string.
The format was first invented by Pleroma, but is now widely used, including by Pixelfed, Mitra, and Soapbox BE.
See [`features.ts`](https://gitlab.com/soapbox-pub/soapbox/-/blob/develop/app/soapbox/utils/features.ts) for the complete list of features.
See [`features.ts`](https://gitlab.com/soapbox-pub/soapbox/-/blob/main/src/utils/features.ts) for the complete list of features.
## Forks of other software
@ -73,4 +73,4 @@ For Pleroma forks, the fork name should be in the compat section (eg Soapbox BE)
## Adding support for a new backend
If the backend conforms to the above format, please modify [`features.ts`](https://gitlab.com/soapbox-pub/soapbox/-/blob/develop/app/soapbox/utils/features.ts) and submit a merge request to enable features for your backend!
If the backend conforms to the above format, please modify [`features.ts`](https://gitlab.com/soapbox-pub/soapbox/-/blob/main/src/utils/features.ts) and submit a merge request to enable features for your backend!

Wyświetl plik

@ -18,7 +18,7 @@ location / {
}
```
(See [`mastodon.conf`](https://gitlab.com/soapbox-pub/soapbox/-/blob/develop/installation/mastodon.conf) for a full example.)
(See [`mastodon.conf`](https://gitlab.com/soapbox-pub/soapbox/-/blob/main/installation/mastodon.conf) for a full example.)
Soapbox incorporates much of the [Mastodon API](https://docs.joinmastodon.org/methods/), [Pleroma API](https://api.pleroma.social/), and more.
It detects features supported by the backend to provide the right experience for the backend.

Wyświetl plik

@ -40,5 +40,5 @@ Try again.
## Troubleshooting: it's not working!
Run `node -V` and compare your Node.js version with the version in [`.tool-versions`](https://gitlab.com/soapbox-pub/soapbox/-/blob/develop/.tool-versions).
Run `node -V` and compare your Node.js version with the version in [`.tool-versions`](https://gitlab.com/soapbox-pub/soapbox/-/blob/main/.tool-versions).
If they don't match, try installing [asdf](https://asdf-vm.com/).

Wyświetl plik

@ -12,7 +12,7 @@ NODE_ENV=development
- `yarn dev` - Run the local dev server.
## Building
- `yarn build` - Compile without a dev server, into `/static` directory.
- `yarn build` - Compile without a dev server, into `/dist` directory.
## Translations
- `yarn i18n` - Rebuilds app and updates English locale to prepare for translations in other languages. Should always be run after editing i18n strings.

Wyświetl plik

@ -10,9 +10,9 @@ First, follow the instructions to [install Pleroma](https://docs-develop.pleroma
The Soapbox frontend is the main component of Soapbox. Once you've installed Pleroma, installing Soapbox is a breeze.
First, ssh into the server and download a .zip of the latest build: ``curl -L https://gitlab.com/soapbox-pub/soapbox/-/jobs/artifacts/develop/download?job=build-production -o soapbox-fe.zip``
First, ssh into the server and download a .zip of the latest build: `curl -O https://dl.soapbox.pub/main/soapbox.zip`
Then unpack it into Pleroma's ``instance`` directory: ``busybox unzip soapbox-fe.zip -o -d /opt/pleroma/instance``
Then unpack it into Pleroma's `instance` directory: `busybox unzip soapbox.zip -o -d /opt/pleroma/instance/static`
**That's it! 🎉 Soapbox is installed.** The change will take effect immediately, just refresh your browser tab. It's not necessary to restart the Pleroma service.

Wyświetl plik

@ -7,7 +7,8 @@
<meta name="apple-mobile-web-app-capable" content="yes">
<link href="/manifest.json" rel="manifest">
<!--server-generated-meta-->
<%= snippets %>
<script type="module" src="./src/main.tsx"></script>
<%- snippets %>
</head>
<body class="theme-mode-light no-reduce-motion">
<div id="soapbox" class="h-full">
@ -21,4 +22,4 @@
</div>
<noscript>To use this website, please enable JavaScript.</noscript>
</body>
</html>
</html>

Wyświetl plik

@ -33,7 +33,7 @@ server {
listen 80;
listen [::]:80;
server_name example.com;
root /opt/soapbox/static;
root /opt/soapbox;
location /.well-known/acme-challenge/ { allow all; }
location / { return 301 https://$host$request_uri; }
}
@ -58,7 +58,7 @@ server {
sendfile on;
client_max_body_size 80m;
root /opt/soapbox/static;
root /opt/soapbox;
gzip on;
gzip_disable "msie6";
@ -83,13 +83,13 @@ server {
# Mastodon backend routes.
# These are routes to Mastodon's API and important rendered pages.
location ~ ^/(api|oauth|auth|admin|pghero|sidekiq|manifest.json|media|nodeinfo|unsubscribe|.well-known/(webfinger|host-meta|nodeinfo|change-password)|@(.+)/embed$) {
location ~ ^/(api|inbox|oauth|auth|admin|pghero|sidekiq|manifest.json|media|nodeinfo|unsubscribe|.well-known/(webfinger|host-meta|nodeinfo|change-password)|@(.+)/embed$) {
try_files /dev/null @mastodon;
}
# Mastodon ActivityPub routes.
# Conditionally send to Mastodon by Accept header.
location ~ ^/(inbox|users|@(.+)) {
location ~ ^/(users|@(.+)) {
try_files /dev/null $activitypub_location;
}
@ -101,6 +101,13 @@ server {
try_files $uri @mastodon-packs;
}
# Mastodon Media
location /system {
add_header Cache-Control "public, max-age=31536000, immutable";
add_header Strict-Transport-Security "max-age=31536000" always;
try_files $uri @mastodon-packs;
}
# Soapbox configuration files.
# Enable CORS so we can fetch them.
location /instance {

Wyświetl plik

@ -1,50 +0,0 @@
const ASSET_EXTS = 'css|styl|less|sass|scss|png|jpg|svg|ogg|oga|mp3|ttf|woff|woff2';
module.exports = {
'testPathIgnorePatterns': [
'<rootDir>/node_modules/',
'<rootDir>/vendor/',
'<rootDir>/config/',
'<rootDir>/log/',
'<rootDir>/static/',
'<rootDir>/tmp/',
'<rootDir>/webpack/',
'<rootDir>/app/soapbox/actions/',
],
'setupFiles': [
'raf/polyfill',
],
'setupFilesAfterEnv': [
'<rootDir>/app/soapbox/jest/test-setup.ts',
],
'collectCoverageFrom': [
'app/soapbox/**/*.js',
'app/soapbox/**/*.cjs',
'app/soapbox/**/*.mjs',
'app/soapbox/**/*.ts',
'app/soapbox/**/*.tsx',
'!app/soapbox/service-worker/entry.ts',
'!app/soapbox/jest/test-setup.ts',
'!app/soapbox/jest/test-helpers.ts',
],
'coverageDirectory': '<rootDir>/.coverage/',
'coverageReporters': ['html', 'text', 'text-summary', 'cobertura'],
'reporters': ['default', 'jest-junit'],
'moduleDirectories': [
'<rootDir>/node_modules',
'<rootDir>/app',
],
'testMatch': ['**/*/__tests__/**/?(*.|*-)+(test).(ts|js)?(x)'],
'testEnvironment': 'jsdom',
'transformIgnorePatterns': [
// FIXME: react-sticky-box doesn't provide a CJS build, so transform it for now
// https://github.com/codecks-io/react-sticky-box/issues/79
`/node_modules/(?!(react-sticky-box|blurhash|emoji-mart|.+\\.(${ASSET_EXTS})$))`,
// Ignore node_modules, except static assets
// `/node_modules/(?!.+\\.(${ASSET_EXTS})$)`,
],
'transform': {
'\\.[jt]sx?$': 'babel-jest',
[`\\.(${ASSET_EXTS})$`]: '<rootDir>/jest/assetTransformer.cjs',
},
};

Wyświetl plik

@ -1,12 +0,0 @@
const path = require('path');
// Custom Jest asset transformer
// https://jestjs.io/docs/code-transformation#writing-custom-transformers
// Tries to do basically what Webpack does
module.exports = {
process(src, filename, config, options) {
return {
code: `module.exports = "https://soapbox.test/assets/${path.basename(filename)}";`,
};
},
};

Wyświetl plik

@ -2,6 +2,7 @@
"name": "soapbox",
"displayName": "Soapbox",
"version": "3.2.0",
"type": "module",
"description": "Soapbox frontend for the Fediverse.",
"homepage": "https://soapbox.pub/",
"repository": {
@ -16,44 +17,39 @@
"url": "https://gitlab.com/soapbox-pub/soapbox/-/issues"
},
"scripts": {
"start": "npx webpack-dev-server",
"start": "npx vite serve",
"dev": "${npm_execpath} run start",
"build": "npx webpack",
"build": "npx vite build --emptyOutDir",
"preview": "npx vite preview",
"audit:fix": "npx yarn-audit-fix",
"manage:translations": "npx ts-node ./scripts/translationRunner.ts",
"i18n": "rm -rf build tmp && npx cross-env NODE_ENV=production ${npm_execpath} run build && ${npm_execpath} manage:translations en",
"test": "npx cross-env NODE_ENV=test npx jest",
"test": "npx vitest",
"test:coverage": "${npm_execpath} run test --coverage",
"test:all": "${npm_execpath} run test:coverage && ${npm_execpath} run lint",
"lint": "${npm_execpath} run lint:js && ${npm_execpath} run lint:sass",
"lint:js": "npx eslint --ext .js,.jsx,.cjs,.mjs,.ts,.tsx . --cache",
"lint:sass": "npx stylelint app/styles/**/*.scss",
"prepare": "husky install",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
"lint:sass": "npx stylelint src/styles/**/*.scss",
"prepare": "husky install"
},
"license": "AGPL-3.0-or-later",
"browserslist": [
"> 0.2%",
"> 0.5%",
"last 2 versions",
"last 4 years",
"not dead"
],
"dependencies": {
"@babel/core": "^7.20.12",
"@babel/plugin-transform-react-inline-elements": "^7.18.6",
"@babel/plugin-transform-runtime": "^7.19.6",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@babel/runtime": "^7.20.13",
"@akryum/flexsearch-es": "^0.7.32",
"@babel/plugin-transform-react-inline-elements": "^7.22.5",
"@babel/plugin-transform-runtime": "^7.22.15",
"@babel/preset-env": "^7.22.15",
"@babel/preset-react": "^7.22.15",
"@babel/preset-typescript": "^7.22.15",
"@emoji-mart/data": "^1.1.2",
"@floating-ui/react": "^0.24.0",
"@fontsource/inter": "^4.5.1",
"@fontsource/roboto-mono": "^4.5.8",
"@floating-ui/react": "^0.25.0",
"@fontsource/inter": "^5.0.0",
"@fontsource/roboto-mono": "^5.0.0",
"@gamestdio/websocket": "^0.3.2",
"@jest/globals": "^29.0.0",
"@lcdp/offline-plugin": "^5.1.0",
"@metamask/providers": "^10.0.0",
"@popperjs/core": "^2.11.5",
"@reach/combobox": "^0.18.0",
@ -66,22 +62,22 @@
"@sentry/react": "^7.37.2",
"@sentry/tracing": "^7.37.2",
"@tabler/icons": "^2.0.0",
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/typography": "^0.5.9",
"@tanstack/react-query": "^4.0.10",
"@testing-library/react": "^14.0.0",
"@types/escape-html": "^1.0.1",
"@types/http-link-header": "^1.0.3",
"@types/jest": "^29.0.0",
"@types/leaflet": "^1.8.0",
"@types/lodash": "^4.14.180",
"@types/object-assign": "^4.0.30",
"@types/path-browserify": "^1.0.0",
"@types/react": "^18.0.26",
"@types/react-color": "^3.0.6",
"@types/react-datepicker": "^4.4.2",
"@types/react-dom": "^18.0.10",
"@types/react-helmet": "^6.1.5",
"@types/react-motion": "^0.0.33",
"@types/react-motion": "^0.0.34",
"@types/react-router-dom": "^5.3.3",
"@types/react-sparklines": "^1.7.2",
"@types/react-swipeable-views": "^0.13.1",
@ -89,40 +85,30 @@
"@types/seedrandom": "^3.0.2",
"@types/semver": "^7.3.9",
"@types/uuid": "^9.0.0",
"@types/webpack-assets-manifest": "^5.1.0",
"@types/webpack-bundle-analyzer": "^4.6.0",
"@types/webpack-deadcode-plugin": "^0.1.2",
"autoprefixer": "^10.4.2",
"@vitejs/plugin-react": "^4.0.4",
"autoprefixer": "^10.4.15",
"axios": "^1.2.2",
"axios-mock-adapter": "^1.21.1",
"babel-loader": "^9.0.0",
"axios-mock-adapter": "^1.22.0",
"babel-plugin-preval": "^5.1.0",
"babel-plugin-react-intl": "^7.5.20",
"blurhash": "^2.0.0",
"bootstrap-icons": "^1.5.0",
"bowser": "^2.11.0",
"browserslist": "^4.16.6",
"clsx": "^1.2.1",
"copy-webpack-plugin": "^11.0.0",
"clsx": "^2.0.0",
"core-js": "^3.27.2",
"cryptocurrency-icons": "^0.18.1",
"css-loader": "^6.7.1",
"cssnano": "^5.1.10",
"detect-passive-events": "^2.0.0",
"dotenv": "^16.0.0",
"emoji-datasource": "14.0.0",
"emoji-mart": "^5.5.2",
"escape-html": "^1.0.3",
"exif-js": "^2.3.0",
"flexsearch-ts": "^0.7.31",
"fork-ts-checker-webpack-plugin": "^8.0.0",
"exifr": "^7.1.3",
"graphemesplit": "^2.4.4",
"html-webpack-harddisk-plugin": "^2.0.0",
"html-webpack-plugin": "^5.5.0",
"http-link-header": "^1.0.2",
"immer": "^9.0.19",
"immutable": "^4.2.1",
"imports-loader": "^4.0.0",
"intersection-observer": "^0.12.2",
"intl-messageformat": "9.13.0",
"intl-messageformat-parser": "^6.0.0",
@ -133,10 +119,9 @@
"localforage": "^1.10.0",
"lodash": "^4.7.11",
"mini-css-extract-plugin": "^2.6.0",
"nostr-tools": "^1.8.1",
"nostr-tools": "^1.14.2",
"path-browserify": "^1.0.1",
"postcss": "^8.4.14",
"postcss-loader": "^7.0.0",
"postcss": "^8.4.29",
"process": "^0.11.10",
"punycode": "^2.1.1",
"qrcode.react": "^3.1.0",
@ -169,15 +154,12 @@
"redux-thunk": "^2.2.0",
"reselect": "^4.0.0",
"resize-observer-polyfill": "^1.5.1",
"sass": "^1.20.3",
"sass-loader": "^13.0.0",
"sass": "^1.66.1",
"seedrandom": "^3.0.5",
"semver": "^7.3.8",
"stringz": "^2.0.0",
"substring-trie": "^1.0.2",
"terser-webpack-plugin": "^5.2.3",
"tiny-queue": "^0.2.1",
"ts-loader": "^9.3.0",
"ts-node": "^10.9.1",
"tslib": "^2.3.1",
"twemoji": "https://github.com/twitter/twemoji#v14.0.2",
@ -185,63 +167,47 @@
"typescript": "^5.1.3",
"util": "^0.12.4",
"uuid": "^9.0.0",
"webpack": "^5.72.1",
"webpack-assets-manifest": "^5.1.0",
"webpack-bundle-analyzer": "^4.5.0",
"webpack-cli": "^5.0.0",
"webpack-deadcode-plugin": "^0.1.16",
"webpack-merge": "^5.8.0",
"vite": "^4.4.9",
"vite-plugin-compile-time": "^0.2.1",
"vite-plugin-html": "^3.2.0",
"vite-plugin-require": "^1.1.10",
"vite-plugin-static-copy": "^0.17.0",
"wicg-inert": "^3.1.1",
"zod": "^3.21.4"
},
"devDependencies": {
"@babel/eslint-parser": "^7.19.1",
"@gitbeaker/node": "^35.8.0",
"@jedmao/redux-mock-store": "^3.0.5",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
"@storybook/addon-actions": "^6.5.16",
"@storybook/addon-essentials": "^6.5.16",
"@storybook/addon-interactions": "^6.5.16",
"@storybook/addon-links": "^6.5.16",
"@storybook/addon-postcss": "^2.0.0",
"@storybook/builder-webpack5": "^6.5.16",
"@storybook/manager-webpack5": "^6.5.16",
"@storybook/react": "^6.5.16",
"@storybook/testing-library": "^0.0.13",
"@tailwindcss/aspect-ratio": "^0.4.2",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/jest-dom": "^6.1.3",
"@testing-library/react": "^14.0.0",
"@testing-library/react-hooks": "^8.0.1",
"@testing-library/user-event": "^14.4.3",
"@typescript-eslint/eslint-plugin": "^5.15.0",
"@typescript-eslint/parser": "^5.15.0",
"babel-jest": "^29.4.1",
"@testing-library/user-event": "^14.5.1",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"babel-plugin-transform-require-context": "^0.1.1",
"cross-env": "^7.0.3",
"danger": "^11.0.7",
"eslint": "^8.0.0",
"eslint-plugin-compat": "^4.0.2",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jsdoc": "^43.1.1",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint": "^8.49.0",
"eslint-import-resolver-typescript": "^3.6.0",
"eslint-plugin-compat": "^4.2.0",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-jsdoc": "^46.8.1",
"eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-react": "^7.25.1",
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-tailwindcss": "^3.10.1",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-tailwindcss": "^3.13.0",
"fake-indexeddb": "^4.0.0",
"husky": "^8.0.0",
"jest": "^29.0.0",
"jest-environment-jsdom": "^29.0.0",
"jest-junit": "^15.0.0",
"jsdom": "^22.1.0",
"lint-staged": ">=10",
"raf": "^3.4.1",
"react-intl-translations-manager": "^5.0.3",
"react-refresh": "^0.14.0",
"storybook-react-intl": "^1.1.1",
"stylelint": "^14.0.0",
"stylelint-config-standard-scss": "^6.1.0",
"tailwindcss": "^3.3.1",
"ts-jest": "^29.0.0",
"webpack-dev-server": "^4.9.1",
"rollup-plugin-visualizer": "^5.9.2",
"stylelint": "^15.10.3",
"stylelint-config-standard-scss": "^11.0.0",
"tailwindcss": "^3.3.3",
"vite-plugin-pwa": "^0.16.5",
"vitest": "^0.34.4",
"yargs": "^17.6.2"
},
"resolutions": {

Some files were not shown because too many files have changed in this diff Show More