From cf218e41ef06bcc3051cf32e99b58335ca67c043 Mon Sep 17 00:00:00 2001 From: jon r Date: Tue, 12 Apr 2022 22:13:37 +0000 Subject: [PATCH 01/75] README Upstream changed the location of the document about the differences between the Pleroma and Mastodon APIs. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6068126d3..54aaae634 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Soapbox FE is a [single-page application (SPA)](https://en.wikipedia.org/wiki/Si It has a single HTML file, `index.html`, responsible only for loading the required JavaScript and CSS. It interacts with the backend through [XMLHttpRequest (XHR)](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest). -It incorporates much of the [Mastodon API](https://docs.joinmastodon.org/methods/) used by Pleroma and Mastodon, but requires many [Pleroma-specific features](https://docs-develop.pleroma.social/backend/API/differences_in_mastoapi_responses/) in order to function. +It incorporates much of the [Mastodon API](https://docs.joinmastodon.org/methods/) used by Pleroma and Mastodon, but requires many [Pleroma-specific features](https://docs.pleroma.social/backend/development/API/differences_in_mastoapi_responses/) in order to function. # Running locally From 39b819241f973e6f913667f3bda59083eaf4d142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 14 Apr 2022 15:24:11 +0200 Subject: [PATCH 02/75] Dashboard styles, typescript, add useAppDispatch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../admin/components/latest_accounts_panel.js | 87 ------------------- .../components/latest_accounts_panel.tsx | 63 ++++++++++++++ app/soapbox/features/admin/index.js | 79 ++++++++--------- app/soapbox/features/admin/user_index.js | 1 + app/soapbox/hooks/useAppDispatch.ts | 5 ++ app/styles/components/admin.scss | 60 +------------ app/styles/components/wtf-panel.scss | 2 +- 7 files changed, 107 insertions(+), 190 deletions(-) delete mode 100644 app/soapbox/features/admin/components/latest_accounts_panel.js create mode 100644 app/soapbox/features/admin/components/latest_accounts_panel.tsx create mode 100644 app/soapbox/hooks/useAppDispatch.ts diff --git a/app/soapbox/features/admin/components/latest_accounts_panel.js b/app/soapbox/features/admin/components/latest_accounts_panel.js deleted file mode 100644 index 6377b6810..000000000 --- a/app/soapbox/features/admin/components/latest_accounts_panel.js +++ /dev/null @@ -1,87 +0,0 @@ -import { is } from 'immutable'; -import PropTypes from 'prop-types'; -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { injectIntl, defineMessages } from 'react-intl'; -import { connect } from 'react-redux'; - -import { fetchUsers } from 'soapbox/actions/admin'; -import compareId from 'soapbox/compare_id'; -import AccountListPanel from 'soapbox/features/ui/components/account_list_panel'; - -const messages = defineMessages({ - title: { id: 'admin.latest_accounts_panel.title', defaultMessage: 'Latest Accounts' }, - expand: { id: 'admin.latest_accounts_panel.expand_message', defaultMessage: 'Click to see {count} more {count, plural, one {account} other {accounts}}' }, -}); - -const mapStateToProps = state => { - const accountIds = state.getIn(['admin', 'latestUsers']); - - // HACK: AdminAPI only recently started sorting new users at the top. - // Try a dirty check to see if the users are sorted properly, or don't show the panel. - // Probably works most of the time. - const sortedIds = accountIds.sort(compareId).reverse(); - const hasDates = accountIds.every(id => state.getIn(['accounts', id, 'created_at'])); - const isSorted = hasDates && is(accountIds, sortedIds); - - return { - isSorted, - accountIds, - }; -}; - -export default @connect(mapStateToProps) -@injectIntl -class LatestAccountsPanel extends ImmutablePureComponent { - - static propTypes = { - accountIds: ImmutablePropTypes.orderedSet.isRequired, - limit: PropTypes.number, - }; - - static defaultProps = { - limit: 5, - } - - state = { - total: 0, - } - - componentDidMount() { - const { dispatch, limit } = this.props; - - dispatch(fetchUsers(['local', 'active'], 1, null, limit)) - .then(({ count }) => { - this.setState({ total: count }); - }) - .catch(() => {}); - } - - render() { - const { intl, accountIds, limit, isSorted, ...props } = this.props; - const { total } = this.state; - - if (!isSorted || !accountIds || accountIds.isEmpty()) { - return null; - } - - const expandCount = total - accountIds.size; - - return ( - - ); - } - -} diff --git a/app/soapbox/features/admin/components/latest_accounts_panel.tsx b/app/soapbox/features/admin/components/latest_accounts_panel.tsx new file mode 100644 index 000000000..ae32a40f5 --- /dev/null +++ b/app/soapbox/features/admin/components/latest_accounts_panel.tsx @@ -0,0 +1,63 @@ +import { OrderedSet as ImmutableOrderedSet, is } from 'immutable'; +import React, { useState } from 'react'; +import { useEffect } from 'react'; +import { defineMessages, useIntl } from 'react-intl'; +import { Link } from 'react-router-dom'; + +import { fetchUsers } from 'soapbox/actions/admin'; +import compareId from 'soapbox/compare_id'; +import { Text, Widget } from 'soapbox/components/ui'; +import AccountContainer from 'soapbox/containers/account_container'; +import { useAppSelector } from 'soapbox/hooks'; +import { useAppDispatch } from 'soapbox/hooks/useAppDispatch'; + +const messages = defineMessages({ + title: { id: 'admin.latest_accounts_panel.title', defaultMessage: 'Latest Accounts' }, + expand: { id: 'admin.latest_accounts_panel.expand_message', defaultMessage: 'Click to see {count} more {count, plural, one {account} other {accounts}}' }, +}); + +interface ILatestAccountsPanel { + limit?: number, +} + +const LatestAccountsPanel: React.FC = ({ limit = 5 }) => { + const dispatch = useAppDispatch(); + const intl = useIntl(); + + const accountIds = useAppSelector>((state) => state.admin.get('latestUsers')); + const hasDates = useAppSelector((state) => accountIds.every(id => !!state.accounts.getIn([id, 'created_at']))); + + const [total, setTotal] = useState(0); + + useEffect(() => { + dispatch(fetchUsers(['local', 'active'], 1, null, limit)) + .then((value) => { + setTotal((value as { count: number }).count); + }) + .catch(() => {}); + }, []); + + const sortedIds = accountIds.sort(compareId).reverse(); + const isSorted = hasDates && is(accountIds, sortedIds); + + if (!isSorted || !accountIds || accountIds.isEmpty()) { + return null; + } + + const expandCount = total - accountIds.size; + + return ( + + {accountIds.take(limit).map((account) => ( + + ))} + {!!expandCount && ( + + {intl.formatMessage(messages.expand, { count: expandCount })} + + )} + + ); +}; + +export default LatestAccountsPanel; diff --git a/app/soapbox/features/admin/index.js b/app/soapbox/features/admin/index.js index 09f626f0e..93a38c53f 100644 --- a/app/soapbox/features/admin/index.js +++ b/app/soapbox/features/admin/index.js @@ -7,6 +7,7 @@ import { connect } from 'react-redux'; import { Link } from 'react-router-dom'; import { getSubscribersCsv, getUnsubscribersCsv, getCombinedCsv } from 'soapbox/actions/email_list'; +import { Text } from 'soapbox/components/ui'; import sourceCode from 'soapbox/utils/code'; import { parseVersion } from 'soapbox/utils/features'; import { getFeatures } from 'soapbox/utils/features'; @@ -86,56 +87,46 @@ class Dashboard extends ImmutablePureComponent {
{mau &&
-
-
- -
-
- -
-
+ + + + + +
} -
- -
- -
-
- -
- -
+ + + + + + + + {isNumber(retention) && (
-
-
- {retention}% -
-
- -
-
+ + {retention}% + + + +
)} + + + + + + + +
- -
- -
-
- -
- -
-
-
-
- -
-
- -
-
+ + + + + +
{account.admin && } diff --git a/app/soapbox/features/admin/user_index.js b/app/soapbox/features/admin/user_index.js index d93f99d28..dc7691f69 100644 --- a/app/soapbox/features/admin/user_index.js +++ b/app/soapbox/features/admin/user_index.js @@ -116,6 +116,7 @@ class UserIndex extends ImmutablePureComponent { showLoading={showLoading} onLoadMore={this.handleLoadMore} emptyMessage={intl.formatMessage(messages.empty)} + className='mt-4 space-y-4' > {accountIds.map(id => , diff --git a/app/soapbox/hooks/useAppDispatch.ts b/app/soapbox/hooks/useAppDispatch.ts new file mode 100644 index 000000000..11e7226d5 --- /dev/null +++ b/app/soapbox/hooks/useAppDispatch.ts @@ -0,0 +1,5 @@ +import { useDispatch } from 'react-redux'; + +import { AppDispatch } from 'soapbox/store'; + +export const useAppDispatch = () => useDispatch(); \ No newline at end of file diff --git a/app/styles/components/admin.scss b/app/styles/components/admin.scss index 88a34b05c..56e7e9022 100644 --- a/app/styles/components/admin.scss +++ b/app/styles/components/admin.scss @@ -1,65 +1,9 @@ .dashcounters { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); - margin: 0 -5px 0; - padding: 20px; + @apply grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2 mb-4; } .dashcounter { - box-sizing: border-box; - flex: 0 0 33.333%; - padding: 0 5px; - margin-bottom: 10px; - - > a, - > div { - box-sizing: border-box; - text-decoration: none; - color: inherit; - display: block; - padding: 20px; - background: var(--accent-color--faint); - border-radius: 4px; - transition: 0.2s; - height: 100%; - } - - > a:hover { - background: var(--accent-color--med); - transform: translateY(-2px); - } - - &__num, - &__icon, - &__text { - text-align: center; - font-weight: 500; - font-size: 24px; - line-height: 30px; - color: var(--primary-text-color); - margin-bottom: 10px; - } - - &__icon { - display: flex; - justify-content: center; - - .svg-icon { - width: 48px; - height: 48px; - - svg { - stroke-width: 1px; - } - } - } - - &__label { - font-size: 14px; - color: hsla(var(--primary-text-color_hsl), 0.6); - text-align: center; - font-weight: 500; - } + @apply bg-gray-200 dark:bg-gray-600 p-4 rounded flex flex-col items-center space-y-2 hover:-translate-y-1 transition-transform cursor-pointer; } .dashwidgets { diff --git a/app/styles/components/wtf-panel.scss b/app/styles/components/wtf-panel.scss index 120abe338..113e33e1f 100644 --- a/app/styles/components/wtf-panel.scss +++ b/app/styles/components/wtf-panel.scss @@ -139,13 +139,13 @@ } &__expand-btn { + @apply border-gray-300 dark:border-gray-600; display: block; width: 100%; height: 100%; max-height: 46px; position: relative; border-top: 1px solid; - border-color: var(--brand-color--faint); transition: max-height 150ms ease; overflow: hidden; opacity: 1; From 580633c91549893059c4487168c4396031b43dca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 14 Apr 2022 15:26:27 +0200 Subject: [PATCH 03/75] AccountContainer: use withDate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/components/account.tsx | 4 ++++ .../features/admin/components/latest_accounts_panel.tsx | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/soapbox/components/account.tsx b/app/soapbox/components/account.tsx index 3288127a1..132b9dfa2 100644 --- a/app/soapbox/components/account.tsx +++ b/app/soapbox/components/account.tsx @@ -35,6 +35,7 @@ interface IAccount { showProfileHoverCard?: boolean, timestamp?: string | Date, timestampUrl?: string, + withDate?: boolean, withRelationship?: boolean, } @@ -51,6 +52,7 @@ const Account = ({ showProfileHoverCard = true, timestamp, timestampUrl, + withDate = false, withRelationship = true, }: IAccount) => { const overflowRef = React.useRef(null); @@ -122,6 +124,8 @@ const Account = ({ ); } + if (withDate) timestamp = account.created_at; + const LinkEl: any = showProfileHoverCard ? Link : 'div'; return ( diff --git a/app/soapbox/features/admin/components/latest_accounts_panel.tsx b/app/soapbox/features/admin/components/latest_accounts_panel.tsx index ae32a40f5..7f53abd46 100644 --- a/app/soapbox/features/admin/components/latest_accounts_panel.tsx +++ b/app/soapbox/features/admin/components/latest_accounts_panel.tsx @@ -24,10 +24,10 @@ const LatestAccountsPanel: React.FC = ({ limit = 5 }) => { const dispatch = useAppDispatch(); const intl = useIntl(); - const accountIds = useAppSelector>((state) => state.admin.get('latestUsers')); + const accountIds = useAppSelector>((state) => state.admin.get('latestUsers').take(limit)); const hasDates = useAppSelector((state) => accountIds.every(id => !!state.accounts.getIn([id, 'created_at']))); - const [total, setTotal] = useState(0); + const [total, setTotal] = useState(accountIds.size); useEffect(() => { dispatch(fetchUsers(['local', 'active'], 1, null, limit)) @@ -49,7 +49,7 @@ const LatestAccountsPanel: React.FC = ({ limit = 5 }) => { return ( {accountIds.take(limit).map((account) => ( - + ))} {!!expandCount && ( From bd1e6d364a189e7619fb3ceed64393f69cf94f76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 14 Apr 2022 15:36:07 +0200 Subject: [PATCH 04/75] AutosuggestInput dark styles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/components/autosuggest_input.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/soapbox/components/autosuggest_input.js b/app/soapbox/components/autosuggest_input.js index 1bb2e2edf..d13873c80 100644 --- a/app/soapbox/components/autosuggest_input.js +++ b/app/soapbox/components/autosuggest_input.js @@ -206,8 +206,8 @@ export default class AutosuggestInput extends ImmutablePureComponent { key={key} data-index={i} className={classNames({ - 'px-4 py-2.5 text-sm text-gray-700 cursor-pointer hover:bg-gray-100 group': true, - 'bg-gray-100 hover:bg-gray-100': i === selectedSuggestion, + 'px-4 py-2.5 text-sm text-gray-700 dark:text-gray-400 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 group': true, + 'bg-gray-100 dark:bg-slate-700 hover:bg-gray-100 dark:hover:bg-gray-700': i === selectedSuggestion, })} onMouseDown={this.onSuggestionClick} > @@ -238,7 +238,7 @@ export default class AutosuggestInput extends ImmutablePureComponent { return menu.map((item, i) => (
Date: Thu, 14 Apr 2022 15:37:20 +0200 Subject: [PATCH 05/75] dark mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/styles/components/reply-mentions.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/styles/components/reply-mentions.scss b/app/styles/components/reply-mentions.scss index f94ba5181..5312d0859 100644 --- a/app/styles/components/reply-mentions.scss +++ b/app/styles/components/reply-mentions.scss @@ -1,5 +1,5 @@ .reply-mentions { - @apply text-gray-500 mb-1 text-sm; + @apply text-gray-500 dark:text-gray-400 mb-1 text-sm; &__account { @apply text-primary-600 no-underline; From 23943ccdee732e10a72e955cba8c79f042d68227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 14 Apr 2022 15:51:23 +0200 Subject: [PATCH 06/75] Remote interaction modal styles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../ui/components/unauthorized_modal.js | 39 ++++++++----------- app/styles/components/modal.scss | 11 ++---- 2 files changed, 20 insertions(+), 30 deletions(-) diff --git a/app/soapbox/features/ui/components/unauthorized_modal.js b/app/soapbox/features/ui/components/unauthorized_modal.js index 2e5a1a2f6..cf2c4d79e 100644 --- a/app/soapbox/features/ui/components/unauthorized_modal.js +++ b/app/soapbox/features/ui/components/unauthorized_modal.js @@ -3,16 +3,14 @@ import React from 'react'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; -import { Link, withRouter } from 'react-router-dom'; +import { withRouter } from 'react-router-dom'; import { remoteInteraction } from 'soapbox/actions/interactions'; import snackbar from 'soapbox/actions/snackbar'; import { getSoapboxConfig } from 'soapbox/actions/soapbox'; -import IconButton from 'soapbox/components/icon_button'; +import { Button, Modal, Stack, Text } from 'soapbox/components/ui'; import { getFeatures } from 'soapbox/utils/features'; -import { Modal, Stack, Text } from '../../../components/ui'; - const messages = defineMessages({ close: { id: 'lightbox.close', defaultMessage: 'Close' }, accountPlaceholder: { id: 'remote_interaction.account_placeholder', defaultMessage: 'Enter your username@domain you want to act from' }, @@ -133,11 +131,14 @@ class UnauthorizedModal extends ImmutablePureComponent { } return ( -
-
-

{header}

- -
+ } + secondaryAction={this.onRegister} + secondaryText={} + >
- +
- + - +
{!singleUserMode && ( - <> -

- - - - + + + )} - - -
-
+ ); } diff --git a/app/styles/components/modal.scss b/app/styles/components/modal.scss index c04089318..e1a506187 100644 --- a/app/styles/components/modal.scss +++ b/app/styles/components/modal.scss @@ -817,9 +817,7 @@ &__content { display: flex; flex-direction: column; - // align-items: center; row-gap: 10px; - padding: 10px; .unauthorized-modal-content__button { margin: 0 auto; @@ -832,11 +830,8 @@ gap: 10px; width: 100%; - .button { - width: auto; - margin: 0; - text-transform: none; - overflow: unset; + button { + align-self: flex-end; } } @@ -848,9 +843,9 @@ &::before, &::after { + @apply border-b border-gray-300 dark:border-gray-600; content: ""; flex: 1; - border-bottom: 1px solid hsla(var(--primary-text-color_hsl), 0.2); } } From 92a6058f26a082dd5cce12e8b309d815bd741914 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 14 Apr 2022 17:50:47 +0200 Subject: [PATCH 07/75] typescript MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../{account_notes.js => account_notes.ts} | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) rename app/soapbox/reducers/{account_notes.js => account_notes.ts} (68%) diff --git a/app/soapbox/reducers/account_notes.js b/app/soapbox/reducers/account_notes.ts similarity index 68% rename from app/soapbox/reducers/account_notes.js rename to app/soapbox/reducers/account_notes.ts index 67cd83e1d..b7f6f2eab 100644 --- a/app/soapbox/reducers/account_notes.js +++ b/app/soapbox/reducers/account_notes.ts @@ -1,4 +1,5 @@ -import { Map as ImmutableMap } from 'immutable'; +import { Record as ImmutableRecord } from 'immutable'; +import { AnyAction } from 'redux'; import { ACCOUNT_NOTE_INIT_MODAL, @@ -8,15 +9,19 @@ import { ACCOUNT_NOTE_SUBMIT_SUCCESS, } from '../actions/account_notes'; -const initialState = ImmutableMap({ - edit: ImmutableMap({ - isSubmitting: false, - account: null, - comment: null, - }), +const EditRecord = ImmutableRecord({ + isSubmitting: false, + account: null, + comment: null, }); -export default function account_notes(state = initialState, action) { +const ReducerRecord = ImmutableRecord({ + edit: EditRecord(), +}); + +type State = ReturnType; + +export default function account_notes(state: State = ReducerRecord(), action: AnyAction) { switch (action.type) { case ACCOUNT_NOTE_INIT_MODAL: return state.withMutations((state) => { From f745c9fc9735e6d27655dfa3a31e85a8d4f601d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 14 Apr 2022 18:10:46 +0200 Subject: [PATCH 08/75] typescript, FC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/components/ui/input/input.tsx | 2 +- .../components/latest_accounts_panel.tsx | 2 +- .../delete_account/{index.js => index.tsx} | 6 +- .../developers/developers_challenge.js | 92 ------------------- .../developers/developers_challenge.tsx | 80 ++++++++++++++++ ...developers_menu.js => developers_menu.tsx} | 2 +- app/soapbox/features/developers/index.js | 30 ------ app/soapbox/features/developers/index.tsx | 15 +++ app/soapbox/hooks/index.ts | 1 + 9 files changed, 102 insertions(+), 128 deletions(-) rename app/soapbox/features/delete_account/{index.js => index.tsx} (94%) delete mode 100644 app/soapbox/features/developers/developers_challenge.js create mode 100644 app/soapbox/features/developers/developers_challenge.tsx rename app/soapbox/features/developers/{developers_menu.js => developers_menu.tsx} (97%) delete mode 100644 app/soapbox/features/developers/index.js create mode 100644 app/soapbox/features/developers/index.tsx diff --git a/app/soapbox/components/ui/input/input.tsx b/app/soapbox/components/ui/input/input.tsx index 343a9d0dd..5314ee583 100644 --- a/app/soapbox/components/ui/input/input.tsx +++ b/app/soapbox/components/ui/input/input.tsx @@ -19,7 +19,7 @@ interface IInput extends Pick, 'onCh name?: string, placeholder?: string, value?: string, - onChange?: () => void, + onChange?: (event: React.ChangeEvent) => void, type: 'text' | 'email' | 'tel' | 'password' } diff --git a/app/soapbox/features/admin/components/latest_accounts_panel.tsx b/app/soapbox/features/admin/components/latest_accounts_panel.tsx index 7f53abd46..b1e9a441b 100644 --- a/app/soapbox/features/admin/components/latest_accounts_panel.tsx +++ b/app/soapbox/features/admin/components/latest_accounts_panel.tsx @@ -9,7 +9,7 @@ import compareId from 'soapbox/compare_id'; import { Text, Widget } from 'soapbox/components/ui'; import AccountContainer from 'soapbox/containers/account_container'; import { useAppSelector } from 'soapbox/hooks'; -import { useAppDispatch } from 'soapbox/hooks/useAppDispatch'; +import { useAppDispatch } from 'soapbox/hooks'; const messages = defineMessages({ title: { id: 'admin.latest_accounts_panel.title', defaultMessage: 'Latest Accounts' }, diff --git a/app/soapbox/features/delete_account/index.js b/app/soapbox/features/delete_account/index.tsx similarity index 94% rename from app/soapbox/features/delete_account/index.js rename to app/soapbox/features/delete_account/index.tsx index f0d902e4a..733b9554c 100644 --- a/app/soapbox/features/delete_account/index.js +++ b/app/soapbox/features/delete_account/index.tsx @@ -1,11 +1,11 @@ import PropTypes from 'prop-types'; import * as React from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { useDispatch } from 'react-redux'; import { deleteAccount } from 'soapbox/actions/security'; import snackbar from 'soapbox/actions/snackbar'; import { Button, Card, CardBody, CardHeader, CardTitle, Form, FormActions, FormGroup, Input } from 'soapbox/components/ui'; +import { useAppDispatch } from 'soapbox/hooks'; const messages = defineMessages({ passwordFieldLabel: { id: 'security.fields.password.label', defaultMessage: 'Password' }, @@ -18,12 +18,12 @@ const messages = defineMessages({ const DeleteAccount = () => { const intl = useIntl(); - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const [password, setPassword] = React.useState(''); const [isLoading, setLoading] = React.useState(false); - const handleInputChange = React.useCallback((event) => { + const handleInputChange = React.useCallback((event: React.ChangeEvent) => { event.persist(); setPassword(event.target.value); diff --git a/app/soapbox/features/developers/developers_challenge.js b/app/soapbox/features/developers/developers_challenge.js deleted file mode 100644 index b62191227..000000000 --- a/app/soapbox/features/developers/developers_challenge.js +++ /dev/null @@ -1,92 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import { FormattedMessage, injectIntl, defineMessages } from 'react-intl'; -import { connect } from 'react-redux'; - -import { changeSettingImmediate } from 'soapbox/actions/settings'; -import snackbar from 'soapbox/actions/snackbar'; -import { Button, Form, FormActions, FormGroup, Input, Text } from 'soapbox/components/ui'; - - -import Column from '../ui/components/column'; - -const messages = defineMessages({ - heading: { id: 'column.developers', defaultMessage: 'Developers' }, - answerLabel: { id: 'developers.challenge.answer_label', defaultMessage: 'Answer' }, - answerPlaceholder: { id: 'developers.challenge.answer_placeholder', defaultMessage: 'Your answer' }, - success: { id: 'developers.challenge.success', defaultMessage: 'You are now a developer' }, - fail: { id: 'developers.challenge.fail', defaultMessage: 'Wrong answer' }, -}); - -export default @connect() -@injectIntl -class DevelopersChallenge extends React.Component { - - static propTypes = { - intl: PropTypes.object.isRequired, - dispatch: PropTypes.func.isRequired, - } - - state = { - answer: '', - } - - handleChangeAnswer = e => { - this.setState({ answer: e.target.value }); - } - - handleSubmit = e => { - const { intl, dispatch } = this.props; - const { answer } = this.state; - - if (answer === 'boxsoap') { - dispatch(changeSettingImmediate(['isDeveloper'], true)); - dispatch(snackbar.success(intl.formatMessage(messages.success))); - } else { - dispatch(snackbar.error(intl.formatMessage(messages.fail))); - } - } - - render() { - const { intl } = this.props; - - const challenge = `function soapbox() { - return 'soap|box'.split('|').reverse().join(''); -}`; - - return ( - -
- - soapbox() }} - /> - - - {challenge} - - - - - - - - - -
-
- ); - } - -} diff --git a/app/soapbox/features/developers/developers_challenge.tsx b/app/soapbox/features/developers/developers_challenge.tsx new file mode 100644 index 000000000..d27b46429 --- /dev/null +++ b/app/soapbox/features/developers/developers_challenge.tsx @@ -0,0 +1,80 @@ +import React from 'react'; +import { useState } from 'react'; +import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; +import { useDispatch } from 'react-redux'; + +import { changeSettingImmediate } from 'soapbox/actions/settings'; +import snackbar from 'soapbox/actions/snackbar'; +import { Button, Form, FormActions, FormGroup, Input, Text } from 'soapbox/components/ui'; + + +import Column from '../ui/components/column'; + +const messages = defineMessages({ + heading: { id: 'column.developers', defaultMessage: 'Developers' }, + answerLabel: { id: 'developers.challenge.answer_label', defaultMessage: 'Answer' }, + answerPlaceholder: { id: 'developers.challenge.answer_placeholder', defaultMessage: 'Your answer' }, + success: { id: 'developers.challenge.success', defaultMessage: 'You are now a developer' }, + fail: { id: 'developers.challenge.fail', defaultMessage: 'Wrong answer' }, +}); + +const DevelopersChallenge = () => { + const dispatch = useDispatch(); + const intl = useIntl(); + + const [answer, setAnswer] = useState(''); + + const handleChangeAnswer = (e: React.ChangeEvent) => { + setAnswer(e.target.value); + }; + + const handleSubmit = () => { + if (answer === 'boxsoap') { + dispatch(changeSettingImmediate(['isDeveloper'], true)); + dispatch(snackbar.success(intl.formatMessage(messages.success))); + } else { + dispatch(snackbar.error(intl.formatMessage(messages.fail))); + } + }; + + const challenge = `function soapbox() { + return 'soap|box'.split('|').reverse().join(''); +}`; + + return ( + +
+ + soapbox() }} + /> + + + {challenge} + + + + + + + + + +
+
+ ); +}; + +export default DevelopersChallenge; diff --git a/app/soapbox/features/developers/developers_menu.js b/app/soapbox/features/developers/developers_menu.tsx similarity index 97% rename from app/soapbox/features/developers/developers_menu.js rename to app/soapbox/features/developers/developers_menu.tsx index 832c0bd60..6cabd36a0 100644 --- a/app/soapbox/features/developers/developers_menu.js +++ b/app/soapbox/features/developers/developers_menu.tsx @@ -20,7 +20,7 @@ const Developers = () => { const history = useHistory(); const intl = useIntl(); - const leaveDevelopers = (e) => { + const leaveDevelopers = (e: React.MouseEvent) => { e.preventDefault(); dispatch(changeSettingImmediate(['isDeveloper'], false)); diff --git a/app/soapbox/features/developers/index.js b/app/soapbox/features/developers/index.js deleted file mode 100644 index 3d58c9249..000000000 --- a/app/soapbox/features/developers/index.js +++ /dev/null @@ -1,30 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import { connect } from 'react-redux'; - -import { getSettings } from 'soapbox/actions/settings'; - -import DevelopersChallenge from './developers_challenge'; -import DevelopersMenu from './developers_menu'; - -const mapStateToProps = state => { - const settings = getSettings(state); - - return { - isDeveloper: settings.get('isDeveloper'), - }; -}; - -export default @connect(mapStateToProps) -class Developers extends React.Component { - - static propTypes = { - isDeveloper: PropTypes.bool.isRequired, - } - - render() { - const { isDeveloper } = this.props; - return isDeveloper ? : ; - } - -} diff --git a/app/soapbox/features/developers/index.tsx b/app/soapbox/features/developers/index.tsx new file mode 100644 index 000000000..a192f1683 --- /dev/null +++ b/app/soapbox/features/developers/index.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { getSettings } from 'soapbox/actions/settings'; +import { useAppSelector } from 'soapbox/hooks'; + +import DevelopersChallenge from './developers_challenge'; +import DevelopersMenu from './developers_menu'; + +const Developers: React.FC = () => { + const isDeveloper = useAppSelector((state) => getSettings(state).get('isDeveloper')); + + return isDeveloper ? : ; +}; + +export default Developers; diff --git a/app/soapbox/hooks/index.ts b/app/soapbox/hooks/index.ts index d3c5f4700..6ec919a94 100644 --- a/app/soapbox/hooks/index.ts +++ b/app/soapbox/hooks/index.ts @@ -1,3 +1,4 @@ +export { useAppDispatch } from './useAppDispatch'; export { useAppSelector } from './useAppSelector'; export { useFeatures } from './useFeatures'; export { useOnScreen } from './useOnScreen'; From 5955a0819af90f8543953b00ab468ac54d97ba95 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 14 Apr 2022 13:16:08 -0500 Subject: [PATCH 09/75] Nuke FeaturesPanel, add "More" menu to left sidebar --- app/soapbox/components/dropdown_menu.tsx | 2 +- .../components/sidebar-navigation-link.tsx | 18 ++- app/soapbox/components/sidebar-navigation.tsx | 145 ++++++++++++------ .../features/ui/components/features_panel.js | 74 --------- .../features/ui/util/async-components.js | 4 - app/soapbox/pages/remote_instance_page.js | 8 +- 6 files changed, 113 insertions(+), 138 deletions(-) delete mode 100644 app/soapbox/features/ui/components/features_panel.js diff --git a/app/soapbox/components/dropdown_menu.tsx b/app/soapbox/components/dropdown_menu.tsx index 01a60948d..e28ea0f9e 100644 --- a/app/soapbox/components/dropdown_menu.tsx +++ b/app/soapbox/components/dropdown_menu.tsx @@ -18,7 +18,7 @@ let id = 0; export interface MenuItem { action?: React.EventHandler, middleClick?: React.EventHandler, - text: string, + text: string | JSX.Element, href?: string, to?: string, newTab?: boolean, diff --git a/app/soapbox/components/sidebar-navigation-link.tsx b/app/soapbox/components/sidebar-navigation-link.tsx index bd0567c03..fc63372e7 100644 --- a/app/soapbox/components/sidebar-navigation-link.tsx +++ b/app/soapbox/components/sidebar-navigation-link.tsx @@ -8,17 +8,29 @@ interface ISidebarNavigationLink { count?: number, icon: string, text: string | React.ReactElement, - to: string, + to?: string, + onClick?: React.EventHandler, } -const SidebarNavigationLink = ({ icon, text, to, count }: ISidebarNavigationLink) => { +const SidebarNavigationLink = React.forwardRef((props: ISidebarNavigationLink, ref: React.ForwardedRef): JSX.Element => { + const { icon, text, to = '', count, onClick } = props; const isActive = location.pathname === to; const withCounter = typeof count !== 'undefined'; + const handleClick: React.EventHandler = (e) => { + if (onClick) { + onClick(e); + e.preventDefault(); + e.stopPropagation(); + } + }; + return ( {text} ); -}; +}); export default SidebarNavigationLink; diff --git a/app/soapbox/components/sidebar-navigation.tsx b/app/soapbox/components/sidebar-navigation.tsx index 96411d6c3..3f33e6733 100644 --- a/app/soapbox/components/sidebar-navigation.tsx +++ b/app/soapbox/components/sidebar-navigation.tsx @@ -1,27 +1,110 @@ -import { Map as ImmutableMap } from 'immutable'; +import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutable'; import React from 'react'; import { FormattedMessage } from 'react-intl'; import { getSettings } from 'soapbox/actions/settings'; +import DropdownMenu from 'soapbox/containers/dropdown_menu_container'; import ComposeButton from 'soapbox/features/ui/components/compose-button'; -import { useAppSelector } from 'soapbox/hooks'; +import { useAppSelector, useOwnAccount } from 'soapbox/hooks'; import { getBaseURL } from 'soapbox/utils/accounts'; import { getFeatures } from 'soapbox/utils/features'; import SidebarNavigationLink from './sidebar-navigation-link'; +import type { Menu } from 'soapbox/components/dropdown_menu'; + const SidebarNavigation = () => { - const me = useAppSelector((state) => state.me); const instance = useAppSelector((state) => state.instance); const settings = useAppSelector((state) => getSettings(state)); - const account = useAppSelector((state) => state.accounts.get(me)); + const account = useOwnAccount(); const notificationCount = useAppSelector((state) => state.notifications.get('unread')); const chatsCount = useAppSelector((state) => state.chats.get('items').reduce((acc: any, curr: any) => acc + Math.min(curr.get('unread', 0), 1), 0)); - const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count()); + const followRequestsCount = useAppSelector((state) => state.user_lists.getIn(['follow_requests', 'items'], ImmutableOrderedSet()).count()); + // const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count()); - const baseURL = getBaseURL(ImmutableMap(account)); + const baseURL = account ? getBaseURL(ImmutableMap(account)) : ''; const features = getFeatures(instance); + const makeMenu = (): Menu => { + const menu: Menu = []; + + if (account?.locked || followRequestsCount > 0) { + menu.push({ + to: '/follow_requests', + text: , + icon: require('@tabler/icons/icons/user-plus.svg'), + // TODO: let menu items have a counter + // count: followRequestsCount, + }); + } + + if (features.bookmarks) { + menu.push({ + to: '/bookmarks', + text: , + icon: require('@tabler/icons/icons/bookmark.svg'), + }); + } + + if (features.lists) { + menu.push({ + to: '/lists', + text: , + icon: require('@tabler/icons/icons/list.svg'), + }); + } + + if (account && instance.invites_enabled) { + menu.push({ + to: `${baseURL}/invites`, + icon: require('@tabler/icons/icons/mailbox.svg'), + text: , + }); + } + + if (settings.get('isDeveloper')) { + menu.push({ + to: '/developers', + icon: require('@tabler/icons/icons/code.svg'), + text: , + }); + } + + if (account && account.staff) { + menu.push({ + to: '/admin', + icon: require('@tabler/icons/icons/dashboard.svg'), + text: , + // TODO: let menu items have a counter + // count: dashboardCount, + }); + } + + if (features.localTimeline || features.publicTimeline) { + menu.push(null); + } + + if (features.localTimeline) { + menu.push({ + to: '/timeline/local', + icon: features.federating ? require('@tabler/icons/icons/users.svg') : require('@tabler/icons/icons/world.svg'), + text: features.federating ? instance.title : , + }); + } + + if (features.localTimeline && features.federating) { + menu.push({ + to: '/timeline/fediverse', + icon: require('icons/fediverse.svg'), + text: , + }); + } + + return menu; + }; + + const menu = makeMenu(); + return (
@@ -71,49 +154,13 @@ const SidebarNavigation = () => { ) )} - {(account && account.staff) && ( - } - count={dashboardCount} - /> - )} - - {(account && instance.invites_enabled) && ( - } - /> - )} - - {(settings.get('isDeveloper')) && ( - } - /> - )} - - {(features.localTimeline || features.publicTimeline) && ( -
- )} - - {features.localTimeline && ( - } - /> - )} - - {(features.publicTimeline && features.federating) && ( - } - /> + {menu.length > 0 && ( + + } + /> + )}
diff --git a/app/soapbox/features/ui/components/features_panel.js b/app/soapbox/features/ui/components/features_panel.js deleted file mode 100644 index f54ba36b5..000000000 --- a/app/soapbox/features/ui/components/features_panel.js +++ /dev/null @@ -1,74 +0,0 @@ -import { OrderedSet as ImmutableOrderedSet } from 'immutable'; -import PropTypes from 'prop-types'; -import React from 'react'; -import { injectIntl, defineMessages } from 'react-intl'; -import { connect } from 'react-redux'; -import { NavLink } from 'react-router-dom'; - -import Icon from 'soapbox/components/icon'; -import IconWithCounter from 'soapbox/components/icon_with_counter'; -import { getFeatures } from 'soapbox/utils/features'; - -const messages = defineMessages({ - edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit Profile' }, - preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, - security: { id: 'navigation_bar.security', defaultMessage: 'Security' }, - lists: { id: 'column.lists', defaultMessage: 'Lists' }, - bookmarks: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' }, - follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' }, -}); - -const mapStateToProps = state => { - const me = state.get('me'); - - const instance = state.get('instance'); - const features = getFeatures(instance); - - return { - isLocked: state.getIn(['accounts', me, 'locked']), - followRequestsCount: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableOrderedSet()).count(), - features, - }; -}; - -export default @connect(mapStateToProps) -@injectIntl -class FeaturesPanel extends React.PureComponent { - - static propTypes = { - intl: PropTypes.object.isRequired, - isLocked: PropTypes.bool, - followRequestsCount: PropTypes.number, - features: PropTypes.object.isRequired, - }; - - render() { - const { intl, isLocked, followRequestsCount, features } = this.props; - - return ( -
-
- {(isLocked || followRequestsCount > 0) && - - {intl.formatMessage(messages.follow_requests)} - } - - {features.bookmarks && ( - - - {intl.formatMessage(messages.bookmarks)} - - )} - - {features.lists && ( - - - {intl.formatMessage(messages.lists)} - - )} -
-
- ); - } - -} diff --git a/app/soapbox/features/ui/util/async-components.js b/app/soapbox/features/ui/util/async-components.js index 69d3d401d..15ed6cadf 100644 --- a/app/soapbox/features/ui/util/async-components.js +++ b/app/soapbox/features/ui/util/async-components.js @@ -338,10 +338,6 @@ export function UserPanel() { return import(/* webpackChunkName: "features/ui" */'../components/user_panel'); } -export function FeaturesPanel() { - return import(/* webpackChunkName: "features/ui" */'../components/features_panel'); -} - export function PromoPanel() { return import(/* webpackChunkName: "features/ui" */'../components/promo_panel'); } diff --git a/app/soapbox/pages/remote_instance_page.js b/app/soapbox/pages/remote_instance_page.js index c922be6a5..2ff5e7c4f 100644 --- a/app/soapbox/pages/remote_instance_page.js +++ b/app/soapbox/pages/remote_instance_page.js @@ -7,7 +7,6 @@ import LinkFooter from 'soapbox/features/ui/components/link_footer'; import BundleContainer from 'soapbox/features/ui/containers/bundle_container'; import { PromoPanel, - FeaturesPanel, InstanceInfoPanel, InstanceModerationPanel, } from 'soapbox/features/ui/util/async-components'; @@ -30,7 +29,7 @@ export default @connect(mapStateToProps) class RemoteInstancePage extends ImmutablePureComponent { render() { - const { me, children, params: { instance: host }, disclosed, isAdmin } = this.props; + const { children, params: { instance: host }, disclosed, isAdmin } = this.props; return ( @@ -43,11 +42,6 @@ class RemoteInstancePage extends ImmutablePureComponent { - {me && ( - - {Component => } - - )} {Component => } From 7e42679478f58ac922ba5c03545b3baf0a592380 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 14 Apr 2022 14:15:32 -0400 Subject: [PATCH 10/75] Add meta tag for iOS banner --- app/soapbox/containers/soapbox.js | 6 ++++++ app/soapbox/normalizers/soapbox/soapbox_config.ts | 1 + 2 files changed, 7 insertions(+) diff --git a/app/soapbox/containers/soapbox.js b/app/soapbox/containers/soapbox.js index c1a7db84e..ad14c6e7e 100644 --- a/app/soapbox/containers/soapbox.js +++ b/app/soapbox/containers/soapbox.js @@ -86,6 +86,7 @@ const mapStateToProps = (state) => { locale: validLocale(locale) ? locale : 'en', themeCss: generateThemeCss(soapboxConfig), brandColor: soapboxConfig.get('brandColor'), + appleAppId: soapboxConfig.get('appleAppId'), themeMode: settings.get('themeMode'), singleUserMode, }; @@ -108,6 +109,7 @@ class SoapboxMount extends React.PureComponent { themeCss: PropTypes.string, themeMode: PropTypes.string, brandColor: PropTypes.string, + appleAppId: PropTypes.string, dispatch: PropTypes.func, singleUserMode: PropTypes.bool, }; @@ -171,6 +173,10 @@ class SoapboxMount extends React.PureComponent { {themeCss && } + + {this.props.appleAppId && ( + + )} diff --git a/app/soapbox/normalizers/soapbox/soapbox_config.ts b/app/soapbox/normalizers/soapbox/soapbox_config.ts index a56c5e0ce..f2e8cfef8 100644 --- a/app/soapbox/normalizers/soapbox/soapbox_config.ts +++ b/app/soapbox/normalizers/soapbox/soapbox_config.ts @@ -75,6 +75,7 @@ export const CryptoAddressRecord = ImmutableRecord({ }); export const SoapboxConfigRecord = ImmutableRecord({ + appleAppId: null, logo: '', banner: '', brandColor: '', // Empty From c5646cf87375ed2371cc953ce730218e08421366 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 14 Apr 2022 14:46:07 -0500 Subject: [PATCH 11/75] Add a TestTimeline for previewing certain types of posts --- ...tatus-vertical-video-without-metadata.json | 108 ++++++++++++++++++ .../features/developers/developers_menu.js | 8 ++ app/soapbox/features/test_timeline/index.tsx | 53 +++++++++ app/soapbox/features/ui/index.js | 2 + .../features/ui/util/async-components.js | 4 + 5 files changed, 175 insertions(+) create mode 100644 app/soapbox/__fixtures__/pleroma-status-vertical-video-without-metadata.json create mode 100644 app/soapbox/features/test_timeline/index.tsx diff --git a/app/soapbox/__fixtures__/pleroma-status-vertical-video-without-metadata.json b/app/soapbox/__fixtures__/pleroma-status-vertical-video-without-metadata.json new file mode 100644 index 000000000..edb24b9ef --- /dev/null +++ b/app/soapbox/__fixtures__/pleroma-status-vertical-video-without-metadata.json @@ -0,0 +1,108 @@ +{ + "account": { + "acct": "alex", + "avatar": "https://freespeechextremist.com/images/avi.png", + "avatar_static": "https://freespeechextremist.com/images/avi.png", + "bot": false, + "created_at": "2022-02-28T01:55:05.000Z", + "display_name": "Alex Gleason", + "emojis": [], + "fields": [], + "followers_count": 1, + "following_count": 0, + "header": "https://freespeechextremist.com/images/banner.png", + "header_static": "https://freespeechextremist.com/images/banner.png", + "id": "AGv8wCadU7DqWgMqNk", + "locked": false, + "note": "I'm testing out compatibility with an older Pleroma version", + "pleroma": { + "accepts_chat_messages": true, + "ap_id": "https://freespeechextremist.com/users/alex", + "background_image": null, + "confirmation_pending": false, + "favicon": null, + "hide_favorites": true, + "hide_followers": false, + "hide_followers_count": false, + "hide_follows": false, + "hide_follows_count": false, + "is_admin": false, + "is_moderator": false, + "relationship": {}, + "skip_thread_containment": false, + "tags": [] + }, + "source": { + "fields": [], + "note": "I'm testing out compatibility with an older Pleroma version", + "pleroma": { + "actor_type": "Person", + "discoverable": true + }, + "sensitive": false + }, + "statuses_count": 1, + "url": "https://freespeechextremist.com/users/alex", + "username": "alex" + }, + "application": { + "name": "Web", + "website": null + }, + "bookmarked": false, + "card": null, + "content": "
0f66e92f339705ccc03079b8f647048e15730adf2cc9eaa1071c7c7cf6884b1b.webm", + "created_at": "2022-04-14T19:42:48.000Z", + "emojis": [], + "favourited": false, + "favourites_count": 0, + "id": "AIRxLeIzncpCtsr2hs", + "in_reply_to_account_id": null, + "in_reply_to_id": null, + "language": null, + "media_attachments": [ + { + "description": "0f66e92f339705ccc03079b8f647048e15730adf2cc9eaa1071c7c7cf6884b1b.webm", + "id": "1142674091", + "pleroma": { + "mime_type": "video/webm" + }, + "preview_url": "https://freespeechextremist.com/media/3e34b808-1c84-4ef3-ba56-67cc86b7911a/0f66e92f339705ccc03079b8f647048e15730adf2cc9eaa1071c7c7cf6884b1b.webm?name=0f66e92f339705ccc03079b8f647048e15730adf2cc9eaa1071c7c7cf6884b1b.webm", + "remote_url": "https://freespeechextremist.com/media/3e34b808-1c84-4ef3-ba56-67cc86b7911a/0f66e92f339705ccc03079b8f647048e15730adf2cc9eaa1071c7c7cf6884b1b.webm?name=0f66e92f339705ccc03079b8f647048e15730adf2cc9eaa1071c7c7cf6884b1b.webm", + "text_url": "https://freespeechextremist.com/media/3e34b808-1c84-4ef3-ba56-67cc86b7911a/0f66e92f339705ccc03079b8f647048e15730adf2cc9eaa1071c7c7cf6884b1b.webm?name=0f66e92f339705ccc03079b8f647048e15730adf2cc9eaa1071c7c7cf6884b1b.webm", + "type": "video", + "url": "https://freespeechextremist.com/media/3e34b808-1c84-4ef3-ba56-67cc86b7911a/0f66e92f339705ccc03079b8f647048e15730adf2cc9eaa1071c7c7cf6884b1b.webm?name=0f66e92f339705ccc03079b8f647048e15730adf2cc9eaa1071c7c7cf6884b1b.webm" + } + ], + "mentions": [], + "muted": false, + "pinned": false, + "pleroma": { + "content": { + "text/plain": "0f66e92f339705ccc03079b8f647048e15730adf2cc9eaa1071c7c7cf6884b1b.webm" + }, + "conversation_id": 97191096, + "direct_conversation_id": null, + "emoji_reactions": [], + "expires_at": null, + "in_reply_to_account_acct": null, + "local": true, + "parent_visible": false, + "spoiler_text": { + "text/plain": "" + }, + "thread_muted": false + }, + "poll": null, + "reblog": null, + "reblogged": false, + "reblogs_count": 0, + "replies_count": 0, + "sensitive": false, + "spoiler_text": "", + "tags": [], + "text": null, + "uri": "https://freespeechextremist.com/objects/419b2cad-656a-4dbc-b2b5-94bb75e0afc8", + "url": "https://freespeechextremist.com/notice/AIRxLeIzncpCtsr2hs", + "visibility": "public" +} diff --git a/app/soapbox/features/developers/developers_menu.js b/app/soapbox/features/developers/developers_menu.js index 832c0bd60..f4ab7f8ca 100644 --- a/app/soapbox/features/developers/developers_menu.js +++ b/app/soapbox/features/developers/developers_menu.js @@ -47,6 +47,14 @@ const Developers = () => { + + + + + + + + diff --git a/app/soapbox/features/test_timeline/index.tsx b/app/soapbox/features/test_timeline/index.tsx new file mode 100644 index 000000000..ace235b8f --- /dev/null +++ b/app/soapbox/features/test_timeline/index.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; +import { useDispatch } from 'react-redux'; + +import { importFetchedStatuses } from 'soapbox/actions/importer'; +import { expandTimelineSuccess } from 'soapbox/actions/timelines'; +import SubNavigation from 'soapbox/components/sub_navigation'; + +import { Column } from '../../components/ui'; +import StatusListContainer from '../ui/containers/status_list_container'; + +const messages = defineMessages({ + title: { id: 'column.test', defaultMessage: 'Test timeline' }, +}); + +/** + * List of mock statuses to display in the timeline. + * These get embedded into the build, but only in this chunk, so it's okay. + */ +const MOCK_STATUSES: any[] = [ + require('soapbox/__fixtures__/pleroma-status-with-poll.json'), + require('soapbox/__fixtures__/pleroma-status-vertical-video-without-metadata.json'), + require('soapbox/__fixtures__/pleroma-status-with-poll-with-emojis.json'), + require('soapbox/__fixtures__/pleroma-quote-of-quote-post.json'), + require('soapbox/__fixtures__/truthsocial-status-with-external-video.json'), +]; + +const timelineId = 'test'; +const onlyMedia = false; + +const TestTimeline: React.FC = () => { + const intl = useIntl(); + const dispatch = useDispatch(); + + React.useEffect(() => { + dispatch(importFetchedStatuses(MOCK_STATUSES)); + dispatch(expandTimelineSuccess(timelineId, MOCK_STATUSES, null, false, false, false)); + }, []); + + return ( + + + } + divideType='space' + /> + + ); +}; + +export default TestTimeline; diff --git a/app/soapbox/features/ui/index.js b/app/soapbox/features/ui/index.js index bc50ca114..6fdae02b8 100644 --- a/app/soapbox/features/ui/index.js +++ b/app/soapbox/features/ui/index.js @@ -120,6 +120,7 @@ import { Developers, CreateApp, SettingsStore, + TestTimeline, } from './util/async-components'; import { WrappedRoute } from './util/react_router_helpers'; @@ -341,6 +342,7 @@ class SwitchingColumnsArea extends React.PureComponent { + diff --git a/app/soapbox/features/ui/util/async-components.js b/app/soapbox/features/ui/util/async-components.js index 15ed6cadf..9f031adbc 100644 --- a/app/soapbox/features/ui/util/async-components.js +++ b/app/soapbox/features/ui/util/async-components.js @@ -482,6 +482,10 @@ export function SettingsStore() { return import(/* webpackChunkName: "features/developers" */'../../developers/settings_store'); } +export function TestTimeline() { + return import(/* webpackChunkName: "features/test_timeline" */'../../test_timeline'); +} + export function DatePicker() { return import(/* webpackChunkName: "date_picker" */'../../birthdays/date_picker'); } From 6b281c72a36ed241eb635178b63152ab201994ed Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 14 Apr 2022 15:56:09 -0400 Subject: [PATCH 12/75] Remove Preheader files --- .../public_layout/components/pre_header.js | 59 ------------------- app/soapbox/features/ui/index.js | 2 - 2 files changed, 61 deletions(-) delete mode 100644 app/soapbox/features/public_layout/components/pre_header.js diff --git a/app/soapbox/features/public_layout/components/pre_header.js b/app/soapbox/features/public_layout/components/pre_header.js deleted file mode 100644 index 5130f6804..000000000 --- a/app/soapbox/features/public_layout/components/pre_header.js +++ /dev/null @@ -1,59 +0,0 @@ -import * as React from 'react'; -import { defineMessages, useIntl } from 'react-intl'; -import { Link } from 'react-router-dom'; - -import { IconButton } from 'soapbox/components/ui'; -import { useAppSelector } from 'soapbox/hooks'; - -const messages = defineMessages({ - close: { id: 'pre_header.close', defaultMessage: 'Close' }, -}); - -export default () => { - const intl = useIntl(); - - const [hidden, setHidden] = React.useState(false); - const siteTitle = useAppSelector((state) => state.instance.title); - - const handleClose = () => { - localStorage.setItem('soapbox:welcome-banner', '0'); - setHidden(true); - }; - - React.useEffect(() => { - const shouldBeHidden = localStorage.getItem('soapbox:welcome-banner') === '0'; - - setHidden(shouldBeHidden); - }, []); - - if (hidden) { - return null; - } - - return ( -
-
-
-

- Welcome to {siteTitle} -

- - - Learn More - -
- -
- -
-
-
- ); -}; diff --git a/app/soapbox/features/ui/index.js b/app/soapbox/features/ui/index.js index bc50ca114..10a958cf5 100644 --- a/app/soapbox/features/ui/index.js +++ b/app/soapbox/features/ui/index.js @@ -41,7 +41,6 @@ import { expandNotifications } from '../../actions/notifications'; import { fetchScheduledStatuses } from '../../actions/scheduled_statuses'; import { connectUserStream } from '../../actions/streaming'; import { expandHomeTimeline } from '../../actions/timelines'; -// import PreHeader from '../../features/public_layout/components/pre_header'; // import GroupSidebarPanel from '../groups/sidebar_panel'; import BackgroundShapes from './components/background_shapes'; @@ -727,7 +726,6 @@ class UI extends React.PureComponent {
- {/* */} From 0b3509b5bde2bec2a36b9cbf42b6022201d637be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 14 Apr 2022 22:08:53 +0200 Subject: [PATCH 13/75] Support 'status' notification type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/actions/notifications.js | 4 +- .../notifications/components/filter_bar.js | 7 ++++ .../notifications/components/notification.js | 37 +++++++++++++++++++ app/soapbox/locales/pl.json | 2 + app/soapbox/reducers/notifications.js | 2 +- 5 files changed, 49 insertions(+), 3 deletions(-) diff --git a/app/soapbox/actions/notifications.js b/app/soapbox/actions/notifications.js index a4331bf8c..9ecf61687 100644 --- a/app/soapbox/actions/notifications.js +++ b/app/soapbox/actions/notifications.js @@ -98,7 +98,7 @@ export function updateNotificationsQueue(notification, intlMessages, intlLocale, const isOnNotificationsPage = curPath === '/notifications'; - if (notification.type === 'mention') { + if (['mention', 'status'].includes(notification.type)) { const regex = regexFromFilters(filters); const searchIndex = notification.status.spoiler_text + '\n' + unescapeHTML(notification.status.content); filtered = regex && regex.test(searchIndex); @@ -170,7 +170,7 @@ export function dequeueNotifications() { const excludeTypesFromSettings = getState => getSettings(getState()).getIn(['notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS(); const excludeTypesFromFilter = filter => { - const allTypes = ImmutableList(['follow', 'follow_request', 'favourite', 'reblog', 'mention', 'poll', 'move', 'pleroma:emoji_reaction']); + const allTypes = ImmutableList(['follow', 'follow_request', 'favourite', 'reblog', 'mention', 'status', 'poll', 'move', 'pleroma:emoji_reaction']); return allTypes.filterNot(item => item === filter).toJS(); }; diff --git a/app/soapbox/features/notifications/components/filter_bar.js b/app/soapbox/features/notifications/components/filter_bar.js index a95574036..dfc656279 100644 --- a/app/soapbox/features/notifications/components/filter_bar.js +++ b/app/soapbox/features/notifications/components/filter_bar.js @@ -14,6 +14,7 @@ const messages = defineMessages({ follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' }, moves: { id: 'notifications.filter.moves', defaultMessage: 'Moves' }, emoji_reacts: { id: 'notifications.filter.emoji_reacts', defaultMessage: 'Emoji reacts' }, + statuses: { id: 'notifications.filter.statuses', defaultMessage: 'Updates from people you follow' }, }); export default @injectIntl @@ -79,6 +80,12 @@ class NotificationFilterBar extends React.PureComponent { action: this.onClick('poll'), name: 'poll', }); + items.push({ + text: , + title: intl.formatMessage(messages.statuses), + action: this.onClick('status'), + name: 'status', + }); items.push({ text: , title: intl.formatMessage(messages.follows), diff --git a/app/soapbox/features/notifications/components/notification.js b/app/soapbox/features/notifications/components/notification.js index b3475e1b8..db4685a1e 100644 --- a/app/soapbox/features/notifications/components/notification.js +++ b/app/soapbox/features/notifications/components/notification.js @@ -325,6 +325,41 @@ class Notification extends ImmutablePureComponent { ); } + renderStatus(notification) { + const { intl } = this.props; + + const account = notification.get('account'); + const link = this.renderLink(account); + + return ( + +
+
+
+ +
+ + + + +
+ +
+
+ ); + } + renderPoll(notification) { const { intl } = this.props; @@ -398,6 +433,8 @@ class Notification extends ImmutablePureComponent { return this.renderFavourite(notification); case 'reblog': return this.renderReblog(notification); + case 'status': + return this.renderStatus(notification); case 'poll': return this.renderPoll(notification); case 'move': diff --git a/app/soapbox/locales/pl.json b/app/soapbox/locales/pl.json index 51b349541..2ab4dd79c 100644 --- a/app/soapbox/locales/pl.json +++ b/app/soapbox/locales/pl.json @@ -669,6 +669,7 @@ "notification.pleroma:emoji_reaction": "{name} zareagował(a) na Twój wpis", "notification.poll": "Głosowanie w którym brałeś(-aś) udział zakończyła się", "notification.reblog": "{name} podbił(a) Twój wpis", + "notification.status": "{name} właśnie opublikował(a) wpis", "notifications.clear": "Wyczyść powiadomienia", "notifications.clear_confirmation": "Czy na pewno chcesz bezpowrotnie usunąć wszystkie powiadomienia?", "notifications.clear_heading": "Wyczyść powiadomienia", @@ -700,6 +701,7 @@ "notifications.filter.mentions": "Wspomienia", "notifications.filter.moves": "Przenoszone konta", "notifications.filter.polls": "Wyniki głosowania", + "notifications.filter.statuses": "Nowe wpisy osób, które subskrybujesz", "notifications.group": "{count, number} {count, plural, one {powiadomienie} few {powiadomienia} many {powiadomień} more {powiadomień}}", "notifications.queue_label": "Naciśnij aby zobaczyć {count} {count, plural, one {nowe powiadomienie} few {nowe powiadomienia} many {nowych powiadomień} other {nowe powiadomienia}}", "password_reset.confirmation": "Sprawdź swoją pocztę e-mail, aby potwierdzić.", diff --git a/app/soapbox/reducers/notifications.js b/app/soapbox/reducers/notifications.js index fad2563c3..faade6a82 100644 --- a/app/soapbox/reducers/notifications.js +++ b/app/soapbox/reducers/notifications.js @@ -73,7 +73,7 @@ const isValid = notification => { } // Mastodon can return status notifications with a null status - if (['mention', 'reblog', 'favourite', 'poll'].includes(notification.type) && !notification.status.id) { + if (['mention', 'reblog', 'favourite', 'poll', 'status'].includes(notification.type) && !notification.status.id) { return false; } From 19aa4b254e4341d0c1cbce08c1db26ea0cdf0520 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 14 Apr 2022 22:14:54 +0200 Subject: [PATCH 14/75] Support 'status' notification type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/actions/notifications.js | 4 ++-- .../features/notifications/components/filter_bar.js | 7 +++++++ .../features/notifications/components/notification.js | 8 +++++++- app/soapbox/locales/pl.json | 2 ++ app/soapbox/reducers/notifications.js | 2 +- 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/app/soapbox/actions/notifications.js b/app/soapbox/actions/notifications.js index ce7e5eca0..66918c698 100644 --- a/app/soapbox/actions/notifications.js +++ b/app/soapbox/actions/notifications.js @@ -98,7 +98,7 @@ export function updateNotificationsQueue(notification, intlMessages, intlLocale, const isOnNotificationsPage = curPath === '/notifications'; - if (notification.type === 'mention') { + if (['mention', 'status'].includes(notification.type)) { const regex = regexFromFilters(filters); const searchIndex = notification.status.spoiler_text + '\n' + unescapeHTML(notification.status.content); filtered = regex && regex.test(searchIndex); @@ -170,7 +170,7 @@ export function dequeueNotifications() { const excludeTypesFromSettings = getState => getSettings(getState()).getIn(['notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS(); const excludeTypesFromFilter = filter => { - const allTypes = ImmutableList(['follow', 'follow_request', 'favourite', 'reblog', 'mention', 'poll', 'move', 'pleroma:emoji_reaction']); + const allTypes = ImmutableList(['follow', 'follow_request', 'favourite', 'reblog', 'mention', 'status', 'poll', 'move', 'pleroma:emoji_reaction']); return allTypes.filterNot(item => item === filter).toJS(); }; diff --git a/app/soapbox/features/notifications/components/filter_bar.js b/app/soapbox/features/notifications/components/filter_bar.js index 05e182146..d443205a7 100644 --- a/app/soapbox/features/notifications/components/filter_bar.js +++ b/app/soapbox/features/notifications/components/filter_bar.js @@ -15,6 +15,7 @@ const messages = defineMessages({ follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' }, moves: { id: 'notifications.filter.moves', defaultMessage: 'Moves' }, emoji_reacts: { id: 'notifications.filter.emoji_reacts', defaultMessage: 'Emoji reacts' }, + statuses: { id: 'notifications.filter.statuses', defaultMessage: 'Updates from people you follow' }, }); export default @injectIntl @@ -80,6 +81,12 @@ class NotificationFilterBar extends React.PureComponent { action: this.onClick('poll'), name: 'poll', }); + items.push({ + text: , + title: intl.formatMessage(messages.statuses), + action: this.onClick('status'), + name: 'status', + }); items.push({ text: , title: intl.formatMessage(messages.follows), diff --git a/app/soapbox/features/notifications/components/notification.js b/app/soapbox/features/notifications/components/notification.js index 77fa5762e..00911e68e 100644 --- a/app/soapbox/features/notifications/components/notification.js +++ b/app/soapbox/features/notifications/components/notification.js @@ -36,13 +36,14 @@ const buildLink = (account) => ( ); -export const NOTIFICATION_TYPES = ['follow', 'mention', 'favourite', 'reblog']; +export const NOTIFICATION_TYPES = ['follow', 'mention', 'favourite', 'reblog', 'status']; const icons = { follow: require('@tabler/icons/icons/user-plus.svg'), mention: require('@tabler/icons/icons/at.svg'), favourite: require('@tabler/icons/icons/heart.svg'), reblog: require('@tabler/icons/icons/repeat.svg'), + status: require('@tabler/icons/icons/home.svg'), }; const messages = { @@ -62,6 +63,10 @@ const messages = { id: 'notification.reblog', defaultMessage: '{name} re-TRUTH your TRUTH', }, + status: { + id: 'notification.status', + defaultMessage: '{name} just posted', + }, }; const buildMessage = (type, account) => { @@ -153,6 +158,7 @@ const Notification = (props) => { case 'favourite': case 'mention': case 'reblog': + case 'status': return ( { } // Mastodon can return status notifications with a null status - if (['mention', 'reblog', 'favourite', 'poll'].includes(notification.type) && !notification.status.id) { + if (['mention', 'reblog', 'favourite', 'poll', 'status'].includes(notification.type) && !notification.status.id) { return false; } From 2d0dbf6005141295c188cd468567db9a9ba530c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 14 Apr 2022 22:20:53 +0200 Subject: [PATCH 15/75] dark MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/features/ui/components/actions_modal.js | 2 +- app/styles/components/modal.scss | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/soapbox/features/ui/components/actions_modal.js b/app/soapbox/features/ui/components/actions_modal.js index bcac0c67c..aa0201ca9 100644 --- a/app/soapbox/features/ui/components/actions_modal.js +++ b/app/soapbox/features/ui/components/actions_modal.js @@ -46,7 +46,7 @@ const ActionsModal = ({ status, actions, onClick, onClose }) => { {({ top }) => (
{status && ( - + Date: Thu, 14 Apr 2022 15:22:37 -0500 Subject: [PATCH 16/75] Webpack: add DeadCodePlugin to detect unused files --- package.json | 1 + webpack/shared.js | 14 ++++++++++++++ yarn.lock | 8 ++++++++ 3 files changed, 23 insertions(+) diff --git a/package.json b/package.json index 4caec0c8d..89d94fa92 100644 --- a/package.json +++ b/package.json @@ -191,6 +191,7 @@ "webpack-assets-manifest": "^5.0.6", "webpack-bundle-analyzer": "^4.4.2", "webpack-cli": "^4.8.0", + "webpack-deadcode-plugin": "^0.1.16", "webpack-merge": "^5.8.0", "wicg-inert": "^3.1.1" }, diff --git a/webpack/shared.js b/webpack/shared.js index 0ccd628cb..8135fa512 100644 --- a/webpack/shared.js +++ b/webpack/shared.js @@ -9,6 +9,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const webpack = require('webpack'); const AssetsManifestPlugin = require('webpack-assets-manifest'); +const DeadCodePlugin = require('webpack-deadcode-plugin'); const { env, settings, output } = require('./configuration'); const rules = require('./rules'); @@ -86,6 +87,19 @@ module.exports = { writeToDisk: true, publicPath: true, }), + // https://github.com/MQuy/webpack-deadcode-plugin + new DeadCodePlugin({ + patterns: [ + 'app/**/*', + ], + exclude: [ + '**/*.test.*', + '**/__*__/*', + '**/(LICENSE|README|COPYING)(.md|.txt)?', + // This file is imported with @preval + 'app/soapbox/features/emoji/emoji_map.json', + ], + }), // https://github.com/jantimon/html-webpack-plugin#options new HtmlWebpackPlugin(makeHtmlConfig()), new HtmlWebpackPlugin(makeHtmlConfig({ filename: '404.html' })), diff --git a/yarn.lock b/yarn.lock index 9ed45677b..ece5c1947 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10786,6 +10786,14 @@ webpack-cli@^4.8.0: v8-compile-cache "^2.2.0" webpack-merge "^5.7.3" +webpack-deadcode-plugin@^0.1.16: + version "0.1.16" + resolved "https://registry.yarnpkg.com/webpack-deadcode-plugin/-/webpack-deadcode-plugin-0.1.16.tgz#f58ec7bd908325247396438a50afc209bfd01bdd" + integrity sha512-T3oRlzftN4jU8Wm8rjx9mzYC8zr4CND5kNgh6EBJyhWy68F5kZ334EjdBjOKDEpUHuybBuxi/SviVUFsP0gx4g== + dependencies: + chalk "^3.0.0" + fast-glob "^3.1.1" + webpack-dev-middleware@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.1.0.tgz#90a645b077e85f661c5bb967dc32adc3eceb5cfd" From 8da30ad98f3581a2bee2939b6234b640d332e2c7 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 14 Apr 2022 15:47:55 -0500 Subject: [PATCH 17/75] Fix video letterboxing --- app/soapbox/features/video/index.js | 6 ++++-- app/styles/components/video-player.scss | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/soapbox/features/video/index.js b/app/soapbox/features/video/index.js index 869fcdfce..72d18be0e 100644 --- a/app/soapbox/features/video/index.js +++ b/app/soapbox/features/video/index.js @@ -13,6 +13,8 @@ import Icon from 'soapbox/components/icon'; import { isPanoramic, isPortrait, minimumAspectRatio, maximumAspectRatio } from '../../utils/media_aspect_ratio'; import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen'; +const DEFAULT_HEIGHT = 300; + const messages = defineMessages({ play: { id: 'video.play', defaultMessage: 'Play' }, pause: { id: 'video.pause', defaultMessage: 'Pause' }, @@ -513,7 +515,7 @@ class Video extends React.PureComponent { height = Math.floor(containerWidth / aspectRatio); } - if (height) playerStyle.height = height; + playerStyle.height = height || DEFAULT_HEIGHT; } let warning; @@ -553,7 +555,7 @@ class Video extends React.PureComponent { aria-label={alt} title={alt} width={width} - height={height || 300} + height={height || DEFAULT_HEIGHT} volume={volume} onClick={this.togglePlay} onKeyDown={this.handleVideoKeyDown} diff --git a/app/styles/components/video-player.scss b/app/styles/components/video-player.scss index 4f40b06aa..aff5bb19f 100644 --- a/app/styles/components/video-player.scss +++ b/app/styles/components/video-player.scss @@ -119,6 +119,7 @@ video { object-fit: contain; position: relative; + max-height: 100%; } } From 1482b893234286919b7354d061e535ab28d235e6 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 14 Apr 2022 15:58:22 -0500 Subject: [PATCH 18/75] Fix letterboxed image background --- app/styles/components/media-gallery.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/styles/components/media-gallery.scss b/app/styles/components/media-gallery.scss index 397e98133..d6a32f8c2 100644 --- a/app/styles/components/media-gallery.scss +++ b/app/styles/components/media-gallery.scss @@ -103,6 +103,7 @@ } .media-gallery__preview { + @apply bg-gray-200 dark:bg-slate-900; width: 100%; height: 100%; object-fit: cover; @@ -110,7 +111,6 @@ top: 0; left: 0; z-index: 0; - background: var(--background-color); .still-image--play-on-hover::before { content: 'GIF'; From 952e06ce41924388eb9284115acd28220b7ea5a3 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 14 Apr 2022 19:00:31 -0500 Subject: [PATCH 19/75] AutosuggestTextarea: fix expand animation --- app/soapbox/components/autosuggest_textarea.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/soapbox/components/autosuggest_textarea.js b/app/soapbox/components/autosuggest_textarea.js index 69ec5d7a1..c5cf62b2d 100644 --- a/app/soapbox/components/autosuggest_textarea.js +++ b/app/soapbox/components/autosuggest_textarea.js @@ -257,7 +257,8 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {