From 12b5d754fd780e414a6fb7f54ab0388be568f751 Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Fri, 31 Mar 2023 17:25:44 -0500 Subject: [PATCH] Run through an auto-formatter --- .eslintrc.js | 22 +- .github/ISSUE_TEMPLATE/bug_report.yml | 8 +- .github/PULL_REQUEST_TEMPLATE.md | 2 +- .github/SECURITY.md | 2 +- .github/renovate.json | 11 +- .github/workflows/build-pull-request.yml | 2 +- .github/workflows/cla.yml | 10 +- .github/workflows/deploy-pull-request.yml | 8 +- .github/workflows/docker-pr.yml | 6 +- .github/workflows/lockfile.yml | 4 +- .github/workflows/netlify-dev.yml | 4 +- .github/workflows/prod-deploy.yml | 2 +- .prettierrc.json | 8 +- CONTRIBUTING.md | 9 +- index.html | 10 +- src/app/atoms/avatar/Avatar.jsx | 107 +-- src/app/atoms/avatar/Avatar.scss | 6 +- src/app/atoms/avatar/render.js | 23 +- src/app/atoms/badge/NotificationBadge.jsx | 21 +- src/app/atoms/badge/NotificationBadge.scss | 2 +- src/app/atoms/button/Button.jsx | 76 +- src/app/atoms/button/Button.scss | 12 +- src/app/atoms/button/Checkbox.jsx | 19 +- src/app/atoms/button/Checkbox.scss | 6 +- src/app/atoms/button/IconButton.jsx | 110 +-- src/app/atoms/button/IconButton.scss | 4 +- src/app/atoms/button/RadioButton.jsx | 13 +- src/app/atoms/button/RadioButton.scss | 8 +- src/app/atoms/button/Toggle.jsx | 8 +- src/app/atoms/button/Toggle.scss | 11 +- src/app/atoms/button/_state.scss | 3 +- src/app/atoms/button/script.js | 5 +- src/app/atoms/card/InfoCard.jsx | 40 +- src/app/atoms/card/InfoCard.scss | 7 +- src/app/atoms/chip/Chip.jsx | 21 +- src/app/atoms/chip/Chip.scss | 8 +- src/app/atoms/context-menu/ContextMenu.jsx | 67 +- src/app/atoms/context-menu/ContextMenu.scss | 14 +- .../context-menu/ReusableContextMenu.jsx | 38 +- src/app/atoms/divider/Divider.jsx | 28 +- src/app/atoms/divider/Divider.scss | 10 +- src/app/atoms/header/Header.jsx | 18 +- src/app/atoms/header/Header.scss | 10 +- src/app/atoms/input/Input.jsx | 127 ++-- src/app/atoms/input/Input.scss | 6 +- src/app/atoms/math/Math.jsx | 34 +- src/app/atoms/modal/RawModal.jsx | 45 +- src/app/atoms/modal/RawModal.scss | 2 +- src/app/atoms/scroll/ScrollView.jsx | 38 +- src/app/atoms/scroll/ScrollView.scss | 8 +- src/app/atoms/scroll/_scrollbar.scss | 2 +- .../segmented-controls/SegmentedControls.jsx | 58 +- .../segmented-controls/SegmentedControls.scss | 18 +- src/app/atoms/spinner/Spinner.jsx | 14 +- src/app/atoms/spinner/Spinner.scss | 2 +- src/app/atoms/system-icons/RawIcon.jsx | 12 +- src/app/atoms/system-icons/RawIcon.scss | 2 +- src/app/atoms/tabs/Tabs.jsx | 19 +- src/app/atoms/tabs/Tabs.scss | 8 +- src/app/atoms/text/Text.jsx | 57 +- src/app/atoms/text/Text.scss | 6 +- src/app/atoms/time/Time.jsx | 20 +- src/app/atoms/tooltip/Tooltip.jsx | 16 +- src/app/atoms/tooltip/Tooltip.scss | 2 +- src/app/hooks/useAccountData.js | 8 +- src/app/hooks/useCategorizedSpaces.js | 17 +- src/app/hooks/useCrossSigningStatus.js | 12 +- src/app/hooks/useDeviceList.js | 17 +- src/app/hooks/useForceUpdate.js | 11 +- src/app/hooks/usePermission.js | 6 +- src/app/hooks/useSelectedSpace.js | 11 +- src/app/hooks/useSelectedTab.js | 11 +- src/app/hooks/useSpaceShortcut.js | 17 +- src/app/hooks/useStore.js | 2 +- .../confirm-dialog/ConfirmDialog.jsx | 73 +- .../confirm-dialog/ConfirmDialog.scss | 2 +- src/app/molecules/dialog/Dialog.jsx | 45 +- src/app/molecules/dialog/ReusableDialog.jsx | 27 +- .../following-members/FollowingMembers.jsx | 60 +- .../following-members/FollowingMembers.scss | 6 +- .../GlobalNotification.jsx | 144 ++-- .../global-notification/IgnoreUserList.jsx | 43 +- .../global-notification/IgnoreUserList.scss | 2 +- .../KeywordNotification.jsx | 165 ++-- .../KeywordNotification.scss | 2 +- .../NotificationSelector.jsx | 38 +- .../image-lightbox/ImageLightbox.jsx | 32 +- .../image-lightbox/ImageLightbox.scss | 7 +- src/app/molecules/image-pack/ImagePack.jsx | 324 ++++---- src/app/molecules/image-pack/ImagePack.scss | 2 +- .../molecules/image-pack/ImagePackItem.jsx | 78 +- .../molecules/image-pack/ImagePackItem.scss | 6 +- .../molecules/image-pack/ImagePackProfile.jsx | 135 ++-- .../image-pack/ImagePackProfile.scss | 4 +- .../molecules/image-pack/ImagePackUpload.jsx | 70 +- .../molecules/image-pack/ImagePackUpload.scss | 12 +- .../image-pack/ImagePackUsageSelector.jsx | 28 +- .../molecules/image-upload/ImageUpload.jsx | 70 +- .../molecules/image-upload/ImageUpload.scss | 81 +- .../ExportE2ERoomKeys.jsx | 81 +- .../ExportE2ERoomKeys.scss | 3 +- .../ImportE2ERoomKeys.jsx | 88 ++- .../ImportE2ERoomKeys.scss | 11 +- src/app/molecules/media/Media.jsx | 180 +++-- src/app/molecules/media/Media.scss | 2 +- src/app/molecules/message/Message.jsx | 583 +++++++++------ src/app/molecules/message/Message.scss | 12 +- src/app/molecules/message/TimelineChange.jsx | 62 +- src/app/molecules/message/TimelineChange.scss | 4 +- .../people-selector/PeopleSelector.jsx | 37 +- .../people-selector/PeopleSelector.scss | 4 +- .../molecules/popup-window/PopupWindow.jsx | 98 ++- .../molecules/popup-window/PopupWindow.scss | 5 +- .../PowerLevelSelector.jsx | 52 +- .../PowerLevelSelector.scss | 8 +- .../molecules/room-aliases/RoomAliases.jsx | 180 +++-- .../molecules/room-aliases/RoomAliases.scss | 16 +- src/app/molecules/room-emojis/RoomEmojis.jsx | 85 ++- src/app/molecules/room-emojis/RoomEmojis.scss | 4 +- .../room-encryption/RoomEncryption.jsx | 58 +- .../room-encryption/RoomEncryption.scss | 2 +- .../RoomHistoryVisibility.jsx | 123 +-- .../RoomHistoryVisibility.scss | 12 +- src/app/molecules/room-intro/RoomIntro.jsx | 42 +- src/app/molecules/room-intro/RoomIntro.scss | 4 +- .../molecules/room-members/RoomMembers.jsx | 121 +-- .../molecules/room-members/RoomMembers.scss | 11 +- .../room-notification/RoomNotification.jsx | 174 +++-- .../room-notification/RoomNotification.scss | 12 +- .../molecules/room-options/RoomOptions.jsx | 52 +- .../room-permissions/RoomPermissions.jsx | 319 ++++---- .../room-permissions/RoomPermissions.scss | 2 +- .../molecules/room-profile/RoomProfile.jsx | 161 ++-- .../molecules/room-profile/RoomProfile.scss | 13 +- src/app/molecules/room-search/RoomSearch.jsx | 84 ++- src/app/molecules/room-search/RoomSearch.scss | 12 +- .../molecules/room-selector/RoomSelector.jsx | 71 +- .../molecules/room-selector/RoomSelector.scss | 10 +- src/app/molecules/room-tile/RoomTile.jsx | 62 +- src/app/molecules/room-tile/RoomTile.scss | 2 +- .../room-visibility/RoomVisibility.jsx | 148 ++-- .../room-visibility/RoomVisibility.scss | 12 +- .../molecules/setting-tile/SettingTile.jsx | 22 +- .../molecules/setting-tile/SettingTile.scss | 4 +- .../sidebar-avatar/SidebarAvatar.jsx | 74 +- .../sidebar-avatar/SidebarAvatar.scss | 6 +- .../space-add-existing/SpaceAddExisting.jsx | 219 +++--- .../space-add-existing/SpaceAddExisting.scss | 8 +- .../molecules/space-options/SpaceOptions.jsx | 76 +- src/app/molecules/sso-buttons/SSOButtons.jsx | 51 +- src/app/molecules/sso-buttons/SSOButtons.scss | 4 +- src/app/organisms/create-room/CreateRoom.jsx | 248 +++--- src/app/organisms/create-room/CreateRoom.scss | 10 +- src/app/organisms/drag-drop/DragDrop.jsx | 14 +- src/app/organisms/emoji-board/EmojiBoard.jsx | 165 ++-- src/app/organisms/emoji-board/EmojiBoard.scss | 16 +- .../emoji-board/EmojiBoardOpener.jsx | 33 +- src/app/organisms/emoji-board/custom-emoji.js | 95 ++- src/app/organisms/emoji-board/emoji.js | 88 ++- src/app/organisms/emoji-board/recent.js | 11 +- .../emoji-verification/EmojiVerification.jsx | 116 +-- .../emoji-verification/EmojiVerification.scss | 4 +- src/app/organisms/invite-list/InviteList.jsx | 152 ++-- src/app/organisms/invite-list/InviteList.scss | 4 +- src/app/organisms/invite-user/InviteUser.jsx | 227 ++++-- src/app/organisms/invite-user/InviteUser.scss | 6 +- src/app/organisms/join-alias/JoinAlias.jsx | 109 +-- src/app/organisms/join-alias/JoinAlias.scss | 4 +- src/app/organisms/navigation/Directs.jsx | 63 +- src/app/organisms/navigation/Drawer.jsx | 64 +- src/app/organisms/navigation/Drawer.scss | 14 +- .../organisms/navigation/DrawerBreadcrumb.jsx | 112 +-- .../navigation/DrawerBreadcrumb.scss | 8 +- src/app/organisms/navigation/DrawerHeader.jsx | 140 ++-- .../organisms/navigation/DrawerHeader.scss | 10 +- src/app/organisms/navigation/Home.jsx | 114 +-- src/app/organisms/navigation/Navigation.jsx | 8 +- src/app/organisms/navigation/Navigation.scss | 2 +- .../organisms/navigation/RoomsCategory.jsx | 89 ++- .../organisms/navigation/RoomsCategory.scss | 12 +- src/app/organisms/navigation/Selector.jsx | 67 +- src/app/organisms/navigation/SideBar.jsx | 238 +++--- src/app/organisms/navigation/SideBar.scss | 16 +- .../profile-editor/ProfileEditor.jsx | 77 +- .../profile-editor/ProfileEditor.scss | 9 +- .../profile-viewer/ProfileViewer.jsx | 255 ++++--- .../profile-viewer/ProfileViewer.scss | 16 +- .../organisms/public-rooms/PublicRooms.jsx | 270 ++++--- .../organisms/public-rooms/PublicRooms.scss | 20 +- src/app/organisms/pw/Dialogs.jsx | 22 +- src/app/organisms/pw/Windows.jsx | 48 +- .../organisms/read-receipts/ReadReceipts.jsx | 56 +- src/app/organisms/room/EventLimit.js | 5 +- src/app/organisms/room/PeopleDrawer.jsx | 190 ++--- src/app/organisms/room/PeopleDrawer.scss | 12 +- src/app/organisms/room/Room.jsx | 39 +- src/app/organisms/room/Room.scss | 4 +- src/app/organisms/room/RoomSettings.jsx | 194 +++-- src/app/organisms/room/RoomSettings.scss | 16 +- src/app/organisms/room/RoomView.jsx | 43 +- src/app/organisms/room/RoomView.scss | 10 +- src/app/organisms/room/RoomViewCmdBar.jsx | 157 ++-- src/app/organisms/room/RoomViewCmdBar.scss | 10 +- src/app/organisms/room/RoomViewContent.jsx | 380 ++++++---- src/app/organisms/room/RoomViewContent.scss | 13 +- src/app/organisms/room/RoomViewFloating.jsx | 105 ++- src/app/organisms/room/RoomViewFloating.scss | 19 +- src/app/organisms/room/RoomViewHeader.jsx | 117 ++- src/app/organisms/room/RoomViewHeader.scss | 10 +- src/app/organisms/room/RoomViewInput.jsx | 261 ++++--- src/app/organisms/room/RoomViewInput.scss | 8 +- src/app/organisms/room/TimelineScroll.js | 30 +- src/app/organisms/room/commands.jsx | 142 ++-- src/app/organisms/room/commands.scss | 2 +- src/app/organisms/room/common.jsx | 164 ++-- src/app/organisms/search/Search.jsx | 104 ++- src/app/organisms/search/Search.scss | 19 +- src/app/organisms/settings/AuthRequest.jsx | 48 +- src/app/organisms/settings/AuthRequest.scss | 2 +- src/app/organisms/settings/CrossSigning.jsx | 182 +++-- src/app/organisms/settings/CrossSigning.scss | 6 +- src/app/organisms/settings/DeviceManage.jsx | 271 ++++--- src/app/organisms/settings/DeviceManage.scss | 6 +- src/app/organisms/settings/KeyBackup.jsx | 165 ++-- src/app/organisms/settings/KeyBackup.scss | 4 +- .../settings/SecretStorageAccess.jsx | 105 +-- .../settings/SecretStorageAccess.scss | 2 +- src/app/organisms/settings/Settings.jsx | 430 +++++++---- src/app/organisms/settings/Settings.scss | 17 +- .../shortcut-spaces/ShortcutSpaces.jsx | 120 +-- .../shortcut-spaces/ShortcutSpaces.scss | 10 +- .../organisms/space-manage/SpaceManage.jsx | 231 +++--- .../organisms/space-manage/SpaceManage.scss | 16 +- .../space-settings/SpaceSettings.jsx | 164 ++-- .../space-settings/SpaceSettings.scss | 6 +- .../organisms/sticker-board/StickerBoard.jsx | 62 +- .../organisms/sticker-board/StickerBoard.scss | 10 +- src/app/organisms/view-source/ViewSource.jsx | 44 +- src/app/organisms/view-source/ViewSource.scss | 2 +- src/app/organisms/welcome/Welcome.jsx | 27 +- src/app/organisms/welcome/Welcome.scss | 4 +- src/app/pages/App.jsx | 8 +- src/app/partials/_dir.scss | 12 +- src/app/partials/_screen.scss | 4 +- src/app/partials/_text.scss | 2 +- src/app/templates/auth/Auth.jsx | 703 ++++++++++++------ src/app/templates/auth/Auth.scss | 38 +- src/app/templates/client/Client.jsx | 89 ++- src/app/templates/client/Client.scss | 4 +- src/client/action/accountData.js | 4 +- src/client/action/auth.js | 87 ++- src/client/action/navigation.js | 4 +- src/client/action/notifications.js | 2 +- src/client/action/room.js | 173 +++-- src/client/action/roomTimeline.js | 22 +- src/client/action/settings.js | 4 +- src/client/dispatcher.js | 2 +- src/client/event/hotkeys.js | 58 +- src/client/event/roomList.js | 6 +- src/client/initMatrix.js | 53 +- src/client/state/AccountData.js | 32 +- src/client/state/Notifications.js | 132 ++-- src/client/state/RoomList.js | 220 +++--- src/client/state/RoomTimeline.js | 121 ++- src/client/state/RoomsHierarchy.js | 4 +- src/client/state/RoomsInput.js | 182 +++-- src/client/state/auth.js | 7 +- src/client/state/cons.js | 234 +++--- src/client/state/navigation.js | 109 +-- src/client/state/secretStorageKeys.js | 2 +- src/client/state/settings.js | 104 ++- src/font.js | 10 +- src/index.jsx | 14 +- src/index.scss | 89 +-- src/util/AsyncSearch.js | 28 +- src/util/Postie.js | 31 +- src/util/colorMXID.js | 2 +- src/util/common.js | 32 +- src/util/cryptE2ERoomKeys.js | 142 ++-- src/util/markdown.js | 475 +++++++----- src/util/matrixUtil.js | 97 ++- src/util/mimetypes.js | 52 +- src/util/sanitize.js | 132 +++- src/util/sort.js | 6 +- src/util/twemojify.jsx | 33 +- vite.config.js | 73 +- viteSvgLoader.ts | 12 +- 287 files changed, 10107 insertions(+), 6916 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index e8f9224e..20b84beb 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -9,24 +9,21 @@ module.exports = { "plugin:react-hooks/recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", - 'airbnb', - 'prettier', + "airbnb", + "prettier", ], parser: "@typescript-eslint/parser", parserOptions: { ecmaFeatures: { jsx: true, }, - ecmaVersion: 'latest', - sourceType: 'module', + ecmaVersion: "latest", + sourceType: "module", }, - plugins: [ - 'react', - '@typescript-eslint' - ], + plugins: ["react", "@typescript-eslint"], rules: { - 'linebreak-style': 0, - 'no-underscore-dangle': 0, + "linebreak-style": 0, + "no-underscore-dangle": 0, "import/prefer-default-export": "off", "import/extensions": "off", @@ -38,10 +35,7 @@ module.exports = { }, ], - 'react/no-unstable-nested-components': [ - 'error', - { allowAsProps: true }, - ], + "react/no-unstable-nested-components": ["error", { allowAsProps: true }], "react/jsx-filename-extension": [ "error", { diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 76fc578a..20ce0e4e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -42,10 +42,10 @@ body: label: Platform and versions description: "Provide OS, browser and Cinny version with your Homeserver." placeholder: | - 1. OS: [e.g. Windows 10, MacOS] - 2. Browser: [e.g. chrome 99.5, firefox 97.2] - 3. Cinny version: [e.g. 1.8.1 (app.cinny.in)] - 4. Matrix homeserver: [e.g. matrix.org] + 1. OS: [e.g. Windows 10, MacOS] + 2. Browser: [e.g. chrome 99.5, firefox 97.2] + 3. Cinny version: [e.g. 1.8.1 (app.cinny.in)] + 4. Matrix homeserver: [e.g. matrix.org] render: shell validations: required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 1292f1d2..d481c26b 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,8 +1,8 @@ ### Description - + Fixes # diff --git a/.github/SECURITY.md b/.github/SECURITY.md index 2dd642df..2a84c324 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -1,3 +1,3 @@ # Reporting a Vulnerability -**If you've found a security vulnerability, please report it to cinnyapp@gmail.com** \ No newline at end of file +**If you've found a security vulnerability, please report it to cinnyapp@gmail.com** diff --git a/.github/renovate.json b/.github/renovate.json index 46ce4fdf..f50fc8b0 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -1,15 +1,12 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:base", - ":dependencyDashboardApproval" - ], - "labels": [ "Dependencies" ], + "extends": ["config:base", ":dependencyDashboardApproval"], + "labels": ["Dependencies"], "packageRules": [ { - "matchUpdateTypes": [ "lockFileMaintenance" ] + "matchUpdateTypes": ["lockFileMaintenance"] } ], "lockFileMaintenance": { "enabled": true }, "dependencyDashboard": true -} \ No newline at end of file +} diff --git a/.github/workflows/build-pull-request.yml b/.github/workflows/build-pull-request.yml index 5be04293..1e8bd552 100644 --- a/.github/workflows/build-pull-request.yml +++ b/.github/workflows/build-pull-request.yml @@ -2,7 +2,7 @@ name: Build pull request on: pull_request: - types: ['opened', 'synchronize'] + types: ["opened", "synchronize"] jobs: build-pull-request: diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index b433b8d9..b7d40dcf 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -1,4 +1,4 @@ -name: 'CLA Assistant' +name: "CLA Assistant" on: issue_comment: types: [created] @@ -9,7 +9,7 @@ jobs: CLAssistant: runs-on: ubuntu-latest steps: - - name: 'CLA Assistant' + - name: "CLA Assistant" if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target' # Beta Release uses: cla-assistant/github-action@v2.2.1 @@ -18,10 +18,10 @@ jobs: # the below token should have repo scope and must be manually added by you in the repository's secret PERSONAL_ACCESS_TOKEN: ${{ secrets.CLA_PAT }} with: - path-to-signatures: 'signatures.json' - path-to-document: 'https://github.com/cinnyapp/cla/blob/main/cla.md' # e.g. a CLA or a DCO document + path-to-signatures: "signatures.json" + path-to-document: "https://github.com/cinnyapp/cla/blob/main/cla.md" # e.g. a CLA or a DCO document # branch should not be protected - branch: 'main' + branch: "main" allowlist: ajbura,bot* #below are the optional inputs - If the optional inputs are not given, then default values will be taken diff --git a/.github/workflows/deploy-pull-request.yml b/.github/workflows/deploy-pull-request.yml index ab54f8df..28a00904 100644 --- a/.github/workflows/deploy-pull-request.yml +++ b/.github/workflows/deploy-pull-request.yml @@ -2,8 +2,8 @@ name: Deploy PR to Netlify on: workflow_run: - workflows: ["Build pull request"] - types: [completed] + workflows: ["Build pull request"] + types: [completed] jobs: deploy-pull-request: @@ -52,5 +52,5 @@ jobs: pr_number: ${{ steps.pr.outputs.id }} comment_tag: ${{ steps.pr.outputs.id }} message: | - Preview: ${{ steps.netlify.outputs.deploy-url }} - ⚠️ Exercise caution. Use test accounts. ⚠️ \ No newline at end of file + Preview: ${{ steps.netlify.outputs.deploy-url }} + ⚠️ Exercise caution. Use test accounts. ⚠️ diff --git a/.github/workflows/docker-pr.yml b/.github/workflows/docker-pr.yml index 47dbfe32..9b10ccf0 100644 --- a/.github/workflows/docker-pr.yml +++ b/.github/workflows/docker-pr.yml @@ -1,10 +1,10 @@ -name: 'Docker check' +name: "Docker check" on: pull_request: paths: - - 'Dockerfile' - - '.github/workflows/docker-pr.yml' + - "Dockerfile" + - ".github/workflows/docker-pr.yml" jobs: docker-build: diff --git a/.github/workflows/lockfile.yml b/.github/workflows/lockfile.yml index b417df10..70ca354f 100644 --- a/.github/workflows/lockfile.yml +++ b/.github/workflows/lockfile.yml @@ -3,7 +3,7 @@ name: NPM Lockfile Changes on: pull_request: paths: - - 'package-lock.json' + - "package-lock.json" jobs: lockfile_changes: @@ -23,4 +23,4 @@ jobs: collapsibleThreshold: 25 failOnDowngrade: false path: package-lock.json - updateComment: true \ No newline at end of file + updateComment: true diff --git a/.github/workflows/netlify-dev.yml b/.github/workflows/netlify-dev.yml index 12785f46..9a297147 100644 --- a/.github/workflows/netlify-dev.yml +++ b/.github/workflows/netlify-dev.yml @@ -3,7 +3,7 @@ name: Deploy to Netlify (dev) on: push: branches: - - dev + - dev jobs: deploy-to-netlify: @@ -32,7 +32,7 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} production-deploy: true github-deployment-environment: nightly - github-deployment-description: 'Nightly deployment on each commit to dev branch' + github-deployment-description: "Nightly deployment on each commit to dev branch" env: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID_DEV }} diff --git a/.github/workflows/prod-deploy.yml b/.github/workflows/prod-deploy.yml index 8d72a86e..6c7c958d 100644 --- a/.github/workflows/prod-deploy.yml +++ b/.github/workflows/prod-deploy.yml @@ -31,7 +31,7 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} production-deploy: true github-deployment-environment: stable - github-deployment-description: 'Stable deployment on each release' + github-deployment-description: "Stable deployment on each release" env: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID_APP }} diff --git a/.prettierrc.json b/.prettierrc.json index 5ac85e27..c72a02ba 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,4 +1,8 @@ { - "printWidth": 100, - "singleQuote": true + "tabWidth": 2, + "semi": true, + "useTabs": false, + "singleQuote": false, + "trailingComma": "es5", + "printWidth": 80 } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fea421b2..42fccadc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,6 +5,7 @@ First off, thanks for taking the time to contribute! ❤️ All types of contributions are encouraged and valued. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉 > And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about: +> > - Star the project > - Tweet about it (tag @cinnyapp) > - Refer this project in your project's readme @@ -18,6 +19,7 @@ Bug reports and feature suggestions must use descriptive and concise titles and ## Pull requests > ### Legal Notice +> > When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license. **NOTE: If you want to add new features, please discuss with maintainers before coding or opening a pull request.** This is to ensure that we are on same track and following our roadmap. @@ -26,9 +28,9 @@ Bug reports and feature suggestions must use descriptive and concise titles and Example: -|Not ideal|Better| -|---|----| -|Fixed markAllAsRead in RoomTimeline|Fix read marker when paginating room timeline| +| Not ideal | Better | +| ----------------------------------- | --------------------------------------------- | +| Fixed markAllAsRead in RoomTimeline | Fix read marker when paginating room timeline | It is not always possible to phrase every change in such a manner, but it is desired. @@ -39,6 +41,7 @@ Also, we use [ESLint](https://eslint.org/) for clean and stylistically consisten **For any query or design discussion, join our [Matrix room](https://matrix.to/#/#cinny:matrix.org).** ## Helpful links + - [BEM methodology](http://getbem.com/introduction/) - [Atomic design](https://bradfrost.com/blog/post/atomic-web-design/) - [Matrix JavaScript SDK documentation](https://matrix-org.github.io/matrix-js-sdk/index.html) diff --git a/index.html b/index.html index af1a6268..93219dd4 100644 --- a/index.html +++ b/index.html @@ -18,7 +18,10 @@ - + - + { - let textSize = 's1'; - if (size === 'large') textSize = 'h1'; - if (size === 'small') textSize = 'b1'; - if (size === 'extra-small') textSize = 'b3'; +const Avatar = React.forwardRef( + ({ text, bgColor, iconSrc, iconColor, imageSrc, size }, ref) => { + let textSize = "s1"; + if (size === "large") textSize = "h1"; + if (size === "small") textSize = "b1"; + if (size === "extra-small") textSize = "b3"; - return ( -
- { - imageSrc !== null - ? ( - { e.target.style.backgroundColor = 'transparent'; }} - onError={(e) => { e.target.src = ImageBrokenSVG; }} - alt="" - /> - ) - : ( - - { - iconSrc !== null - ? - : text !== null && ( - - {twemojify(avatarInitials(text))} - - ) - } - - ) - } -
- ); -}); + return ( +
+ {imageSrc !== null ? ( + { + e.target.style.backgroundColor = "transparent"; + }} + onError={(e) => { + e.target.src = ImageBrokenSVG; + }} + alt="" + /> + ) : ( + + {iconSrc !== null ? ( + + ) : ( + text !== null && ( + + {twemojify(avatarInitials(text))} + + ) + )} + + )} +
+ ); + } +); Avatar.defaultProps = { text: null, - bgColor: 'transparent', + bgColor: "transparent", iconSrc: null, iconColor: null, imageSrc: null, - size: 'normal', + size: "normal", }; Avatar.propTypes = { @@ -67,7 +72,7 @@ Avatar.propTypes = { iconSrc: PropTypes.string, iconColor: PropTypes.string, imageSrc: PropTypes.string, - size: PropTypes.oneOf(['large', 'normal', 'small', 'extra-small']), + size: PropTypes.oneOf(["large", "normal", "small", "extra-small"]), }; export default Avatar; diff --git a/src/app/atoms/avatar/Avatar.scss b/src/app/atoms/avatar/Avatar.scss index ea69c9e8..4d16d8c7 100644 --- a/src/app/atoms/avatar/Avatar.scss +++ b/src/app/atoms/avatar/Avatar.scss @@ -1,4 +1,4 @@ -@use '../../partials/flex'; +@use "../../partials/flex"; .avatar-container { display: inline-flex; @@ -36,7 +36,7 @@ .avatar__border { @extend .cp-fx__row--c-c; - + position: absolute; top: 0; left: 0; @@ -53,4 +53,4 @@ box-shadow: var(--bs-surface-border); } } -} \ No newline at end of file +} diff --git a/src/app/atoms/avatar/render.js b/src/app/atoms/avatar/render.js index e8cf1a66..309f9467 100644 --- a/src/app/atoms/avatar/render.js +++ b/src/app/atoms/avatar/render.js @@ -1,15 +1,20 @@ -import { avatarInitials, cssVar } from '../../../util/common'; +import { avatarInitials, cssVar } from "../../../util/common"; // renders the avatar and returns it as an URL export default async function renderAvatar({ - text, bgColor, imageSrc, size, borderRadius, scale, + text, + bgColor, + imageSrc, + size, + borderRadius, + scale, }) { try { - const canvas = document.createElement('canvas'); + const canvas = document.createElement("canvas"); canvas.width = size * scale; canvas.height = size * scale; - const ctx = canvas.getContext('2d'); + const ctx = canvas.getContext("2d"); ctx.scale(scale, scale); @@ -27,7 +32,7 @@ export default async function renderAvatar({ ctx.clip(); const img = new Image(); - img.crossOrigin = 'anonymous'; + img.crossOrigin = "anonymous"; const promise = new Promise((resolve, reject) => { img.onerror = reject; img.onload = resolve; @@ -42,10 +47,10 @@ export default async function renderAvatar({ ctx.fill(); // centered letter - ctx.fillStyle = '#fff'; - ctx.font = `${cssVar('--fs-s1')} ${cssVar('--font-primary')}`; - ctx.textBaseline = 'middle'; - ctx.textAlign = 'center'; + ctx.fillStyle = "#fff"; + ctx.font = `${cssVar("--fs-s1")} ${cssVar("--font-primary")}`; + ctx.textBaseline = "middle"; + ctx.textAlign = "center"; ctx.fillText(avatarInitials(text), size / 2, size / 2); } diff --git a/src/app/atoms/badge/NotificationBadge.jsx b/src/app/atoms/badge/NotificationBadge.jsx index 12c1bd44..ff12873f 100644 --- a/src/app/atoms/badge/NotificationBadge.jsx +++ b/src/app/atoms/badge/NotificationBadge.jsx @@ -1,14 +1,18 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './NotificationBadge.scss'; +import React from "react"; +import PropTypes from "prop-types"; +import "./NotificationBadge.scss"; -import Text from '../text/Text'; +import Text from "../text/Text"; function NotificationBadge({ alert, content }) { - const notificationClass = alert ? ' notification-badge--alert' : ''; + const notificationClass = alert ? " notification-badge--alert" : ""; return (
- {content !== null && {content}} + {content !== null && ( + + {content} + + )}
); } @@ -20,10 +24,7 @@ NotificationBadge.defaultProps = { NotificationBadge.propTypes = { alert: PropTypes.bool, - content: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number, - ]), + content: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), }; export default NotificationBadge; diff --git a/src/app/atoms/badge/NotificationBadge.scss b/src/app/atoms/badge/NotificationBadge.scss index f5cfa73f..564d0032 100644 --- a/src/app/atoms/badge/NotificationBadge.scss +++ b/src/app/atoms/badge/NotificationBadge.scss @@ -18,4 +18,4 @@ min-width: 8px; margin: 0 var(--sp-ultra-tight); } -} \ No newline at end of file +} diff --git a/src/app/atoms/button/Button.jsx b/src/app/atoms/button/Button.jsx index 1c1c950c..4cdf152b 100644 --- a/src/app/atoms/button/Button.jsx +++ b/src/app/atoms/button/Button.jsx @@ -1,40 +1,44 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './Button.scss'; +import React from "react"; +import PropTypes from "prop-types"; +import "./Button.scss"; -import Text from '../text/Text'; -import RawIcon from '../system-icons/RawIcon'; -import { blurOnBubbling } from './script'; +import Text from "../text/Text"; +import RawIcon from "../system-icons/RawIcon"; +import { blurOnBubbling } from "./script"; -const Button = React.forwardRef(({ - id, className, variant, iconSrc, - type, onClick, children, disabled, -}, ref) => { - const iconClass = (iconSrc === null) ? '' : `btn-${variant}--icon`; - return ( - - ); -}); +const Button = React.forwardRef( + ( + { id, className, variant, iconSrc, type, onClick, children, disabled }, + ref + ) => { + const iconClass = iconSrc === null ? "" : `btn-${variant}--icon`; + return ( + + ); + } +); Button.defaultProps = { - id: '', + id: "", className: null, - variant: 'surface', + variant: "surface", iconSrc: null, - type: 'button', + type: "button", onClick: null, disabled: false, }; @@ -42,9 +46,15 @@ Button.defaultProps = { Button.propTypes = { id: PropTypes.string, className: PropTypes.string, - variant: PropTypes.oneOf(['surface', 'primary', 'positive', 'caution', 'danger']), + variant: PropTypes.oneOf([ + "surface", + "primary", + "positive", + "caution", + "danger", + ]), iconSrc: PropTypes.string, - type: PropTypes.oneOf(['button', 'submit', 'reset']), + type: PropTypes.oneOf(["button", "submit", "reset"]), onClick: PropTypes.func, children: PropTypes.node.isRequired, disabled: PropTypes.bool, diff --git a/src/app/atoms/button/Button.scss b/src/app/atoms/button/Button.scss index e1a01bb0..dd08dce0 100644 --- a/src/app/atoms/button/Button.scss +++ b/src/app/atoms/button/Button.scss @@ -1,6 +1,6 @@ -@use 'state'; -@use '../../partials/dir'; -@use '../../partials/text'; +@use "state"; +@use "../../partials/dir"; +@use "../../partials/text"; .btn-surface, .btn-primary, @@ -22,10 +22,9 @@ & .text { @extend .cp-txt__ellipsis; } - + &--icon { @include dir.side(padding, var(--sp-tight), var(--sp-loose)); - } .ic-raw { @include dir.side(margin, 0, var(--sp-extra-tight)); @@ -42,7 +41,6 @@ } } - .btn-surface { box-shadow: var(--bs-surface-border); @include color(var(--tc-surface-high), var(--ic-surface-normal)); @@ -78,4 +76,4 @@ @include state.hover(var(--bg-danger-hover)); @include state.focus(var(--bs-danger-outline)); @include state.active(var(--bg-danger-active)); -} \ No newline at end of file +} diff --git a/src/app/atoms/button/Checkbox.jsx b/src/app/atoms/button/Checkbox.jsx index 7fcea3b5..0fd05770 100644 --- a/src/app/atoms/button/Checkbox.jsx +++ b/src/app/atoms/button/Checkbox.jsx @@ -1,12 +1,11 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './Checkbox.scss'; +import React from "react"; +import PropTypes from "prop-types"; +import "./Checkbox.scss"; -function Checkbox({ - variant, isActive, onToggle, - disabled, tabIndex, -}) { - const className = `checkbox checkbox-${variant}${isActive ? ' checkbox--active' : ''}`; +function Checkbox({ variant, isActive, onToggle, disabled, tabIndex }) { + const className = `checkbox checkbox-${variant}${ + isActive ? " checkbox--active" : "" + }`; if (onToggle === null) return ; return ( // eslint-disable-next-line jsx-a11y/control-has-associated-label @@ -21,7 +20,7 @@ function Checkbox({ } Checkbox.defaultProps = { - variant: 'primary', + variant: "primary", isActive: false, onToggle: null, disabled: false, @@ -29,7 +28,7 @@ Checkbox.defaultProps = { }; Checkbox.propTypes = { - variant: PropTypes.oneOf(['primary', 'positive', 'caution', 'danger']), + variant: PropTypes.oneOf(["primary", "positive", "caution", "danger"]), isActive: PropTypes.bool, onToggle: PropTypes.func, disabled: PropTypes.bool, diff --git a/src/app/atoms/button/Checkbox.scss b/src/app/atoms/button/Checkbox.scss index a54daa63..85c440fd 100644 --- a/src/app/atoms/button/Checkbox.scss +++ b/src/app/atoms/button/Checkbox.scss @@ -1,5 +1,5 @@ -@use '../../partials/flex'; -@use './state'; +@use "../../partials/flex"; +@use "./state"; .checkbox { width: 20px; @@ -36,4 +36,4 @@ } .checkbox-danger.checkbox--active { background-color: var(--bg-danger); -} \ No newline at end of file +} diff --git a/src/app/atoms/button/IconButton.jsx b/src/app/atoms/button/IconButton.jsx index f6a8730e..a6e3ea03 100644 --- a/src/app/atoms/button/IconButton.jsx +++ b/src/app/atoms/button/IconButton.jsx @@ -1,62 +1,80 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './IconButton.scss'; +import React from "react"; +import PropTypes from "prop-types"; +import "./IconButton.scss"; -import RawIcon from '../system-icons/RawIcon'; -import Tooltip from '../tooltip/Tooltip'; -import { blurOnBubbling } from './script'; -import Text from '../text/Text'; +import RawIcon from "../system-icons/RawIcon"; +import Tooltip from "../tooltip/Tooltip"; +import { blurOnBubbling } from "./script"; +import Text from "../text/Text"; -const IconButton = React.forwardRef(({ - variant, size, type, - tooltip, tooltipPlacement, src, - onClick, tabIndex, disabled, isImage, - className, -}, ref) => { - const btn = ( - - ); - if (tooltip === null) return btn; - return ( - {tooltip}} - > - {btn} - - ); -}); +const IconButton = React.forwardRef( + ( + { + variant, + size, + type, + tooltip, + tooltipPlacement, + src, + onClick, + tabIndex, + disabled, + isImage, + className, + }, + ref + ) => { + const btn = ( + + ); + if (tooltip === null) return btn; + return ( + {tooltip}} + > + {btn} + + ); + } +); IconButton.defaultProps = { - variant: 'surface', - size: 'normal', - type: 'button', + variant: "surface", + size: "normal", + type: "button", tooltip: null, - tooltipPlacement: 'top', + tooltipPlacement: "top", onClick: null, tabIndex: 0, disabled: false, isImage: false, - className: '', + className: "", }; IconButton.propTypes = { - variant: PropTypes.oneOf(['surface', 'primary', 'positive', 'caution', 'danger']), - size: PropTypes.oneOf(['normal', 'small', 'extra-small']), - type: PropTypes.oneOf(['button', 'submit', 'reset']), + variant: PropTypes.oneOf([ + "surface", + "primary", + "positive", + "caution", + "danger", + ]), + size: PropTypes.oneOf(["normal", "small", "extra-small"]), + type: PropTypes.oneOf(["button", "submit", "reset"]), tooltip: PropTypes.string, - tooltipPlacement: PropTypes.oneOf(['top', 'right', 'bottom', 'left']), + tooltipPlacement: PropTypes.oneOf(["top", "right", "bottom", "left"]), src: PropTypes.string.isRequired, onClick: PropTypes.func, tabIndex: PropTypes.number, diff --git a/src/app/atoms/button/IconButton.scss b/src/app/atoms/button/IconButton.scss index aa6480c0..2b38bd83 100644 --- a/src/app/atoms/button/IconButton.scss +++ b/src/app/atoms/button/IconButton.scss @@ -1,4 +1,4 @@ -@use 'state'; +@use "state"; .ic-btn { padding: var(--sp-extra-tight); @@ -53,4 +53,4 @@ @include state.hover(var(--bg-danger-hover)); @include focus(var(--bg-danger-hover)); @include state.active(var(--bg-danger-active)); -} \ No newline at end of file +} diff --git a/src/app/atoms/button/RadioButton.jsx b/src/app/atoms/button/RadioButton.jsx index 35b68baf..9044bd70 100644 --- a/src/app/atoms/button/RadioButton.jsx +++ b/src/app/atoms/button/RadioButton.jsx @@ -1,14 +1,17 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './RadioButton.scss'; +import React from "react"; +import PropTypes from "prop-types"; +import "./RadioButton.scss"; function RadioButton({ isActive, onToggle, disabled }) { - if (onToggle === null) return ; + if (onToggle === null) + return ( + + ); return ( // eslint-disable-next-line jsx-a11y/control-has-associated-label ); diff --git a/src/app/atoms/chip/Chip.scss b/src/app/atoms/chip/Chip.scss index 7396b0dc..e69d6e4f 100644 --- a/src/app/atoms/chip/Chip.scss +++ b/src/app/atoms/chip/Chip.scss @@ -1,12 +1,12 @@ -@use '../../partials/dir'; +@use "../../partials/dir"; .chip { padding: var(--sp-ultra-tight) var(--sp-extra-tight); - + display: inline-flex; flex-direction: row; align-items: center; - + background: var(--bg-surface-low); border-radius: var(--bo-radius); box-shadow: var(--bs-surface-border); @@ -28,4 +28,4 @@ height: 16px; @include dir.side(margin, 0, var(--sp-ultra-tight)); } -} \ No newline at end of file +} diff --git a/src/app/atoms/context-menu/ContextMenu.jsx b/src/app/atoms/context-menu/ContextMenu.jsx index 7d1acd44..e870c9c9 100644 --- a/src/app/atoms/context-menu/ContextMenu.jsx +++ b/src/app/atoms/context-menu/ContextMenu.jsx @@ -1,17 +1,15 @@ -import React, { useState, useEffect } from 'react'; -import PropTypes from 'prop-types'; -import './ContextMenu.scss'; +import React, { useState, useEffect } from "react"; +import PropTypes from "prop-types"; +import "./ContextMenu.scss"; -import Tippy from '@tippyjs/react'; -import 'tippy.js/animations/scale-extreme.css'; +import Tippy from "@tippyjs/react"; +import "tippy.js/animations/scale-extreme.css"; -import Text from '../text/Text'; -import Button from '../button/Button'; -import ScrollView from '../scroll/ScrollView'; +import Text from "../text/Text"; +import Button from "../button/Button"; +import ScrollView from "../scroll/ScrollView"; -function ContextMenu({ - content, placement, maxWidth, render, afterToggle, -}) { +function ContextMenu({ content, placement, maxWidth, render, afterToggle }) { const [isVisible, setVisibility] = useState(false); const showMenu = () => setVisibility(true); const hideMenu = () => setVisibility(false); @@ -26,7 +24,11 @@ function ContextMenu({ className="context-menu" visible={isVisible} onClickOutside={hideMenu} - content={{typeof content === 'function' ? content(hideMenu) : content}} + content={ + + {typeof content === "function" ? content(hideMenu) : content} + + } placement={placement} interactive arrow={false} @@ -39,21 +41,15 @@ function ContextMenu({ } ContextMenu.defaultProps = { - maxWidth: 'unset', - placement: 'right', + maxWidth: "unset", + placement: "right", afterToggle: null, }; ContextMenu.propTypes = { - content: PropTypes.oneOfType([ - PropTypes.node, - PropTypes.func, - ]).isRequired, - placement: PropTypes.oneOf(['top', 'right', 'bottom', 'left']), - maxWidth: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number, - ]), + content: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired, + placement: PropTypes.oneOf(["top", "right", "bottom", "left"]), + maxWidth: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), render: PropTypes.func.isRequired, afterToggle: PropTypes.func, }; @@ -61,7 +57,7 @@ ContextMenu.propTypes = { function MenuHeader({ children }) { return (
- { children } + {children}
); } @@ -70,10 +66,7 @@ MenuHeader.propTypes = { children: PropTypes.node.isRequired, }; -function MenuItem({ - variant, iconSrc, type, - onClick, children, disabled, -}) { +function MenuItem({ variant, iconSrc, type, onClick, children, disabled }) { return (
); } MenuItem.defaultProps = { - variant: 'surface', + variant: "surface", iconSrc: null, - type: 'button', + type: "button", disabled: false, onClick: null, }; MenuItem.propTypes = { - variant: PropTypes.oneOf(['surface', 'positive', 'caution', 'danger']), + variant: PropTypes.oneOf(["surface", "positive", "caution", "danger"]), iconSrc: PropTypes.string, - type: PropTypes.oneOf(['button', 'submit']), + type: PropTypes.oneOf(["button", "submit"]), onClick: PropTypes.func, children: PropTypes.node.isRequired, disabled: PropTypes.bool, }; function MenuBorder() { - return
; + return ( +
+ ); } -export { - ContextMenu as default, MenuHeader, MenuItem, MenuBorder, -}; +export { ContextMenu as default, MenuHeader, MenuItem, MenuBorder }; diff --git a/src/app/atoms/context-menu/ContextMenu.scss b/src/app/atoms/context-menu/ContextMenu.scss index 2df9f0a4..c6771c2a 100644 --- a/src/app/atoms/context-menu/ContextMenu.scss +++ b/src/app/atoms/context-menu/ContextMenu.scss @@ -1,6 +1,6 @@ -@use '../../partials/flex'; -@use '../../partials/text'; -@use '../../partials/dir'; +@use "../../partials/flex"; +@use "../../partials/text"; +@use "../../partials/dir"; .context-menu { background-color: var(--bg-surface); @@ -59,11 +59,7 @@ // if item doesn't have icon .text:first-child { - @include dir.side( - margin, - calc(var(--ic-small) + var(--sp-tight)), - 0 - ); + @include dir.side(margin, calc(var(--ic-small) + var(--sp-tight)), 0); } } .btn-surface:focus { @@ -78,4 +74,4 @@ .btn-danger:focus { background-color: var(--bg-danger-hover); } -} \ No newline at end of file +} diff --git a/src/app/atoms/context-menu/ReusableContextMenu.jsx b/src/app/atoms/context-menu/ReusableContextMenu.jsx index 59bdb142..e28aaef3 100644 --- a/src/app/atoms/context-menu/ReusableContextMenu.jsx +++ b/src/app/atoms/context-menu/ReusableContextMenu.jsx @@ -1,9 +1,9 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect, useRef } from "react"; -import cons from '../../../client/state/cons'; -import navigation from '../../../client/state/navigation'; +import cons from "../../../client/state/cons"; +import navigation from "../../../client/state/navigation"; -import ContextMenu from './ContextMenu'; +import ContextMenu from "./ContextMenu"; let key = null; function ReusableContextMenu() { @@ -29,14 +29,20 @@ function ReusableContextMenu() { return; } setData({ - placement, cords, render, afterClose, + placement, + cords, + render, + afterClose, }); }; - navigation.on(cons.events.navigation.REUSABLE_CONTEXT_MENU_OPENED, handleContextMenuOpen); + navigation.on( + cons.events.navigation.REUSABLE_CONTEXT_MENU_OPENED, + handleContextMenuOpen + ); return () => { navigation.removeListener( cons.events.navigation.REUSABLE_CONTEXT_MENU_OPENED, - handleContextMenuOpen, + handleContextMenuOpen ); }; }, [data]); @@ -59,24 +65,24 @@ function ReusableContextMenu() { return ( ( )} diff --git a/src/app/atoms/divider/Divider.jsx b/src/app/atoms/divider/Divider.jsx index 76721241..5b4a3868 100644 --- a/src/app/atoms/divider/Divider.jsx +++ b/src/app/atoms/divider/Divider.jsx @@ -1,28 +1,38 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './Divider.scss'; +import React from "react"; +import PropTypes from "prop-types"; +import "./Divider.scss"; -import Text from '../text/Text'; +import Text from "../text/Text"; function Divider({ text, variant, align }) { const dividerClass = ` divider--${variant} divider--${align}`; return (
- {text !== null && {text}} + {text !== null && ( + + {text} + + )}
); } Divider.defaultProps = { text: null, - variant: 'surface', - align: 'center', + variant: "surface", + align: "center", }; Divider.propTypes = { text: PropTypes.string, - variant: PropTypes.oneOf(['surface', 'primary', 'positive', 'caution', 'danger']), - align: PropTypes.oneOf(['left', 'center', 'right']), + variant: PropTypes.oneOf([ + "surface", + "primary", + "positive", + "caution", + "danger", + ]), + align: PropTypes.oneOf(["left", "center", "right"]), }; export default Divider; diff --git a/src/app/atoms/divider/Divider.scss b/src/app/atoms/divider/Divider.scss index 0f013ff0..f85c4c0d 100644 --- a/src/app/atoms/divider/Divider.scss +++ b/src/app/atoms/divider/Divider.scss @@ -1,5 +1,5 @@ .divider-line { - content: ''; + content: ""; display: inline-block; flex: 1; border-bottom: 1px solid var(--local-divider-color); @@ -36,7 +36,7 @@ } .divider--primary { --local-divider-color: var(--bg-primary); - --local-divider-opacity: .8; + --local-divider-opacity: 0.8; .divider__text { color: var(--tc-primary-high); background-color: var(--bg-primary); @@ -44,7 +44,7 @@ } .divider--positive { --local-divider-color: var(--bg-positive); - --local-divider-opacity: .8; + --local-divider-opacity: 0.8; .divider__text { color: var(--bg-surface); background-color: var(--bg-positive); @@ -52,7 +52,7 @@ } .divider--danger { --local-divider-color: var(--bg-danger); - --local-divider-opacity: .8; + --local-divider-opacity: 0.8; .divider__text { color: var(--bg-surface); background-color: var(--bg-danger); @@ -60,7 +60,7 @@ } .divider--caution { --local-divider-color: var(--bg-caution); - --local-divider-opacity: .8; + --local-divider-opacity: 0.8; .divider__text { color: var(--bg-surface); background-color: var(--bg-caution); diff --git a/src/app/atoms/header/Header.jsx b/src/app/atoms/header/Header.jsx index 3c81e423..c21ab0d3 100644 --- a/src/app/atoms/header/Header.jsx +++ b/src/app/atoms/header/Header.jsx @@ -1,13 +1,9 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './Header.scss'; +import React from "react"; +import PropTypes from "prop-types"; +import "./Header.scss"; function Header({ children }) { - return ( -
- {children} -
- ); + return
{children}
; } Header.propTypes = { @@ -15,11 +11,7 @@ Header.propTypes = { }; function TitleWrapper({ children }) { - return ( -
- {children} -
- ); + return
{children}
; } TitleWrapper.propTypes = { diff --git a/src/app/atoms/header/Header.scss b/src/app/atoms/header/Header.scss index 9e45f824..99162e80 100644 --- a/src/app/atoms/header/Header.scss +++ b/src/app/atoms/header/Header.scss @@ -1,5 +1,5 @@ -@use '../../partials/text'; -@use '../../partials/dir'; +@use "../../partials/text"; +@use "../../partials/dir"; .header { @include dir.side(padding, var(--sp-normal), var(--sp-extra-tight)); @@ -15,7 +15,7 @@ display: flex; align-items: center; margin: 0 var(--sp-tight); - + &:first-child { @include dir.side(margin, 0, var(--sp-tight)); } @@ -24,7 +24,7 @@ @extend .cp-txt__ellipsis; min-width: 0; } - & > .text-b3{ + & > .text-b3 { flex: 1; min-width: 0; @@ -40,4 +40,4 @@ display: -webkit-box; } } -} \ No newline at end of file +} diff --git a/src/app/atoms/input/Input.jsx b/src/app/atoms/input/Input.jsx index 96c94967..98ba7e51 100644 --- a/src/app/atoms/input/Input.jsx +++ b/src/app/atoms/input/Input.jsx @@ -1,75 +1,92 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './Input.scss'; +import React from "react"; +import PropTypes from "prop-types"; +import "./Input.scss"; -import TextareaAutosize from 'react-autosize-textarea'; +import TextareaAutosize from "react-autosize-textarea"; function Input({ - id, label, name, value, placeholder, - required, type, onChange, forwardRef, - resizable, minHeight, onResize, state, - onKeyDown, disabled, autoFocus, + id, + label, + name, + value, + placeholder, + required, + type, + onChange, + forwardRef, + resizable, + minHeight, + onResize, + state, + onKeyDown, + disabled, + autoFocus, }) { return (
- { label !== '' && } - { resizable - ? ( - - ) : ( - - )} + {label !== "" && ( + + )} + {resizable ? ( + + ) : ( + + )}
); } Input.defaultProps = { id: null, - name: '', - label: '', - value: '', - placeholder: '', - type: 'text', + name: "", + label: "", + value: "", + placeholder: "", + type: "text", required: false, onChange: null, forwardRef: null, resizable: false, minHeight: 46, onResize: null, - state: 'normal', + state: "normal", onKeyDown: null, disabled: false, autoFocus: false, @@ -88,7 +105,7 @@ Input.propTypes = { resizable: PropTypes.bool, minHeight: PropTypes.number, onResize: PropTypes.func, - state: PropTypes.oneOf(['normal', 'success', 'error']), + state: PropTypes.oneOf(["normal", "success", "error"]), onKeyDown: PropTypes.func, disabled: PropTypes.bool, autoFocus: PropTypes.bool, diff --git a/src/app/atoms/input/Input.scss b/src/app/atoms/input/Input.scss index 40fe43ec..3a6b63b5 100644 --- a/src/app/atoms/input/Input.scss +++ b/src/app/atoms/input/Input.scss @@ -1,4 +1,4 @@ -@use '../../atoms/scroll/scrollbar'; +@use "../../atoms/scroll/scrollbar"; .input { display: block; @@ -47,6 +47,6 @@ box-shadow: var(--bs-primary-border); } &::placeholder { - color: var(--tc-surface-low) + color: var(--tc-surface-low); } -} \ No newline at end of file +} diff --git a/src/app/atoms/math/Math.jsx b/src/app/atoms/math/Math.jsx index ab52a478..d53ead86 100644 --- a/src/app/atoms/math/Math.jsx +++ b/src/app/atoms/math/Math.jsx @@ -1,23 +1,27 @@ -import React, { useEffect, useRef } from 'react'; -import PropTypes from 'prop-types'; -import './Math.scss'; +import React, { useEffect, useRef } from "react"; +import PropTypes from "prop-types"; +import "./Math.scss"; -import katex from 'katex'; -import 'katex/dist/katex.min.css'; +import katex from "katex"; +import "katex/dist/katex.min.css"; -import 'katex/dist/contrib/copy-tex'; +import "katex/dist/contrib/copy-tex"; -const Math = React.memo(({ - content, throwOnError, errorColor, displayMode, -}) => { - const ref = useRef(null); +const Math = React.memo( + ({ content, throwOnError, errorColor, displayMode }) => { + const ref = useRef(null); - useEffect(() => { - katex.render(content, ref.current, { throwOnError, errorColor, displayMode }); - }, [content, throwOnError, errorColor, displayMode]); + useEffect(() => { + katex.render(content, ref.current, { + throwOnError, + errorColor, + displayMode, + }); + }, [content, throwOnError, errorColor, displayMode]); - return ; -}); + return ; + } +); Math.defaultProps = { throwOnError: null, errorColor: null, diff --git a/src/app/atoms/modal/RawModal.jsx b/src/app/atoms/modal/RawModal.jsx index 450be0e0..66a25b33 100644 --- a/src/app/atoms/modal/RawModal.jsx +++ b/src/app/atoms/modal/RawModal.jsx @@ -1,36 +1,43 @@ -import React, { useEffect } from 'react'; -import PropTypes from 'prop-types'; -import './RawModal.scss'; +import React, { useEffect } from "react"; +import PropTypes from "prop-types"; +import "./RawModal.scss"; -import Modal from 'react-modal'; +import Modal from "react-modal"; -import navigation from '../../../client/state/navigation'; +import navigation from "../../../client/state/navigation"; -Modal.setAppElement('#root'); +Modal.setAppElement("#root"); function RawModal({ - className, overlayClassName, - isOpen, size, onAfterOpen, onAfterClose, - onRequestClose, closeFromOutside, children, + className, + overlayClassName, + isOpen, + size, + onAfterOpen, + onAfterClose, + onRequestClose, + closeFromOutside, + children, }) { - let modalClass = (className !== null) ? `${className} ` : ''; + let modalClass = className !== null ? `${className} ` : ""; switch (size) { - case 'large': - modalClass += 'raw-modal__large '; + case "large": + modalClass += "raw-modal__large "; break; - case 'medium': - modalClass += 'raw-modal__medium '; + case "medium": + modalClass += "raw-modal__medium "; break; - case 'small': + case "small": default: - modalClass += 'raw-modal__small '; + modalClass += "raw-modal__small "; } useEffect(() => { navigation.setIsRawModalVisible(isOpen); }, [isOpen]); - const modalOverlayClass = (overlayClassName !== null) ? `${overlayClassName} ` : ''; + const modalOverlayClass = + overlayClassName !== null ? `${overlayClassName} ` : ""; return ( { - let scrollbarClasses = ''; - if (horizontal) scrollbarClasses += ' scrollbar__h'; - if (vertical) scrollbarClasses += ' scrollbar__v'; - if (autoHide) scrollbarClasses += ' scrollbar--auto-hide'; - if (invisible) scrollbarClasses += ' scrollbar--invisible'; - return ( -
- {children} -
- ); -}); +const ScrollView = React.forwardRef( + ({ horizontal, vertical, autoHide, invisible, onScroll, children }, ref) => { + let scrollbarClasses = ""; + if (horizontal) scrollbarClasses += " scrollbar__h"; + if (vertical) scrollbarClasses += " scrollbar__v"; + if (autoHide) scrollbarClasses += " scrollbar--auto-hide"; + if (invisible) scrollbarClasses += " scrollbar--invisible"; + return ( +
+ {children} +
+ ); + } +); ScrollView.defaultProps = { horizontal: false, diff --git a/src/app/atoms/scroll/ScrollView.scss b/src/app/atoms/scroll/ScrollView.scss index 251037e1..44a766c1 100644 --- a/src/app/atoms/scroll/ScrollView.scss +++ b/src/app/atoms/scroll/ScrollView.scss @@ -1,8 +1,8 @@ -@use '../../partials/dir'; -@use '_scrollbar'; +@use "../../partials/dir"; +@use "_scrollbar"; @mixin paddingForSafari($padding) { - @media not all and (min-resolution:.001dpcm) { + @media not all and (min-resolution: 0.001dpcm) { @include dir.side(padding, 0, $padding); } } @@ -28,4 +28,4 @@ @include scrollbar.scroll--invisible; @include paddingForSafari(0); } -} \ No newline at end of file +} diff --git a/src/app/atoms/scroll/_scrollbar.scss b/src/app/atoms/scroll/_scrollbar.scss index 78ec75ad..cd688991 100644 --- a/src/app/atoms/scroll/_scrollbar.scss +++ b/src/app/atoms/scroll/_scrollbar.scss @@ -62,4 +62,4 @@ &::-webkit-scrollbar { display: none; } -} \ No newline at end of file +} diff --git a/src/app/atoms/segmented-controls/SegmentedControls.jsx b/src/app/atoms/segmented-controls/SegmentedControls.jsx index 1d54dd06..27709edb 100644 --- a/src/app/atoms/segmented-controls/SegmentedControls.jsx +++ b/src/app/atoms/segmented-controls/SegmentedControls.jsx @@ -1,15 +1,13 @@ -import React, { useState, useEffect } from 'react'; -import PropTypes from 'prop-types'; -import './SegmentedControls.scss'; +import React, { useState, useEffect } from "react"; +import PropTypes from "prop-types"; +import "./SegmentedControls.scss"; -import { blurOnBubbling } from '../button/script'; +import { blurOnBubbling } from "../button/script"; -import Text from '../text/Text'; -import RawIcon from '../system-icons/RawIcon'; +import Text from "../text/Text"; +import RawIcon from "../system-icons/RawIcon"; -function SegmentedControls({ - selected, segments, onSelect, -}) { +function SegmentedControls({ selected, segments, onSelect }) { const [select, setSelect] = useState(selected); function selectSegment(segmentIndex) { @@ -23,32 +21,34 @@ function SegmentedControls({ return (
- { - segments.map((segment, index) => ( - - )) - } + {segments.map((segment, index) => ( + + ))}
); } SegmentedControls.propTypes = { selected: PropTypes.number.isRequired, - segments: PropTypes.arrayOf(PropTypes.shape({ - iconSrc: PropTypes.string, - text: PropTypes.string, - })).isRequired, + segments: PropTypes.arrayOf( + PropTypes.shape({ + iconSrc: PropTypes.string, + text: PropTypes.string, + }) + ).isRequired, onSelect: PropTypes.func.isRequired, }; diff --git a/src/app/atoms/segmented-controls/SegmentedControls.scss b/src/app/atoms/segmented-controls/SegmentedControls.scss index fb1fd987..d907c79f 100644 --- a/src/app/atoms/segmented-controls/SegmentedControls.scss +++ b/src/app/atoms/segmented-controls/SegmentedControls.scss @@ -1,5 +1,5 @@ -@use '../button/state'; -@use '../../partials/dir'; +@use "../button/state"; +@use "../../partials/dir"; .segmented-controls { background-color: var(--bg-surface-low); @@ -40,18 +40,22 @@ & + .segment-btn .segment-btn__base { border: none; } - &:first-child{ + &:first-child { border-left: none; } &:last-child { border-right: none; } - [dir=rtl] & { + [dir="rtl"] & { border-left: 1px solid var(--bg-surface-border); border-right: 1px solid var(--bg-surface-border); - &:first-child { border-right: none;} - &:last-child { border-left: none;} + &:first-child { + border-right: none; + } + &:last-child { + border-left: none; + } } } -} \ No newline at end of file +} diff --git a/src/app/atoms/spinner/Spinner.jsx b/src/app/atoms/spinner/Spinner.jsx index 61c9747e..8a474d68 100644 --- a/src/app/atoms/spinner/Spinner.jsx +++ b/src/app/atoms/spinner/Spinner.jsx @@ -1,19 +1,17 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './Spinner.scss'; +import React from "react"; +import PropTypes from "prop-types"; +import "./Spinner.scss"; function Spinner({ size }) { - return ( -
- ); + return
; } Spinner.defaultProps = { - size: 'normal', + size: "normal", }; Spinner.propTypes = { - size: PropTypes.oneOf(['normal', 'small']), + size: PropTypes.oneOf(["normal", "small"]), }; export default Spinner; diff --git a/src/app/atoms/spinner/Spinner.scss b/src/app/atoms/spinner/Spinner.scss index 73fbf676..82b0e820 100644 --- a/src/app/atoms/spinner/Spinner.scss +++ b/src/app/atoms/spinner/Spinner.scss @@ -19,4 +19,4 @@ to { transform: rotate(1turn); } -} \ No newline at end of file +} diff --git a/src/app/atoms/system-icons/RawIcon.jsx b/src/app/atoms/system-icons/RawIcon.jsx index a6697f4f..446fca3f 100644 --- a/src/app/atoms/system-icons/RawIcon.jsx +++ b/src/app/atoms/system-icons/RawIcon.jsx @@ -1,12 +1,12 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './RawIcon.scss'; +import React from "react"; +import PropTypes from "prop-types"; +import "./RawIcon.scss"; function RawIcon({ color, size, src, isImage }) { const style = {}; if (color !== null) style.backgroundColor = color; if (isImage) { - style.backgroundColor = 'transparent'; + style.backgroundColor = "transparent"; style.backgroundImage = `url("${src}")`; } else { style.WebkitMaskImage = `url("${src}")`; @@ -18,13 +18,13 @@ function RawIcon({ color, size, src, isImage }) { RawIcon.defaultProps = { color: null, - size: 'normal', + size: "normal", isImage: false, }; RawIcon.propTypes = { color: PropTypes.string, - size: PropTypes.oneOf(['large', 'normal', 'small', 'extra-small']), + size: PropTypes.oneOf(["large", "normal", "small", "extra-small"]), src: PropTypes.string.isRequired, isImage: PropTypes.bool, }; diff --git a/src/app/atoms/system-icons/RawIcon.scss b/src/app/atoms/system-icons/RawIcon.scss index 56fc9b3c..87c85b8f 100644 --- a/src/app/atoms/system-icons/RawIcon.scss +++ b/src/app/atoms/system-icons/RawIcon.scss @@ -25,4 +25,4 @@ } .ic-raw-extra-small { @include icSize(var(--ic-extra-small)); -} \ No newline at end of file +} diff --git a/src/app/atoms/tabs/Tabs.jsx b/src/app/atoms/tabs/Tabs.jsx index 39800ce3..180c1bc7 100644 --- a/src/app/atoms/tabs/Tabs.jsx +++ b/src/app/atoms/tabs/Tabs.jsx @@ -1,15 +1,12 @@ -import React, { useState } from 'react'; -import PropTypes from 'prop-types'; -import './Tabs.scss'; +import React, { useState } from "react"; +import PropTypes from "prop-types"; +import "./Tabs.scss"; -import Button from '../button/Button'; -import ScrollView from '../scroll/ScrollView'; +import Button from "../button/Button"; +import ScrollView from "../scroll/ScrollView"; -function TabItem({ - selected, iconSrc, - onClick, children, disabled, -}) { - const isSelected = selected ? 'tab-item--selected' : ''; +function TabItem({ selected, iconSrc, onClick, children, disabled }) { + const isSelected = selected ? "tab-item--selected" : ""; return ( + @@ -23,7 +23,8 @@ function ConfirmDialog({ ConfirmDialog.propTypes = { desc: PropTypes.string.isRequired, actionTitle: PropTypes.string.isRequired, - actionType: PropTypes.oneOf(['primary', 'positive', 'danger', 'caution']).isRequired, + actionType: PropTypes.oneOf(["primary", "positive", "danger", "caution"]) + .isRequired, onComplete: PropTypes.func.isRequired, }; @@ -35,24 +36,32 @@ ConfirmDialog.propTypes = { * @return {Promise} does it get's confirmed or not */ // eslint-disable-next-line import/prefer-default-export -export const confirmDialog = (title, desc, actionTitle, actionType = 'primary') => new Promise((resolve) => { - let isCompleted = false; - openReusableDialog( - {title}, - (requestClose) => ( - { - isCompleted = true; - resolve(isConfirmed); - requestClose(); - }} - /> - ), - () => { - if (!isCompleted) resolve(false); - }, - ); -}); +export const confirmDialog = ( + title, + desc, + actionTitle, + actionType = "primary" +) => + new Promise((resolve) => { + let isCompleted = false; + openReusableDialog( + + {title} + , + (requestClose) => ( + { + isCompleted = true; + resolve(isConfirmed); + requestClose(); + }} + /> + ), + () => { + if (!isCompleted) resolve(false); + } + ); + }); diff --git a/src/app/molecules/confirm-dialog/ConfirmDialog.scss b/src/app/molecules/confirm-dialog/ConfirmDialog.scss index 05f88be1..e86680fa 100644 --- a/src/app/molecules/confirm-dialog/ConfirmDialog.scss +++ b/src/app/molecules/confirm-dialog/ConfirmDialog.scss @@ -8,4 +8,4 @@ display: flex; gap: var(--sp-normal); } -} \ No newline at end of file +} diff --git a/src/app/molecules/dialog/Dialog.jsx b/src/app/molecules/dialog/Dialog.jsx index 2f29971d..bef9181b 100644 --- a/src/app/molecules/dialog/Dialog.jsx +++ b/src/app/molecules/dialog/Dialog.jsx @@ -1,22 +1,29 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './Dialog.scss'; +import React from "react"; +import PropTypes from "prop-types"; +import "./Dialog.scss"; -import { twemojify } from '../../../util/twemojify'; +import { twemojify } from "../../../util/twemojify"; -import Text from '../../atoms/text/Text'; -import Header, { TitleWrapper } from '../../atoms/header/Header'; -import ScrollView from '../../atoms/scroll/ScrollView'; -import RawModal from '../../atoms/modal/RawModal'; +import Text from "../../atoms/text/Text"; +import Header, { TitleWrapper } from "../../atoms/header/Header"; +import ScrollView from "../../atoms/scroll/ScrollView"; +import RawModal from "../../atoms/modal/RawModal"; function Dialog({ - className, isOpen, title, onAfterOpen, onAfterClose, - contentOptions, onRequestClose, closeFromOutside, children, + className, + isOpen, + title, + onAfterOpen, + onAfterClose, + contentOptions, + onRequestClose, + closeFromOutside, + children, invisibleScroll, }) { return (
- { - typeof title === 'string' - ? {twemojify(title)} - : title - } + {typeof title === "string" ? ( + + {twemojify(title)} + + ) : ( + title + )} {contentOptions}
-
- {children} -
+
{children}
diff --git a/src/app/molecules/dialog/ReusableDialog.jsx b/src/app/molecules/dialog/ReusableDialog.jsx index 7340e119..d0a2b271 100644 --- a/src/app/molecules/dialog/ReusableDialog.jsx +++ b/src/app/molecules/dialog/ReusableDialog.jsx @@ -1,12 +1,12 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect } from "react"; -import cons from '../../../client/state/cons'; +import cons from "../../../client/state/cons"; -import navigation from '../../../client/state/navigation'; -import IconButton from '../../atoms/button/IconButton'; -import Dialog from './Dialog'; +import navigation from "../../../client/state/navigation"; +import IconButton from "../../atoms/button/IconButton"; +import Dialog from "./Dialog"; -import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; +import CrossIC from "../../../../public/res/ic/outlined/cross.svg"; function ReusableDialog() { const [isOpen, setIsOpen] = useState(false); @@ -19,7 +19,10 @@ function ReusableDialog() { }; navigation.on(cons.events.navigation.REUSABLE_DIALOG_OPENED, handleOpen); return () => { - navigation.removeListener(cons.events.navigation.REUSABLE_DIALOG_OPENED, handleOpen); + navigation.removeListener( + cons.events.navigation.REUSABLE_DIALOG_OPENED, + handleOpen + ); }; }, []); @@ -35,10 +38,16 @@ function ReusableDialog() { return ( } + contentOptions={ + + } invisibleScroll > {data?.render(handleRequestClose) ||
} diff --git a/src/app/molecules/following-members/FollowingMembers.jsx b/src/app/molecules/following-members/FollowingMembers.jsx index 65296535..8041c9a5 100644 --- a/src/app/molecules/following-members/FollowingMembers.jsx +++ b/src/app/molecules/following-members/FollowingMembers.jsx @@ -1,17 +1,17 @@ /* eslint-disable react/prop-types */ -import React, { useState, useEffect } from 'react'; -import PropTypes from 'prop-types'; -import './FollowingMembers.scss'; +import React, { useState, useEffect } from "react"; +import PropTypes from "prop-types"; +import "./FollowingMembers.scss"; -import initMatrix from '../../../client/initMatrix'; -import cons from '../../../client/state/cons'; -import { openReadReceipts } from '../../../client/action/navigation'; +import initMatrix from "../../../client/initMatrix"; +import cons from "../../../client/state/cons"; +import { openReadReceipts } from "../../../client/action/navigation"; -import Text from '../../atoms/text/Text'; -import RawIcon from '../../atoms/system-icons/RawIcon'; -import TickMarkIC from '../../../../public/res/ic/outlined/tick-mark.svg'; +import Text from "../../atoms/text/Text"; +import RawIcon from "../../atoms/system-icons/RawIcon"; +import TickMarkIC from "../../../../public/res/ic/outlined/tick-mark.svg"; -import { getUsersActionJsx } from '../../organisms/room/common'; +import { getUsersActionJsx } from "../../organisms/room/common"; function FollowingMembers({ roomTimeline }) { const [followingMembers, setFollowingMembers] = useState([]); @@ -27,28 +27,38 @@ function FollowingMembers({ roomTimeline }) { setFollowingMembers(roomTimeline.getLiveReaders()); }; updateFollowingMembers(); - roomTimeline.on(cons.events.roomTimeline.LIVE_RECEIPT, updateFollowingMembers); + roomTimeline.on( + cons.events.roomTimeline.LIVE_RECEIPT, + updateFollowingMembers + ); roomsInput.on(cons.events.roomsInput.MESSAGE_SENT, handleOnMessageSent); return () => { - roomTimeline.removeListener(cons.events.roomTimeline.LIVE_RECEIPT, updateFollowingMembers); - roomsInput.removeListener(cons.events.roomsInput.MESSAGE_SENT, handleOnMessageSent); + roomTimeline.removeListener( + cons.events.roomTimeline.LIVE_RECEIPT, + updateFollowingMembers + ); + roomsInput.removeListener( + cons.events.roomsInput.MESSAGE_SENT, + handleOnMessageSent + ); }; }, [roomTimeline]); const filteredM = followingMembers.filter((userId) => userId !== myUserId); - return filteredM.length !== 0 && ( - + return ( + filteredM.length !== 0 && ( + + ) ); } diff --git a/src/app/molecules/following-members/FollowingMembers.scss b/src/app/molecules/following-members/FollowingMembers.scss index a0daf5ad..4c62df4e 100644 --- a/src/app/molecules/following-members/FollowingMembers.scss +++ b/src/app/molecules/following-members/FollowingMembers.scss @@ -1,4 +1,4 @@ -@use '../../partials/text'; +@use "../../partials/text"; .following-members { width: 100%; @@ -7,7 +7,7 @@ justify-content: flex-end; align-items: center; cursor: pointer; - + & .ic-raw { min-width: var(--ic-extra-small); opacity: 0.4; @@ -28,4 +28,4 @@ &:active { background-color: var(--bg-surface-active); } -} \ No newline at end of file +} diff --git a/src/app/molecules/global-notification/GlobalNotification.jsx b/src/app/molecules/global-notification/GlobalNotification.jsx index 865582ce..bb51af92 100644 --- a/src/app/molecules/global-notification/GlobalNotification.jsx +++ b/src/app/molecules/global-notification/GlobalNotification.jsx @@ -1,59 +1,61 @@ -import React from 'react'; +import React from "react"; -import initMatrix from '../../../client/initMatrix'; -import { openReusableContextMenu } from '../../../client/action/navigation'; -import { getEventCords } from '../../../util/common'; +import initMatrix from "../../../client/initMatrix"; +import { openReusableContextMenu } from "../../../client/action/navigation"; +import { getEventCords } from "../../../util/common"; -import Text from '../../atoms/text/Text'; -import Button from '../../atoms/button/Button'; -import { MenuHeader } from '../../atoms/context-menu/ContextMenu'; -import SettingTile from '../setting-tile/SettingTile'; +import Text from "../../atoms/text/Text"; +import Button from "../../atoms/button/Button"; +import { MenuHeader } from "../../atoms/context-menu/ContextMenu"; +import SettingTile from "../setting-tile/SettingTile"; -import NotificationSelector from './NotificationSelector'; +import NotificationSelector from "./NotificationSelector"; -import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg'; +import ChevronBottomIC from "../../../../public/res/ic/outlined/chevron-bottom.svg"; -import { useAccountData } from '../../hooks/useAccountData'; +import { useAccountData } from "../../hooks/useAccountData"; export const notifType = { - ON: 'on', - OFF: 'off', - NOISY: 'noisy', + ON: "on", + OFF: "off", + NOISY: "noisy", }; export const typeToLabel = { - [notifType.ON]: 'On', - [notifType.OFF]: 'Off', - [notifType.NOISY]: 'Noisy', + [notifType.ON]: "On", + [notifType.OFF]: "Off", + [notifType.NOISY]: "Noisy", }; Object.freeze(notifType); -const DM = '.m.rule.room_one_to_one'; -const ENC_DM = '.m.rule.encrypted_room_one_to_one'; -const ROOM = '.m.rule.message'; -const ENC_ROOM = '.m.rule.encrypted'; +const DM = ".m.rule.room_one_to_one"; +const ENC_DM = ".m.rule.encrypted_room_one_to_one"; +const ROOM = ".m.rule.message"; +const ENC_ROOM = ".m.rule.encrypted"; export function getActionType(rule) { const { actions } = rule; - if (actions.find((action) => action?.set_tweak === 'sound')) return notifType.NOISY; - if (actions.find((action) => action?.set_tweak === 'highlight')) return notifType.ON; - if (actions.find((action) => action === 'dont_notify')) return notifType.OFF; + if (actions.find((action) => action?.set_tweak === "sound")) + return notifType.NOISY; + if (actions.find((action) => action?.set_tweak === "highlight")) + return notifType.ON; + if (actions.find((action) => action === "dont_notify")) return notifType.OFF; return notifType.OFF; } export function getTypeActions(type, highlightValue = false) { - if (type === notifType.OFF) return ['dont_notify']; + if (type === notifType.OFF) return ["dont_notify"]; - const highlight = { set_tweak: 'highlight' }; - if (typeof highlightValue === 'boolean') highlight.value = highlightValue; - if (type === notifType.ON) return ['notify', highlight]; + const highlight = { set_tweak: "highlight" }; + if (typeof highlightValue === "boolean") highlight.value = highlightValue; + if (type === notifType.ON) return ["notify", highlight]; - const sound = { set_tweak: 'sound', value: 'default' }; - return ['notify', sound, highlight]; + const sound = { set_tweak: "sound", value: "default" }; + return ["notify", sound, highlight]; } function useGlobalNotif() { const mx = initMatrix.matrixClient; - const pushRules = useAccountData('m.push_rules')?.getContent(); + const pushRules = useAccountData("m.push_rules")?.getContent(); const underride = pushRules?.global?.underride ?? []; const rulesToType = { [DM]: notifType.ON, @@ -65,12 +67,14 @@ function useGlobalNotif() { const getRuleCondition = (rule) => { const condition = []; if (rule === DM || rule === ENC_DM) { - condition.push({ kind: 'room_member_count', is: '2' }); + condition.push({ kind: "room_member_count", is: "2" }); } condition.push({ - kind: 'event_match', - key: 'type', - pattern: [ENC_DM, ENC_ROOM].includes(rule) ? 'm.room.encrypted' : 'm.room.message', + kind: "event_match", + key: "type", + pattern: [ENC_DM, ENC_ROOM].includes(rule) + ? "m.room.encrypted" + : "m.room.message", }); return condition; }; @@ -93,7 +97,7 @@ function useGlobalNotif() { } ruleContent.actions = getTypeActions(type); - mx.setAccountData('m.push_rules', content); + mx.setAccountData("m.push_rules", content); }; const dmRule = underride.find((rule) => rule.rule_id === DM); @@ -114,8 +118,8 @@ function GlobalNotification() { const onSelect = (evt, rule) => { openReusableContextMenu( - 'bottom', - getEventCords(evt, '.btn-surface'), + "bottom", + getEventCords(evt, ".btn-surface"), (requestClose) => ( - ), + ) ); }; @@ -133,39 +137,67 @@ function GlobalNotification() { Global Notifications onSelect(evt, DM)} iconSrc={ChevronBottomIC}> - { typeToLabel[rulesToType[DM]] } + options={ + - )} - content={Default notification settings for all direct message.} + } + content={ + + Default notification settings for all direct message. + + } /> onSelect(evt, ENC_DM)} iconSrc={ChevronBottomIC}> + options={ + - )} - content={Default notification settings for all encrypted direct message.} + } + content={ + + Default notification settings for all encrypted direct message. + + } /> onSelect(evt, ROOM)} iconSrc={ChevronBottomIC}> + options={ + - )} - content={Default notification settings for all room message.} + } + content={ + + Default notification settings for all room message. + + } /> onSelect(evt, ENC_ROOM)} iconSrc={ChevronBottomIC}> + options={ + - )} - content={Default notification settings for all encrypted room message.} + } + content={ + + Default notification settings for all encrypted room message. + + } />
); diff --git a/src/app/molecules/global-notification/IgnoreUserList.jsx b/src/app/molecules/global-notification/IgnoreUserList.jsx index 87ee6272..0ea6f3b9 100644 --- a/src/app/molecules/global-notification/IgnoreUserList.jsx +++ b/src/app/molecules/global-notification/IgnoreUserList.jsx @@ -1,31 +1,31 @@ -import React from 'react'; -import './IgnoreUserList.scss'; +import React from "react"; +import "./IgnoreUserList.scss"; -import initMatrix from '../../../client/initMatrix'; -import * as roomActions from '../../../client/action/room'; +import initMatrix from "../../../client/initMatrix"; +import * as roomActions from "../../../client/action/room"; -import Text from '../../atoms/text/Text'; -import Chip from '../../atoms/chip/Chip'; -import Input from '../../atoms/input/Input'; -import Button from '../../atoms/button/Button'; -import { MenuHeader } from '../../atoms/context-menu/ContextMenu'; -import SettingTile from '../setting-tile/SettingTile'; +import Text from "../../atoms/text/Text"; +import Chip from "../../atoms/chip/Chip"; +import Input from "../../atoms/input/Input"; +import Button from "../../atoms/button/Button"; +import { MenuHeader } from "../../atoms/context-menu/ContextMenu"; +import SettingTile from "../setting-tile/SettingTile"; -import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; +import CrossIC from "../../../../public/res/ic/outlined/cross.svg"; -import { useAccountData } from '../../hooks/useAccountData'; +import { useAccountData } from "../../hooks/useAccountData"; function IgnoreUserList() { - useAccountData('m.ignored_user_list'); + useAccountData("m.ignored_user_list"); const ignoredUsers = initMatrix.matrixClient.getIgnoredUsers(); const handleSubmit = (evt) => { evt.preventDefault(); const { ignoreInput } = evt.target.elements; const value = ignoreInput.value.trim(); - const userIds = value.split(' ').filter((v) => v.match(/^@\S+:\S+$/)); + const userIds = value.split(" ").filter((v) => v.match(/^@\S+:\S+$/)); if (userIds.length === 0) return; - ignoreInput.value = ''; + ignoreInput.value = ""; roomActions.ignore(userIds); }; @@ -34,12 +34,17 @@ function IgnoreUserList() { Ignored users - Ignore userId if you do not want to receive their messages or invites. + + Ignore userId if you do not want to receive their messages or + invites. +
- +
{ignoredUsers.length > 0 && (
@@ -55,7 +60,7 @@ function IgnoreUserList() {
)} - )} + } /> ); diff --git a/src/app/molecules/global-notification/IgnoreUserList.scss b/src/app/molecules/global-notification/IgnoreUserList.scss index 92831558..05a746dc 100644 --- a/src/app/molecules/global-notification/IgnoreUserList.scss +++ b/src/app/molecules/global-notification/IgnoreUserList.scss @@ -14,4 +14,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/app/molecules/global-notification/KeywordNotification.jsx b/src/app/molecules/global-notification/KeywordNotification.jsx index 8484d41d..25bf44e4 100644 --- a/src/app/molecules/global-notification/KeywordNotification.jsx +++ b/src/app/molecules/global-notification/KeywordNotification.jsx @@ -1,35 +1,38 @@ -import React from 'react'; -import './KeywordNotification.scss'; +import React from "react"; +import "./KeywordNotification.scss"; -import initMatrix from '../../../client/initMatrix'; -import { openReusableContextMenu } from '../../../client/action/navigation'; -import { getEventCords } from '../../../util/common'; +import initMatrix from "../../../client/initMatrix"; +import { openReusableContextMenu } from "../../../client/action/navigation"; +import { getEventCords } from "../../../util/common"; -import Text from '../../atoms/text/Text'; -import Chip from '../../atoms/chip/Chip'; -import Input from '../../atoms/input/Input'; -import Button from '../../atoms/button/Button'; -import { MenuHeader } from '../../atoms/context-menu/ContextMenu'; -import SettingTile from '../setting-tile/SettingTile'; +import Text from "../../atoms/text/Text"; +import Chip from "../../atoms/chip/Chip"; +import Input from "../../atoms/input/Input"; +import Button from "../../atoms/button/Button"; +import { MenuHeader } from "../../atoms/context-menu/ContextMenu"; +import SettingTile from "../setting-tile/SettingTile"; -import NotificationSelector from './NotificationSelector'; +import NotificationSelector from "./NotificationSelector"; -import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg'; -import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; +import ChevronBottomIC from "../../../../public/res/ic/outlined/chevron-bottom.svg"; +import CrossIC from "../../../../public/res/ic/outlined/cross.svg"; -import { useAccountData } from '../../hooks/useAccountData'; +import { useAccountData } from "../../hooks/useAccountData"; import { - notifType, typeToLabel, getActionType, getTypeActions, -} from './GlobalNotification'; + notifType, + typeToLabel, + getActionType, + getTypeActions, +} from "./GlobalNotification"; -const DISPLAY_NAME = '.m.rule.contains_display_name'; -const ROOM_PING = '.m.rule.roomnotif'; -const USERNAME = '.m.rule.contains_user_name'; -const KEYWORD = 'keyword'; +const DISPLAY_NAME = ".m.rule.contains_display_name"; +const ROOM_PING = ".m.rule.roomnotif"; +const USERNAME = ".m.rule.contains_user_name"; +const KEYWORD = "keyword"; function useKeywordNotif() { const mx = initMatrix.matrixClient; - const pushRules = useAccountData('m.push_rules')?.getContent(); + const pushRules = useAccountData("m.push_rules")?.getContent(); const override = pushRules?.global?.override ?? []; const content = pushRules?.global?.content ?? []; @@ -60,12 +63,12 @@ function useKeywordNotif() { or.push(orRule); } if (rule === DISPLAY_NAME) { - orRule.conditions = [{ kind: 'contains_display_name' }]; + orRule.conditions = [{ kind: "contains_display_name" }]; orRule.actions = getTypeActions(type, true); } else { orRule.conditions = [ - { kind: 'event_match', key: 'content.body', pattern: '@room' }, - { kind: 'sender_notification_permission', key: 'room' }, + { kind: "event_match", key: "content.body", pattern: "@room" }, + { kind: "sender_notification_permission", key: "room" }, ]; orRule.actions = getTypeActions(type, true); } @@ -92,7 +95,7 @@ function useKeywordNotif() { }); } - mx.setAccountData('m.push_rules', evtContent); + mx.setAccountData("m.push_rules", evtContent); }; const addKeyword = (keyword) => { @@ -104,11 +107,13 @@ function useKeywordNotif() { default: false, actions: getTypeActions(rulesToType[KEYWORD] ?? notifType.NOISY, true), }); - mx.setAccountData('m.push_rules', pushRules); + mx.setAccountData("m.push_rules", pushRules); }; const removeKeyword = (rule) => { - pushRules.global.content = content.filter((r) => r.rule_id !== rule.rule_id); - mx.setAccountData('m.push_rules', pushRules); + pushRules.global.content = content.filter( + (r) => r.rule_id !== rule.rule_id + ); + mx.setAccountData("m.push_rules", pushRules); }; const dsRule = override.find((rule) => rule.rule_id === DISPLAY_NAME); @@ -131,20 +136,16 @@ function useKeywordNotif() { } function GlobalNotification() { - const { - rulesToType, - pushRules, - setRule, - addKeyword, - removeKeyword, - } = useKeywordNotif(); + const { rulesToType, pushRules, setRule, addKeyword, removeKeyword } = + useKeywordNotif(); - const keywordRules = pushRules?.global?.content.filter((r) => r.rule_id !== USERNAME) ?? []; + const keywordRules = + pushRules?.global?.content.filter((r) => r.rule_id !== USERNAME) ?? []; const onSelect = (evt, rule) => { openReusableContextMenu( - 'bottom', - getEventCords(evt, '.btn-surface'), + "bottom", + getEventCords(evt, ".btn-surface"), (requestClose) => ( - ), + ) ); }; @@ -161,9 +162,9 @@ function GlobalNotification() { evt.preventDefault(); const { keywordInput } = evt.target.elements; const value = keywordInput.value.trim(); - if (value === '') return; + if (value === "") return; addKeyword(value); - keywordInput.value = ''; + keywordInput.value = ""; }; return ( @@ -171,50 +172,84 @@ function GlobalNotification() { Mentions & keywords onSelect(evt, DISPLAY_NAME)} iconSrc={ChevronBottomIC}> - { typeToLabel[rulesToType[DISPLAY_NAME]] } + options={ + - )} - content={Default notification settings for all message containing your display name.} + } + content={ + + Default notification settings for all message containing your + display name. + + } /> onSelect(evt, USERNAME)} iconSrc={ChevronBottomIC}> - { typeToLabel[rulesToType[USERNAME]] } + options={ + - )} - content={Default notification settings for all message containing your username.} + } + content={ + + Default notification settings for all message containing your + username. + + } /> onSelect(evt, ROOM_PING)} iconSrc={ChevronBottomIC}> + options={ + - )} - content={Default notification settings for all messages containing @room.} + } + content={ + + Default notification settings for all messages containing @room. + + } /> - { rulesToType[KEYWORD] && ( + {rulesToType[KEYWORD] && ( onSelect(evt, KEYWORD)} iconSrc={ChevronBottomIC}> + options={ + - )} - content={Default notification settings for all message containing keywords.} + } + content={ + + Default notification settings for all message containing keywords. + + } /> )} - Get notification when a message contains keyword. + + Get notification when a message contains keyword. +
- +
{keywordRules.length > 0 && (
@@ -230,7 +265,7 @@ function GlobalNotification() {
)} - )} + } /> ); diff --git a/src/app/molecules/global-notification/KeywordNotification.scss b/src/app/molecules/global-notification/KeywordNotification.scss index 4d1bfd48..1d3ff997 100644 --- a/src/app/molecules/global-notification/KeywordNotification.scss +++ b/src/app/molecules/global-notification/KeywordNotification.scss @@ -14,4 +14,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/app/molecules/global-notification/NotificationSelector.jsx b/src/app/molecules/global-notification/NotificationSelector.jsx index b2a8f4ec..e99daad3 100644 --- a/src/app/molecules/global-notification/NotificationSelector.jsx +++ b/src/app/molecules/global-notification/NotificationSelector.jsx @@ -1,25 +1,41 @@ -import React from 'react'; -import PropTypes from 'prop-types'; +import React from "react"; +import PropTypes from "prop-types"; -import { MenuHeader, MenuItem } from '../../atoms/context-menu/ContextMenu'; +import { MenuHeader, MenuItem } from "../../atoms/context-menu/ContextMenu"; -import CheckIC from '../../../../public/res/ic/outlined/check.svg'; +import CheckIC from "../../../../public/res/ic/outlined/check.svg"; -function NotificationSelector({ - value, onSelect, -}) { +function NotificationSelector({ value, onSelect }) { return (
Notification - onSelect('off')}>Off - onSelect('on')}>On - onSelect('noisy')}>Noisy + onSelect("off")} + > + Off + + onSelect("on")} + > + On + + onSelect("noisy")} + > + Noisy +
); } NotificationSelector.propTypes = { - value: PropTypes.oneOf(['off', 'on', 'noisy']).isRequired, + value: PropTypes.oneOf(["off", "on", "noisy"]).isRequired, onSelect: PropTypes.func.isRequired, }; diff --git a/src/app/molecules/image-lightbox/ImageLightbox.jsx b/src/app/molecules/image-lightbox/ImageLightbox.jsx index c1c45db8..83d151b2 100644 --- a/src/app/molecules/image-lightbox/ImageLightbox.jsx +++ b/src/app/molecules/image-lightbox/ImageLightbox.jsx @@ -1,18 +1,16 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './ImageLightbox.scss'; -import FileSaver from 'file-saver'; +import React from "react"; +import PropTypes from "prop-types"; +import "./ImageLightbox.scss"; +import FileSaver from "file-saver"; -import Text from '../../atoms/text/Text'; -import RawModal from '../../atoms/modal/RawModal'; -import IconButton from '../../atoms/button/IconButton'; +import Text from "../../atoms/text/Text"; +import RawModal from "../../atoms/modal/RawModal"; +import IconButton from "../../atoms/button/IconButton"; -import DownloadSVG from '../../../../public/res/ic/outlined/download.svg'; -import ExternalSVG from '../../../../public/res/ic/outlined/external.svg'; +import DownloadSVG from "../../../../public/res/ic/outlined/download.svg"; +import ExternalSVG from "../../../../public/res/ic/outlined/external.svg"; -function ImageLightbox({ - url, alt, isOpen, onRequestClose, -}) { +function ImageLightbox({ url, alt, isOpen, onRequestClose }) { const handleDownload = () => { FileSaver.saveAs(url, alt); }; @@ -26,8 +24,14 @@ function ImageLightbox({ size="large" >
- {alt} - window.open(url)} size="small" src={ExternalSVG} /> + + {alt} + + window.open(url)} + size="small" + src={ExternalSVG} + />
diff --git a/src/app/molecules/image-lightbox/ImageLightbox.scss b/src/app/molecules/image-lightbox/ImageLightbox.scss index 201fc91c..ca5a7d8f 100644 --- a/src/app/molecules/image-lightbox/ImageLightbox.scss +++ b/src/app/molecules/image-lightbox/ImageLightbox.scss @@ -1,5 +1,5 @@ -@use '../../partials/flex'; -@use '../../partials/text'; +@use "../../partials/flex"; +@use "../../partials/text"; .image-lightbox__modal { box-shadow: none; @@ -21,7 +21,6 @@ background-color: var(--bg-overlay-low); } - .image-lightbox__header > *, .image-lightbox__content > * { pointer-events: all; @@ -47,4 +46,4 @@ max-height: 100%; border-radius: var(--bo-radius); } -} \ No newline at end of file +} diff --git a/src/app/molecules/image-pack/ImagePack.jsx b/src/app/molecules/image-pack/ImagePack.jsx index f88886c1..7680f7a1 100644 --- a/src/app/molecules/image-pack/ImagePack.jsx +++ b/src/app/molecules/image-pack/ImagePack.jsx @@ -1,75 +1,80 @@ -import React, { - useState, useMemo, useReducer, useEffect, -} from 'react'; -import PropTypes from 'prop-types'; -import './ImagePack.scss'; +import React, { useState, useMemo, useReducer, useEffect } from "react"; +import PropTypes from "prop-types"; +import "./ImagePack.scss"; -import initMatrix from '../../../client/initMatrix'; -import { openReusableDialog } from '../../../client/action/navigation'; -import { suffixRename } from '../../../util/common'; +import initMatrix from "../../../client/initMatrix"; +import { openReusableDialog } from "../../../client/action/navigation"; +import { suffixRename } from "../../../util/common"; -import Button from '../../atoms/button/Button'; -import Text from '../../atoms/text/Text'; -import Input from '../../atoms/input/Input'; -import Checkbox from '../../atoms/button/Checkbox'; -import { MenuHeader } from '../../atoms/context-menu/ContextMenu'; +import Button from "../../atoms/button/Button"; +import Text from "../../atoms/text/Text"; +import Input from "../../atoms/input/Input"; +import Checkbox from "../../atoms/button/Checkbox"; +import { MenuHeader } from "../../atoms/context-menu/ContextMenu"; -import { ImagePack as ImagePackBuilder } from '../../organisms/emoji-board/custom-emoji'; -import { confirmDialog } from '../confirm-dialog/ConfirmDialog'; -import ImagePackProfile from './ImagePackProfile'; -import ImagePackItem from './ImagePackItem'; -import ImagePackUpload from './ImagePackUpload'; +import { ImagePack as ImagePackBuilder } from "../../organisms/emoji-board/custom-emoji"; +import { confirmDialog } from "../confirm-dialog/ConfirmDialog"; +import ImagePackProfile from "./ImagePackProfile"; +import ImagePackItem from "./ImagePackItem"; +import ImagePackUpload from "./ImagePackUpload"; -const renameImagePackItem = (shortcode) => new Promise((resolve) => { - let isCompleted = false; +const renameImagePackItem = (shortcode) => + new Promise((resolve) => { + let isCompleted = false; - openReusableDialog( - Rename, - (requestClose) => ( -
-
{ - e.preventDefault(); - const sc = e.target.shortcode.value; - if (sc.trim() === '') return; - isCompleted = true; - resolve(sc.trim()); - requestClose(); - }} - > - -
- - -
- ), - () => { - if (!isCompleted) resolve(null); - }, - ); -}); + openReusableDialog( + + Rename + , + (requestClose) => ( +
+
{ + e.preventDefault(); + const sc = e.target.shortcode.value; + if (sc.trim() === "") return; + isCompleted = true; + resolve(sc.trim()); + requestClose(); + }} + > + +
+ + +
+ ), + () => { + if (!isCompleted) resolve(null); + } + ); + }); function getUsage(usage) { - if (usage.includes('emoticon') && usage.includes('sticker')) return 'both'; - if (usage.includes('emoticon')) return 'emoticon'; - if (usage.includes('sticker')) return 'sticker'; + if (usage.includes("emoticon") && usage.includes("sticker")) return "both"; + if (usage.includes("emoticon")) return "emoticon"; + if (usage.includes("sticker")) return "sticker"; - return 'both'; + return "both"; } function isGlobalPack(roomId, stateKey) { const mx = initMatrix.matrixClient; - const globalContent = mx.getAccountData('im.ponies.emote_rooms')?.getContent(); - if (typeof globalContent !== 'object') return false; + const globalContent = mx + .getAccountData("im.ponies.emote_rooms") + ?.getContent(); + if (typeof globalContent !== "object") return false; const { rooms } = globalContent; - if (typeof rooms !== 'object') return false; + if (typeof rooms !== "object") return false; return rooms[roomId]?.[stateKey] !== undefined; } @@ -78,13 +83,17 @@ function useRoomImagePack(roomId, stateKey) { const mx = initMatrix.matrixClient; const room = mx.getRoom(roomId); - const packEvent = room.currentState.getStateEvents('im.ponies.room_emotes', stateKey); - const pack = useMemo(() => ( - ImagePackBuilder.parsePack(packEvent.getId(), packEvent.getContent()) - ), [room, stateKey]); + const packEvent = room.currentState.getStateEvents( + "im.ponies.room_emotes", + stateKey + ); + const pack = useMemo( + () => ImagePackBuilder.parsePack(packEvent.getId(), packEvent.getContent()), + [room, stateKey] + ); const sendPackContent = (content) => { - mx.sendStateEvent(roomId, 'im.ponies.room_emotes', content, stateKey); + mx.sendStateEvent(roomId, "im.ponies.room_emotes", content, stateKey); }; return { @@ -95,16 +104,21 @@ function useRoomImagePack(roomId, stateKey) { function useUserImagePack() { const mx = initMatrix.matrixClient; - const packEvent = mx.getAccountData('im.ponies.user_emotes'); - const pack = useMemo(() => ( - ImagePackBuilder.parsePack(mx.getUserId(), packEvent?.getContent() ?? { - pack: { display_name: 'Personal' }, - images: {}, - }) - ), []); + const packEvent = mx.getAccountData("im.ponies.user_emotes"); + const pack = useMemo( + () => + ImagePackBuilder.parsePack( + mx.getUserId(), + packEvent?.getContent() ?? { + pack: { display_name: "Personal" }, + images: {}, + } + ), + [] + ); const sendPackContent = (content) => { - mx.setAccountData('im.ponies.user_emotes', content); + mx.setAccountData("im.ponies.user_emotes", content); }; return { @@ -117,12 +131,11 @@ function useImagePackHandles(pack, sendPackContent) { const [, forceUpdate] = useReducer((count) => count + 1, 0); const getNewKey = (key) => { - if (typeof key !== 'string') return undefined; - let newKey = key?.replace(/\s/g, '_'); + if (typeof key !== "string") return undefined; + let newKey = key?.replace(/\s/g, "_"); if (pack.getImages().get(newKey)) { - newKey = suffixRename( - newKey, - (suffixedKey) => pack.getImages().get(suffixedKey), + newKey = suffixRename(newKey, (suffixedKey) => + pack.getImages().get(suffixedKey) ); } return newKey; @@ -141,10 +154,12 @@ function useImagePackHandles(pack, sendPackContent) { }; const handleUsageChange = (newUsage) => { const usage = []; - if (newUsage === 'emoticon' || newUsage === 'both') usage.push('emoticon'); - if (newUsage === 'sticker' || newUsage === 'both') usage.push('sticker'); + if (newUsage === "emoticon" || newUsage === "both") usage.push("emoticon"); + if (newUsage === "sticker" || newUsage === "both") usage.push("sticker"); pack.setUsage(usage); - pack.getImages().forEach((img) => pack.setImageUsage(img.shortcode, undefined)); + pack + .getImages() + .forEach((img) => pack.setImageUsage(img.shortcode, undefined)); sendPackContent(pack.getContent()); forceUpdate(); @@ -161,10 +176,10 @@ function useImagePackHandles(pack, sendPackContent) { }; const handleDeleteItem = async (key) => { const isConfirmed = await confirmDialog( - 'Delete', + "Delete", `Are you sure that you want to delete "${key}"?`, - 'Delete', - 'danger', + "Delete", + "danger" ); if (!isConfirmed) return; pack.removeImage(key); @@ -174,8 +189,8 @@ function useImagePackHandles(pack, sendPackContent) { }; const handleUsageItem = (key, newUsage) => { const usage = []; - if (newUsage === 'emoticon' || newUsage === 'both') usage.push('emoticon'); - if (newUsage === 'sticker' || newUsage === 'both') usage.push('sticker'); + if (newUsage === "emoticon" || newUsage === "both") usage.push("emoticon"); + if (newUsage === "sticker" || newUsage === "both") usage.push("sticker"); pack.setImageUsage(key, usage); sendPackContent(pack.getContent()); @@ -205,21 +220,23 @@ function useImagePackHandles(pack, sendPackContent) { } function addGlobalImagePack(mx, roomId, stateKey) { - const content = mx.getAccountData('im.ponies.emote_rooms')?.getContent() ?? {}; + const content = + mx.getAccountData("im.ponies.emote_rooms")?.getContent() ?? {}; if (!content.rooms) content.rooms = {}; if (!content.rooms[roomId]) content.rooms[roomId] = {}; content.rooms[roomId][stateKey] = {}; - return mx.setAccountData('im.ponies.emote_rooms', content); + return mx.setAccountData("im.ponies.emote_rooms", content); } function removeGlobalImagePack(mx, roomId, stateKey) { - const content = mx.getAccountData('im.ponies.emote_rooms')?.getContent() ?? {}; + const content = + mx.getAccountData("im.ponies.emote_rooms")?.getContent() ?? {}; if (!content.rooms) return Promise.resolve(); if (!content.rooms[roomId]) return Promise.resolve(); delete content.rooms[roomId][stateKey]; if (Object.keys(content.rooms[roomId]).length === 0) { delete content.rooms[roomId]; } - return mx.setAccountData('im.ponies.emote_rooms', content); + return mx.setAccountData("im.ponies.emote_rooms", content); } function ImagePack({ roomId, stateKey, handlePackDelete }) { @@ -247,14 +264,17 @@ function ImagePack({ roomId, stateKey, handlePackDelete }) { }; const myPowerlevel = room.getMember(mx.getUserId())?.powerLevel || 0; - const canChange = room.currentState.hasSufficientPowerLevelFor('state_default', myPowerlevel); + const canChange = room.currentState.hasSufficientPowerLevelFor( + "state_default", + myPowerlevel + ); const handleDeletePack = async () => { const isConfirmed = await confirmDialog( - 'Delete Pack', + "Delete Pack", `Are you sure that you want to delete "${pack.displayName}"?`, - 'Delete', - 'danger', + "Delete", + "danger" ); if (!isConfirmed) return; handlePackDelete(stateKey); @@ -265,18 +285,20 @@ function ImagePack({ roomId, stateKey, handlePackDelete }) { return (
- { canChange && ( - - )} - { images.length === 0 ? null : ( + {canChange && } + {images.length === 0 ? null : (
Image @@ -300,21 +322,27 @@ function ImagePack({ roomId, stateKey, handlePackDelete }) {
{pack.images.size > 2 && ( + )} + {handlePackDelete && ( + )} - { handlePackDelete && }
)}
- +
Use globally - Add this pack to your account to use in all rooms. + + Add this pack to your account to use in all rooms. +
@@ -351,8 +379,12 @@ function ImagePackUser() { return (
- { images.length === 0 ? null : ( + {images.length === 0 ? null : (
Image @@ -380,14 +412,10 @@ function ImagePackUser() { ))}
)} - {(pack.images.size > 2) && ( + {pack.images.size > 2 && (
)} @@ -400,11 +428,13 @@ function useGlobalImagePack() { const mx = initMatrix.matrixClient; const roomIdToStateKeys = new Map(); - const globalContent = mx.getAccountData('im.ponies.emote_rooms')?.getContent() ?? { rooms: {} }; + const globalContent = mx + .getAccountData("im.ponies.emote_rooms") + ?.getContent() ?? { rooms: {} }; const { rooms } = globalContent; Object.keys(rooms).forEach((roomId) => { - if (typeof rooms[roomId] !== 'object') return; + if (typeof rooms[roomId] !== "object") return; const room = mx.getRoom(roomId); const stateKeys = Object.keys(rooms[roomId]); if (!room || stateKeys.length === 0) return; @@ -413,11 +443,11 @@ function useGlobalImagePack() { useEffect(() => { const handleEvent = (event) => { - if (event.getType() === 'im.ponies.emote_rooms') forceUpdate(); + if (event.getType() === "im.ponies.emote_rooms") forceUpdate(); }; - mx.addListener('accountData', handleEvent); + mx.addListener("accountData", handleEvent); return () => { - mx.removeListener('accountData', handleEvent); + mx.removeListener("accountData", handleEvent); }; }, []); @@ -436,29 +466,39 @@ function ImagePackGlobal() {
Global packs
- { - roomIdToStateKeys.size > 0 - ? [...roomIdToStateKeys].map(([roomId, stateKeys]) => { - const room = mx.getRoom(roomId); - return ( - stateKeys.map((stateKey) => { - const data = room.currentState.getStateEvents('im.ponies.room_emotes', stateKey); - const pack = ImagePackBuilder.parsePack(data?.getId(), data?.getContent()); - if (!pack) return null; - return ( -
- handleChange(roomId, stateKey)} isActive /> -
- {pack.displayName ?? 'Unknown'} - {room.name} -
-
- ); - }) + {roomIdToStateKeys.size > 0 ? ( + [...roomIdToStateKeys].map(([roomId, stateKeys]) => { + const room = mx.getRoom(roomId); + return stateKeys.map((stateKey) => { + const data = room.currentState.getStateEvents( + "im.ponies.room_emotes", + stateKey ); - }) - :
No global packs
- } + const pack = ImagePackBuilder.parsePack( + data?.getId(), + data?.getContent() + ); + if (!pack) return null; + return ( +
+ handleChange(roomId, stateKey)} + isActive + /> +
+ {pack.displayName ?? "Unknown"} + {room.name} +
+
+ ); + }); + }) + ) : ( +
+ No global packs +
+ )}
); diff --git a/src/app/molecules/image-pack/ImagePack.scss b/src/app/molecules/image-pack/ImagePack.scss index 91d6a185..f867ce75 100644 --- a/src/app/molecules/image-pack/ImagePack.scss +++ b/src/app/molecules/image-pack/ImagePack.scss @@ -1,4 +1,4 @@ -@use '../../partials/flex'; +@use "../../partials/flex"; .image-pack { &-item { diff --git a/src/app/molecules/image-pack/ImagePackItem.jsx b/src/app/molecules/image-pack/ImagePackItem.jsx index 27436793..270608b6 100644 --- a/src/app/molecules/image-pack/ImagePackItem.jsx +++ b/src/app/molecules/image-pack/ImagePackItem.jsx @@ -1,28 +1,33 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './ImagePackItem.scss'; +import React from "react"; +import PropTypes from "prop-types"; +import "./ImagePackItem.scss"; -import { openReusableContextMenu } from '../../../client/action/navigation'; -import { getEventCords } from '../../../util/common'; +import { openReusableContextMenu } from "../../../client/action/navigation"; +import { getEventCords } from "../../../util/common"; -import Avatar from '../../atoms/avatar/Avatar'; -import Text from '../../atoms/text/Text'; -import Button from '../../atoms/button/Button'; -import RawIcon from '../../atoms/system-icons/RawIcon'; -import IconButton from '../../atoms/button/IconButton'; -import ImagePackUsageSelector from './ImagePackUsageSelector'; +import Avatar from "../../atoms/avatar/Avatar"; +import Text from "../../atoms/text/Text"; +import Button from "../../atoms/button/Button"; +import RawIcon from "../../atoms/system-icons/RawIcon"; +import IconButton from "../../atoms/button/IconButton"; +import ImagePackUsageSelector from "./ImagePackUsageSelector"; -import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg'; -import PencilIC from '../../../../public/res/ic/outlined/pencil.svg'; -import BinIC from '../../../../public/res/ic/outlined/bin.svg'; +import ChevronBottomIC from "../../../../public/res/ic/outlined/chevron-bottom.svg"; +import PencilIC from "../../../../public/res/ic/outlined/pencil.svg"; +import BinIC from "../../../../public/res/ic/outlined/bin.svg"; function ImagePackItem({ - url, shortcode, usage, onUsageChange, onDelete, onRename, + url, + shortcode, + usage, + onUsageChange, + onDelete, + onRename, }) { const handleUsageSelect = (event) => { openReusableContextMenu( - 'bottom', - getEventCords(event, '.btn-surface'), + "bottom", + getEventCords(event, ".btn-surface"), (closeMenu) => ( - ), + ) ); }; return (
- +
{shortcode}
- {onRename && onRename(shortcode)} />} - {onDelete && onDelete(shortcode)} />} + {onRename && ( + onRename(shortcode)} + /> + )} + {onDelete && ( + onDelete(shortcode)} + /> + )}
@@ -67,7 +93,7 @@ ImagePackItem.defaultProps = { ImagePackItem.propTypes = { url: PropTypes.string.isRequired, shortcode: PropTypes.string.isRequired, - usage: PropTypes.oneOf(['emoticon', 'sticker', 'both']).isRequired, + usage: PropTypes.oneOf(["emoticon", "sticker", "both"]).isRequired, onUsageChange: PropTypes.func, onDelete: PropTypes.func, onRename: PropTypes.func, diff --git a/src/app/molecules/image-pack/ImagePackItem.scss b/src/app/molecules/image-pack/ImagePackItem.scss index ab1be3a7..fdf8cfc7 100644 --- a/src/app/molecules/image-pack/ImagePackItem.scss +++ b/src/app/molecules/image-pack/ImagePackItem.scss @@ -1,5 +1,5 @@ -@use '../../partials/flex'; -@use '../../partials/dir'; +@use "../../partials/flex"; +@use "../../partials/dir"; .image-pack-item { margin: 0 var(--sp-normal); @@ -40,4 +40,4 @@ gap: var(--sp-ultra-tight); } } -} \ No newline at end of file +} diff --git a/src/app/molecules/image-pack/ImagePackProfile.jsx b/src/app/molecules/image-pack/ImagePackProfile.jsx index b639936d..663292ab 100644 --- a/src/app/molecules/image-pack/ImagePackProfile.jsx +++ b/src/app/molecules/image-pack/ImagePackProfile.jsx @@ -1,24 +1,29 @@ -import React, { useState } from 'react'; -import PropTypes from 'prop-types'; -import './ImagePackProfile.scss'; +import React, { useState } from "react"; +import PropTypes from "prop-types"; +import "./ImagePackProfile.scss"; -import { openReusableContextMenu } from '../../../client/action/navigation'; -import { getEventCords } from '../../../util/common'; +import { openReusableContextMenu } from "../../../client/action/navigation"; +import { getEventCords } from "../../../util/common"; -import Text from '../../atoms/text/Text'; -import Avatar from '../../atoms/avatar/Avatar'; -import Button from '../../atoms/button/Button'; -import IconButton from '../../atoms/button/IconButton'; -import Input from '../../atoms/input/Input'; -import ImageUpload from '../image-upload/ImageUpload'; -import ImagePackUsageSelector from './ImagePackUsageSelector'; +import Text from "../../atoms/text/Text"; +import Avatar from "../../atoms/avatar/Avatar"; +import Button from "../../atoms/button/Button"; +import IconButton from "../../atoms/button/IconButton"; +import Input from "../../atoms/input/Input"; +import ImageUpload from "../image-upload/ImageUpload"; +import ImagePackUsageSelector from "./ImagePackUsageSelector"; -import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg'; -import PencilIC from '../../../../public/res/ic/outlined/pencil.svg'; +import ChevronBottomIC from "../../../../public/res/ic/outlined/chevron-bottom.svg"; +import PencilIC from "../../../../public/res/ic/outlined/pencil.svg"; function ImagePackProfile({ - avatarUrl, displayName, attribution, usage, - onUsageChange, onAvatarChange, onEditProfile, + avatarUrl, + displayName, + attribution, + usage, + onUsageChange, + onAvatarChange, + onEditProfile, }) { const [isEdit, setIsEdit] = useState(false); @@ -35,8 +40,8 @@ function ImagePackProfile({ const handleUsageSelect = (event) => { openReusableContextMenu( - 'bottom', - getEventCords(event, '.btn-surface'), + "bottom", + getEventCords(event, ".btn-surface"), (closeMenu) => ( - ), + ) ); }; return (
- { - onAvatarChange - ? ( - onAvatarChange(undefined)} - /> - ) - : - } + {onAvatarChange ? ( + onAvatarChange(undefined)} + /> + ) : ( + + )}
- { - isEdit - ? ( -
- - -
- - -
-
- ) : ( - <> -
- {displayName} - {onEditProfile && setIsEdit(true)} src={PencilIC} tooltip="Edit" />} -
- {attribution && {attribution}} - - ) - } + {isEdit ? ( +
+ + +
+ + +
+
+ ) : ( + <> +
+ {displayName} + {onEditProfile && ( + setIsEdit(true)} + src={PencilIC} + tooltip="Edit" + /> + )} +
+ {attribution && {attribution}} + + )}
Pack usage @@ -95,9 +114,9 @@ function ImagePackProfile({ iconSrc={onUsageChange ? ChevronBottomIC : null} > - {usage === 'emoticon' && 'Emoji'} - {usage === 'sticker' && 'Sticker'} - {usage === 'both' && 'Both'} + {usage === "emoticon" && "Emoji"} + {usage === "sticker" && "Sticker"} + {usage === "both" && "Both"}
@@ -116,7 +135,7 @@ ImagePackProfile.propTypes = { avatarUrl: PropTypes.string, displayName: PropTypes.string.isRequired, attribution: PropTypes.string, - usage: PropTypes.oneOf(['emoticon', 'sticker', 'both']).isRequired, + usage: PropTypes.oneOf(["emoticon", "sticker", "both"]).isRequired, onUsageChange: PropTypes.func, onAvatarChange: PropTypes.func, onEditProfile: PropTypes.func, diff --git a/src/app/molecules/image-pack/ImagePackProfile.scss b/src/app/molecules/image-pack/ImagePackProfile.scss index d21212fb..9da2b8c4 100644 --- a/src/app/molecules/image-pack/ImagePackProfile.scss +++ b/src/app/molecules/image-pack/ImagePackProfile.scss @@ -1,4 +1,4 @@ -@use '../../partials/flex'; +@use "../../partials/flex"; .image-pack-profile { padding: var(--sp-normal); @@ -34,4 +34,4 @@ margin-bottom: var(--sp-ultra-tight); } } -} \ No newline at end of file +} diff --git a/src/app/molecules/image-pack/ImagePackUpload.jsx b/src/app/molecules/image-pack/ImagePackUpload.jsx index 6295de1c..0a31916c 100644 --- a/src/app/molecules/image-pack/ImagePackUpload.jsx +++ b/src/app/molecules/image-pack/ImagePackUpload.jsx @@ -1,15 +1,15 @@ -import React, { useState, useRef } from 'react'; -import PropTypes from 'prop-types'; -import './ImagePackUpload.scss'; +import React, { useState, useRef } from "react"; +import PropTypes from "prop-types"; +import "./ImagePackUpload.scss"; -import initMatrix from '../../../client/initMatrix'; -import { scaleDownImage } from '../../../util/common'; +import initMatrix from "../../../client/initMatrix"; +import { scaleDownImage } from "../../../util/common"; -import Text from '../../atoms/text/Text'; -import Button from '../../atoms/button/Button'; -import Input from '../../atoms/input/Input'; -import IconButton from '../../atoms/button/IconButton'; -import CirclePlusIC from '../../../../public/res/ic/outlined/circle-plus.svg'; +import Text from "../../atoms/text/Text"; +import Button from "../../atoms/button/Button"; +import Input from "../../atoms/input/Input"; +import IconButton from "../../atoms/button/IconButton"; +import CirclePlusIC from "../../../../public/res/ic/outlined/circle-plus.svg"; function ImagePackUpload({ onUpload }) { const mx = initMatrix.matrixClient; @@ -23,7 +23,7 @@ function ImagePackUpload({ onUpload }) { if (!imgFile) return; const { shortcodeInput } = evt.target; const shortcode = shortcodeInput.value.trim(); - if (shortcode === '') return; + if (shortcode === "") return; setProgress(true); const image = await scaleDownImage(imgFile, 512, 512); @@ -32,37 +32,53 @@ function ImagePackUpload({ onUpload }) { onUpload(shortcode, url); setProgress(false); setImgFile(null); - shortcodeRef.current.value = ''; + shortcodeRef.current.value = ""; }; const handleFileChange = (evt) => { const img = evt.target.files[0]; if (!img) return; setImgFile(img); - shortcodeRef.current.value = img.name.slice(0, img.name.indexOf('.')); + shortcodeRef.current.value = img.name.slice(0, img.name.indexOf(".")); shortcodeRef.current.focus(); }; const handleRemove = () => { setImgFile(null); inputRef.current.value = null; - shortcodeRef.current.value = ''; + shortcodeRef.current.value = ""; }; return (
- - { - imgFile - ? ( -
- - {imgFile.name} -
- ) - : - } - - + + {imgFile ? ( +
+ + {imgFile.name} +
+ ) : ( + + )} + +
); } diff --git a/src/app/molecules/image-pack/ImagePackUpload.scss b/src/app/molecules/image-pack/ImagePackUpload.scss index 75b57ed2..c9c44233 100644 --- a/src/app/molecules/image-pack/ImagePackUpload.scss +++ b/src/app/molecules/image-pack/ImagePackUpload.scss @@ -1,5 +1,5 @@ -@use '../../partials/dir'; -@use '../../partials/text'; +@use "../../partials/dir"; +@use "../../partials/text"; .image-pack-upload { padding: var(--sp-normal); @@ -19,7 +19,7 @@ background: var(--bg-surface-low); border-radius: var(--bo-radius); box-shadow: var(--bs-surface-border); - + & button { --parent-height: 40px; width: var(--parent-height); @@ -28,16 +28,16 @@ justify-content: center; align-items: center; } - + & .ic-raw { background-color: var(--bg-caution); transform: rotate(45deg); } - + & .text { @extend .cp-txt__ellipsis; @include dir.side(margin, var(--sp-ultra-tight), var(--sp-normal)); max-width: 86px; } } -} \ No newline at end of file +} diff --git a/src/app/molecules/image-pack/ImagePackUsageSelector.jsx b/src/app/molecules/image-pack/ImagePackUsageSelector.jsx index 279b3816..720c4d25 100644 --- a/src/app/molecules/image-pack/ImagePackUsageSelector.jsx +++ b/src/app/molecules/image-pack/ImagePackUsageSelector.jsx @@ -1,31 +1,31 @@ -import React from 'react'; -import PropTypes from 'prop-types'; +import React from "react"; +import PropTypes from "prop-types"; -import { MenuHeader, MenuItem } from '../../atoms/context-menu/ContextMenu'; -import CheckIC from '../../../../public/res/ic/outlined/check.svg'; +import { MenuHeader, MenuItem } from "../../atoms/context-menu/ContextMenu"; +import CheckIC from "../../../../public/res/ic/outlined/check.svg"; function ImagePackUsageSelector({ usage, onSelect }) { return (
Usage onSelect('emoticon')} + iconSrc={usage === "emoticon" ? CheckIC : undefined} + variant={usage === "emoticon" ? "positive" : "surface"} + onClick={() => onSelect("emoticon")} > Emoji onSelect('sticker')} + iconSrc={usage === "sticker" ? CheckIC : undefined} + variant={usage === "sticker" ? "positive" : "surface"} + onClick={() => onSelect("sticker")} > Sticker onSelect('both')} + iconSrc={usage === "both" ? CheckIC : undefined} + variant={usage === "both" ? "positive" : "surface"} + onClick={() => onSelect("both")} > Both @@ -34,7 +34,7 @@ function ImagePackUsageSelector({ usage, onSelect }) { } ImagePackUsageSelector.propTypes = { - usage: PropTypes.oneOf(['emoticon', 'sticker', 'both']).isRequired, + usage: PropTypes.oneOf(["emoticon", "sticker", "both"]).isRequired, onSelect: PropTypes.func.isRequired, }; diff --git a/src/app/molecules/image-upload/ImageUpload.jsx b/src/app/molecules/image-upload/ImageUpload.jsx index 53fc7e16..ed6f2257 100644 --- a/src/app/molecules/image-upload/ImageUpload.jsx +++ b/src/app/molecules/image-upload/ImageUpload.jsx @@ -1,18 +1,22 @@ -import React, { useState, useRef } from 'react'; -import PropTypes from 'prop-types'; -import './ImageUpload.scss'; +import React, { useState, useRef } from "react"; +import PropTypes from "prop-types"; +import "./ImageUpload.scss"; -import initMatrix from '../../../client/initMatrix'; +import initMatrix from "../../../client/initMatrix"; -import Text from '../../atoms/text/Text'; -import Avatar from '../../atoms/avatar/Avatar'; -import Spinner from '../../atoms/spinner/Spinner'; -import RawIcon from '../../atoms/system-icons/RawIcon'; +import Text from "../../atoms/text/Text"; +import Avatar from "../../atoms/avatar/Avatar"; +import Spinner from "../../atoms/spinner/Spinner"; +import RawIcon from "../../atoms/system-icons/RawIcon"; -import PlusIC from '../../../../public/res/ic/outlined/plus.svg'; +import PlusIC from "../../../../public/res/ic/outlined/plus.svg"; function ImageUpload({ - text, bgColor, imageSrc, onUpload, onRequestRemove, + text, + bgColor, + imageSrc, + onUpload, + onRequestRemove, size, }) { const [uploadPromise, setUploadPromise] = useState(null); @@ -26,7 +30,7 @@ function ImageUpload({ setUploadPromise(uPromise); const res = await uPromise; - if (typeof res?.content_uri === 'string') onUpload(res.content_uri); + if (typeof res?.content_uri === "string") onUpload(res.content_uri); setUploadPromise(null); } catch { setUploadPromise(null); @@ -50,40 +54,48 @@ function ImageUpload({ uploadImageRef.current.click(); }} > - -
- {uploadPromise === null && ( - size === 'large' - ? Upload - : - )} + +
+ {uploadPromise === null && + (size === "large" ? ( + + Upload + + ) : ( + + ))} {uploadPromise !== null && }
- { (typeof imageSrc === 'string' || uploadPromise !== null) && ( + {(typeof imageSrc === "string" || uploadPromise !== null) && ( )} - +
); } ImageUpload.defaultProps = { text: null, - bgColor: 'transparent', + bgColor: "transparent", imageSrc: null, - size: 'large', + size: "large", }; ImageUpload.propTypes = { @@ -92,7 +104,7 @@ ImageUpload.propTypes = { imageSrc: PropTypes.string, onUpload: PropTypes.func.isRequired, onRequestRemove: PropTypes.func.isRequired, - size: PropTypes.oneOf(['large', 'normal']), + size: PropTypes.oneOf(["large", "normal"]), }; export default ImageUpload; diff --git a/src/app/molecules/image-upload/ImageUpload.scss b/src/app/molecules/image-upload/ImageUpload.scss index 3ae38bc0..835fa81a 100644 --- a/src/app/molecules/image-upload/ImageUpload.scss +++ b/src/app/molecules/image-upload/ImageUpload.scss @@ -1,49 +1,48 @@ .img-upload__wrapper { - display: flex; - flex-direction: column; - align-items: center; + display: flex; + flex-direction: column; + align-items: center; } .img-upload { - display: flex; - cursor: pointer; - position: relative; + display: flex; + cursor: pointer; + position: relative; - &__process { - width: 100%; - height: 100%; - border-radius: var(--bo-radius); - display: flex; - justify-content: center; - align-items: center; - background-color: rgba(0, 0, 0, .6); - - position: absolute; - left: 0; - right: 0; - z-index: 1; - & .text { - text-transform: uppercase; - color: white; - } - &--stopped { - display: none; - } - & .donut-spinner { - border-color: rgb(255, 255, 255, .3); - border-left-color: white; - } - } - &:hover .img-upload__process--stopped { - display: flex; - } + &__process { + width: 100%; + height: 100%; + border-radius: var(--bo-radius); + display: flex; + justify-content: center; + align-items: center; + background-color: rgba(0, 0, 0, 0.6); + position: absolute; + left: 0; + right: 0; + z-index: 1; + & .text { + text-transform: uppercase; + color: white; + } + &--stopped { + display: none; + } + & .donut-spinner { + border-color: rgb(255, 255, 255, 0.3); + border-left-color: white; + } + } + &:hover .img-upload__process--stopped { + display: flex; + } - &__btn-cancel { - margin-top: var(--sp-extra-tight); - cursor: pointer; - & .text { - color: var(--tc-danger-normal) - } - } + &__btn-cancel { + margin-top: var(--sp-extra-tight); + cursor: pointer; + & .text { + color: var(--tc-danger-normal); + } + } } diff --git a/src/app/molecules/import-export-e2e-room-keys/ExportE2ERoomKeys.jsx b/src/app/molecules/import-export-e2e-room-keys/ExportE2ERoomKeys.jsx index b7738a6a..93775f47 100644 --- a/src/app/molecules/import-export-e2e-room-keys/ExportE2ERoomKeys.jsx +++ b/src/app/molecules/import-export-e2e-room-keys/ExportE2ERoomKeys.jsx @@ -1,18 +1,18 @@ -import React, { useState, useEffect, useRef } from 'react'; -import './ExportE2ERoomKeys.scss'; +import React, { useState, useEffect, useRef } from "react"; +import "./ExportE2ERoomKeys.scss"; -import FileSaver from 'file-saver'; +import FileSaver from "file-saver"; -import initMatrix from '../../../client/initMatrix'; -import cons from '../../../client/state/cons'; -import { encryptMegolmKeyFile } from '../../../util/cryptE2ERoomKeys'; +import initMatrix from "../../../client/initMatrix"; +import cons from "../../../client/state/cons"; +import { encryptMegolmKeyFile } from "../../../util/cryptE2ERoomKeys"; -import Text from '../../atoms/text/Text'; -import Button from '../../atoms/button/Button'; -import Input from '../../atoms/input/Input'; -import Spinner from '../../atoms/spinner/Spinner'; +import Text from "../../atoms/text/Text"; +import Button from "../../atoms/button/Button"; +import Input from "../../atoms/input/Input"; +import Spinner from "../../atoms/spinner/Spinner"; -import { useStore } from '../../hooks/useStore'; +import { useStore } from "../../hooks/useStore"; function ExportE2ERoomKeys() { const isMountStore = useStore(); @@ -29,14 +29,14 @@ function ExportE2ERoomKeys() { if (password !== confirmPasswordRef.current.value) { setStatus({ isOngoing: false, - msg: 'Password does not match.', + msg: "Password does not match.", type: cons.status.ERROR, }); return; } setStatus({ isOngoing: true, - msg: 'Getting keys...', + msg: "Getting keys...", type: cons.status.IN_FLIGHT, }); try { @@ -44,19 +44,22 @@ function ExportE2ERoomKeys() { if (isMountStore.getItem()) { setStatus({ isOngoing: true, - msg: 'Encrypting keys...', + msg: "Encrypting keys...", type: cons.status.IN_FLIGHT, }); } - const encKeys = await encryptMegolmKeyFile(JSON.stringify(keys), password); + const encKeys = await encryptMegolmKeyFile( + JSON.stringify(keys), + password + ); const blob = new Blob([encKeys], { - type: 'text/plain;charset=us-ascii', + type: "text/plain;charset=us-ascii", }); - FileSaver.saveAs(blob, 'cinny-keys.txt'); + FileSaver.saveAs(blob, "cinny-keys.txt"); if (isMountStore.getItem()) { setStatus({ isOngoing: false, - msg: 'Successfully exported all keys.', + msg: "Successfully exported all keys.", type: cons.status.SUCCESS, }); } @@ -64,7 +67,7 @@ function ExportE2ERoomKeys() { if (isMountStore.getItem()) { setStatus({ isOngoing: false, - msg: e.friendlyText || 'Failed to export keys. Please try again.', + msg: e.friendlyText || "Failed to export keys. Please try again.", type: cons.status.ERROR, }); } @@ -80,19 +83,45 @@ function ExportE2ERoomKeys() { return (
-
{ e.preventDefault(); exportE2ERoomKeys(); }}> - - - + { + e.preventDefault(); + exportE2ERoomKeys(); + }} + > + + +
- { status.type === cons.status.IN_FLIGHT && ( + {status.type === cons.status.IN_FLIGHT && (
{status.msg}
)} - {status.type === cons.status.SUCCESS && {status.msg}} - {status.type === cons.status.ERROR && {status.msg}} + {status.type === cons.status.SUCCESS && ( + + {status.msg} + + )} + {status.type === cons.status.ERROR && ( + + {status.msg} + + )}
); } diff --git a/src/app/molecules/import-export-e2e-room-keys/ExportE2ERoomKeys.scss b/src/app/molecules/import-export-e2e-room-keys/ExportE2ERoomKeys.scss index a8bd029d..d03d3695 100644 --- a/src/app/molecules/import-export-e2e-room-keys/ExportE2ERoomKeys.scss +++ b/src/app/molecules/import-export-e2e-room-keys/ExportE2ERoomKeys.scss @@ -24,5 +24,4 @@ margin-top: var(--sp-tight); color: var(--tc-danger-high); } - -} \ No newline at end of file +} diff --git a/src/app/molecules/import-export-e2e-room-keys/ImportE2ERoomKeys.jsx b/src/app/molecules/import-export-e2e-room-keys/ImportE2ERoomKeys.jsx index b5a44b0e..4d94cec9 100644 --- a/src/app/molecules/import-export-e2e-room-keys/ImportE2ERoomKeys.jsx +++ b/src/app/molecules/import-export-e2e-room-keys/ImportE2ERoomKeys.jsx @@ -1,19 +1,19 @@ -import React, { useState, useEffect, useRef } from 'react'; -import './ImportE2ERoomKeys.scss'; +import React, { useState, useEffect, useRef } from "react"; +import "./ImportE2ERoomKeys.scss"; -import initMatrix from '../../../client/initMatrix'; -import cons from '../../../client/state/cons'; -import { decryptMegolmKeyFile } from '../../../util/cryptE2ERoomKeys'; +import initMatrix from "../../../client/initMatrix"; +import cons from "../../../client/state/cons"; +import { decryptMegolmKeyFile } from "../../../util/cryptE2ERoomKeys"; -import Text from '../../atoms/text/Text'; -import IconButton from '../../atoms/button/IconButton'; -import Button from '../../atoms/button/Button'; -import Input from '../../atoms/input/Input'; -import Spinner from '../../atoms/spinner/Spinner'; +import Text from "../../atoms/text/Text"; +import IconButton from "../../atoms/button/IconButton"; +import Button from "../../atoms/button/Button"; +import Input from "../../atoms/input/Input"; +import Spinner from "../../atoms/spinner/Spinner"; -import CirclePlusIC from '../../../../public/res/ic/outlined/circle-plus.svg'; +import CirclePlusIC from "../../../../public/res/ic/outlined/circle-plus.svg"; -import { useStore } from '../../hooks/useStore'; +import { useStore } from "../../hooks/useStore"; function ImportE2ERoomKeys() { const isMountStore = useStore(); @@ -32,7 +32,7 @@ function ImportE2ERoomKeys() { if (isMountStore.getItem()) { setStatus({ isOngoing: true, - msg: 'Decrypting file...', + msg: "Decrypting file...", type: cons.status.IN_FLIGHT, }); } @@ -41,7 +41,7 @@ function ImportE2ERoomKeys() { if (isMountStore.getItem()) { setStatus({ isOngoing: true, - msg: 'Decrypting messages...', + msg: "Decrypting messages...", type: cons.status.IN_FLIGHT, }); } @@ -49,7 +49,7 @@ function ImportE2ERoomKeys() { if (isMountStore.getItem()) { setStatus({ isOngoing: false, - msg: 'Successfully imported all keys.', + msg: "Successfully imported all keys.", type: cons.status.SUCCESS, }); inputRef.current.value = null; @@ -59,7 +59,7 @@ function ImportE2ERoomKeys() { if (isMountStore.getItem()) { setStatus({ isOngoing: false, - msg: e.friendlyText || 'Failed to decrypt keys. Please try again.', + msg: e.friendlyText || "Failed to decrypt keys. Please try again.", type: cons.status.ERROR, }); } @@ -68,7 +68,7 @@ function ImportE2ERoomKeys() { const importE2ERoomKeys = () => { const password = passwordRef.current.value; - if (password === '' || keyFile === null) return; + if (password === "" || keyFile === null) return; if (status.isOngoing) return; tryDecrypt(keyFile, password); @@ -76,7 +76,7 @@ function ImportE2ERoomKeys() { const handleFileChange = (e) => { const file = e.target.files.item(0); - passwordRef.current.value = ''; + passwordRef.current.value = ""; setKeyFile(file); setStatus({ isOngoing: false, @@ -105,27 +105,59 @@ function ImportE2ERoomKeys() { return (
- + -
{ e.preventDefault(); importE2ERoomKeys(); }}> - { keyFile !== null && ( + { + e.preventDefault(); + importE2ERoomKeys(); + }} + > + {keyFile !== null && (
- + {keyFile.name}
)} - {keyFile === null && } - - + {keyFile === null && ( + + )} + +
- { status.type === cons.status.IN_FLIGHT && ( + {status.type === cons.status.IN_FLIGHT && (
{status.msg}
)} - {status.type === cons.status.SUCCESS && {status.msg}} - {status.type === cons.status.ERROR && {status.msg}} + {status.type === cons.status.SUCCESS && ( + + {status.msg} + + )} + {status.type === cons.status.ERROR && ( + + {status.msg} + + )}
); } diff --git a/src/app/molecules/import-export-e2e-room-keys/ImportE2ERoomKeys.scss b/src/app/molecules/import-export-e2e-room-keys/ImportE2ERoomKeys.scss index 4f27fdfa..bbb3f190 100644 --- a/src/app/molecules/import-export-e2e-room-keys/ImportE2ERoomKeys.scss +++ b/src/app/molecules/import-export-e2e-room-keys/ImportE2ERoomKeys.scss @@ -1,5 +1,5 @@ -@use '../../partials/text'; -@use '../../partials/dir'; +@use "../../partials/text"; +@use "../../partials/dir"; .import-e2e-room-keys { &__file { @@ -22,7 +22,7 @@ background-color: var(--bg-caution); transform: rotate(45deg); } - + & .text { @extend .cp-txt__ellipsis; @include dir.side(margin, var(--sp-tight), var(--sp-loose)); @@ -33,8 +33,7 @@ &__form { display: flex; margin-top: var(--sp-extra-tight); - - + & .input-container { flex: 1; margin: 0 var(--sp-tight); @@ -58,4 +57,4 @@ margin-top: var(--sp-tight); color: var(--tc-positive-high); } -} \ No newline at end of file +} diff --git a/src/app/molecules/media/Media.jsx b/src/app/molecules/media/Media.jsx index e2b61775..f38a21e4 100644 --- a/src/app/molecules/media/Media.jsx +++ b/src/app/molecules/media/Media.jsx @@ -1,20 +1,20 @@ -import React, { useState, useEffect } from 'react'; -import PropTypes from 'prop-types'; -import './Media.scss'; +import React, { useState, useEffect } from "react"; +import PropTypes from "prop-types"; +import "./Media.scss"; -import encrypt from 'browser-encrypt-attachment'; +import encrypt from "browser-encrypt-attachment"; -import { BlurhashCanvas } from 'react-blurhash'; -import Text from '../../atoms/text/Text'; -import IconButton from '../../atoms/button/IconButton'; -import Spinner from '../../atoms/spinner/Spinner'; -import ImageLightbox from '../image-lightbox/ImageLightbox'; +import { BlurhashCanvas } from "react-blurhash"; +import Text from "../../atoms/text/Text"; +import IconButton from "../../atoms/button/IconButton"; +import Spinner from "../../atoms/spinner/Spinner"; +import ImageLightbox from "../image-lightbox/ImageLightbox"; -import DownloadSVG from '../../../../public/res/ic/outlined/download.svg'; -import ExternalSVG from '../../../../public/res/ic/outlined/external.svg'; -import PlaySVG from '../../../../public/res/ic/outlined/play.svg'; +import DownloadSVG from "../../../../public/res/ic/outlined/download.svg"; +import ExternalSVG from "../../../../public/res/ic/outlined/external.svg"; +import PlaySVG from "../../../../public/res/ic/outlined/play.svg"; -import { getBlobSafeMimeType } from '../../../util/mimetypes'; +import { getBlobSafeMimeType } from "../../../util/mimetypes"; async function getDecryptedBlob(response, type, decryptData) { const arrayBuffer = await response.arrayBuffer(); @@ -25,9 +25,11 @@ async function getDecryptedBlob(response, type, decryptData) { async function getUrl(link, type, decryptData) { try { - const response = await fetch(link, { method: 'GET' }); + const response = await fetch(link, { method: "GET" }); if (decryptData !== null) { - return URL.createObjectURL(await getDecryptedBlob(response, type, decryptData)); + return URL.createObjectURL( + await getDecryptedBlob(response, type, decryptData) + ); } const blob = await response.blob(); return URL.createObjectURL(blob); @@ -41,10 +43,7 @@ function getNativeHeight(width, height, maxWidth = 296) { return scale * height; } -function FileHeader({ - name, link, external, - file, type, -}) { +function FileHeader({ name, link, external, file, type }) { const [url, setUrl] = useState(null); async function getFile() { @@ -61,20 +60,25 @@ function FileHeader({ } return (
- {name} - { link !== null && ( + + {name} + + {link !== null && ( <> - { - external && ( - window.open(url || link)} - /> - ) - } - + {external && ( + window.open(url || link)} + /> + )} + @@ -111,7 +113,7 @@ function File({ } File.defaultProps = { file: null, - type: '', + type: "", }; File.propTypes = { name: PropTypes.string.isRequired, @@ -120,9 +122,7 @@ File.propTypes = { file: PropTypes.shape({}), }; -function Image({ - name, width, height, link, file, type, blurhash, -}) { +function Image({ name, width, height, link, file, type, blurhash }) { const [url, setUrl] = useState(null); const [blur, setBlur] = useState(true); const [lightbox, setLightbox] = useState(false); @@ -149,17 +149,19 @@ function Image({ <>
- { blurhash && blur && } - { url !== null && ( + {blurhash && blur && } + {url !== null && ( setBlur(false)} src={url || link} alt={name} @@ -182,8 +184,8 @@ Image.defaultProps = { file: null, width: null, height: null, - type: '', - blurhash: '', + type: "", + blurhash: "", }; Image.propTypes = { name: PropTypes.string.isRequired, @@ -195,9 +197,7 @@ Image.propTypes = { blurhash: PropTypes.string, }; -function Sticker({ - name, height, width, link, file, type, -}) { +function Sticker({ name, height, width, link, file, type }) { const [url, setUrl] = useState(null); useEffect(() => { @@ -214,14 +214,19 @@ function Sticker({ }, []); return ( -
- { url !== null && {name}} +
+ {url !== null && {name}}
); } Sticker.defaultProps = { file: null, - type: '', + type: "", width: null, height: null, }; @@ -234,9 +239,7 @@ Sticker.propTypes = { type: PropTypes.string, }; -function Audio({ - name, link, type, file, -}) { +function Audio({ name, link, type, file }) { const [isLoading, setIsLoading] = useState(false); const [url, setUrl] = useState(null); @@ -252,11 +255,22 @@ function Audio({ return (
- +
- { url === null && isLoading && } - { url === null && !isLoading && } - { url !== null && ( + {url === null && isLoading && } + {url === null && !isLoading && ( + + )} + {url !== null && ( /* eslint-disable-next-line jsx-a11y/media-has-caption */
); diff --git a/src/app/organisms/room/EventLimit.js b/src/app/organisms/room/EventLimit.js index de87da37..b749b143 100644 --- a/src/app/organisms/room/EventLimit.js +++ b/src/app/organisms/room/EventLimit.js @@ -7,7 +7,10 @@ class EventLimit { } get maxEvents() { - return Math.round(document.body.clientHeight / this.SMALLEST_EVT_HEIGHT) * this.PAGES_COUNT; + return ( + Math.round(document.body.clientHeight / this.SMALLEST_EVT_HEIGHT) * + this.PAGES_COUNT + ); } get from() { diff --git a/src/app/organisms/room/PeopleDrawer.jsx b/src/app/organisms/room/PeopleDrawer.jsx index 8f983247..1b8a052d 100644 --- a/src/app/organisms/room/PeopleDrawer.jsx +++ b/src/app/organisms/room/PeopleDrawer.jsx @@ -1,37 +1,41 @@ -import React, { - useState, useEffect, useCallback, useRef, -} from 'react'; -import PropTypes from 'prop-types'; -import './PeopleDrawer.scss'; +import React, { useState, useEffect, useCallback, useRef } from "react"; +import PropTypes from "prop-types"; +import "./PeopleDrawer.scss"; -import initMatrix from '../../../client/initMatrix'; -import { getPowerLabel, getUsernameOfRoomMember } from '../../../util/matrixUtil'; -import colorMXID from '../../../util/colorMXID'; -import { openInviteUser, openProfileViewer } from '../../../client/action/navigation'; -import AsyncSearch from '../../../util/AsyncSearch'; -import { memberByAtoZ, memberByPowerLevel } from '../../../util/sort'; +import initMatrix from "../../../client/initMatrix"; +import { + getPowerLabel, + getUsernameOfRoomMember, +} from "../../../util/matrixUtil"; +import colorMXID from "../../../util/colorMXID"; +import { + openInviteUser, + openProfileViewer, +} from "../../../client/action/navigation"; +import AsyncSearch from "../../../util/AsyncSearch"; +import { memberByAtoZ, memberByPowerLevel } from "../../../util/sort"; -import Text from '../../atoms/text/Text'; -import Header, { TitleWrapper } from '../../atoms/header/Header'; -import RawIcon from '../../atoms/system-icons/RawIcon'; -import IconButton from '../../atoms/button/IconButton'; -import Button from '../../atoms/button/Button'; -import ScrollView from '../../atoms/scroll/ScrollView'; -import Input from '../../atoms/input/Input'; -import SegmentedControl from '../../atoms/segmented-controls/SegmentedControls'; -import PeopleSelector from '../../molecules/people-selector/PeopleSelector'; +import Text from "../../atoms/text/Text"; +import Header, { TitleWrapper } from "../../atoms/header/Header"; +import RawIcon from "../../atoms/system-icons/RawIcon"; +import IconButton from "../../atoms/button/IconButton"; +import Button from "../../atoms/button/Button"; +import ScrollView from "../../atoms/scroll/ScrollView"; +import Input from "../../atoms/input/Input"; +import SegmentedControl from "../../atoms/segmented-controls/SegmentedControls"; +import PeopleSelector from "../../molecules/people-selector/PeopleSelector"; -import AddUserIC from '../../../../public/res/ic/outlined/add-user.svg'; -import SearchIC from '../../../../public/res/ic/outlined/search.svg'; -import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; +import AddUserIC from "../../../../public/res/ic/outlined/add-user.svg"; +import SearchIC from "../../../../public/res/ic/outlined/search.svg"; +import CrossIC from "../../../../public/res/ic/outlined/cross.svg"; function simplyfiMembers(members) { const mx = initMatrix.matrixClient; return members.map((member) => ({ userId: member.userId, name: getUsernameOfRoomMember(member), - username: member.userId.slice(1, member.userId.indexOf(':')), - avatarSrc: member.getAvatarUrl(mx.baseUrl, 24, 24, 'crop'), + username: member.userId.slice(1, member.userId.indexOf(":")), + avatarSrc: member.getAvatarUrl(mx.baseUrl, 24, 24, "crop"), peopleRole: getPowerLabel(member.powerLevel), powerLevel: members.powerLevel, })); @@ -45,14 +49,14 @@ function PeopleDrawer({ roomId }) { const canInvite = room?.canInvite(mx.getUserId()); const [itemCount, setItemCount] = useState(PER_PAGE_MEMBER); - const [membership, setMembership] = useState('join'); + const [membership, setMembership] = useState("join"); const [memberList, setMemberList] = useState([]); const [searchedMembers, setSearchedMembers] = useState(null); const searchRef = useRef(null); const getMembersWithMembership = useCallback( (mship) => room.getMembersWithMembership(mship), - [roomId, membership], + [roomId, membership] ); function loadMorePeople() { @@ -68,8 +72,8 @@ function PeopleDrawer({ roomId }) { function handleSearch(e) { const term = e.target.value; - if (term === '' || term === undefined) { - searchRef.current.value = ''; + if (term === "" || term === undefined) { + searchRef.current.value = ""; searchRef.current.focus(); setSearchedMembers(null); setItemCount(PER_PAGE_MEMBER); @@ -78,7 +82,7 @@ function PeopleDrawer({ roomId }) { useEffect(() => { asyncSearch.setup(memberList, { - keys: ['name', 'username', 'userId'], + keys: ["name", "username", "userId"], limit: PER_PAGE_MEMBER, }); }, [memberList]); @@ -92,11 +96,12 @@ function PeopleDrawer({ roomId }) { setMemberList( simplyfiMembers( getMembersWithMembership(membership) - .sort(memberByAtoZ).sort(memberByPowerLevel), - ), + .sort(memberByAtoZ) + .sort(memberByPowerLevel) + ) ); }; - searchRef.current.value = ''; + searchRef.current.value = ""; updateMemberList(); isLoadingMembers = true; room.loadMembersIfNeeded().then(() => { @@ -106,89 +111,95 @@ function PeopleDrawer({ roomId }) { }); asyncSearch.on(asyncSearch.RESULT_SENT, handleSearchData); - mx.on('RoomMember.membership', updateMemberList); - mx.on('RoomMember.powerLevel', updateMemberList); + mx.on("RoomMember.membership", updateMemberList); + mx.on("RoomMember.powerLevel", updateMemberList); return () => { isRoomChanged = true; setMemberList([]); setSearchedMembers(null); setItemCount(PER_PAGE_MEMBER); asyncSearch.removeListener(asyncSearch.RESULT_SENT, handleSearchData); - mx.removeListener('RoomMember.membership', updateMemberList); - mx.removeListener('RoomMember.powerLevel', updateMemberList); + mx.removeListener("RoomMember.membership", updateMemberList); + mx.removeListener("RoomMember.powerLevel", updateMemberList); }; }, [roomId, membership]); useEffect(() => { - setMembership('join'); + setMembership("join"); }, [roomId]); - const mList = searchedMembers !== null ? searchedMembers.data : memberList.slice(0, itemCount); + const mList = + searchedMembers !== null + ? searchedMembers.data + : memberList.slice(0, itemCount); return (
People - {`${room.getJoinedMemberCount()} members`} + {`${room.getJoinedMemberCount()} members`} - openInviteUser(roomId)} tooltip="Invite" src={AddUserIC} disabled={!canInvite} /> + openInviteUser(roomId)} + tooltip="Invite" + src={AddUserIC} + disabled={!canInvite} + />
{ - const getSegmentIndex = { - join: 0, - invite: 1, - ban: 2, - }; - return getSegmentIndex[membership]; - })() - } - segments={[{ text: 'Joined' }, { text: 'Invited' }, { text: 'Banned' }]} + selected={(() => { + const getSegmentIndex = { + join: 0, + invite: 1, + ban: 2, + }; + return getSegmentIndex[membership]; + })()} + segments={[ + { text: "Joined" }, + { text: "Invited" }, + { text: "Banned" }, + ]} onSelect={(index) => { const selectSegment = [ - () => setMembership('join'), - () => setMembership('invite'), - () => setMembership('ban'), + () => setMembership("join"), + () => setMembership("invite"), + () => setMembership("ban"), ]; selectSegment[index]?.(); }} /> - { - mList.map((member) => ( - openProfileViewer(member.userId, roomId)} - avatarSrc={member.avatarSrc} - name={member.name} - color={colorMXID(member.userId)} - peopleRole={member.peopleRole} - /> - )) - } - { - (searchedMembers?.data.length === 0 || memberList.length === 0) - && ( -
- No results found! -
- ) - } + {mList.map((member) => ( + openProfileViewer(member.userId, roomId)} + avatarSrc={member.avatarSrc} + name={member.name} + color={colorMXID(member.userId)} + peopleRole={member.peopleRole} + /> + ))} + {(searchedMembers?.data.length === 0 || + memberList.length === 0) && ( +
+ No results found! +
+ )}
- { - mList.length !== 0 - && memberList.length > itemCount - && searchedMembers === null - && ( + {mList.length !== 0 && + memberList.length > itemCount && + searchedMembers === null && ( - ) - } + )}
@@ -196,11 +207,16 @@ function PeopleDrawer({ roomId }) {
e.preventDefault()} className="people-search"> - - { - searchedMembers !== null - && - } + + {searchedMembers !== null && ( + + )}
diff --git a/src/app/organisms/room/PeopleDrawer.scss b/src/app/organisms/room/PeopleDrawer.scss index cfc5f6c9..5959da4d 100644 --- a/src/app/organisms/room/PeopleDrawer.scss +++ b/src/app/organisms/room/PeopleDrawer.scss @@ -1,5 +1,5 @@ -@use '../../partials/flex'; -@use '../../partials/dir'; +@use "../../partials/flex"; +@use "../../partials/dir"; .people-drawer { @extend .cp-fx__column; @@ -29,7 +29,7 @@ & .people-search { --search-input-height: 40px; min-height: var(--search-input-height); - + margin: 0 var(--sp-extra-tight); position: relative; @@ -64,7 +64,7 @@ .people-drawer__content { padding-top: var(--sp-extra-tight); padding-bottom: calc(2 * var(--sp-normal)); - + & .people-selector { padding: var(--sp-extra-tight); border-radius: var(--bo-radius); @@ -72,7 +72,7 @@ @include dir.side(margin, var(--sp-extra-tight), 0); } } - + & .segmented-controls { display: flex; margin-bottom: var(--sp-extra-tight); @@ -90,4 +90,4 @@ & .btn-surface { width: 100%; } -} \ No newline at end of file +} diff --git a/src/app/organisms/room/Room.jsx b/src/app/organisms/room/Room.jsx index 447686af..49486c09 100644 --- a/src/app/organisms/room/Room.jsx +++ b/src/app/organisms/room/Room.jsx @@ -1,17 +1,17 @@ -import React, { useState, useEffect } from 'react'; -import './Room.scss'; +import React, { useState, useEffect } from "react"; +import "./Room.scss"; -import initMatrix from '../../../client/initMatrix'; -import cons from '../../../client/state/cons'; -import settings from '../../../client/state/settings'; -import RoomTimeline from '../../../client/state/RoomTimeline'; -import navigation from '../../../client/state/navigation'; -import { openNavigation } from '../../../client/action/navigation'; +import initMatrix from "../../../client/initMatrix"; +import cons from "../../../client/state/cons"; +import settings from "../../../client/state/settings"; +import RoomTimeline from "../../../client/state/RoomTimeline"; +import navigation from "../../../client/state/navigation"; +import { openNavigation } from "../../../client/action/navigation"; -import Welcome from '../welcome/Welcome'; -import RoomView from './RoomView'; -import RoomSettings from './RoomSettings'; -import PeopleDrawer from './PeopleDrawer'; +import Welcome from "../welcome/Welcome"; +import RoomView from "./RoomView"; +import RoomSettings from "./RoomSettings"; +import PeopleDrawer from "./PeopleDrawer"; function Room() { const [roomInfo, setRoomInfo] = useState({ @@ -41,15 +41,24 @@ function Room() { navigation.on(cons.events.navigation.ROOM_SELECTED, handleRoomSelected); return () => { - navigation.removeListener(cons.events.navigation.ROOM_SELECTED, handleRoomSelected); + navigation.removeListener( + cons.events.navigation.ROOM_SELECTED, + handleRoomSelected + ); }; }, [roomInfo]); useEffect(() => { const handleDrawerToggling = (visiblity) => setIsDrawer(visiblity); - settings.on(cons.events.settings.PEOPLE_DRAWER_TOGGLED, handleDrawerToggling); + settings.on( + cons.events.settings.PEOPLE_DRAWER_TOGGLED, + handleDrawerToggling + ); return () => { - settings.removeListener(cons.events.settings.PEOPLE_DRAWER_TOGGLED, handleDrawerToggling); + settings.removeListener( + cons.events.settings.PEOPLE_DRAWER_TOGGLED, + handleDrawerToggling + ); }; }, []); diff --git a/src/app/organisms/room/Room.scss b/src/app/organisms/room/Room.scss index 11a00074..181a958f 100644 --- a/src/app/organisms/room/Room.scss +++ b/src/app/organisms/room/Room.scss @@ -1,5 +1,5 @@ -@use '../../partials/flex'; -@use '../../partials/screen'; +@use "../../partials/flex"; +@use "../../partials/screen"; .room { @extend .cp-fx__row; diff --git a/src/app/organisms/room/RoomSettings.jsx b/src/app/organisms/room/RoomSettings.jsx index 63277347..c3727653 100644 --- a/src/app/organisms/room/RoomSettings.jsx +++ b/src/app/organisms/room/RoomSettings.jsx @@ -1,79 +1,89 @@ -import React, { useState, useEffect } from 'react'; -import PropTypes from 'prop-types'; -import './RoomSettings.scss'; +import React, { useState, useEffect } from "react"; +import PropTypes from "prop-types"; +import "./RoomSettings.scss"; -import { blurOnBubbling } from '../../atoms/button/script'; +import { blurOnBubbling } from "../../atoms/button/script"; -import initMatrix from '../../../client/initMatrix'; -import cons from '../../../client/state/cons'; -import navigation from '../../../client/state/navigation'; -import { openInviteUser, toggleRoomSettings } from '../../../client/action/navigation'; -import * as roomActions from '../../../client/action/room'; +import initMatrix from "../../../client/initMatrix"; +import cons from "../../../client/state/cons"; +import navigation from "../../../client/state/navigation"; +import { + openInviteUser, + toggleRoomSettings, +} from "../../../client/action/navigation"; +import * as roomActions from "../../../client/action/room"; -import Text from '../../atoms/text/Text'; -import RawIcon from '../../atoms/system-icons/RawIcon'; -import Header, { TitleWrapper } from '../../atoms/header/Header'; -import ScrollView from '../../atoms/scroll/ScrollView'; -import Tabs from '../../atoms/tabs/Tabs'; -import { MenuHeader, MenuItem } from '../../atoms/context-menu/ContextMenu'; -import RoomProfile from '../../molecules/room-profile/RoomProfile'; -import RoomSearch from '../../molecules/room-search/RoomSearch'; -import RoomNotification from '../../molecules/room-notification/RoomNotification'; -import RoomVisibility from '../../molecules/room-visibility/RoomVisibility'; -import RoomAliases from '../../molecules/room-aliases/RoomAliases'; -import RoomHistoryVisibility from '../../molecules/room-history-visibility/RoomHistoryVisibility'; -import RoomEncryption from '../../molecules/room-encryption/RoomEncryption'; -import RoomPermissions from '../../molecules/room-permissions/RoomPermissions'; -import RoomMembers from '../../molecules/room-members/RoomMembers'; -import RoomEmojis from '../../molecules/room-emojis/RoomEmojis'; +import Text from "../../atoms/text/Text"; +import RawIcon from "../../atoms/system-icons/RawIcon"; +import Header, { TitleWrapper } from "../../atoms/header/Header"; +import ScrollView from "../../atoms/scroll/ScrollView"; +import Tabs from "../../atoms/tabs/Tabs"; +import { MenuHeader, MenuItem } from "../../atoms/context-menu/ContextMenu"; +import RoomProfile from "../../molecules/room-profile/RoomProfile"; +import RoomSearch from "../../molecules/room-search/RoomSearch"; +import RoomNotification from "../../molecules/room-notification/RoomNotification"; +import RoomVisibility from "../../molecules/room-visibility/RoomVisibility"; +import RoomAliases from "../../molecules/room-aliases/RoomAliases"; +import RoomHistoryVisibility from "../../molecules/room-history-visibility/RoomHistoryVisibility"; +import RoomEncryption from "../../molecules/room-encryption/RoomEncryption"; +import RoomPermissions from "../../molecules/room-permissions/RoomPermissions"; +import RoomMembers from "../../molecules/room-members/RoomMembers"; +import RoomEmojis from "../../molecules/room-emojis/RoomEmojis"; -import UserIC from '../../../../public/res/ic/outlined/user.svg'; -import SettingsIC from '../../../../public/res/ic/outlined/settings.svg'; -import EmojiIC from '../../../../public/res/ic/outlined/emoji.svg'; -import SearchIC from '../../../../public/res/ic/outlined/search.svg'; -import ShieldUserIC from '../../../../public/res/ic/outlined/shield-user.svg'; -import LockIC from '../../../../public/res/ic/outlined/lock.svg'; -import AddUserIC from '../../../../public/res/ic/outlined/add-user.svg'; -import LeaveArrowIC from '../../../../public/res/ic/outlined/leave-arrow.svg'; -import ChevronTopIC from '../../../../public/res/ic/outlined/chevron-top.svg'; +import UserIC from "../../../../public/res/ic/outlined/user.svg"; +import SettingsIC from "../../../../public/res/ic/outlined/settings.svg"; +import EmojiIC from "../../../../public/res/ic/outlined/emoji.svg"; +import SearchIC from "../../../../public/res/ic/outlined/search.svg"; +import ShieldUserIC from "../../../../public/res/ic/outlined/shield-user.svg"; +import LockIC from "../../../../public/res/ic/outlined/lock.svg"; +import AddUserIC from "../../../../public/res/ic/outlined/add-user.svg"; +import LeaveArrowIC from "../../../../public/res/ic/outlined/leave-arrow.svg"; +import ChevronTopIC from "../../../../public/res/ic/outlined/chevron-top.svg"; -import { useForceUpdate } from '../../hooks/useForceUpdate'; -import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog'; +import { useForceUpdate } from "../../hooks/useForceUpdate"; +import { confirmDialog } from "../../molecules/confirm-dialog/ConfirmDialog"; const tabText = { - GENERAL: 'General', - SEARCH: 'Search', - MEMBERS: 'Members', - EMOJIS: 'Emojis', - PERMISSIONS: 'Permissions', - SECURITY: 'Security', + GENERAL: "General", + SEARCH: "Search", + MEMBERS: "Members", + EMOJIS: "Emojis", + PERMISSIONS: "Permissions", + SECURITY: "Security", }; -const tabItems = [{ - iconSrc: SettingsIC, - text: tabText.GENERAL, - disabled: false, -}, { - iconSrc: SearchIC, - text: tabText.SEARCH, - disabled: false, -}, { - iconSrc: UserIC, - text: tabText.MEMBERS, - disabled: false, -}, { - iconSrc: EmojiIC, - text: tabText.EMOJIS, - disabled: false, -}, { - iconSrc: ShieldUserIC, - text: tabText.PERMISSIONS, - disabled: false, -}, { - iconSrc: LockIC, - text: tabText.SECURITY, - disabled: false, -}]; +const tabItems = [ + { + iconSrc: SettingsIC, + text: tabText.GENERAL, + disabled: false, + }, + { + iconSrc: SearchIC, + text: tabText.SEARCH, + disabled: false, + }, + { + iconSrc: UserIC, + text: tabText.MEMBERS, + disabled: false, + }, + { + iconSrc: EmojiIC, + text: tabText.EMOJIS, + disabled: false, + }, + { + iconSrc: ShieldUserIC, + text: tabText.PERMISSIONS, + disabled: false, + }, + { + iconSrc: LockIC, + text: tabText.SECURITY, + disabled: false, + }, +]; function GeneralSettings({ roomId }) { const mx = initMatrix.matrixClient; @@ -95,10 +105,10 @@ function GeneralSettings({ roomId }) { variant="danger" onClick={async () => { const isConfirmed = await confirmDialog( - 'Leave room', + "Leave room", `Are you sure that you want to leave "${room.name}" room?`, - 'Leave', - 'danger', + "Leave", + "danger" ); if (!isConfirmed) return; roomActions.leave(roomId); @@ -109,7 +119,9 @@ function GeneralSettings({ roomId }) {
- Notification (Changing this will only affect you) + + Notification (Changing this will only affect you) +
@@ -168,7 +180,10 @@ function RoomSettings({ roomId }) { navigation.on(cons.events.navigation.ROOM_SETTINGS_TOGGLED, settingsToggle); return () => { mounted = false; - navigation.removeListener(cons.events.navigation.ROOM_SETTINGS_TOGGLED, settingsToggle); + navigation.removeListener( + cons.events.navigation.ROOM_SETTINGS_TOGGLED, + settingsToggle + ); }; }, []); @@ -183,12 +198,15 @@ function RoomSettings({ roomId }) { className="room-settings__header-btn" onClick={() => toggleRoomSettings()} type="button" - onMouseUp={(e) => blurOnBubbling(e, '.room-settings__header-btn')} + onMouseUp={(e) => blurOnBubbling(e, ".room-settings__header-btn")} > {`${room.name}`} - — room settings + + {" "} + — room settings + @@ -197,16 +215,30 @@ function RoomSettings({ roomId }) { tab.text === selectedTab.text)} + defaultSelected={tabItems.findIndex( + (tab) => tab.text === selectedTab.text + )} onSelect={handleTabChange} />
- {selectedTab.text === tabText.GENERAL && } - {selectedTab.text === tabText.SEARCH && } - {selectedTab.text === tabText.MEMBERS && } - {selectedTab.text === tabText.EMOJIS && } - {selectedTab.text === tabText.PERMISSIONS && } - {selectedTab.text === tabText.SECURITY && } + {selectedTab.text === tabText.GENERAL && ( + + )} + {selectedTab.text === tabText.SEARCH && ( + + )} + {selectedTab.text === tabText.MEMBERS && ( + + )} + {selectedTab.text === tabText.EMOJIS && ( + + )} + {selectedTab.text === tabText.PERMISSIONS && ( + + )} + {selectedTab.text === tabText.SECURITY && ( + + )}
diff --git a/src/app/organisms/room/RoomSettings.scss b/src/app/organisms/room/RoomSettings.scss index ab7fca5c..7b418aa3 100644 --- a/src/app/organisms/room/RoomSettings.scss +++ b/src/app/organisms/room/RoomSettings.scss @@ -1,5 +1,5 @@ -@use '../../partials/dir'; -@use '../../partials/flex'; +@use "../../partials/dir"; +@use "../../partials/flex"; .room-settings { height: 100%; @@ -18,8 +18,8 @@ padding: var(--sp-ultra-tight) var(--sp-extra-tight); border-radius: calc(var(--bo-radius) / 2); cursor: pointer; - - @media (hover:hover) { + + @media (hover: hover) { &:hover { background-color: var(--bg-surface-hover); box-shadow: var(--bs-surface-outline); @@ -40,7 +40,7 @@ margin: var(--sp-extra-loose); } } - + & .tabs { position: sticky; top: 0; @@ -54,7 +54,7 @@ padding: 0 var(--sp-normal); } } - + &__cards-wrapper { padding: 0 var(--sp-normal); @include dir.side(padding, var(--sp-normal), var(--sp-extra-tight)); @@ -75,7 +75,7 @@ .room-settings .room-permissions__card, .room-settings .room-search__form, -.room-settings .room-search__result-item , +.room-settings .room-search__result-item, .room-settings .room-members { @extend .room-settings__card; -} \ No newline at end of file +} diff --git a/src/app/organisms/room/RoomView.jsx b/src/app/organisms/room/RoomView.jsx index b94c35c4..a5e40c9c 100644 --- a/src/app/organisms/room/RoomView.jsx +++ b/src/app/organisms/room/RoomView.jsx @@ -1,17 +1,17 @@ -import React, { useEffect, useRef } from 'react'; -import PropTypes from 'prop-types'; -import './RoomView.scss'; +import React, { useEffect, useRef } from "react"; +import PropTypes from "prop-types"; +import "./RoomView.scss"; -import EventEmitter from 'events'; +import EventEmitter from "events"; -import cons from '../../../client/state/cons'; -import navigation from '../../../client/state/navigation'; +import cons from "../../../client/state/cons"; +import navigation from "../../../client/state/navigation"; -import RoomViewHeader from './RoomViewHeader'; -import RoomViewContent from './RoomViewContent'; -import RoomViewFloating from './RoomViewFloating'; -import RoomViewInput from './RoomViewInput'; -import RoomViewCmdBar from './RoomViewCmdBar'; +import RoomViewHeader from "./RoomViewHeader"; +import RoomViewContent from "./RoomViewContent"; +import RoomViewFloating from "./RoomViewFloating"; +import RoomViewInput from "./RoomViewInput"; +import RoomViewCmdBar from "./RoomViewCmdBar"; const viewEvent = new EventEmitter(); @@ -23,19 +23,22 @@ function RoomView({ roomTimeline, eventId }) { useEffect(() => { const settingsToggle = (isVisible) => { const roomView = roomViewRef.current; - roomView.classList.toggle('room-view--dropped'); + roomView.classList.toggle("room-view--dropped"); const roomViewContent = roomView.children[1]; if (isVisible) { setTimeout(() => { if (!navigation.isRoomSettings) return; - roomViewContent.style.visibility = 'hidden'; + roomViewContent.style.visibility = "hidden"; }, 200); - } else roomViewContent.style.visibility = 'visible'; + } else roomViewContent.style.visibility = "visible"; }; navigation.on(cons.events.navigation.ROOM_SETTINGS_TOGGLED, settingsToggle); return () => { - navigation.removeListener(cons.events.navigation.ROOM_SETTINGS_TOGGLED, settingsToggle); + navigation.removeListener( + cons.events.navigation.ROOM_SETTINGS_TOGGLED, + settingsToggle + ); }; }, []); @@ -44,14 +47,8 @@ function RoomView({ roomTimeline, eventId }) {
- - + +
( - {`${cmd}${cmd.isOptions ? cmdOptString : ''}`} + {`${cmd}${cmd.isOptions ? cmdOptString : ""}`} )); } @@ -117,9 +117,9 @@ function renderSuggestions({ prefix, option, suggestions }, fireCmd) { } const cmd = { - '/': (cmds) => renderCmdSuggestions(prefix, cmds), - ':': (emos) => renderEmojiSuggestion(prefix, emos), - '@': (members) => renderNameSuggestion(prefix, members), + "/": (cmds) => renderCmdSuggestions(prefix, cmds), + ":": (emos) => renderEmojiSuggestion(prefix, emos), + "@": (members) => renderNameSuggestion(prefix, members), }; return cmd[prefix]?.(suggestions); } @@ -132,36 +132,44 @@ function RoomViewCmdBar({ roomId, roomTimeline, viewEvent }) { function displaySuggestions(suggestions) { if (suggestions.length === 0) { - setCmd({ prefix: cmd?.prefix || cmdPrefix, error: 'No suggestion found.' }); - viewEvent.emit('cmd_error'); + setCmd({ + prefix: cmd?.prefix || cmdPrefix, + error: "No suggestion found.", + }); + viewEvent.emit("cmd_error"); return; } - setCmd({ prefix: cmd?.prefix || cmdPrefix, suggestions, option: cmdOption }); + setCmd({ + prefix: cmd?.prefix || cmdPrefix, + suggestions, + option: cmdOption, + }); } function processCmd(prefix, slug) { let searchTerm = slug; cmdOption = undefined; cmdPrefix = prefix; - if (prefix === '/') { - const cmdSlugParts = slug.split('/'); + if (prefix === "/") { + const cmdSlugParts = slug.split("/"); [searchTerm, cmdOption] = cmdSlugParts; } - if (prefix === ':') { + if (prefix === ":") { if (searchTerm.length <= 3) { - if (searchTerm.match(/^[-]?(\))$/)) searchTerm = 'smile'; - else if (searchTerm.match(/^[-]?(s|S)$/)) searchTerm = 'confused'; - else if (searchTerm.match(/^[-]?(o|O|0)$/)) searchTerm = 'astonished'; - else if (searchTerm.match(/^[-]?(\|)$/)) searchTerm = 'neutral_face'; - else if (searchTerm.match(/^[-]?(d|D)$/)) searchTerm = 'grin'; - else if (searchTerm.match(/^[-]?(\/)$/)) searchTerm = 'frown'; - else if (searchTerm.match(/^[-]?(p|P)$/)) searchTerm = 'stuck_out_tongue'; - else if (searchTerm.match(/^'[-]?(\()$/)) searchTerm = 'cry'; - else if (searchTerm.match(/^[-]?(x|X)$/)) searchTerm = 'dizzy_face'; - else if (searchTerm.match(/^[-]?(\()$/)) searchTerm = 'pleading_face'; - else if (searchTerm.match(/^[-]?(\$)$/)) searchTerm = 'money'; - else if (searchTerm.match(/^(<3)$/)) searchTerm = 'heart'; - else if (searchTerm.match(/^(c|ca|cat)$/)) searchTerm = '_cat'; + if (searchTerm.match(/^[-]?(\))$/)) searchTerm = "smile"; + else if (searchTerm.match(/^[-]?(s|S)$/)) searchTerm = "confused"; + else if (searchTerm.match(/^[-]?(o|O|0)$/)) searchTerm = "astonished"; + else if (searchTerm.match(/^[-]?(\|)$/)) searchTerm = "neutral_face"; + else if (searchTerm.match(/^[-]?(d|D)$/)) searchTerm = "grin"; + else if (searchTerm.match(/^[-]?(\/)$/)) searchTerm = "frown"; + else if (searchTerm.match(/^[-]?(p|P)$/)) + searchTerm = "stuck_out_tongue"; + else if (searchTerm.match(/^'[-]?(\()$/)) searchTerm = "cry"; + else if (searchTerm.match(/^[-]?(x|X)$/)) searchTerm = "dizzy_face"; + else if (searchTerm.match(/^[-]?(\()$/)) searchTerm = "pleading_face"; + else if (searchTerm.match(/^[-]?(\$)$/)) searchTerm = "money"; + else if (searchTerm.match(/^(<3)$/)) searchTerm = "heart"; + else if (searchTerm.match(/^(c|ca|cat)$/)) searchTerm = "_cat"; } } @@ -173,22 +181,30 @@ function RoomViewCmdBar({ roomId, roomTimeline, viewEvent }) { const mx = initMatrix.matrixClient; const setupSearch = { - '/': () => { + "/": () => { asyncSearch.setup(Object.keys(commands), { isContain: true }); setCmd({ prefix, suggestions: Object.keys(commands) }); }, - ':': () => { + ":": () => { const parentIds = initMatrix.roomList.getAllParentSpaces(roomId); const parentRooms = [...parentIds].map((id) => mx.getRoom(id)); - const emojis = getEmojiForCompletion(mx, [mx.getRoom(roomId), ...parentRooms]); + const emojis = getEmojiForCompletion(mx, [ + mx.getRoom(roomId), + ...parentRooms, + ]); const recentEmoji = getRecentEmojis(20); - asyncSearch.setup(emojis, { keys: ['shortcode'], isContain: true, limit: 20 }); + asyncSearch.setup(emojis, { + keys: ["shortcode"], + isContain: true, + limit: 20, + }); setCmd({ prefix, - suggestions: recentEmoji.length > 0 ? recentEmoji : emojis.slice(26, 46), + suggestions: + recentEmoji.length > 0 ? recentEmoji : emojis.slice(26, 46), }); }, - '@': () => { + "@": () => { const members = mx .getRoom(roomId) .getJoinedMembers() @@ -196,7 +212,7 @@ function RoomViewCmdBar({ roomId, roomTimeline, viewEvent }) { name: member.name, userId: member.userId.slice(1), })); - asyncSearch.setup(members, { keys: ['name', 'userId'], limit: 20 }); + asyncSearch.setup(members, { keys: ["name", "userId"], limit: 20 }); const endIndex = members.length > 20 ? 20 : members.length; setCmd({ prefix, suggestions: members.slice(0, endIndex) }); }, @@ -209,19 +225,21 @@ function RoomViewCmdBar({ roomId, roomTimeline, viewEvent }) { cmdPrefix = undefined; } function fireCmd(myCmd) { - if (myCmd.prefix === '/') { - viewEvent.emit('cmd_fired', { + if (myCmd.prefix === "/") { + viewEvent.emit("cmd_fired", { replace: `/${myCmd.result.name}`, }); } - if (myCmd.prefix === ':') { + if (myCmd.prefix === ":") { if (!myCmd.result.mxc) addRecentEmoji(myCmd.result.unicode); - viewEvent.emit('cmd_fired', { - replace: myCmd.result.mxc ? `:${myCmd.result.shortcode}: ` : myCmd.result.unicode, + viewEvent.emit("cmd_fired", { + replace: myCmd.result.mxc + ? `:${myCmd.result.shortcode}: ` + : myCmd.result.unicode, }); } - if (myCmd.prefix === '@') { - viewEvent.emit('cmd_fired', { + if (myCmd.prefix === "@") { + viewEvent.emit("cmd_fired", { replace: `@${myCmd.result.userId}`, }); } @@ -231,42 +249,43 @@ function RoomViewCmdBar({ roomId, roomTimeline, viewEvent }) { function listenKeyboard(event) { const { activeElement } = document; const lastCmdItem = document.activeElement.parentNode.lastElementChild; - if (event.key === 'Escape') { - if (activeElement.className !== 'cmd-item') return; - viewEvent.emit('focus_msg_input'); + if (event.key === "Escape") { + if (activeElement.className !== "cmd-item") return; + viewEvent.emit("focus_msg_input"); } - if (event.key === 'Tab') { - if (lastCmdItem.className !== 'cmd-item') return; + if (event.key === "Tab") { + if (lastCmdItem.className !== "cmd-item") return; if (lastCmdItem !== activeElement) return; if (event.shiftKey) return; - viewEvent.emit('focus_msg_input'); + viewEvent.emit("focus_msg_input"); event.preventDefault(); } } useEffect(() => { - viewEvent.on('cmd_activate', activateCmd); - viewEvent.on('cmd_deactivate', deactivateCmd); + viewEvent.on("cmd_activate", activateCmd); + viewEvent.on("cmd_deactivate", deactivateCmd); return () => { deactivateCmd(); - viewEvent.removeListener('cmd_activate', activateCmd); - viewEvent.removeListener('cmd_deactivate', deactivateCmd); + viewEvent.removeListener("cmd_activate", activateCmd); + viewEvent.removeListener("cmd_deactivate", deactivateCmd); }; }, [roomId]); useEffect(() => { - if (cmd !== null) document.body.addEventListener('keydown', listenKeyboard); - viewEvent.on('cmd_process', processCmd); + if (cmd !== null) document.body.addEventListener("keydown", listenKeyboard); + viewEvent.on("cmd_process", processCmd); asyncSearch.on(asyncSearch.RESULT_SENT, displaySuggestions); return () => { - if (cmd !== null) document.body.removeEventListener('keydown', listenKeyboard); + if (cmd !== null) + document.body.removeEventListener("keydown", listenKeyboard); - viewEvent.removeListener('cmd_process', processCmd); + viewEvent.removeListener("cmd_process", processCmd); asyncSearch.removeListener(asyncSearch.RESULT_SENT, displaySuggestions); }; }, [cmd]); - const isError = typeof cmd?.error === 'string'; + const isError = typeof cmd?.error === "string"; if (cmd === null || isError) { return (
@@ -282,7 +301,9 @@ function RoomViewCmdBar({ roomId, roomTimeline, viewEvent }) {
-
{renderSuggestions(cmd, fireCmd)}
+
+ {renderSuggestions(cmd, fireCmd)} +
diff --git a/src/app/organisms/room/RoomViewCmdBar.scss b/src/app/organisms/room/RoomViewCmdBar.scss index 3f03fb06..995fd389 100644 --- a/src/app/organisms/room/RoomViewCmdBar.scss +++ b/src/app/organisms/room/RoomViewCmdBar.scss @@ -1,6 +1,6 @@ -@use '../../partials/flex'; -@use '../../partials/text'; -@use '../../partials/dir'; +@use "../../partials/flex"; +@use "../../partials/text"; +@use "../../partials/dir"; .cmd-bar { --cmd-bar-height: 28px; @@ -41,7 +41,7 @@ padding: 0 var(--sp-extra-tight); border-radius: var(--bo-radius) var(--bo-radius) 0 0; cursor: pointer; - + display: inline-flex; align-items: center; @@ -54,4 +54,4 @@ border-bottom: 2px solid transparent; outline: none; } -} \ No newline at end of file +} diff --git a/src/app/organisms/room/RoomViewContent.jsx b/src/app/organisms/room/RoomViewContent.jsx index 745ece82..2b439304 100644 --- a/src/app/organisms/room/RoomViewContent.jsx +++ b/src/app/organisms/room/RoomViewContent.jsx @@ -2,32 +2,36 @@ /* eslint-disable jsx-a11y/click-events-have-key-events */ /* eslint-disable react/prop-types */ import React, { - useState, useEffect, useLayoutEffect, useCallback, useRef, -} from 'react'; -import PropTypes from 'prop-types'; -import './RoomViewContent.scss'; + useState, + useEffect, + useLayoutEffect, + useCallback, + useRef, +} from "react"; +import PropTypes from "prop-types"; +import "./RoomViewContent.scss"; -import dateFormat from 'dateformat'; -import { twemojify } from '../../../util/twemojify'; +import dateFormat from "dateformat"; +import { twemojify } from "../../../util/twemojify"; -import initMatrix from '../../../client/initMatrix'; -import cons from '../../../client/state/cons'; -import navigation from '../../../client/state/navigation'; -import { openProfileViewer } from '../../../client/action/navigation'; -import { diffMinutes, isInSameDay, Throttle } from '../../../util/common'; -import { markAsRead } from '../../../client/action/notifications'; +import initMatrix from "../../../client/initMatrix"; +import cons from "../../../client/state/cons"; +import navigation from "../../../client/state/navigation"; +import { openProfileViewer } from "../../../client/action/navigation"; +import { diffMinutes, isInSameDay, Throttle } from "../../../util/common"; +import { markAsRead } from "../../../client/action/notifications"; -import Divider from '../../atoms/divider/Divider'; -import ScrollView from '../../atoms/scroll/ScrollView'; -import { Message, PlaceholderMessage } from '../../molecules/message/Message'; -import RoomIntro from '../../molecules/room-intro/RoomIntro'; -import TimelineChange from '../../molecules/message/TimelineChange'; +import Divider from "../../atoms/divider/Divider"; +import ScrollView from "../../atoms/scroll/ScrollView"; +import { Message, PlaceholderMessage } from "../../molecules/message/Message"; +import RoomIntro from "../../molecules/room-intro/RoomIntro"; +import TimelineChange from "../../molecules/message/TimelineChange"; -import { useStore } from '../../hooks/useStore'; -import { useForceUpdate } from '../../hooks/useForceUpdate'; -import { parseTimelineChange } from './common'; -import TimelineScroll from './TimelineScroll'; -import EventLimit from './EventLimit'; +import { useStore } from "../../hooks/useStore"; +import { useForceUpdate } from "../../hooks/useForceUpdate"; +import { parseTimelineChange } from "./common"; +import TimelineScroll from "./TimelineScroll"; +import EventLimit from "./EventLimit"; const PAG_LIMIT = 30; const MAX_MSG_DIFF_MINUTES = 5; @@ -56,38 +60,43 @@ function RoomIntroContainer({ event, timeline }) { const mx = initMatrix.matrixClient; const { roomList } = initMatrix; const { room } = timeline; - const roomTopic = room.currentState.getStateEvents('m.room.topic')[0]?.getContent().topic; + const roomTopic = room.currentState + .getStateEvents("m.room.topic")[0] + ?.getContent().topic; const isDM = roomList.directs.has(timeline.roomId); - let avatarSrc = room.getAvatarUrl(mx.baseUrl, 80, 80, 'crop'); - avatarSrc = isDM ? room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 80, 80, 'crop') : avatarSrc; + let avatarSrc = room.getAvatarUrl(mx.baseUrl, 80, 80, "crop"); + avatarSrc = isDM + ? room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 80, 80, "crop") + : avatarSrc; const heading = isDM ? room.name : `Welcome to ${room.name}`; - const topic = twemojify(roomTopic || '', undefined, true); + const topic = twemojify(roomTopic || "", undefined, true); const nameJsx = twemojify(room.name); - const desc = isDM - ? ( - <> - This is the beginning of your direct message history with @ - {nameJsx} - {'. '} - {topic} - - ) - : ( - <> - {'This is the beginning of the '} - {nameJsx} - {' room. '} - {topic} - - ); + const desc = isDM ? ( + <> + This is the beginning of your direct message history with @ + {nameJsx} + {". "} + {topic} + + ) : ( + <> + {"This is the beginning of the "} + {nameJsx} + {" room. "} + {topic} + + ); useEffect(() => { const handleUpdate = () => nameForceUpdate(); roomList.on(cons.events.roomList.ROOM_PROFILE_UPDATED, handleUpdate); return () => { - roomList.removeListener(cons.events.roomList.ROOM_PROFILE_UPDATED, handleUpdate); + roomList.removeListener( + cons.events.roomList.ROOM_PROFILE_UPDATED, + handleUpdate + ); }; }, []); @@ -98,7 +107,14 @@ function RoomIntroContainer({ event, timeline }) { name={room.name} heading={twemojify(heading)} desc={desc} - time={event ? `Created at ${dateFormat(event.getDate(), 'dd mmmm yyyy, hh:MM TT')}` : null} + time={ + event + ? `Created at ${dateFormat( + event.getDate(), + "dd mmmm yyyy, hh:MM TT" + )}` + : null + } /> ); } @@ -106,15 +122,17 @@ function RoomIntroContainer({ event, timeline }) { function handleOnClickCapture(e) { const { target, nativeEvent } = e; - const userId = target.getAttribute('data-mx-pill'); + const userId = target.getAttribute("data-mx-pill"); if (userId) { const roomId = navigation.selectedRoomId; openProfileViewer(userId, roomId); } - const spoiler = nativeEvent.composedPath().find((el) => el?.hasAttribute?.('data-mx-spoiler')); + const spoiler = nativeEvent + .composedPath() + .find((el) => el?.hasAttribute?.("data-mx-spoiler")); if (spoiler) { - spoiler.classList.toggle('data-mx-spoiler--visible'); + spoiler.classList.toggle("data-mx-spoiler--visible"); } } @@ -125,17 +143,17 @@ function renderEvent( isFocus, isEdit, setEdit, - cancelEdit, + cancelEdit ) { - const isBodyOnly = (prevMEvent !== null - && prevMEvent.getSender() === mEvent.getSender() - && prevMEvent.getType() !== 'm.room.member' - && prevMEvent.getType() !== 'm.room.create' - && diffMinutes(mEvent.getDate(), prevMEvent.getDate()) <= MAX_MSG_DIFF_MINUTES - ); + const isBodyOnly = + prevMEvent !== null && + prevMEvent.getSender() === mEvent.getSender() && + prevMEvent.getType() !== "m.room.member" && + prevMEvent.getType() !== "m.room.create" && + diffMinutes(mEvent.getDate(), prevMEvent.getDate()) <= MAX_MSG_DIFF_MINUTES; const timestamp = mEvent.getTs(); - if (mEvent.getType() === 'm.room.member') { + if (mEvent.getType() === "m.room.member") { const timelineChange = parseTimelineChange(mEvent); if (timelineChange === null) return
; return ( @@ -166,7 +184,7 @@ function useTimeline(roomTimeline, eventId, readUptoEvtStore, eventLimitRef) { const [timelineInfo, setTimelineInfo] = useState(null); const setEventTimeline = async (eId) => { - if (typeof eId === 'string') { + if (typeof eId === "string") { const isLoaded = await roomTimeline.loadEventTimeline(eId); if (isLoaded) return; // if eventTimeline failed to load, @@ -189,12 +207,19 @@ function useTimeline(roomTimeline, eventId, readUptoEvtStore, eventLimitRef) { if (isSpecificEvent) { focusEventIndex = roomTimeline.getEventIndex(eId); } - if (!readUptoEvtStore.getItem() && roomTimeline.hasEventInTimeline(readUpToId)) { + if ( + !readUptoEvtStore.getItem() && + roomTimeline.hasEventInTimeline(readUpToId) + ) { // either opening live timeline or jump to unread. - readUptoEvtStore.setItem(roomTimeline.findEventByIdInTimelineSet(readUpToId)); + readUptoEvtStore.setItem( + roomTimeline.findEventByIdInTimelineSet(readUpToId) + ); } if (readUptoEvtStore.getItem() && !isSpecificEvent) { - focusEventIndex = roomTimeline.getUnreadEventIndex(readUptoEvtStore.getItem().getId()); + focusEventIndex = roomTimeline.getUnreadEventIndex( + readUptoEvtStore.getItem().getId() + ); } if (focusEventIndex > -1) { @@ -221,7 +246,7 @@ function usePaginate( readUptoEvtStore, forceUpdateLimit, timelineScrollRef, - eventLimitRef, + eventLimitRef ) { const [info, setInfo] = useState(null); @@ -231,17 +256,27 @@ function usePaginate( if (loaded === 0) return; if (!readUptoEvtStore.getItem()) { const readUpToId = roomTimeline.getReadUpToEventId(); - readUptoEvtStore.setItem(roomTimeline.findEventByIdInTimelineSet(readUpToId)); + readUptoEvtStore.setItem( + roomTimeline.findEventByIdInTimelineSet(readUpToId) + ); } limit.paginate(backwards, PAG_LIMIT, roomTimeline.timeline.length); - setTimeout(() => setInfo({ - backwards, - loaded, - })); + setTimeout(() => + setInfo({ + backwards, + loaded, + }) + ); }; - roomTimeline.on(cons.events.roomTimeline.PAGINATED, handlePaginatedFromServer); + roomTimeline.on( + cons.events.roomTimeline.PAGINATED, + handlePaginatedFromServer + ); return () => { - roomTimeline.removeListener(cons.events.roomTimeline.PAGINATED, handlePaginatedFromServer); + roomTimeline.removeListener( + cons.events.roomTimeline.PAGINATED, + handlePaginatedFromServer + ); }; }, [roomTimeline]); @@ -283,17 +318,17 @@ function useHandleScroll( readUptoEvtStore, forceUpdateLimit, timelineScrollRef, - eventLimitRef, + eventLimitRef ) { const handleScroll = useCallback(() => { const timelineScroll = timelineScrollRef.current; const limit = eventLimitRef.current; requestAnimationFrame(() => { // emit event to toggle scrollToBottom button visibility - const isAtBottom = ( - timelineScroll.bottom < 16 && !roomTimeline.canPaginateForward() - && limit.length >= roomTimeline.timeline.length - ); + const isAtBottom = + timelineScroll.bottom < 16 && + !roomTimeline.canPaginateForward() && + limit.length >= roomTimeline.timeline.length; roomTimeline.emit(cons.events.roomTimeline.AT_BOTTOM, isAtBottom); if (isAtBottom && readUptoEvtStore.getItem()) { requestAnimationFrame(() => markAsRead(roomTimeline.roomId)); @@ -320,7 +355,12 @@ function useHandleScroll( return [handleScroll, handleScrollToLive]; } -function useEventArrive(roomTimeline, readUptoEvtStore, timelineScrollRef, eventLimitRef) { +function useEventArrive( + roomTimeline, + readUptoEvtStore, + timelineScrollRef, + eventLimitRef +) { const myUserId = initMatrix.matrixClient.getUserId(); const [newEvent, setEvent] = useState(null); @@ -334,19 +374,27 @@ function useEventArrive(roomTimeline, readUptoEvtStore, timelineScrollRef, event } const readUpToEvent = readUptoEvtStore.getItem(); const readUpToId = roomTimeline.getReadUpToEventId(); - const isUnread = readUpToEvent ? readUpToEvent?.getId() === readUpToId : true; + const isUnread = readUpToEvent + ? readUpToEvent?.getId() === readUpToId + : true; if (isUnread === false) { - if (document.visibilityState === 'visible' && timelineScroll.bottom < 16) { + if ( + document.visibilityState === "visible" && + timelineScroll.bottom < 16 + ) { requestAnimationFrame(() => markAsRead(roomTimeline.roomId)); } else { - readUptoEvtStore.setItem(roomTimeline.findEventByIdInTimelineSet(readUpToId)); + readUptoEvtStore.setItem( + roomTimeline.findEventByIdInTimelineSet(readUpToId) + ); } return; } const { timeline } = roomTimeline; - const unreadMsgIsLast = timeline[timeline.length - 2].getId() === readUpToId; + const unreadMsgIsLast = + timeline[timeline.length - 2].getId() === readUpToId; if (unreadMsgIsLast) { requestAnimationFrame(() => markAsRead(roomTimeline.roomId)); } @@ -354,7 +402,8 @@ function useEventArrive(roomTimeline, readUptoEvtStore, timelineScrollRef, event const handleEvent = (event) => { const tLength = roomTimeline.timeline.length; - const isViewingLive = roomTimeline.isServingLiveTimeline() && limit.length >= tLength - 1; + const isViewingLive = + roomTimeline.isServingLiveTimeline() && limit.length >= tLength - 1; const isAttached = timelineScroll.bottom < SCROLL_TRIGGER_POS; if (isViewingLive && isAttached) { @@ -363,7 +412,9 @@ function useEventArrive(roomTimeline, readUptoEvtStore, timelineScrollRef, event setEvent(event); return; } - const isRelates = (event.getType() === 'm.reaction' || event.getRelation()?.rel_type === 'm.replace'); + const isRelates = + event.getType() === "m.reaction" || + event.getRelation()?.rel_type === "m.replace"; if (isRelates) { setEvent(event); return; @@ -382,7 +433,10 @@ function useEventArrive(roomTimeline, readUptoEvtStore, timelineScrollRef, event roomTimeline.on(cons.events.roomTimeline.EVENT_REDACTED, handleEventRedact); return () => { roomTimeline.removeListener(cons.events.roomTimeline.EVENT, handleEvent); - roomTimeline.removeListener(cons.events.roomTimeline.EVENT_REDACTED, handleEventRedact); + roomTimeline.removeListener( + cons.events.roomTimeline.EVENT_REDACTED, + handleEventRedact + ); }; }, [roomTimeline]); @@ -403,13 +457,18 @@ function RoomViewContent({ eventId, roomTimeline }) { const readUptoEvtStore = useStore(roomTimeline); const [onLimitUpdate, forceUpdateLimit] = useForceUpdate(); - const timelineInfo = useTimeline(roomTimeline, eventId, readUptoEvtStore, eventLimitRef); + const timelineInfo = useTimeline( + roomTimeline, + eventId, + readUptoEvtStore, + eventLimitRef + ); const [paginateInfo, autoPaginate] = usePaginate( roomTimeline, readUptoEvtStore, forceUpdateLimit, timelineScrollRef, - eventLimitRef, + eventLimitRef ); const [handleScroll, handleScrollToLive] = useHandleScroll( roomTimeline, @@ -417,9 +476,14 @@ function RoomViewContent({ eventId, roomTimeline }) { readUptoEvtStore, forceUpdateLimit, timelineScrollRef, - eventLimitRef, + eventLimitRef + ); + const newEvent = useEventArrive( + roomTimeline, + readUptoEvtStore, + timelineScrollRef, + eventLimitRef ); - const newEvent = useEventArrive(roomTimeline, readUptoEvtStore, timelineScrollRef, eventLimitRef); const { timeline } = roomTimeline; @@ -443,7 +507,10 @@ function RoomViewContent({ eventId, roomTimeline }) { } if (timelineScroll.bottom < 16 && !roomTimeline.canPaginateForward()) { const readUpToId = roomTimeline.getReadUpToEventId(); - if (readUptoEvtStore.getItem()?.getId() === readUpToId || readUpToId === null) { + if ( + readUptoEvtStore.getItem()?.getId() === readUpToId || + readUpToId === null + ) { requestAnimationFrame(() => markAsRead(roomTimeline.roomId)); } } @@ -451,10 +518,16 @@ function RoomViewContent({ eventId, roomTimeline }) { } autoPaginate(); - roomTimeline.on(cons.events.roomTimeline.SCROLL_TO_LIVE, handleScrollToLive); + roomTimeline.on( + cons.events.roomTimeline.SCROLL_TO_LIVE, + handleScrollToLive + ); return () => { if (timelineSVRef.current === null) return; - roomTimeline.removeListener(cons.events.roomTimeline.SCROLL_TO_LIVE, handleScrollToLive); + roomTimeline.removeListener( + cons.events.roomTimeline.SCROLL_TO_LIVE, + handleScrollToLive + ); }; }, [timelineInfo]); @@ -476,46 +549,56 @@ function RoomViewContent({ eventId, roomTimeline }) { useEffect(() => { const timelineScroll = timelineScrollRef.current; if (!roomTimeline.initialized) return; - if (timelineScroll.bottom < 16 && !roomTimeline.canPaginateForward() && document.visibilityState === 'visible') { + if ( + timelineScroll.bottom < 16 && + !roomTimeline.canPaginateForward() && + document.visibilityState === "visible" + ) { timelineScroll.scrollToBottom(); } else { timelineScroll.tryRestoringScroll(); } }, [newEvent]); - const listenKeyboard = useCallback((event) => { - if (event.ctrlKey || event.altKey || event.metaKey) return; - if (event.key !== 'ArrowUp') return; - if (navigation.isRawModalVisible) return; + const listenKeyboard = useCallback( + (event) => { + if (event.ctrlKey || event.altKey || event.metaKey) return; + if (event.key !== "ArrowUp") return; + if (navigation.isRawModalVisible) return; - if (document.activeElement.id !== 'message-textarea') return; - if (document.activeElement.value !== '') return; + if (document.activeElement.id !== "message-textarea") return; + if (document.activeElement.value !== "") return; - const { - timeline: tl, activeTimeline, liveTimeline, matrixClient: mx, - } = roomTimeline; - const limit = eventLimitRef.current; - if (activeTimeline !== liveTimeline) return; - if (tl.length > limit.length) return; + const { + timeline: tl, + activeTimeline, + liveTimeline, + matrixClient: mx, + } = roomTimeline; + const limit = eventLimitRef.current; + if (activeTimeline !== liveTimeline) return; + if (tl.length > limit.length) return; - const mTypes = ['m.text']; - for (let i = tl.length - 1; i >= 0; i -= 1) { - const mE = tl[i]; - if ( - mE.getSender() === mx.getUserId() - && mE.getType() === 'm.room.message' - && mTypes.includes(mE.getContent()?.msgtype) - ) { - setEditEventId(mE.getId()); - return; + const mTypes = ["m.text"]; + for (let i = tl.length - 1; i >= 0; i -= 1) { + const mE = tl[i]; + if ( + mE.getSender() === mx.getUserId() && + mE.getType() === "m.room.message" && + mTypes.includes(mE.getContent()?.msgtype) + ) { + setEditEventId(mE.getId()); + return; + } } - } - }, [roomTimeline]); + }, + [roomTimeline] + ); useEffect(() => { - document.body.addEventListener('keydown', listenKeyboard); + document.body.addEventListener("keydown", listenKeyboard); return () => { - document.body.removeEventListener('keydown', listenKeyboard); + document.body.removeEventListener("keydown", listenKeyboard); }; }, [listenKeyboard]); @@ -525,7 +608,7 @@ function RoomViewContent({ eventId, roomTimeline }) { throttle._(() => { const backwards = timelineScroll?.calcScroll(); - if (typeof backwards !== 'boolean') return; + if (typeof backwards !== "boolean") return; handleScroll(backwards); }, 200)(); }; @@ -549,34 +632,57 @@ function RoomViewContent({ eventId, roomTimeline }) { const prevMEvent = timeline[i - 1] ?? null; if (i === 0 && !roomTimeline.canPaginateBackward()) { - if (mEvent.getType() === 'm.room.create') { + if (mEvent.getType() === "m.room.create") { tl.push( - , + ); itemCountIndex += 1; // eslint-disable-next-line no-continue continue; } else { - tl.push(); + tl.push( + + ); itemCountIndex += 1; } } let isNewEvent = false; if (!unreadDivider) { - unreadDivider = (readUptoEvent - && prevMEvent?.getTs() <= readUptoEvent.getTs() - && readUptoEvent.getTs() < mEvent.getTs()); + unreadDivider = + readUptoEvent && + prevMEvent?.getTs() <= readUptoEvent.getTs() && + readUptoEvent.getTs() < mEvent.getTs(); if (unreadDivider) { isNewEvent = true; - tl.push(); + tl.push( + + ); itemCountIndex += 1; if (jumpToItemIndex === -1) jumpToItemIndex = itemCountIndex; } } - const dayDivider = prevMEvent && !isInSameDay(mEvent.getDate(), prevMEvent.getDate()); + const dayDivider = + prevMEvent && !isInSameDay(mEvent.getDate(), prevMEvent.getDate()); if (dayDivider) { - tl.push(); + tl.push( + + ); itemCountIndex += 1; } @@ -584,15 +690,17 @@ function RoomViewContent({ eventId, roomTimeline }) { const isFocus = focusId === mEvent.getId(); if (isFocus) jumpToItemIndex = itemCountIndex; - tl.push(renderEvent( - roomTimeline, - mEvent, - isNewEvent ? null : prevMEvent, - isFocus, - editEventId === mEvent.getId(), - setEditEventId, - cancelEdit, - )); + tl.push( + renderEvent( + roomTimeline, + mEvent, + isNewEvent ? null : prevMEvent, + isFocus, + editEventId === mEvent.getId(), + setEditEventId, + cancelEdit + ) + ); itemCountIndex += 1; } if (roomTimeline.canPaginateForward() || limit.length < timeline.length) { @@ -606,7 +714,9 @@ function RoomViewContent({ eventId, roomTimeline }) {
- { roomTimeline.initialized ? renderTimeline() : loadingMsgPlaceholders('loading', 3) } + {roomTimeline.initialized + ? renderTimeline() + : loadingMsgPlaceholders("loading", 3)}
diff --git a/src/app/organisms/room/RoomViewContent.scss b/src/app/organisms/room/RoomViewContent.scss index 1afd187e..47b8909d 100644 --- a/src/app/organisms/room/RoomViewContent.scss +++ b/src/app/organisms/room/RoomViewContent.scss @@ -1,4 +1,4 @@ -@use '../../partials/dir'; +@use "../../partials/dir"; .room-view__content { min-height: 100%; @@ -15,16 +15,17 @@ & .message, & .ph-msg, & .timeline-change { - @include dir.prop(border-radius, - 0 var(--bo-radius) var(--bo-radius) 0, - var(--bo-radius) 0 0 var(--bo-radius), + @include dir.prop( + border-radius, + 0 var(--bo-radius) var(--bo-radius) 0, + var(--bo-radius) 0 0 var(--bo-radius) ); } - + & > .divider { margin: var(--sp-extra-tight); @include dir.side(margin, var(--sp-normal), var(--sp-extra-tight)); @include dir.side(padding, calc(var(--av-small) + var(--sp-tight)), 0); } } -} \ No newline at end of file +} diff --git a/src/app/organisms/room/RoomViewFloating.jsx b/src/app/organisms/room/RoomViewFloating.jsx index d027aff2..5196e375 100644 --- a/src/app/organisms/room/RoomViewFloating.jsx +++ b/src/app/organisms/room/RoomViewFloating.jsx @@ -1,20 +1,20 @@ /* eslint-disable react/prop-types */ -import React, { useState, useEffect } from 'react'; -import PropTypes from 'prop-types'; -import './RoomViewFloating.scss'; +import React, { useState, useEffect } from "react"; +import PropTypes from "prop-types"; +import "./RoomViewFloating.scss"; -import initMatrix from '../../../client/initMatrix'; -import cons from '../../../client/state/cons'; -import { markAsRead } from '../../../client/action/notifications'; +import initMatrix from "../../../client/initMatrix"; +import cons from "../../../client/state/cons"; +import { markAsRead } from "../../../client/action/notifications"; -import Text from '../../atoms/text/Text'; -import Button from '../../atoms/button/Button'; +import Text from "../../atoms/text/Text"; +import Button from "../../atoms/button/Button"; -import MessageIC from '../../../../public/res/ic/outlined/message.svg'; -import MessageUnreadIC from '../../../../public/res/ic/outlined/message-unread.svg'; -import TickMarkIC from '../../../../public/res/ic/outlined/tick-mark.svg'; +import MessageIC from "../../../../public/res/ic/outlined/message.svg"; +import MessageUnreadIC from "../../../../public/res/ic/outlined/message-unread.svg"; +import TickMarkIC from "../../../../public/res/ic/outlined/tick-mark.svg"; -import { getUsersActionJsx } from './common'; +import { getUsersActionJsx } from "./common"; function useJumpToEvent(roomTimeline) { const [eventId, setEventId] = useState(null); @@ -33,7 +33,10 @@ function useJumpToEvent(roomTimeline) { // we only show "Jump to unread" btn only if the event is not in timeline. // if event is in timeline // we will automatically open the timeline from that event position - if (!readEventId?.startsWith('~') && !roomTimeline.hasEventInTimeline(readEventId)) { + if ( + !readEventId?.startsWith("~") && + !roomTimeline.hasEventInTimeline(readEventId) + ) { setEventId(readEventId); } @@ -42,7 +45,10 @@ function useJumpToEvent(roomTimeline) { notifications.on(cons.events.notifications.FULL_READ, handleMarkAsRead); return () => { - notifications.removeListener(cons.events.notifications.FULL_READ, handleMarkAsRead); + notifications.removeListener( + cons.events.notifications.FULL_READ, + handleMarkAsRead + ); setEventId(null); }; }, [roomTimeline]); @@ -61,9 +67,15 @@ function useTypingMembers(roomTimeline) { useEffect(() => { setTypingMembers(new Set()); - roomTimeline.on(cons.events.roomTimeline.TYPING_MEMBERS_UPDATED, updateTyping); + roomTimeline.on( + cons.events.roomTimeline.TYPING_MEMBERS_UPDATED, + updateTyping + ); return () => { - roomTimeline?.removeListener(cons.events.roomTimeline.TYPING_MEMBERS_UPDATED, updateTyping); + roomTimeline?.removeListener( + cons.events.roomTimeline.TYPING_MEMBERS_UPDATED, + updateTyping + ); }; }, [roomTimeline]); @@ -77,16 +89,19 @@ function useScrollToBottom(roomTimeline) { useEffect(() => { setIsAtBottom(true); roomTimeline.on(cons.events.roomTimeline.AT_BOTTOM, handleAtBottom); - return () => roomTimeline.removeListener(cons.events.roomTimeline.AT_BOTTOM, handleAtBottom); + return () => + roomTimeline.removeListener( + cons.events.roomTimeline.AT_BOTTOM, + handleAtBottom + ); }, [roomTimeline]); return [isAtBottom, setIsAtBottom]; } -function RoomViewFloating({ - roomId, roomTimeline, -}) { - const [isJumpToEvent, jumpToEvent, cancelJumpToEvent] = useJumpToEvent(roomTimeline); +function RoomViewFloating({ roomId, roomTimeline }) { + const [isJumpToEvent, jumpToEvent, cancelJumpToEvent] = + useJumpToEvent(roomTimeline); const [typingMembers] = useTypingMembers(roomTimeline); const [isAtBottom, setIsAtBottom] = useScrollToBottom(roomTimeline); @@ -97,21 +112,49 @@ function RoomViewFloating({ return ( <> -
- -
-
0 ? ' room-view__typing--open' : ''}`}> -
- {getUsersActionJsx(roomId, [...typingMembers], 'typing...')} +
0 ? " room-view__typing--open" : "" + }`} + > +
+
+
+ + {getUsersActionJsx(roomId, [...typingMembers], "typing...")} +
-
+
diff --git a/src/app/organisms/room/RoomViewFloating.scss b/src/app/organisms/room/RoomViewFloating.scss index 75802175..52324cd3 100644 --- a/src/app/organisms/room/RoomViewFloating.scss +++ b/src/app/organisms/room/RoomViewFloating.scss @@ -1,6 +1,6 @@ - @use '../../partials/flex'; -@use '../../partials/text'; -@use '../../partials/dir'; +@use "../../partials/flex"; +@use "../../partials/text"; +@use "../../partials/dir"; .room-view { &__typing { @@ -46,25 +46,24 @@ background: var(--tc-surface-high); border-radius: 50%; } - - + .bouncing-loader::before, .bouncing-loader::after { content: ""; } - + .bouncing-loader > div { margin: 0 4px; } - + .bouncing-loader > div { animation-delay: 0.2s; } - + .bouncing-loader::after { animation-delay: 0.4s; } - + @keyframes bouncing-loader { to { opacity: 0.1; @@ -122,4 +121,4 @@ @extend .cp-fx__item-one; } } -} \ No newline at end of file +} diff --git a/src/app/organisms/room/RoomViewHeader.jsx b/src/app/organisms/room/RoomViewHeader.jsx index 46a6ba0e..fea7eed5 100644 --- a/src/app/organisms/room/RoomViewHeader.jsx +++ b/src/app/organisms/room/RoomViewHeader.jsx @@ -1,41 +1,47 @@ -import React, { useEffect, useRef } from 'react'; -import PropTypes from 'prop-types'; -import './RoomViewHeader.scss'; +import React, { useEffect, useRef } from "react"; +import PropTypes from "prop-types"; +import "./RoomViewHeader.scss"; -import { twemojify } from '../../../util/twemojify'; -import { blurOnBubbling } from '../../atoms/button/script'; +import { twemojify } from "../../../util/twemojify"; +import { blurOnBubbling } from "../../atoms/button/script"; -import initMatrix from '../../../client/initMatrix'; -import cons from '../../../client/state/cons'; -import navigation from '../../../client/state/navigation'; -import { toggleRoomSettings, openReusableContextMenu, openNavigation } from '../../../client/action/navigation'; -import { togglePeopleDrawer } from '../../../client/action/settings'; -import colorMXID from '../../../util/colorMXID'; -import { getEventCords } from '../../../util/common'; +import initMatrix from "../../../client/initMatrix"; +import cons from "../../../client/state/cons"; +import navigation from "../../../client/state/navigation"; +import { + toggleRoomSettings, + openReusableContextMenu, + openNavigation, +} from "../../../client/action/navigation"; +import { togglePeopleDrawer } from "../../../client/action/settings"; +import colorMXID from "../../../util/colorMXID"; +import { getEventCords } from "../../../util/common"; -import { tabText } from './RoomSettings'; -import Text from '../../atoms/text/Text'; -import RawIcon from '../../atoms/system-icons/RawIcon'; -import IconButton from '../../atoms/button/IconButton'; -import Header, { TitleWrapper } from '../../atoms/header/Header'; -import Avatar from '../../atoms/avatar/Avatar'; -import RoomOptions from '../../molecules/room-options/RoomOptions'; +import { tabText } from "./RoomSettings"; +import Text from "../../atoms/text/Text"; +import RawIcon from "../../atoms/system-icons/RawIcon"; +import IconButton from "../../atoms/button/IconButton"; +import Header, { TitleWrapper } from "../../atoms/header/Header"; +import Avatar from "../../atoms/avatar/Avatar"; +import RoomOptions from "../../molecules/room-options/RoomOptions"; -import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg'; -import SearchIC from '../../../../public/res/ic/outlined/search.svg'; -import UserIC from '../../../../public/res/ic/outlined/user.svg'; -import VerticalMenuIC from '../../../../public/res/ic/outlined/vertical-menu.svg'; -import BackArrowIC from '../../../../public/res/ic/outlined/chevron-left.svg'; +import ChevronBottomIC from "../../../../public/res/ic/outlined/chevron-bottom.svg"; +import SearchIC from "../../../../public/res/ic/outlined/search.svg"; +import UserIC from "../../../../public/res/ic/outlined/user.svg"; +import VerticalMenuIC from "../../../../public/res/ic/outlined/vertical-menu.svg"; +import BackArrowIC from "../../../../public/res/ic/outlined/chevron-left.svg"; -import { useForceUpdate } from '../../hooks/useForceUpdate'; +import { useForceUpdate } from "../../hooks/useForceUpdate"; function RoomViewHeader({ roomId }) { const [, forceUpdate] = useForceUpdate(); const mx = initMatrix.matrixClient; const isDM = initMatrix.roomList.directs.has(roomId); const room = mx.getRoom(roomId); - let avatarSrc = room.getAvatarUrl(mx.baseUrl, 36, 36, 'crop'); - avatarSrc = isDM ? room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 36, 36, 'crop') : avatarSrc; + let avatarSrc = room.getAvatarUrl(mx.baseUrl, 36, 36, "crop"); + avatarSrc = isDM + ? room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 36, 36, "crop") + : avatarSrc; const roomName = room.name; const roomHeaderBtnRef = useRef(null); @@ -43,12 +49,15 @@ function RoomViewHeader({ roomId }) { const settingsToggle = (isVisibile) => { const rawIcon = roomHeaderBtnRef.current.lastElementChild; rawIcon.style.transform = isVisibile - ? 'rotateX(180deg)' - : 'rotateX(0deg)'; + ? "rotateX(180deg)" + : "rotateX(0deg)"; }; navigation.on(cons.events.navigation.ROOM_SETTINGS_TOGGLED, settingsToggle); return () => { - navigation.removeListener(cons.events.navigation.ROOM_SETTINGS_TOGGLED, settingsToggle); + navigation.removeListener( + cons.events.navigation.ROOM_SETTINGS_TOGGLED, + settingsToggle + ); }; }, []); @@ -61,15 +70,20 @@ function RoomViewHeader({ roomId }) { roomList.on(cons.events.roomList.ROOM_PROFILE_UPDATED, handleProfileUpdate); return () => { - roomList.removeListener(cons.events.roomList.ROOM_PROFILE_UPDATED, handleProfileUpdate); + roomList.removeListener( + cons.events.roomList.ROOM_PROFILE_UPDATED, + handleProfileUpdate + ); }; }, [roomId]); const openRoomOptions = (e) => { openReusableContextMenu( - 'bottom', - getEventCords(e, '.ic-btn'), - (closeMenu) => , + "bottom", + getEventCords(e, ".ic-btn"), + (closeMenu) => ( + + ) ); }; @@ -86,17 +100,40 @@ function RoomViewHeader({ roomId }) { className="room-header__btn" onClick={() => toggleRoomSettings()} type="button" - onMouseUp={(e) => blurOnBubbling(e, '.room-header__btn')} + onMouseUp={(e) => blurOnBubbling(e, ".room-header__btn")} > - + - {twemojify(roomName)} + + {twemojify(roomName)} + - {mx.isRoomEncrypted(roomId) === false && toggleRoomSettings(tabText.SEARCH)} tooltip="Search" src={SearchIC} />} - - toggleRoomSettings(tabText.MEMBERS)} tooltip="Members" src={UserIC} /> + {mx.isRoomEncrypted(roomId) === false && ( + toggleRoomSettings(tabText.SEARCH)} + tooltip="Search" + src={SearchIC} + /> + )} + + toggleRoomSettings(tabText.MEMBERS)} + tooltip="Members" + src={UserIC} + /> { roomsInput.on(cons.events.roomsInput.ATTACHMENT_SET, setAttachment); - viewEvent.on('focus_msg_input', requestFocusInput); + viewEvent.on("focus_msg_input", requestFocusInput); return () => { - roomsInput.removeListener(cons.events.roomsInput.ATTACHMENT_SET, setAttachment); - viewEvent.removeListener('focus_msg_input', requestFocusInput); + roomsInput.removeListener( + cons.events.roomsInput.ATTACHMENT_SET, + setAttachment + ); + viewEvent.removeListener("focus_msg_input", requestFocusInput); }; }, []); @@ -83,13 +87,15 @@ function RoomViewInput({ function uploadingProgress(myRoomId, { loaded, total }) { if (myRoomId !== roomId) return; const progressPer = Math.round((loaded * 100) / total); - uploadProgressRef.current.textContent = `Uploading: ${bytesToSize(loaded)}/${bytesToSize(total)} (${progressPer}%)`; + uploadProgressRef.current.textContent = `Uploading: ${bytesToSize( + loaded + )}/${bytesToSize(total)} (${progressPer}%)`; inputBaseRef.current.style.backgroundImage = `linear-gradient(90deg, var(--bg-surface-hover) ${progressPer}%, var(--bg-surface-low) ${progressPer}%)`; } function clearAttachment(myRoomId) { if (roomId !== myRoomId) return; setAttachment(null); - inputBaseRef.current.style.backgroundImage = 'unset'; + inputBaseRef.current.style.backgroundImage = "unset"; uploadInputRef.current.value = null; } @@ -103,7 +109,7 @@ function RoomViewInput({ function activateCmd(prefix) { isCmdActivated = true; rightOptionsA11Y(false); - viewEvent.emit('cmd_activate', prefix); + viewEvent.emit("cmd_activate", prefix); } function deactivateCmd() { isCmdActivated = false; @@ -112,7 +118,7 @@ function RoomViewInput({ } function deactivateCmdAndEmit() { deactivateCmd(); - viewEvent.emit('cmd_deactivate'); + viewEvent.emit("cmd_deactivate"); } function setCursorPosition(pos) { setTimeout(() => { @@ -125,7 +131,8 @@ function RoomViewInput({ const targetInput = msg.slice(0, cursor); const cmdParts = targetInput.match(CMD_REGEX); const leadingInput = msg.slice(0, cmdParts.index); - if (replacement.length > 0) setCursorPosition(leadingInput.length + replacement.length); + if (replacement.length > 0) + setCursorPosition(leadingInput.length + replacement.length); return leadingInput + replacement + msg.slice(cursor); } function firedCmd(cmdData) { @@ -133,7 +140,7 @@ function RoomViewInput({ textAreaRef.current.value = replaceCmdWith( msg, cmdCursorPos, - typeof cmdData?.replace !== 'undefined' ? cmdData.replace : '', + typeof cmdData?.replace !== "undefined" ? cmdData.replace : "" ); deactivateCmd(); } @@ -146,16 +153,22 @@ function RoomViewInput({ function setUpReply(userId, eventId, body, formattedBody) { setReplyTo({ userId, eventId, body }); roomsInput.setReplyTo(roomId, { - userId, eventId, body, formattedBody, + userId, + eventId, + body, + formattedBody, }); focusInput(); } useEffect(() => { - roomsInput.on(cons.events.roomsInput.UPLOAD_PROGRESS_CHANGES, uploadingProgress); + roomsInput.on( + cons.events.roomsInput.UPLOAD_PROGRESS_CHANGES, + uploadingProgress + ); roomsInput.on(cons.events.roomsInput.ATTACHMENT_CANCELED, clearAttachment); roomsInput.on(cons.events.roomsInput.FILE_UPLOADED, clearAttachment); - viewEvent.on('cmd_fired', firedCmd); + viewEvent.on("cmd_fired", firedCmd); navigation.on(cons.events.navigation.REPLY_TO_CLICKED, setUpReply); if (textAreaRef?.current !== null) { isTyping = false; @@ -164,19 +177,31 @@ function RoomViewInput({ setReplyTo(roomsInput.getReplyTo(roomId)); } return () => { - roomsInput.removeListener(cons.events.roomsInput.UPLOAD_PROGRESS_CHANGES, uploadingProgress); - roomsInput.removeListener(cons.events.roomsInput.ATTACHMENT_CANCELED, clearAttachment); - roomsInput.removeListener(cons.events.roomsInput.FILE_UPLOADED, clearAttachment); - viewEvent.removeListener('cmd_fired', firedCmd); - navigation.removeListener(cons.events.navigation.REPLY_TO_CLICKED, setUpReply); + roomsInput.removeListener( + cons.events.roomsInput.UPLOAD_PROGRESS_CHANGES, + uploadingProgress + ); + roomsInput.removeListener( + cons.events.roomsInput.ATTACHMENT_CANCELED, + clearAttachment + ); + roomsInput.removeListener( + cons.events.roomsInput.FILE_UPLOADED, + clearAttachment + ); + viewEvent.removeListener("cmd_fired", firedCmd); + navigation.removeListener( + cons.events.navigation.REPLY_TO_CLICKED, + setUpReply + ); if (isCmdActivated) deactivateCmd(); if (textAreaRef?.current === null) return; const msg = textAreaRef.current.value; - textAreaRef.current.style.height = 'unset'; - inputBaseRef.current.style.backgroundImage = 'unset'; - if (msg.trim() === '') { - roomsInput.setMessage(roomId, ''); + textAreaRef.current.style.height = "unset"; + inputBaseRef.current.style.backgroundImage = "unset"; + if (msg.trim() === "") { + roomsInput.setMessage(roomId, ""); return; } roomsInput.setMessage(roomId, msg); @@ -185,8 +210,8 @@ function RoomViewInput({ const sendBody = async (body, options) => { const opt = options ?? {}; - if (!opt.msgType) opt.msgType = 'm.text'; - if (typeof opt.autoMarkdown !== 'boolean') opt.autoMarkdown = true; + if (!opt.msgType) opt.msgType = "m.text"; + if (typeof opt.autoMarkdown !== "boolean") opt.autoMarkdown = true; if (roomsInput.isSending(roomId)) return; sendIsTyping(false); @@ -195,26 +220,30 @@ function RoomViewInput({ roomsInput.setAttachment(roomId, attachment); } textAreaRef.current.disabled = true; - textAreaRef.current.style.cursor = 'not-allowed'; + textAreaRef.current.style.cursor = "not-allowed"; await roomsInput.sendInput(roomId, opt); textAreaRef.current.disabled = false; - textAreaRef.current.style.cursor = 'unset'; + textAreaRef.current.style.cursor = "unset"; focusInput(); textAreaRef.current.value = roomsInput.getMessage(roomId); - textAreaRef.current.style.height = 'unset'; + textAreaRef.current.style.height = "unset"; if (replyTo !== null) setReplyTo(null); }; const processCommand = (cmdBody) => { - const spaceIndex = cmdBody.indexOf(' '); + const spaceIndex = cmdBody.indexOf(" "); const cmdName = cmdBody.slice(1, spaceIndex > -1 ? spaceIndex : undefined); - const cmdData = spaceIndex > -1 ? cmdBody.slice(spaceIndex + 1) : ''; + const cmdData = spaceIndex > -1 ? cmdBody.slice(spaceIndex + 1) : ""; if (!commands[cmdName]) { - confirmDialog('Invalid Command', `"${cmdName}" is not a valid command.`, 'Alright'); + confirmDialog( + "Invalid Command", + `"${cmdName}" is not a valid command.`, + "Alright" + ); return; } - if (['me', 'shrug', 'plain'].includes(cmdName)) { + if (["me", "shrug", "plain"].includes(cmdName)) { commands[cmdName].exe(roomId, cmdData, sendBody); return; } @@ -224,13 +253,13 @@ function RoomViewInput({ const sendMessage = async () => { requestAnimationFrame(() => deactivateCmdAndEmit()); const msgBody = textAreaRef.current.value.trim(); - if (msgBody.startsWith('/')) { + if (msgBody.startsWith("/")) { processCommand(msgBody.trim()); - textAreaRef.current.value = ''; - textAreaRef.current.style.height = 'unset'; + textAreaRef.current.value = ""; + textAreaRef.current.style.height = "unset"; return; } - if (msgBody === '' && attachment === null) return; + if (msgBody === "" && attachment === null) return; sendBody(msgBody); }; @@ -239,7 +268,7 @@ function RoomViewInput({ }; function processTyping(msg) { - const isEmptyMsg = msg === ''; + const isEmptyMsg = msg === ""; if (isEmptyMsg && isTyping) { sendIsTyping(false); @@ -266,7 +295,7 @@ function RoomViewInput({ const cmdPrefix = cmdParts[1]; const cmdSlug = cmdParts[2]; - if (cmdPrefix === ':') { + if (cmdPrefix === ":") { // skip emoji autofill command if link is suspected. const checkForLink = targetInput.slice(0, cmdParts.index); if (checkForLink.match(/(http|https|mailto|matrix|ircs|irc)$/)) { @@ -276,12 +305,12 @@ function RoomViewInput({ } cmdCursorPos = cursor; - if (cmdSlug === '') { + if (cmdSlug === "") { activateCmd(cmdPrefix); return; } if (!isCmdActivated) activateCmd(cmdPrefix); - viewEvent.emit('cmd_process', cmdPrefix, cmdSlug); + viewEvent.emit("cmd_process", cmdPrefix, cmdSlug); } const handleMsgTyping = (e) => { @@ -291,12 +320,12 @@ function RoomViewInput({ }; const handleKeyDown = (e) => { - if (e.key === 'Escape') { + if (e.key === "Escape") { e.preventDefault(); roomsInput.cancelReplyTo(roomId); setReplyTo(null); } - if (e.key === 'Enter' && e.shiftKey === false) { + if (e.key === "Enter" && e.shiftKey === false) { e.preventDefault(); sendMessage(); } @@ -313,7 +342,7 @@ function RoomViewInput({ for (let i = 0; i < e.clipboardData.items.length; i += 1) { const item = e.clipboardData.items[i]; - if (item.type.indexOf('image') !== -1) { + if (item.type.indexOf("image") !== -1) { const image = item.getAsFile(); if (attachment === null) { setAttachment(image); @@ -346,27 +375,44 @@ function RoomViewInput({ } function renderInputs() { - const canISend = roomTimeline.room.currentState.maySendMessage(mx.getUserId()); - const tombstoneEvent = roomTimeline.room.currentState.getStateEvents('m.room.tombstone')[0]; + const canISend = roomTimeline.room.currentState.maySendMessage( + mx.getUserId() + ); + const tombstoneEvent = + roomTimeline.room.currentState.getStateEvents("m.room.tombstone")[0]; if (!canISend || tombstoneEvent) { return ( - { - tombstoneEvent - ? tombstoneEvent.getContent()?.body ?? 'This room has been replaced and is no longer active.' - : 'You do not have permission to post to this room' - } + {tombstoneEvent + ? tombstoneEvent.getContent()?.body ?? + "This room has been replaced and is no longer active." + : "You do not have permission to post to this room"} ); } return ( <> -
- - +
+ +
- {roomTimeline.isEncrypted() && } + {roomTimeline.isEncrypted() && ( + + )} { openReusableContextMenu( - 'top', + "top", (() => { const cords = getEventCords(e); cords.y -= 20; @@ -399,7 +445,7 @@ function RoomViewInput({ closeMenu(); }} /> - ), + ) ); }} tooltip="Sticker" @@ -408,7 +454,7 @@ function RoomViewInput({ { const cords = getEventCords(e); - cords.x += (document.dir === 'rtl' ? -80 : 80); + cords.x += document.dir === "rtl" ? -80 : 80; cords.y -= 250; openEmojiBoard(cords, addEmoji); }} @@ -422,18 +468,30 @@ function RoomViewInput({ } function attachFile() { - const fileType = attachment.type.slice(0, attachment.type.indexOf('/')); + const fileType = attachment.type.slice(0, attachment.type.indexOf("/")); return (
-
- {fileType === 'image' && {attachment.name}} - {fileType === 'video' && } - {fileType === 'audio' && } - {fileType !== 'image' && fileType !== 'video' && fileType !== 'audio' && } +
+ {fileType === "image" && ( + {attachment.name} + )} + {fileType === "video" && } + {fileType === "audio" && } + {fileType !== "image" && + fileType !== "video" && + fileType !== "audio" && }
{attachment.name} - {`size: ${bytesToSize(attachment.size)}`} + + {`size: ${bytesToSize( + attachment.size + )}`} +
); @@ -464,12 +522,15 @@ function RoomViewInput({ return ( <> - { replyTo !== null && attachReply()} - { attachment !== null && attachFile() } -
{ e.preventDefault(); }}> - { - renderInputs() - } + {replyTo !== null && attachReply()} + {attachment !== null && attachFile()} + { + e.preventDefault(); + }} + > + {renderInputs()}
); diff --git a/src/app/organisms/room/RoomViewInput.scss b/src/app/organisms/room/RoomViewInput.scss index 9fb7c4de..e485be57 100644 --- a/src/app/organisms/room/RoomViewInput.scss +++ b/src/app/organisms/room/RoomViewInput.scss @@ -1,4 +1,4 @@ -@use '../../partials/dir'; +@use "../../partials/dir"; .room-input { padding: var(--sp-extra-tight) calc(var(--sp-normal) - 2px); @@ -17,7 +17,7 @@ display: flex; align-items: center; - margin: 0 calc(var(--sp-tight) - 2px); + margin: 0 calc(var(--sp-tight) - 2px); background-color: var(--bg-surface-low); box-shadow: var(--bs-surface-border); border-radius: var(--bo-radius); @@ -26,7 +26,7 @@ transform: scale(0.8); margin: 0 var(--sp-extra-tight); } - + & .scrollbar { max-height: 50vh; flex: 1; @@ -105,4 +105,4 @@ @include dir.side(margin, 17px, 13px); border-radius: 0; } -} \ No newline at end of file +} diff --git a/src/app/organisms/room/TimelineScroll.js b/src/app/organisms/room/TimelineScroll.js index ccdc9a97..6b05e2d4 100644 --- a/src/app/organisms/room/TimelineScroll.js +++ b/src/app/organisms/room/TimelineScroll.js @@ -1,9 +1,11 @@ -import { getScrollInfo } from '../../../util/common'; +import { getScrollInfo } from "../../../util/common"; class TimelineScroll { constructor(target) { if (target === null) { - throw new Error('Can not initialize TimelineScroll, target HTMLElement in null'); + throw new Error( + "Can not initialize TimelineScroll, target HTMLElement in null" + ); } this.scroll = target; @@ -33,7 +35,9 @@ class TimelineScroll { const scrollInfo = getScrollInfo(this.scroll); let scrollTop = 0; - const ot = this.inTopHalf ? this.topMsg?.offsetTop : this.bottomMsg?.offsetTop; + const ot = this.inTopHalf + ? this.topMsg?.offsetTop + : this.bottomMsg?.offsetTop; if (!ot) scrollTop = Math.round(this.height - this.viewHeight); else scrollTop = ot - this.diff; @@ -47,7 +51,11 @@ class TimelineScroll { if (offsetTop === undefined) return; // if msg is already in visible are we don't need to scroll to that - if (offsetTop > scrollInfo.top && offsetTop < (scrollInfo.top + scrollInfo.viewHeight)) return; + if ( + offsetTop > scrollInfo.top && + offsetTop < scrollInfo.top + scrollInfo.viewHeight + ) + return; const to = offsetTop - offset; this._scrollTo(scrollInfo, to); @@ -68,7 +76,7 @@ class TimelineScroll { const maxScrollTop = scrollInfo.height - scrollInfo.viewHeight; - sInfo.top = (scrollTop > maxScrollTop) ? maxScrollTop : scrollTop; + sInfo.top = scrollTop > maxScrollTop ? maxScrollTop : scrollTop; this._updateCalc(sInfo); } @@ -81,12 +89,12 @@ class TimelineScroll { // TODO: classname 'ph-msg' prevent this class from being used const PLACEHOLDER_COUNT = 2; - this.topMsg = msgs[0]?.className === 'ph-msg' - ? msgs[PLACEHOLDER_COUNT] - : msgs[0]; - this.bottomMsg = msgs[lMsgIndex]?.className === 'ph-msg' - ? msgs[lMsgIndex - PLACEHOLDER_COUNT] - : msgs[lMsgIndex]; + this.topMsg = + msgs[0]?.className === "ph-msg" ? msgs[PLACEHOLDER_COUNT] : msgs[0]; + this.bottomMsg = + msgs[lMsgIndex]?.className === "ph-msg" + ? msgs[lMsgIndex - PLACEHOLDER_COUNT] + : msgs[lMsgIndex]; } // we calculate the difference between first/last message and current scrollTop. diff --git a/src/app/organisms/room/commands.jsx b/src/app/organisms/room/commands.jsx index 463f9d94..48f53b10 100644 --- a/src/app/organisms/room/commands.jsx +++ b/src/app/organisms/room/commands.jsx @@ -1,13 +1,16 @@ -import React from 'react'; -import './commands.scss'; +import React from "react"; +import "./commands.scss"; -import initMatrix from '../../../client/initMatrix'; -import * as roomActions from '../../../client/action/room'; -import { hasDMWith, hasDevices } from '../../../util/matrixUtil'; -import { selectRoom, openReusableDialog } from '../../../client/action/navigation'; +import initMatrix from "../../../client/initMatrix"; +import * as roomActions from "../../../client/action/room"; +import { hasDMWith, hasDevices } from "../../../util/matrixUtil"; +import { + selectRoom, + openReusableDialog, +} from "../../../client/action/navigation"; -import Text from '../../atoms/text/Text'; -import SettingTile from '../../molecules/setting-tile/SettingTile'; +import Text from "../../atoms/text/Text"; +import SettingTile from "../../molecules/setting-tile/SettingTile"; const MXID_REG = /^@\S+:\S+$/; const ROOM_ID_ALIAS_REG = /^(#|!)\S+:\S+$/; @@ -21,9 +24,9 @@ export function processMxidAndReason(data) { if (reasonMatch) { idData = data.slice(0, reasonMatch.index); reason = data.slice(reasonMatch.index + reasonMatch[0].length); - if (reason.trim() === '') reason = undefined; + if (reason.trim() === "") reason = undefined; } - const rawIds = idData.split(' '); + const rawIds = idData.split(" "); const userIds = rawIds.filter((id) => id.match(MXID_REG)); return { userIds, @@ -33,44 +36,46 @@ export function processMxidAndReason(data) { const commands = { me: { - name: 'me', - description: 'Display action', + name: "me", + description: "Display action", exe: (roomId, data, onSuccess) => { const body = data.trim(); - if (body === '') return; - onSuccess(body, { msgType: 'm.emote' }); + if (body === "") return; + onSuccess(body, { msgType: "m.emote" }); }, }, shrug: { - name: 'shrug', - description: 'Send ¯\\_(ツ)_/¯ as message', - exe: (roomId, data, onSuccess) => onSuccess( - `¯\\_(ツ)_/¯${data.trim() !== '' ? ` ${data}` : ''}`, - { msgType: 'm.text' }, - ), + name: "shrug", + description: "Send ¯\\_(ツ)_/¯ as message", + exe: (roomId, data, onSuccess) => + onSuccess(`¯\\_(ツ)_/¯${data.trim() !== "" ? ` ${data}` : ""}`, { + msgType: "m.text", + }), }, plain: { - name: 'plain', - description: 'Send plain text message', + name: "plain", + description: "Send plain text message", exe: (roomId, data, onSuccess) => { const body = data.trim(); - if (body === '') return; - onSuccess(body, { msgType: 'm.text', autoMarkdown: false }); + if (body === "") return; + onSuccess(body, { msgType: "m.text", autoMarkdown: false }); }, }, help: { - name: 'help', - description: 'View all commands', + name: "help", + description: "View all commands", // eslint-disable-next-line no-use-before-define exe: () => openHelpDialog(), }, startdm: { - name: 'startdm', - description: 'Start direct message with user. Example: /startdm userId1', + name: "startdm", + description: "Start direct message with user. Example: /startdm userId1", exe: async (roomId, data) => { const mx = initMatrix.matrixClient; - const rawIds = data.split(' '); - const userIds = rawIds.filter((id) => id.match(MXID_REG) && id !== mx.getUserId()); + const rawIds = data.split(" "); + const userIds = rawIds.filter( + (id) => id.match(MXID_REG) && id !== mx.getUserId() + ); if (userIds.length === 0) return; if (userIds.length === 1) { const dmRoomId = hasDMWith(userIds[0]); @@ -86,98 +91,103 @@ const commands = { }, }, join: { - name: 'join', - description: 'Join room with address. Example: /join address1 address2', + name: "join", + description: "Join room with address. Example: /join address1 address2", exe: (roomId, data) => { - const rawIds = data.split(' '); + const rawIds = data.split(" "); const roomIds = rawIds.filter((id) => id.match(ROOM_ID_ALIAS_REG)); roomIds.map((id) => roomActions.join(id)); }, }, leave: { - name: 'leave', - description: 'Leave current room.', + name: "leave", + description: "Leave current room.", exe: (roomId, data) => { - if (data.trim() === '') { + if (data.trim() === "") { roomActions.leave(roomId); return; } - const rawIds = data.split(' '); + const rawIds = data.split(" "); const roomIds = rawIds.filter((id) => id.match(ROOM_ID_REG)); roomIds.map((id) => roomActions.leave(id)); }, }, invite: { - name: 'invite', - description: 'Invite user to room. Example: /invite userId1 userId2 [-r reason]', + name: "invite", + description: + "Invite user to room. Example: /invite userId1 userId2 [-r reason]", exe: (roomId, data) => { const { userIds, reason } = processMxidAndReason(data); userIds.map((id) => roomActions.invite(roomId, id, reason)); }, }, disinvite: { - name: 'disinvite', - description: 'Disinvite user to room. Example: /disinvite userId1 userId2 [-r reason]', + name: "disinvite", + description: + "Disinvite user to room. Example: /disinvite userId1 userId2 [-r reason]", exe: (roomId, data) => { const { userIds, reason } = processMxidAndReason(data); userIds.map((id) => roomActions.kick(roomId, id, reason)); }, }, kick: { - name: 'kick', - description: 'Kick user from room. Example: /kick userId1 userId2 [-r reason]', + name: "kick", + description: + "Kick user from room. Example: /kick userId1 userId2 [-r reason]", exe: (roomId, data) => { const { userIds, reason } = processMxidAndReason(data); userIds.map((id) => roomActions.kick(roomId, id, reason)); }, }, ban: { - name: 'ban', - description: 'Ban user from room. Example: /ban userId1 userId2 [-r reason]', + name: "ban", + description: + "Ban user from room. Example: /ban userId1 userId2 [-r reason]", exe: (roomId, data) => { const { userIds, reason } = processMxidAndReason(data); userIds.map((id) => roomActions.ban(roomId, id, reason)); }, }, unban: { - name: 'unban', - description: 'Unban user from room. Example: /unban userId1 userId2', + name: "unban", + description: "Unban user from room. Example: /unban userId1 userId2", exe: (roomId, data) => { - const rawIds = data.split(' '); + const rawIds = data.split(" "); const userIds = rawIds.filter((id) => id.match(MXID_REG)); userIds.map((id) => roomActions.unban(roomId, id)); }, }, ignore: { - name: 'ignore', - description: 'Ignore user. Example: /ignore userId1 userId2', + name: "ignore", + description: "Ignore user. Example: /ignore userId1 userId2", exe: (roomId, data) => { - const rawIds = data.split(' '); + const rawIds = data.split(" "); const userIds = rawIds.filter((id) => id.match(MXID_REG)); if (userIds.length > 0) roomActions.ignore(userIds); }, }, unignore: { - name: 'unignore', - description: 'Unignore user. Example: /unignore userId1 userId2', + name: "unignore", + description: "Unignore user. Example: /unignore userId1 userId2", exe: (roomId, data) => { - const rawIds = data.split(' '); + const rawIds = data.split(" "); const userIds = rawIds.filter((id) => id.match(MXID_REG)); if (userIds.length > 0) roomActions.unignore(userIds); }, }, myroomnick: { - name: 'myroomnick', - description: 'Change nick in current room.', + name: "myroomnick", + description: "Change nick in current room.", exe: (roomId, data) => { const nick = data.trim(); - if (nick === '') return; + if (nick === "") return; roomActions.setMyRoomNick(roomId, nick); }, }, myroomavatar: { - name: 'myroomavatar', - description: 'Change profile picture in current room. Example /myroomavatar mxc://xyzabc', + name: "myroomavatar", + description: + "Change profile picture in current room. Example /myroomavatar mxc://xyzabc", exe: (roomId, data) => { if (data.match(MXC_REG)) { roomActions.setMyRoomAvatar(roomId, data); @@ -185,15 +195,15 @@ const commands = { }, }, converttodm: { - name: 'converttodm', - description: 'Convert room to direct message', + name: "converttodm", + description: "Convert room to direct message", exe: (roomId) => { roomActions.convertToDm(roomId); }, }, converttoroom: { - name: 'converttoroom', - description: 'Convert direct message to room', + name: "converttoroom", + description: "Convert direct message to room", exe: (roomId) => { roomActions.convertToRoom(roomId); }, @@ -202,7 +212,9 @@ const commands = { function openHelpDialog() { openReusableDialog( - Commands, + + Commands + , () => (
{Object.keys(commands).map((cmdName) => ( @@ -213,7 +225,7 @@ function openHelpDialog() { /> ))}
- ), + ) ); } diff --git a/src/app/organisms/room/commands.scss b/src/app/organisms/room/commands.scss index 62839378..de79a684 100644 --- a/src/app/organisms/room/commands.scss +++ b/src/app/organisms/room/commands.scss @@ -7,4 +7,4 @@ margin-bottom: var(--sp-extra-loose); } } -} \ No newline at end of file +} diff --git a/src/app/organisms/room/common.jsx b/src/app/organisms/room/common.jsx index 28974a85..f92eecbf 100644 --- a/src/app/organisms/room/common.jsx +++ b/src/app/organisms/room/common.jsx @@ -1,9 +1,9 @@ -import React from 'react'; +import React from "react"; -import { twemojify } from '../../../util/twemojify'; +import { twemojify } from "../../../util/twemojify"; -import initMatrix from '../../../client/initMatrix'; -import { getUsername, getUsernameOfRoomMember } from '../../../util/matrixUtil'; +import initMatrix from "../../../client/initMatrix"; +import { getUsername, getUsernameOfRoomMember } from "../../../util/matrixUtil"; function getTimelineJSXMessages() { return { @@ -11,16 +11,16 @@ function getTimelineJSXMessages() { return ( <> {twemojify(user)} - {' joined the room'} + {" joined the room"} ); }, leave(user, reason) { - const reasonMsg = (typeof reason === 'string') ? `: ${reason}` : ''; + const reasonMsg = typeof reason === "string" ? `: ${reason}` : ""; return ( <> {twemojify(user)} - {' left the room'} + {" left the room"} {twemojify(reasonMsg)} ); @@ -29,7 +29,7 @@ function getTimelineJSXMessages() { return ( <> {twemojify(inviter)} - {' invited '} + {" invited "} {twemojify(user)} ); @@ -38,9 +38,9 @@ function getTimelineJSXMessages() { return ( <> {twemojify(inviter)} - {' canceled '} + {" canceled "} {twemojify(user)} - {'\'s invite'} + {"'s invite"} ); }, @@ -48,27 +48,27 @@ function getTimelineJSXMessages() { return ( <> {twemojify(user)} - {' rejected the invitation'} + {" rejected the invitation"} ); }, kick(actor, user, reason) { - const reasonMsg = (typeof reason === 'string') ? `: ${reason}` : ''; + const reasonMsg = typeof reason === "string" ? `: ${reason}` : ""; return ( <> {twemojify(actor)} - {' kicked '} + {" kicked "} {twemojify(user)} {twemojify(reasonMsg)} ); }, ban(actor, user, reason) { - const reasonMsg = (typeof reason === 'string') ? `: ${reason}` : ''; + const reasonMsg = typeof reason === "string" ? `: ${reason}` : ""; return ( <> {twemojify(actor)} - {' banned '} + {" banned "} {twemojify(user)} {twemojify(reasonMsg)} @@ -78,7 +78,7 @@ function getTimelineJSXMessages() { return ( <> {twemojify(actor)} - {' unbanned '} + {" unbanned "} {twemojify(user)} ); @@ -87,7 +87,7 @@ function getTimelineJSXMessages() { return ( <> {twemojify(user)} - {' set a avatar'} + {" set a avatar"} ); }, @@ -95,7 +95,7 @@ function getTimelineJSXMessages() { return ( <> {twemojify(user)} - {' changed their avatar'} + {" changed their avatar"} ); }, @@ -103,7 +103,7 @@ function getTimelineJSXMessages() { return ( <> {twemojify(user)} - {' removed their avatar'} + {" removed their avatar"} ); }, @@ -111,7 +111,7 @@ function getTimelineJSXMessages() { return ( <> {twemojify(user)} - {' set display name to '} + {" set display name to "} {twemojify(newName)} ); @@ -120,7 +120,7 @@ function getTimelineJSXMessages() { return ( <> {twemojify(user)} - {' changed their display name to '} + {" changed their display name to "} {twemojify(newName)} ); @@ -129,7 +129,7 @@ function getTimelineJSXMessages() { return ( <> {twemojify(user)} - {' removed their display name '} + {" removed their display name "} {twemojify(lastName)} ); @@ -140,31 +140,50 @@ function getTimelineJSXMessages() { function getUsersActionJsx(roomId, userIds, actionStr) { const room = initMatrix.matrixClient.getRoom(roomId); const getUserDisplayName = (userId) => { - if (room?.getMember(userId)) return getUsernameOfRoomMember(room.getMember(userId)); + if (room?.getMember(userId)) + return getUsernameOfRoomMember(room.getMember(userId)); return getUsername(userId); }; const getUserJSX = (userId) => {twemojify(getUserDisplayName(userId))}; - if (!Array.isArray(userIds)) return 'Idle'; - if (userIds.length === 0) return 'Idle'; + if (!Array.isArray(userIds)) return "Idle"; + if (userIds.length === 0) return "Idle"; const MAX_VISIBLE_COUNT = 3; const u1Jsx = getUserJSX(userIds[0]); // eslint-disable-next-line react/jsx-one-expression-per-line - if (userIds.length === 1) return <>{u1Jsx} is {actionStr}; + if (userIds.length === 1) + return ( + <> + {u1Jsx} is {actionStr} + + ); const u2Jsx = getUserJSX(userIds[1]); // eslint-disable-next-line react/jsx-one-expression-per-line - if (userIds.length === 2) return <>{u1Jsx} and {u2Jsx} are {actionStr}; + if (userIds.length === 2) + return ( + <> + {u1Jsx} and {u2Jsx} are {actionStr} + + ); const u3Jsx = getUserJSX(userIds[2]); if (userIds.length === 3) { // eslint-disable-next-line react/jsx-one-expression-per-line - return <>{u1Jsx}, {u2Jsx} and {u3Jsx} are {actionStr}; + return ( + <> + {u1Jsx}, {u2Jsx} and {u3Jsx} are {actionStr} + + ); } const othersCount = userIds.length - MAX_VISIBLE_COUNT; // eslint-disable-next-line react/jsx-one-expression-per-line - return <>{u1Jsx}, {u2Jsx}, {u3Jsx} and {othersCount} others are {actionStr}; + return ( + <> + {u1Jsx}, {u2Jsx}, {u3Jsx} and {othersCount} others are {actionStr} + + ); } function parseTimelineChange(mEvent) { @@ -180,43 +199,84 @@ function parseTimelineChange(mEvent) { const userName = getUsername(mEvent.getStateKey()); switch (content.membership) { - case 'invite': return makeReturnObj('invite', tJSXMsgs.invite(senderName, userName)); - case 'ban': return makeReturnObj('leave', tJSXMsgs.ban(senderName, userName, content.reason)); - case 'join': - if (prevContent.membership === 'join') { + case "invite": + return makeReturnObj("invite", tJSXMsgs.invite(senderName, userName)); + case "ban": + return makeReturnObj( + "leave", + tJSXMsgs.ban(senderName, userName, content.reason) + ); + case "join": + if (prevContent.membership === "join") { if (content.displayname !== prevContent.displayname) { - if (typeof content.displayname === 'undefined') return makeReturnObj('avatar', tJSXMsgs.nameRemoved(sender, prevContent.displayname)); - if (typeof prevContent.displayname === 'undefined') return makeReturnObj('avatar', tJSXMsgs.nameSets(sender, content.displayname)); - return makeReturnObj('avatar', tJSXMsgs.nameChanged(prevContent.displayname, content.displayname)); + if (typeof content.displayname === "undefined") + return makeReturnObj( + "avatar", + tJSXMsgs.nameRemoved(sender, prevContent.displayname) + ); + if (typeof prevContent.displayname === "undefined") + return makeReturnObj( + "avatar", + tJSXMsgs.nameSets(sender, content.displayname) + ); + return makeReturnObj( + "avatar", + tJSXMsgs.nameChanged(prevContent.displayname, content.displayname) + ); } if (content.avatar_url !== prevContent.avatar_url) { - if (typeof content.avatar_url === 'undefined') return makeReturnObj('avatar', tJSXMsgs.avatarRemoved(content.displayname)); - if (typeof prevContent.avatar_url === 'undefined') return makeReturnObj('avatar', tJSXMsgs.avatarSets(content.displayname)); - return makeReturnObj('avatar', tJSXMsgs.avatarChanged(content.displayname)); + if (typeof content.avatar_url === "undefined") + return makeReturnObj( + "avatar", + tJSXMsgs.avatarRemoved(content.displayname) + ); + if (typeof prevContent.avatar_url === "undefined") + return makeReturnObj( + "avatar", + tJSXMsgs.avatarSets(content.displayname) + ); + return makeReturnObj( + "avatar", + tJSXMsgs.avatarChanged(content.displayname) + ); } return null; } - return makeReturnObj('join', tJSXMsgs.join(senderName)); - case 'leave': + return makeReturnObj("join", tJSXMsgs.join(senderName)); + case "leave": if (sender === mEvent.getStateKey()) { switch (prevContent.membership) { - case 'invite': return makeReturnObj('invite-cancel', tJSXMsgs.rejectInvite(senderName)); - default: return makeReturnObj('leave', tJSXMsgs.leave(senderName, content.reason)); + case "invite": + return makeReturnObj( + "invite-cancel", + tJSXMsgs.rejectInvite(senderName) + ); + default: + return makeReturnObj( + "leave", + tJSXMsgs.leave(senderName, content.reason) + ); } } switch (prevContent.membership) { - case 'invite': return makeReturnObj('invite-cancel', tJSXMsgs.cancelInvite(senderName, userName)); - case 'ban': return makeReturnObj('other', tJSXMsgs.unban(senderName, userName)); + case "invite": + return makeReturnObj( + "invite-cancel", + tJSXMsgs.cancelInvite(senderName, userName) + ); + case "ban": + return makeReturnObj("other", tJSXMsgs.unban(senderName, userName)); // sender is not target and made the target leave, // if not from invite/ban then this is a kick - default: return makeReturnObj('leave', tJSXMsgs.kick(senderName, userName, content.reason)); + default: + return makeReturnObj( + "leave", + tJSXMsgs.kick(senderName, userName, content.reason) + ); } - default: return null; + default: + return null; } } -export { - getTimelineJSXMessages, - getUsersActionJsx, - parseTimelineChange, -}; +export { getTimelineJSXMessages, getUsersActionJsx, parseTimelineChange }; diff --git a/src/app/organisms/search/Search.jsx b/src/app/organisms/search/Search.jsx index 64c898bf..d0b28c24 100644 --- a/src/app/organisms/search/Search.jsx +++ b/src/app/organisms/search/Search.jsx @@ -1,24 +1,24 @@ -import React, { useState, useEffect, useRef } from 'react'; -import './Search.scss'; +import React, { useState, useEffect, useRef } from "react"; +import "./Search.scss"; -import initMatrix from '../../../client/initMatrix'; -import cons from '../../../client/state/cons'; -import navigation from '../../../client/state/navigation'; -import AsyncSearch from '../../../util/AsyncSearch'; -import { selectRoom, selectTab } from '../../../client/action/navigation'; -import { joinRuleToIconSrc } from '../../../util/matrixUtil'; -import { roomIdByActivity } from '../../../util/sort'; +import initMatrix from "../../../client/initMatrix"; +import cons from "../../../client/state/cons"; +import navigation from "../../../client/state/navigation"; +import AsyncSearch from "../../../util/AsyncSearch"; +import { selectRoom, selectTab } from "../../../client/action/navigation"; +import { joinRuleToIconSrc } from "../../../util/matrixUtil"; +import { roomIdByActivity } from "../../../util/sort"; -import Text from '../../atoms/text/Text'; -import RawIcon from '../../atoms/system-icons/RawIcon'; -import IconButton from '../../atoms/button/IconButton'; -import Input from '../../atoms/input/Input'; -import RawModal from '../../atoms/modal/RawModal'; -import ScrollView from '../../atoms/scroll/ScrollView'; -import RoomSelector from '../../molecules/room-selector/RoomSelector'; +import Text from "../../atoms/text/Text"; +import RawIcon from "../../atoms/system-icons/RawIcon"; +import IconButton from "../../atoms/button/IconButton"; +import Input from "../../atoms/input/Input"; +import RawModal from "../../atoms/modal/RawModal"; +import ScrollView from "../../atoms/scroll/ScrollView"; +import RoomSelector from "../../molecules/room-selector/RoomSelector"; -import SearchIC from '../../../../public/res/ic/outlined/search.svg'; -import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; +import SearchIC from "../../../../public/res/ic/outlined/search.svg"; +import CrossIC from "../../../../public/res/ic/outlined/cross.svg"; function useVisiblityToggle(setResult) { const [isOpen, setIsOpen] = useState(false); @@ -33,7 +33,10 @@ function useVisiblityToggle(setResult) { }; navigation.on(cons.events.navigation.SEARCH_OPENED, handleSearchOpen); return () => { - navigation.removeListener(cons.events.navigation.SEARCH_OPENED, handleSearchOpen); + navigation.removeListener( + cons.events.navigation.SEARCH_OPENED, + handleSearchOpen + ); }; }, []); @@ -56,21 +59,23 @@ function mapRoomIds(roomIds) { const room = mx.getRoom(roomId); const parentSet = roomIdToParents.get(roomId); const parentNames = parentSet ? [] : undefined; - parentSet?.forEach((parentId) => parentNames.push(mx.getRoom(parentId).name)); + parentSet?.forEach((parentId) => + parentNames.push(mx.getRoom(parentId).name) + ); - const parents = parentNames ? parentNames.join(', ') : null; + const parents = parentNames ? parentNames.join(", ") : null; - let type = 'room'; - if (room.isSpaceRoom()) type = 'space'; - else if (directs.has(roomId)) type = 'direct'; + let type = "room"; + if (room.isSpaceRoom()) type = "space"; + else if (directs.has(roomId)) type = "direct"; - return ({ + return { type, name: room.name, parents, roomId, room, - }); + }; }); } @@ -100,8 +105,8 @@ function Search() { let ids = null; if (prefix) { - if (prefix === '#') ids = [...rooms]; - else if (prefix === '@') ids = [...directs]; + if (prefix === "#") ids = [...rooms]; + else if (prefix === "@") ids = [...directs]; else ids = [...spaces]; } else { ids = [...rooms].concat([...directs], [...spaces]); @@ -109,7 +114,7 @@ function Search() { ids.sort(roomIdByActivity); const mappedIds = mapRoomIds(ids); - asyncSearch.setup(mappedIds, { keys: 'name', isContain: true, limit: 20 }); + asyncSearch.setup(mappedIds, { keys: "name", isContain: true, limit: 20 }); if (prefix) handleSearchResults(mappedIds, prefix); else asyncSearch.search(term); }; @@ -124,7 +129,7 @@ function Search() { loadRecentRooms(); asyncSearch.on(asyncSearch.RESULT_SENT, handleSearchResults); - if (typeof result.term === 'string') { + if (typeof result.term === "string") { generateResults(result.term); searchRef.current.value = result.term; } @@ -148,14 +153,14 @@ function Search() { const { value } = searchRef.current; if (value.length === 0) requestClose(); else { - searchRef.current.value = ''; + searchRef.current.value = ""; searchRef.current.focus(); loadRecentRooms(); } }; const openItem = (roomId, type) => { - if (type === 'space') selectTab(roomId); + if (type === "space") selectTab(roomId); else selectRoom(roomId); requestClose(); }; @@ -172,10 +177,16 @@ function Search() { const renderRoomSelector = (item) => { let imageSrc = null; let iconSrc = null; - if (item.type === 'direct') { - imageSrc = item.room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null; + if (item.type === "direct") { + imageSrc = + item.room + .getAvatarFallbackMember() + ?.getAvatarUrl(mx.baseUrl, 24, 24, "crop") || null; } else { - iconSrc = joinRuleToIconSrc(item.room.getJoinRule(), item.type === 'space'); + iconSrc = joinRuleToIconSrc( + item.room.getJoinRule(), + item.type === "space" + ); } return ( @@ -204,24 +215,39 @@ function Search() { size="small" >
-
{ e.preventDefault(); openFirstResult(); }}> + { + e.preventDefault(); + openFirstResult(); + }} + > - +
- { Array.isArray(result?.chunk) && result.chunk.map(renderRoomSelector) } + {Array.isArray(result?.chunk) && + result.chunk.map(renderRoomSelector)}
- Type # for rooms, @ for DMs and * for spaces. Hotkey: Ctrl + k + + Type # for rooms, @ for DMs and * for spaces. Hotkey: Ctrl + k +
diff --git a/src/app/organisms/search/Search.scss b/src/app/organisms/search/Search.scss index 3612d742..486b1963 100644 --- a/src/app/organisms/search/Search.scss +++ b/src/app/organisms/search/Search.scss @@ -1,4 +1,4 @@ -@use '../../partials/dir'; +@use "../../partials/dir"; .search-dialog__modal { --modal-height: 380px; @@ -58,23 +58,30 @@ display: inline-block; width: 100%; height: 8px; - background-image: linear-gradient(to bottom, var(--bg-surface), var(--bg-surface-transparent)); + background-image: linear-gradient( + to bottom, + var(--bg-surface), + var(--bg-surface-transparent) + ); } &::after { top: unset; bottom: 0; - background-image: linear-gradient(to bottom, var(--bg-surface-transparent), var(--bg-surface)); + background-image: linear-gradient( + to bottom, + var(--bg-surface-transparent), + var(--bg-surface) + ); } } &__content { padding: var(--sp-extra-tight); - @include dir.side(padding, var(--sp-normal), var(--sp-extra-tight)); + @include dir.side(padding, var(--sp-normal), var(--sp-extra-tight)); } &__footer { padding: var(--sp-tight) var(--sp-normal); text-align: center; } - -} \ No newline at end of file +} diff --git a/src/app/organisms/settings/AuthRequest.jsx b/src/app/organisms/settings/AuthRequest.jsx index ca07c2a2..a533d1a1 100644 --- a/src/app/organisms/settings/AuthRequest.jsx +++ b/src/app/organisms/settings/AuthRequest.jsx @@ -1,23 +1,23 @@ -import React, { useState } from 'react'; -import PropTypes from 'prop-types'; -import './AuthRequest.scss'; +import React, { useState } from "react"; +import PropTypes from "prop-types"; +import "./AuthRequest.scss"; -import initMatrix from '../../../client/initMatrix'; -import { openReusableDialog } from '../../../client/action/navigation'; +import initMatrix from "../../../client/initMatrix"; +import { openReusableDialog } from "../../../client/action/navigation"; -import Text from '../../atoms/text/Text'; -import Button from '../../atoms/button/Button'; -import Input from '../../atoms/input/Input'; -import Spinner from '../../atoms/spinner/Spinner'; +import Text from "../../atoms/text/Text"; +import Button from "../../atoms/button/Button"; +import Input from "../../atoms/input/Input"; +import Spinner from "../../atoms/spinner/Spinner"; -import { useStore } from '../../hooks/useStore'; +import { useStore } from "../../hooks/useStore"; let lastUsedPassword; const getAuthId = (password) => ({ - type: 'm.login.password', + type: "m.login.password", password, identifier: { - type: 'm.id.user', + type: "m.id.user", user: initMatrix.matrixClient.getUserId(), }, }); @@ -30,7 +30,7 @@ function AuthRequest({ onComplete, makeRequest }) { mountStore.setItem(true); e.preventDefault(); const password = e.target.password.value; - if (password.trim() === '') return; + if (password.trim() === "") return; try { setStatus({ ongoing: true }); await makeRequest(getAuthId(password)); @@ -40,11 +40,11 @@ function AuthRequest({ onComplete, makeRequest }) { } catch (err) { lastUsedPassword = undefined; if (!mountStore.getItem()) return; - if (err.errcode === 'M_FORBIDDEN') { - setStatus({ error: 'Wrong password. Please enter correct password.' }); + if (err.errcode === "M_FORBIDDEN") { + setStatus({ error: "Wrong password. Please enter correct password." }); return; } - setStatus({ error: 'Request failed!' }); + setStatus({ error: "Request failed!" }); } }; @@ -64,7 +64,11 @@ function AuthRequest({ onComplete, makeRequest }) { /> {status.ongoing && } {status.error && {status.error}} - {(status === false || status.error) && } + {(status === false || status.error) && ( + + )}
); @@ -89,13 +93,17 @@ export const authRequest = async (title, makeRequest) => { if (e.httpStatus !== 401 || e.data?.flows === undefined) return false; const { flows } = e.data; - const canUsePassword = flows.find((f) => f.stages.includes('m.login.password')); + const canUsePassword = flows.find((f) => + f.stages.includes("m.login.password") + ); if (!canUsePassword) return false; return new Promise((resolve) => { let isCompleted = false; openReusableDialog( - {title}, + + {title} + , (requestClose) => ( { @@ -108,7 +116,7 @@ export const authRequest = async (title, makeRequest) => { ), () => { if (!isCompleted) resolve(false); - }, + } ); }); } diff --git a/src/app/organisms/settings/AuthRequest.scss b/src/app/organisms/settings/AuthRequest.scss index 35e95bf2..cbc8f5d5 100644 --- a/src/app/organisms/settings/AuthRequest.scss +++ b/src/app/organisms/settings/AuthRequest.scss @@ -9,4 +9,4 @@ color: var(--tc-danger-high); margin-top: var(--sp-ultra-tight) !important; } -} \ No newline at end of file +} diff --git a/src/app/organisms/settings/CrossSigning.jsx b/src/app/organisms/settings/CrossSigning.jsx index 563e3152..4080ccbb 100644 --- a/src/app/organisms/settings/CrossSigning.jsx +++ b/src/app/organisms/settings/CrossSigning.jsx @@ -1,45 +1,49 @@ /* eslint-disable react/jsx-one-expression-per-line */ -import React, { useState } from 'react'; -import './CrossSigning.scss'; -import FileSaver from 'file-saver'; -import { Formik } from 'formik'; -import { twemojify } from '../../../util/twemojify'; +import React, { useState } from "react"; +import "./CrossSigning.scss"; +import FileSaver from "file-saver"; +import { Formik } from "formik"; +import { twemojify } from "../../../util/twemojify"; -import initMatrix from '../../../client/initMatrix'; -import { openReusableDialog } from '../../../client/action/navigation'; -import { copyToClipboard } from '../../../util/common'; -import { clearSecretStorageKeys } from '../../../client/state/secretStorageKeys'; +import initMatrix from "../../../client/initMatrix"; +import { openReusableDialog } from "../../../client/action/navigation"; +import { copyToClipboard } from "../../../util/common"; +import { clearSecretStorageKeys } from "../../../client/state/secretStorageKeys"; -import Text from '../../atoms/text/Text'; -import Button from '../../atoms/button/Button'; -import Input from '../../atoms/input/Input'; -import Spinner from '../../atoms/spinner/Spinner'; -import SettingTile from '../../molecules/setting-tile/SettingTile'; +import Text from "../../atoms/text/Text"; +import Button from "../../atoms/button/Button"; +import Input from "../../atoms/input/Input"; +import Spinner from "../../atoms/spinner/Spinner"; +import SettingTile from "../../molecules/setting-tile/SettingTile"; -import { authRequest } from './AuthRequest'; -import { useCrossSigningStatus } from '../../hooks/useCrossSigningStatus'; +import { authRequest } from "./AuthRequest"; +import { useCrossSigningStatus } from "../../hooks/useCrossSigningStatus"; const failedDialog = () => { const renderFailure = (requestClose) => (
- {twemojify('❌')} - Failed to setup cross signing. Please try again. + {twemojify("❌")} + + Failed to setup cross signing. Please try again. +
); openReusableDialog( - Setup cross signing, - renderFailure, + + Setup cross signing + , + renderFailure ); }; const securityKeyDialog = (key) => { const downloadKey = () => { const blob = new Blob([key.encodedPrivateKey], { - type: 'text/plain;charset=us-ascii', + type: "text/plain;charset=us-ascii", }); - FileSaver.saveAs(blob, 'security-key.txt'); + FileSaver.saveAs(blob, "security-key.txt"); }; const copyKey = () => { copyToClipboard(key.encodedPrivateKey); @@ -48,11 +52,11 @@ const securityKeyDialog = (key) => { const renderSecurityKey = () => (
Please save this security key somewhere safe. - - {key.encodedPrivateKey} - + {key.encodedPrivateKey}
- +
@@ -62,19 +66,23 @@ const securityKeyDialog = (key) => { downloadKey(); openReusableDialog( - Security Key, - () => renderSecurityKey(), + + Security Key + , + () => renderSecurityKey() ); }; function CrossSigningSetup() { - const initialValues = { phrase: '', confirmPhrase: '' }; + const initialValues = { phrase: "", confirmPhrase: "" }; const [genWithPhrase, setGenWithPhrase] = useState(undefined); const setup = async (securityPhrase = undefined) => { const mx = initMatrix.matrixClient; - setGenWithPhrase(typeof securityPhrase === 'string'); - const recoveryKey = await mx.createRecoveryKeyFromPassphrase(securityPhrase); + setGenWithPhrase(typeof securityPhrase === "string"); + const recoveryKey = await mx.createRecoveryKeyFromPassphrase( + securityPhrase + ); clearSecretStorageKeys(); await mx.bootstrapSecretStorage({ @@ -84,7 +92,7 @@ function CrossSigningSetup() { }); const authUploadDeviceSigningKeys = async (makeRequest) => { - const isDone = await authRequest('Setup cross signing', async (auth) => { + const isDone = await authRequest("Setup cross signing", async (auth) => { await makeRequest(auth); }); setTimeout(() => { @@ -101,18 +109,21 @@ function CrossSigningSetup() { const validator = (values) => { const errors = {}; - if (values.phrase === '12345678') { - errors.phrase = 'How about 87654321 ?'; + if (values.phrase === "12345678") { + errors.phrase = "How about 87654321 ?"; } - if (values.phrase === '87654321') { - errors.phrase = 'Your are playing with 🔥'; + if (values.phrase === "87654321") { + errors.phrase = "Your are playing with 🔥"; } const PHRASE_REGEX = /^([^\s]){8,127}$/; if (values.phrase.length > 0 && !PHRASE_REGEX.test(values.phrase)) { - errors.phrase = 'Phrase must contain 8-127 characters with no space.'; + errors.phrase = "Phrase must contain 8-127 characters with no space."; } - if (values.confirmPhrase.length > 0 && values.confirmPhrase !== values.phrase) { - errors.confirmPhrase = 'Phrase don\'t match.'; + if ( + values.confirmPhrase.length > 0 && + values.confirmPhrase !== values.phrase + ) { + errors.confirmPhrase = "Phrase don't match."; } return errors; }; @@ -121,10 +132,18 @@ function CrossSigningSetup() {
- We will generate a Security Key, - which you can use to manage messages backup and session verification. + We will generate a Security Key, which you can use to manage + messages backup and session verification. - {genWithPhrase !== false && } + {genWithPhrase !== false && ( + + )} {genWithPhrase === false && }
OR @@ -133,9 +152,7 @@ function CrossSigningSetup() { onSubmit={(values) => setup(values.phrase)} validate={validator} > - {({ - values, errors, handleChange, handleSubmit, - }) => ( + {({ values, errors, handleChange, handleSubmit }) => (
Alternatively you can also set a Security Phrase - so you don't have to remember long Security Key, - and optionally save the Key as backup. + so you don't have to remember long Security Key, and optionally + save the Key as backup. - {errors.phrase && {errors.phrase}} + {errors.phrase && ( + + {errors.phrase} + + )} - {errors.confirmPhrase && {errors.confirmPhrase}} - {genWithPhrase !== true && } + {errors.confirmPhrase && ( + + {errors.confirmPhrase} + + )} + {genWithPhrase !== true && ( + + )} {genWithPhrase === true && } )} @@ -177,31 +210,37 @@ function CrossSigningSetup() { const setupDialog = () => { openReusableDialog( - Setup cross signing, - () => , + + Setup cross signing + , + () => ); }; function CrossSigningReset() { return (
- {twemojify('✋🧑‍🚒🤚')} + {twemojify("✋🧑‍🚒🤚")} Resetting cross-signing keys is permanent. - Anyone you have verified with will see security alerts and your message backup will be lost. - You almost certainly do not want to do this, - unless you have lost Security Key or Phrase and - every session you can cross-sign from. + Anyone you have verified with will see security alerts and your message + backup will be lost. You almost certainly do not want to do this, unless + you have lost Security Key or Phrase and every session you + can cross-sign from. - +
); } const resetDialog = () => { openReusableDialog( - Reset cross signing, - () => , + + Reset cross signing + , + () => ); }; @@ -210,12 +249,23 @@ function CrossSignin() { return ( Setup to verify and keep track of all your sessions. Also required to backup encrypted message.} - options={( - isCSEnabled - ? - : - )} + content={ + + Setup to verify and keep track of all your sessions. Also required to + backup encrypted message. + + } + options={ + isCSEnabled ? ( + + ) : ( + + ) + } /> ); } diff --git a/src/app/organisms/settings/CrossSigning.scss b/src/app/organisms/settings/CrossSigning.scss index b4b606d0..6a1a26c3 100644 --- a/src/app/organisms/settings/CrossSigning.scss +++ b/src/app/organisms/settings/CrossSigning.scss @@ -17,11 +17,11 @@ margin: var(--sp-tight) 0; display: flex; align-items: center; - + &::before, &::after { flex: 1; - content: ''; + content: ""; margin: var(--sp-tight) 0; border-bottom: 1px solid var(--bg-surface-border); } @@ -52,4 +52,4 @@ & > .text { padding-bottom: var(--sp-normal); } -} \ No newline at end of file +} diff --git a/src/app/organisms/settings/DeviceManage.jsx b/src/app/organisms/settings/DeviceManage.jsx index 4825e238..0c687852 100644 --- a/src/app/organisms/settings/DeviceManage.jsx +++ b/src/app/organisms/settings/DeviceManage.jsx @@ -1,65 +1,74 @@ -import React, { useState, useEffect } from 'react'; -import './DeviceManage.scss'; -import dateFormat from 'dateformat'; +import React, { useState, useEffect } from "react"; +import "./DeviceManage.scss"; +import dateFormat from "dateformat"; -import initMatrix from '../../../client/initMatrix'; -import { isCrossVerified } from '../../../util/matrixUtil'; -import { openReusableDialog, openEmojiVerification } from '../../../client/action/navigation'; +import initMatrix from "../../../client/initMatrix"; +import { isCrossVerified } from "../../../util/matrixUtil"; +import { + openReusableDialog, + openEmojiVerification, +} from "../../../client/action/navigation"; -import Text from '../../atoms/text/Text'; -import Button from '../../atoms/button/Button'; -import IconButton from '../../atoms/button/IconButton'; -import Input from '../../atoms/input/Input'; -import { MenuHeader } from '../../atoms/context-menu/ContextMenu'; -import InfoCard from '../../atoms/card/InfoCard'; -import Spinner from '../../atoms/spinner/Spinner'; -import SettingTile from '../../molecules/setting-tile/SettingTile'; +import Text from "../../atoms/text/Text"; +import Button from "../../atoms/button/Button"; +import IconButton from "../../atoms/button/IconButton"; +import Input from "../../atoms/input/Input"; +import { MenuHeader } from "../../atoms/context-menu/ContextMenu"; +import InfoCard from "../../atoms/card/InfoCard"; +import Spinner from "../../atoms/spinner/Spinner"; +import SettingTile from "../../molecules/setting-tile/SettingTile"; -import PencilIC from '../../../../public/res/ic/outlined/pencil.svg'; -import BinIC from '../../../../public/res/ic/outlined/bin.svg'; -import InfoIC from '../../../../public/res/ic/outlined/info.svg'; +import PencilIC from "../../../../public/res/ic/outlined/pencil.svg"; +import BinIC from "../../../../public/res/ic/outlined/bin.svg"; +import InfoIC from "../../../../public/res/ic/outlined/info.svg"; -import { authRequest } from './AuthRequest'; -import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog'; +import { authRequest } from "./AuthRequest"; +import { confirmDialog } from "../../molecules/confirm-dialog/ConfirmDialog"; -import { useStore } from '../../hooks/useStore'; -import { useDeviceList } from '../../hooks/useDeviceList'; -import { useCrossSigningStatus } from '../../hooks/useCrossSigningStatus'; -import { accessSecretStorage } from './SecretStorageAccess'; +import { useStore } from "../../hooks/useStore"; +import { useDeviceList } from "../../hooks/useDeviceList"; +import { useCrossSigningStatus } from "../../hooks/useCrossSigningStatus"; +import { accessSecretStorage } from "./SecretStorageAccess"; -const promptDeviceName = async (deviceName) => new Promise((resolve) => { - let isCompleted = false; +const promptDeviceName = async (deviceName) => + new Promise((resolve) => { + let isCompleted = false; - const renderContent = (onComplete) => { - const handleSubmit = (e) => { - e.preventDefault(); - const name = e.target.session.value; - if (typeof name !== 'string') onComplete(null); - onComplete(name); + const renderContent = (onComplete) => { + const handleSubmit = (e) => { + e.preventDefault(); + const name = e.target.session.value; + if (typeof name !== "string") onComplete(null); + onComplete(name); + }; + return ( +
+ +
+ + +
+
+ ); }; - return ( -
- -
- - -
-
- ); - }; - openReusableDialog( - Edit session name, - (requestClose) => renderContent((name) => { - isCompleted = true; - resolve(name); - requestClose(); - }), - () => { - if (!isCompleted) resolve(null); - }, - ); -}); + openReusableDialog( + + Edit session name + , + (requestClose) => + renderContent((name) => { + isCompleted = true; + resolve(name); + requestClose(); + }), + () => { + if (!isCompleted) resolve(null); + } + ); + }); function DeviceManage() { const TRUNCATED_COUNT = 4; @@ -99,7 +108,7 @@ function DeviceManage() { const handleRename = async (device) => { const newName = await promptDeviceName(device.display_name); - if (newName === null || newName.trim() === '') return; + if (newName === null || newName.trim() === "") return; if (newName.trim() === device.display_name) return; addToProcessing(device); try { @@ -116,8 +125,8 @@ function DeviceManage() { const isConfirmed = await confirmDialog( `Logout ${device.display_name}`, `You are about to logout "${device.display_name}" session.`, - 'Logout', - 'danger', + "Logout", + "danger" ); if (!isConfirmed) return; addToProcessing(device); @@ -130,7 +139,7 @@ function DeviceManage() { }; const verifyWithKey = async (device) => { - const keyData = await accessSecretStorage('Session verification'); + const keyData = await accessSecretStorage("Session verification"); if (!keyData) return; addToProcessing(device); await mx.checkOwnCrossSigningTrust(); @@ -160,42 +169,72 @@ function DeviceManage() { return ( + title={ + {displayName} - {`${displayName ? ' — ' : ''}${deviceId}`} - {isCurrentDevice && Current} + {`${ + displayName ? " — " : "" + }${deviceId}`} + {isCurrentDevice && ( + + Current + + )} - )} - options={ - processing.includes(deviceId) - ? - : ( - <> - {(isCSEnabled && canVerify) && } - handleRename(device)} src={PencilIC} tooltip="Rename" /> - handleRemove(device)} src={BinIC} tooltip="Remove session" /> - - ) } - content={( + options={ + processing.includes(deviceId) ? ( + + ) : ( + <> + {isCSEnabled && canVerify && ( + + )} + handleRename(device)} + src={PencilIC} + tooltip="Rename" + /> + handleRemove(device)} + src={BinIC} + tooltip="Remove session" + /> + + ) + } + content={ <> {lastTS && ( Last activity - - {dateFormat(new Date(lastTS), ' hh:MM TT, dd/mm/yyyy')} + + {dateFormat(new Date(lastTS), " hh:MM TT, dd/mm/yyyy")} - {lastIP ? ` at ${lastIP}` : ''} + {lastIP ? ` at ${lastIP}` : ""} )} {isCurrentDevice && ( - - {`Session Key: ${initMatrix.matrixClient.getDeviceEd25519Key().match(/.{1,4}/g).join(' ')}`} + + {`Session Key: ${initMatrix.matrixClient + .getDeviceEd25519Key() + .match(/.{1,4}/g) + .join(" ")}`} )} - )} + } /> ); }; @@ -203,22 +242,24 @@ function DeviceManage() { const unverified = []; const verified = []; const noEncryption = []; - deviceList.sort((a, b) => b.last_seen_ts - a.last_seen_ts).forEach((device) => { - const isVerified = isCrossVerified(device.device_id); - if (isVerified === true) { - verified.push(device); - } else if (isVerified === false) { - unverified.push(device); - } else { - noEncryption.push(device); - } - }); + deviceList + .sort((a, b) => b.last_seen_ts - a.last_seen_ts) + .forEach((device) => { + const isVerified = isCrossVerified(device.device_id); + if (isVerified === true) { + verified.push(device); + } else if (isVerified === false) { + unverified.push(device); + } else { + noEncryption.push(device); + } + }); return (
Unverified sessions {!isCSEnabled && ( -
+
)} - { - unverified.length > 0 - ? unverified.map((device) => renderDevice(device, false)) - : No unverified sessions - } + {unverified.length > 0 ? ( + unverified.map((device) => renderDevice(device, false)) + ) : ( + No unverified sessions + )}
{noEncryption.length > 0 && ( -
- Sessions without encryption support - {noEncryption.map((device) => renderDevice(device, null))} -
+
+ Sessions without encryption support + {noEncryption.map((device) => renderDevice(device, null))} +
)}
Verified sessions - { - verified.length > 0 - ? verified.map((device, index) => { - if (truncated && index >= TRUNCATED_COUNT) return null; - return renderDevice(device, true); - }) - : No verified sessions - } - { verified.length > TRUNCATED_COUNT && ( - )} - { deviceList.length > 0 && ( - Session names are visible to everyone, so do not put any private info here. + {deviceList.length > 0 && ( + + Session names are visible to everyone, so do not put any private + info here. + )}
diff --git a/src/app/organisms/settings/DeviceManage.scss b/src/app/organisms/settings/DeviceManage.scss index 5116cda8..cc48873f 100644 --- a/src/app/organisms/settings/DeviceManage.scss +++ b/src/app/organisms/settings/DeviceManage.scss @@ -1,4 +1,4 @@ -@use '../../partials/flex'; +@use "../../partials/flex"; .device-manage { &__loading { @@ -13,7 +13,7 @@ margin: var(--sp-normal); } & .setting-tile:last-of-type { - border-bottom: none; + border-bottom: none; } & .setting-tile__options { display: flex; @@ -43,4 +43,4 @@ gap: var(--sp-normal); } } -} \ No newline at end of file +} diff --git a/src/app/organisms/settings/KeyBackup.jsx b/src/app/organisms/settings/KeyBackup.jsx index 75f032bc..fb942bc4 100644 --- a/src/app/organisms/settings/KeyBackup.jsx +++ b/src/app/organisms/settings/KeyBackup.jsx @@ -1,28 +1,28 @@ /* eslint-disable react/prop-types */ -import React, { useState, useEffect } from 'react'; -import PropTypes from 'prop-types'; -import './KeyBackup.scss'; -import { twemojify } from '../../../util/twemojify'; +import React, { useState, useEffect } from "react"; +import PropTypes from "prop-types"; +import "./KeyBackup.scss"; +import { twemojify } from "../../../util/twemojify"; -import initMatrix from '../../../client/initMatrix'; -import { openReusableDialog } from '../../../client/action/navigation'; -import { deletePrivateKey } from '../../../client/state/secretStorageKeys'; +import initMatrix from "../../../client/initMatrix"; +import { openReusableDialog } from "../../../client/action/navigation"; +import { deletePrivateKey } from "../../../client/state/secretStorageKeys"; -import Text from '../../atoms/text/Text'; -import Button from '../../atoms/button/Button'; -import IconButton from '../../atoms/button/IconButton'; -import Spinner from '../../atoms/spinner/Spinner'; -import InfoCard from '../../atoms/card/InfoCard'; -import SettingTile from '../../molecules/setting-tile/SettingTile'; +import Text from "../../atoms/text/Text"; +import Button from "../../atoms/button/Button"; +import IconButton from "../../atoms/button/IconButton"; +import Spinner from "../../atoms/spinner/Spinner"; +import InfoCard from "../../atoms/card/InfoCard"; +import SettingTile from "../../molecules/setting-tile/SettingTile"; -import { accessSecretStorage } from './SecretStorageAccess'; +import { accessSecretStorage } from "./SecretStorageAccess"; -import InfoIC from '../../../../public/res/ic/outlined/info.svg'; -import BinIC from '../../../../public/res/ic/outlined/bin.svg'; -import DownloadIC from '../../../../public/res/ic/outlined/download.svg'; +import InfoIC from "../../../../public/res/ic/outlined/info.svg"; +import BinIC from "../../../../public/res/ic/outlined/bin.svg"; +import DownloadIC from "../../../../public/res/ic/outlined/download.svg"; -import { useStore } from '../../hooks/useStore'; -import { useCrossSigningStatus } from '../../hooks/useCrossSigningStatus'; +import { useStore } from "../../hooks/useStore"; +import { useCrossSigningStatus } from "../../hooks/useCrossSigningStatus"; function CreateKeyBackupDialog({ keyData }) { const [done, setDone] = useState(false); @@ -34,10 +34,9 @@ function CreateKeyBackupDialog({ keyData }) { let info; try { - info = await mx.prepareKeyBackupVersion( - null, - { secureSecretStorage: true }, - ); + info = await mx.prepareKeyBackupVersion(null, { + secureSecretStorage: true, + }); info = await mx.createKeyBackupVersion(info); await mx.scheduleAllGroupSessionsForBackup(); if (!mountStore.getItem()) return; @@ -65,7 +64,7 @@ function CreateKeyBackupDialog({ keyData }) { )} {done === true && ( <> - {twemojify('✅')} + {twemojify("✅")} Successfully created backup )} @@ -99,7 +98,9 @@ function RestoreKeyBackupDialog({ keyData }) { meBreath = true; }, 200); - setStatus({ message: `Restoring backup keys... (${progress.successes}/${progress.total})` }); + setStatus({ + message: `Restoring backup keys... (${progress.successes}/${progress.total})`, + }); }; try { @@ -108,17 +109,24 @@ function RestoreKeyBackupDialog({ keyData }) { backupInfo, undefined, undefined, - { progressCallback }, + { + progressCallback, + } ); if (!mountStore.getItem()) return; - setStatus({ done: `Successfully restored backup keys (${info.imported}/${info.total}).` }); + setStatus({ + done: `Successfully restored backup keys (${info.imported}/${info.total}).`, + }); } catch (e) { if (!mountStore.getItem()) return; - if (e.errcode === 'RESTORE_BACKUP_ERROR_BAD_KEY') { + if (e.errcode === "RESTORE_BACKUP_ERROR_BAD_KEY") { deletePrivateKey(keyData.keyId); - setStatus({ error: 'Failed to restore backup. Key is invalid!', errorCode: 'BAD_KEY' }); + setStatus({ + error: "Failed to restore backup. Key is invalid!", + errorCode: "BAD_KEY", + }); } else { - setStatus({ error: 'Failed to restore backup.', errCode: 'UNKNOWN' }); + setStatus({ error: "Failed to restore backup.", errCode: "UNKNOWN" }); } } }; @@ -133,12 +141,12 @@ function RestoreKeyBackupDialog({ keyData }) { {(status === false || status.message) && (
- {status.message ?? 'Restoring backup keys...'} + {status.message ?? "Restoring backup keys..."}
)} {status.done && ( <> - {twemojify('✅')} + {twemojify("✅")} {status.done} )} @@ -176,14 +184,16 @@ function DeleteKeyBackupDialog({ requestClose }) { return (
- {twemojify('🗑')} + {twemojify("🗑")} Deleting key backup is permanent. All encrypted messages keys stored on server will be deleted. - { - isDeleting - ? - : - } + {isDeleting ? ( + + ) : ( + + )}
); } @@ -208,57 +218,78 @@ function KeyBackup() { fetchKeyBackupVersion(); const handleAccountData = (event) => { - if (event.getType() === 'm.megolm_backup.v1') { + if (event.getType() === "m.megolm_backup.v1") { fetchKeyBackupVersion(); } }; - mx.on('accountData', handleAccountData); + mx.on("accountData", handleAccountData); return () => { - mx.removeListener('accountData', handleAccountData); + mx.removeListener("accountData", handleAccountData); }; }, [isCSEnabled]); const openCreateKeyBackup = async () => { - const keyData = await accessSecretStorage('Create Key Backup'); + const keyData = await accessSecretStorage("Create Key Backup"); if (keyData === null) return; openReusableDialog( - Create Key Backup, + + Create Key Backup + , () => , - () => fetchKeyBackupVersion(), + () => fetchKeyBackupVersion() ); }; const openRestoreKeyBackup = async () => { - const keyData = await accessSecretStorage('Restore Key Backup'); + const keyData = await accessSecretStorage("Restore Key Backup"); if (keyData === null) return; openReusableDialog( - Restore Key Backup, - () => , + + Restore Key Backup + , + () => ); }; - const openDeleteKeyBackup = () => openReusableDialog( - Delete Key Backup, - (requestClose) => ( - { - if (isDone) setKeyBackup(null); - requestClose(); - }} - /> - ), - ); + const openDeleteKeyBackup = () => + openReusableDialog( + + Delete Key Backup + , + (requestClose) => ( + { + if (isDone) setKeyBackup(null); + requestClose(); + }} + /> + ) + ); const renderOptions = () => { if (keyBackup === undefined) return ; - if (keyBackup === null) return ; + if (keyBackup === null) + return ( + + ); return ( <> - - + + ); }; @@ -266,12 +297,16 @@ function KeyBackup() { return ( - Online backup your encrypted messages keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Security Key. + + Online backup your encrypted messages keys with your account data in + case you lose access to your sessions. Your keys will be secured + with a unique Security Key. + {!isCSEnabled && ( )} - )} + } options={isCSEnabled ? renderOptions() : null} /> ); diff --git a/src/app/organisms/settings/KeyBackup.scss b/src/app/organisms/settings/KeyBackup.scss index 1f2b9b66..05927133 100644 --- a/src/app/organisms/settings/KeyBackup.scss +++ b/src/app/organisms/settings/KeyBackup.scss @@ -6,7 +6,7 @@ padding: var(--sp-normal) 0; display: flex; align-items: center; - + & > .text { margin: 0 var(--sp-normal); } @@ -24,4 +24,4 @@ & > .text { padding-bottom: var(--sp-normal); } -} \ No newline at end of file +} diff --git a/src/app/organisms/settings/SecretStorageAccess.jsx b/src/app/organisms/settings/SecretStorageAccess.jsx index e4fceb07..868c95f6 100644 --- a/src/app/organisms/settings/SecretStorageAccess.jsx +++ b/src/app/organisms/settings/SecretStorageAccess.jsx @@ -1,19 +1,23 @@ -import React, { useState } from 'react'; -import PropTypes from 'prop-types'; -import './SecretStorageAccess.scss'; -import { deriveKey } from 'matrix-js-sdk/lib/crypto/key_passphrase'; +import React, { useState } from "react"; +import PropTypes from "prop-types"; +import "./SecretStorageAccess.scss"; +import { deriveKey } from "matrix-js-sdk/lib/crypto/key_passphrase"; -import initMatrix from '../../../client/initMatrix'; -import { openReusableDialog } from '../../../client/action/navigation'; -import { getDefaultSSKey, getSSKeyInfo } from '../../../util/matrixUtil'; -import { storePrivateKey, hasPrivateKey, getPrivateKey } from '../../../client/state/secretStorageKeys'; +import initMatrix from "../../../client/initMatrix"; +import { openReusableDialog } from "../../../client/action/navigation"; +import { getDefaultSSKey, getSSKeyInfo } from "../../../util/matrixUtil"; +import { + storePrivateKey, + hasPrivateKey, + getPrivateKey, +} from "../../../client/state/secretStorageKeys"; -import Text from '../../atoms/text/Text'; -import Button from '../../atoms/button/Button'; -import Input from '../../atoms/input/Input'; -import Spinner from '../../atoms/spinner/Spinner'; +import Text from "../../atoms/text/Text"; +import Button from "../../atoms/button/Button"; +import Input from "../../atoms/input/Input"; +import Spinner from "../../atoms/spinner/Spinner"; -import { useStore } from '../../hooks/useStore'; +import { useStore } from "../../hooks/useStore"; function SecretStorageAccess({ onComplete }) { const mx = initMatrix.matrixClient; @@ -39,7 +43,7 @@ function SecretStorageAccess({ onComplete }) { if (!mountStore.getItem()) return; if (!isCorrect) { - setError(`Incorrect Security ${key ? 'Key' : 'Phrase'}`); + setError(`Incorrect Security ${key ? "Key" : "Phrase"}`); setProcess(false); return; } @@ -51,7 +55,7 @@ function SecretStorageAccess({ onComplete }) { }); } catch (e) { if (!mountStore.getItem()) return; - setError(`Incorrect Security ${key ? 'Key' : 'Phrase'}`); + setError(`Incorrect Security ${key ? "Key" : "Phrase"}`); setProcess(false); } }; @@ -59,7 +63,7 @@ function SecretStorageAccess({ onComplete }) { const handleForm = async (e) => { e.preventDefault(); const password = e.target.password.value; - if (password.trim() === '') return; + if (password.trim() === "") return; const data = {}; if (withPhrase) data.phrase = password; else data.key = password; @@ -76,7 +80,7 @@ function SecretStorageAccess({ onComplete }) {
{error}} {!process && (
- - {isPassphrase && } + + {isPassphrase && ( + + )}
)}
@@ -101,33 +111,36 @@ SecretStorageAccess.propTypes = { * @param {string} title Title of secret storage access dialog * @returns {Promise} resolve to keyData or null */ -export const accessSecretStorage = (title) => new Promise((resolve) => { - let isCompleted = false; - const defaultSSKey = getDefaultSSKey(); - if (hasPrivateKey(defaultSSKey)) { - resolve({ keyId: defaultSSKey, privateKey: getPrivateKey(defaultSSKey) }); - return; - } - const handleComplete = (keyData) => { - isCompleted = true; - storePrivateKey(keyData.keyId, keyData.privateKey); - resolve(keyData); - }; +export const accessSecretStorage = (title) => + new Promise((resolve) => { + let isCompleted = false; + const defaultSSKey = getDefaultSSKey(); + if (hasPrivateKey(defaultSSKey)) { + resolve({ keyId: defaultSSKey, privateKey: getPrivateKey(defaultSSKey) }); + return; + } + const handleComplete = (keyData) => { + isCompleted = true; + storePrivateKey(keyData.keyId, keyData.privateKey); + resolve(keyData); + }; - openReusableDialog( - {title}, - (requestClose) => ( - { - handleComplete(keyData); - requestClose(requestClose); - }} - /> - ), - () => { - if (!isCompleted) resolve(null); - }, - ); -}); + openReusableDialog( + + {title} + , + (requestClose) => ( + { + handleComplete(keyData); + requestClose(requestClose); + }} + /> + ), + () => { + if (!isCompleted) resolve(null); + } + ); + }); export default SecretStorageAccess; diff --git a/src/app/organisms/settings/SecretStorageAccess.scss b/src/app/organisms/settings/SecretStorageAccess.scss index a7c0a9f6..0ee6a7f0 100644 --- a/src/app/organisms/settings/SecretStorageAccess.scss +++ b/src/app/organisms/settings/SecretStorageAccess.scss @@ -17,4 +17,4 @@ & .donut-spinner { margin-top: var(--sp-normal); } -} \ No newline at end of file +} diff --git a/src/app/organisms/settings/Settings.jsx b/src/app/organisms/settings/Settings.jsx index a0869b61..8f65f6d0 100644 --- a/src/app/organisms/settings/Settings.jsx +++ b/src/app/organisms/settings/Settings.jsx @@ -1,48 +1,55 @@ -import React, { useState, useEffect } from 'react'; -import './Settings.scss'; +import React, { useState, useEffect } from "react"; +import "./Settings.scss"; -import initMatrix from '../../../client/initMatrix'; -import cons from '../../../client/state/cons'; -import settings from '../../../client/state/settings'; -import navigation from '../../../client/state/navigation'; +import initMatrix from "../../../client/initMatrix"; +import cons from "../../../client/state/cons"; +import settings from "../../../client/state/settings"; +import navigation from "../../../client/state/navigation"; import { - toggleSystemTheme, toggleMarkdown, toggleMembershipEvents, toggleNickAvatarEvents, - toggleNotifications, toggleNotificationSounds, -} from '../../../client/action/settings'; -import { usePermission } from '../../hooks/usePermission'; + toggleSystemTheme, + toggleMarkdown, + toggleMembershipEvents, + toggleNickAvatarEvents, + toggleNotifications, + toggleNotificationSounds, +} from "../../../client/action/settings"; +import { usePermission } from "../../hooks/usePermission"; -import Text from '../../atoms/text/Text'; -import IconButton from '../../atoms/button/IconButton'; -import Button from '../../atoms/button/Button'; -import Toggle from '../../atoms/button/Toggle'; -import Tabs from '../../atoms/tabs/Tabs'; -import { MenuHeader } from '../../atoms/context-menu/ContextMenu'; -import SegmentedControls from '../../atoms/segmented-controls/SegmentedControls'; +import Text from "../../atoms/text/Text"; +import IconButton from "../../atoms/button/IconButton"; +import Button from "../../atoms/button/Button"; +import Toggle from "../../atoms/button/Toggle"; +import Tabs from "../../atoms/tabs/Tabs"; +import { MenuHeader } from "../../atoms/context-menu/ContextMenu"; +import SegmentedControls from "../../atoms/segmented-controls/SegmentedControls"; -import PopupWindow from '../../molecules/popup-window/PopupWindow'; -import SettingTile from '../../molecules/setting-tile/SettingTile'; -import ImportE2ERoomKeys from '../../molecules/import-export-e2e-room-keys/ImportE2ERoomKeys'; -import ExportE2ERoomKeys from '../../molecules/import-export-e2e-room-keys/ExportE2ERoomKeys'; -import { ImagePackUser, ImagePackGlobal } from '../../molecules/image-pack/ImagePack'; -import GlobalNotification from '../../molecules/global-notification/GlobalNotification'; -import KeywordNotification from '../../molecules/global-notification/KeywordNotification'; -import IgnoreUserList from '../../molecules/global-notification/IgnoreUserList'; +import PopupWindow from "../../molecules/popup-window/PopupWindow"; +import SettingTile from "../../molecules/setting-tile/SettingTile"; +import ImportE2ERoomKeys from "../../molecules/import-export-e2e-room-keys/ImportE2ERoomKeys"; +import ExportE2ERoomKeys from "../../molecules/import-export-e2e-room-keys/ExportE2ERoomKeys"; +import { + ImagePackUser, + ImagePackGlobal, +} from "../../molecules/image-pack/ImagePack"; +import GlobalNotification from "../../molecules/global-notification/GlobalNotification"; +import KeywordNotification from "../../molecules/global-notification/KeywordNotification"; +import IgnoreUserList from "../../molecules/global-notification/IgnoreUserList"; -import ProfileEditor from '../profile-editor/ProfileEditor'; -import CrossSigning from './CrossSigning'; -import KeyBackup from './KeyBackup'; -import DeviceManage from './DeviceManage'; +import ProfileEditor from "../profile-editor/ProfileEditor"; +import CrossSigning from "./CrossSigning"; +import KeyBackup from "./KeyBackup"; +import DeviceManage from "./DeviceManage"; -import SunIC from '../../../../public/res/ic/outlined/sun.svg'; -import EmojiIC from '../../../../public/res/ic/outlined/emoji.svg'; -import LockIC from '../../../../public/res/ic/outlined/lock.svg'; -import BellIC from '../../../../public/res/ic/outlined/bell.svg'; -import InfoIC from '../../../../public/res/ic/outlined/info.svg'; -import PowerIC from '../../../../public/res/ic/outlined/power.svg'; -import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; +import SunIC from "../../../../public/res/ic/outlined/sun.svg"; +import EmojiIC from "../../../../public/res/ic/outlined/emoji.svg"; +import LockIC from "../../../../public/res/ic/outlined/lock.svg"; +import BellIC from "../../../../public/res/ic/outlined/bell.svg"; +import InfoIC from "../../../../public/res/ic/outlined/info.svg"; +import PowerIC from "../../../../public/res/ic/outlined/power.svg"; +import CrossIC from "../../../../public/res/ic/outlined/cross.svg"; -import CinnySVG from '../../../../public/res/svg/cinny.svg'; -import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog'; +import CinnySVG from "../../../../public/res/svg/cinny.svg"; +import { confirmDialog } from "../../molecules/confirm-dialog/ConfirmDialog"; function AppearanceSection() { const [, updateState] = useState({}); @@ -53,24 +60,31 @@ function AppearanceSection() { Theme { toggleSystemTheme(); updateState({}); }} + onToggle={() => { + toggleSystemTheme(); + updateState({}); + }} /> - )} - content={Use light or dark mode based on the system settings.} + } + content={ + + Use light or dark mode based on the system settings. + + } /> { if (settings.useSystemTheme) toggleSystemTheme(); @@ -78,40 +92,62 @@ function AppearanceSection() { updateState({}); }} /> - )} + } />
Room messages { toggleMarkdown(); updateState({}); }} + onToggle={() => { + toggleMarkdown(); + updateState({}); + }} /> - )} - content={Format messages with markdown syntax before sending.} + } + content={ + + Format messages with markdown syntax before sending. + + } /> { toggleMembershipEvents(); updateState({}); }} + onToggle={() => { + toggleMembershipEvents(); + updateState({}); + }} /> - )} - content={Hide membership change messages from room timeline. (Join, Leave, Invite, Kick and Ban)} + } + content={ + + Hide membership change messages from room timeline. (Join, Leave, + Invite, Kick and Ban) + + } /> { toggleNickAvatarEvents(); updateState({}); }} + onToggle={() => { + toggleNickAvatarEvents(); + updateState({}); + }} /> - )} - content={Hide nick and avatar change messages from room timeline.} + } + content={ + + Hide nick and avatar change messages from room timeline. + + } />
@@ -119,16 +155,23 @@ function AppearanceSection() { } function NotificationsSection() { - const [permission, setPermission] = usePermission('notifications', window.Notification?.permission); + const [permission, setPermission] = usePermission( + "notifications", + window.Notification?.permission + ); const [, updateState] = useState({}); const renderOptions = () => { if (window.Notification === undefined) { - return Not supported in this browser.; + return ( + + Not supported in this browser. + + ); } - if (permission === 'granted') { + if (permission === "granted") { return ( window.Notification.requestPermission().then(setPermission)} + onClick={() => + window.Notification.requestPermission().then(setPermission) + } > Request permission @@ -158,17 +203,26 @@ function NotificationsSection() { Show desktop notification when new messages arrive.
} + content={ + + Show desktop notification when new messages arrive. + + } /> { toggleNotificationSounds(); updateState({}); }} + onToggle={() => { + toggleNotificationSounds(); + updateState({}); + }} /> - )} - content={Play sound when new messages arrive.} + } + content={ + Play sound when new messages arrive. + } />
@@ -181,8 +235,12 @@ function NotificationsSection() { function EmojiSection() { return ( <> -
-
+
+ +
+
+ +
); } @@ -200,21 +258,29 @@ function SecuritySection() { Export/Import encryption keys - Export end-to-end encryption room keys to decrypt old messages in other session. In order to encrypt keys you need to set a password, which will be used while importing. + + Export end-to-end encryption room keys to decrypt old messages + in other session. In order to encrypt keys you need to set a + password, which will be used while importing. + - )} + } /> - {'To decrypt older messages, Export E2EE room keys from Element (Settings > Security & Privacy > Encryption > Cryptography) and import them here. Imported keys are encrypted so you\'ll have to enter the password you set in order to decrypt it.'} + + { + "To decrypt older messages, Export E2EE room keys from Element (Settings > Security & Privacy > Encryption > Cryptography) and import them here. Imported keys are encrypted so you'll have to enter the password you set in order to decrypt it." + } + - )} + } />
@@ -231,14 +297,28 @@ function AboutSection() {
Cinny - {`v${cons.version}`} + {`v${cons.version}`} Yet another matrix client
- - - + + +
@@ -248,16 +328,94 @@ function AboutSection() {
@@ -267,38 +425,44 @@ function AboutSection() { } export const tabText = { - APPEARANCE: 'Appearance', - NOTIFICATIONS: 'Notifications', - EMOJI: 'Emoji', - SECURITY: 'Security', - ABOUT: 'About', + APPEARANCE: "Appearance", + NOTIFICATIONS: "Notifications", + EMOJI: "Emoji", + SECURITY: "Security", + ABOUT: "About", }; -const tabItems = [{ - text: tabText.APPEARANCE, - iconSrc: SunIC, - disabled: false, - render: () => , -}, { - text: tabText.NOTIFICATIONS, - iconSrc: BellIC, - disabled: false, - render: () => , -}, { - text: tabText.EMOJI, - iconSrc: EmojiIC, - disabled: false, - render: () => , -}, { - text: tabText.SECURITY, - iconSrc: LockIC, - disabled: false, - render: () => , -}, { - text: tabText.ABOUT, - iconSrc: InfoIC, - disabled: false, - render: () => , -}]; +const tabItems = [ + { + text: tabText.APPEARANCE, + iconSrc: SunIC, + disabled: false, + render: () => , + }, + { + text: tabText.NOTIFICATIONS, + iconSrc: BellIC, + disabled: false, + render: () => , + }, + { + text: tabText.EMOJI, + iconSrc: EmojiIC, + disabled: false, + render: () => , + }, + { + text: tabText.SECURITY, + iconSrc: LockIC, + disabled: false, + render: () => , + }, + { + text: tabText.ABOUT, + iconSrc: InfoIC, + disabled: false, + render: () => , + }, +]; function useWindowToggle(setSelectedTab) { const [isOpen, setIsOpen] = useState(false); @@ -311,7 +475,10 @@ function useWindowToggle(setSelectedTab) { }; navigation.on(cons.events.navigation.SETTINGS_OPENED, openSettings); return () => { - navigation.removeListener(cons.events.navigation.SETTINGS_OPENED, openSettings); + navigation.removeListener( + cons.events.navigation.SETTINGS_OPENED, + openSettings + ); }; }, []); @@ -326,7 +493,14 @@ function Settings() { const handleTabChange = (tabItem) => setSelectedTab(tabItem); const handleLogout = async () => { - if (await confirmDialog('Logout', 'Are you sure that you want to logout your session?', 'Logout', 'danger')) { + if ( + await confirmDialog( + "Logout", + "Are you sure that you want to logout your session?", + "Logout", + "danger" + ) + ) { initMatrix.logout(); } }; @@ -335,15 +509,19 @@ function Settings() { Settings} - contentOptions={( + title={ + + Settings + + } + contentOptions={ <> - )} + } onRequestClose={requestClose} > {isOpen && ( @@ -351,11 +529,13 @@ function Settings() { tab.text === selectedTab.text)} + defaultSelected={tabItems.findIndex( + (tab) => tab.text === selectedTab.text + )} onSelect={handleTabChange} />
- { selectedTab.render() } + {selectedTab.render()}
)} diff --git a/src/app/organisms/settings/Settings.scss b/src/app/organisms/settings/Settings.scss index 1d686cd7..3c407b29 100644 --- a/src/app/organisms/settings/Settings.scss +++ b/src/app/organisms/settings/Settings.scss @@ -1,12 +1,12 @@ -@use '../../partials/flex'; -@use '../../partials/dir'; -@use '../../partials/screen'; +@use "../../partials/flex"; +@use "../../partials/dir"; +@use "../../partials/screen"; .settings-window { & .pw { background-color: var(--bg-surface-low); } - + .header .btn-danger { margin: 0 var(--sp-tight); box-shadow: none; @@ -14,7 +14,7 @@ & .profile-editor { padding: var(--sp-loose) var(--sp-extra-loose); - } + } & .tabs { position: sticky; @@ -57,7 +57,7 @@ @extend .settings-window__card; } -.settings-window__cards-wrapper{ +.settings-window__cards-wrapper { & .setting-tile { margin: 0 var(--sp-normal); margin-top: var(--sp-normal); @@ -79,11 +79,10 @@ &__branding { padding: var(--sp-normal); display: flex; - + & > div { margin: 0 var(--sp-loose); } - } &__btns { & button { @@ -91,7 +90,7 @@ @include dir.side(margin, 0, var(--sp-tight)); } } - + &__credits { padding: 0 var(--sp-normal); & ul { diff --git a/src/app/organisms/shortcut-spaces/ShortcutSpaces.jsx b/src/app/organisms/shortcut-spaces/ShortcutSpaces.jsx index 62ec76a3..4ffe47b7 100644 --- a/src/app/organisms/shortcut-spaces/ShortcutSpaces.jsx +++ b/src/app/organisms/shortcut-spaces/ShortcutSpaces.jsx @@ -1,35 +1,38 @@ -import React, { useState, useEffect } from 'react'; -import './ShortcutSpaces.scss'; +import React, { useState, useEffect } from "react"; +import "./ShortcutSpaces.scss"; -import initMatrix from '../../../client/initMatrix'; -import cons from '../../../client/state/cons'; -import navigation from '../../../client/state/navigation'; -import { createSpaceShortcut, deleteSpaceShortcut } from '../../../client/action/accountData'; -import { joinRuleToIconSrc } from '../../../util/matrixUtil'; -import { roomIdByAtoZ } from '../../../util/sort'; +import initMatrix from "../../../client/initMatrix"; +import cons from "../../../client/state/cons"; +import navigation from "../../../client/state/navigation"; +import { + createSpaceShortcut, + deleteSpaceShortcut, +} from "../../../client/action/accountData"; +import { joinRuleToIconSrc } from "../../../util/matrixUtil"; +import { roomIdByAtoZ } from "../../../util/sort"; -import Text from '../../atoms/text/Text'; -import Button from '../../atoms/button/Button'; -import IconButton from '../../atoms/button/IconButton'; -import Checkbox from '../../atoms/button/Checkbox'; -import Spinner from '../../atoms/spinner/Spinner'; -import RoomSelector from '../../molecules/room-selector/RoomSelector'; -import Dialog from '../../molecules/dialog/Dialog'; +import Text from "../../atoms/text/Text"; +import Button from "../../atoms/button/Button"; +import IconButton from "../../atoms/button/IconButton"; +import Checkbox from "../../atoms/button/Checkbox"; +import Spinner from "../../atoms/spinner/Spinner"; +import RoomSelector from "../../molecules/room-selector/RoomSelector"; +import Dialog from "../../molecules/dialog/Dialog"; -import PinIC from '../../../../public/res/ic/outlined/pin.svg'; -import PinFilledIC from '../../../../public/res/ic/filled/pin.svg'; -import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; +import PinIC from "../../../../public/res/ic/outlined/pin.svg"; +import PinFilledIC from "../../../../public/res/ic/filled/pin.svg"; +import CrossIC from "../../../../public/res/ic/outlined/cross.svg"; -import { useSpaceShortcut } from '../../hooks/useSpaceShortcut'; +import { useSpaceShortcut } from "../../hooks/useSpaceShortcut"; function ShortcutSpacesContent() { const mx = initMatrix.matrixClient; const { spaces, roomIdToParents } = initMatrix.roomList; const [spaceShortcut] = useSpaceShortcut(); - const spaceWithoutShortcut = [...spaces].filter( - (spaceId) => !spaceShortcut.includes(spaceId), - ).sort(roomIdByAtoZ); + const spaceWithoutShortcut = [...spaces] + .filter((spaceId) => !spaceShortcut.includes(spaceId)) + .sort(roomIdByAtoZ); const [process, setProcess] = useState(null); const [selected, setSelected] = useState([]); @@ -68,7 +71,7 @@ function ShortcutSpacesContent() { const parentNames = parentSet ? [...parentSet].map((parentId) => mx.getRoom(parentId).name) : undefined; - const parents = parentNames ? parentNames.join(', ') : null; + const parents = parentNames ? parentNames.join(", ") : null; const toggleSelected = () => toggleSelection(spaceId); const deleteShortcut = () => deleteSpaceShortcut(spaceId); @@ -85,40 +88,50 @@ function ShortcutSpacesContent() { notificationCount={0} isAlert={false} onClick={isShortcut ? deleteShortcut : toggleSelected} - options={isShortcut ? ( - - ) : ( - - )} + options={ + isShortcut ? ( + + ) : ( + + ) + } /> ); }; return ( <> - Pinned spaces + + Pinned spaces + {spaceShortcut.length === 0 && No pinned spaces} {spaceShortcut.map((spaceId) => renderSpace(spaceId, true))} - Unpinned spaces + + Unpinned spaces + {spaceWithoutShortcut.length === 0 && No unpinned spaces} {spaceWithoutShortcut.map((spaceId) => renderSpace(spaceId, false))} {selected.length !== 0 && (
{process && } - {process || `${selected.length} spaces selected`} - { !process && ( - + + {process || `${selected.length} spaces selected`} + + {!process && ( + )}
)} @@ -133,7 +146,10 @@ function useVisibilityToggle() { const handleOpen = () => setIsOpen(true); navigation.on(cons.events.navigation.SHORTCUT_SPACES_OPENED, handleOpen); return () => { - navigation.removeListener(cons.events.navigation.SHORTCUT_SPACES_OPENED, handleOpen); + navigation.removeListener( + cons.events.navigation.SHORTCUT_SPACES_OPENED, + handleOpen + ); }; }, []); @@ -149,19 +165,17 @@ function ShortcutSpaces() { Pin spaces - )} - contentOptions={} + } + contentOptions={ + + } onRequestClose={requestClose} > - { - isOpen - ? - :
- } + {isOpen ? :
}
); } diff --git a/src/app/organisms/shortcut-spaces/ShortcutSpaces.scss b/src/app/organisms/shortcut-spaces/ShortcutSpaces.scss index 686c8cc0..f09f8817 100644 --- a/src/app/organisms/shortcut-spaces/ShortcutSpaces.scss +++ b/src/app/organisms/shortcut-spaces/ShortcutSpaces.scss @@ -1,5 +1,5 @@ -@use '../../partials/dir'; -@use '../../partials/flex'; +@use "../../partials/dir"; +@use "../../partials/flex"; .shortcut-spaces { height: 100%; @@ -12,7 +12,7 @@ padding: 0 var(--sp-extra-tight); } } - + &__header { margin-top: var(--sp-extra-tight); padding: var(--sp-extra-tight); @@ -39,12 +39,12 @@ border-top: 1px solid var(--bg-surface-border); display: flex; align-items: center; - + & > .text { @extend .cp-fx__item-one; padding: 0 var(--sp-tight); } - + & > button { @include dir.side(margin, var(--sp-normal), 0); } diff --git a/src/app/organisms/space-manage/SpaceManage.jsx b/src/app/organisms/space-manage/SpaceManage.jsx index cf042da4..3be427ab 100644 --- a/src/app/organisms/space-manage/SpaceManage.jsx +++ b/src/app/organisms/space-manage/SpaceManage.jsx @@ -1,68 +1,73 @@ /* eslint-disable react/prop-types */ -import React, { useState, useEffect } from 'react'; -import PropTypes from 'prop-types'; -import './SpaceManage.scss'; +import React, { useState, useEffect } from "react"; +import PropTypes from "prop-types"; +import "./SpaceManage.scss"; -import { twemojify } from '../../../util/twemojify'; +import { twemojify } from "../../../util/twemojify"; -import initMatrix from '../../../client/initMatrix'; -import cons from '../../../client/state/cons'; -import navigation from '../../../client/state/navigation'; -import colorMXID from '../../../util/colorMXID'; -import { selectRoom, selectTab } from '../../../client/action/navigation'; -import RoomsHierarchy from '../../../client/state/RoomsHierarchy'; -import { joinRuleToIconSrc } from '../../../util/matrixUtil'; -import { join } from '../../../client/action/room'; -import { Debounce } from '../../../util/common'; +import initMatrix from "../../../client/initMatrix"; +import cons from "../../../client/state/cons"; +import navigation from "../../../client/state/navigation"; +import colorMXID from "../../../util/colorMXID"; +import { selectRoom, selectTab } from "../../../client/action/navigation"; +import RoomsHierarchy from "../../../client/state/RoomsHierarchy"; +import { joinRuleToIconSrc } from "../../../util/matrixUtil"; +import { join } from "../../../client/action/room"; +import { Debounce } from "../../../util/common"; -import Text from '../../atoms/text/Text'; -import RawIcon from '../../atoms/system-icons/RawIcon'; -import Button from '../../atoms/button/Button'; -import IconButton from '../../atoms/button/IconButton'; -import Checkbox from '../../atoms/button/Checkbox'; -import Avatar from '../../atoms/avatar/Avatar'; -import Spinner from '../../atoms/spinner/Spinner'; -import ScrollView from '../../atoms/scroll/ScrollView'; -import PopupWindow from '../../molecules/popup-window/PopupWindow'; +import Text from "../../atoms/text/Text"; +import RawIcon from "../../atoms/system-icons/RawIcon"; +import Button from "../../atoms/button/Button"; +import IconButton from "../../atoms/button/IconButton"; +import Checkbox from "../../atoms/button/Checkbox"; +import Avatar from "../../atoms/avatar/Avatar"; +import Spinner from "../../atoms/spinner/Spinner"; +import ScrollView from "../../atoms/scroll/ScrollView"; +import PopupWindow from "../../molecules/popup-window/PopupWindow"; -import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; -import ChevronRightIC from '../../../../public/res/ic/outlined/chevron-right.svg'; -import InfoIC from '../../../../public/res/ic/outlined/info.svg'; +import CrossIC from "../../../../public/res/ic/outlined/cross.svg"; +import ChevronRightIC from "../../../../public/res/ic/outlined/chevron-right.svg"; +import InfoIC from "../../../../public/res/ic/outlined/info.svg"; -import { useForceUpdate } from '../../hooks/useForceUpdate'; -import { useStore } from '../../hooks/useStore'; +import { useForceUpdate } from "../../hooks/useForceUpdate"; +import { useStore } from "../../hooks/useStore"; function SpaceManageBreadcrumb({ path, onSelect }) { return (
- { - path.map((item, index) => ( - - {index > 0 && } - - - )) - } + {path.map((item, index) => ( + + {index > 0 && } + + + ))}
); } SpaceManageBreadcrumb.propTypes = { - path: PropTypes.arrayOf(PropTypes.exact({ - roomId: PropTypes.string, - name: PropTypes.string, - })).isRequired, + path: PropTypes.arrayOf( + PropTypes.exact({ + roomId: PropTypes.string, + name: PropTypes.string, + }) + ).isRequired, onSelect: PropTypes.func.isRequired, }; function SpaceManageItem({ - parentId, roomInfo, onSpaceClick, requestClose, - isSelected, onSelect, roomHierarchy, + parentId, + roomInfo, + onSpaceClick, + requestClose, + isSelected, + onSelect, + roomHierarchy, }) { const [isExpand, setIsExpand] = useState(false); const [isJoining, setIsJoining] = useState(false); @@ -70,18 +75,30 @@ function SpaceManageItem({ const { directs } = initMatrix.roomList; const mx = initMatrix.matrixClient; const parentRoom = mx.getRoom(parentId); - const isSpace = roomInfo.room_type === 'm.space'; + const isSpace = roomInfo.room_type === "m.space"; const roomId = roomInfo.room_id; - const canManage = parentRoom?.currentState.maySendStateEvent('m.space.child', mx.getUserId()) || false; - const isSuggested = parentRoom?.currentState.getStateEvents('m.space.child', roomId)?.getContent().suggested === true; + const canManage = + parentRoom?.currentState.maySendStateEvent( + "m.space.child", + mx.getUserId() + ) || false; + const isSuggested = + parentRoom?.currentState + .getStateEvents("m.space.child", roomId) + ?.getContent().suggested === true; const room = mx.getRoom(roomId); - const isJoined = !!(room?.getMyMembership() === 'join' || null); - const name = room?.name || roomInfo.name || roomInfo.canonical_alias || roomId; - let imageSrc = mx.mxcUrlToHttp(roomInfo.avatar_url, 24, 24, 'crop') || null; + const isJoined = !!(room?.getMyMembership() === "join" || null); + const name = + room?.name || roomInfo.name || roomInfo.canonical_alias || roomId; + let imageSrc = mx.mxcUrlToHttp(roomInfo.avatar_url, 24, 24, "crop") || null; if (!imageSrc && room) { - imageSrc = room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null; - if (imageSrc === null) imageSrc = room.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null; + imageSrc = + room + .getAvatarFallbackMember() + ?.getAvatarUrl(mx.baseUrl, 24, 24, "crop") || null; + if (imageSrc === null) + imageSrc = room.getAvatarUrl(mx.baseUrl, 24, 24, "crop") || null; } const isDM = directs.has(roomId); @@ -106,7 +123,10 @@ function SpaceManageItem({ iconSrc={ isDM ? null - : joinRuleToIconSrc((roomInfo.join_rules || roomInfo.join_rule), isSpace) + : joinRuleToIconSrc( + roomInfo.join_rules || roomInfo.join_rule, + isSpace + ) } size="extra-small" /> @@ -114,13 +134,16 @@ function SpaceManageItem({ const roomNameJSX = ( {twemojify(name)} - {` • ${roomInfo.num_joined_members} members`} + {` • ${roomInfo.num_joined_members} members`} ); const expandBtnJsx = ( +
- {canManage && onSelect(roomId)} variant="positive" />} + {canManage && ( + onSelect(roomId)} + variant="positive" + /> + )} {roomInfo.topic && expandBtnJsx} - { - isJoined - ? - : - } + {isJoined ? ( + + ) : ( + + )}
- {isExpand && roomInfo.topic && {twemojify(roomInfo.topic, undefined, true)}} + {isExpand && roomInfo.topic && ( + {twemojify(roomInfo.topic, undefined, true)} + )}
); } @@ -172,14 +203,14 @@ function SpaceManageFooter({ parentId, selected }) { const { currentState } = room; const allSuggested = selected.every((roomId) => { - const sEvent = currentState.getStateEvents('m.space.child', roomId); + const sEvent = currentState.getStateEvents("m.space.child", roomId); return !!sEvent?.getContent()?.suggested; }); const handleRemove = () => { setProcess(`Removing ${selected.length} items`); selected.forEach((roomId) => { - mx.sendStateEvent(parentId, 'm.space.child', {}, roomId); + mx.sendStateEvent(parentId, "m.space.child", {}, roomId); }); }; @@ -187,28 +218,32 @@ function SpaceManageFooter({ parentId, selected }) { if (isMark) setProcess(`Marking as suggested ${selected.length} items`); else setProcess(`Marking as not suggested ${selected.length} items`); selected.forEach((roomId) => { - const sEvent = room.currentState.getStateEvents('m.space.child', roomId); + const sEvent = room.currentState.getStateEvents("m.space.child", roomId); if (!sEvent) return; const content = { ...sEvent.getContent() }; if (isMark && content.suggested) return; if (!isMark && !content.suggested) return; content.suggested = isMark; - mx.sendStateEvent(parentId, 'm.space.child', content, roomId); + mx.sendStateEvent(parentId, "m.space.child", content, roomId); }); }; return (
{process && } - {process || `${selected.length} item selected`} - { !process && ( + + {process || `${selected.length} item selected`} + + {!process && ( <> - + )} @@ -265,7 +300,7 @@ function useChildUpdate(roomId, roomsHierarchy) { let isMounted = true; const handleStateEvent = (event) => { if (event.getRoomId() !== roomId) return; - if (event.getType() !== 'm.space.child') return; + if (event.getType() !== "m.space.child") return; debounce._(() => { if (!isMounted) return; @@ -273,10 +308,10 @@ function useChildUpdate(roomId, roomsHierarchy) { forceUpdate(); }, 500)(); }; - mx.on('RoomState.events', handleStateEvent); + mx.on("RoomState.events", handleStateEvent); return () => { isMounted = false; - mx.removeListener('RoomState.events', handleStateEvent); + mx.removeListener("RoomState.events", handleStateEvent); }; }, [roomId, roomsHierarchy]); } @@ -339,17 +374,19 @@ function SpaceManageContent({ roomId, requestClose }) { {spacePath.length > 1 && ( )} - Rooms and spaces + + Rooms and spaces +
{!isLoading && currentHierarchy?.rooms?.length === 1 && ( - Either the space contains private rooms or you need to join space to view it's rooms. + Either the space contains private rooms or you need to join space to + view it's rooms. )} - {currentHierarchy && (currentHierarchy.rooms?.map((roomInfo) => ( - roomInfo.room_id === currentPath.roomId - ? null - : ( + {currentHierarchy && + currentHierarchy.rooms?.map((roomInfo) => + roomInfo.room_id === currentPath.roomId ? null : ( ) - )))} + )} {!currentHierarchy && loading...}
{currentHierarchy?.canLoadMore && !isLoading && ( @@ -393,7 +430,10 @@ function useWindowToggle() { }; navigation.on(cons.events.navigation.SPACE_MANAGE_OPENED, openSpaceManage); return () => { - navigation.removeListener(cons.events.navigation.SPACE_MANAGE_OPENED, openSpaceManage); + navigation.removeListener( + cons.events.navigation.SPACE_MANAGE_OPENED, + openSpaceManage + ); }; }, []); @@ -410,20 +450,25 @@ function SpaceManage() { {roomId && twemojify(room.name)} - — manage rooms + + {" "} + — manage rooms + - )} - contentOptions={} + } + contentOptions={ + + } onRequestClose={requestClose} > - { - roomId - ? - :
- } + {roomId ? ( + + ) : ( +
+ )} ); } diff --git a/src/app/organisms/space-manage/SpaceManage.scss b/src/app/organisms/space-manage/SpaceManage.scss index b72c92d8..328621a6 100644 --- a/src/app/organisms/space-manage/SpaceManage.scss +++ b/src/app/organisms/space-manage/SpaceManage.scss @@ -1,6 +1,6 @@ -@use '../../partials/text'; -@use '../../partials/dir'; -@use '../../partials/flex'; +@use "../../partials/text"; +@use "../../partials/dir"; +@use "../../partials/flex"; .space-manage { & .pw__content-wrapper { @@ -14,7 +14,7 @@ .space-manage__content { margin-bottom: var(--sp-extra-loose); - + & > .text { margin-top: var(--sp-extra-tight); padding: var(--sp-extra-tight) var(--sp-normal); @@ -27,7 +27,7 @@ padding: var(--sp-extra-tight); } } - + & > button { margin: var(--sp-normal); } @@ -73,7 +73,6 @@ background-color: var(--bg-surface); } } - } .space-manage-item { @@ -101,8 +100,7 @@ & .checkbox { @include dir.side(margin, 0, var(--sp-tight)); } - - + &__btn { @extend .cp-fx__item-one; display: flex; @@ -165,4 +163,4 @@ & > button { @include dir.side(margin, var(--sp-normal), 0); } -} \ No newline at end of file +} diff --git a/src/app/organisms/space-settings/SpaceSettings.jsx b/src/app/organisms/space-settings/SpaceSettings.jsx index 2c9d6d46..13a1b706 100644 --- a/src/app/organisms/space-settings/SpaceSettings.jsx +++ b/src/app/organisms/space-settings/SpaceSettings.jsx @@ -1,70 +1,75 @@ -import React, { useState, useEffect } from 'react'; -import PropTypes from 'prop-types'; -import './SpaceSettings.scss'; +import React, { useState, useEffect } from "react"; +import PropTypes from "prop-types"; +import "./SpaceSettings.scss"; -import { twemojify } from '../../../util/twemojify'; +import { twemojify } from "../../../util/twemojify"; -import initMatrix from '../../../client/initMatrix'; -import cons from '../../../client/state/cons'; -import navigation from '../../../client/state/navigation'; -import { leave } from '../../../client/action/room'; +import initMatrix from "../../../client/initMatrix"; +import cons from "../../../client/state/cons"; +import navigation from "../../../client/state/navigation"; +import { leave } from "../../../client/action/room"; import { createSpaceShortcut, deleteSpaceShortcut, categorizeSpace, unCategorizeSpace, -} from '../../../client/action/accountData'; +} from "../../../client/action/accountData"; -import Text from '../../atoms/text/Text'; -import IconButton from '../../atoms/button/IconButton'; -import Tabs from '../../atoms/tabs/Tabs'; -import { MenuHeader, MenuItem } from '../../atoms/context-menu/ContextMenu'; -import PopupWindow from '../../molecules/popup-window/PopupWindow'; -import RoomProfile from '../../molecules/room-profile/RoomProfile'; -import RoomVisibility from '../../molecules/room-visibility/RoomVisibility'; -import RoomAliases from '../../molecules/room-aliases/RoomAliases'; -import RoomPermissions from '../../molecules/room-permissions/RoomPermissions'; -import RoomMembers from '../../molecules/room-members/RoomMembers'; -import RoomEmojis from '../../molecules/room-emojis/RoomEmojis'; +import Text from "../../atoms/text/Text"; +import IconButton from "../../atoms/button/IconButton"; +import Tabs from "../../atoms/tabs/Tabs"; +import { MenuHeader, MenuItem } from "../../atoms/context-menu/ContextMenu"; +import PopupWindow from "../../molecules/popup-window/PopupWindow"; +import RoomProfile from "../../molecules/room-profile/RoomProfile"; +import RoomVisibility from "../../molecules/room-visibility/RoomVisibility"; +import RoomAliases from "../../molecules/room-aliases/RoomAliases"; +import RoomPermissions from "../../molecules/room-permissions/RoomPermissions"; +import RoomMembers from "../../molecules/room-members/RoomMembers"; +import RoomEmojis from "../../molecules/room-emojis/RoomEmojis"; -import UserIC from '../../../../public/res/ic/outlined/user.svg'; -import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; -import SettingsIC from '../../../../public/res/ic/outlined/settings.svg'; -import ShieldUserIC from '../../../../public/res/ic/outlined/shield-user.svg'; -import LeaveArrowIC from '../../../../public/res/ic/outlined/leave-arrow.svg'; -import PinIC from '../../../../public/res/ic/outlined/pin.svg'; -import PinFilledIC from '../../../../public/res/ic/filled/pin.svg'; -import CategoryIC from '../../../../public/res/ic/outlined/category.svg'; -import CategoryFilledIC from '../../../../public/res/ic/filled/category.svg'; -import EmojiIC from '../../../../public/res/ic/outlined/emoji.svg'; +import UserIC from "../../../../public/res/ic/outlined/user.svg"; +import CrossIC from "../../../../public/res/ic/outlined/cross.svg"; +import SettingsIC from "../../../../public/res/ic/outlined/settings.svg"; +import ShieldUserIC from "../../../../public/res/ic/outlined/shield-user.svg"; +import LeaveArrowIC from "../../../../public/res/ic/outlined/leave-arrow.svg"; +import PinIC from "../../../../public/res/ic/outlined/pin.svg"; +import PinFilledIC from "../../../../public/res/ic/filled/pin.svg"; +import CategoryIC from "../../../../public/res/ic/outlined/category.svg"; +import CategoryFilledIC from "../../../../public/res/ic/filled/category.svg"; +import EmojiIC from "../../../../public/res/ic/outlined/emoji.svg"; -import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog'; -import { useForceUpdate } from '../../hooks/useForceUpdate'; +import { confirmDialog } from "../../molecules/confirm-dialog/ConfirmDialog"; +import { useForceUpdate } from "../../hooks/useForceUpdate"; const tabText = { - GENERAL: 'General', - MEMBERS: 'Members', - EMOJIS: 'Emojis', - PERMISSIONS: 'Permissions', + GENERAL: "General", + MEMBERS: "Members", + EMOJIS: "Emojis", + PERMISSIONS: "Permissions", }; -const tabItems = [{ - iconSrc: SettingsIC, - text: tabText.GENERAL, - disabled: false, -}, { - iconSrc: UserIC, - text: tabText.MEMBERS, - disabled: false, -}, { - iconSrc: EmojiIC, - text: tabText.EMOJIS, - disabled: false, -}, { - iconSrc: ShieldUserIC, - text: tabText.PERMISSIONS, - disabled: false, -}]; +const tabItems = [ + { + iconSrc: SettingsIC, + text: tabText.GENERAL, + disabled: false, + }, + { + iconSrc: UserIC, + text: tabText.MEMBERS, + disabled: false, + }, + { + iconSrc: EmojiIC, + text: tabText.EMOJIS, + disabled: false, + }, + { + iconSrc: ShieldUserIC, + text: tabText.PERMISSIONS, + disabled: false, + }, +]; function GeneralSettings({ roomId }) { const isPinned = initMatrix.accountData.spaceShortcut.has(roomId); @@ -84,7 +89,7 @@ function GeneralSettings({ roomId }) { }} iconSrc={isCategorized ? CategoryFilledIC : CategoryIC} > - {isCategorized ? 'Uncategorize subspaces' : 'Categorize subspaces'} + {isCategorized ? "Uncategorize subspaces" : "Categorize subspaces"} { @@ -94,16 +99,16 @@ function GeneralSettings({ roomId }) { }} iconSrc={isPinned ? PinFilledIC : PinIC} > - {isPinned ? 'Unpin from sidebar' : 'Pin to sidebar'} + {isPinned ? "Unpin from sidebar" : "Pin to sidebar"} { const isConfirmed = await confirmDialog( - 'Leave space', + "Leave space", `Are you sure that you want to leave "${roomName}" space?`, - 'Leave', - 'danger', + "Leave", + "danger" ); if (isConfirmed) leave(roomId); }} @@ -137,9 +142,15 @@ function useWindowToggle(setSelectedTab) { const tabItem = tabItems.find((item) => item.text === tab); if (tabItem) setSelectedTab(tabItem); }; - navigation.on(cons.events.navigation.SPACE_SETTINGS_OPENED, openSpaceSettings); + navigation.on( + cons.events.navigation.SPACE_SETTINGS_OPENED, + openSpaceSettings + ); return () => { - navigation.removeListener(cons.events.navigation.SPACE_SETTINGS_OPENED, openSpaceSettings); + navigation.removeListener( + cons.events.navigation.SPACE_SETTINGS_OPENED, + openSpaceSettings + ); }; }, []); @@ -165,13 +176,18 @@ function SpaceSettings() { {isOpen && twemojify(room.name)} - — space settings + + {" "} + — space settings + - )} - contentOptions={} + } + contentOptions={ + + } onRequestClose={requestClose} > {isOpen && ( @@ -179,14 +195,24 @@ function SpaceSettings() { tab.text === selectedTab.text)} + defaultSelected={tabItems.findIndex( + (tab) => tab.text === selectedTab.text + )} onSelect={handleTabChange} />
- {selectedTab.text === tabText.GENERAL && } - {selectedTab.text === tabText.MEMBERS && } - {selectedTab.text === tabText.EMOJIS && } - {selectedTab.text === tabText.PERMISSIONS && } + {selectedTab.text === tabText.GENERAL && ( + + )} + {selectedTab.text === tabText.MEMBERS && ( + + )} + {selectedTab.text === tabText.EMOJIS && ( + + )} + {selectedTab.text === tabText.PERMISSIONS && ( + + )}
)} diff --git a/src/app/organisms/space-settings/SpaceSettings.scss b/src/app/organisms/space-settings/SpaceSettings.scss index d1cf9807..a192e00e 100644 --- a/src/app/organisms/space-settings/SpaceSettings.scss +++ b/src/app/organisms/space-settings/SpaceSettings.scss @@ -1,4 +1,4 @@ -@use '../../partials/dir.scss'; +@use "../../partials/dir.scss"; .space-settings { & .pw { @@ -7,7 +7,7 @@ & .room-profile { padding: var(--sp-loose) var(--sp-extra-loose); - } + } & .tabs { position: sticky; @@ -27,7 +27,7 @@ @include dir.side(padding, var(--sp-normal), var(--sp-extra-tight)); } } - + .space-settings__card { margin: var(--sp-normal) 0; background-color: var(--bg-surface); diff --git a/src/app/organisms/sticker-board/StickerBoard.jsx b/src/app/organisms/sticker-board/StickerBoard.jsx index 91e25918..7528f3b7 100644 --- a/src/app/organisms/sticker-board/StickerBoard.jsx +++ b/src/app/organisms/sticker-board/StickerBoard.jsx @@ -1,15 +1,15 @@ /* eslint-disable jsx-a11y/click-events-have-key-events */ /* eslint-disable jsx-a11y/no-static-element-interactions */ -import React, { useRef } from 'react'; -import PropTypes from 'prop-types'; -import './StickerBoard.scss'; +import React, { useRef } from "react"; +import PropTypes from "prop-types"; +import "./StickerBoard.scss"; -import initMatrix from '../../../client/initMatrix'; -import { getRelevantPacks } from '../emoji-board/custom-emoji'; +import initMatrix from "../../../client/initMatrix"; +import { getRelevantPacks } from "../emoji-board/custom-emoji"; -import Text from '../../atoms/text/Text'; -import ScrollView from '../../atoms/scroll/ScrollView'; -import IconButton from '../../atoms/button/IconButton'; +import Text from "../../atoms/text/Text"; +import ScrollView from "../../atoms/scroll/ScrollView"; +import IconButton from "../../atoms/button/IconButton"; function StickerBoard({ roomId, onSelect }) { const mx = initMatrix.matrixClient; @@ -19,18 +19,17 @@ function StickerBoard({ roomId, onSelect }) { const parentIds = initMatrix.roomList.getAllParentSpaces(room.roomId); const parentRooms = [...parentIds].map((id) => mx.getRoom(id)); - const packs = getRelevantPacks( - mx, - [room, ...parentRooms], - ).filter((pack) => pack.getStickers().length !== 0); + const packs = getRelevantPacks(mx, [room, ...parentRooms]).filter( + (pack) => pack.getStickers().length !== 0 + ); function isTargetNotSticker(target) { - return target.classList.contains('sticker-board__sticker') === false; + return target.classList.contains("sticker-board__sticker") === false; } function getStickerData(target) { - const mxc = target.getAttribute('data-mx-sticker'); - const body = target.getAttribute('title'); - const httpUrl = target.getAttribute('src'); + const mxc = target.getAttribute("data-mx-sticker"); + const body = target.getAttribute("title"); + const httpUrl = target.getAttribute("src"); return { mxc, body, httpUrl }; } const handleOnSelect = (e) => { @@ -47,7 +46,9 @@ function StickerBoard({ roomId, onSelect }) { const renderPack = (pack) => (
- {pack.displayName ?? 'Unknown'} + + {pack.displayName ?? "Unknown"} +
{pack.getStickers().map((sticker) => (
{packs.map((pack, index) => { - const src = mx.mxcUrlToHttp(pack.avatarUrl ?? pack.getStickers()[0].mxc); + const src = mx.mxcUrlToHttp( + pack.avatarUrl ?? pack.getStickers()[0].mxc + ); return ( openGroup(index)} src={src} - tooltip={pack.displayName || 'Unknown'} + tooltip={pack.displayName || "Unknown"} tooltipPlacement="left" isImage /> @@ -87,19 +90,14 @@ function StickerBoard({ roomId, onSelect }) { )}
-
- { - packs.length > 0 - ? packs.map(renderPack) - : ( -
- There is no sticker pack. -
- ) - } +
+ {packs.length > 0 ? ( + packs.map(renderPack) + ) : ( +
+ There is no sticker pack. +
+ )}
diff --git a/src/app/organisms/sticker-board/StickerBoard.scss b/src/app/organisms/sticker-board/StickerBoard.scss index b4e55130..c18cc7a1 100644 --- a/src/app/organisms/sticker-board/StickerBoard.scss +++ b/src/app/organisms/sticker-board/StickerBoard.scss @@ -1,4 +1,4 @@ -@use '../../partials/dir'; +@use "../../partials/dir"; .sticker-board { --sticker-board-height: 390px; @@ -8,7 +8,7 @@ display: flex; & > .scrollbar { - width: initial; + width: initial; height: var(--sticker-board-height); } @@ -40,7 +40,7 @@ top: 0; z-index: 99; background-color: var(--bg-surface); - + @include dir.side(margin, var(--sp-extra-tight), 0); padding: var(--sp-extra-tight) var(--sp-ultra-tight); text-transform: uppercase; @@ -55,7 +55,7 @@ gap: var(--sp-normal) var(--sp-tight); img { - width: 76px; + width: 76px; height: 76px; object-fit: contain; cursor: pointer; @@ -71,4 +71,4 @@ align-items: center; text-align: center; } -} \ No newline at end of file +} diff --git a/src/app/organisms/view-source/ViewSource.jsx b/src/app/organisms/view-source/ViewSource.jsx index 9bd3334f..7512d90e 100644 --- a/src/app/organisms/view-source/ViewSource.jsx +++ b/src/app/organisms/view-source/ViewSource.jsx @@ -1,16 +1,16 @@ -import React, { useEffect, useState } from 'react'; -import PropTypes from 'prop-types'; -import './ViewSource.scss'; +import React, { useEffect, useState } from "react"; +import PropTypes from "prop-types"; +import "./ViewSource.scss"; -import cons from '../../../client/state/cons'; -import navigation from '../../../client/state/navigation'; +import cons from "../../../client/state/cons"; +import navigation from "../../../client/state/navigation"; -import IconButton from '../../atoms/button/IconButton'; -import { MenuHeader } from '../../atoms/context-menu/ContextMenu'; -import ScrollView from '../../atoms/scroll/ScrollView'; -import PopupWindow from '../../molecules/popup-window/PopupWindow'; +import IconButton from "../../atoms/button/IconButton"; +import { MenuHeader } from "../../atoms/context-menu/ContextMenu"; +import ScrollView from "../../atoms/scroll/ScrollView"; +import PopupWindow from "../../molecules/popup-window/PopupWindow"; -import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; +import CrossIC from "../../../../public/res/ic/outlined/cross.svg"; function ViewSourceBlock({ title, json }) { return ( @@ -18,9 +18,7 @@ function ViewSourceBlock({ title, json }) { {title}
-          
-            {JSON.stringify(json, null, 2)}
-          
+          {JSON.stringify(json, null, 2)}
         
@@ -42,7 +40,10 @@ function ViewSource() { }; navigation.on(cons.events.navigation.VIEWSOURCE_OPENED, loadViewSource); return () => { - navigation.removeListener(cons.events.navigation.VIEWSOURCE_OPENED, loadViewSource); + navigation.removeListener( + cons.events.navigation.VIEWSOURCE_OPENED, + loadViewSource + ); }; }, []); @@ -52,7 +53,12 @@ function ViewSource() { const renderViewSource = () => (
- {event.isEncrypted() && } + {event.isEncrypted() && ( + + )}
); @@ -63,7 +69,13 @@ function ViewSource() { title="View source" onAfterClose={handleAfterClose} onRequestClose={() => setIsOpen(false)} - contentOptions={ setIsOpen(false)} tooltip="Close" />} + contentOptions={ + setIsOpen(false)} + tooltip="Close" + /> + } > {event ? renderViewSource() :
} diff --git a/src/app/organisms/view-source/ViewSource.scss b/src/app/organisms/view-source/ViewSource.scss index 9ceab8b0..c82ab25e 100644 --- a/src/app/organisms/view-source/ViewSource.scss +++ b/src/app/organisms/view-source/ViewSource.scss @@ -1,4 +1,4 @@ -@use '../../partials/dir'; +@use "../../partials/dir"; .view-source { @include dir.side(margin, var(--sp-normal), var(--sp-extra-tight)); diff --git a/src/app/organisms/welcome/Welcome.jsx b/src/app/organisms/welcome/Welcome.jsx index 6d135bee..49f9748b 100644 --- a/src/app/organisms/welcome/Welcome.jsx +++ b/src/app/organisms/welcome/Welcome.jsx @@ -1,17 +1,30 @@ -import React from 'react'; -import './Welcome.scss'; +import React from "react"; +import "./Welcome.scss"; -import Text from '../../atoms/text/Text'; +import Text from "../../atoms/text/Text"; -import CinnySvg from '../../../../public/res/svg/cinny.svg'; +import CinnySvg from "../../../../public/res/svg/cinny.svg"; function Welcome() { return (
- Cinny logo - Welcome to Cinny - Yet another matrix client + Cinny logo + + Welcome to Cinny + + + Yet another matrix client +
); diff --git a/src/app/organisms/welcome/Welcome.scss b/src/app/organisms/welcome/Welcome.scss index e55bb8ed..1d958380 100644 --- a/src/app/organisms/welcome/Welcome.scss +++ b/src/app/organisms/welcome/Welcome.scss @@ -1,4 +1,4 @@ -@use '../../partials/flex'; +@use "../../partials/flex"; .app-welcome { width: 100%; @@ -20,4 +20,4 @@ &__subheading { color: var(--tc-surface-normal); } -} \ No newline at end of file +} diff --git a/src/app/pages/App.jsx b/src/app/pages/App.jsx index af7cc29b..2d8adf8f 100644 --- a/src/app/pages/App.jsx +++ b/src/app/pages/App.jsx @@ -1,9 +1,9 @@ -import React from 'react'; +import React from "react"; -import { isAuthenticated } from '../../client/state/auth'; +import { isAuthenticated } from "../../client/state/auth"; -import Auth from '../templates/auth/Auth'; -import Client from '../templates/client/Client'; +import Auth from "../templates/auth/Auth"; +import Client from "../templates/client/Client"; function App() { return isAuthenticated() ? : ; diff --git a/src/app/partials/_dir.scss b/src/app/partials/_dir.scss index b5d4a696..4eef987d 100644 --- a/src/app/partials/_dir.scss +++ b/src/app/partials/_dir.scss @@ -8,19 +8,19 @@ #{$property}: { left: $left; right: $right; - }; - - [dir=rtl] & { + } + + [dir="rtl"] & { #{$property}: { left: $right; right: $left; - }; + } } } @mixin prop($property, $ltr, $rtl) { #{$property}: $ltr; - [dir=rtl] & { + [dir="rtl"] & { #{$property}: $rtl; } -} \ No newline at end of file +} diff --git a/src/app/partials/_screen.scss b/src/app/partials/_screen.scss index 99a0907b..9487fac0 100644 --- a/src/app/partials/_screen.scss +++ b/src/app/partials/_screen.scss @@ -1,4 +1,3 @@ - $breakpoint-tablet: 1124px; $breakpoint-mobile: 750px; @@ -7,8 +6,7 @@ $breakpoint-mobile: 750px; @media screen and (max-width: $breakpoint-mobile) { @content; } - } - @else if $deviceBreakpoint==tabletBreakpoint { + } @else if $deviceBreakpoint==tabletBreakpoint { @media screen and (max-width: $breakpoint-tablet) { @content; } diff --git a/src/app/partials/_text.scss b/src/app/partials/_text.scss index ab5e2805..d6d9f97b 100644 --- a/src/app/partials/_text.scss +++ b/src/app/partials/_text.scss @@ -2,4 +2,4 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; -} \ No newline at end of file +} diff --git a/src/app/templates/auth/Auth.jsx b/src/app/templates/auth/Auth.jsx index 7c211736..202f31b2 100644 --- a/src/app/templates/auth/Auth.jsx +++ b/src/app/templates/auth/Auth.jsx @@ -1,48 +1,56 @@ /* eslint-disable react/prop-types */ -import React, { useState, useEffect, useRef } from 'react'; -import PropTypes from 'prop-types'; -import './Auth.scss'; -import ReCAPTCHA from 'react-google-recaptcha'; -import { Formik } from 'formik'; +import React, { useState, useEffect, useRef } from "react"; +import PropTypes from "prop-types"; +import "./Auth.scss"; +import ReCAPTCHA from "react-google-recaptcha"; +import { Formik } from "formik"; -import * as auth from '../../../client/action/auth'; -import cons from '../../../client/state/cons'; -import { Debounce, getUrlPrams } from '../../../util/common'; -import { getBaseUrl } from '../../../util/matrixUtil'; +import * as auth from "../../../client/action/auth"; +import cons from "../../../client/state/cons"; +import { Debounce, getUrlPrams } from "../../../util/common"; +import { getBaseUrl } from "../../../util/matrixUtil"; -import Text from '../../atoms/text/Text'; -import Button from '../../atoms/button/Button'; -import IconButton from '../../atoms/button/IconButton'; -import Input from '../../atoms/input/Input'; -import Spinner from '../../atoms/spinner/Spinner'; -import ScrollView from '../../atoms/scroll/ScrollView'; -import Header, { TitleWrapper } from '../../atoms/header/Header'; -import Avatar from '../../atoms/avatar/Avatar'; -import ContextMenu, { MenuItem, MenuHeader } from '../../atoms/context-menu/ContextMenu'; +import Text from "../../atoms/text/Text"; +import Button from "../../atoms/button/Button"; +import IconButton from "../../atoms/button/IconButton"; +import Input from "../../atoms/input/Input"; +import Spinner from "../../atoms/spinner/Spinner"; +import ScrollView from "../../atoms/scroll/ScrollView"; +import Header, { TitleWrapper } from "../../atoms/header/Header"; +import Avatar from "../../atoms/avatar/Avatar"; +import ContextMenu, { + MenuItem, + MenuHeader, +} from "../../atoms/context-menu/ContextMenu"; -import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg'; -import EyeIC from '../../../../public/res/ic/outlined/eye.svg'; -import EyeBlindIC from '../../../../public/res/ic/outlined/eye-blind.svg'; -import CinnySvg from '../../../../public/res/svg/cinny.svg'; -import SSOButtons from '../../molecules/sso-buttons/SSOButtons'; +import ChevronBottomIC from "../../../../public/res/ic/outlined/chevron-bottom.svg"; +import EyeIC from "../../../../public/res/ic/outlined/eye.svg"; +import EyeBlindIC from "../../../../public/res/ic/outlined/eye-blind.svg"; +import CinnySvg from "../../../../public/res/svg/cinny.svg"; +import SSOButtons from "../../molecules/sso-buttons/SSOButtons"; const LOCALPART_SIGNUP_REGEX = /^[a-z0-9_\-.=/]+$/; -const BAD_LOCALPART_ERROR = 'Username can only contain characters a-z, 0-9, or \'=_-./\''; -const USER_ID_TOO_LONG_ERROR = 'Your user ID, including the hostname, can\'t be more than 255 characters long.'; +const BAD_LOCALPART_ERROR = + "Username can only contain characters a-z, 0-9, or '=_-./'"; +const USER_ID_TOO_LONG_ERROR = + "Your user ID, including the hostname, can't be more than 255 characters long."; -const PASSWORD_STRENGHT_REGEX = /^(?=.*\d)(?=.*[A-Z])(?=.*[a-z])(?=.*[^\w\d\s:])([^\s]){8,127}$/; -const BAD_PASSWORD_ERROR = 'Password must contain at least 1 lowercase, 1 uppercase, 1 number, 1 non-alphanumeric character, 8-127 characters with no space.'; -const CONFIRM_PASSWORD_ERROR = 'Passwords don\'t match.'; +const PASSWORD_STRENGHT_REGEX = + /^(?=.*\d)(?=.*[A-Z])(?=.*[a-z])(?=.*[^\w\d\s:])([^\s]){8,127}$/; +const BAD_PASSWORD_ERROR = + "Password must contain at least 1 lowercase, 1 uppercase, 1 number, 1 non-alphanumeric character, 8-127 characters with no space."; +const CONFIRM_PASSWORD_ERROR = "Passwords don't match."; const EMAIL_REGEX = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i; -const BAD_EMAIL_ERROR = 'Invalid email address'; +const BAD_EMAIL_ERROR = "Invalid email address"; function isValidInput(value, regex) { - if (typeof regex === 'string') return regex === value; + if (typeof regex === "string") return regex === value; return regex.test(value); } function normalizeUsername(rawUsername) { - const noLeadingAt = rawUsername.indexOf('@') === 0 ? rawUsername.substr(1) : rawUsername; + const noLeadingAt = + rawUsername.indexOf("@") === 0 ? rawUsername.substr(1) : rawUsername; return noLeadingAt.trim(); } @@ -50,11 +58,14 @@ let searchingHs = null; function Homeserver({ onChange }) { const [hs, setHs] = useState(null); const [debounce] = useState(new Debounce()); - const [process, setProcess] = useState({ isLoading: true, message: 'Loading homeserver list...' }); + const [process, setProcess] = useState({ + isLoading: true, + message: "Loading homeserver list...", + }); const hsRef = useRef(); const setupHsConfig = async (servername) => { - setProcess({ isLoading: true, message: 'Looking for homeserver...' }); + setProcess({ isLoading: true, message: "Looking for homeserver..." }); let baseUrl = null; baseUrl = await getBaseUrl(servername); @@ -64,41 +75,60 @@ function Homeserver({ onChange }) { Promise.allSettled([tempClient.loginFlows(), tempClient.register()]) .then((values) => { - const loginFlow = values[0].status === 'fulfilled' ? values[0]?.value : undefined; - const registerFlow = values[1].status === 'rejected' ? values[1]?.reason?.data : undefined; - if (loginFlow === undefined || registerFlow === undefined) throw new Error(); + const loginFlow = + values[0].status === "fulfilled" ? values[0]?.value : undefined; + const registerFlow = + values[1].status === "rejected" ? values[1]?.reason?.data : undefined; + if (loginFlow === undefined || registerFlow === undefined) + throw new Error(); if (searchingHs !== servername) return; onChange({ baseUrl, login: loginFlow, register: registerFlow }); setProcess({ isLoading: false }); - }).catch(() => { + }) + .catch(() => { if (searchingHs !== servername) return; onChange(null); - setProcess({ isLoading: false, error: 'Unable to connect. Please check your input.' }); + setProcess({ + isLoading: false, + error: "Unable to connect. Please check your input.", + }); }); }; useEffect(() => { onChange(null); - if (hs === null || hs?.selected.trim() === '') return; + if (hs === null || hs?.selected.trim() === "") return; searchingHs = hs.selected; setupHsConfig(hs.selected); }, [hs]); useEffect(async () => { const link = window.location.href; - const configFileUrl = `${link}${link[link.length - 1] === '/' ? '' : '/'}config.json`; + const configFileUrl = `${link}${ + link[link.length - 1] === "/" ? "" : "/" + }config.json`; try { - const result = await (await fetch(configFileUrl, { method: 'GET' })).json(); + const result = await ( + await fetch(configFileUrl, { method: "GET" }) + ).json(); const selectedHs = result?.defaultHomeserver; const hsList = result?.homeserverList; const allowCustom = result?.allowCustomHomeservers ?? true; - if (!hsList?.length > 0 || selectedHs < 0 || selectedHs >= hsList?.length) { + if ( + !hsList?.length > 0 || + selectedHs < 0 || + selectedHs >= hsList?.length + ) { throw new Error(); } setHs({ selected: hsList[selectedHs], list: hsList, allowCustom }); } catch { - setHs({ selected: 'matrix.org', list: ['matrix.org'], allowCustom: true }); + setHs({ + selected: "matrix.org", + list: ["matrix.org"], + allowCustom: true, + }); } }, []); @@ -126,26 +156,30 @@ function Homeserver({ onChange }) { content={(hideMenu) => ( <> Homeserver list - { - hs?.list.map((hsName) => ( - { - hideMenu(); - hsRef.current.value = hsName; - setHs({ ...hs, selected: hsName }); - }} - > - {hsName} - - )) - } + {hs?.list.map((hsName) => ( + { + hideMenu(); + hsRef.current.value = hsName; + setHs({ ...hs, selected: hsName }); + }} + > + {hsName} + + ))} )} - render={(toggleMenu) => } + render={(toggleMenu) => ( + + )} />
- {process.error !== undefined && {process.error}} + {process.error !== undefined && ( + + {process.error} + + )} {process.isLoading && (
@@ -162,17 +196,28 @@ Homeserver.propTypes = { function Login({ loginFlow, baseUrl }) { const [typeIndex, setTypeIndex] = useState(0); const [passVisible, setPassVisible] = useState(false); - const loginTypes = ['Username', 'Email']; - const isPassword = loginFlow?.filter((flow) => flow.type === 'm.login.password')[0]; - const ssoProviders = loginFlow?.filter((flow) => flow.type === 'm.login.sso')[0]; + const loginTypes = ["Username", "Email"]; + const isPassword = loginFlow?.filter( + (flow) => flow.type === "m.login.password" + )[0]; + const ssoProviders = loginFlow?.filter( + (flow) => flow.type === "m.login.sso" + )[0]; const initialValues = { - username: '', password: '', email: '', other: '', + username: "", + password: "", + email: "", + other: "", }; const validator = (values) => { const errors = {}; - if (typeIndex === 1 && values.email.length > 0 && !isValidInput(values.email, EMAIL_REGEX)) { + if ( + typeIndex === 1 && + values.email.length > 0 && + !isValidInput(values.email, EMAIL_REGEX) + ) { errors.email = BAD_EMAIL_ERROR; } return errors; @@ -186,33 +231,38 @@ function Login({ loginFlow, baseUrl }) { userBaseUrl = await getBaseUrl(userBaseUrl); } - return auth.login( - userBaseUrl, - typeIndex === 0 ? normalizeUsername(username) : undefined, - typeIndex === 1 ? values.email : undefined, - values.password, - ).then(() => { - actions.setSubmitting(true); - window.location.reload(); - }).catch((error) => { - let msg = error.message; - if (msg === 'Unknown message') msg = 'Please check your credentials'; - actions.setErrors({ - password: msg === 'Invalid password' ? msg : undefined, - other: msg !== 'Invalid password' ? msg : undefined, + return auth + .login( + userBaseUrl, + typeIndex === 0 ? normalizeUsername(username) : undefined, + typeIndex === 1 ? values.email : undefined, + values.password + ) + .then(() => { + actions.setSubmitting(true); + window.location.reload(); + }) + .catch((error) => { + let msg = error.message; + if (msg === "Unknown message") msg = "Please check your credentials"; + actions.setErrors({ + password: msg === "Invalid password" ? msg : undefined, + other: msg !== "Invalid password" ? msg : undefined, + }); + actions.setSubmitting(false); }); - actions.setSubmitting(false); - }); }; return ( <>
- Login + + Login + {isPassword && ( ( + content={(hideMenu) => loginTypes.map((type, index) => ( )) - )} + } render={(toggleMenu) => ( +
@@ -275,9 +374,7 @@ function Login({ loginFlow, baseUrl }) { ); } Login.propTypes = { - loginFlow: PropTypes.arrayOf( - PropTypes.shape({}), - ).isRequired, + loginFlow: PropTypes.arrayOf(PropTypes.shape({})).isRequired, baseUrl: PropTypes.string.isRequired, }; @@ -289,7 +386,9 @@ function Register({ registerInfo, loginFlow, baseUrl }) { const [cPassVisible, setCPassVisible] = useState(false); const formRef = useRef(); - const ssoProviders = loginFlow?.filter((flow) => flow.type === 'm.login.sso')[0]; + const ssoProviders = loginFlow?.filter( + (flow) => flow.type === "m.login.sso" + )[0]; const isDisabled = registerInfo.errcode !== undefined; const { flows, params, session } = registerInfo; @@ -300,28 +399,42 @@ function Register({ registerInfo, loginFlow, baseUrl }) { let isDummy = false; flows?.forEach((flow) => { - if (isEmailRequired && flow.stages.indexOf('m.login.email.identity') === -1) isEmailRequired = false; - if (!isEmail) isEmail = flow.stages.indexOf('m.login.email.identity') > -1; - if (!isRecaptcha) isRecaptcha = flow.stages.indexOf('m.login.recaptcha') > -1; - if (!isTerms) isTerms = flow.stages.indexOf('m.login.terms') > -1; - if (!isDummy) isDummy = flow.stages.indexOf('m.login.dummy') > -1; + if (isEmailRequired && flow.stages.indexOf("m.login.email.identity") === -1) + isEmailRequired = false; + if (!isEmail) isEmail = flow.stages.indexOf("m.login.email.identity") > -1; + if (!isRecaptcha) + isRecaptcha = flow.stages.indexOf("m.login.recaptcha") > -1; + if (!isTerms) isTerms = flow.stages.indexOf("m.login.terms") > -1; + if (!isDummy) isDummy = flow.stages.indexOf("m.login.dummy") > -1; }); const initialValues = { - username: '', password: '', confirmPassword: '', email: '', other: '', + username: "", + password: "", + confirmPassword: "", + email: "", + other: "", }; const validator = (values) => { const errors = {}; if (values.username.list > 255) errors.username = USER_ID_TOO_LONG_ERROR; - if (values.username.length > 0 && !isValidInput(values.username, LOCALPART_SIGNUP_REGEX)) { + if ( + values.username.length > 0 && + !isValidInput(values.username, LOCALPART_SIGNUP_REGEX) + ) { errors.username = BAD_LOCALPART_ERROR; } - if (values.password.length > 0 && !isValidInput(values.password, PASSWORD_STRENGHT_REGEX)) { + if ( + values.password.length > 0 && + !isValidInput(values.password, PASSWORD_STRENGHT_REGEX) + ) { errors.password = BAD_PASSWORD_ERROR; } - if (values.confirmPassword.length > 0 - && !isValidInput(values.confirmPassword, values.password)) { + if ( + values.confirmPassword.length > 0 && + !isValidInput(values.confirmPassword, values.password) + ) { errors.confirmPassword = CONFIRM_PASSWORD_ERROR; } if (values.email.length > 0 && !isValidInput(values.email, EMAIL_REGEX)) { @@ -332,29 +445,49 @@ function Register({ registerInfo, loginFlow, baseUrl }) { const submitter = (values, actions) => { const tempClient = auth.createTemporaryClient(baseUrl); clientSecret = tempClient.generateClientSecret(); - return tempClient.isUsernameAvailable(values.username) + return tempClient + .isUsernameAvailable(values.username) .then(async (isAvail) => { if (!isAvail) { - actions.setErrors({ username: 'Username is already taken' }); + actions.setErrors({ username: "Username is already taken" }); actions.setSubmitting(false); return; } if (isEmail && values.email.length > 0) { - const result = await auth.verifyEmail(baseUrl, values.email, clientSecret, 1); + const result = await auth.verifyEmail( + baseUrl, + values.email, + clientSecret, + 1 + ); if (result.errcode) { - if (result.errcode === 'M_THREEPID_IN_USE') actions.setErrors({ email: result.error }); + if (result.errcode === "M_THREEPID_IN_USE") + actions.setErrors({ email: result.error }); else actions.setErrors({ others: result.error || result.message }); actions.setSubmitting(false); return; } sid = result.sid; } - setProcess({ type: 'processing', message: 'Registration in progress....' }); + setProcess({ + type: "processing", + message: "Registration in progress....", + }); actions.setSubmitting(false); - }).catch((err) => { + }) + .catch((err) => { const msg = err.message || err.error; - if (['M_USER_IN_USE', 'M_INVALID_USERNAME', 'M_EXCLUSIVE'].indexOf(err.errcode) > -1) { - actions.setErrors({ username: err.errcode === 'M_USER_IN_USE' ? 'Username is already taken' : msg }); + if ( + ["M_USER_IN_USE", "M_INVALID_USERNAME", "M_EXCLUSIVE"].indexOf( + err.errcode + ) > -1 + ) { + actions.setErrors({ + username: + err.errcode === "M_USER_IN_USE" + ? "Username is already taken" + : msg, + }); } else if (msg) actions.setErrors({ other: msg }); actions.setSubmitting(false); @@ -369,31 +502,38 @@ function Register({ registerInfo, loginFlow, baseUrl }) { }; useEffect(() => { - if (process.type !== 'processing') return; + if (process.type !== "processing") return; const asyncProcess = async () => { const [username, password, email] = getInputs(); - const d = await auth.completeRegisterStage(baseUrl, username, password, { session }); + const d = await auth.completeRegisterStage(baseUrl, username, password, { + session, + }); - if (isRecaptcha && !d.completed.includes('m.login.recaptcha')) { - const sitekey = params['m.login.recaptcha'].public_key; - setProcess({ type: 'm.login.recaptcha', sitekey }); + if (isRecaptcha && !d.completed.includes("m.login.recaptcha")) { + const sitekey = params["m.login.recaptcha"].public_key; + setProcess({ type: "m.login.recaptcha", sitekey }); return; } - if (isTerms && !d.completed.includes('m.login.terms')) { - const pp = params['m.login.terms'].policies.privacy_policy; + if (isTerms && !d.completed.includes("m.login.terms")) { + const pp = params["m.login.terms"].policies.privacy_policy; const url = pp?.en.url || pp[Object.keys(pp)[0]].url; - setProcess({ type: 'm.login.terms', url }); + setProcess({ type: "m.login.terms", url }); return; } if (isEmail && email.length > 0) { - setProcess({ type: 'm.login.email.identity', email }); + setProcess({ type: "m.login.email.identity", email }); return; } if (isDummy) { - const data = await auth.completeRegisterStage(baseUrl, username, password, { - type: 'm.login.dummy', - session, - }); + const data = await auth.completeRegisterStage( + baseUrl, + username, + password, + { + type: "m.login.dummy", + session, + } + ); if (data.done) refreshWindow(); } }; @@ -401,46 +541,76 @@ function Register({ registerInfo, loginFlow, baseUrl }) { }, [process]); const handleRecaptcha = async (value) => { - if (typeof value !== 'string') return; + if (typeof value !== "string") return; const [username, password] = getInputs(); const d = await auth.completeRegisterStage(baseUrl, username, password, { - type: 'm.login.recaptcha', + type: "m.login.recaptcha", response: value, session, }); if (d.done) refreshWindow(); - else setProcess({ type: 'processing', message: 'Registration in progress...' }); + else + setProcess({ + type: "processing", + message: "Registration in progress...", + }); }; const handleTerms = async () => { const [username, password] = getInputs(); const d = await auth.completeRegisterStage(baseUrl, username, password, { - type: 'm.login.terms', + type: "m.login.terms", session, }); if (d.done) refreshWindow(); - else setProcess({ type: 'processing', message: 'Registration in progress...' }); + else + setProcess({ + type: "processing", + message: "Registration in progress...", + }); }; const handleEmailVerify = async () => { const [username, password] = getInputs(); const d = await auth.completeRegisterStage(baseUrl, username, password, { - type: 'm.login.email.identity', + type: "m.login.email.identity", threepidCreds: { sid, client_secret: clientSecret }, threepid_creds: { sid, client_secret: clientSecret }, session, }); if (d.done) refreshWindow(); - else setProcess({ type: 'processing', message: 'Registration in progress...' }); + else + setProcess({ + type: "processing", + message: "Registration in progress...", + }); }; return ( <> - {process.type === 'processing' && } - {process.type === 'm.login.recaptcha' && } - {process.type === 'm.login.terms' && } - {process.type === 'm.login.email.identity' && } + {process.type === "processing" && ( + + )} + {process.type === "m.login.recaptcha" && ( + + )} + {process.type === "m.login.terms" && ( + + )} + {process.type === "m.login.email.identity" && ( + + )}
- {!isDisabled && Register} - {isDisabled && {registerInfo.error}} + {!isDisabled && ( + + Register + + )} + {isDisabled && ( + {registerInfo.error} + )}
{!isDisabled && ( - {({ - values, errors, handleChange, handleSubmit, isSubmitting, - }) => ( + {({ values, errors, handleChange, handleSubmit, isSubmitting }) => ( <> - {process.type === undefined && isSubmitting && } + {process.type === undefined && isSubmitting && ( + + )}
- - {errors.username && {errors.username}} + + {errors.username && ( + + {errors.username} + + )}
- - setPassVisible(!passVisible)} src={passVisible ? EyeIC : EyeBlindIC} size="extra-small" /> + + setPassVisible(!passVisible)} + src={passVisible ? EyeIC : EyeBlindIC} + size="extra-small" + />
- {errors.password && {errors.password}} + {errors.password && ( + + {errors.password} + + )}
- - setCPassVisible(!cPassVisible)} src={cPassVisible ? EyeIC : EyeBlindIC} size="extra-small" /> + + setCPassVisible(!cPassVisible)} + src={cPassVisible ? EyeIC : EyeBlindIC} + size="extra-small" + />
- {errors.confirmPassword && {errors.confirmPassword}} - {isEmail && } - {errors.email && {errors.email}} - {errors.other && {errors.other}} + {errors.confirmPassword && ( + + {errors.confirmPassword} + + )} + {isEmail && ( + + )} + {errors.email && ( + + {errors.email} + + )} + {errors.other && ( + + {errors.other} + + )}
- +
@@ -489,15 +723,13 @@ function Register({ registerInfo, loginFlow, baseUrl }) { } Register.propTypes = { registerInfo: PropTypes.shape({}).isRequired, - loginFlow: PropTypes.arrayOf( - PropTypes.shape({}), - ).isRequired, + loginFlow: PropTypes.arrayOf(PropTypes.shape({})).isRequired, baseUrl: PropTypes.string.isRequired, }; function AuthCard() { const [hsConfig, setHsConfig] = useState(null); - const [type, setType] = useState('login'); + const [type, setType] = useState("login"); const handleHsChange = (info) => { console.log(info); @@ -507,26 +739,29 @@ function AuthCard() { return ( <> - { hsConfig !== null && ( - type === 'login' - ? - : ( - - ) - )} - { hsConfig !== null && ( + {hsConfig !== null && + (type === "login" ? ( + + ) : ( + + ))} + {hsConfig !== null && ( - {`${(type === 'login' ? 'Don\'t have' : 'Already have')} an account?`} + {`${type === "login" ? "Don't have" : "Already have"} an account?`} )} @@ -535,7 +770,7 @@ function AuthCard() { } function Auth() { - const [loginToken, setLoginToken] = useState(getUrlPrams('loginToken')); + const [loginToken, setLoginToken] = useState(getUrlPrams("loginToken")); useEffect(async () => { if (!loginToken) return; @@ -548,7 +783,7 @@ function Auth() { await auth.loginWithToken(baseUrl, loginToken); const { href } = window.location; - window.location.replace(href.slice(0, href.indexOf('?'))); + window.location.replace(href.slice(0, href.indexOf("?"))); } catch { setLoginToken(null); } @@ -564,7 +799,9 @@ function Auth() {
- Cinny + + Cinny +
@@ -597,7 +848,7 @@ function LoadingScreen({ message }) { return ( -
+
{message}
@@ -610,8 +861,10 @@ LoadingScreen.propTypes = { function Recaptcha({ message, sitekey, onChange }) { return ( -
- {message} +
+ + {message} +
@@ -626,19 +879,49 @@ Recaptcha.propTypes = { function Terms({ url, onSubmit }) { return ( -
{ e.preventDefault(); onSubmit(); }}> -
- Agree with terms -
- In order to complete registration, you need to agree to the terms and conditions. -
- + { + e.preventDefault(); + onSubmit(); + }} + > +
+ + Agree with terms + +
+ + In order to complete registration, you need to agree to the terms + and conditions. + +
+ - {'I accept '} - Terms and Conditions + {"I accept "} + + Terms and Conditions +
- +
@@ -652,16 +935,20 @@ Terms.propTypes = { function EmailVerify({ email, onContinue }) { return ( -
- Verify email -
+
+ + Verify email + +
- {'Please check your email '} + {"Please check your email "} {`(${email})`} - {' and validate before continuing further.'} + {" and validate before continuing further."}
- +
); @@ -671,11 +958,7 @@ EmailVerify.propTypes = { }; function ProcessWrapper({ children }) { - return ( -
- {children} -
- ); + return
{children}
; } ProcessWrapper.propTypes = { children: PropTypes.node.isRequired, diff --git a/src/app/templates/auth/Auth.scss b/src/app/templates/auth/Auth.scss index 956a2700..ae785d5a 100644 --- a/src/app/templates/auth/Auth.scss +++ b/src/app/templates/auth/Auth.scss @@ -1,12 +1,15 @@ -@use '../../partials/flex'; -@use '../../partials/dir'; +@use "../../partials/flex"; +@use "../../partials/dir"; .auth__base { --pattern-size: 48px; min-height: 100%; background-color: var(--bg-surface-low); - background-image: radial-gradient(rgba(0, 0, 0, 6%) 2px, rgba(0, 0, 0, 0%) 2px); + background-image: radial-gradient( + rgba(0, 0, 0, 6%) 2px, + rgba(0, 0, 0, 0%) 2px + ); background-size: var(--pattern-size) var(--pattern-size); display: flex; @@ -31,7 +34,9 @@ } & a { color: var(--tc-surface-normal); - &:hover { text-decoration: underline; } + &:hover { + text-decoration: underline; + } } } .auth-card { @@ -42,7 +47,8 @@ overflow: hidden; &__content { - padding: var(--sp-extra-loose) calc(var(--sp-normal) + var(--sp-extra-loose)); + padding: var(--sp-extra-loose) + calc(var(--sp-normal) + var(--sp-extra-loose)); } &__switch { margin-top: var(--sp-loose) !important; @@ -65,19 +71,21 @@ & .input { background-color: var(--bg-surface); @include dir.prop(border-right-width, 0, 1px); - @include dir.prop(border-left-width, 1px, 0 ); - @include dir.prop(border-radius, + @include dir.prop(border-left-width, 1px, 0); + @include dir.prop( + border-radius, var(--bo-radius) 0 0 var(--bo-radius), - 0 var(--bo-radius) var(--bo-radius) 0, + 0 var(--bo-radius) var(--bo-radius) 0 ); } } & .ic-btn { height: 46px; border: 1px solid var(--bg-surface-border); - @include dir.prop(border-radius, + @include dir.prop( + border-radius, 0 var(--bo-radius) var(--bo-radius) 0, - var(--bo-radius) 0 0 var(--bo-radius), + var(--bo-radius) 0 0 var(--bo-radius) ); } @@ -101,7 +109,7 @@ &__pass-eye-wrapper { margin: var(--sp-tight) 0 var(--sp-ultra-tight); } - + &__heading { display: flex; justify-content: space-between; @@ -113,7 +121,7 @@ & .ic-btn { position: absolute; @include dir.prop(right, 6px, unset); - @include dir.prop(left, unset, 6px ); + @include dir.prop(left, unset, 6px); bottom: 6px; border-radius: 4px; } @@ -141,7 +149,7 @@ &::before, &::after { flex: 1; - content: ''; + content: ""; margin: var(--sp-tight); border-bottom: 1px solid var(--bg-surface-border); } @@ -164,10 +172,10 @@ min-height: 100%; width: 100%; background-color: var(--bg-surface-low); - opacity: .96; + opacity: 0.96; position: fixed; top: 0; left: 0; z-index: 999; -} \ No newline at end of file +} diff --git a/src/app/templates/client/Client.jsx b/src/app/templates/client/Client.jsx index d83845b8..b6241b0a 100644 --- a/src/app/templates/client/Client.jsx +++ b/src/app/templates/client/Client.jsx @@ -1,32 +1,32 @@ -import React, { useState, useEffect, useRef } from 'react'; -import './Client.scss'; +import React, { useState, useEffect, useRef } from "react"; +import "./Client.scss"; -import { initHotkeys } from '../../../client/event/hotkeys'; -import { initRoomListListener } from '../../../client/event/roomList'; +import { initHotkeys } from "../../../client/event/hotkeys"; +import { initRoomListListener } from "../../../client/event/roomList"; -import Text from '../../atoms/text/Text'; -import Spinner from '../../atoms/spinner/Spinner'; -import Navigation from '../../organisms/navigation/Navigation'; -import ContextMenu, { MenuItem } from '../../atoms/context-menu/ContextMenu'; -import IconButton from '../../atoms/button/IconButton'; -import ReusableContextMenu from '../../atoms/context-menu/ReusableContextMenu'; -import Room from '../../organisms/room/Room'; -import Windows from '../../organisms/pw/Windows'; -import Dialogs from '../../organisms/pw/Dialogs'; -import EmojiBoardOpener from '../../organisms/emoji-board/EmojiBoardOpener'; +import Text from "../../atoms/text/Text"; +import Spinner from "../../atoms/spinner/Spinner"; +import Navigation from "../../organisms/navigation/Navigation"; +import ContextMenu, { MenuItem } from "../../atoms/context-menu/ContextMenu"; +import IconButton from "../../atoms/button/IconButton"; +import ReusableContextMenu from "../../atoms/context-menu/ReusableContextMenu"; +import Room from "../../organisms/room/Room"; +import Windows from "../../organisms/pw/Windows"; +import Dialogs from "../../organisms/pw/Dialogs"; +import EmojiBoardOpener from "../../organisms/emoji-board/EmojiBoardOpener"; -import initMatrix from '../../../client/initMatrix'; -import navigation from '../../../client/state/navigation'; -import cons from '../../../client/state/cons'; -import DragDrop from '../../organisms/drag-drop/DragDrop'; +import initMatrix from "../../../client/initMatrix"; +import navigation from "../../../client/state/navigation"; +import cons from "../../../client/state/cons"; +import DragDrop from "../../organisms/drag-drop/DragDrop"; -import VerticalMenuIC from '../../../../public/res/ic/outlined/vertical-menu.svg'; +import VerticalMenuIC from "../../../../public/res/ic/outlined/vertical-menu.svg"; function Client() { const [isLoading, changeLoading] = useState(true); - const [loadingMsg, setLoadingMsg] = useState('Heating up'); + const [loadingMsg, setLoadingMsg] = useState("Heating up"); const [dragCounter, setDragCounter] = useState(0); - const classNameHidden = 'client__item-hidden'; + const classNameHidden = "client__item-hidden"; const navWrapperRef = useRef(null); const roomWrapperRef = useRef(null); @@ -42,20 +42,29 @@ function Client() { useEffect(() => { navigation.on(cons.events.navigation.ROOM_SELECTED, onRoomSelected); - navigation.on(cons.events.navigation.NAVIGATION_OPENED, onNavigationSelected); + navigation.on( + cons.events.navigation.NAVIGATION_OPENED, + onNavigationSelected + ); - return (() => { - navigation.removeListener(cons.events.navigation.ROOM_SELECTED, onRoomSelected); - navigation.removeListener(cons.events.navigation.NAVIGATION_OPENED, onNavigationSelected); - }); + return () => { + navigation.removeListener( + cons.events.navigation.ROOM_SELECTED, + onRoomSelected + ); + navigation.removeListener( + cons.events.navigation.NAVIGATION_OPENED, + onNavigationSelected + ); + }; }, []); useEffect(() => { let counter = 0; const iId = setInterval(() => { const msgList = [ - 'Almost there...', - 'Looks like you have a lot of stuff to heat up!', + "Almost there...", + "Looks like you have a lot of stuff to heat up!", ]; if (counter === msgList.length - 1) { setLoadingMsg(msgList[msgList.length - 1]); @@ -65,7 +74,7 @@ function Client() { setLoadingMsg(msgList[counter]); counter += 1; }, 15000); - initMatrix.once('init_loading_finished', () => { + initMatrix.once("init_loading_finished", () => { clearInterval(iId); initHotkeys(); initRoomListListener(initMatrix.roomList); @@ -80,22 +89,32 @@ function Client() {
initMatrix.clearCacheAndReload()}> Clear cache & reload initMatrix.logout()}>Logout + } + render={(toggle) => ( + )} - render={(toggle) => } />
- {loadingMsg} + + {loadingMsg} +
- Cinny + + Cinny +
); @@ -105,7 +124,7 @@ function Client() { if (!e.dataTransfer.types) return false; for (let i = 0; i < e.dataTransfer.types.length; i += 1) { - if (e.dataTransfer.types[i] === 'Files') return true; + if (e.dataTransfer.types[i] === "Files") return true; } return false; } @@ -120,7 +139,7 @@ function Client() { e.preventDefault(); if (!navigation.selectedRoomId || modalOpen()) { - e.dataTransfer.dropEffect = 'none'; + e.dataTransfer.dropEffect = "none"; } } diff --git a/src/app/templates/client/Client.scss b/src/app/templates/client/Client.scss index cdb8fcc9..1f249e75 100644 --- a/src/app/templates/client/Client.scss +++ b/src/app/templates/client/Client.scss @@ -1,4 +1,4 @@ -@use '../../partials/screen'; +@use "../../partials/screen"; .client-container { display: flex; @@ -7,7 +7,7 @@ .navigation__wrapper { width: var(--navigation-width); - + @include screen.smallerThan(mobileBreakpoint) { width: 100%; } diff --git a/src/client/action/accountData.js b/src/client/action/accountData.js index 1fb49fbf..135727b3 100644 --- a/src/client/action/accountData.js +++ b/src/client/action/accountData.js @@ -1,5 +1,5 @@ -import appDispatcher from '../dispatcher'; -import cons from '../state/cons'; +import appDispatcher from "../dispatcher"; +import cons from "../state/cons"; /** * @param {string | string[]} roomId - room id or array of them to add into shortcuts diff --git a/src/client/action/auth.js b/src/client/action/auth.js index f9be13bc..c8bd25a9 100644 --- a/src/client/action/auth.js +++ b/src/client/action/auth.js @@ -1,5 +1,5 @@ -import * as sdk from 'matrix-js-sdk'; -import cons from '../state/cons'; +import * as sdk from "matrix-js-sdk"; +import cons from "../state/cons"; function updateLocalStore(accessToken, deviceId, userId, baseUrl) { localStorage.setItem(cons.secretKey.ACCESS_TOKEN, accessToken); @@ -15,62 +15,78 @@ function createTemporaryClient(baseUrl) { async function startSsoLogin(baseUrl, type, idpId) { const client = createTemporaryClient(baseUrl); localStorage.setItem(cons.secretKey.BASE_URL, client.baseUrl); - window.location.href = client.getSsoLoginUrl(window.location.href, type, idpId); + window.location.href = client.getSsoLoginUrl( + window.location.href, + type, + idpId + ); } async function login(baseUrl, username, email, password) { const identifier = {}; if (username) { - identifier.type = 'm.id.user'; + identifier.type = "m.id.user"; identifier.user = username; } else if (email) { - identifier.type = 'm.id.thirdparty'; - identifier.medium = 'email'; + identifier.type = "m.id.thirdparty"; + identifier.medium = "email"; identifier.address = email; - } else throw new Error('Bad Input'); + } else throw new Error("Bad Input"); const client = createTemporaryClient(baseUrl); - const res = await client.login('m.login.password', { + const res = await client.login("m.login.password", { identifier, password, initial_device_display_name: cons.DEVICE_DISPLAY_NAME, }); - const myBaseUrl = res?.well_known?.['m.homeserver']?.base_url || client.baseUrl; + const myBaseUrl = + res?.well_known?.["m.homeserver"]?.base_url || client.baseUrl; updateLocalStore(res.access_token, res.device_id, res.user_id, myBaseUrl); } async function loginWithToken(baseUrl, token) { const client = createTemporaryClient(baseUrl); - const res = await client.login('m.login.token', { + const res = await client.login("m.login.token", { token, initial_device_display_name: cons.DEVICE_DISPLAY_NAME, }); - const myBaseUrl = res?.well_known?.['m.homeserver']?.base_url || client.baseUrl; + const myBaseUrl = + res?.well_known?.["m.homeserver"]?.base_url || client.baseUrl; updateLocalStore(res.access_token, res.device_id, res.user_id, myBaseUrl); } // eslint-disable-next-line camelcase -async function verifyEmail(baseUrl, email, client_secret, send_attempt, next_link) { - const res = await fetch(`${baseUrl}/_matrix/client/r0/register/email/requestToken`, { - method: 'POST', - body: JSON.stringify({ - email, client_secret, send_attempt, next_link, - }), - headers: { - 'Content-Type': 'application/json; charset=utf-8', - }, - credentials: 'same-origin', - }); +async function verifyEmail( + baseUrl, + email, + client_secret, + send_attempt, + next_link +) { + const res = await fetch( + `${baseUrl}/_matrix/client/r0/register/email/requestToken`, + { + method: "POST", + body: JSON.stringify({ + email, + client_secret, + send_attempt, + next_link, + }), + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + credentials: "same-origin", + } + ); const data = await res.json(); return data; } -async function completeRegisterStage( - baseUrl, username, password, auth, -) { +async function completeRegisterStage(baseUrl, username, password, auth) { const tempClient = createTemporaryClient(baseUrl); try { @@ -83,7 +99,12 @@ async function completeRegisterStage( const data = { completed: result.completed || [] }; if (result.access_token) { data.done = true; - updateLocalStore(result.access_token, result.device_id, result.user_id, baseUrl); + updateLocalStore( + result.access_token, + result.device_id, + result.user_id, + baseUrl + ); } return data; } catch (e) { @@ -91,14 +112,22 @@ async function completeRegisterStage( const data = { completed: result.completed || [] }; if (result.access_token) { data.done = true; - updateLocalStore(result.access_token, result.device_id, result.user_id, baseUrl); + updateLocalStore( + result.access_token, + result.device_id, + result.user_id, + baseUrl + ); } return data; } } export { - createTemporaryClient, login, verifyEmail, - loginWithToken, startSsoLogin, + createTemporaryClient, + login, + verifyEmail, + loginWithToken, + startSsoLogin, completeRegisterStage, }; diff --git a/src/client/action/navigation.js b/src/client/action/navigation.js index 4ee78a63..a6d0d6ad 100644 --- a/src/client/action/navigation.js +++ b/src/client/action/navigation.js @@ -1,5 +1,5 @@ -import appDispatcher from '../dispatcher'; -import cons from '../state/cons'; +import appDispatcher from "../dispatcher"; +import cons from "../state/cons"; export function selectTab(tabId) { appDispatcher.dispatch({ diff --git a/src/client/action/notifications.js b/src/client/action/notifications.js index a869632a..4561be01 100644 --- a/src/client/action/notifications.js +++ b/src/client/action/notifications.js @@ -1,4 +1,4 @@ -import initMatrix from '../initMatrix'; +import initMatrix from "../initMatrix"; // eslint-disable-next-line import/prefer-default-export export async function markAsRead(roomId) { diff --git a/src/client/action/room.js b/src/client/action/room.js index 996c2680..7cb449e7 100644 --- a/src/client/action/room.js +++ b/src/client/action/room.js @@ -1,7 +1,7 @@ -import initMatrix from '../initMatrix'; -import appDispatcher from '../dispatcher'; -import cons from '../state/cons'; -import { getIdServer } from '../../util/matrixUtil'; +import initMatrix from "../initMatrix"; +import appDispatcher from "../dispatcher"; +import cons from "../state/cons"; +import { getIdServer } from "../../util/matrixUtil"; /** * https://github.com/matrix-org/matrix-react-sdk/blob/1e6c6e9d800890c732d60429449bc280de01a647/src/Rooms.js#L73 @@ -11,10 +11,11 @@ import { getIdServer } from '../../util/matrixUtil'; */ function addRoomToMDirect(roomId, userId) { const mx = initMatrix.matrixClient; - const mDirectsEvent = mx.getAccountData('m.direct'); + const mDirectsEvent = mx.getAccountData("m.direct"); let userIdToRoomIds = {}; - if (typeof mDirectsEvent !== 'undefined') userIdToRoomIds = mDirectsEvent.getContent(); + if (typeof mDirectsEvent !== "undefined") + userIdToRoomIds = mDirectsEvent.getContent(); // remove it from the lists of any others users // (it can only be a DM room for one person) @@ -38,7 +39,7 @@ function addRoomToMDirect(roomId, userId) { userIdToRoomIds[userId] = roomIds; } - return mx.setAccountData('m.direct', userIdToRoomIds); + return mx.setAccountData("m.direct", userIdToRoomIds); } /** @@ -58,7 +59,10 @@ function guessDMRoomTargetId(room, myUserId) { room.getJoinedMembers().forEach((member) => { if (member.userId === myUserId) return; - if (typeof oldestMemberTs === 'undefined' || (member.events.member && member.events.member.getTs() < oldestMemberTs)) { + if ( + typeof oldestMemberTs === "undefined" || + (member.events.member && member.events.member.getTs() < oldestMemberTs) + ) { oldestMember = member; oldestMemberTs = member.events.member.getTs(); } @@ -69,13 +73,16 @@ function guessDMRoomTargetId(room, myUserId) { room.currentState.getMembers().forEach((member) => { if (member.userId === myUserId) return; - if (typeof oldestMemberTs === 'undefined' || (member.events.member && member.events.member.getTs() < oldestMemberTs)) { + if ( + typeof oldestMemberTs === "undefined" || + (member.events.member && member.events.member.getTs() < oldestMemberTs) + ) { oldestMember = member; oldestMemberTs = member.events.member.getTs(); } }); - if (typeof oldestMember === 'undefined') return myUserId; + if (typeof oldestMember === "undefined") return myUserId; return oldestMember.userId; } @@ -97,14 +104,17 @@ function convertToRoom(roomId) { */ async function join(roomIdOrAlias, isDM = false, via = undefined) { const mx = initMatrix.matrixClient; - const roomIdParts = roomIdOrAlias.split(':'); + const roomIdParts = roomIdOrAlias.split(":"); const viaServers = via || [roomIdParts[1]]; try { const resultRoom = await mx.joinRoom(roomIdOrAlias, { viaServers }); if (isDM) { - const targetUserId = guessDMRoomTargetId(mx.getRoom(resultRoom.roomId), mx.getUserId()); + const targetUserId = guessDMRoomTargetId( + mx.getRoom(resultRoom.roomId), + mx.getUserId() + ); await addRoomToMDirect(resultRoom.roomId, targetUserId); } appDispatcher.dispatch({ @@ -134,7 +144,7 @@ async function leave(roomId) { isDM, }); } catch { - console.error('Unable to leave room.'); + console.error("Unable to leave room."); } } @@ -142,7 +152,7 @@ async function create(options, isDM = false) { const mx = initMatrix.matrixClient; try { const result = await mx.createRoom(options); - if (isDM && typeof options.invite?.[0] === 'string') { + if (isDM && typeof options.invite?.[0] === "string") { await addRoomToMDirect(result.room_id, options.invite[0]); } appDispatcher.dispatch({ @@ -152,11 +162,17 @@ async function create(options, isDM = false) { }); return result; } catch (e) { - const errcodes = ['M_UNKNOWN', 'M_BAD_JSON', 'M_ROOM_IN_USE', 'M_INVALID_ROOM_STATE', 'M_UNSUPPORTED_ROOM_VERSION']; + const errcodes = [ + "M_UNKNOWN", + "M_BAD_JSON", + "M_ROOM_IN_USE", + "M_INVALID_ROOM_STATE", + "M_UNSUPPORTED_ROOM_VERSION", + ]; if (errcodes.includes(e.errcode)) { throw new Error(e); } - throw new Error('Something went wrong!'); + throw new Error("Something went wrong!"); } } @@ -164,16 +180,16 @@ async function createDM(userIdOrIds, isEncrypted = true) { const options = { is_direct: true, invite: Array.isArray(userIdOrIds) ? userIdOrIds : [userIdOrIds], - visibility: 'private', - preset: 'trusted_private_chat', + visibility: "private", + preset: "trusted_private_chat", initial_state: [], }; if (isEncrypted) { options.initial_state.push({ - type: 'm.room.encryption', - state_key: '', + type: "m.room.encryption", + state_key: "", content: { - algorithm: 'm.megolm.v1.aes-sha2', + algorithm: "m.megolm.v1.aes-sha2", }, }); } @@ -193,7 +209,7 @@ async function createRoom(opts) { const blockFederation = opts.blockFederation ?? false; const mx = initMatrix.matrixClient; - const visibility = joinRule === 'public' ? 'public' : 'private'; + const visibility = joinRule === "public" ? "public" : "private"; const options = { creation_content: undefined, name, @@ -204,17 +220,17 @@ async function createRoom(opts) { power_level_content_override: undefined, }; if (isSpace) { - options.creation_content = { type: 'm.space' }; + options.creation_content = { type: "m.space" }; } if (blockFederation) { - options.creation_content = { 'm.federate': false }; + options.creation_content = { "m.federate": false }; } if (isEncrypted) { options.initial_state.push({ - type: 'm.room.encryption', - state_key: '', + type: "m.room.encryption", + state_key: "", content: { - algorithm: 'm.megolm.v1.aes-sha2', + algorithm: "m.megolm.v1.aes-sha2", }, }); } @@ -227,7 +243,7 @@ async function createRoom(opts) { } if (parentId) { options.initial_state.push({ - type: 'm.space.parent', + type: "m.space.parent", state_key: parentId, content: { canonical: true, @@ -235,22 +251,24 @@ async function createRoom(opts) { }, }); } - if (parentId && joinRule === 'restricted') { + if (parentId && joinRule === "restricted") { const caps = await mx.getCapabilities(); - if (caps['m.room_versions'].available?.['9'] !== 'stable') { + if (caps["m.room_versions"].available?.["9"] !== "stable") { throw new Error("ERROR: The server doesn't support restricted rooms"); } - if (Number(caps['m.room_versions'].default) < 9) { - options.room_version = '9'; + if (Number(caps["m.room_versions"].default) < 9) { + options.room_version = "9"; } options.initial_state.push({ - type: 'm.room.join_rules', + type: "m.room.join_rules", content: { - join_rule: 'restricted', - allow: [{ - type: 'm.room_membership', - room_id: parentId, - }], + join_rule: "restricted", + allow: [ + { + type: "m.room_membership", + room_id: parentId, + }, + ], }, }); } @@ -258,11 +276,16 @@ async function createRoom(opts) { const result = await create(options); if (parentId) { - await mx.sendStateEvent(parentId, 'm.space.child', { - auto_join: false, - suggested: false, - via: [getIdServer(mx.getUserId())], - }, result.room_id); + await mx.sendStateEvent( + parentId, + "m.space.child", + { + auto_join: false, + suggested: false, + via: [getIdServer(mx.getUserId())], + }, + result.room_id + ); } return result; @@ -315,43 +338,73 @@ async function setPowerLevel(roomId, userId, powerLevel) { const mx = initMatrix.matrixClient; const room = mx.getRoom(roomId); - const powerlevelEvent = room.currentState.getStateEvents('m.room.power_levels')[0]; + const powerlevelEvent = room.currentState.getStateEvents( + "m.room.power_levels" + )[0]; - const result = await mx.setPowerLevel(roomId, userId, powerLevel, powerlevelEvent); + const result = await mx.setPowerLevel( + roomId, + userId, + powerLevel, + powerlevelEvent + ); return result; } async function setMyRoomNick(roomId, nick) { const mx = initMatrix.matrixClient; const room = mx.getRoom(roomId); - const mEvent = room.currentState.getStateEvents('m.room.member', mx.getUserId()); + const mEvent = room.currentState.getStateEvents( + "m.room.member", + mx.getUserId() + ); const content = mEvent?.getContent(); if (!content) return; - await mx.sendStateEvent(roomId, 'm.room.member', { - ...content, - displayname: nick, - }, mx.getUserId()); + await mx.sendStateEvent( + roomId, + "m.room.member", + { + ...content, + displayname: nick, + }, + mx.getUserId() + ); } async function setMyRoomAvatar(roomId, mxc) { const mx = initMatrix.matrixClient; const room = mx.getRoom(roomId); - const mEvent = room.currentState.getStateEvents('m.room.member', mx.getUserId()); + const mEvent = room.currentState.getStateEvents( + "m.room.member", + mx.getUserId() + ); const content = mEvent?.getContent(); if (!content) return; - await mx.sendStateEvent(roomId, 'm.room.member', { - ...content, - avatar_url: mxc, - }, mx.getUserId()); + await mx.sendStateEvent( + roomId, + "m.room.member", + { + ...content, + avatar_url: mxc, + }, + mx.getUserId() + ); } export { convertToDm, convertToRoom, - join, leave, - createDM, createRoom, - invite, kick, ban, unban, - ignore, unignore, + join, + leave, + createDM, + createRoom, + invite, + kick, + ban, + unban, + ignore, + unignore, setPowerLevel, - setMyRoomNick, setMyRoomAvatar, + setMyRoomNick, + setMyRoomAvatar, }; diff --git a/src/client/action/roomTimeline.js b/src/client/action/roomTimeline.js index 41c62d4f..a8ad450d 100644 --- a/src/client/action/roomTimeline.js +++ b/src/client/action/roomTimeline.js @@ -1,10 +1,15 @@ -import initMatrix from '../initMatrix'; +import initMatrix from "../initMatrix"; async function redactEvent(roomId, eventId, reason) { const mx = initMatrix.matrixClient; try { - await mx.redactEvent(roomId, eventId, undefined, typeof reason === 'undefined' ? undefined : { reason }); + await mx.redactEvent( + roomId, + eventId, + undefined, + typeof reason === "undefined" ? undefined : { reason } + ); return true; } catch (e) { throw new Error(e); @@ -14,21 +19,18 @@ async function redactEvent(roomId, eventId, reason) { async function sendReaction(roomId, toEventId, reaction, shortcode) { const mx = initMatrix.matrixClient; const content = { - 'm.relates_to': { + "m.relates_to": { event_id: toEventId, key: reaction, - rel_type: 'm.annotation', + rel_type: "m.annotation", }, }; - if (typeof shortcode === 'string') content.shortcode = shortcode; + if (typeof shortcode === "string") content.shortcode = shortcode; try { - await mx.sendEvent(roomId, 'm.reaction', content); + await mx.sendEvent(roomId, "m.reaction", content); } catch (e) { throw new Error(e); } } -export { - redactEvent, - sendReaction, -}; +export { redactEvent, sendReaction }; diff --git a/src/client/action/settings.js b/src/client/action/settings.js index 7b539c8d..0b2a1ce9 100644 --- a/src/client/action/settings.js +++ b/src/client/action/settings.js @@ -1,5 +1,5 @@ -import appDispatcher from '../dispatcher'; -import cons from '../state/cons'; +import appDispatcher from "../dispatcher"; +import cons from "../state/cons"; export function toggleSystemTheme() { appDispatcher.dispatch({ diff --git a/src/client/dispatcher.js b/src/client/dispatcher.js index 12a48721..9f6c70c3 100644 --- a/src/client/dispatcher.js +++ b/src/client/dispatcher.js @@ -1,4 +1,4 @@ -import { Dispatcher } from 'flux'; +import { Dispatcher } from "flux"; const appDispatcher = new Dispatcher(); export default appDispatcher; diff --git a/src/client/event/hotkeys.js b/src/client/event/hotkeys.js index e59ce3d7..e3ae8cb6 100644 --- a/src/client/event/hotkeys.js +++ b/src/client/event/hotkeys.js @@ -1,6 +1,6 @@ -import { openSearch, toggleRoomSettings } from '../action/navigation'; -import navigation from '../state/navigation'; -import { markAsRead } from '../action/notifications'; +import { openSearch, toggleRoomSettings } from "../action/navigation"; +import navigation from "../state/navigation"; +import { markAsRead } from "../action/notifications"; function shouldFocusMessageField(code) { // do not focus on F keys @@ -8,18 +8,18 @@ function shouldFocusMessageField(code) { // do not focus on numlock/scroll lock if ( - code.metaKey - || code.startsWith('OS') - || code.startsWith('Meta') - || code.startsWith('Shift') - || code.startsWith('Alt') - || code.startsWith('Control') - || code.startsWith('Arrow') - || code === 'Tab' - || code === 'Space' - || code === 'Enter' - || code === 'NumLock' - || code === 'ScrollLock' + code.metaKey || + code.startsWith("OS") || + code.startsWith("Meta") || + code.startsWith("Shift") || + code.startsWith("Alt") || + code.startsWith("Control") || + code.startsWith("Arrow") || + code === "Tab" || + code === "Space" || + code === "Enter" || + code === "NumLock" || + code === "ScrollLock" ) { return false; } @@ -31,20 +31,22 @@ function listenKeyboard(event) { // Ctrl/Cmd + if (event.ctrlKey || event.metaKey) { // open search modal - if (event.key === 'k') { + if (event.key === "k") { event.preventDefault(); if (navigation.isRawModalVisible) return; openSearch(); } // focus message field on paste - if (event.key === 'v') { + if (event.key === "v") { if (navigation.isRawModalVisible) return; - const msgTextarea = document.getElementById('message-textarea'); + const msgTextarea = document.getElementById("message-textarea"); const { activeElement } = document; - if (activeElement !== msgTextarea - && ['input', 'textarea'].includes(activeElement.tagName.toLowerCase()) - ) return; + if ( + activeElement !== msgTextarea && + ["input", "textarea"].includes(activeElement.tagName.toLowerCase()) + ) + return; msgTextarea?.focus(); } } @@ -52,7 +54,7 @@ function listenKeyboard(event) { if (!event.ctrlKey && !event.altKey && !event.metaKey) { if (navigation.isRawModalVisible) return; - if (event.key === 'Escape') { + if (event.key === "Escape") { if (navigation.isRoomSettings) { toggleRoomSettings(); return; @@ -63,25 +65,29 @@ function listenKeyboard(event) { } } - if (['input', 'textarea'].includes(document.activeElement.tagName.toLowerCase())) { + if ( + ["input", "textarea"].includes( + document.activeElement.tagName.toLowerCase() + ) + ) { return; } // focus the text field on most keypresses if (shouldFocusMessageField(event.code)) { // press any key to focus and type in message field - const msgTextarea = document.getElementById('message-textarea'); + const msgTextarea = document.getElementById("message-textarea"); msgTextarea?.focus(); } } } function initHotkeys() { - document.body.addEventListener('keydown', listenKeyboard); + document.body.addEventListener("keydown", listenKeyboard); } function removeHotkeys() { - document.body.removeEventListener('keydown', listenKeyboard); + document.body.removeEventListener("keydown", listenKeyboard); } export { initHotkeys, removeHotkeys }; diff --git a/src/client/event/roomList.js b/src/client/event/roomList.js index 6592d67f..5cbfa2d8 100644 --- a/src/client/event/roomList.js +++ b/src/client/event/roomList.js @@ -1,6 +1,6 @@ -import cons from '../state/cons'; -import navigation from '../state/navigation'; -import { selectTab, selectSpace, selectRoom } from '../action/navigation'; +import cons from "../state/cons"; +import navigation from "../state/navigation"; +import { selectTab, selectSpace, selectRoom } from "../action/navigation"; function initRoomListListener(roomList) { const listenRoomLeave = (roomId) => { diff --git a/src/client/initMatrix.js b/src/client/initMatrix.js index 420f3154..6efcb620 100644 --- a/src/client/initMatrix.js +++ b/src/client/initMatrix.js @@ -1,15 +1,15 @@ -import EventEmitter from 'events'; -import * as sdk from 'matrix-js-sdk'; -import Olm from '@matrix-org/olm'; +import EventEmitter from "events"; +import * as sdk from "matrix-js-sdk"; +import Olm from "@matrix-org/olm"; // import { logger } from 'matrix-js-sdk/lib/logger'; -import { secret } from './state/auth'; -import RoomList from './state/RoomList'; -import AccountData from './state/AccountData'; -import RoomsInput from './state/RoomsInput'; -import Notifications from './state/Notifications'; -import { cryptoCallbacks } from './state/secretStorageKeys'; -import navigation from './state/navigation'; +import { secret } from "./state/auth"; +import RoomList from "./state/RoomList"; +import AccountData from "./state/AccountData"; +import RoomsInput from "./state/RoomsInput"; +import Notifications from "./state/Notifications"; +import { cryptoCallbacks } from "./state/secretStorageKeys"; +import navigation from "./state/navigation"; global.Olm = Olm; @@ -32,7 +32,7 @@ class InitMatrix extends EventEmitter { const indexedDBStore = new sdk.IndexedDBStore({ indexedDB: global.indexedDB, localStorage: global.localStorage, - dbName: 'web-sync-store', + dbName: "web-sync-store", }); await indexedDBStore.startup(); @@ -41,13 +41,14 @@ class InitMatrix extends EventEmitter { accessToken: secret.accessToken, userId: secret.userId, store: indexedDBStore, - cryptoStore: new sdk.IndexedDBCryptoStore(global.indexedDB, 'crypto-store'), + cryptoStore: new sdk.IndexedDBCryptoStore( + global.indexedDB, + "crypto-store" + ), deviceId: secret.deviceId, timelineSupport: true, cryptoCallbacks, - verificationMethods: [ - 'm.sas.v1', - ], + verificationMethods: ["m.sas.v1"], }); await this.matrixClient.initCrypto(); @@ -61,14 +62,14 @@ class InitMatrix extends EventEmitter { setupSync() { const sync = { NULL: () => { - console.log('NULL state'); + console.log("NULL state"); }, SYNCING: () => { - console.log('SYNCING state'); + console.log("SYNCING state"); }, PREPARED: (prevState) => { - console.log('PREPARED state'); - console.log('Previous state: ', prevState); + console.log("PREPARED state"); + console.log("Previous state: ", prevState); // TODO: remove global.initMatrix at end global.initMatrix = this; if (prevState === null) { @@ -76,30 +77,30 @@ class InitMatrix extends EventEmitter { this.accountData = new AccountData(this.roomList); this.roomsInput = new RoomsInput(this.matrixClient, this.roomList); this.notifications = new Notifications(this.roomList); - this.emit('init_loading_finished'); + this.emit("init_loading_finished"); this.notifications._initNoti(); } else { this.notifications?._initNoti(); } }, RECONNECTING: () => { - console.log('RECONNECTING state'); + console.log("RECONNECTING state"); }, CATCHUP: () => { - console.log('CATCHUP state'); + console.log("CATCHUP state"); }, ERROR: () => { - console.log('ERROR state'); + console.log("ERROR state"); }, STOPPED: () => { - console.log('STOPPED state'); + console.log("STOPPED state"); }, }; - this.matrixClient.on('sync', (state, prevState) => sync[state](prevState)); + this.matrixClient.on("sync", (state, prevState) => sync[state](prevState)); } listenEvents() { - this.matrixClient.on('Session.logged_out', async () => { + this.matrixClient.on("Session.logged_out", async () => { this.matrixClient.stopClient(); await this.matrixClient.clearStores(); window.localStorage.clear(); diff --git a/src/client/state/AccountData.js b/src/client/state/AccountData.js index 6fc811a3..b5b1a652 100644 --- a/src/client/state/AccountData.js +++ b/src/client/state/AccountData.js @@ -1,6 +1,6 @@ -import EventEmitter from 'events'; -import appDispatcher from '../dispatcher'; -import cons from './cons'; +import EventEmitter from "events"; +import appDispatcher from "../dispatcher"; +import cons from "./cons"; class AccountData extends EventEmitter { constructor(roomList) { @@ -22,7 +22,9 @@ class AccountData extends EventEmitter { } _getAccountData() { - return this.matrixClient.getAccountData(cons.IN_CINNY_SPACES)?.getContent() || {}; + return ( + this.matrixClient.getAccountData(cons.IN_CINNY_SPACES)?.getContent() || {} + ); } _populateSpaceShortcut() { @@ -81,13 +83,19 @@ class AccountData extends EventEmitter { addRoomId(action.roomId); } this._updateSpaceShortcutData([...this.spaceShortcut]); - this.emit(cons.events.accountData.SPACE_SHORTCUT_UPDATED, action.roomId); + this.emit( + cons.events.accountData.SPACE_SHORTCUT_UPDATED, + action.roomId + ); }, [cons.actions.accountData.DELETE_SPACE_SHORTCUT]: () => { if (!this.spaceShortcut.has(action.roomId)) return; this.spaceShortcut.delete(action.roomId); this._updateSpaceShortcutData([...this.spaceShortcut]); - this.emit(cons.events.accountData.SPACE_SHORTCUT_UPDATED, action.roomId); + this.emit( + cons.events.accountData.SPACE_SHORTCUT_UPDATED, + action.roomId + ); }, [cons.actions.accountData.MOVE_SPACE_SHORTCUTS]: () => { const { roomId, toIndex } = action; @@ -104,20 +112,26 @@ class AccountData extends EventEmitter { if (this.categorizedSpaces.has(action.roomId)) return; this.categorizedSpaces.add(action.roomId); this._updateCategorizedSpacesData([...this.categorizedSpaces]); - this.emit(cons.events.accountData.CATEGORIZE_SPACE_UPDATED, action.roomId); + this.emit( + cons.events.accountData.CATEGORIZE_SPACE_UPDATED, + action.roomId + ); }, [cons.actions.accountData.UNCATEGORIZE_SPACE]: () => { if (!this.categorizedSpaces.has(action.roomId)) return; this.categorizedSpaces.delete(action.roomId); this._updateCategorizedSpacesData([...this.categorizedSpaces]); - this.emit(cons.events.accountData.CATEGORIZE_SPACE_UPDATED, action.roomId); + this.emit( + cons.events.accountData.CATEGORIZE_SPACE_UPDATED, + action.roomId + ); }, }; actions[action.type]?.(); } _listenEvents() { - this.matrixClient.on('accountData', (event) => { + this.matrixClient.on("accountData", (event) => { if (event.getType() !== cons.IN_CINNY_SPACES) return; this._populateSpaceShortcut(); this.emit(cons.events.accountData.SPACE_SHORTCUT_UPDATED); diff --git a/src/client/state/Notifications.js b/src/client/state/Notifications.js index db4610a3..e30b95c4 100644 --- a/src/client/state/Notifications.js +++ b/src/client/state/Notifications.js @@ -1,37 +1,39 @@ -import EventEmitter from 'events'; -import renderAvatar from '../../app/atoms/avatar/render'; -import { cssColorMXID } from '../../util/colorMXID'; -import { selectRoom } from '../action/navigation'; -import cons from './cons'; -import navigation from './navigation'; -import settings from './settings'; -import { setFavicon } from '../../util/common'; +import EventEmitter from "events"; +import renderAvatar from "../../app/atoms/avatar/render"; +import { cssColorMXID } from "../../util/colorMXID"; +import { selectRoom } from "../action/navigation"; +import cons from "./cons"; +import navigation from "./navigation"; +import settings from "./settings"; +import { setFavicon } from "../../util/common"; -import LogoSVG from '../../../public/res/svg/cinny.svg'; -import LogoUnreadSVG from '../../../public/res/svg/cinny-unread.svg'; -import LogoHighlightSVG from '../../../public/res/svg/cinny-highlight.svg'; -import { html, plain } from '../../util/markdown'; +import LogoSVG from "../../../public/res/svg/cinny.svg"; +import LogoUnreadSVG from "../../../public/res/svg/cinny-unread.svg"; +import LogoHighlightSVG from "../../../public/res/svg/cinny-highlight.svg"; +import { html, plain } from "../../util/markdown"; function isNotifEvent(mEvent) { const eType = mEvent.getType(); if (!cons.supportEventTypes.includes(eType)) return false; - if (eType === 'm.room.member') return false; + if (eType === "m.room.member") return false; if (mEvent.isRedacted()) return false; - if (mEvent.getRelation()?.rel_type === 'm.replace') return false; + if (mEvent.getRelation()?.rel_type === "m.replace") return false; return true; } function isMutedRule(rule) { - return rule.actions[0] === 'dont_notify' && rule.conditions[0].kind === 'event_match'; + return ( + rule.actions[0] === "dont_notify" && + rule.conditions[0].kind === "event_match" + ); } function findMutedRule(overrideRules, roomId) { - return overrideRules.find((rule) => ( - rule.rule_id === roomId - && isMutedRule(rule) - )); + return overrideRules.find( + (rule) => rule.rule_id === roomId && isMutedRule(rule) + ); } class Notifications extends EventEmitter { @@ -63,8 +65,8 @@ class Notifications extends EventEmitter { if (this.getNotiType(room.roomId) === cons.notifs.MUTE) return; if (this.doesRoomHaveUnread(room) === false) return; - const total = room.getUnreadNotificationCount('total'); - const highlight = room.getUnreadNotificationCount('highlight'); + const total = room.getUnreadNotificationCount("total"); + const highlight = room.getUnreadNotificationCount("highlight"); this._setNoti(room.roomId, total ?? 0, highlight ?? 0); }; [...this.roomList.rooms].forEach(addNoti); @@ -95,25 +97,28 @@ class Notifications extends EventEmitter { const mx = this.matrixClient; let pushRule; try { - pushRule = mx.getRoomPushRule('global', roomId); + pushRule = mx.getRoomPushRule("global", roomId); } catch { pushRule = undefined; } if (pushRule === undefined) { - const overrideRules = mx.getAccountData('m.push_rules')?.getContent()?.global?.override; + const overrideRules = mx.getAccountData("m.push_rules")?.getContent() + ?.global?.override; if (overrideRules === undefined) return cons.notifs.DEFAULT; const isMuted = findMutedRule(overrideRules, roomId); return isMuted ? cons.notifs.MUTE : cons.notifs.DEFAULT; } - if (pushRule.actions[0] === 'notify') return cons.notifs.ALL_MESSAGES; + if (pushRule.actions[0] === "notify") return cons.notifs.ALL_MESSAGES; return cons.notifs.MENTIONS_AND_KEYWORDS; } getNoti(roomId) { - return this.roomIdToNoti.get(roomId) || { total: 0, highlight: 0, from: null }; + return ( + this.roomIdToNoti.get(roomId) || { total: 0, highlight: 0, from: null } + ); } getTotalNoti(roomId) { @@ -179,7 +184,12 @@ class Notifications extends EventEmitter { noti.from.add(fromId); } this.roomIdToNoti.set(id, noti); - this.emit(cons.events.notifications.NOTI_CHANGED, id, noti.total, prevTotal); + this.emit( + cons.events.notifications.NOTI_CHANGED, + id, + noti.total, + prevTotal + ); }; const noti = this.getNoti(roomId); @@ -216,7 +226,12 @@ class Notifications extends EventEmitter { this.emit(cons.events.notifications.NOTI_CHANGED, id, null, prevTotal); } else { this.roomIdToNoti.set(id, noti); - this.emit(cons.events.notifications.NOTI_CHANGED, id, noti.total, prevTotal); + this.emit( + cons.events.notifications.NOTI_CHANGED, + id, + noti.total, + prevTotal + ); } }; @@ -234,7 +249,11 @@ class Notifications extends EventEmitter { const actions = this.matrixClient.getPushActionsForEvent(mEvent); if (!actions?.notify) return; - if (navigation.selectedRoomId === room.roomId && document.visibilityState === 'visible') return; + if ( + navigation.selectedRoomId === room.roomId && + document.visibilityState === "visible" + ) + return; if (mEvent.isEncrypted()) { await mEvent.attemptDecryption(this.matrixClient.crypto); @@ -252,7 +271,12 @@ class Notifications extends EventEmitter { const icon = await renderAvatar({ text: mEvent.sender.name, bgColor: cssColorMXID(mEvent.getSender()), - imageSrc: mEvent.sender?.getAvatarUrl(this.matrixClient.baseUrl, iconSize, iconSize, 'crop'), + imageSrc: mEvent.sender?.getAvatarUrl( + this.matrixClient.baseUrl, + iconSize, + iconSize, + "crop" + ), size: iconSize, borderRadius: 8, scale: 8, @@ -260,9 +284,9 @@ class Notifications extends EventEmitter { const content = mEvent.getContent(); - const state = { kind: 'notification', onlyPlain: true }; + const state = { kind: "notification", onlyPlain: true }; let body; - if (content.format === 'org.matrix.custom.html') { + if (content.format === "org.matrix.custom.html") { body = html(content.formatted_body, state); } else { body = plain(content.body, state); @@ -305,20 +329,20 @@ class Notifications extends EventEmitter { _playNotiSound() { if (!this._notiAudio) { - this._notiAudio = document.getElementById('notificationSound'); + this._notiAudio = document.getElementById("notificationSound"); } this._notiAudio.play(); } _playInviteSound() { if (!this._inviteAudio) { - this._inviteAudio = document.getElementById('inviteSound'); + this._inviteAudio = document.getElementById("inviteSound"); } this._inviteAudio.play(); } _listenEvents() { - this.matrixClient.on('Room.timeline', (mEvent, room) => { + this.matrixClient.on("Room.timeline", (mEvent, room) => { if (mEvent.isRedaction()) this._deletePopupNoti(mEvent.event.redacts); if (room.isSpaceRoom()) return; @@ -330,8 +354,8 @@ class Notifications extends EventEmitter { if (lastTimelineEvent.getId() !== mEvent.getId()) return; if (mEvent.getSender() === this.matrixClient.getUserId()) return; - const total = room.getUnreadNotificationCount('total'); - const highlight = room.getUnreadNotificationCount('highlight'); + const total = room.getUnreadNotificationCount("total"); + const highlight = room.getUnreadNotificationCount("highlight"); if (this.getNotiType(room.roomId) === cons.notifs.MUTE) { this.deleteNoti(room.roomId, total ?? 0, highlight ?? 0); @@ -340,13 +364,13 @@ class Notifications extends EventEmitter { this._setNoti(room.roomId, total ?? 0, highlight ?? 0); - if (this.matrixClient.getSyncState() === 'SYNCING') { + if (this.matrixClient.getSyncState() === "SYNCING") { this._displayPopupNoti(mEvent, room); } }); - this.matrixClient.on('accountData', (mEvent, oldMEvent) => { - if (mEvent.getType() === 'm.push_rules') { + this.matrixClient.on("accountData", (mEvent, oldMEvent) => { + if (mEvent.getType() === "m.push_rules") { const override = mEvent?.getContent()?.global?.override; const oldOverride = oldMEvent?.getContent()?.global?.override; if (!override || !oldOverride) return; @@ -364,30 +388,38 @@ class Notifications extends EventEmitter { return true; }; - const mutedRules = override.filter((rule) => isMuteToggled(rule, oldOverride)); - const unMutedRules = oldOverride.filter((rule) => isMuteToggled(rule, override)); + const mutedRules = override.filter((rule) => + isMuteToggled(rule, oldOverride) + ); + const unMutedRules = oldOverride.filter((rule) => + isMuteToggled(rule, override) + ); mutedRules.forEach((rule) => { this.emit(cons.events.notifications.MUTE_TOGGLED, rule.rule_id, true); this.deleteNoti(rule.rule_id); }); unMutedRules.forEach((rule) => { - this.emit(cons.events.notifications.MUTE_TOGGLED, rule.rule_id, false); + this.emit( + cons.events.notifications.MUTE_TOGGLED, + rule.rule_id, + false + ); const room = this.matrixClient.getRoom(rule.rule_id); if (!this.doesRoomHaveUnread(room)) return; - const total = room.getUnreadNotificationCount('total'); - const highlight = room.getUnreadNotificationCount('highlight'); + const total = room.getUnreadNotificationCount("total"); + const highlight = room.getUnreadNotificationCount("highlight"); this._setNoti(room.roomId, total ?? 0, highlight ?? 0); }); } }); - this.matrixClient.on('Room.receipt', (mEvent, room) => { - if (mEvent.getType() === 'm.receipt') { + this.matrixClient.on("Room.receipt", (mEvent, room) => { + if (mEvent.getType() === "m.receipt") { if (room.isSpaceRoom()) return; const content = mEvent.getContent(); const readedEventId = Object.keys(content)[0]; - const readerUserId = Object.keys(content[readedEventId]['m.read'])[0]; + const readerUserId = Object.keys(content[readedEventId]["m.read"])[0]; if (readerUserId !== this.matrixClient.getUserId()) return; this.deleteNoti(room.roomId); @@ -396,11 +428,11 @@ class Notifications extends EventEmitter { } }); - this.matrixClient.on('Room.myMembership', (room, membership) => { - if (membership === 'leave' && this.hasNoti(room.roomId)) { + this.matrixClient.on("Room.myMembership", (room, membership) => { + if (membership === "leave" && this.hasNoti(room.roomId)) { this.deleteNoti(room.roomId); } - if (membership === 'invite') { + if (membership === "invite") { this._playInviteSound(); } }); diff --git a/src/client/state/RoomList.js b/src/client/state/RoomList.js index a1570480..cb068cc9 100644 --- a/src/client/state/RoomList.js +++ b/src/client/state/RoomList.js @@ -1,9 +1,12 @@ -import EventEmitter from 'events'; -import appDispatcher from '../dispatcher'; -import cons from './cons'; +import EventEmitter from "events"; +import appDispatcher from "../dispatcher"; +import cons from "./cons"; function isMEventSpaceChild(mEvent) { - return mEvent.getType() === 'm.space.child' && Object.keys(mEvent.getContent()).length > 0; + return ( + mEvent.getType() === "m.space.child" && + Object.keys(mEvent.getContent()).length > 0 + ); } /** @@ -13,9 +16,10 @@ function isMEventSpaceChild(mEvent) { */ async function waitFor(callback, timeout = 400, maxTry = -1) { if (maxTry === 0) return false; - const isOver = async () => new Promise((resolve) => { - setTimeout(() => resolve(callback()), timeout); - }); + const isOver = async () => + new Promise((resolve) => { + setTimeout(() => resolve(callback()), timeout); + }); if (await isOver()) return true; return waitFor(callback, timeout, maxTry - 1); @@ -52,11 +56,15 @@ class RoomList extends EventEmitter { } getOrphanSpaces() { - return [...this.spaces].filter((roomId) => !this.roomIdToParents.has(roomId)); + return [...this.spaces].filter( + (roomId) => !this.roomIdToParents.has(roomId) + ); } getOrphanRooms() { - return [...this.rooms].filter((roomId) => !this.roomIdToParents.has(roomId)); + return [...this.rooms].filter( + (roomId) => !this.roomIdToParents.has(roomId) + ); } getOrphans() { @@ -67,7 +75,7 @@ class RoomList extends EventEmitter { getSpaceChildren(roomId) { const space = this.matrixClient.getRoom(roomId); if (space === null) return null; - const mSpaceChild = space?.currentState.getStateEvents('m.space.child'); + const mSpaceChild = space?.currentState.getStateEvents("m.space.child"); const children = []; mSpaceChild.forEach((mEvent) => { @@ -89,7 +97,7 @@ class RoomList extends EventEmitter { child.forEach((childId) => { const room = this.matrixClient.getRoom(childId); - if (room === null || room.getMyMembership() !== 'join') return; + if (room === null || room.getMyMembership() !== "join") return; if (room.isSpaceRoom()) categorizeSpace(childId); else mappedChild.add(childId); }); @@ -172,7 +180,7 @@ class RoomList extends EventEmitter { this.processingRooms.set(action.roomId, { roomId: action.roomId, isDM: action.isDM, - task: 'JOIN', + task: "JOIN", }); } }, @@ -187,7 +195,7 @@ class RoomList extends EventEmitter { this.processingRooms.set(action.roomId, { roomId: action.roomId, isDM: action.isDM, - task: 'CREATE', + task: "CREATE", }); } }, @@ -197,11 +205,9 @@ class RoomList extends EventEmitter { getMDirects() { const mDirectsId = new Set(); - const mDirect = this.matrixClient - .getAccountData('m.direct') - ?.getContent(); + const mDirect = this.matrixClient.getAccountData("m.direct")?.getContent(); - if (typeof mDirect === 'undefined') return mDirectsId; + if (typeof mDirect === "undefined") return mDirectsId; Object.keys(mDirect).forEach((direct) => { mDirect[direct].forEach((directId) => mDirectsId.add(directId)); @@ -220,21 +226,23 @@ class RoomList extends EventEmitter { this.inviteRooms.clear(); this.matrixClient.getRooms().forEach((room) => { const { roomId } = room; - const tombstone = room.currentState.events.get('m.room.tombstone'); - if (tombstone?.get('') !== undefined) { - const repRoomId = tombstone.get('').getContent().replacement_room; - const repRoomMembership = this.matrixClient.getRoom(repRoomId)?.getMyMembership(); - if (repRoomMembership === 'join') return; + const tombstone = room.currentState.events.get("m.room.tombstone"); + if (tombstone?.get("") !== undefined) { + const repRoomId = tombstone.get("").getContent().replacement_room; + const repRoomMembership = this.matrixClient + .getRoom(repRoomId) + ?.getMyMembership(); + if (repRoomMembership === "join") return; } - if (room.getMyMembership() === 'invite') { + if (room.getMyMembership() === "invite") { if (this._isDMInvite(room)) this.inviteDirects.add(roomId); else if (room.isSpaceRoom()) this.inviteSpaces.add(roomId); else this.inviteRooms.add(roomId); return; } - if (room.getMyMembership() !== 'join') return; + if (room.getMyMembership() !== "join") return; if (this.mDirects.has(roomId)) this.directs.add(roomId); else if (room.isSpaceRoom()) this.addToSpaces(roomId); @@ -246,13 +254,13 @@ class RoomList extends EventEmitter { if (this.mDirects.has(room.roomId)) return true; const me = room.getMember(this.matrixClient.getUserId()); const myEventContent = me.events.member.getContent(); - return myEventContent.membership === 'invite' && myEventContent.is_direct; + return myEventContent.membership === "invite" && myEventContent.is_direct; } _listenEvents() { // Update roomList when m.direct changes - this.matrixClient.on('accountData', (event) => { - if (event.getType() !== 'm.direct') return; + this.matrixClient.on("accountData", (event) => { + if (event.getType() !== "m.direct") return; const latestMDirects = this.getMDirects(); @@ -262,7 +270,7 @@ class RoomList extends EventEmitter { const myRoom = this.matrixClient.getRoom(directId); if (myRoom === null) return; - if (myRoom.getMyMembership() === 'join') { + if (myRoom.getMyMembership() === "join") { this.directs.add(directId); this.rooms.delete(directId); this.emit(cons.events.roomList.ROOMLIST_UPDATED); @@ -275,7 +283,7 @@ class RoomList extends EventEmitter { const myRoom = this.matrixClient.getRoom(directId); if (myRoom === null) return; - if (myRoom.getMyMembership() === 'join') { + if (myRoom.getMyMembership() === "join") { this.directs.delete(directId); this.rooms.add(directId); this.emit(cons.events.roomList.ROOMLIST_UPDATED); @@ -283,13 +291,13 @@ class RoomList extends EventEmitter { }); }); - this.matrixClient.on('Room.name', (room) => { + this.matrixClient.on("Room.name", (room) => { this.emit(cons.events.roomList.ROOMLIST_UPDATED); this.emit(cons.events.roomList.ROOM_PROFILE_UPDATED, room.roomId); }); - this.matrixClient.on('RoomState.events', (mEvent, state) => { - if (mEvent.getType() === 'm.space.child') { + this.matrixClient.on("RoomState.events", (mEvent, state) => { + if (mEvent.getType() === "m.space.child") { const roomId = mEvent.event.room_id; const childId = mEvent.event.state_key; if (isMEventSpaceChild(mEvent)) { @@ -304,86 +312,94 @@ class RoomList extends EventEmitter { this.emit(cons.events.roomList.ROOMLIST_UPDATED); return; } - if (mEvent.getType() === 'm.room.join_rules') { + if (mEvent.getType() === "m.room.join_rules") { this.emit(cons.events.roomList.ROOMLIST_UPDATED); return; } - if (['m.room.avatar', 'm.room.topic'].includes(mEvent.getType())) { - if (mEvent.getType() === 'm.room.avatar') { + if (["m.room.avatar", "m.room.topic"].includes(mEvent.getType())) { + if (mEvent.getType() === "m.room.avatar") { this.emit(cons.events.roomList.ROOMLIST_UPDATED); } this.emit(cons.events.roomList.ROOM_PROFILE_UPDATED, state.roomId); } }); - this.matrixClient.on('Room.myMembership', async (room, membership, prevMembership) => { - // room => prevMembership = null | invite | join | leave | kick | ban | unban - // room => membership = invite | join | leave | kick | ban | unban - const { roomId } = room; - const isRoomReady = () => this.matrixClient.getRoom(roomId) !== null; - if (['join', 'invite'].includes(membership) && isRoomReady() === false) { - if (await waitFor(isRoomReady, 200, 100) === false) return; + this.matrixClient.on( + "Room.myMembership", + async (room, membership, prevMembership) => { + // room => prevMembership = null | invite | join | leave | kick | ban | unban + // room => membership = invite | join | leave | kick | ban | unban + const { roomId } = room; + const isRoomReady = () => this.matrixClient.getRoom(roomId) !== null; + if ( + ["join", "invite"].includes(membership) && + isRoomReady() === false + ) { + if ((await waitFor(isRoomReady, 200, 100)) === false) return; + } + + if (membership === "unban") return; + + if (membership === "invite") { + if (this._isDMInvite(room)) this.inviteDirects.add(roomId); + else if (room.isSpaceRoom()) this.inviteSpaces.add(roomId); + else this.inviteRooms.add(roomId); + + this.emit(cons.events.roomList.INVITELIST_UPDATED, roomId); + return; + } + + if (prevMembership === "invite") { + if (this.inviteDirects.has(roomId)) this.inviteDirects.delete(roomId); + else if (this.inviteSpaces.has(roomId)) + this.inviteSpaces.delete(roomId); + else this.inviteRooms.delete(roomId); + + this.emit(cons.events.roomList.INVITELIST_UPDATED, roomId); + } + + if (["leave", "kick", "ban"].includes(membership)) { + if (this.directs.has(roomId)) this.directs.delete(roomId); + else if (this.spaces.has(roomId)) this.deleteFromSpaces(roomId); + else this.rooms.delete(roomId); + this.emit(cons.events.roomList.ROOM_LEAVED, roomId); + this.emit(cons.events.roomList.ROOMLIST_UPDATED); + return; + } + + // when user create room/DM OR accept room/dm invite from this client. + // we will update this.rooms/this.directs with user action + if (membership === "join" && this.processingRooms.has(roomId)) { + const procRoomInfo = this.processingRooms.get(roomId); + + if (procRoomInfo.isDM) this.directs.add(roomId); + else if (room.isSpaceRoom()) this.addToSpaces(roomId); + else this.rooms.add(roomId); + + if (procRoomInfo.task === "CREATE") + this.emit(cons.events.roomList.ROOM_CREATED, roomId); + this.emit(cons.events.roomList.ROOM_JOINED, roomId); + this.emit(cons.events.roomList.ROOMLIST_UPDATED); + + this.processingRooms.delete(roomId); + return; + } + + if (this.mDirects.has(roomId) && membership === "join") { + this.directs.add(roomId); + this.emit(cons.events.roomList.ROOM_JOINED, roomId); + this.emit(cons.events.roomList.ROOMLIST_UPDATED); + return; + } + + if (membership === "join") { + if (room.isSpaceRoom()) this.addToSpaces(roomId); + else this.rooms.add(roomId); + this.emit(cons.events.roomList.ROOM_JOINED, roomId); + this.emit(cons.events.roomList.ROOMLIST_UPDATED); + } } - - if (membership === 'unban') return; - - if (membership === 'invite') { - if (this._isDMInvite(room)) this.inviteDirects.add(roomId); - else if (room.isSpaceRoom()) this.inviteSpaces.add(roomId); - else this.inviteRooms.add(roomId); - - this.emit(cons.events.roomList.INVITELIST_UPDATED, roomId); - return; - } - - if (prevMembership === 'invite') { - if (this.inviteDirects.has(roomId)) this.inviteDirects.delete(roomId); - else if (this.inviteSpaces.has(roomId)) this.inviteSpaces.delete(roomId); - else this.inviteRooms.delete(roomId); - - this.emit(cons.events.roomList.INVITELIST_UPDATED, roomId); - } - - if (['leave', 'kick', 'ban'].includes(membership)) { - if (this.directs.has(roomId)) this.directs.delete(roomId); - else if (this.spaces.has(roomId)) this.deleteFromSpaces(roomId); - else this.rooms.delete(roomId); - this.emit(cons.events.roomList.ROOM_LEAVED, roomId); - this.emit(cons.events.roomList.ROOMLIST_UPDATED); - return; - } - - // when user create room/DM OR accept room/dm invite from this client. - // we will update this.rooms/this.directs with user action - if (membership === 'join' && this.processingRooms.has(roomId)) { - const procRoomInfo = this.processingRooms.get(roomId); - - if (procRoomInfo.isDM) this.directs.add(roomId); - else if (room.isSpaceRoom()) this.addToSpaces(roomId); - else this.rooms.add(roomId); - - if (procRoomInfo.task === 'CREATE') this.emit(cons.events.roomList.ROOM_CREATED, roomId); - this.emit(cons.events.roomList.ROOM_JOINED, roomId); - this.emit(cons.events.roomList.ROOMLIST_UPDATED); - - this.processingRooms.delete(roomId); - return; - } - - if (this.mDirects.has(roomId) && membership === 'join') { - this.directs.add(roomId); - this.emit(cons.events.roomList.ROOM_JOINED, roomId); - this.emit(cons.events.roomList.ROOMLIST_UPDATED); - return; - } - - if (membership === 'join') { - if (room.isSpaceRoom()) this.addToSpaces(roomId); - else this.rooms.add(roomId); - this.emit(cons.events.roomList.ROOM_JOINED, roomId); - this.emit(cons.events.roomList.ROOMLIST_UPDATED); - } - }); + ); } } export default RoomList; diff --git a/src/client/state/RoomTimeline.js b/src/client/state/RoomTimeline.js index 57d91c14..726f3199 100644 --- a/src/client/state/RoomTimeline.js +++ b/src/client/state/RoomTimeline.js @@ -1,15 +1,15 @@ -import EventEmitter from 'events'; -import initMatrix from '../initMatrix'; -import cons from './cons'; +import EventEmitter from "events"; +import initMatrix from "../initMatrix"; +import cons from "./cons"; -import settings from './settings'; +import settings from "./settings"; function isEdited(mEvent) { - return mEvent.getRelation()?.rel_type === 'm.replace'; + return mEvent.getRelation()?.rel_type === "m.replace"; } function isReaction(mEvent) { - return mEvent.getType() === 'm.reaction'; + return mEvent.getType() === "m.reaction"; } function hideMemberEvents(mEvent) { @@ -17,11 +17,16 @@ function hideMemberEvents(mEvent) { const prevContent = mEvent.getPrevContent(); const { membership } = content; if (settings.hideMembershipEvents) { - if (membership === 'invite' || membership === 'ban' || membership === 'leave') return true; - if (prevContent.membership !== 'join') return true; + if ( + membership === "invite" || + membership === "ban" || + membership === "leave" + ) + return true; + if (prevContent.membership !== "join") return true; } if (settings.hideNickAvatarEvents) { - if (membership === 'join' && prevContent.membership === 'join') return true; + if (membership === "join" && prevContent.membership === "join") return true; } return false; } @@ -36,7 +41,7 @@ function addToMap(myMap, mEvent) { if (relateToId === null) return null; const mEventId = mEvent.getId(); - if (typeof myMap.get(relateToId) === 'undefined') myMap.set(relateToId, []); + if (typeof myMap.get(relateToId) === "undefined") myMap.set(relateToId, []); const mEvents = myMap.get(relateToId); if (mEvents.find((ev) => ev.getId() === mEventId)) return mEvent; mEvents.push(mEvent); @@ -107,9 +112,9 @@ class RoomTimeline extends EventEmitter { } canPaginateBackward() { - if (this.timeline[0]?.getType() === 'm.room.create') return false; + if (this.timeline[0]?.getType() === "m.room.create") return false; const tm = getFirstLinkedTimeline(this.activeTimeline); - return tm.getPaginationToken('b') !== null; + return tm.getPaginationToken("b") !== null; } canPaginateForward() { @@ -125,7 +130,7 @@ class RoomTimeline extends EventEmitter { } addToTimeline(mEvent) { - if (mEvent.getType() === 'm.room.member' && hideMemberEvents(mEvent)) { + if (mEvent.getType() === "m.room.member" && hideMemberEvents(mEvent)) { return; } if (mEvent.isRedacted()) return; @@ -154,7 +159,8 @@ class RoomTimeline extends EventEmitter { } async _reset() { - if (this.isEncrypted()) await this.decryptAllEventsOfTimeline(this.activeTimeline); + if (this.isEncrypted()) + await this.decryptAllEventsOfTimeline(this.activeTimeline); this._populateTimelines(); if (!this.initialized) { this.initialized = true; @@ -173,7 +179,10 @@ class RoomTimeline extends EventEmitter { // we use first unfiltered EventTimelineSet for room pagination. const timelineSet = this.getUnfilteredTimelineSet(); try { - const eventTimeline = await this.matrixClient.getEventTimeline(timelineSet, eventId); + const eventTimeline = await this.matrixClient.getEventTimeline( + timelineSet, + eventId + ); this.activeTimeline = eventTimeline; await this._reset(); this.emit(cons.events.roomTimeline.READY, eventId); @@ -193,7 +202,7 @@ class RoomTimeline extends EventEmitter { ? getFirstLinkedTimeline(this.activeTimeline) : getLastLinkedTimeline(this.activeTimeline); - if (timelineToPaginate.getPaginationToken(backwards ? 'b' : 'f') === null) { + if (timelineToPaginate.getPaginationToken(backwards ? "b" : "f") === null) { this.emit(cons.events.roomTimeline.PAGINATED, backwards, 0); this.isOngoingPagination = false; return false; @@ -201,9 +210,13 @@ class RoomTimeline extends EventEmitter { const oldSize = this.timeline.length; try { - await this.matrixClient.paginateEventTimeline(timelineToPaginate, { backwards, limit }); + await this.matrixClient.paginateEventTimeline(timelineToPaginate, { + backwards, + limit, + }); - if (this.isEncrypted()) await this.decryptAllEventsOfTimeline(this.activeTimeline); + if (this.isEncrypted()) + await this.decryptAllEventsOfTimeline(this.activeTimeline); this._populateTimelines(); const loaded = this.timeline.length - oldSize; @@ -222,7 +235,9 @@ class RoomTimeline extends EventEmitter { .getEvents() .filter((event) => event.isEncrypted() && !event.clearEvent) .reverse() - .map((event) => event.attemptDecryption(this.matrixClient.crypto, { isRetry: true })); + .map((event) => + event.attemptDecryption(this.matrixClient.crypto, { isRetry: true }) + ); return Promise.allSettled(decryptionPromises); } @@ -244,7 +259,11 @@ class RoomTimeline extends EventEmitter { if (!mEvent) return []; for (let i = liveEvents.length - 1; i >= 0; i -= 1) { - readers.splice(readers.length, 0, ...this.room.getUsersReadUpTo(liveEvents[i])); + readers.splice( + readers.length, + 0, + ...this.room.getUsersReadUpTo(liveEvents[i]) + ); if (mEvent === liveEvents[i]) break; } @@ -256,15 +275,17 @@ class RoomTimeline extends EventEmitter { const getLatestVisibleEvent = () => { for (let i = liveEvents.length - 1; i >= 0; i -= 1) { const mEvent = liveEvents[i]; - if (mEvent.getType() === 'm.room.member' && hideMemberEvents(mEvent)) { + if (mEvent.getType() === "m.room.member" && hideMemberEvents(mEvent)) { // eslint-disable-next-line no-continue continue; } - if (!mEvent.isRedacted() - && !isReaction(mEvent) - && !isEdited(mEvent) - && cons.supportEventTypes.includes(mEvent.getType()) - ) return mEvent; + if ( + !mEvent.isRedacted() && + !isReaction(mEvent) && + !isEdited(mEvent) && + cons.supportEventTypes.includes(mEvent.getType()) + ) + return mEvent; } return liveEvents[liveEvents.length - 1]; }; @@ -296,7 +317,10 @@ class RoomTimeline extends EventEmitter { return this.timeline.findIndex((mEvent) => mEvent.getId() === eventId); } - findEventByIdInTimelineSet(eventId, eventTimelineSet = this.getUnfilteredTimelineSet()) { + findEventByIdInTimelineSet( + eventId, + eventTimelineSet = this.getUnfilteredTimelineSet() + ) { return eventTimelineSet.findEventById(eventId); } @@ -311,7 +335,13 @@ class RoomTimeline extends EventEmitter { } _listenEvents() { - this._listenRoomTimeline = (event, room, toStartOfTimeline, removed, data) => { + this._listenRoomTimeline = ( + event, + room, + toStartOfTimeline, + removed, + data + ) => { if (room.roomId !== this.roomId) return; if (this.isOngoingPagination) return; @@ -369,7 +399,10 @@ class RoomTimeline extends EventEmitter { const isTyping = member.typing; if (isTyping) this.typingMembers.add(member.userId); else this.typingMembers.delete(member.userId); - this.emit(cons.events.roomTimeline.TYPING_MEMBERS_UPDATED, new Set([...this.typingMembers])); + this.emit( + cons.events.roomTimeline.TYPING_MEMBERS_UPDATED, + new Set([...this.typingMembers]) + ); }; this._listenReciptEvent = (event, room) => { // we only process receipt for latest message here. @@ -381,26 +414,32 @@ class RoomTimeline extends EventEmitter { const lastEventId = lastMEvent.getId(); const lastEventRecipt = receiptContent[lastEventId]; - if (typeof lastEventRecipt === 'undefined') return; - if (lastEventRecipt['m.read']) { + if (typeof lastEventRecipt === "undefined") return; + if (lastEventRecipt["m.read"]) { this.emit(cons.events.roomTimeline.LIVE_RECEIPT); } }; - this.matrixClient.on('Room.timeline', this._listenRoomTimeline); - this.matrixClient.on('Room.redaction', this._listenRedaction); - this.matrixClient.on('Event.decrypted', this._listenDecryptEvent); - this.matrixClient.on('RoomMember.typing', this._listenTypingEvent); - this.matrixClient.on('Room.receipt', this._listenReciptEvent); + this.matrixClient.on("Room.timeline", this._listenRoomTimeline); + this.matrixClient.on("Room.redaction", this._listenRedaction); + this.matrixClient.on("Event.decrypted", this._listenDecryptEvent); + this.matrixClient.on("RoomMember.typing", this._listenTypingEvent); + this.matrixClient.on("Room.receipt", this._listenReciptEvent); } removeInternalListeners() { if (!this.initialized) return; - this.matrixClient.removeListener('Room.timeline', this._listenRoomTimeline); - this.matrixClient.removeListener('Room.redaction', this._listenRedaction); - this.matrixClient.removeListener('Event.decrypted', this._listenDecryptEvent); - this.matrixClient.removeListener('RoomMember.typing', this._listenTypingEvent); - this.matrixClient.removeListener('Room.receipt', this._listenReciptEvent); + this.matrixClient.removeListener("Room.timeline", this._listenRoomTimeline); + this.matrixClient.removeListener("Room.redaction", this._listenRedaction); + this.matrixClient.removeListener( + "Event.decrypted", + this._listenDecryptEvent + ); + this.matrixClient.removeListener( + "RoomMember.typing", + this._listenTypingEvent + ); + this.matrixClient.removeListener("Room.receipt", this._listenReciptEvent); } } diff --git a/src/client/state/RoomsHierarchy.js b/src/client/state/RoomsHierarchy.js index f3ffb1fc..538d5bb0 100644 --- a/src/client/state/RoomsHierarchy.js +++ b/src/client/state/RoomsHierarchy.js @@ -1,4 +1,4 @@ -import { RoomHierarchy } from 'matrix-js-sdk/lib/room-hierarchy'; +import { RoomHierarchy } from "matrix-js-sdk/lib/room-hierarchy"; class RoomsHierarchy { constructor(matrixClient, limit = 20, maxDepth = 1, suggestedOnly = false) { @@ -32,7 +32,7 @@ class RoomsHierarchy { { roomId, client: this.matrixClient }, limit, this._maxDepth, - this._suggestedOnly, + this._suggestedOnly ); this.roomIdToHierarchy.set(roomId, roomHierarchy); } diff --git a/src/client/state/RoomsInput.js b/src/client/state/RoomsInput.js index d1e0aedb..864c00f8 100644 --- a/src/client/state/RoomsInput.js +++ b/src/client/state/RoomsInput.js @@ -1,20 +1,20 @@ -import EventEmitter from 'events'; -import encrypt from 'browser-encrypt-attachment'; -import { encode } from 'blurhash'; -import { getShortcodeToEmoji } from '../../app/organisms/emoji-board/custom-emoji'; -import { getBlobSafeMimeType } from '../../util/mimetypes'; -import { sanitizeText } from '../../util/sanitize'; -import cons from './cons'; -import settings from './settings'; -import { markdown, plain } from '../../util/markdown'; +import EventEmitter from "events"; +import encrypt from "browser-encrypt-attachment"; +import { encode } from "blurhash"; +import { getShortcodeToEmoji } from "../../app/organisms/emoji-board/custom-emoji"; +import { getBlobSafeMimeType } from "../../util/mimetypes"; +import { sanitizeText } from "../../util/sanitize"; +import cons from "./cons"; +import settings from "./settings"; +import { markdown, plain } from "../../util/markdown"; -const blurhashField = 'xyz.amorgan.blurhash'; +const blurhashField = "xyz.amorgan.blurhash"; function encodeBlurhash(img) { - const canvas = document.createElement('canvas'); + const canvas = document.createElement("canvas"); canvas.width = 100; canvas.height = 100; - const context = canvas.getContext('2d'); + const context = canvas.getContext("2d"); context.drawImage(img, 0, 0, canvas.width, canvas.height); const data = context.getImageData(0, 0, canvas.width, canvas.height); return encode(data.data, data.width, data.height, 4, 4); @@ -31,8 +31,8 @@ function loadImage(url) { function loadVideo(videoFile) { return new Promise((resolve, reject) => { - const video = document.createElement('video'); - video.preload = 'metadata'; + const video = document.createElement("video"); + video.preload = "metadata"; video.playsInline = true; video.muted = true; @@ -55,8 +55,10 @@ function loadVideo(videoFile) { reader.onerror = (e) => { reject(e); }; - if (videoFile.type === 'video/quicktime') { - const quicktimeVideoFile = new File([videoFile], videoFile.name, { type: 'video/mp4' }); + if (videoFile.type === "video/quicktime") { + const quicktimeVideoFile = new File([videoFile], videoFile.name, { + type: "video/mp4", + }); reader.readAsDataURL(quicktimeVideoFile); } else { reader.readAsDataURL(videoFile); @@ -78,10 +80,10 @@ function getVideoThumbnail(video, width, height, mimeType) { targetWidth = MAX_WIDTH; } - const canvas = document.createElement('canvas'); + const canvas = document.createElement("canvas"); canvas.width = targetWidth; canvas.height = targetHeight; - const context = canvas.getContext('2d'); + const context = canvas.getContext("2d"); context.drawImage(video, 0, 0, targetWidth, targetHeight); canvas.toBlob((thumbnail) => { @@ -109,9 +111,10 @@ class RoomsInput extends EventEmitter { cleanEmptyEntry(roomId) { const input = this.getInput(roomId); - const isEmpty = typeof input.attachment === 'undefined' - && typeof input.replyTo === 'undefined' - && (typeof input.message === 'undefined' || input.message === ''); + const isEmpty = + typeof input.attachment === "undefined" && + typeof input.replyTo === "undefined" && + (typeof input.message === "undefined" || input.message === ""); if (isEmpty) { this.roomIdToInput.delete(roomId); } @@ -125,12 +128,12 @@ class RoomsInput extends EventEmitter { const input = this.getInput(roomId); input.message = message; this.roomIdToInput.set(roomId, input); - if (message === '') this.cleanEmptyEntry(roomId); + if (message === "") this.cleanEmptyEntry(roomId); } getMessage(roomId) { const input = this.getInput(roomId); - if (typeof input.message === 'undefined') return ''; + if (typeof input.message === "undefined") return ""; return input.message; } @@ -142,13 +145,13 @@ class RoomsInput extends EventEmitter { getReplyTo(roomId) { const input = this.getInput(roomId); - if (typeof input.replyTo === 'undefined') return null; + if (typeof input.replyTo === "undefined") return null; return input.replyTo; } cancelReplyTo(roomId) { const input = this.getInput(roomId); - if (typeof input.replyTo === 'undefined') return; + if (typeof input.replyTo === "undefined") return; delete input.replyTo; this.roomIdToInput.set(roomId, input); } @@ -163,13 +166,13 @@ class RoomsInput extends EventEmitter { getAttachment(roomId) { const input = this.getInput(roomId); - if (typeof input.attachment === 'undefined') return null; + if (typeof input.attachment === "undefined") return null; return input.attachment.file; } cancelAttachment(roomId) { const input = this.getInput(roomId); - if (typeof input.attachment === 'undefined') return; + if (typeof input.attachment === "undefined") return; const { uploadingPromise } = input.attachment; @@ -188,15 +191,20 @@ class RoomsInput extends EventEmitter { } getContent(roomId, options, message, reply, edit) { - const msgType = options?.msgType || 'm.text'; + const msgType = options?.msgType || "m.text"; const autoMarkdown = options?.autoMarkdown ?? true; const room = this.matrixClient.getRoom(roomId); const userNames = room.currentState.userIdsToDisplayNames; const parentIds = this.roomList.getAllParentSpaces(room.roomId); - const parentRooms = [...parentIds].map((id) => this.matrixClient.getRoom(id)); - const emojis = getShortcodeToEmoji(this.matrixClient, [room, ...parentRooms]); + const parentRooms = [...parentIds].map((id) => + this.matrixClient.getRoom(id) + ); + const emojis = getShortcodeToEmoji(this.matrixClient, [ + room, + ...parentRooms, + ]); const output = settings.isMarkdown && autoMarkdown ? markdown : plain; const body = output(message, { userNames, emojis }); @@ -207,49 +215,60 @@ class RoomsInput extends EventEmitter { }; if (!body.onlyPlain || reply) { - content.format = 'org.matrix.custom.html'; + content.format = "org.matrix.custom.html"; content.formatted_body = body.html; } if (edit) { - content['m.new_content'] = { ...content }; - content['m.relates_to'] = { + content["m.new_content"] = { ...content }; + content["m.relates_to"] = { event_id: edit.getId(), - rel_type: 'm.replace', + rel_type: "m.replace", }; - const isReply = edit.getWireContent()['m.relates_to']?.['m.in_reply_to']; + const isReply = edit.getWireContent()["m.relates_to"]?.["m.in_reply_to"]; if (isReply) { - content.format = 'org.matrix.custom.html'; + content.format = "org.matrix.custom.html"; content.formatted_body = body.html; } content.body = ` * ${content.body}`; - if (content.formatted_body) content.formatted_body = ` * ${content.formatted_body}`; + if (content.formatted_body) + content.formatted_body = ` * ${content.formatted_body}`; if (isReply) { const eBody = edit.getContent().body; - const replyHead = eBody.substring(0, eBody.indexOf('\n\n')); + const replyHead = eBody.substring(0, eBody.indexOf("\n\n")); if (replyHead) content.body = `${replyHead}\n\n${content.body}`; const eFBody = edit.getContent().formatted_body; - const fReplyHead = eFBody.substring(0, eFBody.indexOf('')); - if (fReplyHead) content.formatted_body = `${fReplyHead}${content.formatted_body}`; + const fReplyHead = eFBody.substring(0, eFBody.indexOf("")); + if (fReplyHead) + content.formatted_body = `${fReplyHead}${content.formatted_body}`; } } if (reply) { - content['m.relates_to'] = { - 'm.in_reply_to': { + content["m.relates_to"] = { + "m.in_reply_to": { event_id: reply.eventId, }, }; - content.body = `> <${reply.userId}> ${reply.body.replace(/\n/g, '\n> ')}\n\n${content.body}`; + content.body = `> <${reply.userId}> ${reply.body.replace( + /\n/g, + "\n> " + )}\n\n${content.body}`; - const replyToLink = `In reply to`; - const userLink = `${sanitizeText(reply.userId)}`; - const fallback = `
${replyToLink}${userLink}
${reply.formattedBody || sanitizeText(reply.body)}
`; + const replyToLink = `In reply to`; + const userLink = `${sanitizeText(reply.userId)}`; + const fallback = `
${replyToLink}${userLink}
${ + reply.formattedBody || sanitizeText(reply.body) + }
`; content.formatted_body = fallback + content.formatted_body; } @@ -265,8 +284,13 @@ class RoomsInput extends EventEmitter { if (!this.isSending(roomId)) return; } - if (this.getMessage(roomId).trim() !== '') { - const content = this.getContent(roomId, options, input.message, input.replyTo); + if (this.getMessage(roomId).trim() !== "") { + const content = this.getContent( + roomId, + options, + input.message, + input.replyTo + ); this.matrixClient.sendMessage(roomId, content); } @@ -294,7 +318,7 @@ class RoomsInput extends EventEmitter { // send sticker without info } - this.matrixClient.sendEvent(roomId, 'm.sticker', { + this.matrixClient.sendEvent(roomId, "m.sticker", { body, url, info, @@ -303,7 +327,10 @@ class RoomsInput extends EventEmitter { } async sendFile(roomId, file) { - const fileType = getBlobSafeMimeType(file.type).slice(0, file.type.indexOf('/')); + const fileType = getBlobSafeMimeType(file.type).slice( + 0, + file.type.indexOf("/") + ); const info = { mimetype: file.type, size: file.size, @@ -311,18 +338,18 @@ class RoomsInput extends EventEmitter { const content = { info }; let uploadData = null; - if (fileType === 'image') { + if (fileType === "image") { const img = await loadImage(URL.createObjectURL(file)); info.w = img.width; info.h = img.height; info[blurhashField] = encodeBlurhash(img); - content.msgtype = 'm.image'; - content.body = file.name || 'Image'; - } else if (fileType === 'video') { - content.msgtype = 'm.video'; - content.body = file.name || 'Video'; + content.msgtype = "m.image"; + content.body = file.name || "Image"; + } else if (fileType === "video") { + content.msgtype = "m.video"; + content.body = file.name || "Video"; try { const video = await loadVideo(file); @@ -331,8 +358,16 @@ class RoomsInput extends EventEmitter { info.h = video.videoHeight; info[blurhashField] = encodeBlurhash(video); - const thumbnailData = await getVideoThumbnail(video, video.videoWidth, video.videoHeight, 'image/jpeg'); - const thumbnailUploadData = await this.uploadFile(roomId, thumbnailData.thumbnail); + const thumbnailData = await getVideoThumbnail( + video, + video.videoWidth, + video.videoHeight, + "image/jpeg" + ); + const thumbnailUploadData = await this.uploadFile( + roomId, + thumbnailData.thumbnail + ); info.thumbnail_info = thumbnailData.info; if (this.matrixClient.isRoomEncrypted(roomId)) { info.thumbnail_file = thumbnailUploadData.file; @@ -343,12 +378,12 @@ class RoomsInput extends EventEmitter { this.emit(cons.events.roomsInput.FILE_UPLOAD_CANCELED, roomId); return; } - } else if (fileType === 'audio') { - content.msgtype = 'm.audio'; - content.body = file.name || 'Audio'; + } else if (fileType === "audio") { + content.msgtype = "m.audio"; + content.body = file.name || "Audio"; } else { - content.msgtype = 'm.file'; - content.body = file.name || 'File'; + content.msgtype = "m.file"; + content.body = file.name || "File"; } try { @@ -378,18 +413,23 @@ class RoomsInput extends EventEmitter { if (isEncryptedRoom) { const dataBuffer = await file.arrayBuffer(); - if (typeof this.getInput(roomId).attachment === 'undefined') throw new Error('Attachment canceled'); + if (typeof this.getInput(roomId).attachment === "undefined") + throw new Error("Attachment canceled"); const encryptedResult = await encrypt.encryptAttachment(dataBuffer); - if (typeof this.getInput(roomId).attachment === 'undefined') throw new Error('Attachment canceled'); + if (typeof this.getInput(roomId).attachment === "undefined") + throw new Error("Attachment canceled"); encryptInfo = encryptedResult.info; encryptBlob = new Blob([encryptedResult.data]); } - const uploadingPromise = this.matrixClient.uploadContent(isEncryptedRoom ? encryptBlob : file, { - // don't send filename if room is encrypted. - includeFilename: !isEncryptedRoom, - progressHandler, - }); + const uploadingPromise = this.matrixClient.uploadContent( + isEncryptedRoom ? encryptBlob : file, + { + // don't send filename if room is encrypted. + includeFilename: !isEncryptedRoom, + progressHandler, + } + ); const input = this.getInput(roomId); input.attachment.uploadingPromise = uploadingPromise; @@ -414,7 +454,7 @@ class RoomsInput extends EventEmitter { { msgType: mEvent.getWireContent().msgtype }, editedBody, null, - mEvent, + mEvent ); this.matrixClient.sendMessage(roomId, content); } diff --git a/src/client/state/auth.js b/src/client/state/auth.js index fbc23f6f..19c3fe8e 100644 --- a/src/client/state/auth.js +++ b/src/client/state/auth.js @@ -1,4 +1,4 @@ -import cons from './cons'; +import cons from "./cons"; function getSecret(key) { return localStorage.getItem(key); @@ -13,7 +13,4 @@ const secret = { baseUrl: getSecret(cons.secretKey.BASE_URL), }; -export { - isAuthenticated, - secret, -}; +export { isAuthenticated, secret }; diff --git a/src/client/state/cons.js b/src/client/state/cons.js index 8d9fda54..d759b653 100644 --- a/src/client/state/cons.js +++ b/src/client/state/cons.js @@ -1,155 +1,155 @@ const cons = { - version: '2.2.6', + version: "2.2.6", secretKey: { - ACCESS_TOKEN: 'cinny_access_token', - DEVICE_ID: 'cinny_device_id', - USER_ID: 'cinny_user_id', - BASE_URL: 'cinny_hs_base_url', + ACCESS_TOKEN: "cinny_access_token", + DEVICE_ID: "cinny_device_id", + USER_ID: "cinny_user_id", + BASE_URL: "cinny_hs_base_url", }, - DEVICE_DISPLAY_NAME: 'Cinny Web', - IN_CINNY_SPACES: 'in.cinny.spaces', + DEVICE_DISPLAY_NAME: "Cinny Web", + IN_CINNY_SPACES: "in.cinny.spaces", tabs: { - HOME: 'home', - DIRECTS: 'dm', + HOME: "home", + DIRECTS: "dm", }, supportEventTypes: [ - 'm.room.create', - 'm.room.message', - 'm.room.encrypted', - 'm.room.member', - 'm.sticker', + "m.room.create", + "m.room.message", + "m.room.encrypted", + "m.room.member", + "m.sticker", ], notifs: { - DEFAULT: 'default', - ALL_MESSAGES: 'all_messages', - MENTIONS_AND_KEYWORDS: 'mentions_and_keywords', - MUTE: 'mute', + DEFAULT: "default", + ALL_MESSAGES: "all_messages", + MENTIONS_AND_KEYWORDS: "mentions_and_keywords", + MUTE: "mute", }, status: { - PRE_FLIGHT: 'pre-flight', - IN_FLIGHT: 'in-flight', - SUCCESS: 'success', - ERROR: 'error', + PRE_FLIGHT: "pre-flight", + IN_FLIGHT: "in-flight", + SUCCESS: "success", + ERROR: "error", }, actions: { navigation: { - SELECT_TAB: 'SELECT_TAB', - SELECT_SPACE: 'SELECT_SPACE', - SELECT_ROOM: 'SELECT_ROOM', - OPEN_SPACE_SETTINGS: 'OPEN_SPACE_SETTINGS', - OPEN_SPACE_MANAGE: 'OPEN_SPACE_MANAGE', - OPEN_SPACE_ADDEXISTING: 'OPEN_SPACE_ADDEXISTING', - TOGGLE_ROOM_SETTINGS: 'TOGGLE_ROOM_SETTINGS', - OPEN_SHORTCUT_SPACES: 'OPEN_SHORTCUT_SPACES', - OPEN_INVITE_LIST: 'OPEN_INVITE_LIST', - OPEN_PUBLIC_ROOMS: 'OPEN_PUBLIC_ROOMS', - OPEN_CREATE_ROOM: 'OPEN_CREATE_ROOM', - OPEN_JOIN_ALIAS: 'OPEN_JOIN_ALIAS', - OPEN_INVITE_USER: 'OPEN_INVITE_USER', - OPEN_PROFILE_VIEWER: 'OPEN_PROFILE_VIEWER', - OPEN_SETTINGS: 'OPEN_SETTINGS', - OPEN_EMOJIBOARD: 'OPEN_EMOJIBOARD', - OPEN_READRECEIPTS: 'OPEN_READRECEIPTS', - OPEN_VIEWSOURCE: 'OPEN_VIEWSOURCE', - CLICK_REPLY_TO: 'CLICK_REPLY_TO', - OPEN_SEARCH: 'OPEN_SEARCH', - OPEN_REUSABLE_CONTEXT_MENU: 'OPEN_REUSABLE_CONTEXT_MENU', - OPEN_NAVIGATION: 'OPEN_NAVIGATION', - OPEN_REUSABLE_DIALOG: 'OPEN_REUSABLE_DIALOG', - OPEN_EMOJI_VERIFICATION: 'OPEN_EMOJI_VERIFICATION', + SELECT_TAB: "SELECT_TAB", + SELECT_SPACE: "SELECT_SPACE", + SELECT_ROOM: "SELECT_ROOM", + OPEN_SPACE_SETTINGS: "OPEN_SPACE_SETTINGS", + OPEN_SPACE_MANAGE: "OPEN_SPACE_MANAGE", + OPEN_SPACE_ADDEXISTING: "OPEN_SPACE_ADDEXISTING", + TOGGLE_ROOM_SETTINGS: "TOGGLE_ROOM_SETTINGS", + OPEN_SHORTCUT_SPACES: "OPEN_SHORTCUT_SPACES", + OPEN_INVITE_LIST: "OPEN_INVITE_LIST", + OPEN_PUBLIC_ROOMS: "OPEN_PUBLIC_ROOMS", + OPEN_CREATE_ROOM: "OPEN_CREATE_ROOM", + OPEN_JOIN_ALIAS: "OPEN_JOIN_ALIAS", + OPEN_INVITE_USER: "OPEN_INVITE_USER", + OPEN_PROFILE_VIEWER: "OPEN_PROFILE_VIEWER", + OPEN_SETTINGS: "OPEN_SETTINGS", + OPEN_EMOJIBOARD: "OPEN_EMOJIBOARD", + OPEN_READRECEIPTS: "OPEN_READRECEIPTS", + OPEN_VIEWSOURCE: "OPEN_VIEWSOURCE", + CLICK_REPLY_TO: "CLICK_REPLY_TO", + OPEN_SEARCH: "OPEN_SEARCH", + OPEN_REUSABLE_CONTEXT_MENU: "OPEN_REUSABLE_CONTEXT_MENU", + OPEN_NAVIGATION: "OPEN_NAVIGATION", + OPEN_REUSABLE_DIALOG: "OPEN_REUSABLE_DIALOG", + OPEN_EMOJI_VERIFICATION: "OPEN_EMOJI_VERIFICATION", }, room: { - JOIN: 'JOIN', - LEAVE: 'LEAVE', - CREATE: 'CREATE', + JOIN: "JOIN", + LEAVE: "LEAVE", + CREATE: "CREATE", }, accountData: { - CREATE_SPACE_SHORTCUT: 'CREATE_SPACE_SHORTCUT', - DELETE_SPACE_SHORTCUT: 'DELETE_SPACE_SHORTCUT', - MOVE_SPACE_SHORTCUTS: 'MOVE_SPACE_SHORTCUTS', - CATEGORIZE_SPACE: 'CATEGORIZE_SPACE', - UNCATEGORIZE_SPACE: 'UNCATEGORIZE_SPACE', + CREATE_SPACE_SHORTCUT: "CREATE_SPACE_SHORTCUT", + DELETE_SPACE_SHORTCUT: "DELETE_SPACE_SHORTCUT", + MOVE_SPACE_SHORTCUTS: "MOVE_SPACE_SHORTCUTS", + CATEGORIZE_SPACE: "CATEGORIZE_SPACE", + UNCATEGORIZE_SPACE: "UNCATEGORIZE_SPACE", }, settings: { - TOGGLE_SYSTEM_THEME: 'TOGGLE_SYSTEM_THEME', - TOGGLE_MARKDOWN: 'TOGGLE_MARKDOWN', - TOGGLE_PEOPLE_DRAWER: 'TOGGLE_PEOPLE_DRAWER', - TOGGLE_MEMBERSHIP_EVENT: 'TOGGLE_MEMBERSHIP_EVENT', - TOGGLE_NICKAVATAR_EVENT: 'TOGGLE_NICKAVATAR_EVENT', - TOGGLE_NOTIFICATIONS: 'TOGGLE_NOTIFICATIONS', - TOGGLE_NOTIFICATION_SOUNDS: 'TOGGLE_NOTIFICATION_SOUNDS', + TOGGLE_SYSTEM_THEME: "TOGGLE_SYSTEM_THEME", + TOGGLE_MARKDOWN: "TOGGLE_MARKDOWN", + TOGGLE_PEOPLE_DRAWER: "TOGGLE_PEOPLE_DRAWER", + TOGGLE_MEMBERSHIP_EVENT: "TOGGLE_MEMBERSHIP_EVENT", + TOGGLE_NICKAVATAR_EVENT: "TOGGLE_NICKAVATAR_EVENT", + TOGGLE_NOTIFICATIONS: "TOGGLE_NOTIFICATIONS", + TOGGLE_NOTIFICATION_SOUNDS: "TOGGLE_NOTIFICATION_SOUNDS", }, }, events: { navigation: { - TAB_SELECTED: 'TAB_SELECTED', - SPACE_SELECTED: 'SPACE_SELECTED', - ROOM_SELECTED: 'ROOM_SELECTED', - SPACE_SETTINGS_OPENED: 'SPACE_SETTINGS_OPENED', - SPACE_MANAGE_OPENED: 'SPACE_MANAGE_OPENED', - SPACE_ADDEXISTING_OPENED: 'SPACE_ADDEXISTING_OPENED', - ROOM_SETTINGS_TOGGLED: 'ROOM_SETTINGS_TOGGLED', - SHORTCUT_SPACES_OPENED: 'SHORTCUT_SPACES_OPENED', - INVITE_LIST_OPENED: 'INVITE_LIST_OPENED', - PUBLIC_ROOMS_OPENED: 'PUBLIC_ROOMS_OPENED', - CREATE_ROOM_OPENED: 'CREATE_ROOM_OPENED', - JOIN_ALIAS_OPENED: 'JOIN_ALIAS_OPENED', - INVITE_USER_OPENED: 'INVITE_USER_OPENED', - SETTINGS_OPENED: 'SETTINGS_OPENED', - PROFILE_VIEWER_OPENED: 'PROFILE_VIEWER_OPENED', - EMOJIBOARD_OPENED: 'EMOJIBOARD_OPENED', - READRECEIPTS_OPENED: 'READRECEIPTS_OPENED', - VIEWSOURCE_OPENED: 'VIEWSOURCE_OPENED', - REPLY_TO_CLICKED: 'REPLY_TO_CLICKED', - SEARCH_OPENED: 'SEARCH_OPENED', - REUSABLE_CONTEXT_MENU_OPENED: 'REUSABLE_CONTEXT_MENU_OPENED', - NAVIGATION_OPENED: 'NAVIGATION_OPENED', - REUSABLE_DIALOG_OPENED: 'REUSABLE_DIALOG_OPENED', - EMOJI_VERIFICATION_OPENED: 'EMOJI_VERIFICATION_OPENED', + TAB_SELECTED: "TAB_SELECTED", + SPACE_SELECTED: "SPACE_SELECTED", + ROOM_SELECTED: "ROOM_SELECTED", + SPACE_SETTINGS_OPENED: "SPACE_SETTINGS_OPENED", + SPACE_MANAGE_OPENED: "SPACE_MANAGE_OPENED", + SPACE_ADDEXISTING_OPENED: "SPACE_ADDEXISTING_OPENED", + ROOM_SETTINGS_TOGGLED: "ROOM_SETTINGS_TOGGLED", + SHORTCUT_SPACES_OPENED: "SHORTCUT_SPACES_OPENED", + INVITE_LIST_OPENED: "INVITE_LIST_OPENED", + PUBLIC_ROOMS_OPENED: "PUBLIC_ROOMS_OPENED", + CREATE_ROOM_OPENED: "CREATE_ROOM_OPENED", + JOIN_ALIAS_OPENED: "JOIN_ALIAS_OPENED", + INVITE_USER_OPENED: "INVITE_USER_OPENED", + SETTINGS_OPENED: "SETTINGS_OPENED", + PROFILE_VIEWER_OPENED: "PROFILE_VIEWER_OPENED", + EMOJIBOARD_OPENED: "EMOJIBOARD_OPENED", + READRECEIPTS_OPENED: "READRECEIPTS_OPENED", + VIEWSOURCE_OPENED: "VIEWSOURCE_OPENED", + REPLY_TO_CLICKED: "REPLY_TO_CLICKED", + SEARCH_OPENED: "SEARCH_OPENED", + REUSABLE_CONTEXT_MENU_OPENED: "REUSABLE_CONTEXT_MENU_OPENED", + NAVIGATION_OPENED: "NAVIGATION_OPENED", + REUSABLE_DIALOG_OPENED: "REUSABLE_DIALOG_OPENED", + EMOJI_VERIFICATION_OPENED: "EMOJI_VERIFICATION_OPENED", }, roomList: { - ROOMLIST_UPDATED: 'ROOMLIST_UPDATED', - INVITELIST_UPDATED: 'INVITELIST_UPDATED', - ROOM_JOINED: 'ROOM_JOINED', - ROOM_LEAVED: 'ROOM_LEAVED', - ROOM_CREATED: 'ROOM_CREATED', - ROOM_PROFILE_UPDATED: 'ROOM_PROFILE_UPDATED', + ROOMLIST_UPDATED: "ROOMLIST_UPDATED", + INVITELIST_UPDATED: "INVITELIST_UPDATED", + ROOM_JOINED: "ROOM_JOINED", + ROOM_LEAVED: "ROOM_LEAVED", + ROOM_CREATED: "ROOM_CREATED", + ROOM_PROFILE_UPDATED: "ROOM_PROFILE_UPDATED", }, accountData: { - SPACE_SHORTCUT_UPDATED: 'SPACE_SHORTCUT_UPDATED', - CATEGORIZE_SPACE_UPDATED: 'CATEGORIZE_SPACE_UPDATED', + SPACE_SHORTCUT_UPDATED: "SPACE_SHORTCUT_UPDATED", + CATEGORIZE_SPACE_UPDATED: "CATEGORIZE_SPACE_UPDATED", }, notifications: { - NOTI_CHANGED: 'NOTI_CHANGED', - FULL_READ: 'FULL_READ', - MUTE_TOGGLED: 'MUTE_TOGGLED', + NOTI_CHANGED: "NOTI_CHANGED", + FULL_READ: "FULL_READ", + MUTE_TOGGLED: "MUTE_TOGGLED", }, roomTimeline: { - READY: 'READY', - EVENT: 'EVENT', - PAGINATED: 'PAGINATED', - TYPING_MEMBERS_UPDATED: 'TYPING_MEMBERS_UPDATED', - LIVE_RECEIPT: 'LIVE_RECEIPT', - EVENT_REDACTED: 'EVENT_REDACTED', - AT_BOTTOM: 'AT_BOTTOM', - SCROLL_TO_LIVE: 'SCROLL_TO_LIVE', + READY: "READY", + EVENT: "EVENT", + PAGINATED: "PAGINATED", + TYPING_MEMBERS_UPDATED: "TYPING_MEMBERS_UPDATED", + LIVE_RECEIPT: "LIVE_RECEIPT", + EVENT_REDACTED: "EVENT_REDACTED", + AT_BOTTOM: "AT_BOTTOM", + SCROLL_TO_LIVE: "SCROLL_TO_LIVE", }, roomsInput: { - MESSAGE_SENT: 'MESSAGE_SENT', - ATTACHMENT_SET: 'ATTACHMENT_SET', - FILE_UPLOADED: 'FILE_UPLOADED', - UPLOAD_PROGRESS_CHANGES: 'UPLOAD_PROGRESS_CHANGES', - FILE_UPLOAD_CANCELED: 'FILE_UPLOAD_CANCELED', - ATTACHMENT_CANCELED: 'ATTACHMENT_CANCELED', + MESSAGE_SENT: "MESSAGE_SENT", + ATTACHMENT_SET: "ATTACHMENT_SET", + FILE_UPLOADED: "FILE_UPLOADED", + UPLOAD_PROGRESS_CHANGES: "UPLOAD_PROGRESS_CHANGES", + FILE_UPLOAD_CANCELED: "FILE_UPLOAD_CANCELED", + ATTACHMENT_CANCELED: "ATTACHMENT_CANCELED", }, settings: { - SYSTEM_THEME_TOGGLED: 'SYSTEM_THEME_TOGGLED', - MARKDOWN_TOGGLED: 'MARKDOWN_TOGGLED', - PEOPLE_DRAWER_TOGGLED: 'PEOPLE_DRAWER_TOGGLED', - MEMBERSHIP_EVENTS_TOGGLED: 'MEMBERSHIP_EVENTS_TOGGLED', - NICKAVATAR_EVENTS_TOGGLED: 'NICKAVATAR_EVENTS_TOGGLED', - NOTIFICATIONS_TOGGLED: 'NOTIFICATIONS_TOGGLED', - NOTIFICATION_SOUNDS_TOGGLED: 'NOTIFICATION_SOUNDS_TOGGLED', + SYSTEM_THEME_TOGGLED: "SYSTEM_THEME_TOGGLED", + MARKDOWN_TOGGLED: "MARKDOWN_TOGGLED", + PEOPLE_DRAWER_TOGGLED: "PEOPLE_DRAWER_TOGGLED", + MEMBERSHIP_EVENTS_TOGGLED: "MEMBERSHIP_EVENTS_TOGGLED", + NICKAVATAR_EVENTS_TOGGLED: "NICKAVATAR_EVENTS_TOGGLED", + NOTIFICATIONS_TOGGLED: "NOTIFICATIONS_TOGGLED", + NOTIFICATION_SOUNDS_TOGGLED: "NOTIFICATION_SOUNDS_TOGGLED", }, }, }; diff --git a/src/client/state/navigation.js b/src/client/state/navigation.js index 07231cd4..52642188 100644 --- a/src/client/state/navigation.js +++ b/src/client/state/navigation.js @@ -1,6 +1,6 @@ -import EventEmitter from 'events'; -import appDispatcher from '../dispatcher'; -import cons from './cons'; +import EventEmitter from "events"; +import appDispatcher from "../dispatcher"; +import cons from "./cons"; class Navigation extends EventEmitter { constructor() { @@ -22,7 +22,7 @@ class Navigation extends EventEmitter { } _addToSpacePath(roomId, asRoot) { - if (typeof roomId !== 'string') { + if (typeof roomId !== "string") { this.selectedSpacePath = [cons.tabs.HOME]; return; } @@ -41,9 +41,9 @@ class Navigation extends EventEmitter { _mapRoomToSpace(roomId) { const { roomList, accountData } = this.initMatrix; if ( - this.selectedTab === cons.tabs.HOME - && roomList.rooms.has(roomId) - && !roomList.roomIdToParents.has(roomId) + this.selectedTab === cons.tabs.HOME && + roomList.rooms.has(roomId) && + !roomList.roomIdToParents.has(roomId) ) { this.spaceToRoom.set(cons.tabs.HOME, { roomId, @@ -51,7 +51,10 @@ class Navigation extends EventEmitter { }); return; } - if (this.selectedTab === cons.tabs.DIRECTS && roomList.directs.has(roomId)) { + if ( + this.selectedTab === cons.tabs.DIRECTS && + roomList.directs.has(roomId) + ) { this.spaceToRoom.set(cons.tabs.DIRECTS, { roomId, timestamp: Date.now(), @@ -85,15 +88,18 @@ class Navigation extends EventEmitter { this.removeRecentRoom(prevSelectedRoomId); this.addRecentRoom(prevSelectedRoomId); this.removeRecentRoom(this.selectedRoomId); - if (this.isRoomSettings && typeof this.selectedRoomId === 'string') { + if (this.isRoomSettings && typeof this.selectedRoomId === "string") { this.isRoomSettings = !this.isRoomSettings; - this.emit(cons.events.navigation.ROOM_SETTINGS_TOGGLED, this.isRoomSettings); + this.emit( + cons.events.navigation.ROOM_SETTINGS_TOGGLED, + this.isRoomSettings + ); } this.emit( cons.events.navigation.ROOM_SELECTED, this.selectedRoomId, prevSelectedRoomId, - eventId, + eventId ); } @@ -127,7 +133,9 @@ class Navigation extends EventEmitter { } } - const spaceInPath = [...this.selectedSpacePath].reverse().find((sId) => parents.has(sId)); + const spaceInPath = [...this.selectedSpacePath] + .reverse() + .find((sId) => parents.has(sId)); if (spaceInPath) { this._selectSpace(spaceInPath, false, false); return; @@ -212,7 +220,9 @@ class Navigation extends EventEmitter { if (categorizedSpaces.has(spaceId)) { const categories = roomList.getCategorizedSpaces([spaceId]); - const latestSelectedRoom = this._getLatestSelectedRoomId([...categories.keys()]); + const latestSelectedRoom = this._getLatestSelectedRoomId([ + ...categories.keys(), + ]); if (latestSelectedRoom) { this._selectRoom(latestSelectedRoom); @@ -248,7 +258,10 @@ class Navigation extends EventEmitter { this._selectRoom(data.roomId); return; } - const children = tabId === cons.tabs.HOME ? roomList.getOrphanRooms() : [...roomList.directs]; + const children = + tabId === cons.tabs.HOME + ? roomList.getOrphanRooms() + : [...roomList.directs]; this._selectRoom(this._getLatestActiveRoomId(children)); return; } @@ -256,7 +269,7 @@ class Navigation extends EventEmitter { } removeRecentRoom(roomId) { - if (typeof roomId !== 'string') return; + if (typeof roomId !== "string") return; const roomIdIndex = this.recentRooms.indexOf(roomId); if (roomIdIndex >= 0) { this.recentRooms.splice(roomIdIndex, 1); @@ -264,7 +277,7 @@ class Navigation extends EventEmitter { } addRecentRoom(roomId) { - if (typeof roomId !== 'string') return; + if (typeof roomId !== "string") return; this.recentRooms.push(roomId); if (this.recentRooms.length > 10) { @@ -284,9 +297,10 @@ class Navigation extends EventEmitter { navigate(action) { const actions = { [cons.actions.navigation.SELECT_TAB]: () => { - const roomId = ( + const roomId = action.tabId !== cons.tabs.HOME && action.tabId !== cons.tabs.DIRECTS - ) ? action.tabId : null; + ? action.tabId + : null; this._selectSpace(roomId, true); this._selectTab(action.tabId); @@ -299,20 +313,27 @@ class Navigation extends EventEmitter { this._selectRoom(action.roomId, action.eventId); }, [cons.actions.navigation.OPEN_SPACE_SETTINGS]: () => { - this.emit(cons.events.navigation.SPACE_SETTINGS_OPENED, action.roomId, action.tabText); + this.emit( + cons.events.navigation.SPACE_SETTINGS_OPENED, + action.roomId, + action.tabText + ); }, [cons.actions.navigation.OPEN_SPACE_MANAGE]: () => { this.emit(cons.events.navigation.SPACE_MANAGE_OPENED, action.roomId); }, [cons.actions.navigation.OPEN_SPACE_ADDEXISTING]: () => { - this.emit(cons.events.navigation.SPACE_ADDEXISTING_OPENED, action.roomId); + this.emit( + cons.events.navigation.SPACE_ADDEXISTING_OPENED, + action.roomId + ); }, [cons.actions.navigation.TOGGLE_ROOM_SETTINGS]: () => { this.isRoomSettings = !this.isRoomSettings; this.emit( cons.events.navigation.ROOM_SETTINGS_TOGGLED, this.isRoomSettings, - action.tabText, + action.tabText ); }, [cons.actions.navigation.OPEN_SHORTCUT_SPACES]: () => { @@ -322,26 +343,34 @@ class Navigation extends EventEmitter { this.emit(cons.events.navigation.INVITE_LIST_OPENED); }, [cons.actions.navigation.OPEN_PUBLIC_ROOMS]: () => { - this.emit(cons.events.navigation.PUBLIC_ROOMS_OPENED, action.searchTerm); + this.emit( + cons.events.navigation.PUBLIC_ROOMS_OPENED, + action.searchTerm + ); }, [cons.actions.navigation.OPEN_CREATE_ROOM]: () => { this.emit( cons.events.navigation.CREATE_ROOM_OPENED, action.isSpace, - action.parentId, + action.parentId ); }, [cons.actions.navigation.OPEN_JOIN_ALIAS]: () => { - this.emit( - cons.events.navigation.JOIN_ALIAS_OPENED, - action.term, - ); + this.emit(cons.events.navigation.JOIN_ALIAS_OPENED, action.term); }, [cons.actions.navigation.OPEN_INVITE_USER]: () => { - this.emit(cons.events.navigation.INVITE_USER_OPENED, action.roomId, action.searchTerm); + this.emit( + cons.events.navigation.INVITE_USER_OPENED, + action.roomId, + action.searchTerm + ); }, [cons.actions.navigation.OPEN_PROFILE_VIEWER]: () => { - this.emit(cons.events.navigation.PROFILE_VIEWER_OPENED, action.userId, action.roomId); + this.emit( + cons.events.navigation.PROFILE_VIEWER_OPENED, + action.userId, + action.roomId + ); }, [cons.actions.navigation.OPEN_SETTINGS]: () => { this.emit(cons.events.navigation.SETTINGS_OPENED, action.tabText); @@ -353,21 +382,18 @@ class Navigation extends EventEmitter { this.emit( cons.events.navigation.EMOJIBOARD_OPENED, action.cords, - action.requestEmojiCallback, + action.requestEmojiCallback ); }, [cons.actions.navigation.OPEN_READRECEIPTS]: () => { this.emit( cons.events.navigation.READRECEIPTS_OPENED, action.roomId, - action.userIds, + action.userIds ); }, [cons.actions.navigation.OPEN_VIEWSOURCE]: () => { - this.emit( - cons.events.navigation.VIEWSOURCE_OPENED, - action.event, - ); + this.emit(cons.events.navigation.VIEWSOURCE_OPENED, action.event); }, [cons.actions.navigation.CLICK_REPLY_TO]: () => { this.emit( @@ -375,14 +401,11 @@ class Navigation extends EventEmitter { action.userId, action.eventId, action.body, - action.formattedBody, + action.formattedBody ); }, [cons.actions.navigation.OPEN_SEARCH]: () => { - this.emit( - cons.events.navigation.SEARCH_OPENED, - action.term, - ); + this.emit(cons.events.navigation.SEARCH_OPENED, action.term); }, [cons.actions.navigation.OPEN_REUSABLE_CONTEXT_MENU]: () => { this.emit( @@ -390,7 +413,7 @@ class Navigation extends EventEmitter { action.placement, action.cords, action.render, - action.afterClose, + action.afterClose ); }, [cons.actions.navigation.OPEN_REUSABLE_DIALOG]: () => { @@ -398,14 +421,14 @@ class Navigation extends EventEmitter { cons.events.navigation.REUSABLE_DIALOG_OPENED, action.title, action.render, - action.afterClose, + action.afterClose ); }, [cons.actions.navigation.OPEN_EMOJI_VERIFICATION]: () => { this.emit( cons.events.navigation.EMOJI_VERIFICATION_OPENED, action.request, - action.targetDevice, + action.targetDevice ); }, }; diff --git a/src/client/state/secretStorageKeys.js b/src/client/state/secretStorageKeys.js index 7439e7a5..512322df 100644 --- a/src/client/state/secretStorageKeys.js +++ b/src/client/state/secretStorageKeys.js @@ -2,7 +2,7 @@ const secretStorageKeys = new Map(); export function storePrivateKey(keyId, privateKey) { if (privateKey instanceof Uint8Array === false) { - throw new Error('Unable to store, privateKey is invalid.'); + throw new Error("Unable to store, privateKey is invalid."); } secretStorageKeys.set(keyId, privateKey); } diff --git a/src/client/state/settings.js b/src/client/state/settings.js index 32f55fcc..b88a7634 100644 --- a/src/client/state/settings.js +++ b/src/client/state/settings.js @@ -1,10 +1,10 @@ -import EventEmitter from 'events'; -import appDispatcher from '../dispatcher'; +import EventEmitter from "events"; +import appDispatcher from "../dispatcher"; -import cons from './cons'; +import cons from "./cons"; function getSettings() { - const settings = localStorage.getItem('settings'); + const settings = localStorage.getItem("settings"); if (settings === null) return null; return JSON.parse(settings); } @@ -13,14 +13,14 @@ function setSettings(key, value) { let settings = getSettings(); if (settings === null) settings = {}; settings[key] = value; - localStorage.setItem('settings', JSON.stringify(settings)); + localStorage.setItem("settings", JSON.stringify(settings)); } class Settings extends EventEmitter { constructor() { super(); - this.themes = ['', 'silver-theme', 'dark-theme', 'butter-theme']; + this.themes = ["", "silver-theme", "dark-theme", "butter-theme"]; this.themeIndex = this.getThemeIndex(); this.useSystemTheme = this.getUseSystemTheme(); @@ -31,15 +31,18 @@ class Settings extends EventEmitter { this._showNotifications = this.getShowNotifications(); this.isNotificationSounds = this.getIsNotificationSounds(); - this.isTouchScreenDevice = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0); + this.isTouchScreenDevice = + "ontouchstart" in window || + navigator.maxTouchPoints > 0 || + navigator.msMaxTouchPoints > 0; } getThemeIndex() { - if (typeof this.themeIndex === 'number') return this.themeIndex; + if (typeof this.themeIndex === "number") return this.themeIndex; const settings = getSettings(); if (settings === null) return 0; - if (typeof settings.themeIndex === 'undefined') return 0; + if (typeof settings.themeIndex === "undefined") return 0; // eslint-disable-next-line radix return parseInt(settings.themeIndex); } @@ -49,9 +52,9 @@ class Settings extends EventEmitter { } _clearTheme() { - document.body.classList.remove('system-theme'); + document.body.classList.remove("system-theme"); this.themes.forEach((themeName) => { - if (themeName === '') return; + if (themeName === "") return; document.body.classList.remove(themeName); }); } @@ -59,7 +62,7 @@ class Settings extends EventEmitter { applyTheme() { this._clearTheme(); if (this.useSystemTheme) { - document.body.classList.add('system-theme'); + document.body.classList.add("system-theme"); } else if (this.themes[this.themeIndex]) { document.body.classList.add(this.themes[this.themeIndex]); } @@ -67,83 +70,87 @@ class Settings extends EventEmitter { setTheme(themeIndex) { this.themeIndex = themeIndex; - setSettings('themeIndex', this.themeIndex); + setSettings("themeIndex", this.themeIndex); this.applyTheme(); } toggleUseSystemTheme() { this.useSystemTheme = !this.useSystemTheme; - setSettings('useSystemTheme', this.useSystemTheme); + setSettings("useSystemTheme", this.useSystemTheme); this.applyTheme(); this.emit(cons.events.settings.SYSTEM_THEME_TOGGLED, this.useSystemTheme); } getUseSystemTheme() { - if (typeof this.useSystemTheme === 'boolean') return this.useSystemTheme; + if (typeof this.useSystemTheme === "boolean") return this.useSystemTheme; const settings = getSettings(); if (settings === null) return true; - if (typeof settings.useSystemTheme === 'undefined') return true; + if (typeof settings.useSystemTheme === "undefined") return true; return settings.useSystemTheme; } getIsMarkdown() { - if (typeof this.isMarkdown === 'boolean') return this.isMarkdown; + if (typeof this.isMarkdown === "boolean") return this.isMarkdown; const settings = getSettings(); if (settings === null) return true; - if (typeof settings.isMarkdown === 'undefined') return true; + if (typeof settings.isMarkdown === "undefined") return true; return settings.isMarkdown; } getHideMembershipEvents() { - if (typeof this.hideMembershipEvents === 'boolean') return this.hideMembershipEvents; + if (typeof this.hideMembershipEvents === "boolean") + return this.hideMembershipEvents; const settings = getSettings(); if (settings === null) return false; - if (typeof settings.hideMembershipEvents === 'undefined') return false; + if (typeof settings.hideMembershipEvents === "undefined") return false; return settings.hideMembershipEvents; } getHideNickAvatarEvents() { - if (typeof this.hideNickAvatarEvents === 'boolean') return this.hideNickAvatarEvents; + if (typeof this.hideNickAvatarEvents === "boolean") + return this.hideNickAvatarEvents; const settings = getSettings(); if (settings === null) return true; - if (typeof settings.hideNickAvatarEvents === 'undefined') return true; + if (typeof settings.hideNickAvatarEvents === "undefined") return true; return settings.hideNickAvatarEvents; } getIsPeopleDrawer() { - if (typeof this.isPeopleDrawer === 'boolean') return this.isPeopleDrawer; + if (typeof this.isPeopleDrawer === "boolean") return this.isPeopleDrawer; const settings = getSettings(); if (settings === null) return true; - if (typeof settings.isPeopleDrawer === 'undefined') return true; + if (typeof settings.isPeopleDrawer === "undefined") return true; return settings.isPeopleDrawer; } get showNotifications() { - if (window.Notification?.permission !== 'granted') return false; + if (window.Notification?.permission !== "granted") return false; return this._showNotifications; } getShowNotifications() { - if (typeof this._showNotifications === 'boolean') return this._showNotifications; + if (typeof this._showNotifications === "boolean") + return this._showNotifications; const settings = getSettings(); if (settings === null) return true; - if (typeof settings.showNotifications === 'undefined') return true; + if (typeof settings.showNotifications === "undefined") return true; return settings.showNotifications; } getIsNotificationSounds() { - if (typeof this.isNotificationSounds === 'boolean') return this.isNotificationSounds; + if (typeof this.isNotificationSounds === "boolean") + return this.isNotificationSounds; const settings = getSettings(); if (settings === null) return true; - if (typeof settings.isNotificationSounds === 'undefined') return true; + if (typeof settings.isNotificationSounds === "undefined") return true; return settings.isNotificationSounds; } @@ -154,37 +161,52 @@ class Settings extends EventEmitter { }, [cons.actions.settings.TOGGLE_MARKDOWN]: () => { this.isMarkdown = !this.isMarkdown; - setSettings('isMarkdown', this.isMarkdown); + setSettings("isMarkdown", this.isMarkdown); this.emit(cons.events.settings.MARKDOWN_TOGGLED, this.isMarkdown); }, [cons.actions.settings.TOGGLE_PEOPLE_DRAWER]: () => { this.isPeopleDrawer = !this.isPeopleDrawer; - setSettings('isPeopleDrawer', this.isPeopleDrawer); - this.emit(cons.events.settings.PEOPLE_DRAWER_TOGGLED, this.isPeopleDrawer); + setSettings("isPeopleDrawer", this.isPeopleDrawer); + this.emit( + cons.events.settings.PEOPLE_DRAWER_TOGGLED, + this.isPeopleDrawer + ); }, [cons.actions.settings.TOGGLE_MEMBERSHIP_EVENT]: () => { this.hideMembershipEvents = !this.hideMembershipEvents; - setSettings('hideMembershipEvents', this.hideMembershipEvents); - this.emit(cons.events.settings.MEMBERSHIP_EVENTS_TOGGLED, this.hideMembershipEvents); + setSettings("hideMembershipEvents", this.hideMembershipEvents); + this.emit( + cons.events.settings.MEMBERSHIP_EVENTS_TOGGLED, + this.hideMembershipEvents + ); }, [cons.actions.settings.TOGGLE_NICKAVATAR_EVENT]: () => { this.hideNickAvatarEvents = !this.hideNickAvatarEvents; - setSettings('hideNickAvatarEvents', this.hideNickAvatarEvents); - this.emit(cons.events.settings.NICKAVATAR_EVENTS_TOGGLED, this.hideNickAvatarEvents); + setSettings("hideNickAvatarEvents", this.hideNickAvatarEvents); + this.emit( + cons.events.settings.NICKAVATAR_EVENTS_TOGGLED, + this.hideNickAvatarEvents + ); }, [cons.actions.settings.TOGGLE_NOTIFICATIONS]: async () => { - if (window.Notification?.permission !== 'granted') { + if (window.Notification?.permission !== "granted") { this._showNotifications = false; } else { this._showNotifications = !this._showNotifications; } - setSettings('showNotifications', this._showNotifications); - this.emit(cons.events.settings.NOTIFICATIONS_TOGGLED, this._showNotifications); + setSettings("showNotifications", this._showNotifications); + this.emit( + cons.events.settings.NOTIFICATIONS_TOGGLED, + this._showNotifications + ); }, [cons.actions.settings.TOGGLE_NOTIFICATION_SOUNDS]: () => { this.isNotificationSounds = !this.isNotificationSounds; - setSettings('isNotificationSounds', this.isNotificationSounds); - this.emit(cons.events.settings.NOTIFICATION_SOUNDS_TOGGLED, this.isNotificationSounds); + setSettings("isNotificationSounds", this.isNotificationSounds); + this.emit( + cons.events.settings.NOTIFICATION_SOUNDS_TOGGLED, + this.isNotificationSounds + ); }, }; diff --git a/src/font.js b/src/font.js index 94b1f478..153a26e5 100644 --- a/src/font.js +++ b/src/font.js @@ -1,5 +1,5 @@ -import '@fontsource/roboto/300.css'; -import '@fontsource/roboto/400.css'; -import '@fontsource/roboto/500.css'; -import '@fontsource/roboto/700.css'; -import '@fontsource/inter/variable.css'; +import "@fontsource/roboto/300.css"; +import "@fontsource/roboto/400.css"; +import "@fontsource/roboto/500.css"; +import "@fontsource/roboto/700.css"; +import "@fontsource/inter/variable.css"; diff --git a/src/index.jsx b/src/index.jsx index a252f6f0..b399968a 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -1,12 +1,12 @@ -import React from 'react'; -import ReactDom from 'react-dom'; -import './font'; -import './index.scss'; +import React from "react"; +import ReactDom from "react-dom"; +import "./font"; +import "./index.scss"; -import settings from './client/state/settings'; +import settings from "./client/state/settings"; -import App from './app/pages/App'; +import App from "./app/pages/App"; settings.applyTheme(); -ReactDom.render(, document.getElementById('root')); +ReactDom.render(, document.getElementById("root")); diff --git a/src/index.scss b/src/index.scss index 39d0612b..9c5f705b 100644 --- a/src/index.scss +++ b/src/index.scss @@ -1,14 +1,13 @@ -@use './app/partials/screen'; +@use "./app/partials/screen"; :root { - /* background color | --bg-[background type]: value */ - --bg-surface: #FFFFFF; - --bg-surface-transparent: #FFFFFF00; - --bg-surface-low: #F6F6F6; - --bg-surface-low-transparent: #F6F6F600; - --bg-surface-extra-low: #F6F6F6; - --bg-surface-extra-low-transparent: #F6F6F600; + --bg-surface: #ffffff; + --bg-surface-transparent: #ffffff00; + --bg-surface-low: #f6f6f6; + --bg-surface-low-transparent: #f6f6f600; + --bg-surface-extra-low: #f6f6f6; + --bg-surface-extra-low-transparent: #f6f6f600; --bg-surface-hover: rgba(0, 0, 0, 3%); --bg-surface-active: rgba(0, 0, 0, 5%); --bg-surface-border: rgba(0, 0, 0, 6%); @@ -22,7 +21,7 @@ --bg-positive-hover: rgba(69, 184, 59, 8%); --bg-positive-active: rgba(69, 184, 59, 15%); --bg-positive-border: rgba(69, 184, 59, 40%); - + --bg-caution: rgb(255, 179, 0); --bg-caution-hover: rgba(255, 179, 0, 8%); --bg-caution-active: rgba(255, 179, 0, 15%); @@ -37,18 +36,18 @@ --bg-badge: #989898; --bg-ping: hsla(137deg, 100%, 68%, 40%); --bg-ping-hover: hsla(137deg, 100%, 68%, 50%); - --bg-divider: hsla(0, 0%, 0%, .1); + --bg-divider: hsla(0, 0%, 0%, 0.1); /* text color | --tc-[background type]-[priority]: value */ --tc-surface-high: #000000; --tc-surface-normal: rgba(0, 0, 0, 78%); --tc-surface-normal-low: rgba(0, 0, 0, 60%); --tc-surface-low: rgba(0, 0, 0, 48%); - + --tc-primary-high: #ffffff; --tc-primary-normal: rgba(255, 255, 255, 68%); --tc-primary-low: rgba(255, 255, 255, 40%); - + --tc-positive-high: var(--bg-positive); --tc-positive-normal: rgb(69, 184, 59, 80%); --tc-positive-low: rgb(69, 184, 59, 60%); @@ -56,7 +55,7 @@ --tc-caution-high: var(--bg-caution); --tc-caution-normal: rgb(255, 179, 0, 80%); --tc-caution-low: rgb(255, 179, 0, 60%); - + --tc-danger-high: var(--bg-danger); --tc-danger-normal: rgba(240, 71, 71, 88%); --tc-danger-low: rgba(240, 71, 71, 60%); @@ -66,7 +65,6 @@ --tc-tooltip: white; --tc-badge: white; - /* system icons | --ic-[background type]-[priority]: value */ --ic-surface-high: #272727; --ic-surface-normal: #626262; @@ -102,7 +100,6 @@ --av-small: 36px; --av-extra-small: 24px; - /* shadow and overlay */ --bg-overlay: rgba(0, 0, 0, 20%); --bg-overlay-low: rgba(0, 0, 0, 50%); @@ -124,11 +121,9 @@ --bs-danger-border: inset 0 0 0 1px var(--bg-danger-border); --bs-danger-outline: 0 0 0 2px var(--bg-danger-border); - /* border */ --bo-radius: 8px; - /* font styles: font-size, letter-spacing, line-hight */ --fs-h1: 36px; --ls-h1: -1.5px; @@ -160,7 +155,6 @@ --fw-medium: 500; --fw-bold: 700; - /* spacing | --sp-[space]: value */ --sp-none: 0px; --sp-ultra-tight: 4px; @@ -170,17 +164,18 @@ --sp-loose: 20px; --sp-extra-loose: 32px; - /* other */ --border-width: 1px; --header-height: 54px; --navigation-sidebar-width: calc(64px + var(--border-width)); --navigation-drawer-width: calc(280px + var(--border-width)); - --navigation-width: calc(var(--navigation-sidebar-width) + var(--navigation-drawer-width)); + --navigation-width: calc( + var(--navigation-sidebar-width) + var(--navigation-drawer-width) + ); --people-drawer-width: calc(268px - var(--border-width)); --popup-window-drawer-width: 280px; - + @include screen.smallerThan(tabletBreakpoint) { --navigation-drawer-width: calc(240px + var(--border-width)); --people-drawer-width: calc(256px - var(--border-width)); @@ -191,11 +186,10 @@ --fluid-push: cubic-bezier(0, 0.8, 0.67, 0.97); --fluid-slide-down: cubic-bezier(0.02, 0.82, 0.4, 0.96); --fluid-slide-up: cubic-bezier(0.13, 0.56, 0.25, 0.99); - - --font-primary: 'Roboto', sans-serif; - --font-secondary: 'Roboto', sans-serif; -} + --font-primary: "Roboto", sans-serif; + --font-secondary: "Roboto", sans-serif; +} .silver-theme { /* background color | --bg-[background type]: value */ @@ -228,15 +222,14 @@ --bg-badge: hsl(0, 0%, 75%); --bg-ping: hsla(137deg, 100%, 38%, 40%); --bg-ping-hover: hsla(137deg, 100%, 38%, 50%); - --bg-divider: hsla(0, 0%, 100%, .1); - + --bg-divider: hsla(0, 0%, 100%, 0.1); /* text color | --tc-[background type]-[priority]: value */ --tc-surface-high: rgba(255, 255, 255, 98%); --tc-surface-normal: rgba(255, 255, 255, 94%); --tc-surface-normal-low: rgba(255, 255, 255, 60%); --tc-surface-low: rgba(255, 255, 255, 58%); - + --tc-primary-high: #ffffff; --tc-primary-normal: rgba(255, 255, 255, 0.68); --tc-primary-low: rgba(255, 255, 255, 0.4); @@ -262,7 +255,7 @@ --mx-uc-7: hsl(243, 100%, 74%); --mx-uc-8: hsl(94, 66%, 50%); } - + /* shadow and overlay */ --bg-overlay: rgba(0, 0, 0, 60%); --bg-overlay-low: rgba(0, 0, 0, 80%); @@ -274,7 +267,7 @@ --bs-primary-border: inset 0 0 0 1px var(--bg-primary-border); --bs-primary-outline: 0 0 0 2px var(--bg-primary-border); - + /* font styles: font-size, letter-spacing, line-hight */ --fs-h1: 35.6px; @@ -292,7 +285,7 @@ /* override normal font weight for dark mode */ --fw-normal: 350; - --font-secondary: 'InterVariable', 'Roboto', sans-serif; + --font-secondary: "InterVariable", "Roboto", sans-serif; } .dark-theme, @@ -317,14 +310,12 @@ --bg-badge: #c4c1ab; - /* text color | --tc-[background type]-[priority]: value */ --tc-surface-high: rgb(255, 251, 222, 94%); --tc-surface-normal: rgba(255, 251, 222, 94%); - --tc-surface-normal-low: rgba(255, 251, 222, 60%); + --tc-surface-normal-low: rgba(255, 251, 222, 60%); --tc-surface-low: rgba(255, 251, 222, 58%); - /* system icons | --ic-[background type]-[priority]: value */ --ic-surface-high: rgb(255, 251, 222); --ic-surface-normal: rgba(255, 251, 222, 84%); @@ -387,9 +378,11 @@ body { height: 100%; } -*, *::before, *::after { +*, +*::before, +*::after { box-sizing: border-box; - -webkit-tap-highlight-color: rgba(0,0,0,0); + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -webkit-tap-highlight-color: transparent; } a { @@ -428,16 +421,16 @@ button { textarea, input, input[type], -input[type=text], -input[type=username], -input[type=password], -input[type=email], -input[type=checkbox] { +input[type="text"], +input[type="username"], +input[type="password"], +input[type="email"], +input[type="checkbox"] { -webkit-appearance: none; -moz-appearance: none; appearance: none; } -input[type=checkbox] { +input[type="checkbox"] { margin: 0; padding: 0; width: 20px; @@ -468,11 +461,11 @@ textarea { } .noselect { -webkit-touch-callout: none; /* iOS Safari */ - -webkit-user-select: none; /* Safari */ - -khtml-user-select: none; /* Konqueror HTML */ - -moz-user-select: none; /* Old versions of Firefox */ - -ms-user-select: none; /* Internet Explorer/Edge */ - user-select: none; /* Non-prefixed version, currently + -webkit-user-select: none; /* Safari */ + -khtml-user-select: none; /* Konqueror HTML */ + -moz-user-select: none; /* Old versions of Firefox */ + -ms-user-select: none; /* Internet Explorer/Edge */ + user-select: none; /* Non-prefixed version, currently supported by Chrome, Edge, Opera and Firefox */ } @@ -484,4 +477,4 @@ audio:not([controls]) { display: flex; justify-content: center; align-items: center; -} \ No newline at end of file +} diff --git a/src/util/AsyncSearch.js b/src/util/AsyncSearch.js index d0a2130e..af961a4d 100644 --- a/src/util/AsyncSearch.js +++ b/src/util/AsyncSearch.js @@ -1,4 +1,4 @@ -import EventEmitter from 'events'; +import EventEmitter from "events"; class AsyncSearch extends EventEmitter { constructor() { @@ -6,7 +6,7 @@ class AsyncSearch extends EventEmitter { this._reset(); - this.RESULT_SENT = 'RESULT_SENT'; + this.RESULT_SENT = "RESULT_SENT"; } _reset() { @@ -59,7 +59,7 @@ class AsyncSearch extends EventEmitter { this._softReset(); this.term = this._normalize(term); - if (this.term === '') { + if (this.term === "") { this._sendFindings(); return; } @@ -78,7 +78,11 @@ class AsyncSearch extends EventEmitter { ) { if (this._match(this.dataList[searchIndex])) { this.findingList.push(this.dataList[searchIndex]); - if (typeof this.limit === 'number' && this.findingList.length >= this.limit) break; + if ( + typeof this.limit === "number" && + this.findingList.length >= this.limit + ) + break; } const calcFinishTime = window.performance.now(); @@ -93,20 +97,20 @@ class AsyncSearch extends EventEmitter { } } - if (lastFindingCount !== this.findingList.length - || lastFindingCount === 0) this._sendFindings(); + if (lastFindingCount !== this.findingList.length || lastFindingCount === 0) + this._sendFindings(); this._softReset(); } _match(item) { - if (typeof item === 'string') { + if (typeof item === "string") { return this._compare(item); } - if (typeof item === 'object') { + if (typeof item === "object") { if (Array.isArray(this.searchKeys)) { return !!this.searchKeys.find((key) => this._compare(item[key])); } - if (typeof this.searchKeys === 'string') { + if (typeof this.searchKeys === "string") { return this._compare(item[this.searchKeys]); } } @@ -114,16 +118,16 @@ class AsyncSearch extends EventEmitter { } _compare(item) { - if (typeof item !== 'string') return false; + if (typeof item !== "string") return false; const myItem = this._normalize(item); if (this.isContain) return myItem.indexOf(this.term) !== -1; return myItem.startsWith(this.term); } _normalize(item) { - let myItem = item.normalize(this.normalizeUnicode ? 'NFKC' : 'NFC'); + let myItem = item.normalize(this.normalizeUnicode ? "NFKC" : "NFC"); if (!this.isCaseSensitive) myItem = myItem.toLocaleLowerCase(); - if (this.ignoreWhitespace) myItem = myItem.replace(/\s/g, ''); + if (this.ignoreWhitespace) myItem = myItem.replace(/\s/g, ""); return myItem; } diff --git a/src/util/Postie.js b/src/util/Postie.js index 73c8f9e8..5d796931 100644 --- a/src/util/Postie.js +++ b/src/util/Postie.js @@ -15,7 +15,9 @@ class Postie { const subscribers = this._getSubscribers(topic); const inboxes = subscribers.get(address); if (inboxes === undefined) { - throw new Error(`Inbox on topic:"${topic}" at address:"${address}" doesn't exist.`); + throw new Error( + `Inbox on topic:"${topic}" at address:"${address}" doesn't exist.` + ); } return inboxes; } @@ -30,9 +32,7 @@ class Postie { } hasTopicAndSubscriber(topic, address) { - return (this.hasTopic(topic)) - ? this.hasSubscriber(topic, address) - : false; + return this.hasTopic(topic) ? this.hasSubscriber(topic, address) : false; } /** @@ -41,8 +41,8 @@ class Postie { * @param {function} inbox - The inbox function to receive post data */ subscribe(topic, address, inbox) { - if (typeof inbox !== 'function') { - throw new TypeError('Inbox must be a function.'); + if (typeof inbox !== "function") { + throw new TypeError("Inbox must be a function."); } if (this._topics.has(topic) === false) { @@ -59,12 +59,19 @@ class Postie { unsubscribe(topic, address, inbox) { const subscribers = this._getSubscribers(topic); - if (!subscribers) throw new Error(`Unable to unsubscribe. Topic: "${topic}" doesn't exist.`); + if (!subscribers) + throw new Error( + `Unable to unsubscribe. Topic: "${topic}" doesn't exist.` + ); const inboxes = subscribers.get(address); - if (!inboxes) throw new Error(`Unable to unsubscribe. Subscriber on topic:"${topic}" at address:"${address}" doesn't exist`); + if (!inboxes) + throw new Error( + `Unable to unsubscribe. Subscriber on topic:"${topic}" at address:"${address}" doesn't exist` + ); - if (!inboxes.delete(inbox)) throw new Error('Unable to unsubscribe. Inbox doesn\'t exist'); + if (!inboxes.delete(inbox)) + throw new Error("Unable to unsubscribe. Inbox doesn't exist"); if (inboxes.size === 0) subscribers.delete(address); if (subscribers.size === 0) this._topics.delete(topic); @@ -78,12 +85,14 @@ class Postie { post(topic, address, data) { const sendPost = (inboxes, addr) => { if (inboxes === undefined) { - throw new Error(`Unable to post on topic:"${topic}" at address:"${addr}". Subscriber doesn't exist.`); + throw new Error( + `Unable to post on topic:"${topic}" at address:"${addr}". Subscriber doesn't exist.` + ); } inboxes.forEach((inbox) => inbox(data)); }; - if (typeof address === 'string') { + if (typeof address === "string") { sendPost(this._getInboxes(topic, address), address); return; } diff --git a/src/util/colorMXID.js b/src/util/colorMXID.js index 4d303aae..75c70a0d 100644 --- a/src/util/colorMXID.js +++ b/src/util/colorMXID.js @@ -10,7 +10,7 @@ export function hashCode(str) { for (i = 0; i < str.length; i += 1) { chr = str.charCodeAt(i); // eslint-disable-next-line no-bitwise - hash = ((hash << 5) - hash) + chr; + hash = (hash << 5) - hash + chr; // eslint-disable-next-line no-bitwise hash |= 0; } diff --git a/src/util/common.js b/src/util/common.js index 2affe27d..f2e261c6 100644 --- a/src/util/common.js +++ b/src/util/common.js @@ -1,10 +1,10 @@ /* eslint-disable max-classes-per-file */ export function bytesToSize(bytes) { - const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; - if (bytes === 0) return 'n/a'; + const sizes = ["Bytes", "KB", "MB", "GB", "TB"]; + if (bytes === 0) return "n/a"; const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)), 10); if (i === 0) return `${bytes} ${sizes[i]}`; - return `${(bytes / (1024 ** i)).toFixed(1)} ${sizes[i]}`; + return `${(bytes / 1024 ** i).toFixed(1)} ${sizes[i]}`; } export function diffMinutes(dt2, dt1) { @@ -15,9 +15,9 @@ export function diffMinutes(dt2, dt1) { export function isInSameDay(dt2, dt1) { return ( - dt2.getFullYear() === dt1.getFullYear() - && dt2.getMonth() === dt1.getMonth() - && dt2.getDate() === dt1.getDate() + dt2.getFullYear() === dt1.getFullYear() && + dt2.getMonth() === dt1.getMonth() && + dt2.getDate() === dt1.getDate() ); } @@ -48,7 +48,7 @@ export function getEventCords(ev, targetSelector) { } export function abbreviateNumber(number) { - if (number > 99) return '99+'; + if (number > 99) return "99+"; return number; } @@ -120,9 +120,9 @@ export function cssVar(name) { } export function setFavicon(url) { - const favicon = document.querySelector('#favicon'); + const favicon = document.querySelector("#favicon"); if (!favicon) return; - favicon.setAttribute('href', url); + favicon.setAttribute("href", url); } export function copyToClipboard(text) { @@ -130,15 +130,15 @@ export function copyToClipboard(text) { navigator.clipboard.writeText(text); } else { const host = document.body; - const copyInput = document.createElement('input'); - copyInput.style.position = 'fixed'; - copyInput.style.opacity = '0'; + const copyInput = document.createElement("input"); + copyInput.style.position = "fixed"; + copyInput.style.opacity = "0"; copyInput.value = text; host.append(copyInput); copyInput.select(); copyInput.setSelectionRange(0, 99999); - document.execCommand('Copy'); + document.execCommand("Copy"); copyInput.remove(); } } @@ -189,10 +189,10 @@ export function scaleDownImage(imageFile, width, height) { newWidth = width; } - const canvas = document.createElement('canvas'); + const canvas = document.createElement("canvas"); canvas.width = newWidth; canvas.height = newHeight; - const ctx = canvas.getContext('2d'); + const ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0, newWidth, newHeight); canvas.toBlob((thumbnail) => { @@ -212,7 +212,7 @@ export function scaleDownImage(imageFile, width, height) { * @returns {RegExp} */ export function idRegex(sigil, flags, prefix) { - const servername = '(?:[a-zA-Z0-9-.]*[a-zA-Z0-9]+|\\[\\S+?\\])(?::\\d+)?'; + const servername = "(?:[a-zA-Z0-9-.]*[a-zA-Z0-9]+|\\[\\S+?\\])(?::\\d+)?"; return new RegExp(`${prefix}(${sigil}\\S+:${servername})`, flags); } diff --git a/src/util/cryptE2ERoomKeys.js b/src/util/cryptE2ERoomKeys.js index 50254a3c..219c65be 100644 --- a/src/util/cryptE2ERoomKeys.js +++ b/src/util/cryptE2ERoomKeys.js @@ -17,7 +17,7 @@ function friendlyError(msg, friendlyText) { } function cryptoFailMsg() { - return 'Your browser does not support the required cryptography extensions'; + return "Your browser does not support the required cryptography extensions"; } /** * Derive the AES and HMAC-SHA-256 keys for the file @@ -33,11 +33,11 @@ async function deriveKeys(salt, iterations, password) { let key; try { key = await subtleCrypto.importKey( - 'raw', + "raw", new TextEncoder().encode(password), - { name: 'PBKDF2' }, + { name: "PBKDF2" }, false, - ['deriveBits'], + ["deriveBits"] ); } catch (e) { throw friendlyError(`subtleCrypto.importKey failed: ${e}`, cryptoFailMsg()); @@ -47,46 +47,56 @@ async function deriveKeys(salt, iterations, password) { try { keybits = await subtleCrypto.deriveBits( { - name: 'PBKDF2', + name: "PBKDF2", salt, iterations, - hash: 'SHA-512', + hash: "SHA-512", }, key, - 512, + 512 ); } catch (e) { - throw friendlyError(`subtleCrypto.deriveBits failed: ${e}`, cryptoFailMsg()); + throw friendlyError( + `subtleCrypto.deriveBits failed: ${e}`, + cryptoFailMsg() + ); } const now = new Date(); - console.log(`E2e import/export: deriveKeys took ${(now - start)}ms`); + console.log(`E2e import/export: deriveKeys took ${now - start}ms`); const aesKey = keybits.slice(0, 32); const hmacKey = keybits.slice(32); - const aesProm = subtleCrypto.importKey( - 'raw', - aesKey, - { name: 'AES-CTR' }, - false, - ['encrypt', 'decrypt'], - ).catch((e) => { - throw friendlyError(`subtleCrypto.importKey failed for AES key: ${e}`, cryptoFailMsg()); - }); + const aesProm = subtleCrypto + .importKey("raw", aesKey, { name: "AES-CTR" }, false, [ + "encrypt", + "decrypt", + ]) + .catch((e) => { + throw friendlyError( + `subtleCrypto.importKey failed for AES key: ${e}`, + cryptoFailMsg() + ); + }); - const hmacProm = subtleCrypto.importKey( - 'raw', - hmacKey, - { - name: 'HMAC', - hash: { name: 'SHA-256' }, - }, - false, - ['sign', 'verify'], - ).catch((e) => { - throw friendlyError(`subtleCrypto.importKey failed for HMAC key: ${e}`, cryptoFailMsg()); - }); + const hmacProm = subtleCrypto + .importKey( + "raw", + hmacKey, + { + name: "HMAC", + hash: { name: "SHA-256" }, + }, + false, + ["sign", "verify"] + ) + .catch((e) => { + throw friendlyError( + `subtleCrypto.importKey failed for HMAC key: ${e}`, + cryptoFailMsg() + ); + }); // eslint-disable-next-line no-return-await return await Promise.all([aesProm, hmacProm]); @@ -121,8 +131,8 @@ function encodeBase64(uint8Array) { return window.btoa(latin1String); } -const HEADER_LINE = '-----BEGIN MEGOLM SESSION DATA-----'; -const TRAILER_LINE = '-----END MEGOLM SESSION DATA-----'; +const HEADER_LINE = "-----BEGIN MEGOLM SESSION DATA-----"; +const TRAILER_LINE = "-----END MEGOLM SESSION DATA-----"; /** * Unbase64 an ascii-armoured megolm key file @@ -141,9 +151,9 @@ function unpackMegolmKeyFile(data) { // look for the start line let lineStart = 0; while (1) { - const lineEnd = fileStr.indexOf('\n', lineStart); + const lineEnd = fileStr.indexOf("\n", lineStart); if (lineEnd < 0) { - throw new Error('Header line not found'); + throw new Error("Header line not found"); } const line = fileStr.slice(lineStart, lineEnd).trim(); @@ -159,14 +169,16 @@ function unpackMegolmKeyFile(data) { // look for the end line while (1) { - const lineEnd = fileStr.indexOf('\n', lineStart); - const line = fileStr.slice(lineStart, lineEnd < 0 ? undefined : lineEnd).trim(); + const lineEnd = fileStr.indexOf("\n", lineStart); + const line = fileStr + .slice(lineStart, lineEnd < 0 ? undefined : lineEnd) + .trim(); if (line === TRAILER_LINE) { break; } if (lineEnd < 0) { - throw new Error('Trailer line not found'); + throw new Error("Trailer line not found"); } // start the next line after the newline @@ -177,7 +189,6 @@ function unpackMegolmKeyFile(data) { return decodeBase64(fileStr.slice(dataStart, dataEnd)); } - /** * ascii-armour a megolm key file * @@ -189,20 +200,20 @@ function unpackMegolmKeyFile(data) { function packMegolmKeyFile(data) { // we split into lines before base64ing, because encodeBase64 doesn't deal // terribly well with large arrays. - const LINE_LENGTH = ((72 * 4) / 3); + const LINE_LENGTH = (72 * 4) / 3; const nLines = Math.ceil(data.length / LINE_LENGTH); const lines = new Array(nLines + 3); lines[0] = HEADER_LINE; let o = 0; let i; for (i = 1; i <= nLines; i += 1) { - lines[i] = encodeBase64(data.subarray(o, o+LINE_LENGTH)); + lines[i] = encodeBase64(data.subarray(o, o + LINE_LENGTH)); o += LINE_LENGTH; } lines[i] = TRAILER_LINE; i += 1; - lines[i] = ''; - return (new TextEncoder().encode(lines.join('\n'))).buffer; + lines[i] = ""; + return new TextEncoder().encode(lines.join("\n")).buffer; } export async function decryptMegolmKeyFile(data, password) { @@ -210,22 +221,23 @@ export async function decryptMegolmKeyFile(data, password) { // check we have a version byte if (body.length < 1) { - throw friendlyError('Invalid file: too short', 'Not a valid keyfile'); + throw friendlyError("Invalid file: too short", "Not a valid keyfile"); } const version = body[0]; if (version !== 1) { - throw friendlyError('Unsupported version', 'Not a valid keyfile'); + throw friendlyError("Unsupported version", "Not a valid keyfile"); } const ciphertextLength = body.length - (1 + 16 + 16 + 4 + 32); if (ciphertextLength < 0) { - throw friendlyError('Invalid file: too short', 'Not a valid keyfile'); + throw friendlyError("Invalid file: too short", "Not a valid keyfile"); } const salt = body.subarray(1, 1 + 16); const iv = body.subarray(17, 17 + 16); - const iterations = body[33] << 24 | body[34] << 16 | body[35] << 8 | body[36]; + const iterations = + (body[33] << 24) | (body[34] << 16) | (body[35] << 8) | body[36]; const ciphertext = body.subarray(37, 37 + ciphertextLength); const hmac = body.subarray(-32); @@ -235,28 +247,31 @@ export async function decryptMegolmKeyFile(data, password) { let isValid; try { isValid = await subtleCrypto.verify( - { name: 'HMAC' }, + { name: "HMAC" }, hmacKey, hmac, - toVerify, + toVerify ); } catch (e) { throw friendlyError(`subtleCrypto.verify failed: ${e}`, cryptoFailMsg()); } if (!isValid) { - throw friendlyError('hmac mismatch', 'Authentication check failed: Incorrect password?'); + throw friendlyError( + "hmac mismatch", + "Authentication check failed: Incorrect password?" + ); } let plaintext; try { plaintext = await subtleCrypto.decrypt( { - name: 'AES-CTR', + name: "AES-CTR", counter: iv, length: 64, }, aesKey, - ciphertext, + ciphertext ); } catch (e) { throw friendlyError(`subtleCrypto.decrypt failed: ${e}`, cryptoFailMsg()); @@ -297,41 +312,40 @@ export async function encryptMegolmKeyFile(data, password, options) { try { ciphertext = await subtleCrypto.encrypt( { - name: 'AES-CTR', + name: "AES-CTR", counter: iv, length: 64, }, aesKey, - encodedData, + encodedData ); } catch (e) { - throw friendlyError('subtleCrypto.encrypt failed: ' + e, cryptoFailMsg()); + throw friendlyError("subtleCrypto.encrypt failed: " + e, cryptoFailMsg()); } const cipherArray = new Uint8Array(ciphertext); - const bodyLength = (1+salt.length+iv.length+4+cipherArray.length+32); + const bodyLength = 1 + salt.length + iv.length + 4 + cipherArray.length + 32; const resultBuffer = new Uint8Array(bodyLength); let idx = 0; resultBuffer[idx++] = 1; // version - resultBuffer.set(salt, idx); idx += salt.length; - resultBuffer.set(iv, idx); idx += iv.length; + resultBuffer.set(salt, idx); + idx += salt.length; + resultBuffer.set(iv, idx); + idx += iv.length; resultBuffer[idx++] = kdfRounds >> 24; resultBuffer[idx++] = (kdfRounds >> 16) & 0xff; resultBuffer[idx++] = (kdfRounds >> 8) & 0xff; resultBuffer[idx++] = kdfRounds & 0xff; - resultBuffer.set(cipherArray, idx); idx += cipherArray.length; + resultBuffer.set(cipherArray, idx); + idx += cipherArray.length; const toSign = resultBuffer.subarray(0, idx); let hmac; try { - hmac = await subtleCrypto.sign( - { name: 'HMAC' }, - hmacKey, - toSign, - ); + hmac = await subtleCrypto.sign({ name: "HMAC" }, hmacKey, toSign); } catch (e) { - throw friendlyError('subtleCrypto.sign failed: ' + e, cryptoFailMsg()); + throw friendlyError("subtleCrypto.sign failed: " + e, cryptoFailMsg()); } const hmacArray = new Uint8Array(hmac); diff --git a/src/util/markdown.js b/src/util/markdown.js index c6c1a490..f0001d1f 100644 --- a/src/util/markdown.js +++ b/src/util/markdown.js @@ -1,15 +1,21 @@ /* eslint-disable no-param-reassign */ /* eslint-disable no-use-before-define */ -import SimpleMarkdown from '@khanacademy/simple-markdown'; -import { idRegex, parseIdUri } from './common'; +import SimpleMarkdown from "@khanacademy/simple-markdown"; +import { idRegex, parseIdUri } from "./common"; const { - defaultRules, parserFor, outputFor, anyScopeRegex, blockRegex, inlineRegex, - sanitizeText, sanitizeUrl, + defaultRules, + parserFor, + outputFor, + anyScopeRegex, + blockRegex, + inlineRegex, + sanitizeText, + sanitizeUrl, } = SimpleMarkdown; function htmlTag(tagName, content, attributes, isClosed) { - let s = ''; + let s = ""; Object.entries(attributes || {}).forEach(([k, v]) => { if (v !== undefined) { s += ` ${sanitizeText(k)}`; @@ -26,7 +32,9 @@ function htmlTag(tagName, content, attributes, isClosed) { } function mathHtml(wrap, node) { - return htmlTag(wrap, htmlTag('code', sanitizeText(node.content)), { 'data-mx-maths': node.content }); + return htmlTag(wrap, htmlTag("code", sanitizeText(node.content)), { + "data-mx-maths": node.content, + }); } const emojiRegex = /^:([\w-]+):/; @@ -38,23 +46,30 @@ const plainRules = { }, userMention: { order: defaultRules.em.order - 0.9, - match: inlineRegex(idRegex('@', undefined, '^')), + match: inlineRegex(idRegex("@", undefined, "^")), parse: (capture, _, state) => ({ - type: 'mention', - content: state.userNames[capture[1]] ? `@${state.userNames[capture[1]]}` : capture[1], + type: "mention", + content: state.userNames[capture[1]] + ? `@${state.userNames[capture[1]]}` + : capture[1], id: capture[1], }), }, roomMention: { order: defaultRules.em.order - 0.8, - match: inlineRegex(idRegex('#', undefined, '^')), - parse: (capture) => ({ type: 'mention', content: capture[1], id: capture[1] }), + match: inlineRegex(idRegex("#", undefined, "^")), + parse: (capture) => ({ + type: "mention", + content: capture[1], + id: capture[1], + }), }, mention: { - plain: (node, _, state) => (state.kind === 'edit' ? node.id : node.content), - html: (node) => htmlTag('a', sanitizeText(node.content), { - href: `https://matrix.to/#/${encodeURIComponent(node.id)}`, - }), + plain: (node, _, state) => (state.kind === "edit" ? node.id : node.content), + html: (node) => + htmlTag("a", sanitizeText(node.content), { + href: `https://matrix.to/#/${encodeURIComponent(node.id)}`, + }), }, emoji: { order: defaultRules.em.order - 0.1, @@ -66,28 +81,35 @@ const plainRules = { if (emoji) return capture; return null; }, - parse: (capture, _, state) => ({ content: capture[1], emoji: state.emojis.get(capture[1]) }), - plain: ({ emoji }) => (emoji.mxc - ? `:${emoji.shortcode}:` - : emoji.unicode), - html: ({ emoji }) => (emoji.mxc - ? htmlTag('img', null, { - 'data-mx-emoticon': null, - src: emoji.mxc, - alt: `:${emoji.shortcode}:`, - title: `:${emoji.shortcode}:`, - height: 32, - }, false) - : emoji.unicode), + parse: (capture, _, state) => ({ + content: capture[1], + emoji: state.emojis.get(capture[1]), + }), + plain: ({ emoji }) => (emoji.mxc ? `:${emoji.shortcode}:` : emoji.unicode), + html: ({ emoji }) => + emoji.mxc + ? htmlTag( + "img", + null, + { + "data-mx-emoticon": null, + src: emoji.mxc, + alt: `:${emoji.shortcode}:`, + title: `:${emoji.shortcode}:`, + height: 32, + }, + false + ) + : emoji.unicode, }, newline: { ...defaultRules.newline, - plain: () => '\n', + plain: () => "\n", }, paragraph: { ...defaultRules.paragraph, plain: (node, output, state) => `${output(node.content, state)}\n\n`, - html: (node, output, state) => htmlTag('p', output(node.content, state)), + html: (node, output, state) => htmlTag("p", output(node.content, state)), }, escape: { ...defaultRules.escape, @@ -96,14 +118,17 @@ const plainRules = { br: { ...defaultRules.br, match: anyScopeRegex(/^ *\n/), - plain: () => '\n', + plain: () => "\n", }, text: { ...defaultRules.text, - match: anyScopeRegex(/^[\s\S]+?(?=[^0-9A-Za-z\s\u00c0-\uffff]| *\n|\w+:\S|$)/), - plain: (node, _, state) => (state.kind === 'edit' - ? node.content.replace(/(\*|_|!\[|\[|\|\||\$\$?)/g, '\\$1') - : node.content), + match: anyScopeRegex( + /^[\s\S]+?(?=[^0-9A-Za-z\s\u00c0-\uffff]| *\n|\w+:\S|$)/ + ), + plain: (node, _, state) => + state.kind === "edit" + ? node.content.replace(/(\*|_|!\[|\[|\|\||\$\$?)/g, "\\$1") + : node.content, }, }; @@ -115,30 +140,41 @@ const markdownRules = { match: blockRegex(/^ *(#{1,6})([^\n:]*?(?: [^\n]*?)?)#* *(?:\n *)*\n/), plain: (node, output, state) => { const out = output(node.content, state); - if (state.kind === 'edit' || state.kind === 'notification' || node.level > 2) { - return `${'#'.repeat(node.level)} ${out}\n\n`; + if ( + state.kind === "edit" || + state.kind === "notification" || + node.level > 2 + ) { + return `${"#".repeat(node.level)} ${out}\n\n`; } - return `${out}\n${(node.level === 1 ? '=' : '-').repeat(out.length)}\n\n`; + return `${out}\n${(node.level === 1 ? "=" : "-").repeat(out.length)}\n\n`; }, }, hr: { ...defaultRules.hr, - plain: () => '---\n\n', + plain: () => "---\n\n", }, codeBlock: { ...defaultRules.codeBlock, - plain: (node) => `\`\`\`${node.lang || ''}\n${node.content}\n\`\`\`\n`, - html: (node) => htmlTag('pre', htmlTag('code', sanitizeText(node.content), { - class: node.lang ? `language-${node.lang}` : undefined, - })), + plain: (node) => `\`\`\`${node.lang || ""}\n${node.content}\n\`\`\`\n`, + html: (node) => + htmlTag( + "pre", + htmlTag("code", sanitizeText(node.content), { + class: node.lang ? `language-${node.lang}` : undefined, + }) + ), }, fence: { ...defaultRules.fence, - match: blockRegex(/^ *(`{3,}|~{3,}) *(?:(\S+) *)?\n([\s\S]+?)\n?\1 *(?:\n *)*\n/), + match: blockRegex( + /^ *(`{3,}|~{3,}) *(?:(\S+) *)?\n([\s\S]+?)\n?\1 *(?:\n *)*\n/ + ), }, blockQuote: { ...defaultRules.blockQuote, - plain: (node, output, state) => `> ${output(node.content, state).trim().replace(/\n/g, '\n> ')}\n\n`, + plain: (node, output, state) => + `> ${output(node.content, state).trim().replace(/\n/g, "\n> ")}\n\n`, }, list: { ...defaultRules.list, @@ -146,15 +182,20 @@ const markdownRules = { const oldList = state._list; state._list = true; - let items = node.items.map((item, i) => { - const prefix = node.ordered ? `${node.start + i}. ` : '* '; - return prefix + output(item, state).replace(/\n/g, `\n${' '.repeat(prefix.length)}`); - }).join('\n'); + let items = node.items + .map((item, i) => { + const prefix = node.ordered ? `${node.start + i}. ` : "* "; + return ( + prefix + + output(item, state).replace(/\n/g, `\n${" ".repeat(prefix.length)}`) + ); + }) + .join("\n"); state._list = oldList; if (!state._list) { - items += '\n\n'; + items += "\n\n"; } return items; }, @@ -167,32 +208,34 @@ const markdownRules = { const colWidth = node.align.map((align) => { switch (align) { - case 'left': - case 'right': + case "left": + case "right": return 2; - case 'center': + case "center": return 3; default: return 1; } }); header.forEach((s, i) => { - if (s.length > colWidth[i])colWidth[i] = s.length; + if (s.length > colWidth[i]) colWidth[i] = s.length; }); - const cells = node.cells.map((row) => row.map((content, i) => { - const s = output(content, state); - if (colWidth[i] === undefined || s.length > colWidth[i]) { - colWidth[i] = s.length; - } - return s; - })); + const cells = node.cells.map((row) => + row.map((content, i) => { + const s = output(content, state); + if (colWidth[i] === undefined || s.length > colWidth[i]) { + colWidth[i] = s.length; + } + return s; + }) + ); function pad(s, i) { switch (node.align[i]) { - case 'right': + case "right": return s.padStart(colWidth[i]); - case 'center': + case "center": return s .padStart(s.length + Math.floor((colWidth[i] - s.length) / 2)) .padEnd(colWidth[i]); @@ -203,58 +246,60 @@ const markdownRules = { const line = colWidth.map((len, i) => { switch (node.align[i]) { - case 'left': - return `:${'-'.repeat(len - 1)}`; - case 'center': - return `:${'-'.repeat(len - 2)}:`; - case 'right': - return `${'-'.repeat(len - 1)}:`; + case "left": + return `:${"-".repeat(len - 1)}`; + case "center": + return `:${"-".repeat(len - 2)}:`; + case "right": + return `${"-".repeat(len - 1)}:`; default: - return '-'.repeat(len); + return "-".repeat(len); } }); const table = [ header.map(pad), line, - ...cells.map((row) => row.map(pad))]; + ...cells.map((row) => row.map(pad)), + ]; - return table.map((row) => `| ${row.join(' | ')} |\n`).join(''); + return table.map((row) => `| ${row.join(" | ")} |\n`).join(""); }, }, displayMath: { order: defaultRules.table.order + 0.1, match: blockRegex(/^ *\$\$ *\n?([\s\S]+?)\n?\$\$ *(?:\n *)*\n/), parse: (capture) => ({ content: capture[1] }), - plain: (node) => (node.content.includes('\n') - ? `$$\n${node.content}\n$$\n` - : `$$${node.content}$$\n`), - html: (node) => mathHtml('div', node), + plain: (node) => + node.content.includes("\n") + ? `$$\n${node.content}\n$$\n` + : `$$${node.content}$$\n`, + html: (node) => mathHtml("div", node), }, shrug: { order: defaultRules.escape.order - 0.1, match: inlineRegex(/^¯\\_\(ツ\)_\/¯/), - parse: (capture) => ({ type: 'text', content: capture[0] }), + parse: (capture) => ({ type: "text", content: capture[0] }), }, tableSeparator: { ...defaultRules.tableSeparator, - plain: () => ' | ', + plain: () => " | ", }, link: { ...defaultRules.link, plain: (node, output, state) => { const out = output(node.content, state); - const target = sanitizeUrl(node.target) || ''; + const target = sanitizeUrl(node.target) || ""; if (out !== target || node.title) { - return `[${out}](${target}${node.title ? ` "${node.title}"` : ''})`; + return `[${out}](${target}${node.title ? ` "${node.title}"` : ""})`; } return out; }, html: (node, output, state) => { const out = output(node.content, state); - const target = sanitizeUrl(node.target) || ''; + const target = sanitizeUrl(node.target) || ""; if (out !== target || node.title) { - return htmlTag('a', out, { + return htmlTag("a", out, { href: target, title: node.title, }); @@ -264,12 +309,21 @@ const markdownRules = { }, image: { ...defaultRules.image, - plain: (node) => `![${node.alt}](${sanitizeUrl(node.target) || ''}${node.title ? ` "${node.title}"` : ''})`, - html: (node) => htmlTag('img', '', { - src: sanitizeUrl(node.target) || '', - alt: node.alt, - title: node.title, - }, false), + plain: (node) => + `![${node.alt}](${sanitizeUrl(node.target) || ""}${ + node.title ? ` "${node.title}"` : "" + })`, + html: (node) => + htmlTag( + "img", + "", + { + src: sanitizeUrl(node.target) || "", + alt: node.alt, + title: node.title, + }, + false + ), }, reflink: undefined, refimage: undefined, @@ -302,55 +356,62 @@ const markdownRules = { reason: capture[2], }), plain: (node, output, state) => { - const warning = `spoiler${node.reason ? `: ${node.reason}` : ''}`; + const warning = `spoiler${node.reason ? `: ${node.reason}` : ""}`; switch (state.kind) { - case 'edit': - return `||${output(node.content, state)}||${node.reason ? `(${node.reason})` : ''}`; - case 'notification': + case "edit": + return `||${output(node.content, state)}||${ + node.reason ? `(${node.reason})` : "" + }`; + case "notification": return `<${warning}>`; default: return `[${warning}](${output(node.content, state)})`; } }, - html: (node, output, state) => htmlTag( - 'span', - output(node.content, state), - { 'data-mx-spoiler': node.reason || null }, - ), + html: (node, output, state) => + htmlTag("span", output(node.content, state), { + "data-mx-spoiler": node.reason || null, + }), }, inlineMath: { order: defaultRules.del.order + 0.2, match: inlineRegex(/^\$(\S[\s\S]+?\S|\S)\$(?!\d)/), parse: (capture) => ({ content: capture[1] }), plain: (node) => `$${node.content}$`, - html: (node) => mathHtml('span', node), + html: (node) => mathHtml("span", node), }, }; function mapElement(el) { switch (el.tagName) { - case 'MX-REPLY': + case "MX-REPLY": return []; - case 'P': - return [{ type: 'paragraph', content: mapChildren(el) }]; - case 'BR': - return [{ type: 'br' }]; + case "P": + return [{ type: "paragraph", content: mapChildren(el) }]; + case "BR": + return [{ type: "br" }]; - case 'H1': - case 'H2': - case 'H3': - case 'H4': - case 'H5': - case 'H6': - return [{ type: 'heading', level: Number(el.tagName[1]), content: mapChildren(el) }]; - case 'HR': - return [{ type: 'hr' }]; - case 'PRE': { + case "H1": + case "H2": + case "H3": + case "H4": + case "H5": + case "H6": + return [ + { + type: "heading", + level: Number(el.tagName[1]), + content: mapChildren(el), + }, + ]; + case "HR": + return [{ type: "hr" }]; + case "PRE": { let lang; if (el.firstChild) { Array.from(el.firstChild.classList).some((c) => { - const langPrefix = 'language-'; + const langPrefix = "language-"; if (c.startsWith(langPrefix)) { lang = c.slice(langPrefix.length); return true; @@ -358,94 +419,117 @@ function mapElement(el) { return false; }); } - return [{ type: 'codeBlock', lang, content: el.innerText }]; + return [{ type: "codeBlock", lang, content: el.innerText }]; } - case 'BLOCKQUOTE': - return [{ type: 'blockQuote', content: mapChildren(el) }]; - case 'UL': - return [{ type: 'list', items: Array.from(el.childNodes).map(mapNode) }]; - case 'OL': - return [{ - type: 'list', - ordered: true, - start: Number(el.getAttribute('start')), - items: Array.from(el.childNodes).map(mapNode), - }]; - case 'TABLE': { - const headerEl = Array.from(el.querySelector('thead > tr').childNodes); - const align = headerEl.map((childE) => childE.style['text-align']); - return [{ - type: 'table', - header: headerEl.map(mapChildren), - align, - cells: Array.from(el.querySelectorAll('tbody > tr')).map((rowEl) => Array.from(rowEl.childNodes).map((childEl, i) => { - if (align[i] === undefined) align[i] = childEl.style['text-align']; - return mapChildren(childEl); - })), - }]; + case "BLOCKQUOTE": + return [{ type: "blockQuote", content: mapChildren(el) }]; + case "UL": + return [{ type: "list", items: Array.from(el.childNodes).map(mapNode) }]; + case "OL": + return [ + { + type: "list", + ordered: true, + start: Number(el.getAttribute("start")), + items: Array.from(el.childNodes).map(mapNode), + }, + ]; + case "TABLE": { + const headerEl = Array.from(el.querySelector("thead > tr").childNodes); + const align = headerEl.map((childE) => childE.style["text-align"]); + return [ + { + type: "table", + header: headerEl.map(mapChildren), + align, + cells: Array.from(el.querySelectorAll("tbody > tr")).map((rowEl) => + Array.from(rowEl.childNodes).map((childEl, i) => { + if (align[i] === undefined) + align[i] = childEl.style["text-align"]; + return mapChildren(childEl); + }) + ), + }, + ]; } - case 'A': { - const href = el.getAttribute('href'); + case "A": { + const href = el.getAttribute("href"); const id = parseIdUri(href); - if (id) return [{ type: 'mention', content: el.innerText, id }]; + if (id) return [{ type: "mention", content: el.innerText, id }]; - return [{ - type: 'link', - target: el.getAttribute('href'), - title: el.getAttribute('title'), - content: mapChildren(el), - }]; + return [ + { + type: "link", + target: el.getAttribute("href"), + title: el.getAttribute("title"), + content: mapChildren(el), + }, + ]; } - case 'IMG': { - const src = el.getAttribute('src'); - let title = el.getAttribute('title'); - if (el.hasAttribute('data-mx-emoticon')) { - if (title.length > 2 && title.startsWith(':') && title.endsWith(':')) { + case "IMG": { + const src = el.getAttribute("src"); + let title = el.getAttribute("title"); + if (el.hasAttribute("data-mx-emoticon")) { + if (title.length > 2 && title.startsWith(":") && title.endsWith(":")) { title = title.slice(1, -1); } - return [{ - type: 'emoji', - content: title, - emoji: { - mxc: src, - shortcode: title, + return [ + { + type: "emoji", + content: title, + emoji: { + mxc: src, + shortcode: title, + }, }, - }]; + ]; } - return [{ - type: 'image', - alt: el.getAttribute('alt'), - target: src, - title, - }]; + return [ + { + type: "image", + alt: el.getAttribute("alt"), + target: src, + title, + }, + ]; } - case 'EM': - case 'I': - return [{ type: 'em', content: mapChildren(el) }]; - case 'STRONG': - case 'B': - return [{ type: 'strong', content: mapChildren(el) }]; - case 'U': - return [{ type: 'u', content: mapChildren(el) }]; - case 'DEL': - case 'STRIKE': - return [{ type: 'del', content: mapChildren(el) }]; - case 'CODE': - return [{ type: 'inlineCode', content: el.innerText }]; + case "EM": + case "I": + return [{ type: "em", content: mapChildren(el) }]; + case "STRONG": + case "B": + return [{ type: "strong", content: mapChildren(el) }]; + case "U": + return [{ type: "u", content: mapChildren(el) }]; + case "DEL": + case "STRIKE": + return [{ type: "del", content: mapChildren(el) }]; + case "CODE": + return [{ type: "inlineCode", content: el.innerText }]; - case 'DIV': - if (el.hasAttribute('data-mx-maths')) { - return [{ type: 'displayMath', content: el.getAttribute('data-mx-maths') }]; + case "DIV": + if (el.hasAttribute("data-mx-maths")) { + return [ + { type: "displayMath", content: el.getAttribute("data-mx-maths") }, + ]; } return mapChildren(el); - case 'SPAN': - if (el.hasAttribute('data-mx-spoiler')) { - return [{ type: 'spoiler', reason: el.getAttribute('data-mx-spoiler'), content: mapChildren(el) }]; + case "SPAN": + if (el.hasAttribute("data-mx-spoiler")) { + return [ + { + type: "spoiler", + reason: el.getAttribute("data-mx-spoiler"), + content: mapChildren(el), + }, + ]; } - if (el.hasAttribute('data-mx-maths')) { - return [{ type: 'inlineMath', content: el.getAttribute('data-mx-maths') }]; + if (el.hasAttribute("data-mx-maths")) { + return [ + { type: "inlineMath", content: el.getAttribute("data-mx-maths") }, + ]; } return mapChildren(el); default: @@ -456,7 +540,7 @@ function mapElement(el) { function mapNode(n) { switch (n.nodeType) { case Node.TEXT_NODE: - return [{ type: 'text', content: n.textContent }]; + return [{ type: "text", content: n.textContent }]; case Node.ELEMENT_NODE: return mapElement(n); default: @@ -473,7 +557,7 @@ function mapChildren(n) { function render(content, state, plainOut, htmlOut) { let c = content; - if (content.length === 1 && content[0].type === 'paragraph') { + if (content.length === 1 && content[0].type === "paragraph") { c = c[0].content; } @@ -482,7 +566,10 @@ function render(content, state, plainOut, htmlOut) { const htmlStr = htmlOut(c, state); - const plainHtml = htmlStr.replace(/
/g, '\n').replace(/<\/p>

/g, '\n\n').replace(/<\/?p>/g, ''); + const plainHtml = htmlStr + .replace(/
/g, "\n") + .replace(/<\/p>

/g, "\n\n") + .replace(/<\/?p>/g, ""); const onlyPlain = sanitizeText(plainStr) === plainHtml; return { @@ -493,12 +580,12 @@ function render(content, state, plainOut, htmlOut) { } const plainParser = parserFor(plainRules); -const plainPlainOut = outputFor(plainRules, 'plain'); -const plainHtmlOut = outputFor(plainRules, 'html'); +const plainPlainOut = outputFor(plainRules, "plain"); +const plainHtmlOut = outputFor(plainRules, "html"); const mdParser = parserFor(markdownRules); -const mdPlainOut = outputFor(markdownRules, 'plain'); -const mdHtmlOut = outputFor(markdownRules, 'html'); +const mdPlainOut = outputFor(markdownRules, "plain"); +const mdHtmlOut = outputFor(markdownRules, "html"); export function plain(source, state) { return render(plainParser(source, state), state, plainPlainOut, plainHtmlOut); @@ -509,7 +596,7 @@ export function markdown(source, state) { } export function html(source, state) { - const el = document.createElement('template'); + const el = document.createElement("template"); el.innerHTML = source; return render(mapChildren(el.content), state, mdPlainOut, mdHtmlOut); } diff --git a/src/util/matrixUtil.js b/src/util/matrixUtil.js index 54ee31bb..6c5b4c6f 100644 --- a/src/util/matrixUtil.js +++ b/src/util/matrixUtil.js @@ -1,22 +1,24 @@ -import initMatrix from '../client/initMatrix'; +import initMatrix from "../client/initMatrix"; -import HashIC from '../../public/res/ic/outlined/hash.svg'; -import HashGlobeIC from '../../public/res/ic/outlined/hash-globe.svg'; -import HashLockIC from '../../public/res/ic/outlined/hash-lock.svg'; -import SpaceIC from '../../public/res/ic/outlined/space.svg'; -import SpaceGlobeIC from '../../public/res/ic/outlined/space-globe.svg'; -import SpaceLockIC from '../../public/res/ic/outlined/space-lock.svg'; +import HashIC from "../../public/res/ic/outlined/hash.svg"; +import HashGlobeIC from "../../public/res/ic/outlined/hash-globe.svg"; +import HashLockIC from "../../public/res/ic/outlined/hash-lock.svg"; +import SpaceIC from "../../public/res/ic/outlined/space.svg"; +import SpaceGlobeIC from "../../public/res/ic/outlined/space-globe.svg"; +import SpaceLockIC from "../../public/res/ic/outlined/space-lock.svg"; -const WELL_KNOWN_URI = '/.well-known/matrix/client'; +const WELL_KNOWN_URI = "/.well-known/matrix/client"; export async function getBaseUrl(servername) { - let protocol = 'https://'; - if (servername.match(/^https?:\/\//) !== null) protocol = ''; + let protocol = "https://"; + if (servername.match(/^https?:\/\//) !== null) protocol = ""; const serverDiscoveryUrl = `${protocol}${servername}${WELL_KNOWN_URI}`; try { - const result = await (await fetch(serverDiscoveryUrl, { method: 'GET' })).json(); + const result = await ( + await fetch(serverDiscoveryUrl, { method: "GET" }) + ).json(); - const baseUrl = result?.['m.homeserver']?.base_url; + const baseUrl = result?.["m.homeserver"]?.base_url; if (baseUrl === undefined) throw new Error(); return baseUrl; } catch (e) { @@ -29,7 +31,7 @@ export function getUsername(userId) { const user = mx.getUser(userId); if (user === null) return userId; let username = user.displayName; - if (typeof username === 'undefined') { + if (typeof username === "undefined") { username = userId; } return username; @@ -45,29 +47,29 @@ export async function isRoomAliasAvailable(alias) { if (result.room_id) return false; return false; } catch (e) { - if (e.errcode === 'M_NOT_FOUND') return true; + if (e.errcode === "M_NOT_FOUND") return true; return false; } } export function getPowerLabel(powerLevel) { - if (powerLevel > 9000) return 'Goku'; - if (powerLevel > 100) return 'Founder'; - if (powerLevel === 100) return 'Admin'; - if (powerLevel >= 50) return 'Mod'; + if (powerLevel > 9000) return "Goku"; + if (powerLevel > 100) return "Founder"; + if (powerLevel === 100) return "Admin"; + if (powerLevel >= 50) return "Mod"; return null; } export function parseReply(rawBody) { - if (rawBody?.indexOf('>') !== 0) return null; - let body = rawBody.slice(rawBody.indexOf('<') + 1); - const user = body.slice(0, body.indexOf('>')); + if (rawBody?.indexOf(">") !== 0) return null; + let body = rawBody.slice(rawBody.indexOf("<") + 1); + const user = body.slice(0, body.indexOf(">")); - body = body.slice(body.indexOf('>') + 2); - const replyBody = body.slice(0, body.indexOf('\n\n')); - body = body.slice(body.indexOf('\n\n') + 2); + body = body.slice(body.indexOf(">") + 2); + const replyBody = body.slice(0, body.indexOf("\n\n")); + body = body.slice(body.indexOf("\n\n") + 2); - if (user === '') return null; + if (user === "") return null; const isUserId = user.match(/^@.+:.+/); @@ -81,7 +83,7 @@ export function parseReply(rawBody) { export function trimHTMLReply(html) { if (!html) return html; - const suffix = ''; + const suffix = ""; const i = html.indexOf(suffix); if (i < 0) { return html; @@ -104,17 +106,21 @@ export function hasDMWith(userId) { } export function joinRuleToIconSrc(joinRule, isSpace) { - return ({ - restricted: () => (isSpace ? SpaceIC : HashIC), - knock: () => (isSpace ? SpaceLockIC : HashLockIC), - invite: () => (isSpace ? SpaceLockIC : HashLockIC), - public: () => (isSpace ? SpaceGlobeIC : HashGlobeIC), - }[joinRule]?.() || null); + return ( + { + restricted: () => (isSpace ? SpaceIC : HashIC), + knock: () => (isSpace ? SpaceLockIC : HashLockIC), + invite: () => (isSpace ? SpaceLockIC : HashLockIC), + public: () => (isSpace ? SpaceGlobeIC : HashGlobeIC), + }[joinRule]?.() || null + ); } // NOTE: it gives userId with minimum power level 50; function getHighestPowerUserId(room) { - const userIdToPower = room.currentState.getStateEvents('m.room.power_levels', '')?.getContent().users; + const userIdToPower = room.currentState + .getStateEvents("m.room.power_levels", "") + ?.getContent().users; let powerUserId = null; if (!userIdToPower) return powerUserId; @@ -132,7 +138,7 @@ function getHighestPowerUserId(room) { } export function getIdServer(userId) { - const idParts = userId.split(':'); + const idParts = userId.split(":"); return idParts[1]; } @@ -163,7 +169,7 @@ export function genRoomVia(room) { } const serverToPop = getServerToPopulation(room); const sortedServers = Object.keys(serverToPop).sort( - (svrA, svrB) => serverToPop[svrB] - serverToPop[svrA], + (svrA, svrB) => serverToPop[svrB] - serverToPop[svrA] ); const mostPop3 = sortedServers.slice(0, 3); if (via.length === 0) return mostPop3; @@ -178,7 +184,12 @@ export function isCrossVerified(deviceId) { const mx = initMatrix.matrixClient; const crossSignInfo = mx.getStoredCrossSigningForUser(mx.getUserId()); const deviceInfo = mx.getStoredDevice(mx.getUserId(), deviceId); - const deviceTrust = crossSignInfo.checkDeviceTrust(crossSignInfo, deviceInfo, false, true); + const deviceTrust = crossSignInfo.checkDeviceTrust( + crossSignInfo, + deviceInfo, + false, + true + ); return deviceTrust.isCrossSigningVerified(); } catch { // device does not support encryption @@ -188,14 +199,14 @@ export function isCrossVerified(deviceId) { export function hasCrossSigningAccountData() { const mx = initMatrix.matrixClient; - const masterKeyData = mx.getAccountData('m.cross_signing.master'); + const masterKeyData = mx.getAccountData("m.cross_signing.master"); return !!masterKeyData; } export function getDefaultSSKey() { const mx = initMatrix.matrixClient; try { - return mx.getAccountData('m.secret_storage.default_key').getContent().key; + return mx.getAccountData("m.secret_storage.default_key").getContent().key; } catch { return undefined; } @@ -214,10 +225,14 @@ export async function hasDevices(userId) { const mx = initMatrix.matrixClient; try { const usersDeviceMap = await mx.downloadKeys([userId, mx.getUserId()]); - return Object.values(usersDeviceMap) - .every((userDevices) => (Object.keys(userDevices).length > 0)); + return Object.values(usersDeviceMap).every( + (userDevices) => Object.keys(userDevices).length > 0 + ); } catch (e) { - console.error("Error determining if it's possible to encrypt to all users: ", e); + console.error( + "Error determining if it's possible to encrypt to all users: ", + e + ); return false; } } diff --git a/src/util/mimetypes.js b/src/util/mimetypes.js index bf7efbce..2fa7be22 100644 --- a/src/util/mimetypes.js +++ b/src/util/mimetypes.js @@ -1,39 +1,39 @@ // https://github.com/matrix-org/matrix-react-sdk/blob/cd15e08fc285da42134817cce50de8011809cd53/src/utils/blobs.ts export const ALLOWED_BLOB_MIMETYPES = [ - 'image/jpeg', - 'image/gif', - 'image/png', - 'image/apng', - 'image/webp', - 'image/avif', + "image/jpeg", + "image/gif", + "image/png", + "image/apng", + "image/webp", + "image/avif", - 'video/mp4', - 'video/webm', - 'video/ogg', - 'video/quicktime', + "video/mp4", + "video/webm", + "video/ogg", + "video/quicktime", - 'audio/mp4', - 'audio/webm', - 'audio/aac', - 'audio/mpeg', - 'audio/ogg', - 'audio/wave', - 'audio/wav', - 'audio/x-wav', - 'audio/x-pn-wav', - 'audio/flac', - 'audio/x-flac', + "audio/mp4", + "audio/webm", + "audio/aac", + "audio/mpeg", + "audio/ogg", + "audio/wave", + "audio/wav", + "audio/x-wav", + "audio/x-pn-wav", + "audio/flac", + "audio/x-flac", ]; export function getBlobSafeMimeType(mimetype) { - if (typeof mimetype !== 'string') return 'application/octet-stream'; - const [type] = mimetype.split(';'); + if (typeof mimetype !== "string") return "application/octet-stream"; + const [type] = mimetype.split(";"); if (!ALLOWED_BLOB_MIMETYPES.includes(type)) { - return 'application/octet-stream'; + return "application/octet-stream"; } // Required for Chromium browsers - if (type === 'video/quicktime') { - return 'video/mp4'; + if (type === "video/quicktime") { + return "video/mp4"; } return type; } diff --git a/src/util/sanitize.js b/src/util/sanitize.js index 79cc0418..4d9c2fde 100644 --- a/src/util/sanitize.js +++ b/src/util/sanitize.js @@ -1,26 +1,67 @@ -import sanitizeHtml from 'sanitize-html'; +import sanitizeHtml from "sanitize-html"; const MAX_TAG_NESTING = 100; let mx = null; const permittedHtmlTags = [ - 'font', 'del', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', - 'blockquote', 'p', 'a', 'ul', 'ol', 'sup', 'sub', - 'li', 'b', 'i', 'u', 'strong', 'em', 'strike', 'code', - 'hr', 'br', 'div', 'table', 'thead', 'tbody', 'tr', 'th', - 'td', 'caption', 'pre', 'span', 'img', 'details', 'summary', + "font", + "del", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "blockquote", + "p", + "a", + "ul", + "ol", + "sup", + "sub", + "li", + "b", + "i", + "u", + "strong", + "em", + "strike", + "code", + "hr", + "br", + "div", + "table", + "thead", + "tbody", + "tr", + "th", + "td", + "caption", + "pre", + "span", + "img", + "details", + "summary", ]; -const urlSchemes = ['https', 'http', 'ftp', 'mailto', 'magnet']; +const urlSchemes = ["https", "http", "ftp", "mailto", "magnet"]; const permittedTagToAttributes = { - font: ['style', 'data-mx-bg-color', 'data-mx-color', 'color'], - span: ['style', 'data-mx-bg-color', 'data-mx-color', 'data-mx-spoiler', 'data-mx-maths', 'data-mx-pill', 'data-mx-ping'], - div: ['data-mx-maths'], - a: ['name', 'target', 'href', 'rel'], - img: ['width', 'height', 'alt', 'title', 'src', 'data-mx-emoticon'], - ol: ['start'], - code: ['class'], + font: ["style", "data-mx-bg-color", "data-mx-color", "color"], + span: [ + "style", + "data-mx-bg-color", + "data-mx-color", + "data-mx-spoiler", + "data-mx-maths", + "data-mx-pill", + "data-mx-ping", + ], + div: ["data-mx-maths"], + a: ["name", "target", "href", "rel"], + img: ["width", "height", "alt", "title", "src", "data-mx-emoticon"], + ol: ["start"], + code: ["class"], }; function transformFontTag(tagName, attribs) { @@ -28,7 +69,7 @@ function transformFontTag(tagName, attribs) { tagName, attribs: { ...attribs, - style: `background-color: ${attribs['data-mx-bg-color']}; color: ${attribs['data-mx-color']}`, + style: `background-color: ${attribs["data-mx-bg-color"]}; color: ${attribs["data-mx-color"]}`, }, }; } @@ -38,51 +79,57 @@ function transformSpanTag(tagName, attribs) { tagName, attribs: { ...attribs, - style: `background-color: ${attribs['data-mx-bg-color']}; color: ${attribs['data-mx-color']}`, + style: `background-color: ${attribs["data-mx-bg-color"]}; color: ${attribs["data-mx-color"]}`, }, }; } function transformATag(tagName, attribs) { - const userLink = decodeURIComponent(attribs.href).match(/^https?:\/\/matrix.to\/#\/(@.+:.+)/); + const userLink = decodeURIComponent(attribs.href).match( + /^https?:\/\/matrix.to\/#\/(@.+:.+)/ + ); if (userLink !== null) { // convert user link to pill const userId = userLink[1]; const pill = { - tagName: 'span', + tagName: "span", attribs: { - 'data-mx-pill': userId, + "data-mx-pill": userId, }, }; if (userId === mx?.getUserId()) { - pill.attribs['data-mx-ping'] = undefined; + pill.attribs["data-mx-ping"] = undefined; } return pill; } - const rex = /[\u{1f300}-\u{1f5ff}\u{1f900}-\u{1f9ff}\u{1f600}-\u{1f64f}\u{1f680}-\u{1f6ff}\u{2600}-\u{26ff}\u{2700}-\u{27bf}\u{1f1e6}-\u{1f1ff}\u{1f191}-\u{1f251}\u{1f004}\u{1f0cf}\u{1f170}-\u{1f171}\u{1f17e}-\u{1f17f}\u{1f18e}\u{3030}\u{2b50}\u{2b55}\u{2934}-\u{2935}\u{2b05}-\u{2b07}\u{2b1b}-\u{2b1c}\u{3297}\u{3299}\u{303d}\u{00a9}\u{00ae}\u{2122}\u{23f3}\u{24c2}\u{23e9}-\u{23ef}\u{25b6}\u{23f8}-\u{23fa}]/ug; - const newHref = attribs.href.replace(rex, (match) => `[e-${match.codePointAt(0).toString(16)}]`); + const rex = + /[\u{1f300}-\u{1f5ff}\u{1f900}-\u{1f9ff}\u{1f600}-\u{1f64f}\u{1f680}-\u{1f6ff}\u{2600}-\u{26ff}\u{2700}-\u{27bf}\u{1f1e6}-\u{1f1ff}\u{1f191}-\u{1f251}\u{1f004}\u{1f0cf}\u{1f170}-\u{1f171}\u{1f17e}-\u{1f17f}\u{1f18e}\u{3030}\u{2b50}\u{2b55}\u{2934}-\u{2935}\u{2b05}-\u{2b07}\u{2b1b}-\u{2b1c}\u{3297}\u{3299}\u{303d}\u{00a9}\u{00ae}\u{2122}\u{23f3}\u{24c2}\u{23e9}-\u{23ef}\u{25b6}\u{23f8}-\u{23fa}]/gu; + const newHref = attribs.href.replace( + rex, + (match) => `[e-${match.codePointAt(0).toString(16)}]` + ); return { tagName, attribs: { ...attribs, href: newHref, - rel: 'noopener', - target: '_blank', + rel: "noopener", + target: "_blank", }, }; } function transformImgTag(tagName, attribs) { const { src } = attribs; - if (src.startsWith('mxc://') === false) { + if (src.startsWith("mxc://") === false) { return { - tagName: 'a', + tagName: "a", attribs: { href: src, - rel: 'noopener', - target: '_blank', + rel: "noopener", + target: "_blank", }, text: attribs.alt || src, }; @@ -101,20 +148,20 @@ export function sanitizeCustomHtml(matrixClient, body) { return sanitizeHtml(body, { allowedTags: permittedHtmlTags, allowedAttributes: permittedTagToAttributes, - disallowedTagsMode: 'discard', + disallowedTagsMode: "discard", allowedSchemes: urlSchemes, allowedSchemesByTag: { a: urlSchemes, }, - allowedSchemesAppliedToAttributes: ['href'], + allowedSchemesAppliedToAttributes: ["href"], allowProtocolRelative: false, allowedClasses: { - code: ['language-*'], + code: ["language-*"], }, allowedStyles: { - '*': { + "*": { color: [/^#(?:[0-9a-fA-F]{3}){1,2}$/], - 'background-color': [/^#(?:[0-9a-fA-F]{3}){1,2}$/], + "background-color": [/^#(?:[0-9a-fA-F]{3}){1,2}$/], }, }, transformTags: { @@ -123,18 +170,25 @@ export function sanitizeCustomHtml(matrixClient, body) { a: transformATag, img: transformImgTag, }, - nonTextTags: ['style', 'script', 'textarea', 'option', 'noscript', 'mx-reply'], + nonTextTags: [ + "style", + "script", + "textarea", + "option", + "noscript", + "mx-reply", + ], nestingLimit: MAX_TAG_NESTING, }); } export function sanitizeText(body) { const tagsToReplace = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''', + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", }; return body.replace(/[&<>'"]/g, (tag) => tagsToReplace[tag] || tag); } diff --git a/src/util/sort.js b/src/util/sort.js index f9a0b790..832b40ee 100644 --- a/src/util/sort.js +++ b/src/util/sort.js @@ -1,4 +1,4 @@ -import initMatrix from '../client/initMatrix'; +import initMatrix from "../client/initMatrix"; export function roomIdByActivity(id1, id2) { const room1 = initMatrix.matrixClient.getRoom(id1); @@ -13,8 +13,8 @@ export function roomIdByAtoZ(aId, bId) { // remove "#" from the room name // To ignore it in sorting - aName = aName.replace(/#/g, ''); - bName = bName.replace(/#/g, ''); + aName = aName.replace(/#/g, ""); + bName = bName.replace(/#/g, ""); if (aName.toLowerCase() < bName.toLowerCase()) { return -1; diff --git a/src/util/twemojify.jsx b/src/util/twemojify.jsx index abe82a66..b795db60 100644 --- a/src/util/twemojify.jsx +++ b/src/util/twemojify.jsx @@ -1,18 +1,19 @@ /* eslint-disable import/prefer-default-export */ -import React, { lazy, Suspense } from 'react'; +import React, { lazy, Suspense } from "react"; -import linkifyHtml from 'linkify-html'; -import parse from 'html-react-parser'; -import twemoji from 'twemoji'; -import { sanitizeText } from './sanitize'; +import linkifyHtml from "linkify-html"; +import parse from "html-react-parser"; +import twemoji from "twemoji"; +import { sanitizeText } from "./sanitize"; -export const TWEMOJI_BASE_URL = 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/'; +export const TWEMOJI_BASE_URL = + "https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/"; -const Math = lazy(() => import('../app/atoms/math/Math')); +const Math = lazy(() => import("../app/atoms/math/Math")); const mathOptions = { replace: (node) => { - const maths = node.attribs?.['data-mx-maths']; + const maths = node.attribs?.["data-mx-maths"]; if (maths) { return ( {maths}}> @@ -20,7 +21,7 @@ const mathOptions = { content={maths} throwOnError={false} errorColor="var(--tc-danger-normal)" - displayMode={node.name === 'div'} + displayMode={node.name === "div"} /> ); @@ -37,8 +38,14 @@ const mathOptions = { * @param {boolean} [maths=false] - render maths (default: false) * @returns React component */ -export function twemojify(text, opts, linkify = false, sanitize = true, maths = false) { - if (typeof text !== 'string') return text; +export function twemojify( + text, + opts, + linkify = false, + sanitize = true, + maths = false +) { + if (typeof text !== "string") return text; let content = text; const options = opts ?? { base: TWEMOJI_BASE_URL }; if (!options.base) { @@ -52,8 +59,8 @@ export function twemojify(text, opts, linkify = false, sanitize = true, maths = content = twemoji.parse(content, options); if (linkify) { content = linkifyHtml(content, { - target: '_blank', - rel: 'noreferrer noopener', + target: "_blank", + rel: "noreferrer noopener", }); } return parse(content, maths ? mathOptions : null); diff --git a/vite.config.js b/vite.config.js index 979e9aa0..88e851c5 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,68 +1,61 @@ -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; -import { wasm } from '@rollup/plugin-wasm'; -import { viteStaticCopy } from 'vite-plugin-static-copy'; -import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfill'; -import inject from '@rollup/plugin-inject'; -import { svgLoader } from './viteSvgLoader'; +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import { wasm } from "@rollup/plugin-wasm"; +import { viteStaticCopy } from "vite-plugin-static-copy"; +import { NodeGlobalsPolyfillPlugin } from "@esbuild-plugins/node-globals-polyfill"; +import inject from "@rollup/plugin-inject"; +import { svgLoader } from "./viteSvgLoader"; const copyFiles = { targets: [ { - src: 'node_modules/@matrix-org/olm/olm.wasm', - dest: '', + src: "node_modules/@matrix-org/olm/olm.wasm", + dest: "", }, { - src: '_redirects', - dest: '', + src: "_redirects", + dest: "", }, { - src: 'config.json', - dest: '', + src: "config.json", + dest: "", }, { - src: 'public/res/android', - dest: 'public/', - } + src: "public/res/android", + dest: "public/", + }, ], -} +}; export default defineConfig({ - appType: 'spa', + appType: "spa", publicDir: false, base: "", server: { port: 8080, host: true, }, - plugins: [ - viteStaticCopy(copyFiles), - svgLoader(), - wasm(), - react(), - ], + plugins: [viteStaticCopy(copyFiles), svgLoader(), wasm(), react()], optimizeDeps: { esbuildOptions: { - define: { - global: 'globalThis' - }, - plugins: [ - // Enable esbuild polyfill plugins - NodeGlobalsPolyfillPlugin({ - process: false, - buffer: true, - }), - ] - } + define: { + global: "globalThis", + }, + plugins: [ + // Enable esbuild polyfill plugins + NodeGlobalsPolyfillPlugin({ + process: false, + buffer: true, + }), + ], + }, }, build: { - outDir: 'dist', + outDir: "dist", sourcemap: true, copyPublicDir: false, rollupOptions: { - plugins: [ - inject({ Buffer: ['buffer', 'Buffer'] }) - ] - } + plugins: [inject({ Buffer: ["buffer", "Buffer"] })], + }, }, }); diff --git a/viteSvgLoader.ts b/viteSvgLoader.ts index a119e3ed..c81161d1 100644 --- a/viteSvgLoader.ts +++ b/viteSvgLoader.ts @@ -1,13 +1,13 @@ -import svgToMiniDataURI from 'mini-svg-data-uri'; -import type { Plugin } from 'rollup'; -import fs from 'fs'; +import svgToMiniDataURI from "mini-svg-data-uri"; +import type { Plugin } from "rollup"; +import fs from "fs"; // TODO: remove this once https://github.com/vitejs/vite/pull/2909 gets merged export const svgLoader = (): Plugin => ({ - name: 'vite-svg-patch-plugin', + name: "vite-svg-patch-plugin", transform: (code, id) => { - if (id.endsWith('.svg')) { - const extractedSvg = fs.readFileSync(id, 'utf8'); + if (id.endsWith(".svg")) { + const extractedSvg = fs.readFileSync(id, "utf8"); const datauri = svgToMiniDataURI.toSrcset(extractedSvg); return `export default "${datauri}"`; }