kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
Merge remote-tracking branch 'origin/main' into fix-profile-dropdown
commit
a7eb9d4112
|
@ -12,10 +12,11 @@
|
|||
yarn-error.log*
|
||||
/junit.xml
|
||||
|
||||
/dist/
|
||||
/static/
|
||||
/static-test/
|
||||
/public/
|
||||
/dist/
|
||||
/soapbox.zip
|
||||
|
||||
.idea
|
||||
.DS_Store
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/node_modules/**
|
||||
/dist/**
|
||||
/static/**
|
||||
/static-test/**
|
||||
/public/**
|
||||
/tmp/**
|
||||
/coverage/**
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -9,11 +9,14 @@
|
|||
/.vs/
|
||||
yarn-error.log*
|
||||
/junit.xml
|
||||
*.timestamp-*
|
||||
*.bundled_*
|
||||
|
||||
/dist/
|
||||
/static/
|
||||
/static-test/
|
||||
/public/
|
||||
/dist/
|
||||
/soapbox.zip
|
||||
|
||||
.idea
|
||||
.DS_Store
|
||||
|
|
|
@ -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
|
|
@ -4,5 +4,5 @@
|
|||
"*.mjs": "eslint --cache",
|
||||
"*.ts": "eslint --cache",
|
||||
"*.tsx": "eslint --cache",
|
||||
"app/styles/**/*.scss": "stylelint"
|
||||
"src/styles/**/*.scss": "stylelint"
|
||||
}
|
||||
|
|
|
@ -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;
|
|
@ -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$/,
|
||||
},
|
||||
},
|
||||
};
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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,
|
||||
};
|
|
@ -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,
|
||||
});
|
|
@ -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;
|
|
@ -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;
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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;
|
|
@ -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');
|
||||
}
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
import './web-push-notifications';
|
|
@ -1,8 +0,0 @@
|
|||
import type { Event, EventTemplate } from 'nostr-tools';
|
||||
|
||||
interface Nostr {
|
||||
getPublicKey(): Promise<string>
|
||||
signEvent(event: EventTemplate): Promise<Event>
|
||||
}
|
||||
|
||||
export default Nostr;
|
|
@ -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.');
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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/).
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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 {
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
};
|
|
@ -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)}";`,
|
||||
};
|
||||
},
|
||||
};
|
142
package.json
142
package.json
|
@ -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
Ładowanie…
Reference in New Issue