diff --git a/.env.example b/.env.example index b116e6d40..d26fe9632 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,3 @@ -NODE_ENV=development +# NODE_ENV=development # BACKEND_URL="https://example.com" # PROXY_HTTPS_INSECURE=false diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 786d52d13..108482b41 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -260,7 +260,6 @@ module.exports = { alphabetize: { order: 'asc' }, }, ], - '@typescript-eslint/no-duplicate-imports': 'error', '@typescript-eslint/member-delimiter-style': [ 'error', { @@ -285,6 +284,7 @@ module.exports = { }, ], 'tailwindcss/migration-from-tailwind-2': 'error', + 'tailwindcss/no-custom-classname': 'warn', }, overrides: [ { diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8c4526d58..e9f0046e4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,6 +6,7 @@ on: - develop - feat/** - fix/** + - refactor/** release: types: [ published ] @@ -27,7 +28,7 @@ jobs: yarn install --ignore-scripts yarn patch-package env NODE_ENV=production yarn build - zip -r static.zip static + zip -r static.zip static/ # -- For artifact mkdir __build/ cp -r static/ __build/ diff --git a/.gitignore b/.gitignore index 92e9362d8..a9ede885f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,8 @@ /.vs/ yarn-error.log* /junit.xml +*.timestamp-* +*.bundled_* /static/ /static-test/ diff --git a/app/soapbox/actions/settings.ts b/app/soapbox/actions/settings.ts index cdcdb2bbe..8e7db84db 100644 --- a/app/soapbox/actions/settings.ts +++ b/app/soapbox/actions/settings.ts @@ -4,7 +4,7 @@ import { createSelector } from 'reselect'; import { v4 as uuid } from 'uuid'; import { patchMe } from 'soapbox/actions/me'; -import messages from 'soapbox/locales/messages'; +import messages from 'soapbox/messages'; import toast from 'soapbox/toast'; import { isLoggedIn } from 'soapbox/utils/auth'; diff --git a/app/soapbox/actions/streaming.ts b/app/soapbox/actions/streaming.ts index fa117d690..e72b20239 100644 --- a/app/soapbox/actions/streaming.ts +++ b/app/soapbox/actions/streaming.ts @@ -2,7 +2,7 @@ import { getLocale, getSettings } from 'soapbox/actions/settings'; import { importEntities } from 'soapbox/entity-store/actions'; import { Entities } from 'soapbox/entity-store/entities'; import { selectEntity } from 'soapbox/entity-store/selectors'; -import messages from 'soapbox/locales/messages'; +import messages from 'soapbox/messages'; import { ChatKeys, IChat, isLastMessage } from 'soapbox/queries/chats'; import { queryClient } from 'soapbox/queries/client'; import { getUnreadChatsCount, updateChatListItem, updateChatMessage } from 'soapbox/utils/chats'; diff --git a/app/soapbox/build-config.js b/app/soapbox/build-config-compiletime.ts similarity index 52% rename from app/soapbox/build-config.js rename to app/soapbox/build-config-compiletime.ts index a11faa0e8..8b048ce2d 100644 --- a/app/soapbox/build-config.js +++ b/app/soapbox/build-config-compiletime.ts @@ -4,19 +4,20 @@ * @module soapbox/build-config */ -const trim = require('lodash/trim'); -const trimEnd = require('lodash/trimEnd'); +// eslint-disable-next-line import/extensions +import trim from 'lodash/trim.js'; +// eslint-disable-next-line import/extensions +import trimEnd from 'lodash/trimEnd.js'; const { NODE_ENV, BACKEND_URL, FE_SUBDIRECTORY, - FE_BUILD_DIR, FE_INSTANCE_SOURCE_DIR, SENTRY_DSN, -} = process.env; +} = process.env || {}; -const sanitizeURL = url => { +const sanitizeURL = (url: string | undefined = '') => { try { return trimEnd(new URL(url).toString(), '/'); } catch { @@ -24,23 +25,20 @@ const sanitizeURL = url => { } }; -const sanitizeBasename = path => { +const sanitizeBasename = (path: string | undefined = '') => { 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({ +const env = { 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, +}; + +export type BuildConfig = typeof env; + +export default () => ({ + data: env, }); diff --git a/app/soapbox/build-config.ts b/app/soapbox/build-config.ts new file mode 100644 index 000000000..72f61ba21 --- /dev/null +++ b/app/soapbox/build-config.ts @@ -0,0 +1,9 @@ +import type { BuildConfig } from './build-config-compiletime'; + +export const { + NODE_ENV, + BACKEND_URL, + FE_SUBDIRECTORY, + FE_INSTANCE_SOURCE_DIR, + SENTRY_DSN, +} = import.meta.compileTime('./build-config-compiletime.ts'); diff --git a/app/soapbox/components/autosuggest-textarea.tsx b/app/soapbox/components/autosuggest-textarea.tsx index e0be3c958..fc337cfd9 100644 --- a/app/soapbox/components/autosuggest-textarea.tsx +++ b/app/soapbox/components/autosuggest-textarea.tsx @@ -1,3 +1,4 @@ +import { install, uninstall } from '@github/hotkey'; import clsx from 'clsx'; import React from 'react'; import ImmutablePureComponent from 'react-immutable-pure-component'; @@ -30,12 +31,14 @@ interface IAutosuggesteTextarea { onFocus: () => void onBlur?: () => void condensed?: boolean + keymap?: string children: React.ReactNode } class AutosuggestTextarea extends ImmutablePureComponent { textarea: HTMLTextAreaElement | null = null; + keymap?: string = undefined; static defaultProps = { autoFocus: true, @@ -171,8 +174,13 @@ class AutosuggestTextarea extends ImmutablePureComponent setTextarea: React.Ref = (c) => { this.textarea = c; + if (this.textarea && this.keymap) install(this.textarea, this.keymap); }; + componentWillUnmount(): void { + if (this.textarea && this.keymap) uninstall(this.textarea); + } + onPaste: React.ClipboardEventHandler = (e) => { if (e.clipboardData && e.clipboardData.files.length === 1) { this.props.onPaste(e.clipboardData.files); @@ -227,7 +235,8 @@ class AutosuggestTextarea extends ImmutablePureComponent } render() { - const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, children, condensed, id } = this.props; + const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, children, condensed, id, keymap } = this.props; + if (keymap) this.keymap = keymap; const { suggestionsHidden } = this.state; const style = { direction: 'ltr', minRows: 10 }; diff --git a/app/soapbox/components/status.tsx b/app/soapbox/components/status.tsx index 7f36650b9..409d7f6c3 100644 --- a/app/soapbox/components/status.tsx +++ b/app/soapbox/components/status.tsx @@ -1,13 +1,9 @@ import clsx from 'clsx'; import React, { useEffect, useRef, useState } from 'react'; -import { HotKeys } from 'react-hotkeys'; import { useIntl, FormattedMessage, defineMessages } from 'react-intl'; import { Link, useHistory } from 'react-router-dom'; -import { mentionCompose, replyCompose } from 'soapbox/actions/compose'; -import { toggleFavourite, toggleReblog } from 'soapbox/actions/interactions'; -import { openModal } from 'soapbox/actions/modals'; -import { toggleStatusHidden, unfilterStatus } from 'soapbox/actions/statuses'; +import { unfilterStatus } from 'soapbox/actions/statuses'; import AccountContainer from 'soapbox/containers/account-container'; import StatusContainer from 'soapbox/containers/neo/status-container'; import QuotedStatus from 'soapbox/features/status/containers/quoted-status-container'; @@ -21,7 +17,6 @@ import Tombstone from './tombstone'; import { Card, Icon, Text } from './ui'; import type { - Account as AccountEntity, Status as StatusEntity, } from 'soapbox/types/entities'; @@ -129,6 +124,7 @@ const Status: React.FC = (props) => { } }; + /* const handleHotkeyOpenMedia = (e?: KeyboardEvent): void => { const status = actualStatus; const firstAttachment = status.media_attachments.first(); @@ -199,13 +195,16 @@ const Status: React.FC = (props) => { const handleHotkeyReact = (): void => { _expandEmojiSelector(); }; + */ const handleUnfilter = () => dispatch(unfilterStatus(status.filtered.size ? status.id : actualStatus.id)); + /* const _expandEmojiSelector = (): void => { const firstEmoji: HTMLDivElement | null | undefined = node.current?.querySelector('.emoji-react-selector .emoji-react-selector__emoji'); firstEmoji?.focus(); }; + */ const renderStatusInfo = () => { if (typeof notification !== 'undefined') { @@ -343,23 +342,23 @@ const Status: React.FC = (props) => { } if (filtered && status.showFiltered) { + /* const minHandlers = muted ? undefined : { moveUp: handleHotkeyMoveUp, moveDown: handleHotkeyMoveDown, }; + */ return ( - -
- - : {status.filtered.join(', ')}. - {' '} - - -
-
+
+ + : {status.filtered.join(', ')}. + {' '} + + +
); } @@ -385,6 +384,7 @@ const Status: React.FC = (props) => { } } + /* const handlers = muted ? undefined : { reply: handleHotkeyReply, favourite: handleHotkeyFavourite, @@ -399,6 +399,7 @@ const Status: React.FC = (props) => { openMedia: handleHotkeyOpenMedia, react: handleHotkeyReact, }; + */ const isUnderReview = actualStatus.visibility === 'self'; const isSensitive = actualStatus.sensitive; @@ -415,70 +416,68 @@ const Status: React.FC = (props) => { } return ( - -
+ - -
- {renderStatusInfo()} - - -
- -
- - - 0)} - contentOption={{ - status: actualStatus, - onClick: handleClick, - collapsable: true, - translatable: true, - }} - /> +
+ {renderStatusInfo()} + + +
- {(!hideActionBar && !isUnderReview) && ( -
- -
- )} -
-
-
-
+
+ + + 0)} + contentOption={{ + status: actualStatus, + onClick: handleClick, + collapsable: true, + translatable: true, + }} + /> + + {(!hideActionBar && !isUnderReview) && ( +
+ +
+ )} +
+ + ); }; diff --git a/app/soapbox/components/tombstone.tsx b/app/soapbox/components/tombstone.tsx index 2c1c0187e..8e9bed5b1 100644 --- a/app/soapbox/components/tombstone.tsx +++ b/app/soapbox/components/tombstone.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { HotKeys } from 'react-hotkeys'; import { FormattedMessage } from 'react-intl'; import { Text } from 'soapbox/components/ui'; @@ -12,26 +11,26 @@ interface ITombstone { /** Represents a deleted item. */ const Tombstone: React.FC = ({ id, onMoveUp, onMoveDown }) => { + /* const handlers = { moveUp: () => onMoveUp?.(id), moveDown: () => onMoveDown?.(id), }; + */ return ( - -
-
- - - -
+
+
+ + +
- +
); }; diff --git a/app/soapbox/components/ui/card/card.tsx b/app/soapbox/components/ui/card/card.tsx index a57fc9a1a..937c02e73 100644 --- a/app/soapbox/components/ui/card/card.tsx +++ b/app/soapbox/components/ui/card/card.tsx @@ -68,7 +68,13 @@ const CardHeader: React.FC = ({ className, children, backHref, onBa const backAttributes = backHref ? { to: backHref } : { onClick: onBackClick }; return ( - + // eslint-disable-next-line react-hooks/rules-of-hooks + {intl.formatMessage(messages.back)} diff --git a/app/soapbox/containers/soapbox.tsx b/app/soapbox/containers/soapbox.tsx index 009ed8929..0a982bc53 100644 --- a/app/soapbox/containers/soapbox.tsx +++ b/app/soapbox/containers/soapbox.tsx @@ -42,7 +42,7 @@ import { useInstance, useRegistrationStatus, } from 'soapbox/hooks'; -import MESSAGES from 'soapbox/locales/messages'; +import MESSAGES from 'soapbox/messages'; import { normalizeSoapboxConfig } from 'soapbox/normalizers'; import { queryClient } from 'soapbox/queries/client'; import { useCachedLocationHandler } from 'soapbox/utils/redirect'; diff --git a/app/soapbox/custom.ts b/app/soapbox/custom.ts index 461f64845..efebce1e2 100644 --- a/app/soapbox/custom.ts +++ b/app/soapbox/custom.ts @@ -7,9 +7,8 @@ import * as BuildConfig from 'soapbox/build-config'; export const custom = (filename: string, fallback: any = {}): any => { if (BuildConfig.NODE_ENV === 'test') return fallback; - // @ts-ignore: yes it does - const context = require.context('custom', false, /\.json$/); - const path = `./${filename}.json`; + const modules = import.meta.glob('../../custom/*.json', { eager: true }); + const key = `../../custom/${filename}.json`; - return context.keys().includes(path) ? context(path) : fallback; + return modules[key] ? modules[key] : fallback; }; diff --git a/app/soapbox/entity-store/hooks/useBatchedEntities.ts b/app/soapbox/entity-store/hooks/useBatchedEntities.ts index 9ea6b3f8c..db6ed18fa 100644 --- a/app/soapbox/entity-store/hooks/useBatchedEntities.ts +++ b/app/soapbox/entity-store/hooks/useBatchedEntities.ts @@ -9,8 +9,8 @@ import { selectCache, selectListState, useListState } from '../selectors'; import { parseEntitiesPath } from './utils'; -import type { Entity } from '../types'; import type { EntitiesPath, EntityFn, EntitySchema, ExpandedEntitiesPath } from './types'; +import type { Entity } from '../types'; import type { RootState } from 'soapbox/store'; interface UseBatchedEntitiesOpts { diff --git a/app/soapbox/entity-store/hooks/useCreateEntity.ts b/app/soapbox/entity-store/hooks/useCreateEntity.ts index 24ce3af7d..6d876d2e3 100644 --- a/app/soapbox/entity-store/hooks/useCreateEntity.ts +++ b/app/soapbox/entity-store/hooks/useCreateEntity.ts @@ -7,8 +7,8 @@ import { importEntities } from '../actions'; import { parseEntitiesPath } from './utils'; -import type { Entity } from '../types'; import type { EntityCallbacks, EntityFn, EntitySchema, ExpandedEntitiesPath } from './types'; +import type { Entity } from '../types'; interface UseCreateEntityOpts { schema?: EntitySchema diff --git a/app/soapbox/entity-store/hooks/useEntities.ts b/app/soapbox/entity-store/hooks/useEntities.ts index 1ec868c03..08360121a 100644 --- a/app/soapbox/entity-store/hooks/useEntities.ts +++ b/app/soapbox/entity-store/hooks/useEntities.ts @@ -11,8 +11,8 @@ import { selectEntities, selectListState, useListState } from '../selectors'; import { parseEntitiesPath } from './utils'; -import type { Entity } from '../types'; import type { EntityFn, EntitySchema, ExpandedEntitiesPath } from './types'; +import type { Entity } from '../types'; /** Additional options for the hook. */ interface UseEntitiesOpts { diff --git a/app/soapbox/entity-store/hooks/useEntity.ts b/app/soapbox/entity-store/hooks/useEntity.ts index af4aa06bc..b4c248211 100644 --- a/app/soapbox/entity-store/hooks/useEntity.ts +++ b/app/soapbox/entity-store/hooks/useEntity.ts @@ -7,8 +7,8 @@ import { useAppDispatch, useAppSelector, useLoading } from 'soapbox/hooks'; import { importEntities } from '../actions'; import { selectEntity } from '../selectors'; -import type { Entity } from '../types'; import type { EntitySchema, EntityPath, EntityFn } from './types'; +import type { Entity } from '../types'; /** Additional options for the hook. */ interface UseEntityOpts { diff --git a/app/soapbox/entity-store/hooks/useEntityActions.ts b/app/soapbox/entity-store/hooks/useEntityActions.ts index 449817e32..40f581197 100644 --- a/app/soapbox/entity-store/hooks/useEntityActions.ts +++ b/app/soapbox/entity-store/hooks/useEntityActions.ts @@ -4,8 +4,8 @@ import { useCreateEntity } from './useCreateEntity'; import { useDeleteEntity } from './useDeleteEntity'; import { parseEntitiesPath } from './utils'; -import type { Entity } from '../types'; import type { EntitySchema, ExpandedEntitiesPath } from './types'; +import type { Entity } from '../types'; interface UseEntityActionsOpts { schema?: EntitySchema diff --git a/app/soapbox/entity-store/reducer.ts b/app/soapbox/entity-store/reducer.ts index 72d4a1a4c..19e12c579 100644 --- a/app/soapbox/entity-store/reducer.ts +++ b/app/soapbox/entity-store/reducer.ts @@ -1,4 +1,4 @@ -import produce, { enableMapSet } from 'immer'; +import { produce, enableMapSet } from 'immer'; import { ENTITIES_IMPORT, @@ -198,4 +198,4 @@ function reducer(state: Readonly = {}, action: EntityAction): State { } export default reducer; -export type { State }; \ No newline at end of file +export type { State }; diff --git a/app/soapbox/features/audio/index.tsx b/app/soapbox/features/audio/index.tsx index 75c7beba1..e9b8bc3d4 100644 --- a/app/soapbox/features/audio/index.tsx +++ b/app/soapbox/features/audio/index.tsx @@ -299,6 +299,7 @@ const Audio: React.FC = (props) => { }; const _renderCanvas = () => { + // eslint-disable-next-line compat/compat requestAnimationFrame(() => { if (!audio.current) return; diff --git a/app/soapbox/features/chats/components/chat-page/components/chat-page-main.tsx b/app/soapbox/features/chats/components/chat-page/components/chat-page-main.tsx index 890983f64..5120f9be5 100644 --- a/app/soapbox/features/chats/components/chat-page/components/chat-page-main.tsx +++ b/app/soapbox/features/chats/components/chat-page/components/chat-page-main.tsx @@ -122,6 +122,7 @@ const ChatPageMain = () => { history.push('/chats')} /> diff --git a/app/soapbox/features/chats/components/chat-page/components/chat-page-new.tsx b/app/soapbox/features/chats/components/chat-page/components/chat-page-new.tsx index 0b1e0de8d..a27edc0e6 100644 --- a/app/soapbox/features/chats/components/chat-page/components/chat-page-new.tsx +++ b/app/soapbox/features/chats/components/chat-page/components/chat-page-new.tsx @@ -25,6 +25,7 @@ const ChatPageNew: React.FC = () => { history.push('/chats')} /> diff --git a/app/soapbox/features/chats/components/chat-page/components/chat-page-settings.tsx b/app/soapbox/features/chats/components/chat-page/components/chat-page-settings.tsx index dddff635e..e08861bd9 100644 --- a/app/soapbox/features/chats/components/chat-page/components/chat-page-settings.tsx +++ b/app/soapbox/features/chats/components/chat-page/components/chat-page-settings.tsx @@ -52,6 +52,7 @@ const ChatPageSettings = () => { history.push('/chats')} /> diff --git a/app/soapbox/features/chats/components/chat-pane/chat-pane.tsx b/app/soapbox/features/chats/components/chat-pane/chat-pane.tsx index 026eb08e4..20896167c 100644 --- a/app/soapbox/features/chats/components/chat-pane/chat-pane.tsx +++ b/app/soapbox/features/chats/components/chat-pane/chat-pane.tsx @@ -8,9 +8,9 @@ import { useDebounce, useFeatures } from 'soapbox/hooks'; import { IChat, useChats } from 'soapbox/queries/chats'; import ChatList from '../chat-list'; -import ChatSearchInput from '../chat-search-input'; import ChatSearch from '../chat-search/chat-search'; import EmptyResultsBlankslate from '../chat-search/empty-results-blankslate'; +import ChatSearchInput from '../chat-search-input'; import ChatPaneHeader from '../chat-widget/chat-pane-header'; import ChatWindow from '../chat-widget/chat-window'; import ChatSearchHeader from '../chat-widget/headers/chat-search-header'; diff --git a/app/soapbox/features/chats/components/chat-widget/chat-settings.tsx b/app/soapbox/features/chats/components/chat-widget/chat-settings.tsx index 5a38778e0..7e19199f4 100644 --- a/app/soapbox/features/chats/components/chat-widget/chat-settings.tsx +++ b/app/soapbox/features/chats/components/chat-widget/chat-settings.tsx @@ -93,7 +93,10 @@ const ChatSettings = () => { onToggle={minimizeChatPane} title={ -