From 09db721b7e890a8bf41fd520ef2cbb4c291004ab Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Mon, 6 Jul 2026 03:29:18 +1000 Subject: [PATCH] refactor: Consolidate icon imports across components and streamline import statements for improved readability --- src/app/components/AccountDataEditor.tsx | 17 +- src/app/components/BackupRestore.tsx | 19 +- src/app/components/DeviceVerification.tsx | 17 +- .../components/DeviceVerificationSetup.tsx | 16 +- src/app/components/JoinRulesSwitcher.tsx | 16 +- src/app/components/ManualVerification.tsx | 15 +- src/app/components/Pdf-viewer/PdfViewer.tsx | 20 +- .../components/RoomNotificationSwitcher.tsx | 3 +- src/app/components/UIAFlowOverlay.tsx | 15 +- .../account-switcher/AccountSwitcher.tsx | 3 +- .../create-room/AdditionalCreatorInput.tsx | 19 +- .../create-room/CreateRoomAliasInput.tsx | 3 +- .../create-room/CreateRoomKindSelector.tsx | 3 +- .../create-room/RoomVersionSelector.tsx | 15 +- src/app/components/direct-list/DirectList.tsx | 17 +- src/app/components/editor/Editor.preview.tsx | 13 +- src/app/components/editor/Toolbar.tsx | 20 +- .../autocomplete/RoomMentionAutocomplete.tsx | 3 +- .../autocomplete/UserMentionAutocomplete.tsx | 3 +- src/app/components/emoji-board/EmojiBoard.tsx | 3 +- .../emoji-board/components/NoStickerPacks.tsx | 3 +- .../emoji-board/components/SearchInput.tsx | 3 +- .../emoji-board/components/Sidebar.tsx | 15 +- .../emoji-board/useEmojiGroupIcons.ts | 3 +- .../components/event-readers/EventReaders.tsx | 15 +- src/app/components/icons/Icon.tsx | 41 ++ src/app/components/icons/iconSizes.ts | 12 + src/app/components/icons/index.ts | 4 + src/app/components/icons/lucideIcons.ts | 238 ++++++ src/app/components/icons/style.css.ts | 25 + src/app/components/icons/types.ts | 5 + .../components/image-editor/ImageEditor.tsx | 3 +- .../image-pack-view/ImagePackView.tsx | 3 +- .../components/image-pack-view/ImageTile.tsx | 3 +- .../components/image-pack-view/PackMeta.tsx | 14 +- .../image-pack-view/UsageSwitcher.tsx | 3 +- .../components/image-viewer/ImageViewer.tsx | 3 +- .../invite-user-prompt/InviteUserPrompt.tsx | 24 +- .../join-address-prompt/JoinAddressPrompt.tsx | 18 +- .../join-space-prompt/JoinSpacePrompt.tsx | 22 +- .../leave-room-prompt/LeaveRoomPrompt.tsx | 18 +- .../leave-space-prompt/LeaveSpacePrompt.tsx | 18 +- .../manage-rooms-prompt/ManageRoomsPrompt.tsx | 23 +- src/app/components/media/Video.tsx | 3 +- src/app/components/member-tile/MemberTile.tsx | 3 +- src/app/components/message/FileHeader.tsx | 3 +- .../components/message/MsgTypeRenderers.tsx | 3 +- src/app/components/message/Reply.tsx | 3 +- .../message/content/AudioContent.tsx | 3 +- .../message/content/EventContent.tsx | 3 +- .../message/content/FallbackContent.tsx | 3 +- .../message/content/FileContent.tsx | 17 +- .../message/content/ImageContent.tsx | 19 +- .../message/content/VideoContent.tsx | 19 +- .../password-input/PasswordInput.tsx | 3 +- src/app/components/room-avatar/RoomAvatar.tsx | 3 +- src/app/components/room-card/RoomCard.tsx | 19 +- .../room-topic-viewer/RoomTopicViewer.tsx | 3 +- src/app/components/text-viewer/TextViewer.tsx | 3 +- .../components/title-bar/WindowControls.tsx | 3 +- .../components/uia-stages/PasswordStage.tsx | 3 +- src/app/components/uia-stages/SSOStage.tsx | 3 +- .../components/upload-board/UploadBoard.tsx | 3 +- .../upload-card/CompactUploadCardRenderer.tsx | 3 +- src/app/components/upload-card/UploadCard.tsx | 3 +- .../upload-card/UploadCardRenderer.tsx | 3 +- .../components/url-preview/UrlPreviewCard.tsx | 3 +- .../components/url-preview/YouTubeEmbed.tsx | 3 +- .../components/user-profile/CreatorChip.tsx | 3 +- src/app/components/user-profile/PowerChip.tsx | 25 +- src/app/components/user-profile/UserChips.tsx | 20 +- src/app/components/user-profile/UserHero.tsx | 14 +- .../user-profile/UserModeration.tsx | 3 +- .../user-profile/UserRoomProfile.tsx | 3 +- .../components/video-viewer/VideoViewer.tsx | 3 +- src/app/features/ShareRoomPicker.tsx | 20 +- src/app/features/add-existing/AddExisting.tsx | 22 +- src/app/features/call/CallOverlay.tsx | 3 +- src/app/features/call/DockedCallContent.tsx | 3 +- .../call/IncomingCallNotification.tsx | 3 +- .../features/call/SidebarDockedCallPanel.tsx | 3 +- .../developer-tools/DevelopTools.tsx | 21 +- .../developer-tools/SendRoomEvent.tsx | 16 +- .../developer-tools/StateEventEditor.tsx | 16 +- .../emojis-stickers/EmojisStickers.tsx | 3 +- .../emojis-stickers/RoomPacks.tsx | 18 +- .../common-settings/general/EmbedFilters.tsx | 16 +- .../common-settings/general/RoomAddress.tsx | 17 +- .../general/RoomEncryption.tsx | 19 +- .../general/RoomHistoryVisibility.tsx | 15 +- .../common-settings/general/RoomProfile.tsx | 15 +- .../common-settings/general/RoomUpgrade.tsx | 18 +- .../common-settings/members/Members.tsx | 17 +- .../permissions/PermissionGroups.tsx | 3 +- .../permissions/PowersEditor.tsx | 21 +- src/app/features/create-chat/CreateChat.tsx | 3 +- src/app/features/create-room/CreateRoom.tsx | 16 +- .../features/create-room/CreateRoomModal.tsx | 16 +- src/app/features/create-space/CreateSpace.tsx | 20 +- .../create-space/CreateSpaceModal.tsx | 16 +- .../features/forum/ForumAuthorIdentity.tsx | 6 +- .../features/forum/ForumAwareSpaceLayout.tsx | 34 +- src/app/features/forum/ForumBoardDetail.tsx | 144 ++-- src/app/features/forum/ForumBoardView.css.ts | 10 +- src/app/features/forum/ForumBoardView.tsx | 3 +- src/app/features/forum/ForumChatComposer.tsx | 3 +- src/app/features/forum/ForumEventContent.tsx | 64 ++ src/app/features/forum/ForumFeedSidebar.tsx | 192 ++--- src/app/features/forum/ForumFilterBar.tsx | 3 +- .../features/forum/ForumMessageActions.tsx | 176 ++++- .../forum/ForumMessageContextMenu.tsx | 256 +++++++ .../forum/ForumMessageMenuContext.tsx | 26 + src/app/features/forum/ForumNewPostBox.tsx | 5 +- src/app/features/forum/ForumNewPostModal.tsx | 105 ++- src/app/features/forum/ForumPostList.tsx | 32 +- .../features/forum/ForumPostUploadField.tsx | 248 +++++++ src/app/features/forum/ForumReplyComposer.tsx | 62 ++ src/app/features/forum/ForumSpaceShell.tsx | 51 +- src/app/features/forum/ForumThreadDetail.tsx | 692 +++++++++++++++--- .../features/forum/ForumTopicSearchInput.tsx | 3 +- src/app/features/forum/forumFeed.ts | 336 ++++++++- src/app/features/forum/forumRichText.ts | 10 +- src/app/features/forum/forumTheme.css.ts | 355 +++++++-- src/app/features/forum/index.ts | 3 +- src/app/features/forum/useForumBoard.ts | 156 ++-- .../forum/useForumMessageReactions.ts | 135 ++++ .../forum/useForumMessageRenderOptions.ts | 70 ++ .../features/forum/useForumRoomLiveUpdates.ts | 114 +++ .../JoinBeforeNavigate.tsx | 3 +- src/app/features/lobby/DnD.tsx | 3 +- src/app/features/lobby/HierarchyItemMenu.tsx | 17 +- src/app/features/lobby/Lobby.tsx | 7 +- src/app/features/lobby/LobbyHeader.tsx | 19 +- src/app/features/lobby/RoomItem.tsx | 21 +- src/app/features/lobby/SpaceAddControls.tsx | 3 +- src/app/features/lobby/SpaceItem.tsx | 19 +- .../features/message-search/MessageSearch.tsx | 3 +- .../features/message-search/SearchFilters.tsx | 21 +- .../features/message-search/SearchInput.tsx | 3 +- .../message-search/SearchResultGroup.tsx | 3 +- .../room-nav/CallParticipantsIndicator.tsx | 3 +- .../room-nav/RoomNavCategoryButton.tsx | 3 +- src/app/features/room-nav/RoomNavItem.tsx | 21 +- .../features/room-nav/UnjoinedSubRoomItem.tsx | 15 +- .../features/room-settings/RoomSettings.tsx | 3 +- .../room-settings/general/General.tsx | 3 +- .../room-settings/permissions/Permissions.tsx | 3 +- .../room-settings/sub-rooms/SubRooms.tsx | 13 +- src/app/features/room/MembersDrawer.tsx | 22 +- src/app/features/room/RoomInput.tsx | 63 +- src/app/features/room/RoomTimeline.tsx | 18 +- src/app/features/room/RoomViewFollowing.tsx | 14 +- src/app/features/room/RoomViewHeader.tsx | 25 +- src/app/features/room/RoomViewTyping.tsx | 3 +- src/app/features/room/ThreadView.tsx | 15 +- .../features/room/jump-to-time/JumpToTime.tsx | 21 +- src/app/features/room/message/Message.tsx | 27 +- .../features/room/message/MessageEditor.tsx | 16 +- .../room/reaction-viewer/ReactionViewer.tsx | 16 +- .../room/room-pin-menu/RoomPinMenu.tsx | 18 +- src/app/features/search/Search.tsx | 18 +- src/app/features/settings/Settings.tsx | 17 +- src/app/features/settings/about/About.tsx | 3 +- src/app/features/settings/account/Account.tsx | 3 +- .../settings/account/IgnoredUserList.tsx | 3 +- src/app/features/settings/account/Profile.tsx | 22 +- src/app/features/settings/audio/Audio.tsx | 21 +- .../settings/developer-tools/AccountData.tsx | 3 +- .../settings/developer-tools/DevelopTools.tsx | 3 +- .../features/settings/devices/DeviceTile.tsx | 18 +- src/app/features/settings/devices/Devices.tsx | 3 +- .../features/settings/devices/LocalBackup.tsx | 3 +- .../settings/devices/Verification.tsx | 21 +- .../emojis-stickers/EmojisStickers.tsx | 3 +- .../settings/emojis-stickers/GlobalPacks.tsx | 23 +- .../settings/emojis-stickers/UserPack.tsx | 3 +- src/app/features/settings/general/General.tsx | 23 +- .../notifications/KeywordMessages.tsx | 3 +- .../NotificationModeSwitcher.tsx | 15 +- .../settings/notifications/Notifications.tsx | 3 +- .../settings/plugins/PluginButtonSlot.tsx | 14 +- src/app/features/settings/plugins/Plugins.tsx | 24 +- .../features/space-settings/SpaceSettings.tsx | 3 +- .../space-settings/general/General.tsx | 3 +- .../permissions/Permissions.tsx | 3 +- src/app/features/space/SpaceOptionsMenu.tsx | 3 +- src/app/hooks/useMemberEventParser.tsx | 3 +- .../hooks/useRoomsNotificationPreferences.ts | 3 +- src/app/pages/auth/FiledError.tsx | 3 +- src/app/pages/auth/ServerPicker.tsx | 15 +- .../pages/auth/login/PasswordLoginForm.tsx | 20 +- src/app/pages/auth/login/TokenLogin.tsx | 14 +- src/app/pages/client/ClientRoot.tsx | 17 +- src/app/pages/client/WelcomePage.tsx | 3 +- src/app/pages/client/create/Create.tsx | 3 +- src/app/pages/client/direct/Direct.tsx | 17 +- src/app/pages/client/direct/DirectCreate.tsx | 3 +- src/app/pages/client/explore/Explore.tsx | 19 +- src/app/pages/client/explore/Featured.tsx | 3 +- src/app/pages/client/explore/Server.tsx | 21 +- src/app/pages/client/home/CreateRoom.tsx | 3 +- src/app/pages/client/home/Home.tsx | 17 +- .../pages/client/home/HomeThreadsCategory.tsx | 10 +- src/app/pages/client/home/Search.tsx | 3 +- src/app/pages/client/inbox/Inbox.tsx | 3 +- src/app/pages/client/inbox/Invites.tsx | 20 +- src/app/pages/client/inbox/Notifications.tsx | 15 +- src/app/pages/client/sidebar/CreateTab.tsx | 3 +- src/app/pages/client/sidebar/DirectTab.tsx | 3 +- src/app/pages/client/sidebar/ExploreTab.tsx | 3 +- src/app/pages/client/sidebar/HomeTab.tsx | 3 +- src/app/pages/client/sidebar/InboxTab.tsx | 3 +- src/app/pages/client/sidebar/SearchTab.tsx | 3 +- src/app/pages/client/sidebar/SettingsTab.tsx | 3 +- src/app/pages/client/sidebar/SpaceTabs.tsx | 16 +- .../pages/client/sidebar/UnverifiedTab.tsx | 3 +- src/app/pages/client/space/Search.tsx | 3 +- src/app/pages/client/space/Space.tsx | 31 +- src/app/plugins/react-custom-html-parser.tsx | 3 +- src/app/utils/common.ts | 3 +- src/app/utils/room.ts | 2 +- 221 files changed, 3472 insertions(+), 2128 deletions(-) create mode 100644 src/app/components/icons/Icon.tsx create mode 100644 src/app/components/icons/iconSizes.ts create mode 100644 src/app/components/icons/index.ts create mode 100644 src/app/components/icons/lucideIcons.ts create mode 100644 src/app/components/icons/style.css.ts create mode 100644 src/app/components/icons/types.ts create mode 100644 src/app/features/forum/ForumEventContent.tsx create mode 100644 src/app/features/forum/ForumMessageContextMenu.tsx create mode 100644 src/app/features/forum/ForumMessageMenuContext.tsx create mode 100644 src/app/features/forum/ForumPostUploadField.tsx create mode 100644 src/app/features/forum/ForumReplyComposer.tsx create mode 100644 src/app/features/forum/useForumMessageReactions.ts create mode 100644 src/app/features/forum/useForumMessageRenderOptions.ts create mode 100644 src/app/features/forum/useForumRoomLiveUpdates.ts diff --git a/src/app/components/AccountDataEditor.tsx b/src/app/components/AccountDataEditor.tsx index 2dbaf1f..3b9aec5 100644 --- a/src/app/components/AccountDataEditor.tsx +++ b/src/app/components/AccountDataEditor.tsx @@ -1,19 +1,6 @@ import React, { FormEventHandler, useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { - Box, - Text, - Icon, - Icons, - IconButton, - Input, - Button, - TextArea as TextAreaComponent, - color, - Spinner, - Chip, - Scroll, - config, -} from 'folds'; +import { Box, Text, IconButton, Input, Button, TextAreaComponent as TextArea, color, Spinner, Chip, Scroll, config } from 'folds'; +import { Icon, Icons } from './icons'; import { MatrixError } from 'matrix-js-sdk'; import { Cursor } from '../plugins/text-area'; import { syntaxErrorPosition } from '../utils/dom'; diff --git a/src/app/components/BackupRestore.tsx b/src/app/components/BackupRestore.tsx index 068f437..a9f92d2 100644 --- a/src/app/components/BackupRestore.tsx +++ b/src/app/components/BackupRestore.tsx @@ -1,23 +1,8 @@ import React, { MouseEventHandler, useCallback, useState } from 'react'; import { useAtom } from 'jotai'; import { CryptoApi, KeyBackupInfo } from 'matrix-js-sdk/lib/crypto-api'; -import { - Badge, - Box, - Button, - color, - config, - Icon, - IconButton, - Icons, - Menu, - percent, - PopOut, - ProgressBar, - RectCords, - Spinner, - Text, -} from 'folds'; +import { Badge, Box, Button, color, config, IconButton, Menu, percent, PopOut, ProgressBar, RectCords, Spinner, Text } from 'folds'; +import { Icon, Icons } from './icons'; import FocusTrap from 'focus-trap-react'; import { BackupProgressStatus, backupRestoreProgressAtom } from '../state/backupRestore'; import { InfoCard } from './info-card'; diff --git a/src/app/components/DeviceVerification.tsx b/src/app/components/DeviceVerification.tsx index d2502cc..75f2779 100644 --- a/src/app/components/DeviceVerification.tsx +++ b/src/app/components/DeviceVerification.tsx @@ -6,21 +6,8 @@ import { } from 'matrix-js-sdk/lib/crypto-api'; import React, { CSSProperties, useCallback, useEffect, useState } from 'react'; import { VerificationMethod } from 'matrix-js-sdk/lib/types'; -import { - Box, - Button, - config, - Dialog, - Header, - Icon, - IconButton, - Icons, - Overlay, - OverlayBackdrop, - OverlayCenter, - Spinner, - Text, -} from 'folds'; +import { Box, Button, config, Dialog, Header, IconButton, Overlay, OverlayBackdrop, OverlayCenter, Spinner, Text } from 'folds'; +import { Icon, Icons } from './icons'; import FocusTrap from 'focus-trap-react'; import { useVerificationRequestPhase, diff --git a/src/app/components/DeviceVerificationSetup.tsx b/src/app/components/DeviceVerificationSetup.tsx index 433fa6a..a2203a5 100644 --- a/src/app/components/DeviceVerificationSetup.tsx +++ b/src/app/components/DeviceVerificationSetup.tsx @@ -1,18 +1,6 @@ import React, { FormEventHandler, forwardRef, useCallback, useState } from 'react'; -import { - Dialog, - Header, - Box, - Text, - IconButton, - Icon, - Icons, - config, - Button, - Chip, - color, - Spinner, -} from 'folds'; +import { Dialog, Header, Box, Text, IconButton, config, Button, Chip, color, Spinner } from 'folds'; +import { Icon, Icons } from './icons'; import FileSaver from 'file-saver'; import to from 'await-to-js'; import { AuthDict, IAuthData, MatrixError, UIAuthCallback } from 'matrix-js-sdk'; diff --git a/src/app/components/JoinRulesSwitcher.tsx b/src/app/components/JoinRulesSwitcher.tsx index 9507317..878b609 100644 --- a/src/app/components/JoinRulesSwitcher.tsx +++ b/src/app/components/JoinRulesSwitcher.tsx @@ -1,18 +1,6 @@ import React, { MouseEventHandler, useCallback, useMemo, useState } from 'react'; -import { - config, - Box, - MenuItem, - Text, - Icon, - Icons, - IconSrc, - RectCords, - PopOut, - Menu, - Button, - Spinner, -} from 'folds'; +import { config, Box, MenuItem, Text, RectCords, PopOut, Menu, Button, Spinner } from 'folds'; +import { Icon, Icons, IconSrc } from './icons'; import { JoinRule } from 'matrix-js-sdk'; import FocusTrap from 'focus-trap-react'; import { stopPropagation } from '../utils/keyboard'; diff --git a/src/app/components/ManualVerification.tsx b/src/app/components/ManualVerification.tsx index f7cde92..a505d5d 100644 --- a/src/app/components/ManualVerification.tsx +++ b/src/app/components/ManualVerification.tsx @@ -1,17 +1,6 @@ import React, { MouseEventHandler, ReactNode, useCallback, useState } from 'react'; -import { - Box, - Text, - Chip, - Icon, - Icons, - RectCords, - PopOut, - Menu, - config, - MenuItem, - color, -} from 'folds'; +import { Box, Text, Chip, RectCords, PopOut, Menu, config, MenuItem, color } from 'folds'; +import { Icon, Icons } from './icons'; import FocusTrap from 'focus-trap-react'; import { stopPropagation } from '../utils/keyboard'; import { SettingTile } from './setting-tile'; diff --git a/src/app/components/Pdf-viewer/PdfViewer.tsx b/src/app/components/Pdf-viewer/PdfViewer.tsx index 9c7fd98..81d89d0 100644 --- a/src/app/components/Pdf-viewer/PdfViewer.tsx +++ b/src/app/components/Pdf-viewer/PdfViewer.tsx @@ -2,24 +2,8 @@ /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ import React, { FormEventHandler, MouseEventHandler, useEffect, useRef, useState } from 'react'; import classNames from 'classnames'; -import { - Box, - Button, - Chip, - Header, - Icon, - IconButton, - Icons, - Input, - Menu, - PopOut, - RectCords, - Scroll, - Spinner, - Text, - as, - config, -} from 'folds'; +import { Box, Button, Chip, Header, IconButton, Input, Menu, PopOut, RectCords, Scroll, Spinner, Text, as, config } from 'folds'; +import { Icon, Icons } from '../icons'; import FocusTrap from 'focus-trap-react'; import FileSaver from 'file-saver'; import * as css from './PdfViewer.css'; diff --git a/src/app/components/RoomNotificationSwitcher.tsx b/src/app/components/RoomNotificationSwitcher.tsx index 7ce9156..5f3a290 100644 --- a/src/app/components/RoomNotificationSwitcher.tsx +++ b/src/app/components/RoomNotificationSwitcher.tsx @@ -1,4 +1,5 @@ -import { Box, config, Icon, Menu, MenuItem, PopOut, RectCords, Text } from 'folds'; +import { Box, config, Menu, MenuItem, PopOut, RectCords, Text } from 'folds'; +import { Icon } from './icons'; import React, { MouseEventHandler, ReactNode, useMemo, useState } from 'react'; import FocusTrap from 'focus-trap-react'; import { stopPropagation } from '../utils/keyboard'; diff --git a/src/app/components/UIAFlowOverlay.tsx b/src/app/components/UIAFlowOverlay.tsx index a2bdd1b..8b692ae 100644 --- a/src/app/components/UIAFlowOverlay.tsx +++ b/src/app/components/UIAFlowOverlay.tsx @@ -1,17 +1,6 @@ import React, { ReactNode } from 'react'; -import { - Overlay, - OverlayBackdrop, - Box, - config, - Text, - TooltipProvider, - Tooltip, - Icons, - Icon, - Chip, - IconButton, -} from 'folds'; +import { Overlay, OverlayBackdrop, Box, config, Text, TooltipProvider, Tooltip, Chip, IconButton } from 'folds'; +import { Icons, Icon } from './icons'; import FocusTrap from 'focus-trap-react'; export type UIAFlowOverlayProps = { diff --git a/src/app/components/account-switcher/AccountSwitcher.tsx b/src/app/components/account-switcher/AccountSwitcher.tsx index 03a649c..5ed2e51 100644 --- a/src/app/components/account-switcher/AccountSwitcher.tsx +++ b/src/app/components/account-switcher/AccountSwitcher.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from 'react'; -import { Avatar, Box, Icon, Icons, MenuItem, Text, config } from 'folds'; +import { Avatar, Box, MenuItem, Text, config } from 'folds'; +import { Icon, Icons } from '../icons'; import { useAtomValue } from 'jotai'; import { sessionsAtom, Session } from '../../state/sessions'; import { setLocalStorageItem } from '../../state/utils/atomWithLocalStorage'; diff --git a/src/app/components/create-room/AdditionalCreatorInput.tsx b/src/app/components/create-room/AdditionalCreatorInput.tsx index 84b9223..1b1d21a 100644 --- a/src/app/components/create-room/AdditionalCreatorInput.tsx +++ b/src/app/components/create-room/AdditionalCreatorInput.tsx @@ -1,20 +1,5 @@ -import { - Box, - Button, - Chip, - config, - Icon, - Icons, - Input, - Line, - Menu, - MenuItem, - PopOut, - RectCords, - Scroll, - Text, - toRem, -} from 'folds'; +import { Box, Button, Chip, config, Input, Line, Menu, MenuItem, PopOut, RectCords, Scroll, Text, toRem } from 'folds'; +import { Icon, Icons } from '../icons'; import { isKeyHotkey } from 'is-hotkey'; import FocusTrap from 'focus-trap-react'; import React, { diff --git a/src/app/components/create-room/CreateRoomAliasInput.tsx b/src/app/components/create-room/CreateRoomAliasInput.tsx index e84658c..6a958d3 100644 --- a/src/app/components/create-room/CreateRoomAliasInput.tsx +++ b/src/app/components/create-room/CreateRoomAliasInput.tsx @@ -7,7 +7,8 @@ import React, { useState, } from 'react'; import { MatrixError } from 'matrix-js-sdk'; -import { Box, color, Icon, Icons, Input, Spinner, Text, toRem } from 'folds'; +import { Box, color, Input, Spinner, Text, toRem } from 'folds'; +import { Icon, Icons } from '../icons'; import { isKeyHotkey } from 'is-hotkey'; import { getMxIdServer } from '../../utils/matrix'; import { useMatrixClient } from '../../hooks/useMatrixClient'; diff --git a/src/app/components/create-room/CreateRoomKindSelector.tsx b/src/app/components/create-room/CreateRoomKindSelector.tsx index 096954f..cd5f8da 100644 --- a/src/app/components/create-room/CreateRoomKindSelector.tsx +++ b/src/app/components/create-room/CreateRoomKindSelector.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { Box, Text, Icon, Icons, config, IconSrc } from 'folds'; +import { Box, Text, config } from 'folds'; +import { Icon, Icons, IconSrc } from '../icons'; import { SequenceCard } from '../sequence-card'; import { SettingTile } from '../setting-tile'; diff --git a/src/app/components/create-room/RoomVersionSelector.tsx b/src/app/components/create-room/RoomVersionSelector.tsx index 219ded0..aa443c1 100644 --- a/src/app/components/create-room/RoomVersionSelector.tsx +++ b/src/app/components/create-room/RoomVersionSelector.tsx @@ -1,17 +1,6 @@ import React, { MouseEventHandler, useState } from 'react'; -import { - Box, - Button, - Chip, - config, - Icon, - Icons, - Menu, - PopOut, - RectCords, - Text, - toRem, -} from 'folds'; +import { Box, Button, Chip, config, Menu, PopOut, RectCords, Text, toRem } from 'folds'; +import { Icon, Icons } from '../icons'; import FocusTrap from 'focus-trap-react'; import { SettingTile } from '../setting-tile'; import { SequenceCard } from '../sequence-card'; diff --git a/src/app/components/direct-list/DirectList.tsx b/src/app/components/direct-list/DirectList.tsx index c7f5e16..1038489 100644 --- a/src/app/components/direct-list/DirectList.tsx +++ b/src/app/components/direct-list/DirectList.tsx @@ -1,20 +1,7 @@ import React, { MouseEventHandler, forwardRef, useMemo, useState } from 'react'; import { useAtom, useAtomValue } from 'jotai'; -import { - Avatar, - Box, - Button, - Icon, - IconButton, - Icons, - Menu, - MenuItem, - PopOut, - RectCords, - Text, - config, - toRem, -} from 'folds'; +import { Avatar, Box, Button, IconButton, Menu, MenuItem, PopOut, RectCords, Text, config, toRem } from 'folds'; +import { Icon, Icons } from '../icons'; import { useVirtualizer } from '@tanstack/react-virtual'; import FocusTrap from 'focus-trap-react'; import { useNavigate } from 'react-router-dom'; diff --git a/src/app/components/editor/Editor.preview.tsx b/src/app/components/editor/Editor.preview.tsx index b760ddd..a9fd81b 100644 --- a/src/app/components/editor/Editor.preview.tsx +++ b/src/app/components/editor/Editor.preview.tsx @@ -1,17 +1,8 @@ import React, { useState } from 'react'; import FocusTrap from 'focus-trap-react'; -import { - config, - Icon, - IconButton, - Icons, - Line, - Modal, - Overlay, - OverlayBackdrop, - OverlayCenter, -} from 'folds'; +import { config, IconButton, Line, Modal, Overlay, OverlayBackdrop, OverlayCenter } from 'folds'; +import { Icon, Icons } from '../icons'; import { CustomEditor, useEditor } from './Editor'; import { Toolbar } from './Toolbar'; import { stopPropagation } from '../../utils/keyboard'; diff --git a/src/app/components/editor/Toolbar.tsx b/src/app/components/editor/Toolbar.tsx index 7d701c4..6f7f27e 100644 --- a/src/app/components/editor/Toolbar.tsx +++ b/src/app/components/editor/Toolbar.tsx @@ -1,22 +1,6 @@ import FocusTrap from 'focus-trap-react'; -import { - Badge, - Box, - config, - Icon, - IconButton, - Icons, - IconSrc, - Line, - Menu, - PopOut, - RectCords, - Scroll, - Text, - Tooltip, - TooltipProvider, - toRem, -} from 'folds'; +import { Badge, Box, config, IconButton, Line, Menu, PopOut, RectCords, Scroll, Text, Tooltip, TooltipProvider, toRem } from 'folds'; +import { Icon, Icons, IconSrc } from '../icons'; import React, { MouseEventHandler, ReactNode, useState } from 'react'; import { ReactEditor, useSlate } from 'slate-react'; import { diff --git a/src/app/components/editor/autocomplete/RoomMentionAutocomplete.tsx b/src/app/components/editor/autocomplete/RoomMentionAutocomplete.tsx index 4a2b66f..29c44d7 100644 --- a/src/app/components/editor/autocomplete/RoomMentionAutocomplete.tsx +++ b/src/app/components/editor/autocomplete/RoomMentionAutocomplete.tsx @@ -1,6 +1,7 @@ import React, { KeyboardEvent as ReactKeyboardEvent, useCallback, useEffect } from 'react'; import { Editor } from 'slate'; -import { Avatar, Icon, Icons, MenuItem, Text } from 'folds'; +import { Avatar, MenuItem, Text } from 'folds'; +import { Icon, Icons } from '../../icons'; import { JoinRule, MatrixClient } from 'matrix-js-sdk'; import { useAtomValue } from 'jotai'; diff --git a/src/app/components/editor/autocomplete/UserMentionAutocomplete.tsx b/src/app/components/editor/autocomplete/UserMentionAutocomplete.tsx index 7a8012e..014fe44 100644 --- a/src/app/components/editor/autocomplete/UserMentionAutocomplete.tsx +++ b/src/app/components/editor/autocomplete/UserMentionAutocomplete.tsx @@ -1,6 +1,7 @@ import React, { useEffect, KeyboardEvent as ReactKeyboardEvent } from 'react'; import { Editor } from 'slate'; -import { Avatar, Icon, Icons, MenuItem, Text } from 'folds'; +import { Avatar, MenuItem, Text } from 'folds'; +import { Icon, Icons } from '../../icons'; import { MatrixClient, Room, RoomMember } from 'matrix-js-sdk'; import { AutocompleteQuery } from './autocompleteQuery'; diff --git a/src/app/components/emoji-board/EmojiBoard.tsx b/src/app/components/emoji-board/EmojiBoard.tsx index 30da8c7..0778fdd 100644 --- a/src/app/components/emoji-board/EmojiBoard.tsx +++ b/src/app/components/emoji-board/EmojiBoard.tsx @@ -9,7 +9,8 @@ import React, { useMemo, useRef, } from 'react'; -import { Box, config, Icons, Scroll } from 'folds'; +import { Box, config, Scroll } from 'folds'; +import { Icons } from '../icons'; import FocusTrap from 'focus-trap-react'; import { isKeyHotkey } from 'is-hotkey'; import { Room } from 'matrix-js-sdk'; diff --git a/src/app/components/emoji-board/components/NoStickerPacks.tsx b/src/app/components/emoji-board/components/NoStickerPacks.tsx index 6703362..484751a 100644 --- a/src/app/components/emoji-board/components/NoStickerPacks.tsx +++ b/src/app/components/emoji-board/components/NoStickerPacks.tsx @@ -1,6 +1,7 @@ import React from 'react'; -import { Box, toRem, config, Icons, Icon, Text } from 'folds'; +import { Box, toRem, config, Text } from 'folds'; +import { Icons, Icon } from '../../icons'; export function NoStickerPacks() { return ( , 'fill'>; + +export const Icon = forwardRef( + ({ className, size = '400', filled = false, src: LucideIconComponent, style, fill, ...props }, ref) => { + const pixelSize = ICON_PIXEL_SIZES[size]; + + return ( + + ); + } +); + +Icon.displayName = 'Icon'; diff --git a/src/app/components/icons/iconSizes.ts b/src/app/components/icons/iconSizes.ts new file mode 100644 index 0000000..4220c9c --- /dev/null +++ b/src/app/components/icons/iconSizes.ts @@ -0,0 +1,12 @@ +import type { IconSize } from './types'; + +export const ICON_PIXEL_SIZES: Record = { + '50': 10, + '100': 12, + '200': 16, + '300': 20, + '400': 24, + '500': 28, + '600': 32, + Inherit: undefined, +}; diff --git a/src/app/components/icons/index.ts b/src/app/components/icons/index.ts new file mode 100644 index 0000000..9d3894a --- /dev/null +++ b/src/app/components/icons/index.ts @@ -0,0 +1,4 @@ +export { Icon } from './Icon'; +export type { IconProps } from './Icon'; +export { Icons } from './lucideIcons'; +export type { IconName, IconSrc, IconSize } from './types'; diff --git a/src/app/components/icons/lucideIcons.ts b/src/app/components/icons/lucideIcons.ts new file mode 100644 index 0000000..fc8cbda --- /dev/null +++ b/src/app/components/icons/lucideIcons.ts @@ -0,0 +1,238 @@ +import { + ArrowDown, + ArrowLeft, + ArrowRight, + ArrowUp, + ArrowUpDown, + AtSign, + Ban, + Bell, + BellDot, + BellOff, + BellRing, + Bold, + Bookmark, + Boxes, + CaseSensitive, + Check, + CheckCheck, + ChevronDown, + ChevronLeft, + ChevronRight, + ChevronUp, + Circle, + CirclePlus, + Clock, + Code, + Code2, + Compass, + CornerUpLeft, + CornerUpRight, + Download, + ExternalLink, + Eye, + EyeOff, + File, + FileText, + Filter, + Flag, + Globe, + Hash, + Heading1, + Heading2, + Heading3, + Headphones, + Heart, + History, + Home, + Image, + ImagePlay, + Inbox, + Info, + Italic, + Leaf, + Lightbulb, + Link, + List, + ListOrdered, + Lock, + LogOut, + Mail, + MailPlus, + MessageSquare, + MessageSquareDot, + MessageSquarePlus, + MessagesSquare, + Mic, + MicOff, + Minus, + Monitor, + MoreHorizontal, + MoreVertical, + Paperclip, + Pause, + Pencil, + Phone, + Pin, + Play, + Plus, + Power, + Quote, + RefreshCw, + Reply, + Search, + Send, + Server, + Settings, + Shield, + ShieldUser, + Smile, + SmilePlus, + Sparkles, + Star, + Strikethrough, + Sun, + Terminal, + Trash2, + TriangleAlert, + Trophy, + Type, + Underline, + User, + UserPlus, + Video, + VideoOff, + Volume2, + VolumeX, + X, +} from 'lucide-react'; + +export const Icons = { + Alphabet: CaseSensitive, + AlphabetUnderline: Underline, + ArrowBottom: ArrowDown, + ArrowDropBottom: ChevronDown, + ArrowDropLeft: ChevronLeft, + ArrowDropRight: ChevronRight, + ArrowDropTop: ChevronUp, + ArrowGoLeft: CornerUpLeft, + ArrowGoRight: CornerUpRight, + ArrowGoRightCross: LogOut, + ArrowGoRightPlus: ExternalLink, + ArrowLeft, + ArrowRight, + ArrowTop: ArrowUp, + ArrowUpDown, + Attachment: Paperclip, + Ball: Circle, + Bell, + BellMute: BellOff, + BellPing: BellDot, + BellRing, + BlockCode: Code, + BlockQuote: Quote, + Bold, + Bookmark, + Bulb: Lightbulb, + Category: Boxes, + Check, + CheckTwice: CheckCheck, + ChevronBottom: ChevronDown, + ChevronLeft, + ChevronRight, + ChevronTop: ChevronUp, + Clock, + Code: Code2, + Cross: X, + Cup: Trophy, + Delete: Trash2, + Download, + Explore: Compass, + External: ExternalLink, + Eye, + EyeBlind: EyeOff, + File, + Filter, + Flag, + Funnel: Filter, + Hash, + HashGlobe: Globe, + HashLock: Lock, + HashPlus: CirclePlus, + HashSearch: Search, + Heading1, + Heading2, + Heading3, + Headphone: Headphones, + Heart, + Home, + HorizontalDots: MoreHorizontal, + Inbox, + Info, + Italic, + Leaf, + Link, + Lock, + Mail, + MailPlus, + Markdown: FileText, + Mention: AtSign, + Message: MessageSquare, + MessageUnread: MessageSquareDot, + Mic, + MicMute: MicOff, + Minus, + Monitor, + NoEntry: Ban, + OrderList: ListOrdered, + Pause, + Peace: Sparkles, + Pencil, + Phone, + Photo: Image, + Pin, + Play, + Plus, + PlusCircle: CirclePlus, + Power, + Prohibited: Ban, + RecentClock: History, + Reload: RefreshCw, + ReplyArrow: Reply, + Search, + Send, + Server, + Setting: Settings, + Shield, + ShieldLock: Lock, + ShieldUser, + Smile, + SmilePlus, + Sort: ArrowUpDown, + Space: Boxes, + SpaceGlobe: Globe, + SpaceLock: Lock, + SpacePlus: Plus, + SpaceSearch: Search, + Star, + Sticker: Sparkles, + Strike: Strikethrough, + Sun, + Terminal, + Text: Type, + Thread: MessagesSquare, + ThreadPlus: MessageSquarePlus, + ThreadUnread: MessageSquareDot, + Underline, + UnorderList: List, + User, + UserPlus, + VerticalDots: MoreVertical, + VideoCamera: Video, + VideoCameraMute: VideoOff, + Vlc: Play, + VolumeHigh: Volume2, + VolumeMute: VolumeX, + Warning: TriangleAlert, + ImagePlay, +} as const; diff --git a/src/app/components/icons/style.css.ts b/src/app/components/icons/style.css.ts new file mode 100644 index 0000000..3ce3a01 --- /dev/null +++ b/src/app/components/icons/style.css.ts @@ -0,0 +1,25 @@ +import { recipe } from '@vanilla-extract/recipes'; +import { config } from 'folds'; + +export const Icon = recipe({ + base: { + display: 'inline-block', + flexShrink: 0, + verticalAlign: 'middle', + }, + variants: { + size: { + '50': { width: config.size.X50, height: config.size.X50 }, + '100': { width: config.size.X100, height: config.size.X100 }, + '200': { width: config.size.X200, height: config.size.X200 }, + '300': { width: config.size.X300, height: config.size.X300 }, + '400': { width: config.size.X400, height: config.size.X400 }, + '500': { width: config.size.X500, height: config.size.X500 }, + '600': { width: config.size.X600, height: config.size.X600 }, + Inherit: { width: '1em', height: '1em' }, + }, + }, + defaultVariants: { + size: '400', + }, +}); diff --git a/src/app/components/icons/types.ts b/src/app/components/icons/types.ts new file mode 100644 index 0000000..3135246 --- /dev/null +++ b/src/app/components/icons/types.ts @@ -0,0 +1,5 @@ +import type { LucideIcon } from 'lucide-react'; + +export type IconSrc = LucideIcon; +export type IconSize = '50' | '100' | '200' | '300' | '400' | '500' | '600' | 'Inherit'; +export type IconName = keyof typeof import('./lucideIcons').Icons; diff --git a/src/app/components/image-editor/ImageEditor.tsx b/src/app/components/image-editor/ImageEditor.tsx index 1ec474e..4655392 100644 --- a/src/app/components/image-editor/ImageEditor.tsx +++ b/src/app/components/image-editor/ImageEditor.tsx @@ -1,6 +1,7 @@ import React from 'react'; import classNames from 'classnames'; -import { Box, Chip, Header, Icon, IconButton, Icons, Text, as } from 'folds'; +import { Box, Chip, Header, IconButton, Text, as } from 'folds'; +import { Icon, Icons } from '../icons'; import * as css from './ImageEditor.css'; export type ImageEditorProps = { diff --git a/src/app/components/image-pack-view/ImagePackView.tsx b/src/app/components/image-pack-view/ImagePackView.tsx index ab81d50..1546f40 100644 --- a/src/app/components/image-pack-view/ImagePackView.tsx +++ b/src/app/components/image-pack-view/ImagePackView.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { Box, IconButton, Text, Icon, Icons, Scroll, Chip } from 'folds'; +import { Box, IconButton, Text, Scroll, Chip } from 'folds'; +import { Icon, Icons } from '../icons'; import { PackAddress } from '../../plugins/custom-emoji'; import { Page, PageHeader, PageContent } from '../page'; import { useMatrixClient } from '../../hooks/useMatrixClient'; diff --git a/src/app/components/image-pack-view/ImageTile.tsx b/src/app/components/image-pack-view/ImageTile.tsx index 37337ff..eeee3f8 100644 --- a/src/app/components/image-pack-view/ImageTile.tsx +++ b/src/app/components/image-pack-view/ImageTile.tsx @@ -1,5 +1,6 @@ import React, { FormEventHandler, ReactNode, useMemo, useState } from 'react'; -import { Badge, Box, Button, Chip, Icon, Icons, Input, Text } from 'folds'; +import { Badge, Box, Button, Chip, Input, Text } from 'folds'; +import { Icon, Icons } from '../icons'; import { UsageSwitcher, useUsageStr } from './UsageSwitcher'; import { mxcUrlToHttp } from '../../utils/matrix'; import * as css from './style.css'; diff --git a/src/app/components/image-pack-view/PackMeta.tsx b/src/app/components/image-pack-view/PackMeta.tsx index b4cb48b..9de5bde 100644 --- a/src/app/components/image-pack-view/PackMeta.tsx +++ b/src/app/components/image-pack-view/PackMeta.tsx @@ -1,16 +1,6 @@ import React, { FormEventHandler, useCallback, useMemo, useState } from 'react'; -import { - Box, - Text, - Avatar, - AvatarFallback, - Button, - Icon, - Icons, - Input, - TextArea, - Chip, -} from 'folds'; +import { Box, Text, Avatar, AvatarFallback, Button, Input, TextArea, Chip } from 'folds'; +import { Icon, Icons } from '../icons'; import Linkify from 'linkify-react'; import { mxcUrlToHttp } from '../../utils/matrix'; import { useMatrixClient } from '../../hooks/useMatrixClient'; diff --git a/src/app/components/image-pack-view/UsageSwitcher.tsx b/src/app/components/image-pack-view/UsageSwitcher.tsx index 1a1eb43..b8c805b 100644 --- a/src/app/components/image-pack-view/UsageSwitcher.tsx +++ b/src/app/components/image-pack-view/UsageSwitcher.tsx @@ -1,5 +1,6 @@ import React, { MouseEventHandler, useMemo, useState } from 'react'; -import { Box, Button, config, Icon, Icons, Menu, MenuItem, PopOut, RectCords, Text } from 'folds'; +import { Box, Button, config, Menu, MenuItem, PopOut, RectCords, Text } from 'folds'; +import { Icon, Icons } from '../icons'; import FocusTrap from 'focus-trap-react'; import { ImageUsage } from '../../plugins/custom-emoji'; import { stopPropagation } from '../../utils/keyboard'; diff --git a/src/app/components/image-viewer/ImageViewer.tsx b/src/app/components/image-viewer/ImageViewer.tsx index b9fc514..ee78d7b 100644 --- a/src/app/components/image-viewer/ImageViewer.tsx +++ b/src/app/components/image-viewer/ImageViewer.tsx @@ -2,7 +2,8 @@ import React from 'react'; import FileSaver from 'file-saver'; import classNames from 'classnames'; -import { Box, Chip, Header, Icon, IconButton, Icons, Text, as } from 'folds'; +import { Box, Chip, Header, IconButton, Text, as } from 'folds'; +import { Icon, Icons } from '../icons'; import * as css from './ImageViewer.css'; import { useZoom } from '../../hooks/useZoom'; import { usePan } from '../../hooks/usePan'; diff --git a/src/app/components/invite-user-prompt/InviteUserPrompt.tsx b/src/app/components/invite-user-prompt/InviteUserPrompt.tsx index ddac057..ab1284a 100644 --- a/src/app/components/invite-user-prompt/InviteUserPrompt.tsx +++ b/src/app/components/invite-user-prompt/InviteUserPrompt.tsx @@ -7,28 +7,8 @@ import React, { useRef, useState, } from 'react'; -import { - Overlay, - OverlayBackdrop, - OverlayCenter, - Box, - Header, - config, - Text, - IconButton, - Icon, - Icons, - Input, - Button, - Spinner, - color, - TextArea, - Dialog, - Menu, - toRem, - Scroll, - MenuItem, -} from 'folds'; +import { Overlay, OverlayBackdrop, OverlayCenter, Box, Header, config, Text, IconButton, Input, Button, Spinner, color, TextArea, Dialog, Menu, toRem, Scroll, MenuItem } from 'folds'; +import { Icon, Icons } from '../icons'; import { Room } from 'matrix-js-sdk'; import { isKeyHotkey } from 'is-hotkey'; import FocusTrap from 'focus-trap-react'; diff --git a/src/app/components/join-address-prompt/JoinAddressPrompt.tsx b/src/app/components/join-address-prompt/JoinAddressPrompt.tsx index 50a8941..4eff484 100644 --- a/src/app/components/join-address-prompt/JoinAddressPrompt.tsx +++ b/src/app/components/join-address-prompt/JoinAddressPrompt.tsx @@ -1,21 +1,7 @@ import React, { FormEventHandler, useState } from 'react'; import FocusTrap from 'focus-trap-react'; -import { - Dialog, - Overlay, - OverlayCenter, - OverlayBackdrop, - Header, - config, - Box, - Text, - IconButton, - Icon, - Icons, - Button, - Input, - color, -} from 'folds'; +import { Dialog, Overlay, OverlayCenter, OverlayBackdrop, Header, config, Box, Text, IconButton, Button, Input, color } from 'folds'; +import { Icon, Icons } from '../icons'; import { stopPropagation } from '../../utils/keyboard'; import { isRoomAlias, isRoomId } from '../../utils/matrix'; import { parseMatrixToRoom, parseMatrixToRoomEvent, testMatrixTo } from '../../plugins/matrix-to'; diff --git a/src/app/components/join-space-prompt/JoinSpacePrompt.tsx b/src/app/components/join-space-prompt/JoinSpacePrompt.tsx index 72157f1..631fb15 100644 --- a/src/app/components/join-space-prompt/JoinSpacePrompt.tsx +++ b/src/app/components/join-space-prompt/JoinSpacePrompt.tsx @@ -1,25 +1,7 @@ import React, { useMemo, useState } from 'react'; import FocusTrap from 'focus-trap-react'; -import { - Dialog, - Overlay, - OverlayCenter, - OverlayBackdrop, - Header, - config, - Box, - Text, - IconButton, - Icon, - Icons, - color, - Button, - Spinner, - Scroll, - Checkbox, - Avatar, - Badge, -} from 'folds'; +import { Dialog, Overlay, OverlayCenter, OverlayBackdrop, Header, config, Box, Text, IconButton, color, Button, Spinner, Scroll, Checkbox, Avatar, Badge } from 'folds'; +import { Icon, Icons } from '../icons'; import { IHierarchyRoom } from 'matrix-js-sdk/lib/@types/spaces'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { stopPropagation } from '../../utils/keyboard'; diff --git a/src/app/components/leave-room-prompt/LeaveRoomPrompt.tsx b/src/app/components/leave-room-prompt/LeaveRoomPrompt.tsx index 217491e..d2b4f22 100644 --- a/src/app/components/leave-room-prompt/LeaveRoomPrompt.tsx +++ b/src/app/components/leave-room-prompt/LeaveRoomPrompt.tsx @@ -1,21 +1,7 @@ import React, { useCallback, useEffect } from 'react'; import FocusTrap from 'focus-trap-react'; -import { - Dialog, - Overlay, - OverlayCenter, - OverlayBackdrop, - Header, - config, - Box, - Text, - IconButton, - Icon, - Icons, - color, - Button, - Spinner, -} from 'folds'; +import { Dialog, Overlay, OverlayCenter, OverlayBackdrop, Header, config, Box, Text, IconButton, color, Button, Spinner } from 'folds'; +import { Icon, Icons } from '../icons'; import { MatrixError } from 'matrix-js-sdk'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback'; diff --git a/src/app/components/leave-space-prompt/LeaveSpacePrompt.tsx b/src/app/components/leave-space-prompt/LeaveSpacePrompt.tsx index 950b3cd..27e09a6 100644 --- a/src/app/components/leave-space-prompt/LeaveSpacePrompt.tsx +++ b/src/app/components/leave-space-prompt/LeaveSpacePrompt.tsx @@ -1,21 +1,7 @@ import React, { useCallback, useEffect, useState } from 'react'; import FocusTrap from 'focus-trap-react'; -import { - Dialog, - Overlay, - OverlayCenter, - OverlayBackdrop, - Header, - config, - Box, - Text, - IconButton, - Icon, - Icons, - color, - Button, - Spinner, -} from 'folds'; +import { Dialog, Overlay, OverlayCenter, OverlayBackdrop, Header, config, Box, Text, IconButton, color, Button, Spinner } from 'folds'; +import { Icon, Icons } from '../icons'; import { MatrixError, Room } from 'matrix-js-sdk'; import { useAtomValue } from 'jotai'; import { useMatrixClient } from '../../hooks/useMatrixClient'; diff --git a/src/app/components/manage-rooms-prompt/ManageRoomsPrompt.tsx b/src/app/components/manage-rooms-prompt/ManageRoomsPrompt.tsx index ed20a05..6ce94bb 100644 --- a/src/app/components/manage-rooms-prompt/ManageRoomsPrompt.tsx +++ b/src/app/components/manage-rooms-prompt/ManageRoomsPrompt.tsx @@ -1,26 +1,7 @@ import React, { useMemo, useState, useCallback, useRef, useEffect, MouseEvent } from 'react'; import FocusTrap from 'focus-trap-react'; -import { - Dialog, - Overlay, - OverlayCenter, - OverlayBackdrop, - Header, - config, - Box, - Text, - IconButton, - Icon, - Icons, - color, - Button, - Spinner, - Scroll, - Checkbox, - Badge, - Line, - Avatar, -} from 'folds'; +import { Dialog, Overlay, OverlayCenter, OverlayBackdrop, Header, config, Box, Text, IconButton, color, Button, Spinner, Scroll, Checkbox, Badge, Line, Avatar } from 'folds'; +import { Icon, Icons } from '../icons'; import { Room } from 'matrix-js-sdk'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { stopPropagation } from '../../utils/keyboard'; diff --git a/src/app/components/media/Video.tsx b/src/app/components/media/Video.tsx index 2b829cc..8088b56 100644 --- a/src/app/components/media/Video.tsx +++ b/src/app/components/media/Video.tsx @@ -1,7 +1,8 @@ import React, { VideoHTMLAttributes, forwardRef, useRef, useState, useEffect } from 'react'; import classNames from 'classnames'; -import { Box, IconButton, Icon, Icons, Tooltip, TooltipProvider, Text } from 'folds'; +import { Box, IconButton, Tooltip, TooltipProvider, Text } from 'folds'; +import { Icon, Icons } from '../icons'; import * as css from './media.css'; import * as videoCss from './videoControls.css'; diff --git a/src/app/components/member-tile/MemberTile.tsx b/src/app/components/member-tile/MemberTile.tsx index d36d46c..974714f 100644 --- a/src/app/components/member-tile/MemberTile.tsx +++ b/src/app/components/member-tile/MemberTile.tsx @@ -1,5 +1,6 @@ import React, { ReactNode } from 'react'; -import { as, Avatar, Box, Icon, Icons, Text } from 'folds'; +import { as, Avatar, Box, Text } from 'folds'; +import { Icon, Icons } from '../icons'; import { MatrixClient, Room, RoomMember } from 'matrix-js-sdk'; import { getMemberDisplayName } from '../../utils/room'; import { getMxIdLocalPart } from '../../utils/matrix'; diff --git a/src/app/components/message/FileHeader.tsx b/src/app/components/message/FileHeader.tsx index ba1b8e5..f41d94e 100644 --- a/src/app/components/message/FileHeader.tsx +++ b/src/app/components/message/FileHeader.tsx @@ -1,4 +1,5 @@ -import { Badge, Box, Icon, IconButton, Icons, Spinner, Text, as, toRem } from 'folds'; +import { Badge, Box, IconButton, Spinner, Text, as, toRem } from 'folds'; +import { Icon, Icons } from '../icons'; import React, { ReactNode, useCallback } from 'react'; import { EncryptedAttachmentInfo } from 'browser-encrypt-attachment'; import FileSaver from 'file-saver'; diff --git a/src/app/components/message/MsgTypeRenderers.tsx b/src/app/components/message/MsgTypeRenderers.tsx index 73c030a..f86323e 100644 --- a/src/app/components/message/MsgTypeRenderers.tsx +++ b/src/app/components/message/MsgTypeRenderers.tsx @@ -1,5 +1,6 @@ import React, { CSSProperties, ReactNode } from 'react'; -import { Box, Chip, Icon, Icons, Text, toRem } from 'folds'; +import { Box, Chip, Text, toRem } from 'folds'; +import { Icon, Icons } from '../icons'; import { IContent } from 'matrix-js-sdk'; import { JUMBO_EMOJI_REG, URL_REG } from '../../utils/regex'; import { trimReplyFromBody } from '../../utils/room'; diff --git a/src/app/components/message/Reply.tsx b/src/app/components/message/Reply.tsx index 18d727d..fd8e99f 100644 --- a/src/app/components/message/Reply.tsx +++ b/src/app/components/message/Reply.tsx @@ -1,4 +1,5 @@ -import { Box, Icon, Icons, Text, as, color, toRem } from 'folds'; +import { Box, Text, as, color, toRem } from 'folds'; +import { Icon, Icons } from '../icons'; import { EventTimelineSet, Room } from 'matrix-js-sdk'; import React, { MouseEventHandler, ReactNode, useCallback, useMemo } from 'react'; import classNames from 'classnames'; diff --git a/src/app/components/message/content/AudioContent.tsx b/src/app/components/message/content/AudioContent.tsx index a3695d9..3bccb24 100644 --- a/src/app/components/message/content/AudioContent.tsx +++ b/src/app/components/message/content/AudioContent.tsx @@ -1,6 +1,7 @@ /* eslint-disable jsx-a11y/media-has-caption */ import React, { ReactNode, useCallback, useRef, useState } from 'react'; -import { Badge, Chip, Icon, IconButton, Icons, ProgressBar, Spinner, Text, toRem } from 'folds'; +import { Badge, Chip, IconButton, ProgressBar, Spinner, Text, toRem } from 'folds'; +import { Icon, Icons } from '../../icons'; import { EncryptedAttachmentInfo } from 'browser-encrypt-attachment'; import { Range } from 'react-range'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; diff --git a/src/app/components/message/content/EventContent.tsx b/src/app/components/message/content/EventContent.tsx index 130ba8c..43185cd 100644 --- a/src/app/components/message/content/EventContent.tsx +++ b/src/app/components/message/content/EventContent.tsx @@ -1,4 +1,5 @@ -import { Box, Icon, IconSrc } from 'folds'; +import { Box } from 'folds'; +import { Icon, IconSrc } from '../../icons'; import React, { ReactNode } from 'react'; import { BubbleLayout, CompactLayout, ModernLayout } from '..'; import { MessageLayout } from '../../../state/settings'; diff --git a/src/app/components/message/content/FallbackContent.tsx b/src/app/components/message/content/FallbackContent.tsx index 9edb967..ba73a1a 100644 --- a/src/app/components/message/content/FallbackContent.tsx +++ b/src/app/components/message/content/FallbackContent.tsx @@ -1,4 +1,5 @@ -import { Box, Icon, Icons, Text, as, color, config } from 'folds'; +import { Box, Text, as, color, config } from 'folds'; +import { Icon, Icons } from '../../icons'; import React from 'react'; const warningStyle = { color: color.Warning.Main, opacity: config.opacity.P300 }; diff --git a/src/app/components/message/content/FileContent.tsx b/src/app/components/message/content/FileContent.tsx index 4abd3de..72d1ca8 100644 --- a/src/app/components/message/content/FileContent.tsx +++ b/src/app/components/message/content/FileContent.tsx @@ -1,19 +1,6 @@ import React, { ReactNode, useCallback, useState } from 'react'; -import { - Box, - Button, - Icon, - Icons, - Modal, - Overlay, - OverlayBackdrop, - OverlayCenter, - Spinner, - Text, - Tooltip, - TooltipProvider, - as, -} from 'folds'; +import { Box, Button, Modal, Overlay, OverlayBackdrop, OverlayCenter, Spinner, Text, Tooltip, TooltipProvider, as } from 'folds'; +import { Icon, Icons } from '../../icons'; import FileSaver from 'file-saver'; import { EncryptedAttachmentInfo } from 'browser-encrypt-attachment'; import FocusTrap from 'focus-trap-react'; diff --git a/src/app/components/message/content/ImageContent.tsx b/src/app/components/message/content/ImageContent.tsx index 2569f73..14abf3a 100644 --- a/src/app/components/message/content/ImageContent.tsx +++ b/src/app/components/message/content/ImageContent.tsx @@ -1,21 +1,6 @@ import React, { ReactNode, useCallback, useEffect, useState } from 'react'; -import { - Badge, - Box, - Button, - Chip, - Icon, - Icons, - Modal, - Overlay, - OverlayBackdrop, - OverlayCenter, - Spinner, - Text, - Tooltip, - TooltipProvider, - as, -} from 'folds'; +import { Badge, Box, Button, Chip, Modal, Overlay, OverlayBackdrop, OverlayCenter, Spinner, Text, Tooltip, TooltipProvider, as } from 'folds'; +import { Icon, Icons } from '../../icons'; import classNames from 'classnames'; import { BlurhashCanvas } from 'react-blurhash'; import FocusTrap from 'focus-trap-react'; diff --git a/src/app/components/message/content/VideoContent.tsx b/src/app/components/message/content/VideoContent.tsx index 288fbc9..566ac54 100644 --- a/src/app/components/message/content/VideoContent.tsx +++ b/src/app/components/message/content/VideoContent.tsx @@ -1,21 +1,6 @@ import React, { ReactNode, useCallback, useEffect, useState } from 'react'; -import { - Badge, - Box, - Button, - Chip, - Icon, - Icons, - Modal, - Overlay, - OverlayBackdrop, - OverlayCenter, - Spinner, - Text, - Tooltip, - TooltipProvider, - as, -} from 'folds'; +import { Badge, Box, Button, Chip, Modal, Overlay, OverlayBackdrop, OverlayCenter, Spinner, Text, Tooltip, TooltipProvider, as } from 'folds'; +import { Icon, Icons } from '../../icons'; import classNames from 'classnames'; import { BlurhashCanvas } from 'react-blurhash'; import FocusTrap from 'focus-trap-react'; diff --git a/src/app/components/password-input/PasswordInput.tsx b/src/app/components/password-input/PasswordInput.tsx index 4ffe0d8..44c2e08 100644 --- a/src/app/components/password-input/PasswordInput.tsx +++ b/src/app/components/password-input/PasswordInput.tsx @@ -1,5 +1,6 @@ import React, { ComponentProps, forwardRef } from 'react'; -import { Icon, IconButton, Input, config, Icons } from 'folds'; +import { IconButton, Input, config } from 'folds'; +import { Icon, Icons } from '../icons'; import { UseStateProvider } from '../UseStateProvider'; type PasswordInputProps = Omit, 'type' | 'size'> & { diff --git a/src/app/components/room-avatar/RoomAvatar.tsx b/src/app/components/room-avatar/RoomAvatar.tsx index ddda8f7..a02c82f 100644 --- a/src/app/components/room-avatar/RoomAvatar.tsx +++ b/src/app/components/room-avatar/RoomAvatar.tsx @@ -1,5 +1,6 @@ import { JoinRule } from 'matrix-js-sdk'; -import { AvatarFallback, AvatarImage, Icon, Icons, color } from 'folds'; +import { AvatarFallback, AvatarImage, color } from 'folds'; +import { Icon, Icons } from '../icons'; import React, { ComponentProps, ReactEventHandler, ReactNode, forwardRef, useState } from 'react'; import * as css from './RoomAvatar.css'; import { joinRuleToIconSrc } from '../../utils/room'; diff --git a/src/app/components/room-card/RoomCard.tsx b/src/app/components/room-card/RoomCard.tsx index 3dbd525..c51dd33 100644 --- a/src/app/components/room-card/RoomCard.tsx +++ b/src/app/components/room-card/RoomCard.tsx @@ -1,22 +1,7 @@ import React, { ReactNode, useCallback, useRef, useState } from 'react'; import { MatrixError, Room } from 'matrix-js-sdk'; -import { - Avatar, - Badge, - Box, - Button, - Dialog, - Icon, - Icons, - Overlay, - OverlayBackdrop, - OverlayCenter, - Spinner, - Text, - as, - color, - config, -} from 'folds'; +import { Avatar, Badge, Box, Button, Dialog, Overlay, OverlayBackdrop, OverlayCenter, Spinner, Text, as, color, config } from 'folds'; +import { Icon, Icons } from '../icons'; import classNames from 'classnames'; import FocusTrap from 'focus-trap-react'; import * as css from './style.css'; diff --git a/src/app/components/room-topic-viewer/RoomTopicViewer.tsx b/src/app/components/room-topic-viewer/RoomTopicViewer.tsx index 5212877..1a3646e 100644 --- a/src/app/components/room-topic-viewer/RoomTopicViewer.tsx +++ b/src/app/components/room-topic-viewer/RoomTopicViewer.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { as, Box, Header, Icon, IconButton, Icons, Modal, Scroll, Text } from 'folds'; +import { as, Box, Header, IconButton, Modal, Scroll, Text } from 'folds'; +import { Icon, Icons } from '../icons'; import classNames from 'classnames'; import Linkify from 'linkify-react'; import * as css from './style.css'; diff --git a/src/app/components/text-viewer/TextViewer.tsx b/src/app/components/text-viewer/TextViewer.tsx index ec4ed0a..702e1ec 100644 --- a/src/app/components/text-viewer/TextViewer.tsx +++ b/src/app/components/text-viewer/TextViewer.tsx @@ -1,7 +1,8 @@ /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ import React, { ComponentProps, HTMLAttributes, Suspense, forwardRef, lazy } from 'react'; import classNames from 'classnames'; -import { Box, Chip, Header, Icon, IconButton, Icons, Scroll, Text, as } from 'folds'; +import { Box, Chip, Header, IconButton, Scroll, Text, as } from 'folds'; +import { Icon, Icons } from '../icons'; import { ErrorBoundary } from 'react-error-boundary'; import * as css from './TextViewer.css'; import { copyToClipboard } from '../../utils/dom'; diff --git a/src/app/components/title-bar/WindowControls.tsx b/src/app/components/title-bar/WindowControls.tsx index fbb62dc..6fe2b39 100644 --- a/src/app/components/title-bar/WindowControls.tsx +++ b/src/app/components/title-bar/WindowControls.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect } from 'react'; -import { Box, IconButton, Icon } from 'folds'; +import { Box, IconButton } from 'folds'; +import { Icon } from '../icons'; import { invoke } from '@tauri-apps/api/core'; import * as css from './WindowControls.css'; diff --git a/src/app/components/uia-stages/PasswordStage.tsx b/src/app/components/uia-stages/PasswordStage.tsx index 0164cae..8a3479c 100644 --- a/src/app/components/uia-stages/PasswordStage.tsx +++ b/src/app/components/uia-stages/PasswordStage.tsx @@ -1,4 +1,5 @@ -import { Box, Button, color, config, Dialog, Header, Icon, IconButton, Icons, Text } from 'folds'; +import { Box, Button, color, config, Dialog, Header, IconButton, Text } from 'folds'; +import { Icon, Icons } from '../icons'; import React, { FormEventHandler } from 'react'; import { AuthType } from 'matrix-js-sdk'; import { StageComponentProps } from './types'; diff --git a/src/app/components/uia-stages/SSOStage.tsx b/src/app/components/uia-stages/SSOStage.tsx index b937329..47a488a 100644 --- a/src/app/components/uia-stages/SSOStage.tsx +++ b/src/app/components/uia-stages/SSOStage.tsx @@ -1,5 +1,6 @@ import { openExternalUrl } from '../../utils/tauri'; -import { Box, Button, color, config, Dialog, Header, Icon, IconButton, Icons, Text } from 'folds'; +import { Box, Button, color, config, Dialog, Header, IconButton, Text } from 'folds'; +import { Icon, Icons } from '../icons'; import React, { useCallback, useEffect, useState } from 'react'; import { StageComponentProps } from './types'; diff --git a/src/app/components/upload-board/UploadBoard.tsx b/src/app/components/upload-board/UploadBoard.tsx index 42f3899..7458db2 100644 --- a/src/app/components/upload-board/UploadBoard.tsx +++ b/src/app/components/upload-board/UploadBoard.tsx @@ -1,5 +1,6 @@ import React, { MutableRefObject, ReactNode, useImperativeHandle, useRef } from 'react'; -import { Badge, Box, Chip, Header, Icon, Icons, Spinner, Text, as, percent } from 'folds'; +import { Badge, Box, Chip, Header, Spinner, Text, as, percent } from 'folds'; +import { Icon, Icons } from '../icons'; import classNames from 'classnames'; import { useAtomValue } from 'jotai'; diff --git a/src/app/components/upload-card/CompactUploadCardRenderer.tsx b/src/app/components/upload-card/CompactUploadCardRenderer.tsx index b9bada7..4911462 100644 --- a/src/app/components/upload-card/CompactUploadCardRenderer.tsx +++ b/src/app/components/upload-card/CompactUploadCardRenderer.tsx @@ -1,5 +1,6 @@ import React, { useEffect } from 'react'; -import { Chip, Icon, IconButton, Icons, Text, color } from 'folds'; +import { Chip, IconButton, Text, color } from 'folds'; +import { Icon, Icons } from '../icons'; import { UploadCard, UploadCardError, CompactUploadCardProgress } from './UploadCard'; import { TUploadAtom, UploadStatus, UploadSuccess, useBindUploadAtom } from '../../state/upload'; import { useMatrixClient } from '../../hooks/useMatrixClient'; diff --git a/src/app/components/upload-card/UploadCard.tsx b/src/app/components/upload-card/UploadCard.tsx index 1e2edb9..e0d0b8b 100644 --- a/src/app/components/upload-card/UploadCard.tsx +++ b/src/app/components/upload-card/UploadCard.tsx @@ -1,4 +1,5 @@ -import { Badge, Box, Icon, Icons, ProgressBar, Text, percent } from 'folds'; +import { Badge, Box, ProgressBar, Text, percent } from 'folds'; +import { Icon, Icons } from '../icons'; import React, { ReactNode, forwardRef } from 'react'; import * as css from './UploadCard.css'; diff --git a/src/app/components/upload-card/UploadCardRenderer.tsx b/src/app/components/upload-card/UploadCardRenderer.tsx index f5bc68e..fcf02a6 100644 --- a/src/app/components/upload-card/UploadCardRenderer.tsx +++ b/src/app/components/upload-card/UploadCardRenderer.tsx @@ -1,5 +1,6 @@ import React, { ReactNode, useEffect } from 'react'; -import { Box, Chip, Icon, IconButton, Icons, Text, color, config, toRem } from 'folds'; +import { Box, Chip, IconButton, Text, color, config, toRem } from 'folds'; +import { Icon, Icons } from '../icons'; import { UploadCard, UploadCardError, UploadCardProgress } from './UploadCard'; import { UploadStatus, UploadSuccess, useBindUploadAtom } from '../../state/upload'; import { useMatrixClient } from '../../hooks/useMatrixClient'; diff --git a/src/app/components/url-preview/UrlPreviewCard.tsx b/src/app/components/url-preview/UrlPreviewCard.tsx index 9c7245d..18a0e86 100644 --- a/src/app/components/url-preview/UrlPreviewCard.tsx +++ b/src/app/components/url-preview/UrlPreviewCard.tsx @@ -1,6 +1,7 @@ import React, { MouseEvent, useCallback, useEffect, useRef, useState } from 'react'; import { IPreviewUrlResponse } from 'matrix-js-sdk'; -import { Box, Icon, IconButton, Icons, Scroll, Spinner, Text, as, color, config } from 'folds'; +import { Box, IconButton, Scroll, Spinner, Text, as, color, config } from 'folds'; +import { Icon, Icons } from '../icons'; import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { UrlPreview, UrlPreviewContent, UrlPreviewDescription, UrlPreviewImg } from './UrlPreview'; diff --git a/src/app/components/url-preview/YouTubeEmbed.tsx b/src/app/components/url-preview/YouTubeEmbed.tsx index 0438a6e..67613bc 100644 --- a/src/app/components/url-preview/YouTubeEmbed.tsx +++ b/src/app/components/url-preview/YouTubeEmbed.tsx @@ -1,5 +1,6 @@ import React, { MouseEvent, useCallback, useEffect, useState } from 'react'; -import { Box, Icon, Icons, Spinner, Text, as } from 'folds'; +import { Box, Spinner, Text, as } from 'folds'; +import { Icon, Icons } from '../icons'; import * as css from './YouTubeEmbed.css'; import { getYouTubeStream, isYouTubeStreamingAvailable, openExternalUrl } from '../../utils/tauri'; diff --git a/src/app/components/user-profile/CreatorChip.tsx b/src/app/components/user-profile/CreatorChip.tsx index 191c353..f3cc736 100644 --- a/src/app/components/user-profile/CreatorChip.tsx +++ b/src/app/components/user-profile/CreatorChip.tsx @@ -1,4 +1,5 @@ -import { Chip, color, config, Icon, Icons, Menu, MenuItem, PopOut, RectCords, Text } from 'folds'; +import { Chip, color, config, Menu, MenuItem, PopOut, RectCords, Text } from 'folds'; +import { Icon, Icons } from '../icons'; import React, { MouseEventHandler, useState } from 'react'; import FocusTrap from 'focus-trap-react'; import { isKeyHotkey } from 'is-hotkey'; diff --git a/src/app/components/user-profile/PowerChip.tsx b/src/app/components/user-profile/PowerChip.tsx index 59b2cd7..7f04cfc 100644 --- a/src/app/components/user-profile/PowerChip.tsx +++ b/src/app/components/user-profile/PowerChip.tsx @@ -1,26 +1,5 @@ -import { - Box, - Button, - Chip, - color, - config, - Dialog, - Header, - Icon, - IconButton, - Icons, - Line, - Menu, - MenuItem, - Overlay, - OverlayBackdrop, - OverlayCenter, - PopOut, - RectCords, - Spinner, - Text, - toRem, -} from 'folds'; +import { Box, Button, Chip, color, config, Dialog, Header, IconButton, Line, Menu, MenuItem, Overlay, OverlayBackdrop, OverlayCenter, PopOut, RectCords, Spinner, Text, toRem } from 'folds'; +import { Icon, Icons } from '../icons'; import React, { MouseEventHandler, useCallback, useState } from 'react'; import FocusTrap from 'focus-trap-react'; import { isKeyHotkey } from 'is-hotkey'; diff --git a/src/app/components/user-profile/UserChips.tsx b/src/app/components/user-profile/UserChips.tsx index 029e971..e73d5a9 100644 --- a/src/app/components/user-profile/UserChips.tsx +++ b/src/app/components/user-profile/UserChips.tsx @@ -4,24 +4,8 @@ import { useNavigate } from 'react-router-dom'; import FocusTrap from 'focus-trap-react'; import { isKeyHotkey } from 'is-hotkey'; import { Room } from 'matrix-js-sdk'; -import { - PopOut, - Menu, - MenuItem, - color, - config, - Text, - Line, - Chip, - Icon, - Icons, - RectCords, - Spinner, - toRem, - Box, - Scroll, - Avatar, -} from 'folds'; +import { PopOut, Menu, MenuItem, color, config, Text, Line, Chip, RectCords, Spinner, toRem, Box, Scroll, Avatar } from 'folds'; +import { Icon, Icons } from '../icons'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { getMxIdServer } from '../../utils/matrix'; import { useCloseUserRoomProfile } from '../../state/hooks/userRoomProfile'; diff --git a/src/app/components/user-profile/UserHero.tsx b/src/app/components/user-profile/UserHero.tsx index f193ad7..3148c98 100644 --- a/src/app/components/user-profile/UserHero.tsx +++ b/src/app/components/user-profile/UserHero.tsx @@ -1,16 +1,6 @@ import React, { useState } from 'react'; -import { - Avatar, - Box, - Icon, - Icons, - Modal, - Overlay, - OverlayBackdrop, - OverlayCenter, - Text, - toRem, -} from 'folds'; +import { Avatar, Box, Modal, Overlay, OverlayBackdrop, OverlayCenter, Text, toRem } from 'folds'; +import { Icon, Icons } from '../icons'; import classNames from 'classnames'; import FocusTrap from 'focus-trap-react'; import * as css from './styles.css'; diff --git a/src/app/components/user-profile/UserModeration.tsx b/src/app/components/user-profile/UserModeration.tsx index 814bb5b..90fa652 100644 --- a/src/app/components/user-profile/UserModeration.tsx +++ b/src/app/components/user-profile/UserModeration.tsx @@ -1,4 +1,5 @@ -import { Box, Button, color, config, Icon, Icons, Spinner, Text, Input } from 'folds'; +import { Box, Button, color, config, Spinner, Text, Input } from 'folds'; +import { Icon, Icons } from '../icons'; import React, { useCallback, useRef } from 'react'; import { useRoom } from '../../hooks/useRoom'; import { CutoutCard } from '../cutout-card'; diff --git a/src/app/components/user-profile/UserRoomProfile.tsx b/src/app/components/user-profile/UserRoomProfile.tsx index ac27019..78af091 100644 --- a/src/app/components/user-profile/UserRoomProfile.tsx +++ b/src/app/components/user-profile/UserRoomProfile.tsx @@ -1,4 +1,5 @@ -import { Box, Button, color, config, Icon, Icons, Text, toRem } from 'folds'; +import { Box, Button, color, config, Text, toRem } from 'folds'; +import { Icon, Icons } from '../icons'; import React from 'react'; import { useNavigate } from 'react-router-dom'; import { UserHero, UserHeroName } from './UserHero'; diff --git a/src/app/components/video-viewer/VideoViewer.tsx b/src/app/components/video-viewer/VideoViewer.tsx index e136e0e..5b0e97f 100644 --- a/src/app/components/video-viewer/VideoViewer.tsx +++ b/src/app/components/video-viewer/VideoViewer.tsx @@ -1,7 +1,8 @@ import React from 'react'; import FileSaver from 'file-saver'; import classNames from 'classnames'; -import { Box, Chip, Header, Icon, IconButton, Icons, Text, as } from 'folds'; +import { Box, Chip, Header, IconButton, Text, as } from 'folds'; +import { Icon, Icons } from '../icons'; import * as css from './VideoViewer.css'; import { downloadMedia } from '../../utils/matrix'; import { getCurrentAccessToken } from '../../utils/auth'; diff --git a/src/app/features/ShareRoomPicker.tsx b/src/app/features/ShareRoomPicker.tsx index 27d5476..03ddfb0 100644 --- a/src/app/features/ShareRoomPicker.tsx +++ b/src/app/features/ShareRoomPicker.tsx @@ -2,24 +2,8 @@ import { useAtomValue } from 'jotai'; import React, { ChangeEventHandler, useCallback, useMemo, useRef, useState } from 'react'; import { useVirtualizer } from '@tanstack/react-virtual'; import FocusTrap from 'focus-trap-react'; -import { - Avatar, - Box, - Button, - Header, - Icon, - IconButton, - Icons, - Input, - MenuItem, - Modal, - Overlay, - OverlayBackdrop, - OverlayCenter, - Scroll, - Text, - config, -} from 'folds'; +import { Avatar, Box, Button, Header, IconButton, Input, MenuItem, Modal, Overlay, OverlayBackdrop, OverlayCenter, Scroll, Text, config } from 'folds'; +import { Icon, Icons } from '../components/icons'; import { useMatrixClient } from '../hooks/useMatrixClient'; import { useAllJoinedRoomsSet, useGetRoom } from '../hooks/useGetRoom'; import { allRoomsAtom } from '../state/room-list/roomList'; diff --git a/src/app/features/add-existing/AddExisting.tsx b/src/app/features/add-existing/AddExisting.tsx index cbae018..9bcfcee 100644 --- a/src/app/features/add-existing/AddExisting.tsx +++ b/src/app/features/add-existing/AddExisting.tsx @@ -1,24 +1,6 @@ import FocusTrap from 'focus-trap-react'; -import { - Avatar, - Box, - Button, - config, - Header, - Icon, - IconButton, - Icons, - Input, - Menu, - MenuItem, - Modal, - Overlay, - OverlayBackdrop, - OverlayCenter, - Scroll, - Spinner, - Text, -} from 'folds'; +import { Avatar, Box, Button, config, Header, IconButton, Input, Menu, MenuItem, Modal, Overlay, OverlayBackdrop, OverlayCenter, Scroll, Spinner, Text } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import React, { ChangeEventHandler, MouseEventHandler, diff --git a/src/app/features/call/CallOverlay.tsx b/src/app/features/call/CallOverlay.tsx index bbc0d2e..68728c2 100644 --- a/src/app/features/call/CallOverlay.tsx +++ b/src/app/features/call/CallOverlay.tsx @@ -1,5 +1,6 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { Box, Icon, IconButton, Icons, Text, Tooltip, TooltipProvider, color } from 'folds'; +import { Box, IconButton, Text, Tooltip, TooltipProvider, color } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import { useAtom } from 'jotai'; import { useCall } from './useCall'; import { useRoomCallMembers } from './useRoomCallMembers'; diff --git a/src/app/features/call/DockedCallContent.tsx b/src/app/features/call/DockedCallContent.tsx index 82573cf..0a4f513 100644 --- a/src/app/features/call/DockedCallContent.tsx +++ b/src/app/features/call/DockedCallContent.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useMemo, useState } from 'react'; -import { Box, Icon, IconButton, Icons, Text, Tooltip, TooltipProvider, Scroll, color } from 'folds'; +import { Box, IconButton, Text, Tooltip, TooltipProvider, Scroll, color } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import { useSetAtom } from 'jotai'; import { useCall } from './useCall'; import { ScreenShareItem, ScreenShareEntry } from './CallOverlay'; diff --git a/src/app/features/call/IncomingCallNotification.tsx b/src/app/features/call/IncomingCallNotification.tsx index fdb98c2..7a4a134 100644 --- a/src/app/features/call/IncomingCallNotification.tsx +++ b/src/app/features/call/IncomingCallNotification.tsx @@ -1,7 +1,8 @@ /* eslint-disable no-console */ import React, { useCallback, useEffect, useState } from 'react'; import { MatrixEvent, Room, RoomStateEvent } from 'matrix-js-sdk'; -import { Box, Icon, IconButton, Icons, Text, color } from 'folds'; +import { Box, IconButton, Text, color } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { useCall } from './useCall'; import { CallType } from './types'; diff --git a/src/app/features/call/SidebarDockedCallPanel.tsx b/src/app/features/call/SidebarDockedCallPanel.tsx index 6533cac..5f6d304 100644 --- a/src/app/features/call/SidebarDockedCallPanel.tsx +++ b/src/app/features/call/SidebarDockedCallPanel.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from 'react'; -import { Box, Icon, IconButton, Icons, Scroll, Text, Tooltip, TooltipProvider, color } from 'folds'; +import { Box, IconButton, Scroll, Text, Tooltip, TooltipProvider, color } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import { useSetAtom } from 'jotai'; import { useCall } from './useCall'; import { useRoomCallMembers } from './useRoomCallMembers'; diff --git a/src/app/features/common-settings/developer-tools/DevelopTools.tsx b/src/app/features/common-settings/developer-tools/DevelopTools.tsx index 9982e5f..c584bef 100644 --- a/src/app/features/common-settings/developer-tools/DevelopTools.tsx +++ b/src/app/features/common-settings/developer-tools/DevelopTools.tsx @@ -1,23 +1,6 @@ import React, { useCallback, useMemo, useState } from 'react'; -import { - Box, - Text, - IconButton, - Icon, - Icons, - Scroll, - Switch, - Button, - MenuItem, - config, - color, - TextArea, - Dialog, - Overlay, - OverlayBackdrop, - OverlayCenter, - Modal, -} from 'folds'; +import { Box, Text, IconButton, Scroll, Switch, Button, MenuItem, config, color, TextArea, Dialog, Overlay, OverlayBackdrop, OverlayCenter, Modal } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import FocusTrap from 'focus-trap-react'; import { Page, PageContent, PageHeader } from '../../../components/page'; import { SequenceCard } from '../../../components/sequence-card'; diff --git a/src/app/features/common-settings/developer-tools/SendRoomEvent.tsx b/src/app/features/common-settings/developer-tools/SendRoomEvent.tsx index f25ba7c..6dc1601 100644 --- a/src/app/features/common-settings/developer-tools/SendRoomEvent.tsx +++ b/src/app/features/common-settings/developer-tools/SendRoomEvent.tsx @@ -1,19 +1,7 @@ import React, { useCallback, useRef, useState, FormEventHandler, useEffect } from 'react'; import { MatrixError } from 'matrix-js-sdk'; -import { - Box, - Chip, - Icon, - Icons, - IconButton, - Text, - config, - Button, - Spinner, - color, - TextArea as TextAreaComponent, - Input, -} from 'folds'; +import { Box, Chip, IconButton, Text, config, Button, Spinner, color, TextAreaComponent as TextArea, Input } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { Page, PageHeader } from '../../../components/page'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useRoom } from '../../../hooks/useRoom'; diff --git a/src/app/features/common-settings/developer-tools/StateEventEditor.tsx b/src/app/features/common-settings/developer-tools/StateEventEditor.tsx index 0ea9690..2018d90 100644 --- a/src/app/features/common-settings/developer-tools/StateEventEditor.tsx +++ b/src/app/features/common-settings/developer-tools/StateEventEditor.tsx @@ -1,18 +1,6 @@ import React, { FormEventHandler, useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { - Box, - Text, - Icon, - Icons, - IconButton, - Chip, - Scroll, - config, - TextArea as TextAreaComponent, - color, - Spinner, - Button, -} from 'folds'; +import { Box, Text, IconButton, Chip, Scroll, config, TextAreaComponent as TextArea, color, Spinner, Button } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { MatrixError } from 'matrix-js-sdk'; import { Page, PageHeader } from '../../../components/page'; import { SequenceCard } from '../../../components/sequence-card'; diff --git a/src/app/features/common-settings/emojis-stickers/EmojisStickers.tsx b/src/app/features/common-settings/emojis-stickers/EmojisStickers.tsx index ad8ffae..15727c9 100644 --- a/src/app/features/common-settings/emojis-stickers/EmojisStickers.tsx +++ b/src/app/features/common-settings/emojis-stickers/EmojisStickers.tsx @@ -1,5 +1,6 @@ import React, { useState } from 'react'; -import { Box, Icon, IconButton, Icons, Scroll, Text } from 'folds'; +import { Box, IconButton, Scroll, Text } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { Page, PageContent, PageHeader } from '../../../components/page'; import { ImagePack } from '../../../plugins/custom-emoji'; import { ImagePackView } from '../../../components/image-pack-view'; diff --git a/src/app/features/common-settings/emojis-stickers/RoomPacks.tsx b/src/app/features/common-settings/emojis-stickers/RoomPacks.tsx index 8860280..92c88a0 100644 --- a/src/app/features/common-settings/emojis-stickers/RoomPacks.tsx +++ b/src/app/features/common-settings/emojis-stickers/RoomPacks.tsx @@ -1,20 +1,6 @@ import React, { FormEventHandler, useCallback, useMemo, useState } from 'react'; -import { - Box, - Text, - Button, - Icon, - Icons, - Avatar, - AvatarFallback, - toRem, - config, - Input, - Spinner, - color, - IconButton, - Menu, -} from 'folds'; +import { Box, Text, Button, Avatar, AvatarFallback, toRem, config, Input, Spinner, color, IconButton, Menu } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { MatrixError } from 'matrix-js-sdk'; import { SequenceCard } from '../../../components/sequence-card'; import { diff --git a/src/app/features/common-settings/general/EmbedFilters.tsx b/src/app/features/common-settings/general/EmbedFilters.tsx index 661b559..4d332be 100644 --- a/src/app/features/common-settings/general/EmbedFilters.tsx +++ b/src/app/features/common-settings/general/EmbedFilters.tsx @@ -1,18 +1,6 @@ import React, { FormEventHandler, useCallback, useState } from 'react'; -import { - Box, - Button, - Chip, - color, - config, - Icon, - Icons, - Input, - Spinner, - Text, - Tooltip, - TooltipProvider, -} from 'folds'; +import { Box, Button, Chip, color, config, Input, Spinner, Text, Tooltip, TooltipProvider } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { SequenceCard } from '../../../components/sequence-card'; import { SequenceCardStyle } from '../../room-settings/styles.css'; import { SettingTile } from '../../../components/setting-tile'; diff --git a/src/app/features/common-settings/general/RoomAddress.tsx b/src/app/features/common-settings/general/RoomAddress.tsx index 767a967..2f0bcd6 100644 --- a/src/app/features/common-settings/general/RoomAddress.tsx +++ b/src/app/features/common-settings/general/RoomAddress.tsx @@ -1,19 +1,6 @@ import React, { FormEventHandler, useCallback, useState } from 'react'; -import { - Badge, - Box, - Button, - Checkbox, - Chip, - color, - config, - Icon, - Icons, - Input, - Spinner, - Text, - toRem, -} from 'folds'; +import { Badge, Box, Button, Checkbox, Chip, color, config, Input, Spinner, Text, toRem } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { MatrixError } from 'matrix-js-sdk'; import { SettingTile } from '../../../components/setting-tile'; import { SequenceCard } from '../../../components/sequence-card'; diff --git a/src/app/features/common-settings/general/RoomEncryption.tsx b/src/app/features/common-settings/general/RoomEncryption.tsx index 15b1f15..219a30d 100644 --- a/src/app/features/common-settings/general/RoomEncryption.tsx +++ b/src/app/features/common-settings/general/RoomEncryption.tsx @@ -1,20 +1,5 @@ -import { - Badge, - Box, - Button, - color, - config, - Dialog, - Header, - Icon, - IconButton, - Icons, - Overlay, - OverlayBackdrop, - OverlayCenter, - Spinner, - Text, -} from 'folds'; +import { Badge, Box, Button, color, config, Dialog, Header, IconButton, Overlay, OverlayBackdrop, OverlayCenter, Spinner, Text } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import React, { useCallback, useState } from 'react'; import { MatrixError } from 'matrix-js-sdk'; import FocusTrap from 'focus-trap-react'; diff --git a/src/app/features/common-settings/general/RoomHistoryVisibility.tsx b/src/app/features/common-settings/general/RoomHistoryVisibility.tsx index 2e42785..766fdc9 100644 --- a/src/app/features/common-settings/general/RoomHistoryVisibility.tsx +++ b/src/app/features/common-settings/general/RoomHistoryVisibility.tsx @@ -1,17 +1,6 @@ import React, { MouseEventHandler, useCallback, useMemo, useState } from 'react'; -import { - Button, - color, - config, - Icon, - Icons, - Menu, - MenuItem, - PopOut, - RectCords, - Spinner, - Text, -} from 'folds'; +import { Button, color, config, Menu, MenuItem, PopOut, RectCords, Spinner, Text } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { HistoryVisibility, MatrixError } from 'matrix-js-sdk'; import { RoomHistoryVisibilityEventContent } from 'matrix-js-sdk/lib/types'; import FocusTrap from 'focus-trap-react'; diff --git a/src/app/features/common-settings/general/RoomProfile.tsx b/src/app/features/common-settings/general/RoomProfile.tsx index 0f515c3..406a191 100644 --- a/src/app/features/common-settings/general/RoomProfile.tsx +++ b/src/app/features/common-settings/general/RoomProfile.tsx @@ -1,16 +1,5 @@ -import { - Avatar, - Box, - Button, - Chip, - color, - Icon, - Icons, - Input, - Spinner, - Text, - TextArea, -} from 'folds'; +import { Avatar, Box, Button, Chip, color, Input, Spinner, Text, TextArea } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import React, { FormEventHandler, useCallback, useMemo, useState } from 'react'; import { useAtomValue } from 'jotai'; import Linkify from 'linkify-react'; diff --git a/src/app/features/common-settings/general/RoomUpgrade.tsx b/src/app/features/common-settings/general/RoomUpgrade.tsx index 45a480a..e23f5d1 100644 --- a/src/app/features/common-settings/general/RoomUpgrade.tsx +++ b/src/app/features/common-settings/general/RoomUpgrade.tsx @@ -1,20 +1,6 @@ import React, { useCallback, useEffect, useState } from 'react'; -import { - Button, - color, - Spinner, - Text, - Overlay, - OverlayBackdrop, - OverlayCenter, - Dialog, - Header, - config, - Box, - IconButton, - Icon, - Icons, -} from 'folds'; +import { Button, color, Spinner, Text, Overlay, OverlayBackdrop, OverlayCenter, Dialog, Header, config, Box, IconButton } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import FocusTrap from 'focus-trap-react'; import { MatrixError, Method } from 'matrix-js-sdk'; import { RoomTombstoneEventContent } from 'matrix-js-sdk/lib/types'; diff --git a/src/app/features/common-settings/members/Members.tsx b/src/app/features/common-settings/members/Members.tsx index 15cd1d0..c86dd15 100644 --- a/src/app/features/common-settings/members/Members.tsx +++ b/src/app/features/common-settings/members/Members.tsx @@ -6,21 +6,8 @@ import React, { useRef, useState, } from 'react'; -import { - Box, - Chip, - config, - Icon, - IconButton, - Icons, - Input, - PopOut, - RectCords, - Scroll, - Spinner, - Text, - toRem, -} from 'folds'; +import { Box, Chip, config, IconButton, Input, PopOut, RectCords, Scroll, Spinner, Text, toRem } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { useVirtualizer } from '@tanstack/react-virtual'; import { RoomMember } from 'matrix-js-sdk'; import { Page, PageContent, PageHeader } from '../../../components/page'; diff --git a/src/app/features/common-settings/permissions/PermissionGroups.tsx b/src/app/features/common-settings/permissions/PermissionGroups.tsx index 1215077..48d8b93 100644 --- a/src/app/features/common-settings/permissions/PermissionGroups.tsx +++ b/src/app/features/common-settings/permissions/PermissionGroups.tsx @@ -1,6 +1,7 @@ /* eslint-disable react/no-array-index-key */ import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { Badge, Box, Button, Chip, config, Icon, Icons, Menu, Spinner, Text } from 'folds'; +import { Badge, Box, Button, Chip, config, Menu, Spinner, Text } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import produce from 'immer'; import { SequenceCard } from '../../../components/sequence-card'; import { SequenceCardStyle } from '../styles.css'; diff --git a/src/app/features/common-settings/permissions/PowersEditor.tsx b/src/app/features/common-settings/permissions/PowersEditor.tsx index 9730594..a54421d 100644 --- a/src/app/features/common-settings/permissions/PowersEditor.tsx +++ b/src/app/features/common-settings/permissions/PowersEditor.tsx @@ -1,23 +1,6 @@ import React, { FormEventHandler, MouseEventHandler, useCallback, useMemo, useState } from 'react'; -import { - Box, - Text, - Chip, - Icon, - Icons, - IconButton, - Scroll, - Button, - Input, - RectCords, - PopOut, - Menu, - config, - Spinner, - toRem, - TooltipProvider, - Tooltip, -} from 'folds'; +import { Box, Text, Chip, IconButton, Scroll, Button, Input, RectCords, PopOut, Menu, config, Spinner, toRem, TooltipProvider, Tooltip } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { HexColorPicker } from 'react-colorful'; import { useAtomValue } from 'jotai'; import { Page, PageContent, PageHeader } from '../../../components/page'; diff --git a/src/app/features/create-chat/CreateChat.tsx b/src/app/features/create-chat/CreateChat.tsx index 92e20a0..553c779 100644 --- a/src/app/features/create-chat/CreateChat.tsx +++ b/src/app/features/create-chat/CreateChat.tsx @@ -1,4 +1,5 @@ -import { Box, Button, color, config, Icon, Icons, Input, Spinner, Switch, Text } from 'folds'; +import { Box, Button, color, config, Input, Spinner, Switch, Text } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import React, { FormEventHandler, useCallback, useState } from 'react'; import { ICreateRoomStateEvent, MatrixError, Preset, Visibility } from 'matrix-js-sdk'; import { useNavigate } from 'react-router-dom'; diff --git a/src/app/features/create-room/CreateRoom.tsx b/src/app/features/create-room/CreateRoom.tsx index 59a3a82..96a7d4f 100644 --- a/src/app/features/create-room/CreateRoom.tsx +++ b/src/app/features/create-room/CreateRoom.tsx @@ -1,19 +1,7 @@ import React, { FormEventHandler, useCallback, useEffect, useState } from 'react'; import { MatrixError, Room } from 'matrix-js-sdk'; -import { - Box, - Button, - Chip, - color, - config, - Icon, - Icons, - Input, - Spinner, - Switch, - Text, - TextArea, -} from 'folds'; +import { Box, Button, Chip, color, config, Input, Spinner, Switch, Text, TextArea } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import { SettingTile } from '../../components/setting-tile'; import { SequenceCard } from '../../components/sequence-card'; import { diff --git a/src/app/features/create-room/CreateRoomModal.tsx b/src/app/features/create-room/CreateRoomModal.tsx index a68b658..00f6c98 100644 --- a/src/app/features/create-room/CreateRoomModal.tsx +++ b/src/app/features/create-room/CreateRoomModal.tsx @@ -1,18 +1,6 @@ import React, { useCallback } from 'react'; -import { - Box, - config, - Header, - Icon, - IconButton, - Icons, - Modal, - Overlay, - OverlayBackdrop, - OverlayCenter, - Scroll, - Text, -} from 'folds'; +import { Box, config, Header, IconButton, Modal, Overlay, OverlayBackdrop, OverlayCenter, Scroll, Text } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import FocusTrap from 'focus-trap-react'; import { useAtomValue } from 'jotai'; import { useAllJoinedRoomsSet, useGetRoom } from '../../hooks/useGetRoom'; diff --git a/src/app/features/create-space/CreateSpace.tsx b/src/app/features/create-space/CreateSpace.tsx index 93ddc1f..ebb9a65 100644 --- a/src/app/features/create-space/CreateSpace.tsx +++ b/src/app/features/create-space/CreateSpace.tsx @@ -1,24 +1,8 @@ import React, { FormEventHandler, MouseEventHandler, useCallback, useEffect, useState } from 'react'; import { MatrixError, Room } from 'matrix-js-sdk'; import FocusTrap from 'focus-trap-react'; -import { - Box, - Button, - Chip, - color, - config, - Icon, - Icons, - Input, - Menu, - MenuItem, - PopOut, - RectCords, - Spinner, - Switch, - Text, - TextArea, -} from 'folds'; +import { Box, Button, Chip, color, config, Input, Menu, MenuItem, PopOut, RectCords, Spinner, Switch, Text, TextArea } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import { SettingTile } from '../../components/setting-tile'; import { SequenceCard } from '../../components/sequence-card'; import { diff --git a/src/app/features/create-space/CreateSpaceModal.tsx b/src/app/features/create-space/CreateSpaceModal.tsx index c1bc689..55e816b 100644 --- a/src/app/features/create-space/CreateSpaceModal.tsx +++ b/src/app/features/create-space/CreateSpaceModal.tsx @@ -1,18 +1,6 @@ import React from 'react'; -import { - Box, - config, - Header, - Icon, - IconButton, - Icons, - Modal, - Overlay, - OverlayBackdrop, - OverlayCenter, - Scroll, - Text, -} from 'folds'; +import { Box, config, Header, IconButton, Modal, Overlay, OverlayBackdrop, OverlayCenter, Scroll, Text } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import FocusTrap from 'focus-trap-react'; import { useAllJoinedRoomsSet, useGetRoom } from '../../hooks/useGetRoom'; import { SpaceProvider } from '../../hooks/useSpace'; diff --git a/src/app/features/forum/ForumAuthorIdentity.tsx b/src/app/features/forum/ForumAuthorIdentity.tsx index 6c14a3a..6734f5b 100644 --- a/src/app/features/forum/ForumAuthorIdentity.tsx +++ b/src/app/features/forum/ForumAuthorIdentity.tsx @@ -67,11 +67,7 @@ export function ForumAuthorIdentity({ {name} - {server ? ( - - {server} - - ) : null} + {server ? {server} : null} ); diff --git a/src/app/features/forum/ForumAwareSpaceLayout.tsx b/src/app/features/forum/ForumAwareSpaceLayout.tsx index a29a7f8..8dfa98c 100644 --- a/src/app/features/forum/ForumAwareSpaceLayout.tsx +++ b/src/app/features/forum/ForumAwareSpaceLayout.tsx @@ -1,44 +1,14 @@ -import React, { CSSProperties } from 'react'; -import { Outlet } from 'react-router-dom'; -import { Box } from 'folds'; -import classNames from 'classnames'; -import { useSpace } from '../../hooks/useSpace'; +import React from 'react'; import { PageRoot } from '../../components/page'; import { AnimatedOutlet } from '../../components/AnimatedOutlet'; import { MobileFriendlyPageNav } from '../../pages/MobileFriendly'; import { SPACE_PATH } from '../../pages/paths'; import { Space } from '../../pages/client/space/Space'; -import { shouldShowForumLobby } from '../../utils/room'; -import { ContainerColor } from '../../styles/ContainerColor.css'; -import * as theme from './forumTheme.css'; - -const forumOutletShell: CSSProperties = { - display: 'flex', - flex: 1, - flexDirection: 'column', - minHeight: 0, - minWidth: 0, - overflow: 'hidden', -}; /** - * Forum: server list only — forum fills main pane. - * Other spaces: space sidebar + outlet. + * Space sidebar + outlet. Forum spaces render ForumFeedSidebar in Space.tsx. */ export function ForumAwareSpaceLayout() { - const space = useSpace(); - const isForum = shouldShowForumLobby(space); - - if (isForum) { - return ( - -
- -
-
- ); - } - return ( (null); + const [optimisticPost, setOptimisticPost] = useState(null); const { forumSpace, query, + error, selectedPostId, activeTopicRoomId, - error, + threads, + recordThreadReply, } = useForumBoardContext(); const activeTopicRoom = activeTopicRoomId ? mx.getRoom(activeTopicRoomId) : null; - const topicPowerLevels = usePowerLevels(activeTopicRoom ?? forumSpace); + const topicSelected = Boolean(query.topic); + const setActiveThreadId = useSetAtom( activeThreadIdAtomFamily(activeTopicRoomId ?? forumSpace.roomId) ); - const topicSelected = Boolean(query.topic); - useEffect(() => { if (selectedPostId && activeTopicRoomId) { setActiveThreadId(selectedPostId); @@ -42,51 +43,80 @@ export function ForumBoardDetail() { return undefined; }, [selectedPostId, activeTopicRoomId, setActiveThreadId]); - const showComposer = Boolean(activeTopicRoom && topicSelected); + useEffect(() => { + if (optimisticPost && optimisticPost.eventId !== selectedPostId) { + setOptimisticPost(null); + } + }, [optimisticPost, selectedPostId]); + + const selectedThread = useMemo( + () => threads.find((t) => t.eventId === selectedPostId), + [threads, selectedPostId] + ); + + const handleReplySent = useCallback( + (_reply: ForumPost) => { + if (selectedPostId) { + recordThreadReply(selectedPostId); + } + }, + [recordThreadReply, selectedPostId] + ); + + const showThread = Boolean(selectedPostId && activeTopicRoom); + const threadInitialPost = + optimisticPost?.eventId === selectedPostId ? optimisticPost : null; + const detailDropRef = useRef(null); return ( - - - - {error && ( - - {error} - + + + {error && ( + + {error} + + )} + ); diff --git a/src/app/features/forum/ForumBoardView.css.ts b/src/app/features/forum/ForumBoardView.css.ts index 1377964..530abcf 100644 --- a/src/app/features/forum/ForumBoardView.css.ts +++ b/src/app/features/forum/ForumBoardView.css.ts @@ -1,4 +1,4 @@ -import { style } from '@vanilla-extract/css'; +import { style, globalStyle } from '@vanilla-extract/css'; import { color, config } from 'folds'; const border = color.Surface.Container; @@ -292,6 +292,14 @@ export const ForumPageNav = style({ width: 'min(400px, 38vw)', minWidth: '300px', maxWidth: '420px', + height: '100%', + minHeight: 0, +}); + +/** PageNav column must shrink so the feed list can scroll inside. */ +globalStyle(`${ForumPageNav} > div`, { + minHeight: 0, + height: '100%', }); export const ForumFeedSidebar = style({ diff --git a/src/app/features/forum/ForumBoardView.tsx b/src/app/features/forum/ForumBoardView.tsx index 16c0729..9cd9f86 100644 --- a/src/app/features/forum/ForumBoardView.tsx +++ b/src/app/features/forum/ForumBoardView.tsx @@ -1,5 +1,6 @@ import React, { useCallback, useEffect, useRef } from 'react'; -import { Box, Icon, Icons, Text } from 'folds'; +import { Box, Text } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import { Room } from 'matrix-js-sdk'; import { useSetAtom } from 'jotai'; import { Page, PageContent } from '../../components/page'; diff --git a/src/app/features/forum/ForumChatComposer.tsx b/src/app/features/forum/ForumChatComposer.tsx index 36c4aa2..dbe41b8 100644 --- a/src/app/features/forum/ForumChatComposer.tsx +++ b/src/app/features/forum/ForumChatComposer.tsx @@ -2,7 +2,8 @@ import React, { KeyboardEventHandler, useCallback, useRef, useState } from 'reac import { isKeyHotkey } from 'is-hotkey'; import { Editor } from 'slate'; import { ReactEditor } from 'slate-react'; -import { Icon, IconButton, Icons, Line, PopOut } from 'folds'; +import { IconButton, Line, PopOut } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import { CustomEditor, Toolbar, diff --git a/src/app/features/forum/ForumEventContent.tsx b/src/app/features/forum/ForumEventContent.tsx new file mode 100644 index 0000000..d317e43 --- /dev/null +++ b/src/app/features/forum/ForumEventContent.tsx @@ -0,0 +1,64 @@ +import React, { useCallback, useMemo } from 'react'; +import { EventType, Room } from 'matrix-js-sdk'; +import { RenderMessageContent } from '../../components/RenderMessageContent'; +import { getEditedEvent } from '../../utils/room'; +import { GetContentCallback } from '../../../types/matrix/room'; +import { ForumMessageBody } from './ForumMessageBody'; +import { useForumMessageRenderOptions } from './useForumMessageRenderOptions'; +import * as theme from './forumTheme.css'; + +type ForumEventContentProps = { + room: Room | null; + eventId: string; + body: string; + bodyHtml?: string; + senderDisplayName?: string; +}; + +/** Renders a forum post/reply with the same media + rich text pipeline as room messages. */ +export function ForumEventContent({ + room, + eventId, + body, + bodyHtml, + senderDisplayName, +}: ForumEventContentProps) { + const renderOptions = useForumMessageRenderOptions(room); + const event = room?.findEventById(eventId); + const timelineSet = room?.getUnfilteredTimelineSet(); + + const editedEvent = useMemo(() => { + if (!event || !timelineSet) return undefined; + return getEditedEvent(eventId, event, timelineSet); + }, [event, eventId, timelineSet]); + + const getContent = useCallback( + () => editedEvent?.getContent()['m.new_content'] ?? event?.getContent() ?? { body, msgtype: 'm.text' }, + [editedEvent, event, body] + ); + + if (!event || event.getType() !== EventType.RoomMessage) { + return ; + } + + const content = getContent(); + const msgType = typeof content.msgtype === 'string' ? content.msgtype : 'm.text'; + + return ( +
+ +
+ ); +} diff --git a/src/app/features/forum/ForumFeedSidebar.tsx b/src/app/features/forum/ForumFeedSidebar.tsx index 6439b7e..e013a23 100644 --- a/src/app/features/forum/ForumFeedSidebar.tsx +++ b/src/app/features/forum/ForumFeedSidebar.tsx @@ -1,44 +1,21 @@ import React, { useCallback } from 'react'; -import { Box, Text } from 'folds'; +import { Text, color } from 'folds'; import { useMatrixClient } from '../../hooks/useMatrixClient'; -import { getSpaceLobbyPath } from '../../pages/pathUtils'; -import { getCanonicalAliasOrRoomId } from '../../utils/matrix'; -import { useSpace } from '../../hooks/useSpace'; -import { - NavCategory, - NavCategoryHeader, - NavItem, - NavItemContent, - NavLink, -} from '../../components/nav'; -import { RoomNavCategoryButton } from '../room-nav'; -import { useCategoryHandler } from '../../hooks/useCategoryHandler'; -import { makeNavCategoryId } from '../../state/closedNavCategories'; -import { useAtom } from 'jotai'; -import { useClosedNavCategoriesAtom } from '../../state/hooks/closedNavCategories'; +import { usePowerLevels } from '../../hooks/usePowerLevels'; +import { PowerLevelsContextProvider } from '../../hooks/usePowerLevels'; import { useForumBoardContext } from './ForumBoardContext'; -import type { ForumPublishedPost } from './types'; import { ForumFilterBar } from './ForumFilterBar'; -import { ForumNewPostBox } from './ForumNewPostBox'; import { ForumPostList } from './ForumPostList'; -import * as css from './ForumBoardView.css'; +import { ForumNewPostBox } from './ForumNewPostBox'; +import type { ForumPublishedPost } from './types'; +import * as theme from './forumTheme.css'; -/** - * matrixsso-style left column: category/topic nav + filters + post list. - * Renders inside the space PageNav instead of Lobby / ROOMS. - */ +/** Categories, filters, and post list in the space sidebar (matrixsso-style). */ export function ForumFeedSidebar() { const mx = useMatrixClient(); - const space = useSpace(); - const spaceIdOrAlias = getCanonicalAliasOrRoomId(mx, space.roomId); - const lobbyPath = getSpaceLobbyPath(spaceIdOrAlias); - - const [closedCategories, setClosedCategories] = useAtom(useClosedNavCategoriesAtom()); - const handleCategoryClick = useCategoryHandler(setClosedCategories, (categoryId) => - closedCategories.has(categoryId) - ); const { + forumSpace, sections, threads, query, @@ -47,6 +24,7 @@ export function ForumFeedSidebar() { error, selectedPostId, setQueryParam, + setCategory, selectPost, loadMorePosts, hasMorePosts, @@ -54,20 +32,7 @@ export function ForumFeedSidebar() { insertThreadFromNewPost, } = useForumBoardContext(); - const topicSelected = Boolean(query.topic); - const lobbyBase = lobbyPath; - - const buildLobbyTo = useCallback( - (params: Record) => { - const sp = new URLSearchParams(); - Object.entries(params).forEach(([k, v]) => { - if (v) sp.set(k, v); - }); - const q = sp.toString(); - return q ? `${lobbyBase}?${q}` : lobbyBase; - }, - [lobbyBase] - ); + const spacePowerLevels = usePowerLevels(forumSpace); const getTopicRoom = useCallback( (roomId: string | undefined) => (roomId ? mx.getRoom(roomId) : null), @@ -83,103 +48,44 @@ export function ForumFeedSidebar() { ); return ( - - {error && ( - - {error} - - )} - - - - - - All posts - - - - - - {sections.map((section) => { - const categoryId = makeNavCategoryId(space.roomId, `forum-${section.title}`); - const closed = closedCategories.has(categoryId); - const categoryActive = query.category === section.title; - - return ( - - - - {section.title} - - - {!closed && ( - <> - - - - - All topics - - - - - {section.topics.map((topic) => ( - - - - - {topic.name} - - - - - ))} - - )} - - ); - })} - - - - - - - - - - + +
+
+ +
+
+ {error && ( + + {error} + + )} + + +
+
+
); } diff --git a/src/app/features/forum/ForumFilterBar.tsx b/src/app/features/forum/ForumFilterBar.tsx index bf0a89b..0da6ba3 100644 --- a/src/app/features/forum/ForumFilterBar.tsx +++ b/src/app/features/forum/ForumFilterBar.tsx @@ -1,5 +1,6 @@ import React, { FormEventHandler, useCallback, useEffect, useState } from 'react'; -import { Icon, IconButton, Text, Tooltip, TooltipProvider } from 'folds'; +import { IconButton, Text, Tooltip, TooltipProvider } from 'folds'; +import { Icon } from '../../components/icons'; import type { ForumBoardQuery, ForumSection, ForumThreadSummary } from './types'; import { ForumSortIcons } from './forumLucideIcons'; import { ForumTopicSearchInput } from './ForumTopicSearchInput'; diff --git a/src/app/features/forum/ForumMessageActions.tsx b/src/app/features/forum/ForumMessageActions.tsx index 11beca5..e43cf8d 100644 --- a/src/app/features/forum/ForumMessageActions.tsx +++ b/src/app/features/forum/ForumMessageActions.tsx @@ -1,29 +1,175 @@ -import React from 'react'; -import { Icon, Icons } from 'folds'; +import React, { MouseEventHandler, useEffect, useRef, useState } from 'react'; +import { Box, IconButton, Menu, PopOut } from 'folds'; +import { Icon, Icons } from '../../components/icons'; +import { Room } from 'matrix-js-sdk'; +import { EmojiBoard } from '../../components/emoji-board'; +import { copyToClipboard } from '../../utils/dom'; +import { PluginButtonSlot } from '../settings/plugins/PluginButtonSlot'; +import { useForumMessageMenu } from './ForumMessageMenuContext'; import * as theme from './forumTheme.css'; type ForumMessageActionsProps = { + imagePackRooms: Room[]; + eventId?: string; + body?: string; + canSendReaction?: boolean; + onReactionToggle?: (targetEventId: string, key: string, shortcode?: string) => void; onReply: () => void; replyActive?: boolean; + onEdit?: () => void; + canEdit?: boolean; + onDelete?: () => void; + canDelete?: boolean; }; -export function ForumMessageActions({ onReply, replyActive }: ForumMessageActionsProps) { +export function ForumMessageActions({ + imagePackRooms, + eventId, + body, + canSendReaction, + onReactionToggle, + onReply, + replyActive, + onEdit, + canEdit, + onDelete, + canDelete, +}: ForumMessageActionsProps) { + const menu = useForumMessageMenu(); + const emojiBtnRef = useRef(null); + const [emojiBoardAnchor, setEmojiBoardAnchor] = useState(); + const [shiftHeld, setShiftHeld] = useState(false); + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Shift') setShiftHeld(true); + }; + const handleKeyUp = (e: KeyboardEvent) => { + if (e.key === 'Shift') setShiftHeld(false); + }; + window.addEventListener('keydown', handleKeyDown); + window.addEventListener('keyup', handleKeyUp); + return () => { + window.removeEventListener('keydown', handleKeyDown); + window.removeEventListener('keyup', handleKeyUp); + }; + }, []); + + const handleOpenEmojiBoard: MouseEventHandler = () => { + setEmojiBoardAnchor(emojiBtnRef.current?.getBoundingClientRect()); + }; + + const handleOpenMenu: MouseEventHandler = (evt) => { + const target = evt.currentTarget.parentElement ?? evt.currentTarget; + menu?.openMenu(target.getBoundingClientRect()); + }; + + const handleReplyClick: MouseEventHandler = (evt) => { + evt.stopPropagation(); + onReply(); + }; + return ( -
- -
+ + + {canEdit && onEdit && ( + + + + )} + {body && ( + copyToClipboard(body)} + variant="SurfaceVariant" + size="300" + radii="300" + aria-label="Copy raw text" + title="Copy raw text" + > + + + )} + {shiftHeld && canDelete && onDelete && ( + + + + )} + + {menu && ( + + + + )} +
+ ); } diff --git a/src/app/features/forum/ForumMessageContextMenu.tsx b/src/app/features/forum/ForumMessageContextMenu.tsx new file mode 100644 index 0000000..37106cf --- /dev/null +++ b/src/app/features/forum/ForumMessageContextMenu.tsx @@ -0,0 +1,256 @@ +import React, { MouseEventHandler, ReactNode, useCallback, useState } from 'react'; +import FocusTrap from 'focus-trap-react'; +import { MatrixEvent, Room } from 'matrix-js-sdk'; +import { + Box, + Line, + Menu, + MenuItem, + PopOut, + RectCords, + Text, +} from 'folds'; +import { Icon, Icons } from '../../components/icons'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; +import { useSetting } from '../../state/hooks/settings'; +import { settingsAtom } from '../../state/settings'; +import { stopPropagation } from '../../utils/keyboard'; +import { EmojiBoard } from '../../components/emoji-board'; +import { + MessageAllReactionItem, + MessageCopyLinkItem, + MessageCopyRawTextItem, + MessageDeleteItem, + MessagePinItem, + MessageQuickReactions, + MessageReadReceiptItem, + MessageReportItem, + MessageSourceCodeItem, +} from '../room/message/Message'; +import * as menuCss from '../room/message/styles.css'; +import { ForumMessageMenuProvider } from './ForumMessageMenuContext'; +import { useForumMessageReactions } from './useForumMessageReactions'; + +type ForumMessageContextMenuProps = { + room: Room; + mEvent: MatrixEvent | undefined; + imagePackRooms: Room[]; + disabled?: boolean; + canEdit?: boolean; + canDelete?: boolean; + onReply: () => void; + onEdit?: () => void; + onDeleted?: () => void; + className?: string; + children: ReactNode; +}; + +export function ForumMessageContextMenu({ + room, + mEvent, + imagePackRooms, + disabled, + canEdit, + canDelete, + onReply, + onEdit, + onDeleted, + className, + children, +}: ForumMessageContextMenuProps) { + const mx = useMatrixClient(); + const [menuAnchor, setMenuAnchor] = useState(); + const [emojiBoardAnchor, setEmojiBoardAnchor] = useState(); + const [hideActivity] = useSetting(settingsAtom, 'hideActivity'); + const [developerTools] = useSetting(settingsAtom, 'developerTools'); + + const { + canSendReaction, + canPinEvent, + reactionRelations, + hasReactions, + handleReactionToggle, + eventId, + } = useForumMessageReactions(room, mEvent); + + const openMenu = useCallback((anchor: RectCords) => { + setMenuAnchor(anchor); + }, []); + + const closeMenu = useCallback(() => { + setMenuAnchor(undefined); + }, []); + + const handleContextMenu: MouseEventHandler = useCallback( + (evt) => { + if (disabled || !mEvent || evt.altKey || !window.getSelection()?.isCollapsed) return; + const tag = (evt.target as HTMLElement).tagName; + if (tag.toLowerCase() === 'a' || tag.toLowerCase() === 'img') return; + evt.preventDefault(); + setMenuAnchor({ + x: evt.clientX, + y: evt.clientY, + width: 0, + height: 0, + }); + }, + [disabled, mEvent] + ); + + const handleAddReactions: MouseEventHandler = useCallback(() => { + const rect = menuAnchor; + closeMenu(); + setTimeout(() => { + setEmojiBoardAnchor(rect); + }, 100); + }, [closeMenu, menuAnchor]); + + if (!mEvent) { + return
{children}
; + } + + return ( + +
+ {children} + evt.key === 'ArrowDown', + isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', + escapeDeactivates: stopPropagation, + }} + > + + {canSendReaction && ( + { + if (eventId) handleReactionToggle(eventId, key, shortcode); + closeMenu(); + }} + /> + )} + + {canSendReaction && ( + } + radii="300" + onClick={handleAddReactions} + > + + Add Reaction + + + )} + {hasReactions && reactionRelations && ( + + )} + } + radii="300" + onClick={() => { + onReply(); + closeMenu(); + }} + > + + Reply + + + {canEdit && onEdit && ( + } + radii="300" + onClick={() => { + onEdit(); + closeMenu(); + }} + > + + Edit Message + + + )} + {!hideActivity && eventId && ( + + )} + {developerTools && ( + + )} + + + {canPinEvent && ( + + )} + + {((!mEvent.isRedacted() && canDelete) || mEvent.getSender() !== mx.getUserId()) && ( + <> + + + {!mEvent.isRedacted() && canDelete && ( + { + closeMenu(); + onDeleted?.(); + }} + /> + )} + {mEvent.getSender() !== mx.getUserId() && ( + + )} + + + )} + + + } + /> + {canSendReaction && ( + { + if (eventId) handleReactionToggle(eventId, key); + setEmojiBoardAnchor(undefined); + }} + onCustomEmojiSelect={(mxc, shortcode) => { + if (eventId) handleReactionToggle(eventId, mxc, shortcode); + setEmojiBoardAnchor(undefined); + }} + requestClose={() => setEmojiBoardAnchor(undefined)} + /> + } + > + + )} +
+
+ ); +} + +export { useForumMessageReactions }; diff --git a/src/app/features/forum/ForumMessageMenuContext.tsx b/src/app/features/forum/ForumMessageMenuContext.tsx new file mode 100644 index 0000000..e710ff4 --- /dev/null +++ b/src/app/features/forum/ForumMessageMenuContext.tsx @@ -0,0 +1,26 @@ +import React, { createContext, useContext } from 'react'; +import { RectCords } from 'folds'; + +type ForumMessageMenuContextValue = { + openMenu: (anchor: RectCords) => void; +}; + +const ForumMessageMenuContext = createContext(null); + +export function ForumMessageMenuProvider({ + openMenu, + children, +}: { + openMenu: (anchor: RectCords) => void; + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} + +export function useForumMessageMenu() { + return useContext(ForumMessageMenuContext); +} diff --git a/src/app/features/forum/ForumNewPostBox.tsx b/src/app/features/forum/ForumNewPostBox.tsx index e82ed7c..af65112 100644 --- a/src/app/features/forum/ForumNewPostBox.tsx +++ b/src/app/features/forum/ForumNewPostBox.tsx @@ -1,7 +1,8 @@ import React, { useCallback, useState } from 'react'; -import { Box, Button, Icon, Icons, Text } from 'folds'; +import { Box, Button, Text } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import { useMatrixClient } from '../../hooks/useMatrixClient'; -import type { ForumPost, ForumPublishedPost } from './types'; +import type { ForumPost, ForumPublishedPost, ForumSection } from './types'; import { ForumNewPostModal } from './ForumNewPostModal'; import * as t from './forumTheme.css'; diff --git a/src/app/features/forum/ForumNewPostModal.tsx b/src/app/features/forum/ForumNewPostModal.tsx index a0aaa50..cf44f10 100644 --- a/src/app/features/forum/ForumNewPostModal.tsx +++ b/src/app/features/forum/ForumNewPostModal.tsx @@ -1,24 +1,8 @@ import React, { FormEventHandler, KeyboardEventHandler, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { isKeyHotkey } from 'is-hotkey'; import FocusTrap from 'focus-trap-react'; -import { - Box, - Button, - Header, - Icon, - IconButton, - Icons, - Modal, - Overlay, - OverlayBackdrop, - OverlayCenter, - Line, - Scroll, - Text, - color, - config, - toRem, -} from 'folds'; +import { Box, Button, Header, IconButton, Modal, Overlay, OverlayBackdrop, OverlayCenter, Line, Scroll, Text, color, config, toRem } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import { CustomEditor, Toolbar, @@ -32,10 +16,11 @@ import { } from '../../components/editor'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { stopPropagation } from '../../utils/keyboard'; -import { sendForumPost } from './forumFeed'; +import { sendForumPost, sendForumMediaRoot, sendForumThreadAttachment } from './forumFeed'; import { FORUM_EDITOR_OUTPUT_OPTS } from './forumRichText'; import type { ForumPublishedPost, ForumSection } from './types'; import { ForumTopicTargetFields } from './ForumTopicTargetFields'; +import { ForumPostUploadField, useForumPostUploads } from './ForumPostUploadField'; import * as t from './forumTheme.css'; type ForumNewPostModalProps = { @@ -61,6 +46,14 @@ export function ForumNewPostModal({ const [topicRoomId, setTopicRoomId] = useState(initialTopicRoomId); const [sending, setSending] = useState(false); const [error, setError] = useState(null); + const uploadRoomKey = topicRoomId || '__forum_new_post_draft__'; + const { + selectedFiles, + uploadBoardHandlers, + handleUploadsReady, + collectUploadContents, + clearUploads, + } = useForumPostUploads(uploadRoomKey); const categoryTopics = useMemo(() => { if (category) { @@ -80,8 +73,9 @@ export function ForumNewPostModal({ if (sending) return; resetEditor(editor); resetEditorHistory(editor); + clearUploads(); onClose(); - }, [editor, onClose, sending]); + }, [clearUploads, editor, onClose, sending]); const handleSubmit: FormEventHandler = useCallback( async (evt) => { @@ -94,6 +88,9 @@ export function ForumNewPostModal({ toMatrixCustomHTML(editor.children, FORUM_EDITOR_OUTPUT_OPTS) ); + const hasText = !isEmptyEditor(editor); + const hasUploads = selectedFiles.length > 0; + if (!topicRoomId) { setError('Select a topic for this post.'); return; @@ -102,27 +99,53 @@ export function ForumNewPostModal({ setError('Post title is required.'); return; } - if (isEmptyEditor(editor)) { - setError('Post body is required.'); + if (!hasText && !hasUploads) { + setError('Post body or an attachment is required.'); return; } setSending(true); setError(null); try { - const eventId = await sendForumPost(mx, topicRoomId, { - title: trimmedTitle, - plainText, - formattedHtml, - }); + const attachmentContents = hasUploads ? await collectUploadContents() : []; + let eventId: string; + let publishedPlainText = plainText; + let publishedHtml = formattedHtml; + + if (hasText) { + eventId = await sendForumPost(mx, topicRoomId, { + title: trimmedTitle, + plainText, + formattedHtml, + }); + } else { + const [first, ...rest] = attachmentContents; + if (!first) { + throw new Error('Attachments failed to upload.'); + } + eventId = await sendForumMediaRoot(mx, topicRoomId, trimmedTitle, first); + publishedPlainText = typeof first.body === 'string' ? first.body : trimmedTitle; + publishedHtml = ''; + for (const content of rest) { + await sendForumThreadAttachment(mx, topicRoomId, eventId, content); + } + } + + if (hasText) { + for (const content of attachmentContents) { + await sendForumThreadAttachment(mx, topicRoomId, eventId, content); + } + } + resetEditor(editor); resetEditorHistory(editor); + clearUploads(); onPublished?.({ eventId, topicRoomId, title: trimmedTitle, - plainText, - formattedHtml, + plainText: publishedPlainText, + formattedHtml: publishedHtml, }); onClose(); } catch (err) { @@ -131,7 +154,18 @@ export function ForumNewPostModal({ setSending(false); } }, - [editor, mx, onClose, onPublished, sending, title, topicRoomId] + [ + clearUploads, + collectUploadContents, + editor, + mx, + onClose, + onPublished, + selectedFiles.length, + sending, + title, + topicRoomId, + ] ); const handleEditorKeyDown: KeyboardEventHandler = useCallback( @@ -230,7 +264,16 @@ export function ForumNewPostModal({ bottom={
- + + + + +
} /> diff --git a/src/app/features/forum/ForumPostList.tsx b/src/app/features/forum/ForumPostList.tsx index 515a83c..91654a8 100644 --- a/src/app/features/forum/ForumPostList.tsx +++ b/src/app/features/forum/ForumPostList.tsx @@ -1,5 +1,6 @@ -import React from 'react'; -import { Box, Button, Icon, Icons, Spinner, Text } from 'folds'; +import React, { KeyboardEventHandler } from 'react'; +import { Box, Button, Scroll, Spinner, Text } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import { Room } from 'matrix-js-sdk'; import type { ForumThreadSummary } from './types'; import { formatForumTimeAgo } from './forumTime'; @@ -38,7 +39,14 @@ export function ForumPostList({ }: ForumPostListProps) { return ( <> -
+ {loading && threads.length === 0 && ( @@ -56,14 +64,24 @@ export function ForumPostList({ {threads.map((thread) => { const isActive = thread.eventId === selectedPostId; const topicRoom = getTopicRoom(thread.topicRoomId); + + const handleKeyDown: KeyboardEventHandler = (event) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + onSelectPost(thread.eventId, thread.topicRoomId); + } + }; + return (
  • - +
  • ); })} - + {hasMore && onLoadMore && (
    +
    + )} + + ); +} diff --git a/src/app/features/forum/ForumSpaceShell.tsx b/src/app/features/forum/ForumSpaceShell.tsx index 8b59d1a..a80389a 100644 --- a/src/app/features/forum/ForumSpaceShell.tsx +++ b/src/app/features/forum/ForumSpaceShell.tsx @@ -1,5 +1,6 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { Box, Icon, Icons, Text, color } from 'folds'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { Box, Text, color, Scroll } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import classNames from 'classnames'; import { useSetAtom } from 'jotai'; import { useMatrixClient } from '../../hooks/useMatrixClient'; @@ -99,6 +100,7 @@ export function ForumSpaceShell() { const threadInitialPost = optimisticPost?.eventId === selectedPostId ? optimisticPost : null; + const detailDropRef = useRef(null); return ( @@ -149,32 +151,43 @@ export function ForumSpaceShell() { diff --git a/src/app/features/forum/ForumThreadDetail.tsx b/src/app/features/forum/ForumThreadDetail.tsx index 8a0c343..adc2295 100644 --- a/src/app/features/forum/ForumThreadDetail.tsx +++ b/src/app/features/forum/ForumThreadDetail.tsx @@ -1,86 +1,281 @@ // @refresh reset -import React, { useEffect, useState } from 'react'; -import { Box, Spinner, Text } from 'folds'; -import { Room } from 'matrix-js-sdk'; +import { useCallback, useEffect, useMemo, useRef, useState, type MouseEvent, type ReactNode, type RefObject } from 'react'; +import { useAtom, useAtomValue } from 'jotai'; +import { Box, Scroll, Spinner, Text, config } from 'folds'; +import { RelationType, Room } from 'matrix-js-sdk'; +import { ReactEditor } from 'slate-react'; import { useMatrixClient } from '../../hooks/useMatrixClient'; +import { usePowerLevels } from '../../hooks/usePowerLevels'; +import { useRoomPermissions } from '../../hooks/useRoomPermissions'; +import { useRoomCreators } from '../../hooks/useRoomCreators'; +import { useImagePackRooms } from '../../hooks/useImagePackRooms'; +import { roomToParentsAtom } from '../../state/room/roomToParents'; +import { roomIdToReplyDraftAtomFamily } from '../../state/room/roomInputDrafts'; +import { canEditEvent, getEditedEvent } from '../../utils/room'; +import { MessageEditor } from '../room/message/MessageEditor'; +import { Reactions } from '../room/message'; import { formatForumTimeAgo } from './forumTime'; -import { loadPostThread } from './forumFeed'; -import { appendReplyToThread } from './forumTopicHelpers'; +import { loadPostThread, loadPostThreadLocal } from './forumFeed'; +import { countAllReplies } from './forumTopicHelpers'; import type { ForumPost } from './types'; import { ForumAuthorIdentity } from './ForumAuthorIdentity'; -import { ForumMessageBody } from './ForumMessageBody'; +import { ForumEventContent } from './ForumEventContent'; +import { ForumMessageContextMenu } from './ForumMessageContextMenu'; import { ForumMessageActions } from './ForumMessageActions'; -import { ForumRichReplyComposer } from './ForumRichReplyComposer'; +import { ForumReplyComposer } from './ForumReplyComposer'; +import { useForumMessageReactions } from './useForumMessageReactions'; +import { useForumRoomLiveUpdates } from './useForumRoomLiveUpdates'; +import { resetEditor, resetEditorHistory, useEditor } from '../../components/editor'; +import { mobileOrTablet } from '../../utils/user-agent'; import * as theme from './forumTheme.css'; -function ForumThreadReply({ - reply, - room, - threadRootId, - roomId, - depth, - onReplySent, -}: { - reply: ForumPost; - room: Room | null; - threadRootId: string; - roomId: string; - depth: number; - onReplySent: (reply: ForumPost) => void; -}) { - const [replyOpen, setReplyOpen] = useState(false); +function forumBranchKey(eventId: string): string { + return `branch:${eventId}`; +} + +function forumMessageSurfaceClass( + baseClass: string, + eventId: string, + replyTargetEventId?: string +): string { + if (replyTargetEventId && replyTargetEventId === eventId) { + return `${baseClass} ${theme.ForumThreadReplyTarget}`; + } + return baseClass; +} + +type ForumThreadBranchProps = { + className: string; + railClassName: string; + collapsed: boolean; + hiddenCount: number; + onToggle: () => void; + children: ReactNode; +}; + +function morePostsLabel(count: number): string { + return `${count} more ${count === 1 ? 'post' : 'posts'}`; +} + +function ForumThreadBranch({ + className, + railClassName, + collapsed, + hiddenCount, + onToggle, + children, +}: ForumThreadBranchProps) { + const isRootBranch = className === theme.ForumThread; + + const handleToggle = (event: MouseEvent) => { + event.stopPropagation(); + onToggle(); + }; + + if (collapsed && hiddenCount > 0) { + return ( + + ); + } return ( -
    -
    +
    +
    + ); +} + +type ForumThreadMessageProps = { + post: ForumPost; + room: Room; + roomId: string; + threadRootId: string; + fileDropContainerRef: RefObject; + editEventId?: string; + onEditEventId: (eventId?: string) => void; + onComposerSent: () => void; + onDeleted: () => void; + onReplyTo: (eventId: string) => void; + replyTargetEventId?: string; + collapsedBranches: Set; + onToggleBranch: (branchKey: string) => void; +}; + +function ForumThreadMessage({ + post, + room, + roomId, + threadRootId, + fileDropContainerRef, + editEventId, + onEditEventId, + onComposerSent, + onDeleted, + onReplyTo, + replyTargetEventId, + collapsedBranches, + onToggleBranch, +}: ForumThreadMessageProps) { + const mx = useMatrixClient(); + const mEvent = room.findEventById(post.eventId); + const isEditing = editEventId === post.eventId; + const branchKey = forumBranchKey(post.eventId); + const replyCount = countAllReplies(post.replies); + const repliesCollapsed = collapsedBranches.has(branchKey); + const powerLevels = usePowerLevels(room); + const creators = useRoomCreators(room); + const permissions = useRoomPermissions(creators, powerLevels); + const roomToParents = useAtomValue(roomToParentsAtom); + const imagePackRooms = useImagePackRooms(roomId, roomToParents); + + const canEdit = mEvent ? canEditEvent(mx, mEvent) : false; + const canDelete = mEvent + ? permissions.action('redact', mx.getSafeUserId()) || mEvent.getSender() === mx.getUserId() + : false; + const { canSendReaction, reactionRelations, handleReactionToggle } = useForumMessageReactions( + room, + mEvent + ); + + const handleDelete = useCallback(() => { + if (!mEvent) return; + const eventId = mEvent.getId(); + if (!eventId) return; + mx.redactEvent(roomId, eventId).then(onDeleted); + }, [mEvent, mx, onDeleted, roomId]); + + const handleEditClose = useCallback(() => { + onEditEventId(undefined); + onComposerSent(); + }, [onComposerSent, onEditEventId]); + + return ( +
    + onReplyTo(post.eventId)} + onEdit={() => onEditEventId(post.eventId)} + onDeleted={onDeleted} + className={forumMessageSurfaceClass(theme.ForumThreadReplyInner, post.eventId, replyTargetEventId)} + >
    - -
    - {reply.replyToDeleted && ( + {post.replyToDeleted && (

    Replying to a deleted message

    )} - - {!replyOpen && ( -
    - setReplyOpen(true)} /> -
    - )} - {replyOpen && ( -
    - { - setReplyOpen(false); - onReplySent(sentReply); - }} - onCancel={() => setReplyOpen(false)} - /> -
    - )} -
    - {reply.replies.length > 0 && ( -
    - {reply.replies.map((child) => ( - + ) : ( + <> + - ))} -
    + {reactionRelations && ( + + )} + + )} + {!isEditing && ( +
    + onReplyTo(post.eventId)} + replyActive={replyTargetEventId === post.eventId} + onEdit={() => onEditEventId(post.eventId)} + canEdit={canEdit} + onDelete={handleDelete} + canDelete={canDelete} + /> +
    + )} + + {post.replies.length > 0 && ( + onToggleBranch(branchKey)} + > + {!repliesCollapsed && + post.replies.map((child) => ( + + ))} + )}
    ); @@ -91,31 +286,324 @@ type ForumThreadDetailProps = { rootEventId: string; titleHint?: string; initialPost?: ForumPost | null; + fileDropContainerRef?: RefObject; onReplySent?: (reply: ForumPost) => void; }; +type ForumThreadDetailBodyProps = ForumThreadDetailProps & { + post: ForumPost; + room: Room; + onPostUpdate: (post: ForumPost | null) => void; +}; + +function ForumThreadDetailBody({ + roomId, + rootEventId, + titleHint, + post, + room, + fileDropContainerRef, + onReplySent, + onPostUpdate, +}: ForumThreadDetailBodyProps) { + const mx = useMatrixClient(); + const editor = useEditor(); + const [editEventId, setEditEventId] = useState(); + const [replyToEventId, setReplyToEventId] = useState(rootEventId); + const [replyDraft, setReplyDraft] = useAtom(roomIdToReplyDraftAtomFamily(roomId)); + const timelineSet = useMemo(() => room.getUnfilteredTimelineSet(), [room]); + + useEffect( + () => () => { + resetEditor(editor); + resetEditorHistory(editor); + }, + [editor] + ); + + useEffect(() => { + setReplyToEventId(rootEventId); + setReplyDraft(undefined); + }, [rootEventId, setReplyDraft]); + + useEffect(() => { + if (!replyDraft) { + setReplyToEventId(rootEventId); + } + }, [replyDraft, rootEventId]); + const [contentRevision, setContentRevision] = useState(0); + const [collapsedBranches, setCollapsedBranches] = useState>(() => new Set()); + const localDropRef = useRef(null); + const dropRef = fileDropContainerRef ?? localDropRef; + const powerLevels = usePowerLevels(room); + const creators = useRoomCreators(room); + const permissions = useRoomPermissions(creators, powerLevels); + const roomToParents = useAtomValue(roomToParentsAtom); + const imagePackRooms = useImagePackRooms(roomId, roomToParents); + + const reloadThread = useCallback(async () => { + const local = loadPostThreadLocal(mx, roomId, rootEventId); + if (local) onPostUpdate(local); + + const loaded = await loadPostThread(mx, roomId, rootEventId); + onPostUpdate(loaded); + return loaded; + }, [mx, onPostUpdate, roomId, rootEventId]); + + const handleComposerSent = useCallback(async () => { + const applyLoaded = (loaded: ForumPost | null) => { + if (!loaded) return; + onReplySent?.({ + eventId: loaded.eventId, + title: loaded.title, + body: loaded.body, + sender: loaded.sender, + timestamp: Date.now(), + replies: loaded.replies, + }); + }; + + const loaded = await reloadThread(); + applyLoaded(loaded); + + // Local echo can land slightly after send; refresh once more. + window.setTimeout(() => { + void reloadThread().then(applyLoaded); + }, 400); + }, [onReplySent, reloadThread]); + + const handleDeleted = useCallback(() => { + void handleComposerSent(); + }, [handleComposerSent]); + + useForumRoomLiveUpdates( + [room], + () => { + setContentRevision((revision) => revision + 1); + void reloadThread(); + }, + { threadRootId: rootEventId, wait: 150 } + ); + + const rootEvent = room.findEventById(rootEventId); + const rootEditing = editEventId === rootEventId; + const canEditRoot = rootEvent ? canEditEvent(mx, rootEvent) : false; + const canDeleteRoot = rootEvent + ? permissions.action('redact', mx.getSafeUserId()) || rootEvent.getSender() === mx.getUserId() + : false; + const { canSendReaction, reactionRelations, handleReactionToggle } = useForumMessageReactions( + room, + rootEvent + ); + + const handleDeleteRoot = useCallback(() => { + if (!rootEvent) return; + const eventId = rootEvent.getId(); + if (!eventId) return; + mx.redactEvent(roomId, eventId).then(() => { + void handleComposerSent(); + }); + }, [handleComposerSent, mx, rootEvent, roomId]); + + const handleEditRootClose = useCallback(() => { + setEditEventId(undefined); + setContentRevision((revision) => revision + 1); + void handleComposerSent(); + }, [handleComposerSent]); + + const toggleBranch = useCallback((branchKey: string) => { + setCollapsedBranches((prev) => { + const next = new Set(prev); + if (next.has(branchKey)) next.delete(branchKey); + else next.add(branchKey); + return next; + }); + }, []); + + const handleReplyTo = useCallback( + (eventId: string) => { + const replyEvt = room.findEventById(eventId); + if (!replyEvt) return; + + const editedReply = getEditedEvent(eventId, replyEvt, timelineSet); + const content = editedReply?.getContent()['m.new_content'] ?? replyEvt.getContent(); + const { body, formatted_body: formattedBody } = content; + const senderId = replyEvt.getSender(); + + setReplyToEventId(eventId); + if (senderId && typeof body === 'string') { + setReplyDraft({ + userId: senderId, + eventId, + body, + formattedBody, + relation: { rel_type: RelationType.Thread, event_id: rootEventId }, + }); + } + + window.setTimeout(() => { + const target = document.querySelector(`[data-event-id="${eventId}"]`); + target?.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + if (!mobileOrTablet()) ReactEditor.focus(editor); + }, 0); + }, + [editor, rootEventId, room, setReplyDraft, timelineSet] + ); + + const rootBranchKey = forumBranchKey(rootEventId); + const rootReplyCount = countAllReplies(post.replies); + const rootRepliesCollapsed = collapsedBranches.has(rootBranchKey); + + const replyTargetEventId = replyDraft?.eventId; + + const title = useMemo(() => { + const local = loadPostThreadLocal(mx, roomId, rootEventId); + return local?.title || post.title || titleHint || '(untitled post)'; + }, [contentRevision, mx, post.title, roomId, rootEventId, titleHint]); + + return ( +
    + +
    +
    +
    +

    {title}

    +
    + + · {formatForumTimeAgo(post.timestamp)} +
    +
    +
    + handleReplyTo(post.eventId)} + onEdit={() => setEditEventId(rootEventId)} + onDeleted={handleDeleted} + className={forumMessageSurfaceClass(theme.ForumTopicDetailOp, post.eventId, replyTargetEventId)} + > + {rootEditing && rootEvent ? ( + + ) : ( + <> + + {reactionRelations && ( + + )} + + )} + {!rootEditing && ( +
    + handleReplyTo(post.eventId)} + replyActive={replyTargetEventId === post.eventId} + onEdit={() => setEditEventId(rootEventId)} + canEdit={canEditRoot} + onDelete={handleDeleteRoot} + canDelete={canDeleteRoot} + /> +
    + )} +
    + {post.replies.length > 0 && ( + toggleBranch(rootBranchKey)} + > + {!rootRepliesCollapsed && + post.replies.map((reply) => ( + { + void handleComposerSent(); + }} + onDeleted={handleDeleted} + onReplyTo={handleReplyTo} + replyTargetEventId={replyTargetEventId} + collapsedBranches={collapsedBranches} + onToggleBranch={toggleBranch} + /> + ))} + + )} +
    +
    +
    + { + void handleComposerSent(); + }} + /> +
    +
    + ); +} + export function ForumThreadDetail({ roomId, rootEventId, titleHint, initialPost, + fileDropContainerRef, onReplySent, }: ForumThreadDetailProps) { const mx = useMatrixClient(); const [post, setPost] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - const [rootReplyOpen, setRootReplyOpen] = useState(false); const room = mx.getRoom(roomId); - const handleReplySent = (reply: ForumPost) => { - setPost((current) => { - if (!current || !reply.replyToEventId) return current; - return appendReplyToThread(current, reply.replyToEventId, reply); - }); - onReplySent?.(reply); - }; - useEffect(() => { let cancelled = false; @@ -160,7 +648,7 @@ export function ForumThreadDetail({ ); } - if (error || !post) { + if (error || !post || !room) { return ( {error || 'Post not found.'} @@ -168,56 +656,16 @@ export function ForumThreadDetail({ ); } - const title = post.title || titleHint || '(untitled post)'; - return ( -
    -
    -
    -

    {title}

    -
    - - · {formatForumTimeAgo(post.timestamp)} -
    -
    -
    -
    - - {!rootReplyOpen && ( -
    - setRootReplyOpen(true)} /> -
    - )} - {rootReplyOpen && ( -
    - { - setRootReplyOpen(false); - handleReplySent(sentReply); - }} - onCancel={() => setRootReplyOpen(false)} - /> -
    - )} -
    - {post.replies.length > 0 && ( -
    - {post.replies.map((reply) => ( - - ))} -
    - )} -
    + ); } diff --git a/src/app/features/forum/ForumTopicSearchInput.tsx b/src/app/features/forum/ForumTopicSearchInput.tsx index 0ebaeb9..0f274b0 100644 --- a/src/app/features/forum/ForumTopicSearchInput.tsx +++ b/src/app/features/forum/ForumTopicSearchInput.tsx @@ -1,6 +1,7 @@ import React, { KeyboardEventHandler, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import classNames from 'classnames'; -import { Icon, IconButton, Icons } from 'folds'; +import { IconButton } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import { TOPIC_SEARCH_QUALIFIERS, collectAuthorHintsFromThreads, diff --git a/src/app/features/forum/forumFeed.ts b/src/app/features/forum/forumFeed.ts index 15f7989..ea5a74f 100644 --- a/src/app/features/forum/forumFeed.ts +++ b/src/app/features/forum/forumFeed.ts @@ -1,16 +1,19 @@ import { Direction, EventType, + type IContent, type IEventRelation, type IRoomEvent, type MatrixClient, + type MatrixEvent, + type Room, RelationType, } from 'matrix-js-sdk'; import type { IHierarchyRoom } from 'matrix-js-sdk/lib/@types/spaces'; import { getMemberDisplayName, getSpaceChildren, isSpace } from '../../utils/room'; import type { ForumPost, ForumTopic } from './types'; import { annotatePostsWithRoomScope } from './forumTopicHelpers'; -import { bodyHtmlFromMessageContent } from './forumRichText'; +import { bodyHtmlFromMessageContent, titleFromFormattedBody } from './forumRichText'; type RawRelation = { rel_type?: string; @@ -24,6 +27,80 @@ function threadRelationFromContent(content: Record): RawRelatio return relates as RawRelation; } +function isAnnotationRoomMessage(content: Record): boolean { + const relation = threadRelationFromContent(content); + if (!relation?.rel_type) return false; + return ( + relation.rel_type === RelationType.Replace || + relation.rel_type === 'm.replace' || + relation.rel_type === RelationType.Annotation || + relation.rel_type === 'm.annotation' + ); +} + +function isForumRootContent(content: Record): boolean { + if (typeof content['com.matrixsso.title'] === 'string') return true; + const formatted = content.formatted_body; + return typeof formatted === 'string' && /]/i.test(formatted); +} + +function syncForumRootContentAfterEdit( + merged: Record, + baseContent: Record +): Record { + if (!isForumRootContent(baseContent)) return merged; + + const formattedTitle = + typeof merged.formatted_body === 'string' + ? titleFromFormattedBody(merged.formatted_body) + : undefined; + const { title, body } = postTitleFromContent({ + ...merged, + // Ignore stale title so edits can rename the post. + 'com.matrixsso.title': formattedTitle ?? undefined, + }); + + const next: Record = { + ...merged, + 'com.matrixsso.title': title, + }; + + if (typeof merged.body === 'string') { + const plain = merged.body.trim(); + if (body && plain !== body && !plain.startsWith(`${title}\n`)) { + next.body = `${title}\n\n${body}`; + } + } + + return next; +} + +function getLatestEditContent( + event: IRoomEvent, + timelineEvents: IRoomEvent[] +): Record { + const baseContent = event.content as Record; + const targetId = event.event_id; + if (!targetId) return baseContent; + + const edits = timelineEvents + .filter((candidate) => { + if (candidate.type !== EventType.RoomMessage || !candidate.content) return false; + const rel = threadRelationFromContent(candidate.content as Record); + return rel?.rel_type === RelationType.Replace && rel.event_id === targetId; + }) + .sort((a, b) => (b.origin_server_ts || 0) - (a.origin_server_ts || 0)); + + const latestEdit = edits[0]; + if (!latestEdit?.content) return baseContent; + + const newContent = (latestEdit.content as Record)['m.new_content']; + if (!newContent || typeof newContent !== 'object') return baseContent; + + const merged = { ...baseContent, ...(newContent as Record) }; + return syncForumRootContentAfterEdit(merged, baseContent); +} + function parseBundledThreadReplyCount(event: IRoomEvent): number | undefined { const relations = event.unsigned?.['m.relations'] as Record | undefined; const thread = relations?.['m.thread'] as { count?: unknown } | undefined; @@ -38,8 +115,12 @@ function postTitleFromContent(content: Record): { title: string typeof content['com.matrixsso.title'] === 'string' ? String(content['com.matrixsso.title']).trim() : ''; + const htmlTitle = + typeof content.formatted_body === 'string' + ? titleFromFormattedBody(content.formatted_body) + : undefined; const firstLine = rawBody.split(/\r?\n/, 1)[0]?.trim() ?? ''; - const title = (customTitle || firstLine || '(untitled post)').slice(0, 160); + const title = (customTitle || htmlTitle || firstLine || '(untitled post)').slice(0, 160); let body = rawBody; if (customTitle && rawBody.startsWith(customTitle)) { @@ -56,6 +137,11 @@ function postTitleFromContent(content: Record): { title: string return { title, body: body || rawBody }; } +/** Title + body for a forum matrix message payload (including edits). */ +export function forumPostTitleFromContent(content: Record): string { + return postTitleFromContent(content).title; +} + function sortRepliesRecursively(posts: ForumPost[]) { posts.sort((a, b) => a.timestamp - b.timestamp); for (const post of posts) { @@ -77,7 +163,10 @@ function buildPosts(mx: MatrixClient, roomId: string, timelineEvents: IRoomEvent !redactedIds.has(event.event_id) ) .map((event) => { - const content = event.content as Record; + const rawContent = event.content as Record; + if (isAnnotationRoomMessage(rawContent)) return null; + + const content = getLatestEditContent(event, timelineEvents); const relation = threadRelationFromContent(content); const isThreadReply = relation?.rel_type === RelationType.Thread || relation?.rel_type === 'm.thread'; @@ -272,6 +361,9 @@ export async function listTopicFeedPosts( if (!nextBatch || merged.length >= minRoots) break; } + const localPosts = buildPosts(mx, roomId, collectLocalRootPosts(mx, roomId)); + merged = mergeRootPosts(merged, localPosts); + return { posts: merged, nextBatch: nextBatch ?? null }; } @@ -283,11 +375,164 @@ function mergeRootPosts(existing: ForumPost[], incoming: ForumPost[]): ForumPost byId.set(post.eventId, post); continue; } - previous.replies = [...previous.replies, ...post.replies]; + const replyIds = new Set(previous.replies.map((reply) => reply.eventId)); + const mergedReplies = [ + ...previous.replies, + ...post.replies.filter((reply) => !replyIds.has(reply.eventId)), + ]; + byId.set(post.eventId, { ...post, replies: mergedReplies }); } return [...byId.values()].sort((a, b) => b.timestamp - a.timestamp); } +function mergeUniqueRoomEvents(...lists: IRoomEvent[][]): IRoomEvent[] { + const seen = new Set(); + const unique: IRoomEvent[] = []; + for (const list of lists) { + for (const event of list) { + const id = event.event_id; + if (!id || seen.has(id)) continue; + seen.add(id); + unique.push(event); + } + } + return unique; +} + +function isSecondaryMatrixEvent(mEvent: MatrixEvent): boolean { + const relation = mEvent.getRelation(); + if ( + relation?.rel_type === RelationType.Replace || + relation?.rel_type === RelationType.Annotation + ) { + return true; + } + return isAnnotationRoomMessage(mEvent.getContent()); +} + +function eventBelongsToThread(mEvent: MatrixEvent, rootEventId: string): boolean { + if (mEvent.getId() === rootEventId) return true; + + const relation = mEvent.getRelation(); + if (relation?.rel_type === RelationType.Thread && relation.event_id === rootEventId) { + return true; + } + + const contentRelation = threadRelationFromContent(mEvent.getContent()); + if ( + contentRelation?.rel_type === RelationType.Thread && + contentRelation.event_id === rootEventId + ) { + return true; + } + + return false; +} + +function matrixEventToRoomEvent(mEvent: MatrixEvent): IRoomEvent { + return mEvent.event as IRoomEvent; +} + +/** Thread events already in the local room timeline (includes unsent/local echo). */ +export function collectLocalThreadEvents( + mx: MatrixClient, + roomId: string, + rootEventId: string +): IRoomEvent[] { + const rootId = rootEventId.trim(); + const room = mx.getRoom(roomId); + if (!rootId || !room) return []; + + const seen = new Set(); + const events: IRoomEvent[] = []; + + const add = (mEvent: MatrixEvent | undefined | null) => { + if (!mEvent || mEvent.isRedacted()) return; + if (mEvent.getType() !== EventType.RoomMessage) return; + if (isSecondaryMatrixEvent(mEvent)) return; + if (!eventBelongsToThread(mEvent, rootId)) return; + const id = mEvent.getId(); + if (!id || seen.has(id)) return; + seen.add(id); + events.push(matrixEventToRoomEvent(mEvent)); + }; + + add(room.findEventById(rootId)); + + const thread = room.getThread(rootId); + if (thread) { + add(thread.rootEvent); + for (const reply of thread.events) add(reply); + for (const reply of thread.liveTimeline?.getEvents() ?? []) add(reply); + } + + for (const mEvent of room.getUnfilteredTimelineSet().getLiveTimeline().getEvents()) { + add(mEvent); + } + + const targetIds = new Set(seen); + events.push(...collectLocalReplaceEdits(room, targetIds)); + + return events; +} + +function collectLocalReplaceEdits(room: Room, targetIds: Set): IRoomEvent[] { + const events: IRoomEvent[] = []; + const seen = new Set(); + + for (const mEvent of room.getUnfilteredTimelineSet().getLiveTimeline().getEvents()) { + if (mEvent.isRedacted() || mEvent.getType() !== EventType.RoomMessage) continue; + const relation = mEvent.getRelation(); + if (relation?.rel_type !== RelationType.Replace) continue; + const targetId = relation.event_id; + if (!targetId || !targetIds.has(targetId)) continue; + const id = mEvent.getId(); + if (!id || seen.has(id)) continue; + seen.add(id); + events.push(matrixEventToRoomEvent(mEvent)); + } + + return events; +} + +function collectLocalRootPosts(mx: MatrixClient, roomId: string): IRoomEvent[] { + const room = mx.getRoom(roomId); + if (!room) return []; + + const seen = new Set(); + const events: IRoomEvent[] = []; + + for (const mEvent of room.getUnfilteredTimelineSet().getLiveTimeline().getEvents()) { + if (mEvent.isRedacted() || mEvent.getType() !== EventType.RoomMessage) continue; + if (isSecondaryMatrixEvent(mEvent)) continue; + const relation = threadRelationFromContent(mEvent.getContent()); + const isThreadReply = + relation?.rel_type === RelationType.Thread || relation?.rel_type === 'm.thread'; + if (isThreadReply) continue; + const id = mEvent.getId(); + if (!id || seen.has(id)) continue; + seen.add(id); + events.push(matrixEventToRoomEvent(mEvent)); + } + + const rootIds = new Set(events.map((event) => event.event_id).filter(Boolean) as string[]); + events.push(...collectLocalReplaceEdits(room, rootIds)); + + return events; +} + +export function loadPostThreadLocal( + mx: MatrixClient, + roomId: string, + rootEventId: string +): ForumPost | null { + const rootId = rootEventId.trim(); + const events = collectLocalThreadEvents(mx, roomId, rootId); + if (events.length === 0) return null; + const posts = buildPosts(mx, roomId, events); + return posts.find((post) => post.eventId === rootId) ?? posts[0] ?? null; +} + async function fetchRootEvent( mx: MatrixClient, roomId: string, @@ -314,10 +559,10 @@ export async function loadPostThread( rootEventId: string ): Promise { const rootId = rootEventId.trim(); - const rootEvent = await fetchRootEvent(mx, roomId, rootId); - if (!rootEvent?.event_id) return null; + const localEvents = collectLocalThreadEvents(mx, roomId, rootId); - const events: IRoomEvent[] = [rootEvent]; + const rootEvent = await fetchRootEvent(mx, roomId, rootId); + const apiEvents: IRoomEvent[] = rootEvent?.event_id ? [rootEvent] : []; const collectRelations = async ( relationType: RelationType | null, @@ -332,31 +577,32 @@ export async function loadPostThread( recurse: true, }); if (relPayload.chunk?.length) { - events.push(...(relPayload.chunk as IRoomEvent[])); + apiEvents.push(...(relPayload.chunk as IRoomEvent[])); } from = relPayload.next_batch ?? undefined; if (!from) break; } }; - try { - await collectRelations(RelationType.Thread, EventType.RoomMessage); - } catch { + if (rootEvent?.event_id) { try { - await collectRelations(null, EventType.RoomMessage); + await collectRelations(RelationType.Replace, EventType.RoomMessage); } catch { - // Still show root without replies. + // Root may have no replace relations. + } + try { + await collectRelations(RelationType.Thread, EventType.RoomMessage); + } catch { + try { + await collectRelations(null, EventType.RoomMessage); + } catch { + // Still show root without replies. + } } } - const seen = new Set(); - const unique: IRoomEvent[] = []; - for (const event of events) { - const id = event.event_id; - if (!id || seen.has(id)) continue; - seen.add(id); - unique.push(event); - } + const unique = mergeUniqueRoomEvents(localEvents, apiEvents); + if (unique.length === 0) return null; const posts = buildPosts(mx, roomId, unique); return posts.find((post) => post.eventId === rootId) ?? posts[0] ?? null; @@ -423,6 +669,54 @@ export async function sendForumPost( return res.event_id; } +/** Image/file/video root post with a forum title. */ +export async function sendForumMediaRoot( + mx: MatrixClient, + roomId: string, + title: string, + content: IContent +): Promise { + const trimmedTitle = title.trim(); + if (!trimmedTitle) { + throw new Error('Post title is required.'); + } + if (trimmedTitle.length > 140) { + throw new Error('Post title is too long.'); + } + + const res = await mx.sendMessage(roomId, { + ...content, + 'com.matrixsso.title': trimmedTitle, + }); + return res.event_id; +} + +/** Thread attachment (image, file, etc.) under a forum post root. */ +export async function sendForumThreadAttachment( + mx: MatrixClient, + roomId: string, + threadRootId: string, + content: IContent, + replyToEventId?: string +): Promise { + const rootId = threadRootId.trim(); + const parentId = replyToEventId?.trim() || rootId; + if (!rootId) { + throw new Error('Thread root event id is required.'); + } + + const res = await mx.sendMessage(roomId, { + ...content, + 'm.relates_to': { + rel_type: RelationType.Thread, + event_id: rootId, + is_falling_back: false, + 'm.in_reply_to': { event_id: parentId }, + } as IEventRelation, + }); + return res.event_id; +} + export type ForumReplyPayload = { plainText: string; formattedHtml?: string; diff --git a/src/app/features/forum/forumRichText.ts b/src/app/features/forum/forumRichText.ts index b969b9d..a46f598 100644 --- a/src/app/features/forum/forumRichText.ts +++ b/src/app/features/forum/forumRichText.ts @@ -7,8 +7,16 @@ export const FORUM_EDITOR_OUTPUT_OPTS: OutputOptions = { allowBlockMarkdown: true, }; +export function titleFromFormattedBody(html: string | undefined): string | undefined { + if (!html) return undefined; + const match = html.trim().match(/^]*>([\s\S]*?)<\/h1>/i); + if (!match) return undefined; + const text = match[1].replace(/<[^>]+>/g, '').trim(); + return text || undefined; +} + export function stripLeadingTitleHeading(html: string): string { - return html.replace(/^]*>[\s\S]*?<\/h1>\s*/i, '').trim(); + return html.replace(/^\s*]*>[\s\S]*?<\/h1>\s*/i, '').trim(); } export function bodyHtmlFromMessageContent( diff --git a/src/app/features/forum/forumTheme.css.ts b/src/app/features/forum/forumTheme.css.ts index 8f8ec01..9f3a949 100644 --- a/src/app/features/forum/forumTheme.css.ts +++ b/src/app/features/forum/forumTheme.css.ts @@ -96,6 +96,20 @@ export const ForumLeftColumn = style([ }, ]); +/** Feed column inside Cinny space PageNav. */ +export const ForumFeedSidebarRoot = style([ + DefaultReset, + { + display: 'flex', + flex: '1 1 0', + flexDirection: 'column', + height: '100%', + minHeight: 0, + minWidth: 0, + overflow: 'hidden', + }, +]); + export const ForumLeftHeader = style({ flexShrink: 0, borderBottom: `${config.borderWidth.B300} solid ${color.Background.ContainerLine}`, @@ -126,6 +140,7 @@ export const ForumListCard = style({ export const ForumDetailPane = style({ display: 'flex', + flex: '1 1 0', flexDirection: 'column', minHeight: 0, minWidth: 0, @@ -144,18 +159,32 @@ export const ForumTopicTargetFields = style({ /** @deprecated use ForumDetailPane */ export const ForumDetailAside = ForumDetailPane; -export const ForumTopicPostDetail = style({ +/** Scroll slot for folds `Scroll` in forum panels (post list + thread detail). */ +export const ForumPanelScroll = style({ flex: '1 1 0', minHeight: 0, - overflowX: 'hidden', - overflowY: 'auto', - overscrollBehavior: 'contain', + minWidth: 0, +}); + +export const ForumTopicPostDetailScroll = style([ + ForumPanelScroll, + { + backgroundColor: color.Surface.Container, + }, +]); + +export const ForumTopicPostDetailInner = style({ + boxSizing: 'border-box', padding: `0 ${forumDetailPad} ${forumDetailPad}`, paddingRight: forumDetailPadRight, - backgroundColor: color.Surface.Container, - boxSizing: 'border-box', }); +/** @deprecated use ForumTopicPostDetailScroll + ForumTopicPostDetailInner */ +export const ForumTopicPostDetail = style([ + ForumTopicPostDetailScroll, + ForumTopicPostDetailInner, +]); + export const ForumTopicPostDetailWithThread = style({}); export const ForumComposerBar = style({ @@ -392,13 +421,7 @@ export const ForumPostCountMeta = style({ minWidth: 0, }); -export const ForumPostScroll = style({ - flex: '1 1 0', - minHeight: 0, - overflowX: 'hidden', - overflowY: 'auto', - overscrollBehavior: 'contain', -}); +export const ForumPostScroll = style([ForumPanelScroll]); export const ForumPostList = style({ display: 'grid', @@ -526,6 +549,148 @@ export const ForumCategoryLabel = style({ textTransform: 'lowercase', }); +const forumThreadRailHitBase = { + background: 'transparent', + border: 0, + bottom: 0, + cursor: 'pointer', + left: 0, + margin: 0, + padding: 0, + position: 'absolute' as const, + top: 0, + width: toRem(20), + zIndex: 10, +}; + +export const ForumThreadRailHitRoot = style({ + ...forumThreadRailHitBase, + selectors: { + '&::after': { + ...threadLineBefore, + background: threadLineBase, + width: threadLineWidth, + }, + '&:hover::after': { + background: threadLineActive, + }, + }, +}); + +export const ForumThreadRailHitNested = style({ + ...forumThreadRailHitBase, + selectors: { + '&::after': { + ...threadLineBefore, + background: threadLineChild, + width: threadLineWidth, + }, + '&:hover::after': { + background: threadLineActive, + }, + }, +}); + +const forumThreadCollapsedRowBase = { + alignItems: 'stretch', + background: 'transparent', + border: 0, + boxSizing: 'border-box' as const, + cursor: 'pointer', + display: 'flex', + gap: threadRailGap, + marginBottom: threadLineGap, + marginTop: threadLineGap, + minWidth: 0, + padding: 0, + textAlign: 'left' as const, + width: '100%', +}; + +export const ForumThreadCollapsedRowRoot = style([DefaultReset, forumThreadCollapsedRowBase]); + +export const ForumThreadCollapsedRowNested = style([ + DefaultReset, + forumThreadCollapsedRowBase, + { + marginLeft: threadLevelIndent, + width: `calc(100% - ${threadLevelIndent})`, + }, +]); + +export const ForumThreadCollapsedRailRoot = style({ + alignSelf: 'stretch', + background: threadLineBase, + borderRadius: threadLineRadius, + flexShrink: 0, + minHeight: toRem(40), + transition: 'background 0.15s ease', + width: threadLineWidth, + selectors: { + [`${ForumThreadCollapsedRowRoot}:hover &`]: { + background: threadLineActive, + }, + }, +}); + +export const ForumThreadCollapsedRailNested = style({ + alignSelf: 'stretch', + background: threadLineChild, + borderRadius: threadLineRadius, + flexShrink: 0, + minHeight: toRem(40), + transition: 'background 0.15s ease', + width: threadLineWidth, + selectors: { + [`${ForumThreadCollapsedRowNested}:hover &`]: { + background: threadLineActive, + }, + }, +}); + +export const ForumThreadCollapsedMiniPost = style({ + background: color.Background.Container, + border: `${config.borderWidth.B300} solid ${threadLineBase}`, + borderRadius: toRem(6), + boxSizing: 'border-box', + display: 'flex', + flex: 1, + flexDirection: 'column', + gap: toRem(6), + justifyContent: 'center', + minHeight: toRem(40), + minWidth: 0, + padding: `${toRem(9)} ${toRem(11)}`, + transition: 'background 0.15s ease, border-color 0.15s ease', + selectors: { + [`${ForumThreadCollapsedRowRoot}:hover &, ${ForumThreadCollapsedRowNested}:hover &`]: { + background: color.Surface.ContainerHover, + borderColor: color.Primary.Main, + }, + }, +}); + +export const ForumThreadCollapsedMoreLabel = style({ + color: muted, + fontSize: toRem(13), + fontWeight: 600, + lineHeight: 1.3, + selectors: { + [`${ForumThreadCollapsedRowRoot}:hover &, ${ForumThreadCollapsedRowNested}:hover &`]: { + color: color.Primary.Main, + }, + }, +}); + +export const ForumThreadCollapsible = style({ + selectors: { + '&::before': { + opacity: 0, + pointerEvents: 'none', + }, + }, +}); + export const ForumReplyMeta = style({ alignItems: 'center', display: 'inline-flex', @@ -574,11 +739,38 @@ export const ForumEmptyDetail = style({ export const ForumTopicDetailRoot = style({ display: 'flex', + flex: '1 1 0', flexDirection: 'column', + minHeight: 0, minWidth: 0, width: '100%', }); +export const ForumTopicDetailBodyScroll = style([ + ForumPanelScroll, + { + flex: '1 1 0', + minHeight: 0, + }, +]); + +export const ForumTopicDetailBody = style({ + boxSizing: 'border-box', + padding: `0 ${forumDetailPad} ${forumDetailPad}`, + paddingRight: forumDetailPadRight, +}); + +export const ForumTopicPostDetailThreadLayout = style([ + ForumPanelScroll, + { + backgroundColor: color.Surface.Container, + boxSizing: 'border-box', + display: 'flex', + flexDirection: 'column', + minHeight: 0, + }, +]); + export const ForumTopicDetailHead = style({ alignItems: 'flex-start', background: color.Surface.Container, @@ -610,7 +802,16 @@ export const ForumTopicDetailOp = style({ marginRight: 0, maxWidth: '100%', minWidth: 0, - paddingBottom: toRem(36), + paddingBottom: toRem(12), + position: 'relative', +}); + +export const ForumThreadFooterComposer = style({ + backgroundColor: color.Surface.Container, + borderTop: `${config.borderWidth.B300} solid ${color.Surface.ContainerLine}`, + boxSizing: 'border-box', + flexShrink: 0, + padding: `${toRem(12)} ${forumDetailPadRight} ${toRem(8)} ${forumDetailPad}`, position: 'relative', }); @@ -630,6 +831,11 @@ export const ForumMessageBodyRich = style({ wordBreak: 'break-word', }); +export const ForumEventContentRoot = style({ + minWidth: 0, + width: '100%', +}); + globalStyle(`${ForumMessageBodyRich} p`, { margin: '0 0 0.65em', }); @@ -645,11 +851,35 @@ globalStyle(`${ForumMessageBodyRich} blockquote`, { color: muted, }); +const forumNativeScroll = `${ForumTopicPostDetailInner} pre, ${ForumMessageBodyRich} pre`; + globalStyle(`${ForumMessageBodyRich} pre`, { background: color.SurfaceVariant.Container, borderRadius: config.radii.R300, overflow: 'auto', padding: config.space.S300, + scrollbarWidth: 'thin', + scrollbarColor: `${color.Surface.ContainerLine} transparent`, +}); + +globalStyle(`${forumNativeScroll}::-webkit-scrollbar`, { + height: toRem(6), + width: toRem(6), +}); + +globalStyle(`${forumNativeScroll}::-webkit-scrollbar-track`, { + background: 'transparent', +}); + +globalStyle(`${forumNativeScroll}::-webkit-scrollbar-thumb`, { + backgroundColor: color.Surface.ContainerLine, + border: '2px solid transparent', + backgroundClip: 'padding-box', + borderRadius: config.radii.Pill, +}); + +globalStyle(`${forumNativeScroll}::-webkit-scrollbar-thumb:hover`, { + backgroundColor: color.SurfaceVariant.ContainerLine, }); globalStyle(`${ForumMessageBodyRich} code`, { @@ -672,28 +902,29 @@ globalStyle(`${ForumMessageBodyRich} img`, { borderRadius: config.radii.R300, }); -export const ForumReplyActionsFooter = style({ - bottom: toRem(6), - display: 'flex', - justifyContent: 'flex-end', - margin: 0, - pointerEvents: 'none', - position: 'absolute', - right: toRem(10), - zIndex: 2, - maxWidth: `calc(100% - ${toRem(12)})`, -}); +export const ForumMessageOptionsBase = style([ + DefaultReset, + { + position: 'absolute', + top: toRem(6), + right: toRem(6), + zIndex: 3, + // Extend hitbox for easier hover targeting (matches room messages). + paddingLeft: toRem(80), + marginLeft: toRem(-80), + pointerEvents: 'none', + }, +]); -export const ForumMessageActions = style({ - alignItems: 'center', - display: 'flex', - flexShrink: 0, - gap: toRem(3), - marginLeft: 'auto', - opacity: 0, - pointerEvents: 'none', - transition: 'opacity 0.16s ease', -}); +export const ForumMessageOptionsBar = style([ + DefaultReset, + { + padding: config.space.S100, + opacity: 0, + pointerEvents: 'none', + transition: 'opacity 0.12s ease', + }, +]); export const ForumMessageActionBtn = style({ alignItems: 'center', @@ -751,6 +982,10 @@ export const ForumFeedReplyForm = style({ marginTop: 0, }); +export const ForumThreadReplyTarget = style({ + borderLeft: `3px solid ${color.Primary.Main}`, +}); + export const ForumReplyToDeleted = style({ borderLeft: `4px solid ${color.SurfaceVariant.ContainerLine}`, color: muted, @@ -1044,33 +1279,45 @@ globalStyle(`${ForumPostModalEditorWrap} .${editorCss.EditorTextarea}`, { minHeight: toRem(180), }); -globalStyle(`${ForumThreadReplyInner}:hover ${ForumReplyActionsFooter} ${ForumMessageActions}`, { - opacity: 1, - pointerEvents: 'auto', -}); +const forumMessageOptionsVisible = `${ForumMessageOptionsBase}:hover ${ForumMessageOptionsBar}, ${ForumMessageOptionsBase}:focus-within ${ForumMessageOptionsBar}`; -globalStyle(`${ForumThreadReplyInner}:focus-within ${ForumReplyActionsFooter} ${ForumMessageActions}`, { - opacity: 1, - pointerEvents: 'auto', -}); +globalStyle( + `${ForumThreadReplyInner}:hover ${ForumMessageOptionsBase}, ${ForumThreadReplyInner}:focus-within ${ForumMessageOptionsBase}`, + { + pointerEvents: 'auto', + } +); -globalStyle(`${ForumThreadReplyInner}:has(${ForumMessageActionPanels}) ${ForumReplyActionsFooter}`, { +globalStyle( + `${ForumThreadReplyInner}:hover ${ForumMessageOptionsBar}, ${ForumThreadReplyInner}:focus-within ${ForumMessageOptionsBar}, ${forumMessageOptionsVisible}`, + { + opacity: 1, + pointerEvents: 'auto', + } +); + +globalStyle(`${ForumThreadReplyInner}:has(${ForumMessageActionPanels}) ${ForumMessageOptionsBase}`, { display: 'none', }); -globalStyle(`${ForumTopicDetailOp}:has(${ForumMessageActionPanels}) ${ForumReplyActionsFooter}`, { +globalStyle(`${ForumTopicDetailOp}:has(${ForumMessageActionPanels}) ${ForumMessageOptionsBase}`, { display: 'none', }); -globalStyle(`${ForumTopicDetailOp}:hover ${ForumReplyActionsFooter} ${ForumMessageActions}`, { - opacity: 1, - pointerEvents: 'auto', -}); +globalStyle( + `${ForumTopicDetailOp}:hover ${ForumMessageOptionsBase}, ${ForumTopicDetailOp}:focus-within ${ForumMessageOptionsBase}`, + { + pointerEvents: 'auto', + } +); -globalStyle(`${ForumTopicDetailOp}:focus-within ${ForumReplyActionsFooter} ${ForumMessageActions}`, { - opacity: 1, - pointerEvents: 'auto', -}); +globalStyle( + `${ForumTopicDetailOp}:hover ${ForumMessageOptionsBar}, ${ForumTopicDetailOp}:focus-within ${ForumMessageOptionsBar}, ${ForumTopicDetailOp} ${forumMessageOptionsVisible}`, + { + opacity: 1, + pointerEvents: 'auto', + } +); globalStyle( `${ForumThread}:has(> ${ForumThreadReply} > ${ForumThreadReplyInner}:hover)::before, ${ForumThread}:has(> ${ForumThreadReply} > ${ForumThreadReplyInner}:focus-within)::before`, @@ -1110,7 +1357,7 @@ globalStyle(`${ForumTopicDetailOp} ${ForumMessageBody}, ${ForumTopicDetailOp} ${ // Legacy aliases export const ForumApp = ForumTopicLiveApp; -export const ForumDetailScroll = ForumTopicPostDetail; +export const ForumDetailScroll = ForumTopicPostDetailScroll; export const ForumDetailComposerBar = ForumComposerBar; export const ForumChrome = style({ display: 'none' }); export const ForumBrand = style({ display: 'none' }); diff --git a/src/app/features/forum/index.ts b/src/app/features/forum/index.ts index 65a6e58..304cadb 100644 --- a/src/app/features/forum/index.ts +++ b/src/app/features/forum/index.ts @@ -1,4 +1,5 @@ -export { ForumBoardView } from './ForumBoardView'; +export { ForumFeedSidebar } from './ForumFeedSidebar'; +export { ForumBoardDetail } from './ForumBoardDetail'; export { ForumSortIcons } from './forumLucideIcons'; export { ForumSpaceShell } from './ForumSpaceShell'; export { ForumThreadDetail } from './ForumThreadDetail'; diff --git a/src/app/features/forum/useForumBoard.ts b/src/app/features/forum/useForumBoard.ts index 04c3ce5..87c7f64 100644 --- a/src/app/features/forum/useForumBoard.ts +++ b/src/app/features/forum/useForumBoard.ts @@ -1,8 +1,11 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { useSearchParams } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import { MatrixEvent, Room, RoomStateEvent } from 'matrix-js-sdk'; import { useAtomValue } from 'jotai'; import { useMatrixClient } from '../../hooks/useMatrixClient'; +import { useSelectedRoom } from '../../hooks/router/useSelectedRoom'; +import { getCanonicalAliasOrRoomId } from '../../utils/matrix'; +import { getSpaceLobbyPath } from '../../pages/pathUtils'; import { createRoomModalAtom } from '../../state/createRoomModal'; import { createSpaceModalAtom } from '../../state/createSpaceModal'; import { StateEvent } from '../../../types/matrix/room'; @@ -18,7 +21,14 @@ import { listTopicFeedPosts, loadAggregatedTopicFeed, } from './forumFeed'; -import type { ForumBoardQuery, ForumPublishedPost, ForumSection, ForumThreadSummary } from './types'; +import type { + ForumBoardQuery, + ForumPost, + ForumPublishedPost, + ForumSection, + ForumThreadSummary, +} from './types'; +import { useForumRoomLiveUpdates } from './useForumRoomLiveUpdates'; export type ForumBoardScope = { forumSpaceId: string; @@ -62,17 +72,24 @@ function scopesFromSelection( export function useForumBoard(scope: ForumBoardScope, forumSpace: Room) { const mx = useMatrixClient(); - const [searchParams, setSearchParams] = useSearchParams(); + const location = useLocation(); + const navigate = useNavigate(); + const selectedRoomId = useSelectedRoom(); + + const searchParams = useMemo( + () => new URLSearchParams(location.search), + [location.search] + ); const filterQuery: ForumBoardQuery = useMemo( () => ({ category: searchParams.get('category') || undefined, - topic: scope.topicRoomId || searchParams.get('topic') || undefined, + topic: searchParams.get('topic') || undefined, q: searchParams.get('q') || undefined, status: (searchParams.get('status') as ForumBoardQuery['status']) || 'all', sort: (searchParams.get('sort') as ForumBoardQuery['sort']) || 'hot', }), - [searchParams, scope.topicRoomId] + [searchParams] ); const query: ForumBoardQuery = useMemo( @@ -92,41 +109,53 @@ export function useForumBoard(scope: ForumBoardScope, forumSpace: Room) { const [postsNextBatch, setPostsNextBatch] = useState(null); const [error, setError] = useState(null); - const setQueryParam = useCallback( - (key: string, value: string | null) => { - setSearchParams( - (prev) => { - const next = new URLSearchParams(prev); - if (!value) next.delete(key); - else next.set(key, value); - if (key !== 'post') { - next.delete('post'); - next.delete('postRoom'); - } - return next; + const applyForumSearchParams = useCallback( + (mutate: (next: URLSearchParams) => void) => { + const next = new URLSearchParams(location.search); + mutate(next); + const search = next.toString(); + + const onTopicRoomRoute = + Boolean(selectedRoomId) && selectedRoomId !== forumSpace.roomId; + + navigate( + { + pathname: onTopicRoomRoute + ? getSpaceLobbyPath(getCanonicalAliasOrRoomId(mx, forumSpace.roomId)) + : location.pathname, + search: search ? `?${search}` : '', }, { replace: true } ); }, - [setSearchParams] + [location.pathname, location.search, navigate, mx, forumSpace.roomId, selectedRoomId] + ); + + const setQueryParam = useCallback( + (key: string, value: string | null) => { + applyForumSearchParams((next) => { + if (!value) next.delete(key); + else next.set(key, value); + if (key !== 'post') { + next.delete('post'); + next.delete('postRoom'); + } + }); + }, + [applyForumSearchParams] ); const setCategory = useCallback( (category: string | null) => { - setSearchParams( - (prev) => { - const next = new URLSearchParams(prev); - if (!category) next.delete('category'); - else next.set('category', category); - next.delete('topic'); - next.delete('post'); - next.delete('postRoom'); - return next; - }, - { replace: true } - ); + applyForumSearchParams((next) => { + if (!category) next.delete('category'); + else next.set('category', category); + next.delete('topic'); + next.delete('post'); + next.delete('postRoom'); + }); }, - [setSearchParams] + [applyForumSearchParams] ); const loadSections = useCallback(async () => { @@ -142,8 +171,8 @@ export function useForumBoard(scope: ForumBoardScope, forumSpace: Room) { } }, [mx, scope.forumSpaceId]); - const refreshThreads = useCallback(async () => { - setLoadingThreads(true); + const refreshThreads = useCallback(async (options?: { silent?: boolean }) => { + if (!options?.silent) setLoadingThreads(true); setError(null); try { const roomScopes = scopesFromSelection(sections, filterQuery.category, filterQuery.topic); @@ -172,7 +201,7 @@ export function useForumBoard(scope: ForumBoardScope, forumSpace: Room) { } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load forum posts'); } finally { - setLoadingThreads(false); + if (!options?.silent) setLoadingThreads(false); } }, [mx, sections, filterQuery]); @@ -214,17 +243,29 @@ export function useForumBoard(scope: ForumBoardScope, forumSpace: Room) { }, [createSpaceModal, createRoomModal, loadSections]); useEffect(() => { - if (!loadingSections) { - refreshThreads(); - } - }, [loadingSections, refreshThreads]); + if (loadingSections) return; + void refreshThreads(); + }, [ + loadingSections, + refreshThreads, + filterQuery.category, + filterQuery.topic, + filterQuery.q, + filterQuery.status, + filterQuery.sort, + ]); - useEffect(() => { - const timer = window.setInterval(() => { - refreshThreads(); - }, 30_000); - return () => window.clearInterval(timer); - }, [refreshThreads]); + const scopedRooms = useMemo(() => { + if (loadingSections) return []; + const roomScopes = scopesFromSelection(sections, filterQuery.category, filterQuery.topic); + return roomScopes + .map((scope) => mx.getRoom(scope.roomId)) + .filter((room): room is Room => room !== null); + }, [loadingSections, sections, filterQuery.category, filterQuery.topic, mx]); + + useForumRoomLiveUpdates(scopedRooms, () => { + void refreshThreads({ silent: true }); + }); const selectedPostId = query.post ?? null; const selectedThread = threads.find((t) => t.eventId === selectedPostId); @@ -233,23 +274,18 @@ export function useForumBoard(scope: ForumBoardScope, forumSpace: Room) { const selectPost = useCallback( (eventId: string | null, topicRoomId?: string) => { - setSearchParams( - (prev) => { - const next = new URLSearchParams(prev); - if (!eventId) { - next.delete('post'); - next.delete('postRoom'); - } else { - next.set('post', eventId); - if (topicRoomId) next.set('postRoom', topicRoomId); - else next.delete('postRoom'); - } - return next; - }, - { replace: true } - ); + applyForumSearchParams((next) => { + if (!eventId) { + next.delete('post'); + next.delete('postRoom'); + } else { + next.set('post', eventId); + if (topicRoomId) next.set('postRoom', topicRoomId); + else next.delete('postRoom'); + } + }); }, - [setSearchParams] + [applyForumSearchParams] ); const insertThreadFromNewPost = useCallback( diff --git a/src/app/features/forum/useForumMessageReactions.ts b/src/app/features/forum/useForumMessageReactions.ts new file mode 100644 index 0000000..0fae7e7 --- /dev/null +++ b/src/app/features/forum/useForumMessageReactions.ts @@ -0,0 +1,135 @@ +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { + EventType, + MatrixEvent, + RelationType, + Room, + RoomEvent, +} from 'matrix-js-sdk'; +import { RelationsEvent } from 'matrix-js-sdk/lib/models/relations'; +import { MessageEvent } from '../../../types/matrix/room'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; +import { usePowerLevels } from '../../hooks/usePowerLevels'; +import { useRoomCreators } from '../../hooks/useRoomCreators'; +import { useRoomPermissions } from '../../hooks/useRoomPermissions'; +import { getEventReactions, getReactionContent } from '../../utils/room'; +import { eventWithShortcode, factoryEventSentBy } from '../../utils/matrix'; + +export function useForumMessageReactions(room: Room, mEvent: MatrixEvent | undefined) { + const mx = useMatrixClient(); + const [relationsRevision, setRelationsRevision] = useState(0); + const powerLevels = usePowerLevels(room); + const creators = useRoomCreators(room); + const permissions = useRoomPermissions(creators, powerLevels); + + const canSendReaction = permissions.event(MessageEvent.Reaction, mx.getSafeUserId()); + const canPinEvent = permissions.stateEvent('m.room.pinned_events', mx.getSafeUserId()); + + const roomTimelineSet = room.getUnfilteredTimelineSet(); + const eventId = mEvent?.getId(); + + const bumpRelations = useCallback(() => { + setRelationsRevision((revision) => revision + 1); + }, []); + + useEffect(() => { + if (!eventId) return undefined; + + const isReactionForEvent = (matrixEvent: MatrixEvent) => { + if (matrixEvent.getType() !== EventType.Reaction) return false; + return matrixEvent.getRelation()?.event_id === eventId; + }; + + const handleTimeline = (matrixEvent: MatrixEvent, eventRoom?: Room) => { + if (eventRoom?.roomId !== room.roomId) return; + if (isReactionForEvent(matrixEvent)) { + bumpRelations(); + } + }; + + const handleRedaction = (_matrixEvent: MatrixEvent, eventRoom?: Room) => { + if (eventRoom?.roomId !== room.roomId) return; + bumpRelations(); + }; + + mx.on(RoomEvent.Timeline, handleTimeline); + mx.on(RoomEvent.Redaction, handleRedaction); + + return () => { + mx.removeListener(RoomEvent.Timeline, handleTimeline); + mx.removeListener(RoomEvent.Redaction, handleRedaction); + }; + }, [bumpRelations, eventId, mx, room.roomId]); + + useEffect(() => { + if (!eventId) return undefined; + + const relations = getEventReactions(roomTimelineSet, eventId); + if (!relations) return undefined; + + const handleRelationsUpdate = () => { + bumpRelations(); + }; + + relations.on(RelationsEvent.Add, handleRelationsUpdate); + relations.on(RelationsEvent.Redaction, handleRelationsUpdate); + relations.on(RelationsEvent.Remove, handleRelationsUpdate); + + return () => { + relations.removeListener(RelationsEvent.Add, handleRelationsUpdate); + relations.removeListener(RelationsEvent.Redaction, handleRelationsUpdate); + relations.removeListener(RelationsEvent.Remove, handleRelationsUpdate); + }; + }, [bumpRelations, eventId, relationsRevision, roomTimelineSet]); + + const reactionRelations = useMemo(() => { + if (!eventId) return undefined; + return getEventReactions(roomTimelineSet, eventId); + // relationsRevision forces re-read after timeline / relation updates + }, [eventId, relationsRevision, roomTimelineSet]); + + const hasReactions = Boolean(reactionRelations?.getSortedAnnotationsByKey()?.length); + + const handleReactionToggle = useCallback( + (targetEventId: string, key: string, shortcode?: string) => { + const relations = getEventReactions(roomTimelineSet, targetEventId); + const allReactions = relations?.getSortedAnnotationsByKey() ?? []; + const [, reactionsSet] = allReactions.find(([k]) => k === key) ?? []; + const reactions = reactionsSet ? Array.from(reactionsSet) : []; + const myReaction = reactions.find(factoryEventSentBy(mx.getSafeUserId())); + + if (myReaction?.isRelation()) { + const myReactionId = myReaction.getId(); + if (myReactionId) { + mx.redactEvent(room.roomId, myReactionId).finally(() => { + bumpRelations(); + window.setTimeout(bumpRelations, 250); + }); + } + return; + } + + const rShortcode = + shortcode || + (reactions.find(eventWithShortcode)?.getContent().shortcode as string | undefined); + mx.sendEvent( + room.roomId, + MessageEvent.Reaction as never, + getReactionContent(targetEventId, key, rShortcode) + ).finally(() => { + bumpRelations(); + window.setTimeout(bumpRelations, 250); + }); + }, + [bumpRelations, mx, room.roomId, roomTimelineSet] + ); + + return { + canSendReaction, + canPinEvent, + reactionRelations, + hasReactions, + handleReactionToggle, + eventId, + }; +} diff --git a/src/app/features/forum/useForumMessageRenderOptions.ts b/src/app/features/forum/useForumMessageRenderOptions.ts new file mode 100644 index 0000000..8997919 --- /dev/null +++ b/src/app/features/forum/useForumMessageRenderOptions.ts @@ -0,0 +1,70 @@ +import { useMemo } from 'react'; +import { Room } from 'matrix-js-sdk'; +import { HTMLReactParserOptions } from 'html-react-parser'; +import { Opts as LinkifyOpts } from 'linkifyjs'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; +import { useMediaAuthentication } from '../../hooks/useMediaAuthentication'; +import { useMentionClickHandler } from '../../hooks/useMentionClickHandler'; +import { useSpoilerClickHandler } from '../../hooks/useSpoilerClickHandler'; +import { + LINKIFY_OPTS, + factoryRenderLinkifyWithMention, + getReactCustomHtmlParser, + makeMentionCustomProps, + renderMatrixMention, +} from '../../plugins/react-custom-html-parser'; +import { + combineEmbedFilters, + useRoomEmbedFilters, + useRoomWideEmbedFilters, +} from '../../hooks/useRoomEmbedFilters'; +import { useSetting } from '../../state/hooks/settings'; +import { MessageLayout, settingsAtom } from '../../state/settings'; + +export function useForumMessageRenderOptions(room: Room | null | undefined) { + const mx = useMatrixClient(); + const useAuthentication = useMediaAuthentication(); + const [mediaAutoLoad] = useSetting(settingsAtom, 'mediaAutoLoad'); + const [showUrlPreview] = useSetting(settingsAtom, 'urlPreview'); + const [messageLayout] = useSetting(settingsAtom, 'messageLayout'); + + const [personalEmbedFilters] = useRoomEmbedFilters(room ?? undefined); + const roomWideEmbedFilters = useRoomWideEmbedFilters(room ?? undefined); + const combinedEmbedFilters = useMemo( + () => combineEmbedFilters(personalEmbedFilters, roomWideEmbedFilters), + [personalEmbedFilters, roomWideEmbedFilters] + ); + + const mentionClickHandler = useMentionClickHandler(room?.roomId ?? ''); + const spoilerClickHandler = useSpoilerClickHandler(); + + const linkifyOpts = useMemo( + () => ({ + ...LINKIFY_OPTS, + render: factoryRenderLinkifyWithMention((href) => + renderMatrixMention(mx, room?.roomId ?? '', href, makeMentionCustomProps(mentionClickHandler)) + ), + }), + [mx, room?.roomId, mentionClickHandler] + ); + + const htmlReactParserOptions = useMemo( + () => + getReactCustomHtmlParser(mx, room?.roomId ?? '', { + linkifyOpts, + useAuthentication, + handleSpoilerClick: spoilerClickHandler, + handleMentionClick: mentionClickHandler, + }), + [mx, room?.roomId, linkifyOpts, spoilerClickHandler, mentionClickHandler, useAuthentication] + ); + + return { + linkifyOpts, + htmlReactParserOptions, + mediaAutoLoad, + showUrlPreview, + outlineAttachment: messageLayout === MessageLayout.Bubble, + disabledEmbedPatterns: combinedEmbedFilters, + }; +} diff --git a/src/app/features/forum/useForumRoomLiveUpdates.ts b/src/app/features/forum/useForumRoomLiveUpdates.ts new file mode 100644 index 0000000..c7b4eae --- /dev/null +++ b/src/app/features/forum/useForumRoomLiveUpdates.ts @@ -0,0 +1,114 @@ +import { useEffect, useMemo, useRef } from 'react'; +import { + EventType, + MatrixEvent, + RelationType, + Room, + RoomEvent, +} from 'matrix-js-sdk'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; + +type ForumRoomLiveOptions = { + /** When set, only sync for events in this thread root. */ + threadRootId?: string; + /** Debounce window in ms. */ + wait?: number; +}; + +function isForumThreadEvent(mEvent: MatrixEvent, threadRootId: string): boolean { + if (mEvent.getId() === threadRootId) return true; + + const relation = mEvent.getRelation(); + if (relation?.rel_type === RelationType.Thread && relation.event_id === threadRootId) { + return true; + } + + const content = mEvent.getContent(); + const relates = content['m.relates_to']; + if (relates && typeof relates === 'object') { + const rel = relates as { rel_type?: string; event_id?: string }; + if (rel.rel_type === RelationType.Thread && rel.event_id === threadRootId) { + return true; + } + } + + if (relation?.rel_type === RelationType.Replace && relation.event_id === threadRootId) { + return true; + } + + return false; +} + +function isSecondaryMatrixEvent(mEvent: MatrixEvent): boolean { + const relation = mEvent.getRelation(); + return ( + relation?.rel_type === RelationType.Replace || + relation?.rel_type === RelationType.Annotation + ); +} + +function isForumFeedEvent(mEvent: MatrixEvent): boolean { + const type = mEvent.getType(); + return type === EventType.RoomMessage || type === EventType.RoomMessageEncrypted; +} + +function isRelevantEvent(mEvent: MatrixEvent, threadRootId?: string): boolean { + if (mEvent.getType() === EventType.Reaction) return false; + if (isSecondaryMatrixEvent(mEvent)) { + if (!threadRootId) return false; + const targetId = mEvent.getRelation()?.event_id; + return targetId === threadRootId; + } + if (threadRootId) { + return isForumThreadEvent(mEvent, threadRootId); + } + return isForumFeedEvent(mEvent); +} + +/** Debounced live sync when topic room timelines change. */ +export function useForumRoomLiveUpdates( + rooms: Room[], + onUpdate: () => void, + options: ForumRoomLiveOptions = {} +) { + const mx = useMatrixClient(); + const { threadRootId, wait = 200 } = options; + const onUpdateRef = useRef(onUpdate); + onUpdateRef.current = onUpdate; + + const roomIds = useMemo(() => new Set(rooms.map((room) => room.roomId)), [rooms]); + const roomKey = useMemo(() => [...roomIds].sort().join(','), [roomIds]); + + useEffect(() => { + if (!roomKey) return undefined; + + let timeoutId: number | undefined; + + const scheduleUpdate = () => { + if (timeoutId) window.clearTimeout(timeoutId); + timeoutId = window.setTimeout(() => { + onUpdateRef.current(); + }, wait); + }; + + const handleTimeline = (mEvent: MatrixEvent, eventRoom?: Room) => { + if (!eventRoom || !roomIds.has(eventRoom.roomId)) return; + if (!isRelevantEvent(mEvent, threadRootId)) return; + scheduleUpdate(); + }; + + const handleRedaction = (_mEvent: MatrixEvent, eventRoom?: Room) => { + if (!eventRoom || !roomIds.has(eventRoom.roomId)) return; + scheduleUpdate(); + }; + + mx.on(RoomEvent.Timeline, handleTimeline); + mx.on(RoomEvent.Redaction, handleRedaction); + + return () => { + if (timeoutId) window.clearTimeout(timeoutId); + mx.removeListener(RoomEvent.Timeline, handleTimeline); + mx.removeListener(RoomEvent.Redaction, handleRedaction); + }; + }, [mx, roomKey, roomIds, threadRootId, wait]); +} diff --git a/src/app/features/join-before-navigate/JoinBeforeNavigate.tsx b/src/app/features/join-before-navigate/JoinBeforeNavigate.tsx index 4b1e03c..f43c80b 100644 --- a/src/app/features/join-before-navigate/JoinBeforeNavigate.tsx +++ b/src/app/features/join-before-navigate/JoinBeforeNavigate.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { Box, Icon, IconButton, Icons, Scroll, Text, toRem } from 'folds'; +import { Box, IconButton, Scroll, Text, toRem } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import { useAtomValue } from 'jotai'; import { RoomCard } from '../../components/room-card'; import { RoomTopicViewer } from '../../components/room-topic-viewer'; diff --git a/src/app/features/lobby/DnD.tsx b/src/app/features/lobby/DnD.tsx index 5fd7e90..b82f7aa 100644 --- a/src/app/features/lobby/DnD.tsx +++ b/src/app/features/lobby/DnD.tsx @@ -7,7 +7,8 @@ import { import { autoScrollForElements } from '@atlaskit/pragmatic-drag-and-drop-auto-scroll/element'; import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine'; import classNames from 'classnames'; -import { Box, Icon, Icons, as } from 'folds'; +import { Box, as } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import { HierarchyItem } from '../../hooks/useSpaceHierarchy'; import * as css from './DnD.css'; diff --git a/src/app/features/lobby/HierarchyItemMenu.tsx b/src/app/features/lobby/HierarchyItemMenu.tsx index c6aff74..dc039de 100644 --- a/src/app/features/lobby/HierarchyItemMenu.tsx +++ b/src/app/features/lobby/HierarchyItemMenu.tsx @@ -1,20 +1,7 @@ import React, { MouseEventHandler, useCallback, useEffect, useState } from 'react'; import FocusTrap from 'focus-trap-react'; -import { - Box, - IconButton, - Icon, - Icons, - PopOut, - Menu, - MenuItem, - Text, - RectCords, - config, - Line, - Spinner, - toRem, -} from 'folds'; +import { Box, IconButton, PopOut, Menu, MenuItem, Text, RectCords, config, Line, Spinner, toRem } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import { HierarchyItem } from '../../hooks/useSpaceHierarchy'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { MSpaceChildContent, StateEvent } from '../../../types/matrix/room'; diff --git a/src/app/features/lobby/Lobby.tsx b/src/app/features/lobby/Lobby.tsx index 5154f8b..9c26484 100644 --- a/src/app/features/lobby/Lobby.tsx +++ b/src/app/features/lobby/Lobby.tsx @@ -1,5 +1,6 @@ import React, { MouseEventHandler, useCallback, useMemo, useRef, useState } from 'react'; -import { Box, Chip, Icon, IconButton, Icons, Line, Scroll, Spinner, Text, config } from 'folds'; +import { Box, Chip, IconButton, Line, Scroll, Spinner, Text, config } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import { useVirtualizer } from '@tanstack/react-virtual'; import { useAtom, useAtomValue } from 'jotai'; import { useNavigate } from 'react-router-dom'; @@ -41,7 +42,7 @@ import { StateEvent } from '../../../types/matrix/room'; import { CanDropCallback, useDnDMonitor } from './DnD'; import { ASCIILexicalTable, orderKeys } from '../../utils/ASCIILexicalTable'; import { getStateEvent, shouldShowForumLobby } from '../../utils/room'; -import { ForumSpaceShell } from '../forum/ForumSpaceShell'; +import { ForumBoardDetail } from '../forum/ForumBoardDetail'; import { useClosedLobbyCategoriesAtom } from '../../state/hooks/closedLobbyCategories'; import { makeCinnySpacesContent, @@ -151,7 +152,7 @@ const useCanDropLobbyItem = ( }; function ForumSpaceLobby() { - return ; + return ; } export function Lobby() { diff --git a/src/app/features/lobby/LobbyHeader.tsx b/src/app/features/lobby/LobbyHeader.tsx index bd00fe3..0a8540d 100644 --- a/src/app/features/lobby/LobbyHeader.tsx +++ b/src/app/features/lobby/LobbyHeader.tsx @@ -1,21 +1,6 @@ import React, { MouseEventHandler, forwardRef, useEffect, useMemo, useRef, useState } from 'react'; -import { - Avatar, - Box, - Icon, - IconButton, - Icons, - Line, - Menu, - MenuItem, - PopOut, - RectCords, - Text, - Tooltip, - TooltipProvider, - config, - toRem, -} from 'folds'; +import { Avatar, Box, IconButton, Line, Menu, MenuItem, PopOut, RectCords, Text, Tooltip, TooltipProvider, config, toRem } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import FocusTrap from 'focus-trap-react'; import { PageHeader } from '../../components/page'; import { useSetSetting } from '../../state/hooks/settings'; diff --git a/src/app/features/lobby/RoomItem.tsx b/src/app/features/lobby/RoomItem.tsx index 994cda0..1cce168 100644 --- a/src/app/features/lobby/RoomItem.tsx +++ b/src/app/features/lobby/RoomItem.tsx @@ -1,23 +1,6 @@ import React, { MouseEventHandler, ReactNode, useCallback, useRef } from 'react'; -import { - Avatar, - Badge, - Box, - Chip, - Icon, - Icons, - Line, - Overlay, - OverlayBackdrop, - OverlayCenter, - Spinner, - Text, - Tooltip, - TooltipProvider, - as, - color, - toRem, -} from 'folds'; +import { Avatar, Badge, Box, Chip, Line, Overlay, OverlayBackdrop, OverlayCenter, Spinner, Text, Tooltip, TooltipProvider, as, color, toRem } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import FocusTrap from 'focus-trap-react'; import { JoinRule, MatrixError, Room } from 'matrix-js-sdk'; import { IHierarchyRoom } from 'matrix-js-sdk/lib/@types/spaces'; diff --git a/src/app/features/lobby/SpaceAddControls.tsx b/src/app/features/lobby/SpaceAddControls.tsx index 2eaa425..5588e33 100644 --- a/src/app/features/lobby/SpaceAddControls.tsx +++ b/src/app/features/lobby/SpaceAddControls.tsx @@ -1,6 +1,7 @@ import React, { MouseEventHandler, useState } from 'react'; import FocusTrap from 'focus-trap-react'; -import { Box, Chip, Icon, Icons, Menu, MenuItem, PopOut, RectCords, Text, config } from 'folds'; +import { Box, Chip, Menu, MenuItem, PopOut, RectCords, Text, config } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import type { HierarchyItem } from '../../hooks/useSpaceHierarchy'; import { useOpenCreateRoomModal } from '../../state/hooks/createRoomModal'; import { useOpenCreateSpaceModal } from '../../state/hooks/createSpaceModal'; diff --git a/src/app/features/lobby/SpaceItem.tsx b/src/app/features/lobby/SpaceItem.tsx index 1f39236..d0d15c3 100644 --- a/src/app/features/lobby/SpaceItem.tsx +++ b/src/app/features/lobby/SpaceItem.tsx @@ -1,21 +1,6 @@ import React, { MouseEventHandler, ReactNode, useCallback, useRef, useState } from 'react'; -import { - Box, - Avatar, - Text, - Chip, - Icon, - Icons, - as, - Badge, - toRem, - Spinner, - PopOut, - Menu, - MenuItem, - RectCords, - config, -} from 'folds'; +import { Box, Avatar, Text, Chip, as, Badge, toRem, Spinner, PopOut, Menu, MenuItem, RectCords, config } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import FocusTrap from 'focus-trap-react'; import classNames from 'classnames'; import { MatrixError, Room } from 'matrix-js-sdk'; diff --git a/src/app/features/message-search/MessageSearch.tsx b/src/app/features/message-search/MessageSearch.tsx index 26085b5..bfe90eb 100644 --- a/src/app/features/message-search/MessageSearch.tsx +++ b/src/app/features/message-search/MessageSearch.tsx @@ -1,5 +1,6 @@ import React, { RefObject, useEffect, useMemo, useRef } from 'react'; -import { Text, Box, Icon, Icons, config, Spinner, IconButton, Line, toRem } from 'folds'; +import { Text, Box, config, Spinner, IconButton, Line, toRem } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import { useAtomValue } from 'jotai'; import { useVirtualizer } from '@tanstack/react-virtual'; import { useInfiniteQuery } from '@tanstack/react-query'; diff --git a/src/app/features/message-search/SearchFilters.tsx b/src/app/features/message-search/SearchFilters.tsx index 929dd1e..49edccd 100644 --- a/src/app/features/message-search/SearchFilters.tsx +++ b/src/app/features/message-search/SearchFilters.tsx @@ -6,25 +6,8 @@ import React, { useRef, useState, } from 'react'; -import { - Box, - Chip, - Text, - Icon, - Icons, - Line, - config, - PopOut, - Menu, - MenuItem, - Header, - toRem, - Scroll, - Button, - Input, - Badge, - RectCords, -} from 'folds'; +import { Box, Chip, Text, Line, config, PopOut, Menu, MenuItem, Header, toRem, Scroll, Button, Input, Badge, RectCords } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import { SearchOrderBy } from 'matrix-js-sdk'; import FocusTrap from 'focus-trap-react'; import { useVirtualizer } from '@tanstack/react-virtual'; diff --git a/src/app/features/message-search/SearchInput.tsx b/src/app/features/message-search/SearchInput.tsx index 533eb5f..5b9a93a 100644 --- a/src/app/features/message-search/SearchInput.tsx +++ b/src/app/features/message-search/SearchInput.tsx @@ -1,6 +1,7 @@ import React, { FormEventHandler, RefObject } from 'react'; -import { Box, Text, Input, Icon, Icons, Spinner, Chip, config } from 'folds'; +import { Box, Text, Input, Spinner, Chip, config } from 'folds'; +import { Icon, Icons } from '../../components/icons'; type SearchProps = { active?: boolean; loading?: boolean; diff --git a/src/app/features/message-search/SearchResultGroup.tsx b/src/app/features/message-search/SearchResultGroup.tsx index e7a56eb..8d003ac 100644 --- a/src/app/features/message-search/SearchResultGroup.tsx +++ b/src/app/features/message-search/SearchResultGroup.tsx @@ -2,7 +2,8 @@ import React, { MouseEventHandler, useMemo } from 'react'; import { IEventWithRoomId, JoinRule, RelationType, Room } from 'matrix-js-sdk'; import { HTMLReactParserOptions } from 'html-react-parser'; -import { Avatar, Box, Chip, Header, Icon, Icons, Text, config } from 'folds'; +import { Avatar, Box, Chip, Header, Text, config } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import { Opts as LinkifyOpts } from 'linkifyjs'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { diff --git a/src/app/features/room-nav/CallParticipantsIndicator.tsx b/src/app/features/room-nav/CallParticipantsIndicator.tsx index ffef8ea..95cbd3d 100644 --- a/src/app/features/room-nav/CallParticipantsIndicator.tsx +++ b/src/app/features/room-nav/CallParticipantsIndicator.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { Avatar, Box, Text, Icon, Icons, config } from 'folds'; +import { Avatar, Box, Text, config } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { useRoomCallMembers } from '../call/useRoomCallMembers'; import { getMemberAvatarMxc, getMemberDisplayName } from '../../utils/room'; diff --git a/src/app/features/room-nav/RoomNavCategoryButton.tsx b/src/app/features/room-nav/RoomNavCategoryButton.tsx index 3af8aea..7551e22 100644 --- a/src/app/features/room-nav/RoomNavCategoryButton.tsx +++ b/src/app/features/room-nav/RoomNavCategoryButton.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { as, Chip, Icon, Icons, Text } from 'folds'; +import { as, Chip, Text } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import classNames from 'classnames'; import * as css from './styles.css'; diff --git a/src/app/features/room-nav/RoomNavItem.tsx b/src/app/features/room-nav/RoomNavItem.tsx index a1cc516..6ec0f42 100644 --- a/src/app/features/room-nav/RoomNavItem.tsx +++ b/src/app/features/room-nav/RoomNavItem.tsx @@ -2,25 +2,8 @@ import React, { MouseEventHandler, forwardRef, useState, useMemo } from 'react'; import { Room, EventTimeline } from 'matrix-js-sdk'; import { useAtomValue } from 'jotai'; import { useNavigate } from 'react-router-dom'; -import { - Avatar, - Box, - Icon, - IconButton, - Icons, - Text, - Menu, - MenuItem, - config, - PopOut, - toRem, - Line, - RectCords, - Badge, - Spinner, - Tooltip, - TooltipProvider, -} from 'folds'; +import { Avatar, Box, IconButton, Text, Menu, MenuItem, config, PopOut, toRem, Line, RectCords, Badge, Spinner, Tooltip, TooltipProvider } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import { useFocusWithin, useHover } from 'react-aria'; import FocusTrap from 'focus-trap-react'; import { NavItem, NavItemContent, NavItemOptions, NavLink } from '../../components/nav'; diff --git a/src/app/features/room-nav/UnjoinedSubRoomItem.tsx b/src/app/features/room-nav/UnjoinedSubRoomItem.tsx index bdd6248..23333e0 100644 --- a/src/app/features/room-nav/UnjoinedSubRoomItem.tsx +++ b/src/app/features/room-nav/UnjoinedSubRoomItem.tsx @@ -1,17 +1,6 @@ import React, { useCallback } from 'react'; -import { - Box, - Chip, - Icon, - Icons, - Spinner, - Text, - Tooltip, - TooltipProvider, - color, - config, - toRem, -} from 'folds'; +import { Box, Chip, Spinner, Text, Tooltip, TooltipProvider, color, config, toRem } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import { MatrixError, Room } from 'matrix-js-sdk'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback'; diff --git a/src/app/features/room-settings/RoomSettings.tsx b/src/app/features/room-settings/RoomSettings.tsx index 904f325..42a3e9e 100644 --- a/src/app/features/room-settings/RoomSettings.tsx +++ b/src/app/features/room-settings/RoomSettings.tsx @@ -1,6 +1,7 @@ import React, { useMemo, useState } from 'react'; import { useAtomValue } from 'jotai'; -import { Avatar, Box, config, Icon, IconButton, Icons, IconSrc, MenuItem, Text } from 'folds'; +import { Avatar, Box, config, IconButton, MenuItem, Text } from 'folds'; +import { Icon, Icons, IconSrc } from '../../components/icons'; import { JoinRule } from 'matrix-js-sdk'; import { PageNav, PageNavContent, PageNavHeader, PageRoot } from '../../components/page'; import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize'; diff --git a/src/app/features/room-settings/general/General.tsx b/src/app/features/room-settings/general/General.tsx index 92eb32e..d8c6aee 100644 --- a/src/app/features/room-settings/general/General.tsx +++ b/src/app/features/room-settings/general/General.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { Box, Icon, IconButton, Icons, Scroll, Text } from 'folds'; +import { Box, IconButton, Scroll, Text } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { Page, PageContent, PageHeader } from '../../../components/page'; import { usePowerLevels } from '../../../hooks/usePowerLevels'; import { useRoom } from '../../../hooks/useRoom'; diff --git a/src/app/features/room-settings/permissions/Permissions.tsx b/src/app/features/room-settings/permissions/Permissions.tsx index 7572a71..44aae2b 100644 --- a/src/app/features/room-settings/permissions/Permissions.tsx +++ b/src/app/features/room-settings/permissions/Permissions.tsx @@ -1,5 +1,6 @@ import React, { useState } from 'react'; -import { Box, Icon, IconButton, Icons, Scroll, Text } from 'folds'; +import { Box, IconButton, Scroll, Text } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { Page, PageContent, PageHeader } from '../../../components/page'; import { useRoom } from '../../../hooks/useRoom'; import { usePowerLevels } from '../../../hooks/usePowerLevels'; diff --git a/src/app/features/room-settings/sub-rooms/SubRooms.tsx b/src/app/features/room-settings/sub-rooms/SubRooms.tsx index 651f12d..87bad3b 100644 --- a/src/app/features/room-settings/sub-rooms/SubRooms.tsx +++ b/src/app/features/room-settings/sub-rooms/SubRooms.tsx @@ -1,15 +1,6 @@ import React, { useState, useCallback } from 'react'; -import { - Box, - Button, - Text, - Icon, - Icons, - config, - IconButton, - Chip, - Line, -} from 'folds'; +import { Box, Button, Text, config, IconButton, Chip, Line } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { useAtomValue } from 'jotai'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useRoom } from '../../../hooks/useRoom'; diff --git a/src/app/features/room/MembersDrawer.tsx b/src/app/features/room/MembersDrawer.tsx index d920507..0782a74 100644 --- a/src/app/features/room/MembersDrawer.tsx +++ b/src/app/features/room/MembersDrawer.tsx @@ -6,26 +6,8 @@ import React, { useRef, useState, } from 'react'; -import { - Avatar, - Badge, - Box, - Chip, - Header, - Icon, - IconButton, - Icons, - Input, - MenuItem, - PopOut, - RectCords, - Scroll, - Spinner, - Text, - Tooltip, - TooltipProvider, - config, -} from 'folds'; +import { Avatar, Badge, Box, Chip, Header, IconButton, Input, MenuItem, PopOut, RectCords, Scroll, Spinner, Text, Tooltip, TooltipProvider, config } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import { MatrixClient, Room, RoomMember } from 'matrix-js-sdk'; import { useVirtualizer } from '@tanstack/react-virtual'; import classNames from 'classnames'; diff --git a/src/app/features/room/RoomInput.tsx b/src/app/features/room/RoomInput.tsx index d58dd6e..3735fc2 100644 --- a/src/app/features/room/RoomInput.tsx +++ b/src/app/features/room/RoomInput.tsx @@ -12,24 +12,9 @@ import { isKeyHotkey } from 'is-hotkey'; import { EventType, IContent, MsgType, RelationType, Room } from 'matrix-js-sdk'; import { ReactEditor } from 'slate-react'; import { Transforms, Editor } from 'slate'; -import { - Box, - Dialog, - Icon, - IconButton, - Icons, - Line, - Overlay, - OverlayBackdrop, - OverlayCenter, - PopOut, - Scroll, - Text, - color, - config, - toRem, -} from 'folds'; +import { Box, Dialog, IconButton, Line, Overlay, OverlayBackdrop, OverlayCenter, PopOut, Scroll, Text, color, config, toRem } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { CustomEditor, @@ -131,10 +116,32 @@ interface RoomInputProps { room: Room; /** When provided, all messages are sent as replies in this thread. */ threadRootId?: string; + /** Thread parent for nested replies (no reply preview). */ + threadReplyToEventId?: string; + /** Hide the "replying to" banner (forum thread composers). */ + hideReplyPreview?: boolean; + /** Hide the thread badge in the reply preview (forum thread composers). */ + hideReplyThreadIndicator?: boolean; + /** Optional highlight style for the reply preview (e.g. left border). */ + replyPreviewHighlightClassName?: string; onMessageSent?: () => void; } export const RoomInput = forwardRef( - ({ editor, fileDropContainerRef, roomId, room, threadRootId, onMessageSent }, ref) => { + ( + { + editor, + fileDropContainerRef, + roomId, + room, + threadRootId, + threadReplyToEventId, + hideReplyPreview, + hideReplyThreadIndicator, + replyPreviewHighlightClassName, + onMessageSent, + }, + ref + ) => { const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); const [enterForNewline] = useSetting(settingsAtom, 'enterForNewline'); @@ -440,11 +447,12 @@ export const RoomInput = forwardRef( content['m.relates_to'].is_falling_back = false; } } else if (threadRootId) { + const inReplyToEventId = threadReplyToEventId ?? threadRootId; content['m.relates_to'] = { rel_type: RelationType.Thread, event_id: threadRootId, - is_falling_back: true, - 'm.in_reply_to': { event_id: threadRootId }, + is_falling_back: inReplyToEventId === threadRootId, + 'm.in_reply_to': { event_id: inReplyToEventId }, }; } @@ -475,7 +483,7 @@ export const RoomInput = forwardRef( setCommandHint(undefined); sendTypingStatus(false); onMessageSent?.(); - }, [mx, roomId, threadRootId, editor, replyDraft, sendTypingStatus, setReplyDraft, isMarkdown, commands, autoConvertEmoticons, onMessageSent]); + }, [mx, roomId, threadRootId, threadReplyToEventId, editor, replyDraft, sendTypingStatus, setReplyDraft, isMarkdown, commands, autoConvertEmoticons, onMessageSent]); const handleKeyDown: KeyboardEventHandler = useCallback( (evt) => { @@ -702,7 +710,7 @@ export const RoomInput = forwardRef( )}
    )} - {replyDraft && ( + {replyDraft && !hideReplyPreview && (
    ( > - - {replyDraft.relation?.rel_type === RelationType.Thread && } + + {!hideReplyThreadIndicator && + replyDraft.relation?.rel_type === RelationType.Thread && } ; - function renderPluginButtonIcon(button: { icon?: string; lucideIcon?: string; @@ -54,8 +50,14 @@ function renderPluginButtonIcon(button: { ? button.icon.slice('lucide:'.length) : undefined); + if (lucideName && lucideName in Icons) { + return ; + } + if (lucideName) { - const IconComponent = (lucide as Record)[lucideName]; + const IconComponent = (lucide as Record | undefined>)[ + lucideName + ]; if (IconComponent) { return ; } diff --git a/src/app/features/settings/plugins/Plugins.tsx b/src/app/features/settings/plugins/Plugins.tsx index 2a47198..a1e812c 100644 --- a/src/app/features/settings/plugins/Plugins.tsx +++ b/src/app/features/settings/plugins/Plugins.tsx @@ -1,26 +1,6 @@ import React, { useState, useEffect, useCallback, useMemo } from 'react'; -import { - Avatar, - Badge, - Box, - Button, - color, - config, - Header, - Icon, - IconButton, - Icons, - Input, - Menu, - MenuItem, - PopOut, - RectCords, - Scroll, - Spinner, - Switch, - Text, - toRem, -} from 'folds'; +import { Avatar, Badge, Box, Button, color, config, Header, IconButton, Input, Menu, MenuItem, PopOut, RectCords, Scroll, Spinner, Switch, Text, toRem } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { HexColorPicker } from 'react-colorful'; import FocusTrap from 'focus-trap-react'; import { Page, PageContent, PageHeader } from '../../../components/page'; diff --git a/src/app/features/space-settings/SpaceSettings.tsx b/src/app/features/space-settings/SpaceSettings.tsx index e565fb9..44fc1fb 100644 --- a/src/app/features/space-settings/SpaceSettings.tsx +++ b/src/app/features/space-settings/SpaceSettings.tsx @@ -1,6 +1,7 @@ import React, { useMemo, useState } from 'react'; import { useAtomValue } from 'jotai'; -import { Avatar, Box, config, Icon, IconButton, Icons, IconSrc, MenuItem, Text } from 'folds'; +import { Avatar, Box, config, IconButton, MenuItem, Text } from 'folds'; +import { Icon, Icons, IconSrc } from '../../components/icons'; import { JoinRule } from 'matrix-js-sdk'; import { PageNav, PageNavContent, PageNavHeader, PageRoot } from '../../components/page'; import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize'; diff --git a/src/app/features/space-settings/general/General.tsx b/src/app/features/space-settings/general/General.tsx index 641bfa7..c53ddf9 100644 --- a/src/app/features/space-settings/general/General.tsx +++ b/src/app/features/space-settings/general/General.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { Box, Icon, IconButton, Icons, Scroll, Text } from 'folds'; +import { Box, IconButton, Scroll, Text } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { Page, PageContent, PageHeader } from '../../../components/page'; import { usePowerLevels } from '../../../hooks/usePowerLevels'; import { useRoom } from '../../../hooks/useRoom'; diff --git a/src/app/features/space-settings/permissions/Permissions.tsx b/src/app/features/space-settings/permissions/Permissions.tsx index 5697843..477f0a0 100644 --- a/src/app/features/space-settings/permissions/Permissions.tsx +++ b/src/app/features/space-settings/permissions/Permissions.tsx @@ -1,5 +1,6 @@ import React, { useCallback, useMemo, useState } from 'react'; -import { Box, Button, Icon, IconButton, Icons, Scroll, Spinner, Text, color } from 'folds'; +import { Box, Button, IconButton, Scroll, Spinner, Text, color } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { useAtomValue } from 'jotai'; import { Page, PageContent, PageHeader } from '../../../components/page'; import { useRoom } from '../../../hooks/useRoom'; diff --git a/src/app/features/space/SpaceOptionsMenu.tsx b/src/app/features/space/SpaceOptionsMenu.tsx index a9b092a..7157cac 100644 --- a/src/app/features/space/SpaceOptionsMenu.tsx +++ b/src/app/features/space/SpaceOptionsMenu.tsx @@ -1,7 +1,8 @@ import React, { forwardRef, useState } from 'react'; import { Room } from 'matrix-js-sdk'; import { useAtomValue } from 'jotai'; -import { Box, Icon, Icons, Line, Menu, MenuItem, Text, config, toRem } from 'folds'; +import { Box, Line, Menu, MenuItem, Text, config, toRem } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import type { HierarchyItem } from '../../hooks/useSpaceHierarchy'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { useOpenCreateRoomModal } from '../../state/hooks/createRoomModal'; diff --git a/src/app/hooks/useMemberEventParser.tsx b/src/app/hooks/useMemberEventParser.tsx index 8a59aaa..a45327d 100644 --- a/src/app/hooks/useMemberEventParser.tsx +++ b/src/app/hooks/useMemberEventParser.tsx @@ -1,5 +1,6 @@ +import { IconSrc, Icons } from '../components/icons'; import React, { ReactNode } from 'react'; -import { IconSrc, Icons } from 'folds'; + import { MatrixEvent } from 'matrix-js-sdk'; import { IMemberContent, Membership } from '../../types/matrix/room'; import { getMxIdLocalPart } from '../utils/matrix'; diff --git a/src/app/hooks/useRoomsNotificationPreferences.ts b/src/app/hooks/useRoomsNotificationPreferences.ts index b078896..a126ef9 100644 --- a/src/app/hooks/useRoomsNotificationPreferences.ts +++ b/src/app/hooks/useRoomsNotificationPreferences.ts @@ -1,6 +1,7 @@ +import { Icons, IconSrc } from '../components/icons'; import { createContext, useCallback, useContext, useMemo } from 'react'; import { ConditionKind, IPushRules, MatrixClient, PushRuleKind } from 'matrix-js-sdk'; -import { Icons, IconSrc } from 'folds'; + import { AccountDataEvent } from '../../types/matrix/accountData'; import { useAccountData } from './useAccountData'; import { isRoomId } from '../utils/matrix'; diff --git a/src/app/pages/auth/FiledError.tsx b/src/app/pages/auth/FiledError.tsx index d96fc87..5cd5bdb 100644 --- a/src/app/pages/auth/FiledError.tsx +++ b/src/app/pages/auth/FiledError.tsx @@ -1,6 +1,7 @@ import React from 'react'; -import { Box, Icon, Icons, color, Text } from 'folds'; +import { Box, color, Text } from 'folds'; +import { Icon, Icons } from '../../components/icons'; export function FieldError({ message }: { message: string }) { return ( diff --git a/src/app/pages/auth/ServerPicker.tsx b/src/app/pages/auth/ServerPicker.tsx index 7b89041..1e67cf8 100644 --- a/src/app/pages/auth/ServerPicker.tsx +++ b/src/app/pages/auth/ServerPicker.tsx @@ -7,19 +7,8 @@ import React, { useRef, useState, } from 'react'; -import { - Header, - Icon, - IconButton, - Icons, - Input, - Menu, - MenuItem, - PopOut, - RectCords, - Text, - config, -} from 'folds'; +import { Header, IconButton, Input, Menu, MenuItem, PopOut, RectCords, Text, config } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import FocusTrap from 'focus-trap-react'; import { useDebounce } from '../../hooks/useDebounce'; diff --git a/src/app/pages/auth/login/PasswordLoginForm.tsx b/src/app/pages/auth/login/PasswordLoginForm.tsx index 59f93b1..96d659d 100644 --- a/src/app/pages/auth/login/PasswordLoginForm.tsx +++ b/src/app/pages/auth/login/PasswordLoginForm.tsx @@ -1,22 +1,6 @@ import React, { FormEventHandler, MouseEventHandler, useCallback, useState } from 'react'; -import { - Box, - Button, - Header, - Icon, - IconButton, - Icons, - Input, - Menu, - Overlay, - OverlayBackdrop, - OverlayCenter, - PopOut, - RectCords, - Spinner, - Text, - config, -} from 'folds'; +import { Box, Button, Header, IconButton, Input, Menu, Overlay, OverlayBackdrop, OverlayCenter, PopOut, RectCords, Spinner, Text, config } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import FocusTrap from 'focus-trap-react'; import { Link } from 'react-router-dom'; import { MatrixError } from 'matrix-js-sdk'; diff --git a/src/app/pages/auth/login/TokenLogin.tsx b/src/app/pages/auth/login/TokenLogin.tsx index dc21dcc..f4590e3 100644 --- a/src/app/pages/auth/login/TokenLogin.tsx +++ b/src/app/pages/auth/login/TokenLogin.tsx @@ -1,15 +1,5 @@ -import { - Box, - Icon, - Icons, - Overlay, - OverlayBackdrop, - OverlayCenter, - Spinner, - Text, - color, - config, -} from 'folds'; +import { Box, Overlay, OverlayBackdrop, OverlayCenter, Spinner, Text, color, config } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import React, { useCallback, useEffect } from 'react'; import { MatrixError } from 'matrix-js-sdk'; import { useAutoDiscoveryInfo } from '../../../hooks/useAutoDiscoveryInfo'; diff --git a/src/app/pages/client/ClientRoot.tsx b/src/app/pages/client/ClientRoot.tsx index 4534809..c445d94 100644 --- a/src/app/pages/client/ClientRoot.tsx +++ b/src/app/pages/client/ClientRoot.tsx @@ -1,18 +1,5 @@ -import { - Box, - Button, - config, - Dialog, - Icon, - IconButton, - Icons, - Menu, - MenuItem, - PopOut, - RectCords, - Spinner, - Text, -} from 'folds'; +import { Box, Button, config, Dialog, IconButton, Menu, MenuItem, PopOut, RectCords, Spinner, Text } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import { HttpApiEvent, HttpApiEventHandlerMap, MatrixClient, SyncState } from 'matrix-js-sdk'; import FocusTrap from 'focus-trap-react'; import React, { MouseEventHandler, ReactNode, useCallback, useEffect, useRef, useState } from 'react'; diff --git a/src/app/pages/client/WelcomePage.tsx b/src/app/pages/client/WelcomePage.tsx index 5a4564a..37bbb06 100644 --- a/src/app/pages/client/WelcomePage.tsx +++ b/src/app/pages/client/WelcomePage.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from 'react'; -import { Box, Button, Icon, Icons, Text, config, toRem } from 'folds'; +import { Box, Button, Text, config, toRem } from 'folds'; +import { Icon, Icons } from '../../components/icons'; import { Page, PageHero, PageHeroSection } from '../../components/page'; import PaarrotSVG from '../../../../public/res/svg/paarrot.svg'; import { getVersion } from '@tauri-apps/api/app'; diff --git a/src/app/pages/client/create/Create.tsx b/src/app/pages/client/create/Create.tsx index 288169b..7820687 100644 --- a/src/app/pages/client/create/Create.tsx +++ b/src/app/pages/client/create/Create.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { Box, Icon, Icons, Scroll } from 'folds'; +import { Box, Scroll } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { Page, PageContent, diff --git a/src/app/pages/client/direct/Direct.tsx b/src/app/pages/client/direct/Direct.tsx index bc32bd5..78e63e9 100644 --- a/src/app/pages/client/direct/Direct.tsx +++ b/src/app/pages/client/direct/Direct.tsx @@ -1,20 +1,7 @@ import React, { MouseEventHandler, forwardRef, useMemo, useRef, useState } from 'react'; import { useAtom, useAtomValue } from 'jotai'; -import { - Avatar, - Box, - Button, - Icon, - IconButton, - Icons, - Menu, - MenuItem, - PopOut, - RectCords, - Text, - config, - toRem, -} from 'folds'; +import { Avatar, Box, Button, IconButton, Menu, MenuItem, PopOut, RectCords, Text, config, toRem } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { useVirtualizer } from '@tanstack/react-virtual'; import FocusTrap from 'focus-trap-react'; import { useNavigate } from 'react-router-dom'; diff --git a/src/app/pages/client/direct/DirectCreate.tsx b/src/app/pages/client/direct/DirectCreate.tsx index 3deb0b6..127e61d 100644 --- a/src/app/pages/client/direct/DirectCreate.tsx +++ b/src/app/pages/client/direct/DirectCreate.tsx @@ -1,6 +1,7 @@ import React, { useEffect } from 'react'; import { useNavigate, useSearchParams } from 'react-router-dom'; -import { Box, Icon, IconButton, Icons, Scroll } from 'folds'; +import { Box, IconButton, Scroll } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { getDirectCreateSearchParams } from '../../pathSearchParam'; import { getDirectRoomPath } from '../../pathUtils'; diff --git a/src/app/pages/client/explore/Explore.tsx b/src/app/pages/client/explore/Explore.tsx index dae8316..b5df778 100644 --- a/src/app/pages/client/explore/Explore.tsx +++ b/src/app/pages/client/explore/Explore.tsx @@ -1,23 +1,8 @@ import React, { FormEventHandler, useCallback, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import FocusTrap from 'focus-trap-react'; -import { - Avatar, - Box, - Button, - Dialog, - Header, - Icon, - IconButton, - Icons, - Input, - Overlay, - OverlayBackdrop, - OverlayCenter, - Text, - color, - config, -} from 'folds'; +import { Avatar, Box, Button, Dialog, Header, IconButton, Input, Overlay, OverlayBackdrop, OverlayCenter, Text, color, config } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { NavCategory, NavCategoryHeader, diff --git a/src/app/pages/client/explore/Featured.tsx b/src/app/pages/client/explore/Featured.tsx index f056cbb..429e6a2 100644 --- a/src/app/pages/client/explore/Featured.tsx +++ b/src/app/pages/client/explore/Featured.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { Box, Icon, IconButton, Icons, Scroll, Text } from 'folds'; +import { Box, IconButton, Scroll, Text } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { useAtomValue } from 'jotai'; import { useClientConfig } from '../../../hooks/useClientConfig'; import { RoomCard, RoomCardGrid } from '../../../components/room-card'; diff --git a/src/app/pages/client/explore/Server.tsx b/src/app/pages/client/explore/Server.tsx index 48f267c..1d06996 100644 --- a/src/app/pages/client/explore/Server.tsx +++ b/src/app/pages/client/explore/Server.tsx @@ -8,25 +8,8 @@ import React, { useRef, useState, } from 'react'; -import { - Box, - Button, - Chip, - Icon, - IconButton, - Icons, - Input, - Line, - Menu, - MenuItem, - PopOut, - RectCords, - Scroll, - Spinner, - Text, - config, - toRem, -} from 'folds'; +import { Box, Button, Chip, IconButton, Input, Line, Menu, MenuItem, PopOut, RectCords, Scroll, Spinner, Text, config, toRem } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { useNavigate, useParams, useSearchParams } from 'react-router-dom'; import FocusTrap from 'focus-trap-react'; import { useAtomValue } from 'jotai'; diff --git a/src/app/pages/client/home/CreateRoom.tsx b/src/app/pages/client/home/CreateRoom.tsx index fddd75a..b2bf1d1 100644 --- a/src/app/pages/client/home/CreateRoom.tsx +++ b/src/app/pages/client/home/CreateRoom.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { Box, Icon, Icons, Scroll, IconButton } from 'folds'; +import { Box, Scroll, IconButton } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { Page, PageContent, diff --git a/src/app/pages/client/home/Home.tsx b/src/app/pages/client/home/Home.tsx index c70831f..06d1137 100644 --- a/src/app/pages/client/home/Home.tsx +++ b/src/app/pages/client/home/Home.tsx @@ -1,20 +1,7 @@ import React, { MouseEventHandler, forwardRef, useMemo, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { - Avatar, - Box, - Button, - Icon, - IconButton, - Icons, - Menu, - MenuItem, - PopOut, - RectCords, - Text, - config, - toRem, -} from 'folds'; +import { Avatar, Box, Button, IconButton, Menu, MenuItem, PopOut, RectCords, Text, config, toRem } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { useVirtualizer } from '@tanstack/react-virtual'; import { useAtom, useAtomValue } from 'jotai'; import FocusTrap from 'focus-trap-react'; diff --git a/src/app/pages/client/home/HomeThreadsCategory.tsx b/src/app/pages/client/home/HomeThreadsCategory.tsx index 34957a6..9b144d2 100644 --- a/src/app/pages/client/home/HomeThreadsCategory.tsx +++ b/src/app/pages/client/home/HomeThreadsCategory.tsx @@ -1,13 +1,7 @@ import React, { useCallback, useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { - Avatar, - Box, - Icon, - Icons, - Text, - Badge, -} from 'folds'; +import { Avatar, Box, Text, Badge } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { useAtom, useSetAtom } from 'jotai'; import { Room, Thread, ThreadEvent } from 'matrix-js-sdk'; import { NavCategory, NavCategoryHeader, NavItem, NavItemContent, NavButton } from '../../../components/nav'; diff --git a/src/app/pages/client/home/Search.tsx b/src/app/pages/client/home/Search.tsx index d5ddfb7..9f3a3a5 100644 --- a/src/app/pages/client/home/Search.tsx +++ b/src/app/pages/client/home/Search.tsx @@ -1,5 +1,6 @@ import React, { useRef } from 'react'; -import { Box, Icon, Icons, Text, Scroll, IconButton } from 'folds'; +import { Box, Text, Scroll, IconButton } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { Page, PageContent, PageContentCenter, PageHeader } from '../../../components/page'; import { MessageSearch } from '../../../features/message-search'; import { useHomeRooms } from './useHomeRooms'; diff --git a/src/app/pages/client/inbox/Inbox.tsx b/src/app/pages/client/inbox/Inbox.tsx index 7ac9094..c0cf5e7 100644 --- a/src/app/pages/client/inbox/Inbox.tsx +++ b/src/app/pages/client/inbox/Inbox.tsx @@ -1,5 +1,6 @@ import React, { useRef } from 'react'; -import { Avatar, Box, Icon, Icons, Text } from 'folds'; +import { Avatar, Box, Text } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { useAtomValue } from 'jotai'; import { NavCategory, NavCategoryHeader, NavItem, NavItemContent, NavLink } from '../../../components/nav'; import { getInboxInvitesPath, getInboxNotificationsPath } from '../../pathUtils'; diff --git a/src/app/pages/client/inbox/Invites.tsx b/src/app/pages/client/inbox/Invites.tsx index 20518e5..b46c100 100644 --- a/src/app/pages/client/inbox/Invites.tsx +++ b/src/app/pages/client/inbox/Invites.tsx @@ -1,22 +1,6 @@ import React, { useCallback, useMemo, useRef, useState } from 'react'; -import { - Avatar, - Badge, - Box, - Button, - Chip, - Icon, - IconButton, - Icons, - Overlay, - OverlayBackdrop, - OverlayCenter, - Scroll, - Spinner, - Text, - color, - config, -} from 'folds'; +import { Avatar, Badge, Box, Button, Chip, IconButton, Overlay, OverlayBackdrop, OverlayCenter, Scroll, Spinner, Text, color, config } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { useAtomValue } from 'jotai'; import { RoomTopicEventContent } from 'matrix-js-sdk/lib/types'; import FocusTrap from 'focus-trap-react'; diff --git a/src/app/pages/client/inbox/Notifications.tsx b/src/app/pages/client/inbox/Notifications.tsx index 6649376..ac4aa03 100644 --- a/src/app/pages/client/inbox/Notifications.tsx +++ b/src/app/pages/client/inbox/Notifications.tsx @@ -1,18 +1,7 @@ /* eslint-disable react/destructuring-assignment */ import React, { MouseEventHandler, useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { - Avatar, - Box, - Chip, - Header, - Icon, - IconButton, - Icons, - Scroll, - Text, - config, - toRem, -} from 'folds'; +import { Avatar, Box, Chip, Header, IconButton, Scroll, Text, config, toRem } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { useSearchParams } from 'react-router-dom'; import { INotification, diff --git a/src/app/pages/client/sidebar/CreateTab.tsx b/src/app/pages/client/sidebar/CreateTab.tsx index e6575cb..cf50962 100644 --- a/src/app/pages/client/sidebar/CreateTab.tsx +++ b/src/app/pages/client/sidebar/CreateTab.tsx @@ -1,5 +1,6 @@ import React, { MouseEventHandler, useState } from 'react'; -import { Box, config, Icon, Icons, Menu, PopOut, RectCords, Text } from 'folds'; +import { Box, config, Menu, PopOut, RectCords, Text } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import FocusTrap from 'focus-trap-react'; import { useNavigate } from 'react-router-dom'; import { SidebarAvatar, SidebarItem, SidebarItemTooltip } from '../../../components/sidebar'; diff --git a/src/app/pages/client/sidebar/DirectTab.tsx b/src/app/pages/client/sidebar/DirectTab.tsx index 0c978a5..d0d0996 100644 --- a/src/app/pages/client/sidebar/DirectTab.tsx +++ b/src/app/pages/client/sidebar/DirectTab.tsx @@ -1,6 +1,7 @@ import React, { MouseEventHandler, forwardRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { Box, Icon, Icons, Menu, MenuItem, PopOut, RectCords, Text, config, toRem } from 'folds'; +import { Box, Menu, MenuItem, PopOut, RectCords, Text, config, toRem } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import FocusTrap from 'focus-trap-react'; import { useAtomValue } from 'jotai'; import PaarrotSVG from '../../../../../public/res/svg/paarrot.svg'; diff --git a/src/app/pages/client/sidebar/ExploreTab.tsx b/src/app/pages/client/sidebar/ExploreTab.tsx index a45b583..f85b1df 100644 --- a/src/app/pages/client/sidebar/ExploreTab.tsx +++ b/src/app/pages/client/sidebar/ExploreTab.tsx @@ -1,5 +1,6 @@ +import { Icon, Icons } from '../../../components/icons'; import React from 'react'; -import { Icon, Icons } from 'folds'; + import { useNavigate } from 'react-router-dom'; import { useAtomValue } from 'jotai'; import { SidebarAvatar, SidebarItem, SidebarItemTooltip } from '../../../components/sidebar'; diff --git a/src/app/pages/client/sidebar/HomeTab.tsx b/src/app/pages/client/sidebar/HomeTab.tsx index 707a5b5..61bc8fa 100644 --- a/src/app/pages/client/sidebar/HomeTab.tsx +++ b/src/app/pages/client/sidebar/HomeTab.tsx @@ -1,6 +1,7 @@ import React, { MouseEventHandler, forwardRef, useState } from 'react'; import { useNavigateWithTransition } from '../../../hooks/useViewTransitions'; -import { Box, Icon, Icons, Line, Menu, MenuItem, PopOut, RectCords, Text, config, toRem } from 'folds'; +import { Box, Line, Menu, MenuItem, PopOut, RectCords, Text, config, toRem } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { useAtomValue } from 'jotai'; import FocusTrap from 'focus-trap-react'; import { useOrphanRooms } from '../../../state/hooks/roomList'; diff --git a/src/app/pages/client/sidebar/InboxTab.tsx b/src/app/pages/client/sidebar/InboxTab.tsx index 50bc093..6db9578 100644 --- a/src/app/pages/client/sidebar/InboxTab.tsx +++ b/src/app/pages/client/sidebar/InboxTab.tsx @@ -1,6 +1,7 @@ +import { Icon, Icons } from '../../../components/icons'; import React from 'react'; import { useNavigateWithTransition } from '../../../hooks/useViewTransitions'; -import { Icon, Icons } from 'folds'; + import { useAtomValue } from 'jotai'; import { SidebarAvatar, diff --git a/src/app/pages/client/sidebar/SearchTab.tsx b/src/app/pages/client/sidebar/SearchTab.tsx index 7ceb5c4..1a2b6e3 100644 --- a/src/app/pages/client/sidebar/SearchTab.tsx +++ b/src/app/pages/client/sidebar/SearchTab.tsx @@ -1,5 +1,6 @@ +import { Icon, Icons } from '../../../components/icons'; import React from 'react'; -import { Icon, Icons } from 'folds'; + import { useAtom } from 'jotai'; import { SidebarAvatar, SidebarItem, SidebarItemTooltip } from '../../../components/sidebar'; import { searchModalAtom } from '../../../state/searchModal'; diff --git a/src/app/pages/client/sidebar/SettingsTab.tsx b/src/app/pages/client/sidebar/SettingsTab.tsx index 8cdacbe..ce0cc2e 100644 --- a/src/app/pages/client/sidebar/SettingsTab.tsx +++ b/src/app/pages/client/sidebar/SettingsTab.tsx @@ -1,5 +1,6 @@ import React, { MouseEventHandler, useState } from 'react'; -import { Box, config, Icon, Icons, Line, Menu, MenuItem, PopOut, RectCords, Text } from 'folds'; +import { Box, config, Line, Menu, MenuItem, PopOut, RectCords, Text } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import FocusTrap from 'focus-trap-react'; import { useNavigateWithTransition } from '../../../hooks/useViewTransitions'; import { SidebarItem, SidebarAvatar } from '../../../components/sidebar'; diff --git a/src/app/pages/client/sidebar/SpaceTabs.tsx b/src/app/pages/client/sidebar/SpaceTabs.tsx index 3fb26ff..a2148bc 100644 --- a/src/app/pages/client/sidebar/SpaceTabs.tsx +++ b/src/app/pages/client/sidebar/SpaceTabs.tsx @@ -10,20 +10,8 @@ import React, { useState, } from 'react'; import { useNavigateWithTransition } from '../../../hooks/useViewTransitions'; -import { - Box, - Icon, - IconButton, - Icons, - Line, - Menu, - MenuItem, - PopOut, - RectCords, - Text, - config, - toRem, -} from 'folds'; +import { Box, IconButton, Line, Menu, MenuItem, PopOut, RectCords, Text, config, toRem } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { useAtom, useAtomValue } from 'jotai'; import { Room } from 'matrix-js-sdk'; import { diff --git a/src/app/pages/client/sidebar/UnverifiedTab.tsx b/src/app/pages/client/sidebar/UnverifiedTab.tsx index 0a2f117..759b9a2 100644 --- a/src/app/pages/client/sidebar/UnverifiedTab.tsx +++ b/src/app/pages/client/sidebar/UnverifiedTab.tsx @@ -1,5 +1,6 @@ import React, { useState } from 'react'; -import { Badge, color, Icon, Icons, Text } from 'folds'; +import { Badge, color, Text } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { SidebarAvatar, SidebarItem, diff --git a/src/app/pages/client/space/Search.tsx b/src/app/pages/client/space/Search.tsx index 017262b..7744f51 100644 --- a/src/app/pages/client/space/Search.tsx +++ b/src/app/pages/client/space/Search.tsx @@ -1,5 +1,6 @@ import React, { useRef } from 'react'; -import { Box, Icon, Icons, Text, Scroll, IconButton } from 'folds'; +import { Box, Text, Scroll, IconButton } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { useAtomValue } from 'jotai'; import { Page, PageContent, PageContentCenter, PageHeader } from '../../../components/page'; import { MessageSearch } from '../../../features/message-search'; diff --git a/src/app/pages/client/space/Space.tsx b/src/app/pages/client/space/Space.tsx index 5ab3f2e..1a86b1d 100644 --- a/src/app/pages/client/space/Space.tsx +++ b/src/app/pages/client/space/Space.tsx @@ -7,24 +7,8 @@ import React, { useState, } from 'react'; import { useAtom, useAtomValue } from 'jotai'; -import { - Avatar, - Box, - Button, - Icon, - IconButton, - Icons, - Line, - Menu, - MenuItem, - PopOut, - RectCords, - Spinner, - Text, - color, - config, - toRem, -} from 'folds'; +import { Avatar, Box, Button, IconButton, Line, Menu, MenuItem, PopOut, RectCords, Spinner, Text, color, config, toRem } from 'folds'; +import { Icon, Icons } from '../../../components/icons'; import { useVirtualizer } from '@tanstack/react-virtual'; import { JoinRule, Room, Thread, ThreadEvent } from 'matrix-js-sdk'; import { RoomJoinRulesEventContent } from 'matrix-js-sdk/lib/types'; @@ -87,6 +71,8 @@ import { BreakWord } from '../../../styles/Text.css'; import { InviteUserPrompt } from '../../../components/invite-user-prompt'; import { isSpace, shouldShowForumLobby } from '../../../utils/room'; import { SpaceOptionsMenu } from '../../../features/space/SpaceOptionsMenu'; +import { ForumFeedSidebar } from '../../../features/forum/ForumFeedSidebar'; +import * as forumBoardCss from '../../../features/forum/ForumBoardView.css'; function SpaceHeader() { const space = useSpace(); @@ -350,7 +336,14 @@ export function Space() { getSpaceRoomPath(spaceIdOrAlias, getCanonicalAliasOrRoomId(mx, roomId)); if (shouldShowForumLobby(space)) { - return null; + return ( + + + + + + + ); } return ( diff --git a/src/app/plugins/react-custom-html-parser.tsx b/src/app/plugins/react-custom-html-parser.tsx index 723e509..da484bd 100644 --- a/src/app/plugins/react-custom-html-parser.tsx +++ b/src/app/plugins/react-custom-html-parser.tsx @@ -17,7 +17,8 @@ import { } from 'html-react-parser'; import { MatrixClient } from 'matrix-js-sdk'; import classNames from 'classnames'; -import { Box, Chip, config, Header, Icon, IconButton, Icons, Scroll, Text, toRem } from 'folds'; +import { Box, Chip, config, Header, IconButton, Scroll, Text, toRem } from 'folds'; +import { Icon, Icons } from '../components/icons'; import { IntermediateRepresentation, Opts as LinkifyOpts, OptFn } from 'linkifyjs'; import Linkify from 'linkify-react'; import { ErrorBoundary } from 'react-error-boundary'; diff --git a/src/app/utils/common.ts b/src/app/utils/common.ts index 90bc219..aa6570b 100644 --- a/src/app/utils/common.ts +++ b/src/app/utils/common.ts @@ -1,4 +1,5 @@ -import { IconName, IconSrc } from 'folds'; +import { IconName, IconSrc } from '../components/icons'; + export const bytesToSize = (bytes: number): string => { const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; diff --git a/src/app/utils/room.ts b/src/app/utils/room.ts index a864cde..c548a8d 100644 --- a/src/app/utils/room.ts +++ b/src/app/utils/room.ts @@ -1,4 +1,4 @@ -import { IconName, IconSrc } from 'folds'; +import { IconName, IconSrc } from '../components/icons'; import { EventTimeline,