From 52e6e8a55801147da0b50d720b956f899d71a51d Mon Sep 17 00:00:00 2001 From: leprechaun Date: Wed, 13 Jul 2022 20:38:07 -0300 Subject: [PATCH 01/64] * made "prettier" --- src/client/base-handlers.ts | 103 +- src/client/index.tsx | 92 +- src/client/window.ts | 20 +- src/common/api/auth-api.ts | 27 +- src/common/api/bridge.ts | 312 +- src/common/api/helper.ts | 8 +- src/common/api/hive-engine.ts | 96 +- src/common/api/hive.ts | 766 ++--- src/common/api/misc.ts | 73 +- src/common/api/operations.ts | 2254 ++++++++------ src/common/api/private-api.ts | 755 +++-- src/common/api/search-api.ts | 156 +- src/common/app.tsx | 318 +- src/common/components/404/index.tsx | 139 +- .../add-image-mobile/index.spec.tsx | 143 +- .../components/add-image-mobile/index.tsx | 268 +- .../components/add-image/index.spec.tsx | 25 +- src/common/components/add-image/index.tsx | 178 +- src/common/components/add-link/index.spec.tsx | 25 +- src/common/components/add-link/index.tsx | 223 +- src/common/components/alink/index.tsx | 21 +- .../components/author-info-card/index.tsx | 113 +- src/common/components/base/index.tsx | 23 +- .../beneficiary-editor/index.spec.tsx | 64 +- .../components/beneficiary-editor/index.tsx | 393 +-- .../components/bookmark-btn/index.spec.tsx | 125 +- src/common/components/bookmark-btn/index.tsx | 269 +- .../components/bookmarks/index.spec.tsx | 256 +- src/common/components/bookmarks/index.tsx | 444 +-- src/common/components/boost/index.spec.tsx | 188 +- src/common/components/boost/index.tsx | 745 +++-- src/common/components/buy-sell-hive/index.tsx | 203 +- .../components/chart-stats/index.spec.tsx | 56 +- src/common/components/chart-stats/index.tsx | 140 +- .../clickaway-listener/index.spec.tsx | 34 +- .../components/clickaway-listener/index.tsx | 24 +- src/common/components/comment/index.spec.tsx | 86 +- src/common/components/comment/index.tsx | 377 +-- .../community-activities/index.spec.tsx | 111 +- .../components/community-activities/index.tsx | 326 +- .../components/community-card/index.spec.tsx | 147 +- .../components/community-card/index.tsx | 665 +++-- .../components/community-cover/index.spec.tsx | 176 +- .../components/community-cover/index.tsx | 417 +-- .../community-list-item/index.spec.tsx | 68 +- .../components/community-list-item/index.tsx | 201 +- .../components/community-menu/index.spec.tsx | 155 +- .../components/community-menu/index.tsx | 209 +- .../community-post-btn/index.spec.tsx | 24 +- .../components/community-post-btn/index.tsx | 42 +- .../index.spec.tsx | 126 +- .../community-rewards-registration/index.tsx | 315 +- .../community-role-edit/index.spec.tsx | 42 +- .../components/community-role-edit/index.tsx | 318 +- .../components/community-roles/index.spec.tsx | 110 +- .../components/community-roles/index.tsx | 248 +- .../community-selector/index.spec.tsx | 150 +- .../components/community-selector/index.tsx | 548 ++-- .../community-settings/index.spec.tsx | 34 +- .../components/community-settings/index.tsx | 594 ++-- .../community-subscribers/index.spec.tsx | 98 +- .../community-subscribers/index.tsx | 345 ++- src/common/components/contributors/index.tsx | 151 +- .../components/cross-post/index.spec.tsx | 99 +- src/common/components/cross-post/index.tsx | 333 ++- src/common/components/curation/index.spec.tsx | 53 +- src/common/components/curation/index.tsx | 313 +- .../delegated-vesting/index.spec.tsx | 119 +- .../components/delegated-vesting/index.tsx | 532 ++-- src/common/components/detect-bottom/index.tsx | 35 +- src/common/components/discussion/index.tsx | 1434 +++++---- .../components/discussion/test1.spec.tsx | 180 +- .../components/discussion/test2.spec.tsx | 115 +- .../download-trigger/index.spec.tsx | 110 +- .../components/download-trigger/index.tsx | 246 +- src/common/components/drafts/index.spec.tsx | 149 +- src/common/components/drafts/index.tsx | 570 ++-- src/common/components/dropdown/index.spec.tsx | 41 +- src/common/components/dropdown/index.tsx | 320 +- .../components/edit-history/index.spec.tsx | 81 +- src/common/components/edit-history/index.tsx | 379 ++- .../components/editor-toolbar/index.tsx | 1012 ++++--- .../components/emoji-picker/index.spec.tsx | 26 +- src/common/components/emoji-picker/index.tsx | 342 ++- .../components/entry-body-extra/index.tsx | 65 +- .../entry-delete-btn/index.spec.tsx | 27 +- .../components/entry-delete-btn/index.tsx | 132 +- .../entry-index-menu-dropdown/index.spec.tsx | 58 +- .../entry-index-menu-dropdown/index.tsx | 62 +- .../entry-index-menu/index.spec.tsx | 208 +- .../components/entry-index-menu/index.tsx | 987 ++++--- .../components/entry-link/index.spec.tsx | 25 +- src/common/components/entry-link/index.tsx | 99 +- .../components/entry-list-item/index.spec.tsx | 292 +- .../components/entry-list-item/index.tsx | 971 +++--- .../entry-list-loading-item/index.tsx | 40 +- src/common/components/entry-list/index.tsx | 417 +-- .../components/entry-menu/index.spec.tsx | 75 +- src/common/components/entry-menu/index.tsx | 989 ++++--- .../components/entry-payout/index.spec.tsx | 207 +- src/common/components/entry-payout/index.tsx | 363 ++- .../entry-reblog-btn/index.spec.tsx | 99 +- .../components/entry-reblog-btn/index.tsx | 306 +- src/common/components/entry-share/index.tsx | 115 +- .../components/entry-tip-btn/index.spec.tsx | 95 +- src/common/components/entry-tip-btn/index.tsx | 241 +- .../components/entry-vote-btn/index.spec.tsx | 193 +- .../components/entry-vote-btn/index.tsx | 391 ++- .../components/entry-votes/index.spec.tsx | 48 +- src/common/components/entry-votes/index.tsx | 554 ++-- .../components/favorite-btn/index.spec.tsx | 100 +- src/common/components/favorite-btn/index.tsx | 256 +- src/common/components/feedback/index.tsx | 185 +- .../components/follow-controls/index.spec.tsx | 163 +- .../components/follow-controls/index.tsx | 404 +-- .../components/formatted-currency/index.tsx | 19 +- .../components/fragments/index.spec.tsx | 173 +- src/common/components/fragments/index.tsx | 838 +++--- src/common/components/friends/index.spec.tsx | 56 +- src/common/components/friends/index.tsx | 559 ++-- src/common/components/full-height/index.ts | 10 +- src/common/components/gallery/index.spec.tsx | 121 +- src/common/components/gallery/index.tsx | 275 +- .../components/hive-barter/index.spec.tsx | 78 +- src/common/components/hive-barter/index.tsx | 109 +- .../image-upload-button/index.spec.tsx | 26 +- .../components/image-upload-button/index.tsx | 143 +- src/common/components/image-upload/index.tsx | 161 +- src/common/components/intro/index.spec.tsx | 33 +- src/common/components/intro/index.tsx | 56 +- .../components/introduction/index.spec.tsx | 54 +- src/common/components/introduction/index.tsx | 141 +- .../components/key-or-hot-dialog/index.tsx | 164 +- .../components/key-or-hot/index.spec.tsx | 31 +- src/common/components/key-or-hot/index.tsx | 250 +- src/common/components/landing-page/index.tsx | 745 +++-- .../components/leaderboard/index.spec.tsx | 45 +- src/common/components/leaderboard/index.tsx | 280 +- .../components/linear-progress/index.tsx | 18 +- .../list-style-toggle/index.spec.tsx | 16 +- .../components/list-style-toggle/index.tsx | 116 +- .../components/login-required/index.tsx | 79 +- src/common/components/login/index.spec.tsx | 133 +- src/common/components/login/index.tsx | 1254 ++++---- .../components/market-chart/index.spec.tsx | 30 +- src/common/components/market-chart/index.tsx | 321 +- src/common/components/market-data/index.tsx | 207 +- src/common/components/market-data/market.tsx | 372 +-- src/common/components/md-handler/index.ts | 128 +- .../components/message-no-data/index.spec.tsx | 28 +- .../components/message-no-data/index.tsx | 77 +- src/common/components/meta/index.tsx | 82 +- src/common/components/modal-confirm/index.tsx | 74 +- src/common/components/mute-btn/index.spec.tsx | 90 +- src/common/components/mute-btn/index.tsx | 359 ++- src/common/components/navbar/index.spec.tsx | 221 +- src/common/components/navbar/index.tsx | 1179 +++++--- src/common/components/navbar/matchMedia.tsx | 18 +- .../components/notification-handler/index.tsx | 297 +- .../components/notifications/index.spec.tsx | 127 +- src/common/components/notifications/index.tsx | 913 +++--- .../components/open-orders/index.spec.tsx | 64 +- src/common/components/open-orders/index.tsx | 85 +- src/common/components/or-divider/index.tsx | 19 +- src/common/components/orders/index.spec.tsx | 42 +- src/common/components/orders/index.tsx | 76 +- .../components/pagination/index.spec.tsx | 26 +- src/common/components/pagination/index.tsx | 139 +- .../components/password-update/index.spec.tsx | 37 +- .../components/password-update/index.tsx | 436 +-- .../components/popover-confirm/index.tsx | 193 +- .../components/popular-users/index.spec.tsx | 38 +- src/common/components/popular-users/index.tsx | 198 +- .../components/post-scheduler/index.spec.tsx | 39 +- .../components/post-scheduler/index.tsx | 220 +- .../components/preferences/index.spec.tsx | 39 +- src/common/components/preferences/index.tsx | 380 ++- .../components/profile-card/index.spec.tsx | 151 +- src/common/components/profile-card/index.tsx | 501 ++-- .../profile-communities/index.spec.tsx | 97 +- .../components/profile-communities/index.tsx | 211 +- .../components/profile-cover/index.spec.tsx | 223 +- src/common/components/profile-cover/index.tsx | 143 +- .../components/profile-edit/index.spec.tsx | 63 +- src/common/components/profile-edit/index.tsx | 421 +-- src/common/components/profile-info/index.tsx | 226 +- .../components/profile-link/index.spec.tsx | 12 +- src/common/components/profile-link/index.tsx | 70 +- .../components/profile-menu/index.spec.tsx | 83 +- src/common/components/profile-menu/index.tsx | 252 +- .../components/profile-popover/index.tsx | 126 +- .../components/profile-preview/index.tsx | 443 ++- .../components/profile-settings/index.tsx | 123 +- src/common/components/promote/index.spec.tsx | 193 +- src/common/components/promote/index.tsx | 715 +++-- .../proposal-list-item/index.spec.tsx | 84 +- .../components/proposal-list-item/index.tsx | 470 +-- .../proposal-vote-btn/index.spec.tsx | 102 +- .../components/proposal-vote-btn/index.tsx | 270 +- .../components/proposal-votes/index.spec.tsx | 98 +- .../components/proposal-votes/index.tsx | 303 +- src/common/components/purchase/index.spec.tsx | 104 +- src/common/components/purchase/index.tsx | 309 +- .../received-vesting/index.spec.tsx | 74 +- .../components/received-vesting/index.tsx | 355 ++- .../resizable-text-area/index.spec.tsx | 38 +- .../components/resizable-text-area/index.tsx | 104 +- .../components/schedules/index.spec.tsx | 194 +- src/common/components/schedules/index.tsx | 634 ++-- src/common/components/scroll-to-top/index.tsx | 101 +- .../components/search-box/index.spec.tsx | 8 +- src/common/components/search-box/index.tsx | 75 +- .../components/search-comment/index.spec.tsx | 175 +- .../components/search-comment/index.tsx | 730 +++-- .../search-communities/index.spec.tsx | 88 +- .../components/search-communities/index.tsx | 227 +- .../components/search-list-item/index.tsx | 332 ++- .../components/search-people/index.spec.tsx | 92 +- src/common/components/search-people/index.tsx | 237 +- .../components/search-suggester/index.tsx | 483 +-- .../components/search-topics/index.spec.tsx | 101 +- src/common/components/search-topics/index.tsx | 178 +- src/common/components/search/index.spec.tsx | 35 +- src/common/components/search/index.tsx | 60 +- .../selected-tags-card/index.spec.tsx | 63 +- .../components/selected-tags-card/index.tsx | 240 +- .../components/selection-popover/index.tsx | 98 +- .../selection-popover/selection-reference.tsx | 38 +- .../components/similar-entries/index.spec.tsx | 147 +- .../components/similar-entries/index.tsx | 349 ++- src/common/components/skeleton/index.tsx | 10 +- src/common/components/ssr-suspense/index.tsx | 6 +- .../subscription-btn/index.spec.tsx | 75 +- .../components/subscription-btn/index.tsx | 181 +- .../components/suggestion-list/index.spec.tsx | 28 +- .../components/suggestion-list/index.tsx | 435 +-- .../components/switch-lang/index.spec.tsx | 36 +- src/common/components/switch-lang/index.tsx | 103 +- .../components/tag-selector/index.spec.tsx | 110 +- src/common/components/tag-selector/index.tsx | 511 ++-- src/common/components/tag/index.spec.tsx | 193 +- src/common/components/tag/index.tsx | 274 +- .../textarea-autocomplete/index.spec.tsx | 33 +- .../textarea-autocomplete/index.tsx | 260 +- src/common/components/theme/index.ts | 17 +- src/common/components/tooltip/index.tsx | 6 +- .../components/transactions/index.spec.tsx | 624 ++-- src/common/components/transactions/index.tsx | 1162 +++++--- src/common/components/transfer/index.spec.tsx | 586 ++-- src/common/components/transfer/index.tsx | 1922 ++++++------ .../trending-tags-card/index.spec.tsx | 28 +- .../components/trending-tags-card/index.tsx | 129 +- .../components/user-avatar/index.spec.tsx | 92 +- src/common/components/user-avatar/index.tsx | 55 +- src/common/components/user-nav/index.spec.tsx | 174 +- src/common/components/user-nav/index.tsx | 506 ++-- .../components/view-keys/index.spec.tsx | 23 +- src/common/components/view-keys/index.tsx | 386 +-- .../components/wallet-ecency/index.spec.tsx | 210 +- src/common/components/wallet-ecency/index.tsx | 1061 ++++--- .../wallet-hive-engine/index.spec.tsx | 111 +- .../components/wallet-hive-engine/index.tsx | 204 +- .../components/wallet-hive/index.spec.tsx | 187 +- src/common/components/wallet-hive/index.tsx | 1331 +++++---- src/common/components/wallet-menu/index.tsx | 88 +- .../components/withdraw-routes/index.spec.tsx | 106 +- .../components/withdraw-routes/index.tsx | 512 ++-- .../components/witness-card/index.spec.tsx | 38 +- src/common/components/witness-card/index.tsx | 68 +- .../witness-vote-btn/index.spec.tsx | 78 +- .../components/witness-vote-btn/index.tsx | 222 +- .../witnesses-active-proxy/index.spec.tsx | 70 +- .../witnesses-active-proxy/index.tsx | 300 +- .../components/witnesses-extra/index.spec.tsx | 63 +- .../components/witnesses-extra/index.tsx | 207 +- .../components/witnesses-proxy/index.spec.tsx | 44 +- .../components/witnesses-proxy/index.tsx | 245 +- .../components/word-counter/index.spec.tsx | 8 +- src/common/components/word-counter/index.tsx | 33 +- src/common/helper/account-reputation.spec.ts | 40 +- src/common/helper/account-reputation.ts | 42 +- src/common/helper/amount-format-check.spec.ts | 24 +- src/common/helper/app-name.spec.ts | 23 +- src/common/helper/app-name.ts | 24 +- src/common/helper/cross-post.spec.ts | 18 +- src/common/helper/cross-post.ts | 29 +- src/common/helper/currency-symbol.spec.ts | 16 +- src/common/helper/currency-symbol.ts | 13 +- src/common/helper/entry-canonical.spec.ts | 35 +- src/common/helper/entry-canonical.ts | 45 +- src/common/helper/filter-tag-extract.test.ts | 51 +- src/common/helper/filter-tag-extract.ts | 122 +- src/common/helper/hive-engine-wallet.spec.ts | 96 +- src/common/helper/hive-engine-wallet.ts | 18 +- src/common/helper/hive-signer.spec.ts | 20 +- src/common/helper/hive-signer.ts | 110 +- src/common/helper/hive-wallet.ts | 174 +- src/common/helper/is-community.spec.ts | 18 +- src/common/helper/is-empty-date.spec.ts | 18 +- src/common/helper/is-empty-date.ts | 10 +- src/common/helper/keychain.ts | 414 ++- src/common/helper/parse-asset.spec.ts | 16 +- src/common/helper/parse-asset.ts | 44 +- src/common/helper/parse-date.spec.ts | 4 +- src/common/helper/posting.spec.ts | 186 +- src/common/helper/posting.ts | 273 +- src/common/helper/search-query.spec.ts | 95 +- src/common/helper/search-query.ts | 100 +- src/common/helper/temp-entry.spec.ts | 39 +- src/common/helper/temp-entry.ts | 108 +- src/common/helper/test-helper.ts | 2315 ++++++++------- src/common/helper/url-share.ts | 45 +- src/common/helper/user-token.ts | 64 +- src/common/helper/vesting.spec.ts | 10 +- src/common/helper/vesting.ts | 12 +- src/common/i18n/helper.spec.tsx | 17 +- src/common/i18n/helper.tsx | 69 +- src/common/i18n/index.ts | 214 +- src/common/img/svg.tsx | 1119 +++---- src/common/pages/auth.tsx | 122 +- src/common/pages/common.ts | 363 +-- src/common/pages/communities.tsx | 1920 ++++++------ src/common/pages/community.tsx | 253 +- src/common/pages/discover.tsx | 104 +- src/common/pages/entry-index.tsx | 399 +-- src/common/pages/entry.tsx | 2200 ++++++++------ src/common/pages/market.tsx | 182 +- src/common/pages/profile.tsx | 1010 ++++--- src/common/pages/proposals.tsx | 808 ++--- src/common/pages/search.tsx | 196 +- src/common/pages/sign-up.tsx | 451 +-- src/common/pages/static.tsx | 2616 +++++++++++------ src/common/pages/submit.tsx | 2088 +++++++------ src/common/pages/witnesses.tsx | 766 ++--- src/common/routes.ts | 68 +- src/common/store/accounts/index.spec.ts | 28 +- src/common/store/accounts/index.ts | 46 +- src/common/store/accounts/types.ts | 114 +- src/common/store/active-user/index.spec.ts | 46 +- src/common/store/active-user/index.ts | 141 +- src/common/store/active-user/types.ts | 28 +- src/common/store/common.ts | 30 +- src/common/store/communities/index.test.ts | 15 +- src/common/store/communities/index.ts | 34 +- src/common/store/communities/types.ts | 69 +- src/common/store/configure.ts | 41 +- src/common/store/diacritics.ts | 6 +- src/common/store/discussion/index.spec.ts | 48 +- src/common/store/discussion/index.ts | 231 +- src/common/store/discussion/sorter.spec.ts | 36 +- src/common/store/discussion/sorter.ts | 133 +- src/common/store/discussion/types.ts | 51 +- src/common/store/dynamic-props/index.ts | 16 +- src/common/store/dynamic-props/types.ts | 4 +- src/common/store/entries/index.test.ts | 180 +- src/common/store/entries/index.ts | 362 +-- src/common/store/entries/types.ts | 163 +- .../store/entry-pin-tracker/index.test.ts | 34 +- src/common/store/entry-pin-tracker/index.ts | 144 +- src/common/store/entry-pin-tracker/types.ts | 31 +- src/common/store/global/index.test.ts | 74 +- src/common/store/global/index.ts | 161 +- src/common/store/global/types.ts | 168 +- src/common/store/helper.ts | 288 +- src/common/store/index.ts | 201 +- src/common/store/initial-state.ts | 73 +- src/common/store/notifications/index.spec.ts | 73 +- src/common/store/notifications/index.ts | 331 ++- src/common/store/notifications/types.ts | 309 +- src/common/store/points/index.spec.tsx | 46 +- src/common/store/points/index.tsx | 141 +- src/common/store/points/types.tsx | 75 +- src/common/store/reblogs/index.spec.ts | 68 +- src/common/store/reblogs/index.ts | 168 +- src/common/store/reblogs/types.ts | 49 +- src/common/store/signing-key/index.spec.ts | 20 +- src/common/store/signing-key/index.tsx | 39 +- src/common/store/signing-key/types.tsx | 8 +- src/common/store/subscriptions/index.spec.ts | 20 +- src/common/store/subscriptions/index.ts | 44 +- src/common/store/subscriptions/types.ts | 8 +- src/common/store/transactions/index.spec.ts | 52 +- src/common/store/transactions/index.ts | 268 +- src/common/store/transactions/types.ts | 359 +-- src/common/store/trending-tags/index.test.ts | 27 +- src/common/store/trending-tags/index.ts | 51 +- src/common/store/trending-tags/types.ts | 6 +- src/common/store/ui/index.spec.ts | 28 +- src/common/store/ui/index.ts | 99 +- src/common/store/ui/types.ts | 26 +- src/common/store/users/index.spec.ts | 12 +- src/common/store/users/index.ts | 62 +- src/common/store/users/types.ts | 20 +- src/common/store/util.ts | 2 +- src/common/tracker.tsx | 56 +- src/common/util/b64.ts | 6 +- src/common/util/capitalize.spec.ts | 4 +- src/common/util/capitalize.ts | 4 +- src/common/util/clean-string.spec.ts | 4 +- src/common/util/clipboard.ts | 27 +- src/common/util/encoder.ts | 6 +- src/common/util/fix-class-names.spec.ts | 6 +- src/common/util/fix-class-names.ts | 7 +- src/common/util/formatted-number.spec.ts | 14 +- src/common/util/formatted-number.ts | 25 +- src/common/util/input-util.ts | 114 +- src/common/util/is-mobile.tsx | 20 +- src/common/util/local-storage.ts | 49 +- src/common/util/misc.spec.ts | 19 +- src/common/util/misc.ts | 16 +- src/common/util/nl2list.spec.ts | 9 +- src/common/util/nl2list.ts | 4 +- src/common/util/platform.spec.ts | 51 +- src/common/util/platform.ts | 49 +- src/common/util/strip-tags.spec.ts | 10 +- src/common/util/strip-tags.ts | 2 +- src/common/util/truncate.spec.ts | 6 +- src/common/util/truncate.ts | 2 +- src/common/util/twitter.ts | 28 +- src/config.ts | 4 +- .../app/components/navbar/index.spec.tsx | 258 +- src/desktop/app/components/navbar/index.tsx | 942 +++--- src/desktop/app/components/updater/index.tsx | 267 +- src/desktop/app/context-menu.ts | 134 +- src/desktop/app/helper/hive-signer.ts | 98 +- src/desktop/app/img/svg.tsx | 1063 ++++--- src/desktop/app/index.tsx | 150 +- src/desktop/app/main.dev.ts | 279 +- src/desktop/app/menu.ts | 424 +-- src/desktop/app/window.ts | 9 +- src/index.ts | 38 +- src/server/handlers/auth-api.ts | 20 +- src/server/handlers/community.tsx | 104 +- src/server/handlers/entry-index.tsx | 144 +- src/server/handlers/entry.tsx | 99 +- src/server/handlers/fallback.tsx | 138 +- src/server/handlers/profile.tsx | 122 +- src/server/handlers/rss.ts | 128 +- src/server/helper.ts | 60 +- src/server/index.ts | 210 +- src/server/state.ts | 83 +- src/server/template.tsx | 56 +- src/server/util.ts | 42 +- 443 files changed, 53479 insertions(+), 43529 deletions(-) diff --git a/src/client/base-handlers.ts b/src/client/base-handlers.ts index f70264b9161..c3dbb120ea7 100644 --- a/src/client/base-handlers.ts +++ b/src/client/base-handlers.ts @@ -1,73 +1,76 @@ // Base event handlers for browser window -import {history} from "../common/store"; +import {history} from '../common/store'; -import routes from "../common/routes"; +import routes from '../common/routes'; -import {pathToRegexp} from "path-to-regexp"; +import {pathToRegexp} from 'path-to-regexp'; // Global drag&drop const handleDragOver = (e: DragEvent) => { - if (!(e.target && e.dataTransfer)) { - return; - } + if (!(e.target && e.dataTransfer)) { + return; + } - e.preventDefault(); - e.dataTransfer.effectAllowed = 'none'; - e.dataTransfer.dropEffect = 'none'; -} + e.preventDefault(); + e.dataTransfer.effectAllowed = 'none'; + e.dataTransfer.dropEffect = 'none'; +}; // Global click handler const handleClick = (e: Event) => { - const el = e.target as HTMLElement; - - // Anchor link handler - if (el.tagName === "A" || (el.parentElement && el.parentElement.tagName === "A")) { + const el = e.target as HTMLElement; - const href = el.getAttribute("href") || (el.parentElement ? el.parentElement.getAttribute("href") : null); + // Anchor link handler + if ( + el.tagName === 'A' || + (el.parentElement && el.parentElement.tagName === 'A') + ) { + const href = + el.getAttribute('href') || + (el.parentElement ? el.parentElement.getAttribute('href') : null); - if (href && href.startsWith("/") && href.indexOf("#") !== -1) { - const [route, anchor] = href.split("#"); + if (href && href.startsWith('/') && href.indexOf('#') !== -1) { + const [route, anchor] = href.split('#'); - // make sure link matches with one of app routes - if (Object.values(routes).find(p => pathToRegexp(p).test(route))) { - e.preventDefault(); + // make sure link matches with one of app routes + if (Object.values(routes).find(p => pathToRegexp(p).test(route))) { + e.preventDefault(); - let delay = 75; + let delay = 75; - if (history!.location.pathname !== route) { - history!.push(href); - } + if (history!.location.pathname !== route) { + history!.push(href); + } - // scroll to anchor element - const el = document.getElementById(anchor); - if (el) { - setTimeout(() => { - el.scrollIntoView(); - }, delay); - } - } + // scroll to anchor element + const el = document.getElementById(anchor); + if (el) { + setTimeout(() => { + el.scrollIntoView(); + }, delay); } + } } - - // Handle links in static pages. (faq etc...) - if (el.tagName === "A") { - if (el.classList.contains("push-link")) { - e.preventDefault(); - const href = el.getAttribute("href"); - if (href && href.startsWith("/")) { - - // make sure link matches with one of app routes - if (Object.values(routes).find(p => pathToRegexp(p).test(href))) { - e.preventDefault(); - history!.push(href); - } - } + } + + // Handle links in static pages. (faq etc...) + if (el.tagName === 'A') { + if (el.classList.contains('push-link')) { + e.preventDefault(); + const href = el.getAttribute('href'); + if (href && href.startsWith('/')) { + // make sure link matches with one of app routes + if (Object.values(routes).find(p => pathToRegexp(p).test(href))) { + e.preventDefault(); + history!.push(href); } + } } -} + } +}; -document.addEventListener("DOMContentLoaded", function () { - document.body.addEventListener('dragover', handleDragOver); - document.body.addEventListener('click', handleClick); +document.addEventListener('DOMContentLoaded', function () { + document.body.addEventListener('dragover', handleDragOver); + document.body.addEventListener('click', handleClick); }); diff --git a/src/client/index.tsx b/src/client/index.tsx index 9ec79ed008f..f002d39d9ef 100644 --- a/src/client/index.tsx +++ b/src/client/index.tsx @@ -1,30 +1,30 @@ -import React from "react"; -import {hydrate} from "react-dom"; -import {Provider} from "react-redux"; -import {ConnectedRouter} from "connected-react-router"; +import React from 'react'; +import {hydrate} from 'react-dom'; +import {Provider} from 'react-redux'; +import {ConnectedRouter} from 'connected-react-router'; -import configureStore from "../common/store/configure"; +import configureStore from '../common/store/configure'; -import {hasKeyChainAct} from "../common/store/global"; -import {clientStoreTasks} from "../common/store/helper"; +import {hasKeyChainAct} from '../common/store/global'; +import {clientStoreTasks} from '../common/store/helper'; -import {history} from "../common/store"; +import {history} from '../common/store'; -import App from "../common/app"; +import App from '../common/app'; -import {AppWindow} from "./window"; +import {AppWindow} from './window'; -import "../style/theme-day.scss"; -import "../style/theme-night.scss"; +import '../style/theme-day.scss'; +import '../style/theme-night.scss'; import './base-handlers'; declare var window: AppWindow; -const store = configureStore(window["__PRELOADED_STATE__"]); +const store = configureStore(window['__PRELOADED_STATE__']); if (process.env.NODE_ENV === 'production') { - console.log(`@@@@@@@(((((@@@@@@@@@@@@@ + console.log(`@@@@@@@(((((@@@@@@@@@@@@@ @@@(((((((((((((@@@@@@@@@ @((((@@@@@@@@@((((@@@@@@@ @(((@@@(((((@@@((((%@@@@@ @@ -33,48 +33,46 @@ if (process.env.NODE_ENV === 'production') { ((((@@@@@@&&&@@@@@@@@@((( ((((@@@@@@@@@@@@@@@@@(((( (((((%@@@@@@@@@%%(((((((@ -@@(((((((((((((((((((@@@@` -); - console.log('%c%s', 'font-size: 16px;', 'We are hiring!'); - console.log( - '%c%s', - 'font-size: 12px;', - 'Are you developer, looking ways to contribute? \nhttps://github.com/ecency/ecency-vision \n\n' - ); +@@(((((((((((((((((((@@@@`); + console.log('%c%s', 'font-size: 16px;', 'We are hiring!'); + console.log( + '%c%s', + 'font-size: 12px;', + 'Are you developer, looking ways to contribute? \nhttps://github.com/ecency/ecency-vision \n\n', + ); } hydrate( - - - - - , - document.getElementById("root") + + + + + , + document.getElementById('root'), ); clientStoreTasks(store); // Check & activate keychain support -window.addEventListener("load", () => { - setTimeout(() => { - if (window.hive_keychain) { - window.hive_keychain.requestHandshake(() => { - store.dispatch(hasKeyChainAct()); - }); - } - }, 50); +window.addEventListener('load', () => { + setTimeout(() => { + if (window.hive_keychain) { + window.hive_keychain.requestHandshake(() => { + store.dispatch(hasKeyChainAct()); + }); + } + }, 50); }); if (module.hot) { - module.hot.accept("../common/app", () => { - hydrate( - - - - - , - document.getElementById("root") - ); - }); + module.hot.accept('../common/app', () => { + hydrate( + + + + + , + document.getElementById('root'), + ); + }); } - diff --git a/src/client/window.ts b/src/client/window.ts index 86e6305162a..d8777b69fad 100644 --- a/src/client/window.ts +++ b/src/client/window.ts @@ -1,13 +1,13 @@ -import {KeyChainImpl} from "../common/helper/keychain"; +import {KeyChainImpl} from '../common/helper/keychain'; export interface AppWindow extends Window { - usePrivate: boolean; - nws?: WebSocket; - comTag?: {}; - hive_keychain?: KeyChainImpl; - twttr?: { - widgets?: { - load: () => void - } - } + usePrivate: boolean; + nws?: WebSocket; + comTag?: {}; + hive_keychain?: KeyChainImpl; + twttr?: { + widgets?: { + load: () => void; + }; + }; } diff --git a/src/common/api/auth-api.ts b/src/common/api/auth-api.ts index 63c0dc2a64c..0cfe39eae6d 100644 --- a/src/common/api/auth-api.ts +++ b/src/common/api/auth-api.ts @@ -1,16 +1,17 @@ -import axios from "axios"; +import axios from 'axios'; -import {apiBase} from "./helper"; +import {apiBase} from './helper'; -export const hsTokenRenew = (code: string): Promise<{ - username: string; - access_token: string; - refresh_token: string; - expires_in: number; +export const hsTokenRenew = ( + code: string, +): Promise<{ + username: string; + access_token: string; + refresh_token: string; + expires_in: number; }> => - axios - .post(apiBase(`/auth-api/hs-token-refresh`), { - code, - }) - .then((resp) => resp.data); - + axios + .post(apiBase(`/auth-api/hs-token-refresh`), { + code, + }) + .then(resp => resp.data); diff --git a/src/common/api/bridge.ts b/src/common/api/bridge.ts index 46ab40fc2d7..eb3ea37b4b9 100644 --- a/src/common/api/bridge.ts +++ b/src/common/api/bridge.ts @@ -1,179 +1,207 @@ -import {Entry} from "../store/entries/types"; -import {Community} from "../store/communities/types"; -import {Subscription} from "../store/subscriptions/types"; +import {Entry} from '../store/entries/types'; +import {Community} from '../store/communities/types'; +import {Subscription} from '../store/subscriptions/types'; -import {client as hiveClient} from "./hive"; +import {client as hiveClient} from './hive'; -export const dataLimit = typeof window !== "undefined" && window.screen.width < 540 ? 5 : 20 || 20 +export const dataLimit = + typeof window !== 'undefined' && window.screen.width < 540 ? 5 : 20 || 20; -const bridgeApiCall = (endpoint: string, params: {}): Promise => hiveClient.call("bridge", endpoint, params); +const bridgeApiCall = (endpoint: string, params: {}): Promise => + hiveClient.call('bridge', endpoint, params); const resolvePost = (post: Entry, observer: string): Promise => { - const {json_metadata: json} = post; - - if (json.original_author && json.original_permlink && json.tags && json.tags[0] === "cross-post") { - return getPost(json.original_author, json.original_permlink, observer).then(resp => { - if (resp) { - return { - ...post, - original_entry: resp - } - } - - return post; - }).catch(() => { - return post; - }) - } + const {json_metadata: json} = post; + + if ( + json.original_author && + json.original_permlink && + json.tags && + json.tags[0] === 'cross-post' + ) { + return getPost(json.original_author, json.original_permlink, observer) + .then(resp => { + if (resp) { + return { + ...post, + original_entry: resp, + }; + } - return new Promise((resolve) => { - resolve(post); - }); -} + return post; + }) + .catch(() => { + return post; + }); + } + + return new Promise(resolve => { + resolve(post); + }); +}; const resolvePosts = (posts: Entry[], observer: string): Promise => { - const promises = posts.map(p => resolvePost(p, observer)); + const promises = posts.map(p => resolvePost(p, observer)); - return Promise.all(promises); -} + return Promise.all(promises); +}; export const getPostsRanked = ( - sort: string, - start_author: string = "", - start_permlink: string = "", - limit: number = dataLimit, - tag: string = "", - observer: string = "" + sort: string, + start_author: string = '', + start_permlink: string = '', + limit: number = dataLimit, + tag: string = '', + observer: string = '', ): Promise => { - return bridgeApiCall("get_ranked_posts", { - sort, - start_author, - start_permlink, - limit, - tag, - observer, - }).then(resp => { - if (resp) { - return resolvePosts(resp, observer); - } + return bridgeApiCall('get_ranked_posts', { + sort, + start_author, + start_permlink, + limit, + tag, + observer, + }).then(resp => { + if (resp) { + return resolvePosts(resp, observer); + } - return resp; - }) -} + return resp; + }); +}; export const getAccountPosts = ( - sort: string, - account: string, - start_author: string = "", - start_permlink: string = "", - limit: number = dataLimit, - observer: string = "" + sort: string, + account: string, + start_author: string = '', + start_permlink: string = '', + limit: number = dataLimit, + observer: string = '', ): Promise => { - - return bridgeApiCall("get_account_posts", { - sort, - account, - start_author, - start_permlink, - limit, - observer, - }).then(resp => { - if (resp) { - return resolvePosts(resp, observer); - } - - return resp; - }) -} - -export const getPost = (author: string = "", permlink: string = "", observer: string = ""): Promise => { - return bridgeApiCall("get_post", { - author, - permlink, - observer, - }).then(resp => { - if (resp) { - return resolvePost(resp, observer); - } - - return resp; - }) -} - -export interface AccountNotification { - date: string; - id: number; - msg: string; - score: number; - type: string; - url: string; -} - -export const getAccountNotifications = (account: string, lastId: number | null = null, limit = 50): Promise => { - const params: { account: string, last_id?: number, limit: number } = { - account, limit + return bridgeApiCall('get_account_posts', { + sort, + account, + start_author, + start_permlink, + limit, + observer, + }).then(resp => { + if (resp) { + return resolvePosts(resp, observer); } - if (lastId) { - params.last_id = lastId; + return resp; + }); +}; + +export const getPost = ( + author: string = '', + permlink: string = '', + observer: string = '', +): Promise => { + return bridgeApiCall('get_post', { + author, + permlink, + observer, + }).then(resp => { + if (resp) { + return resolvePost(resp, observer); } - return bridgeApiCall("account_notifications", params); -} + return resp; + }); +}; -export const getDiscussion = (author: string, permlink: string): Promise | null> => - bridgeApiCall | null>("get_discussion", { - author, - permlink, - }); +export interface AccountNotification { + date: string; + id: number; + msg: string; + score: number; + type: string; + url: string; +} -export const getCommunity = (name: string, observer: string | undefined = ""): Promise => - bridgeApiCall("get_community", {name, observer}); +export const getAccountNotifications = ( + account: string, + lastId: number | null = null, + limit = 50, +): Promise => { + const params: {account: string; last_id?: number; limit: number} = { + account, + limit, + }; + + if (lastId) { + params.last_id = lastId; + } + + return bridgeApiCall( + 'account_notifications', + params, + ); +}; + +export const getDiscussion = ( + author: string, + permlink: string, +): Promise | null> => + bridgeApiCall | null>('get_discussion', { + author, + permlink, + }); + +export const getCommunity = ( + name: string, + observer: string | undefined = '', +): Promise => + bridgeApiCall('get_community', {name, observer}); export const getCommunities = ( - last: string = "", - limit: number = 100, - query?: string | null, - sort: string = "rank", - observer: string = "" + last: string = '', + limit: number = 100, + query?: string | null, + sort: string = 'rank', + observer: string = '', ): Promise => - bridgeApiCall("list_communities", { - last, - limit, - query, - sort, - observer, - }); + bridgeApiCall('list_communities', { + last, + limit, + query, + sort, + observer, + }); export const normalizePost = (post: any): Promise => - bridgeApiCall("normalize_post", { - post, - }); + bridgeApiCall('normalize_post', { + post, + }); export const getSubscriptions = ( - account: string + account: string, ): Promise => - bridgeApiCall("list_all_subscriptions", { - account - }); - + bridgeApiCall('list_all_subscriptions', { + account, + }); export const getSubscribers = ( - community: string + community: string, ): Promise => - bridgeApiCall("list_subscribers", { - community - }); + bridgeApiCall('list_subscribers', { + community, + }); export interface AccountRelationship { - follows: boolean, - ignores: boolean, - is_blacklisted: boolean, - follows_blacklists: boolean + follows: boolean; + ignores: boolean; + is_blacklisted: boolean; + follows_blacklists: boolean; } -export const getRelationshipBetweenAccounts = (follower: string, following: string): Promise => - bridgeApiCall("get_relationship_between_accounts", [follower, following]); - - - +export const getRelationshipBetweenAccounts = ( + follower: string, + following: string, +): Promise => + bridgeApiCall( + 'get_relationship_between_accounts', + [follower, following], + ); diff --git a/src/common/api/helper.ts b/src/common/api/helper.ts index 33de54252bd..3d06dff125b 100644 --- a/src/common/api/helper.ts +++ b/src/common/api/helper.ts @@ -1,5 +1,7 @@ -import defaults from "../constants/defaults.json"; +import defaults from '../constants/defaults.json'; -export const apiBase = (endpoint: string): string => `${defaults.base}${endpoint}`; +export const apiBase = (endpoint: string): string => + `${defaults.base}${endpoint}`; -export const apiBaseImage = (endpoint: string): string => `${defaults.imageServer}${endpoint}`; +export const apiBaseImage = (endpoint: string): string => + `${defaults.imageServer}${endpoint}`; diff --git a/src/common/api/hive-engine.ts b/src/common/api/hive-engine.ts index deb5a901bf4..726b5e13dcb 100644 --- a/src/common/api/hive-engine.ts +++ b/src/common/api/hive-engine.ts @@ -1,7 +1,7 @@ -import axios from "axios"; -import HiveEngineToken from "../helper/hive-engine-wallet"; -import { TransactionConfirmation } from "@hiveio/dhive"; -import { broadcastPostingJSON } from "./operations"; +import axios from 'axios'; +import HiveEngineToken from '../helper/hive-engine-wallet'; +import {TransactionConfirmation} from '@hiveio/dhive'; +import {broadcastPostingJSON} from './operations'; interface TokenBalance { symbol: string; @@ -42,15 +42,15 @@ export interface TokenStatus { precision: number; } -const HIVE_ENGINE_RPC_URL = "https://api.hive-engine.com/rpc/contracts"; +const HIVE_ENGINE_RPC_URL = 'https://api.hive-engine.com/rpc/contracts'; const getTokenBalances = (account: string): Promise => { const data = { - jsonrpc: "2.0", - method: "find", + jsonrpc: '2.0', + method: 'find', params: { - contract: "tokens", - table: "balances", + contract: 'tokens', + table: 'balances', query: { account: account, }, @@ -60,23 +60,23 @@ const getTokenBalances = (account: string): Promise => { return axios .post(HIVE_ENGINE_RPC_URL, data, { - headers: { "Content-type": "application/json" }, + headers: {'Content-type': 'application/json'}, }) - .then((r) => r.data.result) - .catch((e) => { + .then(r => r.data.result) + .catch(e => { return []; }); }; const getTokens = (tokens: string[]): Promise => { const data = { - jsonrpc: "2.0", - method: "find", + jsonrpc: '2.0', + method: 'find', params: { - contract: "tokens", - table: "tokens", + contract: 'tokens', + table: 'tokens', query: { - symbol: { $in: tokens }, + symbol: {$in: tokens}, }, }, id: 2, @@ -84,52 +84,56 @@ const getTokens = (tokens: string[]): Promise => { return axios .post(HIVE_ENGINE_RPC_URL, data, { - headers: { "Content-type": "application/json" }, + headers: {'Content-type': 'application/json'}, }) - .then((r) => r.data.result) - .catch((e) => { + .then(r => r.data.result) + .catch(e => { return []; }); }; export const getHiveEngineTokenBalances = async ( - account: string + account: string, ): Promise => { -// commented just to try removing the non-existing unknowing HiveEngineTokenBalance type -// ): Promise => { + // commented just to try removing the non-existing unknowing HiveEngineTokenBalance type + // ): Promise => { const balances = await getTokenBalances(account); - const tokens = await getTokens(balances.map((t) => t.symbol)); + const tokens = await getTokens(balances.map(t => t.symbol)); - return balances.map((balance) => { - const token = tokens.find((t) => t.symbol == balance.symbol); - const tokenMetadata = token && JSON.parse(token!.metadata) as TokenMetadata; + return balances.map(balance => { + const token = tokens.find(t => t.symbol == balance.symbol); + const tokenMetadata = + token && (JSON.parse(token!.metadata) as TokenMetadata); - return new HiveEngineToken({ ...balance, ...token, ...tokenMetadata } as any); + return new HiveEngineToken({...balance, ...token, ...tokenMetadata} as any); }); }; -export const getUnclaimedRewards = async( - account: string +export const getUnclaimedRewards = async ( + account: string, ): Promise => { - return (axios - .get(`https://scot-api.hive-engine.com/@${account}?hive=1`) - .then((r) => r.data) - .then((r) => Object.values(r)) - .then((r) => r.filter((t) => (t as TokenStatus).pending_token > 0)) as any) - .catch(() => { - return []; - }); + return ( + axios + .get(`https://scot-api.hive-engine.com/@${account}?hive=1`) + .then(r => r.data) + .then(r => Object.values(r)) + .then(r => r.filter(t => (t as TokenStatus).pending_token > 0)) as any + ).catch(() => { + return []; + }); }; export const claimRewards = async ( account: string, - tokens: string[] + tokens: string[], ): Promise => { - const json = JSON.stringify(tokens.map((r) => { - return { symbol: r }; - })); + const json = JSON.stringify( + tokens.map(r => { + return {symbol: r}; + }), + ); - return broadcastPostingJSON(account, "scot_claim_token", json); + return broadcastPostingJSON(account, 'scot_claim_token', json); }; export const stakeTokens = async ( @@ -138,8 +142,8 @@ export const stakeTokens = async ( amount: string, ): Promise => { const json = JSON.stringify({ - contractName: "tokens", - contractAction: "stake", + contractName: 'tokens', + contractAction: 'stake', contractPayload: { symbol: token, to: account, @@ -147,5 +151,5 @@ export const stakeTokens = async ( }, }); - return broadcastPostingJSON(account, "ssc-mainnet-hive", json); + return broadcastPostingJSON(account, 'ssc-mainnet-hive', json); }; diff --git a/src/common/api/hive.ts b/src/common/api/hive.ts index e1fc1a46117..f00c7ab3864 100644 --- a/src/common/api/hive.ts +++ b/src/common/api/hive.ts @@ -1,473 +1,543 @@ -import {Client, RCAPI, utils} from "@hiveio/dhive"; +import {Client, RCAPI, utils} from '@hiveio/dhive'; -import {RCAccount} from "@hiveio/dhive/lib/chain/rc"; +import {RCAccount} from '@hiveio/dhive/lib/chain/rc'; -import {TrendingTag} from "../store/trending-tags/types"; -import {DynamicProps} from "../store/dynamic-props/types"; -import {FullAccount, AccountProfile, AccountFollowStats} from "../store/accounts/types"; +import {TrendingTag} from '../store/trending-tags/types'; +import {DynamicProps} from '../store/dynamic-props/types'; +import { + FullAccount, + AccountProfile, + AccountFollowStats, +} from '../store/accounts/types'; -import parseAsset from "../helper/parse-asset"; -import {vestsToRshares} from "../helper/vesting"; -import isCommunity from "../helper/is-community"; +import parseAsset from '../helper/parse-asset'; +import {vestsToRshares} from '../helper/vesting'; +import isCommunity from '../helper/is-community'; -import SERVERS from "../constants/servers.json"; -import { dataLimit } from './bridge'; -import moment from "moment"; +import SERVERS from '../constants/servers.json'; +import {dataLimit} from './bridge'; +import moment from 'moment'; export const client = new Client(SERVERS, { - timeout: 4000, - failoverThreshold: 10, - consoleOnFailover: true, + timeout: 4000, + failoverThreshold: 10, + consoleOnFailover: true, }); export interface Vote { - percent: number; - reputation: number; - rshares: string; - time: string; - timestamp?: number; - voter: string; - weight: number; - reward?: number; + percent: number; + reputation: number; + rshares: string; + time: string; + timestamp?: number; + voter: string; + weight: number; + reward?: number; } export interface DynamicGlobalProperties { - hbd_print_rate: number; - total_vesting_fund_hive: string; - total_vesting_shares: string; - hbd_interest_rate: number; - head_block_number: number; - vesting_reward_percent: number; - virtual_supply: string; + hbd_print_rate: number; + total_vesting_fund_hive: string; + total_vesting_shares: string; + hbd_interest_rate: number; + head_block_number: number; + vesting_reward_percent: number; + virtual_supply: string; } export interface FeedHistory { - current_median_history: { - base: string; - quote: string; - }; + current_median_history: { + base: string; + quote: string; + }; } export interface RewardFund { - recent_claims: string; - reward_balance: string; + recent_claims: string; + reward_balance: string; } export interface DelegatedVestingShare { - id: number; - delegatee: string; - delegator: string; - min_delegation_time: string; - vesting_shares: string; + id: number; + delegatee: string; + delegator: string; + min_delegation_time: string; + vesting_shares: string; } export interface Follow { - follower: string; - following: string; - what: string[]; + follower: string; + following: string; + what: string[]; } export interface MarketStatistics { - hbd_volume: string; - highest_bid: string; - hive_volume: string; - latest: string; - lowest_ask: string; - percent_change: string; + hbd_volume: string; + highest_bid: string; + hive_volume: string; + latest: string; + lowest_ask: string; + percent_change: string; } export interface OpenOrdersData { - id: number, - created: string, - expiration: string, - seller: string, - orderid: number, - for_sale: number, - sell_price: { - base: string, - quote: string - }, - real_price: string, - rewarded: boolean + id: number; + created: string; + expiration: string; + seller: string; + orderid: number; + for_sale: number; + sell_price: { + base: string; + quote: string; + }; + real_price: string; + rewarded: boolean; } export interface OrdersDataItem { - created: string; - hbd: number; - hive: number; - order_price: { - base: string; - quote: string; - } - real_price: string; + created: string; + hbd: number; + hive: number; + order_price: { + base: string; + quote: string; + }; + real_price: string; } export interface TradeDataItem { - current_pays: string; - date: number; - open_pays: string; + current_pays: string; + date: number; + open_pays: string; } export interface OrdersData { - bids: OrdersDataItem[]; - asks: OrdersDataItem[]; - trading: OrdersDataItem[]; + bids: OrdersDataItem[]; + asks: OrdersDataItem[]; + trading: OrdersDataItem[]; } export const getPost = (username: string, permlink: string): Promise => - client.call("condenser_api", "get_content", [username, permlink]); + client.call('condenser_api', 'get_content', [username, permlink]); export const getMarketStatistics = (): Promise => - client.call("condenser_api", "get_ticker", []); + client.call('condenser_api', 'get_ticker', []); export const getOrderBook = (limit: number = 500): Promise => - client.call("condenser_api", "get_order_book", [limit]); + client.call('condenser_api', 'get_order_book', [limit]); export const getOpenOrder = (user: string): Promise => - client.call("condenser_api", "get_open_orders", [user]); - -export const getTradeHistory = (limit: number = 1000): Promise => { - let today = moment(Date.now()).subtract(10, 'h').format().split('+')[0]; - return client.call("condenser_api", "get_trade_history", [today, "1969-12-31T23:59:59",limit]);} - -export const getActiveVotes = (author: string, permlink: string): Promise => - client.database.call("get_active_votes", [author, permlink]); - -export const getTrendingTags = (afterTag: string = "", limit: number = 250): Promise => - client.database - .call("get_trending_tags", [afterTag, limit]) - .then((tags: TrendingTag[]) => { - return tags - .filter((x) => x.name !== "") - .filter((x) => !isCommunity(x.name)) - .map((x) => x.name) - } - ); + client.call('condenser_api', 'get_open_orders', [user]); + +export const getTradeHistory = ( + limit: number = 1000, +): Promise => { + let today = moment(Date.now()).subtract(10, 'h').format().split('+')[0]; + return client.call('condenser_api', 'get_trade_history', [ + today, + '1969-12-31T23:59:59', + limit, + ]); +}; + +export const getActiveVotes = ( + author: string, + permlink: string, +): Promise => + client.database.call('get_active_votes', [author, permlink]); + +export const getTrendingTags = ( + afterTag: string = '', + limit: number = 250, +): Promise => + client.database + .call('get_trending_tags', [afterTag, limit]) + .then((tags: TrendingTag[]) => { + return tags + .filter(x => x.name !== '') + .filter(x => !isCommunity(x.name)) + .map(x => x.name); + }); export const lookupAccounts = (q: string, limit = 50): Promise => - client.database.call("lookup_accounts", [q, limit]); + client.database.call('lookup_accounts', [q, limit]); export const getAccounts = (usernames: string[]): Promise => { - return client.database.getAccounts(usernames).then((resp: any[]): FullAccount[] => - resp.map((x) => { - const account: FullAccount = { - name: x.name, - owner: x.owner, - active: x.active, - posting: x.posting, - memo_key: x.memo_key, - post_count: x.post_count, - created: x.created, - reputation: x.reputation, - posting_json_metadata: x.posting_json_metadata, - last_vote_time: x.last_vote_time, - last_post: x.last_post, - json_metadata: x.json_metadata, - reward_hive_balance: x.reward_hive_balance, - reward_hbd_balance: x.reward_hbd_balance, - reward_vesting_hive: x.reward_vesting_hive, - reward_vesting_balance: x.reward_vesting_balance, - balance: x.balance, - hbd_balance: x.hbd_balance, - savings_balance: x.savings_balance, - savings_hbd_balance: x.savings_hbd_balance, - next_vesting_withdrawal: x.next_vesting_withdrawal, - vesting_shares: x.vesting_shares, - delegated_vesting_shares: x.delegated_vesting_shares, - received_vesting_shares: x.received_vesting_shares, - vesting_withdraw_rate: x.vesting_withdraw_rate, - to_withdraw: x.to_withdraw, - withdrawn: x.withdrawn, - witness_votes: x.witness_votes, - proxy: x.proxy, - proxied_vsf_votes: x.proxied_vsf_votes, - voting_manabar: x.voting_manabar, - voting_power: x.voting_power, - downvote_manabar: x.downvote_manabar, - __loaded: true, - }; - - let profile: AccountProfile | undefined; - - try { - profile = JSON.parse(x.posting_json_metadata!).profile; - } catch (e) { - } - - if (!profile) { - try { - profile = JSON.parse(x.json_metadata!).profile; - } catch (e) { - } - } - - if (!profile) { - profile = { - about: '', - cover_image: '', - location: '', - name: '', - profile_image: '', - website: '', - } - } - - return {...account, profile}; - }) + return client.database + .getAccounts(usernames) + .then((resp: any[]): FullAccount[] => + resp.map(x => { + const account: FullAccount = { + name: x.name, + owner: x.owner, + active: x.active, + posting: x.posting, + memo_key: x.memo_key, + post_count: x.post_count, + created: x.created, + reputation: x.reputation, + posting_json_metadata: x.posting_json_metadata, + last_vote_time: x.last_vote_time, + last_post: x.last_post, + json_metadata: x.json_metadata, + reward_hive_balance: x.reward_hive_balance, + reward_hbd_balance: x.reward_hbd_balance, + reward_vesting_hive: x.reward_vesting_hive, + reward_vesting_balance: x.reward_vesting_balance, + balance: x.balance, + hbd_balance: x.hbd_balance, + savings_balance: x.savings_balance, + savings_hbd_balance: x.savings_hbd_balance, + next_vesting_withdrawal: x.next_vesting_withdrawal, + vesting_shares: x.vesting_shares, + delegated_vesting_shares: x.delegated_vesting_shares, + received_vesting_shares: x.received_vesting_shares, + vesting_withdraw_rate: x.vesting_withdraw_rate, + to_withdraw: x.to_withdraw, + withdrawn: x.withdrawn, + witness_votes: x.witness_votes, + proxy: x.proxy, + proxied_vsf_votes: x.proxied_vsf_votes, + voting_manabar: x.voting_manabar, + voting_power: x.voting_power, + downvote_manabar: x.downvote_manabar, + __loaded: true, + }; + + let profile: AccountProfile | undefined; + + try { + profile = JSON.parse(x.posting_json_metadata!).profile; + } catch (e) {} + + if (!profile) { + try { + profile = JSON.parse(x.json_metadata!).profile; + } catch (e) {} + } + + if (!profile) { + profile = { + about: '', + cover_image: '', + location: '', + name: '', + profile_image: '', + website: '', + }; + } + + return {...account, profile}; + }), ); }; -export const getAccount = (username: string): Promise => getAccounts([username]).then((resp) => resp[0]); +export const getAccount = (username: string): Promise => + getAccounts([username]).then(resp => resp[0]); export const getAccountFull = (username: string): Promise => - getAccount(username).then(async (account) => { - let follow_stats: AccountFollowStats | undefined; - try { - follow_stats = await getFollowCount(username); - } catch (e) { - } + getAccount(username).then(async account => { + let follow_stats: AccountFollowStats | undefined; + try { + follow_stats = await getFollowCount(username); + } catch (e) {} - return {...account, follow_stats}; - }); + return {...account, follow_stats}; + }); export const getFollowCount = (username: string): Promise => - client.database.call("get_follow_count", [username]); + client.database.call('get_follow_count', [username]); export const getFollowing = ( - follower: string, - startFollowing: string, - followType = "blog", - limit = 100 -): Promise => client.database.call("get_following", [follower, startFollowing, followType, limit]); + follower: string, + startFollowing: string, + followType = 'blog', + limit = 100, +): Promise => + client.database.call('get_following', [ + follower, + startFollowing, + followType, + limit, + ]); export const getFollowers = ( - following: string, - startFollowing: string, - followType = "blog", - limit = 100 -): Promise => client.database.call("get_followers", [following, startFollowing === "" ? null : startFollowing, followType, limit]); + following: string, + startFollowing: string, + followType = 'blog', + limit = 100, +): Promise => + client.database.call('get_followers', [ + following, + startFollowing === '' ? null : startFollowing, + followType, + limit, + ]); export const findRcAccounts = (username: string): Promise => - new RCAPI(client).findRCAccounts([username]) + new RCAPI(client).findRCAccounts([username]); -export const getDynamicGlobalProperties = (): Promise => +export const getDynamicGlobalProperties = + (): Promise => client.database.getDynamicGlobalProperties().then((r: any) => { - return({ - total_vesting_fund_hive: r.total_vesting_fund_hive || r.total_vesting_fund_steem, + return { + total_vesting_fund_hive: + r.total_vesting_fund_hive || r.total_vesting_fund_steem, total_vesting_shares: r.total_vesting_shares, hbd_print_rate: r.hbd_print_rate || r.sbd_print_rate, hbd_interest_rate: r.hbd_interest_rate, head_block_number: r.head_block_number, vesting_reward_percent: r.vesting_reward_percent, - virtual_supply: r.virtual_supply - })}); - -export const getAccountHistory = (username: string, filters: any[], start: number = -1, limit: number = 20): Promise => { + virtual_supply: r.virtual_supply, + }; + }); - return client.call("condenser_api", "get_account_history", [username, start, limit, ...filters]); -} +export const getAccountHistory = ( + username: string, + filters: any[], + start: number = -1, + limit: number = 20, +): Promise => { + return client.call('condenser_api', 'get_account_history', [ + username, + start, + limit, + ...filters, + ]); +}; -export const getFeedHistory = (): Promise => client.database.call("get_feed_history"); +export const getFeedHistory = (): Promise => + client.database.call('get_feed_history'); -export const getRewardFund = (): Promise => client.database.call("get_reward_fund", ["post"]); +export const getRewardFund = (): Promise => + client.database.call('get_reward_fund', ['post']); export const getDynamicProps = async (): Promise => { - const globalDynamic = await getDynamicGlobalProperties(); - const feedHistory = await getFeedHistory(); - const rewardFund = await getRewardFund(); - - const hivePerMVests = - (parseAsset(globalDynamic.total_vesting_fund_hive).amount / parseAsset(globalDynamic.total_vesting_shares).amount) * - 1e6; - const base = parseAsset(feedHistory.current_median_history.base).amount; - const quote = parseAsset(feedHistory.current_median_history.quote).amount; - const fundRecentClaims = parseFloat(rewardFund.recent_claims); - const fundRewardBalance = parseAsset(rewardFund.reward_balance).amount; - const hbdPrintRate = globalDynamic.hbd_print_rate; - const hbdInterestRate = globalDynamic.hbd_interest_rate; - const headBlock = globalDynamic.head_block_number; - const totalVestingFund = parseAsset(globalDynamic.total_vesting_fund_hive).amount; - const totalVestingShares = parseAsset(globalDynamic.total_vesting_shares).amount; - const virtualSupply = parseAsset(globalDynamic.virtual_supply).amount; - const vestingRewardPercent = globalDynamic.vesting_reward_percent; - - return { - hivePerMVests, - base, - quote, - fundRecentClaims, - fundRewardBalance, - hbdPrintRate, - hbdInterestRate, - headBlock, - totalVestingFund, - totalVestingShares, - virtualSupply, - vestingRewardPercent - }; + const globalDynamic = await getDynamicGlobalProperties(); + const feedHistory = await getFeedHistory(); + const rewardFund = await getRewardFund(); + + const hivePerMVests = + (parseAsset(globalDynamic.total_vesting_fund_hive).amount / + parseAsset(globalDynamic.total_vesting_shares).amount) * + 1e6; + const base = parseAsset(feedHistory.current_median_history.base).amount; + const quote = parseAsset(feedHistory.current_median_history.quote).amount; + const fundRecentClaims = parseFloat(rewardFund.recent_claims); + const fundRewardBalance = parseAsset(rewardFund.reward_balance).amount; + const hbdPrintRate = globalDynamic.hbd_print_rate; + const hbdInterestRate = globalDynamic.hbd_interest_rate; + const headBlock = globalDynamic.head_block_number; + const totalVestingFund = parseAsset( + globalDynamic.total_vesting_fund_hive, + ).amount; + const totalVestingShares = parseAsset( + globalDynamic.total_vesting_shares, + ).amount; + const virtualSupply = parseAsset(globalDynamic.virtual_supply).amount; + const vestingRewardPercent = globalDynamic.vesting_reward_percent; + + return { + hivePerMVests, + base, + quote, + fundRecentClaims, + fundRewardBalance, + hbdPrintRate, + hbdInterestRate, + headBlock, + totalVestingFund, + totalVestingShares, + virtualSupply, + vestingRewardPercent, + }; }; export const getVestingDelegations = ( - username: string, - from: string = "", - limit: number = 50 -): Promise => client.database.call("get_vesting_delegations", [username, from, limit]); + username: string, + from: string = '', + limit: number = 50, +): Promise => + client.database.call('get_vesting_delegations', [username, from, limit]); export interface Witness { - total_missed: number; - url: string; - props: { - account_creation_fee: string; - account_subsidy_budget: number; - maximum_block_size: number; - }, - hbd_exchange_rate: { - base: string; - }, - available_witness_account_subsidies: number; - running_version: string; - owner: string; - signing_key:string, - last_hbd_exchange_update:string + total_missed: number; + url: string; + props: { + account_creation_fee: string; + account_subsidy_budget: number; + maximum_block_size: number; + }; + hbd_exchange_rate: { + base: string; + }; + available_witness_account_subsidies: number; + running_version: string; + owner: string; + signing_key: string; + last_hbd_exchange_update: string; } export const getWitnessesByVote = ( - from: string = "", - limit: number = 50 -): Promise => client.call("condenser_api", "get_witnesses_by_vote", [from, limit]); - + from: string = '', + limit: number = 50, +): Promise => + client.call('condenser_api', 'get_witnesses_by_vote', [from, limit]); export interface Proposal { - creator: string; - daily_pay: { - amount: string - nai: string - precision: number - }; - end_date: string; - id: number; - permlink: string; - proposal_id: number; - receiver: string; - start_date: string; - status: string; - subject: string; - total_votes: string; + creator: string; + daily_pay: { + amount: string; + nai: string; + precision: number; + }; + end_date: string; + id: number; + permlink: string; + proposal_id: number; + receiver: string; + start_date: string; + status: string; + subject: string; + total_votes: string; } -export const getProposals = (): Promise => client.call("database_api", "list_proposals", { - start: [-1], - limit: 200, - order: 'by_total_votes', - order_direction: 'descending', - status: 'all' -}).then(r => r.proposals); +export const getProposals = (): Promise => + client + .call('database_api', 'list_proposals', { + start: [-1], + limit: 200, + order: 'by_total_votes', + order_direction: 'descending', + status: 'all', + }) + .then(r => r.proposals); export interface ProposalVote { - id: number; - proposal: Proposal; - voter: string; + id: number; + proposal: Proposal; + voter: string; } -export const getProposalVotes = (proposalId: number, voter: string = "", limit: number = 300): Promise => - client.call('condenser_api', 'list_proposal_votes', [ - [proposalId, voter], - limit, - 'by_proposal_voter' +export const getProposalVotes = ( + proposalId: number, + voter: string = '', + limit: number = 300, +): Promise => + client + .call('condenser_api', 'list_proposal_votes', [ + [proposalId, voter], + limit, + 'by_proposal_voter', ]) - .then(r => r.filter((x: ProposalVote) => x.proposal.proposal_id === proposalId)) - .then(r => r.map((x: ProposalVote) => ({id: x.id, voter: x.voter}))) - + .then(r => + r.filter((x: ProposalVote) => x.proposal.proposal_id === proposalId), + ) + .then(r => r.map((x: ProposalVote) => ({id: x.id, voter: x.voter}))); export interface WithdrawRoute { - auto_vest: boolean; - from_account: string; - id: number; - percent: number; - to_account: string; + auto_vest: boolean; + from_account: string; + id: number; + percent: number; + to_account: string; } export const getWithdrawRoutes = (account: string): Promise => - client.database.call("get_withdraw_routes", [account, "outgoing"]); + client.database.call('get_withdraw_routes', [account, 'outgoing']); export const votingPower = (account: FullAccount): number => { - // @ts-ignore "Account" is compatible with dhive's "ExtendedAccount" - const calc = account && client.rc.calculateVPMana(account); - const {percentage} = calc; + // @ts-ignore "Account" is compatible with dhive's "ExtendedAccount" + const calc = account && client.rc.calculateVPMana(account); + const {percentage} = calc; - return percentage / 100; + return percentage / 100; }; export const powerRechargeTime = (power: number) => { - const missingPower = 100 - power - return missingPower * 100 * 432000 / 10000; -} + const missingPower = 100 - power; + return (missingPower * 100 * 432000) / 10000; +}; -export const votingValue = (account: FullAccount, dynamicProps: DynamicProps, votingPower: number, weight: number = 10000): number => { - const {fundRecentClaims, fundRewardBalance, base, quote} = dynamicProps; +export const votingValue = ( + account: FullAccount, + dynamicProps: DynamicProps, + votingPower: number, + weight: number = 10000, +): number => { + const {fundRecentClaims, fundRewardBalance, base, quote} = dynamicProps; - const total_vests = - parseAsset(account.vesting_shares).amount + - parseAsset(account.received_vesting_shares).amount - - parseAsset(account.delegated_vesting_shares).amount; + const total_vests = + parseAsset(account.vesting_shares).amount + + parseAsset(account.received_vesting_shares).amount - + parseAsset(account.delegated_vesting_shares).amount; - const rShares = vestsToRshares(total_vests, votingPower, weight); + const rShares = vestsToRshares(total_vests, votingPower, weight); - return rShares / fundRecentClaims * fundRewardBalance * (base / quote); -} + return (rShares / fundRecentClaims) * fundRewardBalance * (base / quote); +}; -const HIVE_VOTING_MANA_REGENERATION_SECONDS = 5*60*60*24; //5 days +const HIVE_VOTING_MANA_REGENERATION_SECONDS = 5 * 60 * 60 * 24; //5 days export const downVotingPower = (account: FullAccount): number => { - const totalShares = parseFloat(account.vesting_shares) + parseFloat(account.received_vesting_shares) - parseFloat(account.delegated_vesting_shares) - parseFloat(account.vesting_withdraw_rate); - const elapsed = Math.floor(Date.now() / 1000) - account.downvote_manabar.last_update_time; - const maxMana = totalShares * 1000000 / 4; - - let currentMana = parseFloat(account.downvote_manabar.current_mana.toString()) + elapsed * maxMana / HIVE_VOTING_MANA_REGENERATION_SECONDS; - - if (currentMana > maxMana) { - currentMana = maxMana; - } - const currentManaPerc = currentMana * 100 / maxMana; - - if (isNaN(currentManaPerc)) { - return 0; - } - - if (currentManaPerc > 100) { - return 100; - } - return currentManaPerc; + const totalShares = + parseFloat(account.vesting_shares) + + parseFloat(account.received_vesting_shares) - + parseFloat(account.delegated_vesting_shares) - + parseFloat(account.vesting_withdraw_rate); + const elapsed = + Math.floor(Date.now() / 1000) - account.downvote_manabar.last_update_time; + const maxMana = (totalShares * 1000000) / 4; + + let currentMana = + parseFloat(account.downvote_manabar.current_mana.toString()) + + (elapsed * maxMana) / HIVE_VOTING_MANA_REGENERATION_SECONDS; + + if (currentMana > maxMana) { + currentMana = maxMana; + } + const currentManaPerc = (currentMana * 100) / maxMana; + + if (isNaN(currentManaPerc)) { + return 0; + } + + if (currentManaPerc > 100) { + return 100; + } + return currentManaPerc; }; export const rcPower = (account: RCAccount): number => { - const calc = client.rc.calculateRCMana(account); - const {percentage} = calc; - return percentage / 100; + const calc = client.rc.calculateRCMana(account); + const {percentage} = calc; + return percentage / 100; }; export interface ConversionRequest { - amount: string; - conversion_date: string; - id: number; - owner: string; - requestid: number; + amount: string; + conversion_date: string; + id: number; + owner: string; + requestid: number; } -export const getConversionRequests = (account: string): Promise => - client.database.call("get_conversion_requests", [account]); +export const getConversionRequests = ( + account: string, +): Promise => + client.database.call('get_conversion_requests', [account]); export interface BlogEntry { - blog: string, - entry_id: number, - author: string, - permlink: string, - reblogged_on: string + blog: string; + entry_id: number; + author: string; + permlink: string; + reblogged_on: string; } -export const getBlogEntries = (username: string, limit: number = dataLimit): Promise => - client.call('condenser_api', 'get_blog_entries', [ - username, - 0, - limit - ]); +export const getBlogEntries = ( + username: string, + limit: number = dataLimit, +): Promise => + client.call('condenser_api', 'get_blog_entries', [username, 0, limit]); diff --git a/src/common/api/misc.ts b/src/common/api/misc.ts index 7abb7b869ad..cceb9f133aa 100644 --- a/src/common/api/misc.ts +++ b/src/common/api/misc.ts @@ -1,41 +1,56 @@ import axios from 'axios'; -import defaults from "../constants/defaults.json"; +import defaults from '../constants/defaults.json'; -import {apiBase} from "./helper"; +import {apiBase} from './helper'; -export const getEmojiData = () => fetch(apiBase("/emoji.json")).then((response) => response.json()); +export const getEmojiData = () => + fetch(apiBase('/emoji.json')).then(response => response.json()); -export const uploadImage = async (file: File, token: string): Promise<{ - url: string +export const uploadImage = async ( + file: File, + token: string, +): Promise<{ + url: string; }> => { - const fData = new FormData(); - fData.append('file', file); - - const postUrl = `${defaults.imageServer}/hs/${token}`; - - return axios.post(postUrl, fData, { - headers: { - 'Content-Type': 'multipart/form-data' - } - }).then(r => r.data); + const fData = new FormData(); + fData.append('file', file); + + const postUrl = `${defaults.imageServer}/hs/${token}`; + + return axios + .post(postUrl, fData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }) + .then(r => r.data); }; -export const getMarketData = (coin: string, vsCurrency: string, fromTs: string, toTs: string): Promise<{ prices?: [number, number] }> => { - const u = `https://api.coingecko.com/api/v3/coins/${coin}/market_chart/range?vs_currency=${vsCurrency}&from=${fromTs}&to=${toTs}` - return axios.get(u).then(r => r.data); -} +export const getMarketData = ( + coin: string, + vsCurrency: string, + fromTs: string, + toTs: string, +): Promise<{prices?: [number, number]}> => { + const u = `https://api.coingecko.com/api/v3/coins/${coin}/market_chart/range?vs_currency=${vsCurrency}&from=${fromTs}&to=${toTs}`; + return axios.get(u).then(r => r.data); +}; export const getCurrencyRate = (cur: string): Promise => { - if (cur === "hbd") { - return new Promise((resolve) => resolve(1)); - } - - const u = `https://api.coingecko.com/api/v3/simple/price?ids=hive_dollar&vs_currencies=${cur}`; - return axios.get(u).then(r => r.data).then(r => r.hive_dollar[cur]); -} + if (cur === 'hbd') { + return new Promise(resolve => resolve(1)); + } + + const u = `https://api.coingecko.com/api/v3/simple/price?ids=hive_dollar&vs_currencies=${cur}`; + return axios + .get(u) + .then(r => r.data) + .then(r => r.hive_dollar[cur]); +}; export const geLatestDesktopTag = (): Promise => - axios.get("https://api.github.com/repos/ecency/ecency-vision/releases/latest") - .then(r => r.data) - .then(r => r.tag_name); + axios + .get('https://api.github.com/repos/ecency/ecency-vision/releases/latest') + .then(r => r.data) + .then(r => r.tag_name); diff --git a/src/common/api/operations.ts b/src/common/api/operations.ts index 90dbc4b4872..24b72e2311e 100644 --- a/src/common/api/operations.ts +++ b/src/common/api/operations.ts @@ -1,1091 +1,1439 @@ -import hs from "hivesigner"; +import hs from 'hivesigner'; -import {PrivateKey, Operation, TransactionConfirmation, AccountUpdateOperation, CustomJsonOperation} from '@hiveio/dhive'; +import { + PrivateKey, + Operation, + TransactionConfirmation, + AccountUpdateOperation, + CustomJsonOperation, +} from '@hiveio/dhive'; import {Parameters} from 'hive-uri'; -import {client as hiveClient} from "./hive"; +import {client as hiveClient} from './hive'; -import {Account} from "../store/accounts/types"; +import {Account} from '../store/accounts/types'; -import {usrActivity} from "./private-api"; +import {usrActivity} from './private-api'; -import {getAccessToken, getPostingKey} from "../helper/user-token"; +import {getAccessToken, getPostingKey} from '../helper/user-token'; -import * as keychain from "../helper/keychain"; +import * as keychain from '../helper/keychain'; -import parseAsset from "../helper/parse-asset"; +import parseAsset from '../helper/parse-asset'; -import {hotSign} from "../helper/hive-signer"; +import {hotSign} from '../helper/hive-signer'; -import {_t} from "../i18n"; -import { TransactionType } from "../components/buy-sell-hive"; +import {_t} from '../i18n'; +import {TransactionType} from '../components/buy-sell-hive'; export interface MetaData { - links?: string[]; - image?: string[]; - thumbnails?: string[]; - users?: string[]; - tags?: string[]; - app?: string; - format?: string; - community?: string; + links?: string[]; + image?: string[]; + thumbnails?: string[]; + users?: string[]; + tags?: string[]; + app?: string; + format?: string; + community?: string; } export interface BeneficiaryRoute { - account: string; - weight: number; + account: string; + weight: number; } export interface CommentOptions { - allow_curation_rewards: boolean; - allow_votes: boolean; - author: string; - permlink: string; - max_accepted_payout: string; - percent_hbd: number; - extensions: Array<[0, { beneficiaries: BeneficiaryRoute[] }]>; + allow_curation_rewards: boolean; + allow_votes: boolean; + author: string; + permlink: string; + max_accepted_payout: string; + percent_hbd: number; + extensions: Array<[0, {beneficiaries: BeneficiaryRoute[]}]>; } -export type RewardType = "default" | "sp" | "dp"; +export type RewardType = 'default' | 'sp' | 'dp'; const handleChainError = (strErr: string) => { - if (/You may only post once every/.test(strErr)) { - return _t("chain-error.min-root-comment"); - } else if (/Your current vote on this comment is identical/.test(strErr)) { - return _t("chain-error.identical-vote"); - } else if (/Please wait to transact, or power up/.test(strErr)) { - return _t("chain-error.insufficient-resource"); - } else if (/Cannot delete a comment with net positive/.test(strErr)) { - return _t("chain-error.delete-comment-with-vote"); - } else if (/children == 0/.test(strErr)) { - return _t("chain-error.comment-children"); - } else if (/comment_cashout/.test(strErr)) { - return _t("chain-error.comment-cashout"); - } else if (/Votes evaluating for comment that is paid out is forbidden/.test(strErr)) { - return _t("chain-error.paid-out-post-forbidden"); - } - - return null; -} + if (/You may only post once every/.test(strErr)) { + return _t('chain-error.min-root-comment'); + } else if (/Your current vote on this comment is identical/.test(strErr)) { + return _t('chain-error.identical-vote'); + } else if (/Please wait to transact, or power up/.test(strErr)) { + return _t('chain-error.insufficient-resource'); + } else if (/Cannot delete a comment with net positive/.test(strErr)) { + return _t('chain-error.delete-comment-with-vote'); + } else if (/children == 0/.test(strErr)) { + return _t('chain-error.comment-children'); + } else if (/comment_cashout/.test(strErr)) { + return _t('chain-error.comment-cashout'); + } else if ( + /Votes evaluating for comment that is paid out is forbidden/.test(strErr) + ) { + return _t('chain-error.paid-out-post-forbidden'); + } + + return null; +}; export const formatError = (err: any): string => { + let chainErr = handleChainError(err.toString()); + if (chainErr) { + return chainErr; + } - let chainErr = handleChainError(err.toString()); + if (err.error_description && typeof err.error_description === 'string') { + let chainErr = handleChainError(err.error_description); if (chainErr) { - return chainErr; + return chainErr; } - if (err.error_description && typeof err.error_description === "string") { - let chainErr = handleChainError(err.error_description); - if (chainErr) { - return chainErr; - } + return err.error_description.substring(0, 80); + } - return err.error_description.substring(0, 80); + if (err.message && typeof err.message === 'string') { + let chainErr = handleChainError(err.message); + if (chainErr) { + return chainErr; } - if (err.message && typeof err.message === "string") { - let chainErr = handleChainError(err.message); - if (chainErr) { - return chainErr; - } - - return err.message.substring(0, 80); - } + return err.message.substring(0, 80); + } - return ''; + return ''; }; -export const broadcastPostingJSON = (username: string, id: string, json: {}): Promise => { - - // With posting private key - const postingKey = getPostingKey(username); - if (postingKey) { - const privateKey = PrivateKey.fromString(postingKey); - - const operation: CustomJsonOperation[1] = { - id, - required_auths: [], - required_posting_auths: [username], - json: JSON.stringify(json) - } +export const broadcastPostingJSON = ( + username: string, + id: string, + json: {}, +): Promise => { + // With posting private key + const postingKey = getPostingKey(username); + if (postingKey) { + const privateKey = PrivateKey.fromString(postingKey); + + const operation: CustomJsonOperation[1] = { + id, + required_auths: [], + required_posting_auths: [username], + json: JSON.stringify(json), + }; - return hiveClient.broadcast.json(operation, privateKey); - } + return hiveClient.broadcast.json(operation, privateKey); + } - // With hivesigner access token + // With hivesigner access token - let token = getAccessToken(username); - return token ? new hs.Client({ + let token = getAccessToken(username); + return token + ? new hs.Client({ accessToken: token, - }).customJson([], [username], id, JSON.stringify(json)) - .then((r: any) => r.result) : Promise.resolve(0); -} - -const broadcastPostingOperations = (username: string, operations: Operation[]): Promise => { - - // With posting private key - const postingKey = getPostingKey(username); - if (postingKey) { - const privateKey = PrivateKey.fromString(postingKey); - - return hiveClient.broadcast.sendOperations(operations, privateKey); - } + }) + .customJson([], [username], id, JSON.stringify(json)) + .then((r: any) => r.result) + : Promise.resolve(0); +}; - // With hivesigner access token - let token = getAccessToken(username); - return token ? new hs.Client({ +const broadcastPostingOperations = ( + username: string, + operations: Operation[], +): Promise => { + // With posting private key + const postingKey = getPostingKey(username); + if (postingKey) { + const privateKey = PrivateKey.fromString(postingKey); + + return hiveClient.broadcast.sendOperations(operations, privateKey); + } + + // With hivesigner access token + let token = getAccessToken(username); + return token + ? new hs.Client({ accessToken: token, - }).broadcast(operations) - .then((r: any) => r.result) : Promise.resolve(0); -} - -export const reblog = (username: string, author: string, permlink: string, _delete: boolean = false): Promise => { - const message = { - account: username, - author, - permlink - }; - - if (_delete) { - message["delete"] = "delete"; - } - - const json = ["reblog", message]; + }) + .broadcast(operations) + .then((r: any) => r.result) + : Promise.resolve(0); +}; - return broadcastPostingJSON(username, "follow", json) - .then((r: TransactionConfirmation) => { - usrActivity(username, 130, r.block_num, r.id).then(); - return r; - }); +export const reblog = ( + username: string, + author: string, + permlink: string, + _delete: boolean = false, +): Promise => { + const message = { + account: username, + author, + permlink, + }; + + if (_delete) { + message['delete'] = 'delete'; + } + + const json = ['reblog', message]; + + return broadcastPostingJSON(username, 'follow', json).then( + (r: TransactionConfirmation) => { + usrActivity(username, 130, r.block_num, r.id).then(); + return r; + }, + ); }; export const comment = ( - username: string, - parentAuthor: string, - parentPermlink: string, - permlink: string, - title: string, - body: string, - jsonMetadata: MetaData, - options: CommentOptions | null, - point: boolean = false + username: string, + parentAuthor: string, + parentPermlink: string, + permlink: string, + title: string, + body: string, + jsonMetadata: MetaData, + options: CommentOptions | null, + point: boolean = false, ): Promise => { - const params = { - parent_author: parentAuthor, - parent_permlink: parentPermlink, - author: username, - permlink, - title, - body, - json_metadata: JSON.stringify(jsonMetadata), - }; - - const opArray: Operation[] = [["comment", params]]; - - if (options) { - const e: Operation = ["comment_options", options]; - opArray.push(e); + const params = { + parent_author: parentAuthor, + parent_permlink: parentPermlink, + author: username, + permlink, + title, + body, + json_metadata: JSON.stringify(jsonMetadata), + }; + + const opArray: Operation[] = [['comment', params]]; + + if (options) { + const e: Operation = ['comment_options', options]; + opArray.push(e); + } + + return broadcastPostingOperations(username, opArray).then(r => { + if (point) { + const t = title ? 100 : 110; + usrActivity(username, t, r.block_num, r.id).then(); } - - return broadcastPostingOperations(username, opArray) - .then((r) => { - if (point) { - const t = title ? 100 : 110; - usrActivity(username, t, r.block_num, r.id).then(); - } - return r; - }) + return r; + }); }; -export const deleteComment = (username: string, author: string, permlink: string): Promise => { - const params = { - author, - permlink, - }; +export const deleteComment = ( + username: string, + author: string, + permlink: string, +): Promise => { + const params = { + author, + permlink, + }; - const opArray: Operation[] = [["delete_comment", params]]; + const opArray: Operation[] = [['delete_comment', params]]; - return broadcastPostingOperations(username, opArray); + return broadcastPostingOperations(username, opArray); }; -export const vote = (username: string, author: string, permlink: string, weight: number): Promise => { - const params = { - voter: username, - author, - permlink, - weight - } - - const opArray: Operation[] = [["vote", params]]; - - return broadcastPostingOperations(username, opArray) - .then((r: TransactionConfirmation) => { - usrActivity(username, 120, r.block_num, r.id).then(); - return r; - }); +export const vote = ( + username: string, + author: string, + permlink: string, + weight: number, +): Promise => { + const params = { + voter: username, + author, + permlink, + weight, + }; + + const opArray: Operation[] = [['vote', params]]; + + return broadcastPostingOperations(username, opArray).then( + (r: TransactionConfirmation) => { + usrActivity(username, 120, r.block_num, r.id).then(); + return r; + }, + ); }; -export const follow = (follower: string, following: string): Promise => { - const json = ["follow", { - follower, - following, - what: ["blog"] - }]; - - return broadcastPostingJSON(follower, "follow", json); -} - -export const unFollow = (follower: string, following: string): Promise => { - const json = ["follow", { - follower, - following, - what: [] - }]; - - return broadcastPostingJSON(follower, "follow", json); -} - -export const ignore = (follower: string, following: string): Promise => { - const json = ["follow", { - follower, - following, - what: ["ignore"] - }]; - - return broadcastPostingJSON(follower, "follow", json); -} - -export const claimRewardBalance = (username: string, rewardHive: string, rewardHbd: string, rewardVests: string): Promise => { - const params = { - account: username, - reward_hive: rewardHive, - reward_hbd: rewardHbd, - reward_vests: rewardVests - } - - const opArray: Operation[] = [['claim_reward_balance', params]]; - - return broadcastPostingOperations(username, opArray); -} - -export const transfer = (from: string, key: PrivateKey, to: string, amount: string, memo: string): Promise => { - const args = { - from, - to, - amount, - memo - }; - - return hiveClient.broadcast.transfer(args, key); -} - -export const transferHot = (from: string, to: string, amount: string, memo: string) => { - - const op: Operation = ['transfer', { - from, - to, - amount, - memo - }]; - - const params: Parameters = {callback: `https://ecency.com/@${from}/wallet`}; - return hs.sendOperation(op, params, () => { - }); -} - -export const transferKc = (from: string, to: string, amount: string, memo: string) => { - const asset = parseAsset(amount); - return keychain.transfer(from, to, asset.amount.toFixed(3).toString(), memo, asset.symbol, true); -} - -export const transferPoint = (from: string, key: PrivateKey, to: string, amount: string, memo: string): Promise => { - const json = JSON.stringify({ - sender: from, - receiver: to, - amount, - memo - }); - - const op = { - id: 'esteem_point_transfer', - json, - required_auths: [from], - required_posting_auths: [] - }; +export const follow = ( + follower: string, + following: string, +): Promise => { + const json = [ + 'follow', + { + follower, + following, + what: ['blog'], + }, + ]; + + return broadcastPostingJSON(follower, 'follow', json); +}; - return hiveClient.broadcast.json(op, key); -} +export const unFollow = ( + follower: string, + following: string, +): Promise => { + const json = [ + 'follow', + { + follower, + following, + what: [], + }, + ]; + + return broadcastPostingJSON(follower, 'follow', json); +}; -export const transferPointHot = (from: string, to: string, amount: string, memo: string) => { - const params = { - authority: "active", - required_auths: `["${from}"]`, - required_posting_auths: "[]", - id: "esteem_point_transfer", - json: JSON.stringify({ - sender: from, - receiver: to, - amount, - memo - }), - } +export const ignore = ( + follower: string, + following: string, +): Promise => { + const json = [ + 'follow', + { + follower, + following, + what: ['ignore'], + }, + ]; + + return broadcastPostingJSON(follower, 'follow', json); +}; - hotSign("custom-json", params, `@${from}/points`); -} +export const claimRewardBalance = ( + username: string, + rewardHive: string, + rewardHbd: string, + rewardVests: string, +): Promise => { + const params = { + account: username, + reward_hive: rewardHive, + reward_hbd: rewardHbd, + reward_vests: rewardVests, + }; -export const transferPointKc = (from: string, to: string, amount: string, memo: string) => { - const json = JSON.stringify({ - sender: from, - receiver: to, - amount, - memo - }); + const opArray: Operation[] = [['claim_reward_balance', params]]; - return keychain.customJson(from, "esteem_point_transfer", "Active", json, "Point Transfer") -} + return broadcastPostingOperations(username, opArray); +}; -export const transferToSavings = (from: string, key: PrivateKey, to: string, amount: string, memo: string): Promise => { +export const transfer = ( + from: string, + key: PrivateKey, + to: string, + amount: string, + memo: string, +): Promise => { + const args = { + from, + to, + amount, + memo, + }; + + return hiveClient.broadcast.transfer(args, key); +}; - const op: Operation = [ - 'transfer_to_savings', - { - from, - to, - amount, - memo - } - ] +export const transferHot = ( + from: string, + to: string, + amount: string, + memo: string, +) => { + const op: Operation = [ + 'transfer', + { + from, + to, + amount, + memo, + }, + ]; + + const params: Parameters = {callback: `https://ecency.com/@${from}/wallet`}; + return hs.sendOperation(op, params, () => {}); +}; - return hiveClient.broadcast.sendOperations([op], key); -} +export const transferKc = ( + from: string, + to: string, + amount: string, + memo: string, +) => { + const asset = parseAsset(amount); + return keychain.transfer( + from, + to, + asset.amount.toFixed(3).toString(), + memo, + asset.symbol, + true, + ); +}; -export const transferToSavingsHot = (from: string, to: string, amount: string, memo: string) => { +export const transferPoint = ( + from: string, + key: PrivateKey, + to: string, + amount: string, + memo: string, +): Promise => { + const json = JSON.stringify({ + sender: from, + receiver: to, + amount, + memo, + }); + + const op = { + id: 'esteem_point_transfer', + json, + required_auths: [from], + required_posting_auths: [], + }; + + return hiveClient.broadcast.json(op, key); +}; - const op: Operation = ['transfer_to_savings', { - from, - to, - amount, - memo - }]; +export const transferPointHot = ( + from: string, + to: string, + amount: string, + memo: string, +) => { + const params = { + authority: 'active', + required_auths: `["${from}"]`, + required_posting_auths: '[]', + id: 'esteem_point_transfer', + json: JSON.stringify({ + sender: from, + receiver: to, + amount, + memo, + }), + }; + + hotSign('custom-json', params, `@${from}/points`); +}; - const params: Parameters = {callback: `https://ecency.com/@${from}/wallet`}; - return hs.sendOperation(op, params, () => { - }); -} +export const transferPointKc = ( + from: string, + to: string, + amount: string, + memo: string, +) => { + const json = JSON.stringify({ + sender: from, + receiver: to, + amount, + memo, + }); + + return keychain.customJson( + from, + 'esteem_point_transfer', + 'Active', + json, + 'Point Transfer', + ); +}; -export const transferToSavingsKc = (from: string, to: string, amount: string, memo: string) => { - const op: Operation = [ - 'transfer_to_savings', - { - from, - to, - amount, - memo - } - ] - - return keychain.broadcast(from, [op], "Active"); -} +export const transferToSavings = ( + from: string, + key: PrivateKey, + to: string, + amount: string, + memo: string, +): Promise => { + const op: Operation = [ + 'transfer_to_savings', + { + from, + to, + amount, + memo, + }, + ]; + + return hiveClient.broadcast.sendOperations([op], key); +}; -export const limitOrderCreate = (owner: string, key: PrivateKey, amount_to_sell: any, min_to_receive: any, orderType: TransactionType): Promise => { - let expiration:any = new Date(Date.now()); - expiration.setDate(expiration.getDate() + 28); - expiration = expiration.toISOString().split(".")[0]; - - const op: Operation = [ - 'limit_order_create', - { - "orderid": Math.floor(Date.now() / 1000), - "owner": owner, - "amount_to_sell": `${amount_to_sell.toFixed(3)} ${orderType === TransactionType.Buy ? 'HBD' : "HIVE"}`, - "min_to_receive": `${min_to_receive.toFixed(3)} ${orderType === TransactionType.Buy ? 'HIVE' : "HBD"}`, - "fill_or_kill": false, - "expiration": expiration - } - ] - - return hiveClient.broadcast.sendOperations([op], key); -} +export const transferToSavingsHot = ( + from: string, + to: string, + amount: string, + memo: string, +) => { + const op: Operation = [ + 'transfer_to_savings', + { + from, + to, + amount, + memo, + }, + ]; + + const params: Parameters = {callback: `https://ecency.com/@${from}/wallet`}; + return hs.sendOperation(op, params, () => {}); +}; -export const limitOrderCancel = (owner: string, key: PrivateKey, orderid:number, ): Promise => { +export const transferToSavingsKc = ( + from: string, + to: string, + amount: string, + memo: string, +) => { + const op: Operation = [ + 'transfer_to_savings', + { + from, + to, + amount, + memo, + }, + ]; + + return keychain.broadcast(from, [op], 'Active'); +}; - const op: Operation = [ - 'limit_order_cancel', - { - "owner": owner, - "orderid": orderid, - } - ] +export const limitOrderCreate = ( + owner: string, + key: PrivateKey, + amount_to_sell: any, + min_to_receive: any, + orderType: TransactionType, +): Promise => { + let expiration: any = new Date(Date.now()); + expiration.setDate(expiration.getDate() + 28); + expiration = expiration.toISOString().split('.')[0]; + + const op: Operation = [ + 'limit_order_create', + { + orderid: Math.floor(Date.now() / 1000), + owner: owner, + amount_to_sell: `${amount_to_sell.toFixed(3)} ${ + orderType === TransactionType.Buy ? 'HBD' : 'HIVE' + }`, + min_to_receive: `${min_to_receive.toFixed(3)} ${ + orderType === TransactionType.Buy ? 'HIVE' : 'HBD' + }`, + fill_or_kill: false, + expiration: expiration, + }, + ]; + + return hiveClient.broadcast.sendOperations([op], key); +}; - return hiveClient.broadcast.sendOperations([op], key); -} +export const limitOrderCancel = ( + owner: string, + key: PrivateKey, + orderid: number, +): Promise => { + const op: Operation = [ + 'limit_order_cancel', + { + owner: owner, + orderid: orderid, + }, + ]; + + return hiveClient.broadcast.sendOperations([op], key); +}; -export const limitOrderCreateHot = (owner:string, amount_to_sell:any, min_to_receive:any, orderType: TransactionType) => { - let expiration:any = new Date(); - expiration.setDate(expiration.getDate() + 28); - expiration = expiration.toISOString().split(".")[0] - const op: Operation = [ - 'limit_order_create', - { - "orderid": Math.floor(Date.now() / 1000), - "owner": owner, - "amount_to_sell": `${amount_to_sell.toFixed(3)} ${orderType === TransactionType.Buy ? 'HBD' : "HIVE"}`, - "min_to_receive": `${min_to_receive.toFixed(3)} ${orderType === TransactionType.Buy ? 'HIVE' : "HBD"}`, - "fill_or_kill": false, - "expiration": expiration - } - ] - - const params: Parameters = {callback: `https://ecency.com/market`}; - return hs.sendOperation(op, params, () => {}); -} +export const limitOrderCreateHot = ( + owner: string, + amount_to_sell: any, + min_to_receive: any, + orderType: TransactionType, +) => { + let expiration: any = new Date(); + expiration.setDate(expiration.getDate() + 28); + expiration = expiration.toISOString().split('.')[0]; + const op: Operation = [ + 'limit_order_create', + { + orderid: Math.floor(Date.now() / 1000), + owner: owner, + amount_to_sell: `${amount_to_sell.toFixed(3)} ${ + orderType === TransactionType.Buy ? 'HBD' : 'HIVE' + }`, + min_to_receive: `${min_to_receive.toFixed(3)} ${ + orderType === TransactionType.Buy ? 'HIVE' : 'HBD' + }`, + fill_or_kill: false, + expiration: expiration, + }, + ]; + + const params: Parameters = {callback: `https://ecency.com/market`}; + return hs.sendOperation(op, params, () => {}); +}; -export const limitOrderCancelHot = (owner:string, orderid:number) => { - const op: Operation = [ - 'limit_order_cancel', - { - "orderid": orderid, - "owner": owner, - } - ] - - const params: Parameters = {callback: `https://ecency.com/market`}; - return hs.sendOperation(op, params, () => {}); -} +export const limitOrderCancelHot = (owner: string, orderid: number) => { + const op: Operation = [ + 'limit_order_cancel', + { + orderid: orderid, + owner: owner, + }, + ]; + + const params: Parameters = {callback: `https://ecency.com/market`}; + return hs.sendOperation(op, params, () => {}); +}; -export const limitOrderCreateKc = (owner:string, amount_to_sell:any, min_to_receive:any, orderType: TransactionType) => { - let expiration:any = new Date(); - expiration.setDate(expiration.getDate() + 28); - expiration = expiration.toISOString().split(".")[0] - const op: Operation = [ - 'limit_order_create', - { - "orderid": Math.floor(Date.now() / 1000), - "owner": owner, - "amount_to_sell": `${amount_to_sell.toFixed(3)} ${orderType === TransactionType.Buy ? 'HBD' : "HIVE"}`, - "min_to_receive": `${min_to_receive.toFixed(3)} ${orderType === TransactionType.Buy ? 'HIVE' : "HBD"}`, - "fill_or_kill": false, - "expiration": expiration - } - ] - - return keychain.broadcast(owner, [op], "Active"); -} +export const limitOrderCreateKc = ( + owner: string, + amount_to_sell: any, + min_to_receive: any, + orderType: TransactionType, +) => { + let expiration: any = new Date(); + expiration.setDate(expiration.getDate() + 28); + expiration = expiration.toISOString().split('.')[0]; + const op: Operation = [ + 'limit_order_create', + { + orderid: Math.floor(Date.now() / 1000), + owner: owner, + amount_to_sell: `${amount_to_sell.toFixed(3)} ${ + orderType === TransactionType.Buy ? 'HBD' : 'HIVE' + }`, + min_to_receive: `${min_to_receive.toFixed(3)} ${ + orderType === TransactionType.Buy ? 'HIVE' : 'HBD' + }`, + fill_or_kill: false, + expiration: expiration, + }, + ]; + + return keychain.broadcast(owner, [op], 'Active'); +}; -export const limitOrderCancelKc = (owner:string, orderid:any) => { - const op: Operation = [ - 'limit_order_cancel', - { - "orderid": orderid, - "owner": owner, - } - ] +export const limitOrderCancelKc = (owner: string, orderid: any) => { + const op: Operation = [ + 'limit_order_cancel', + { + orderid: orderid, + owner: owner, + }, + ]; - return keychain.broadcast(owner, [op], "Active"); -} + return keychain.broadcast(owner, [op], 'Active'); +}; -export const convert = (owner: string, key: PrivateKey, amount: string): Promise => { - const op: Operation = [ - 'convert', - { - owner, - amount, - requestid: new Date().getTime() >>> 0 - } - ] - - return hiveClient.broadcast.sendOperations([op], key); -} +export const convert = ( + owner: string, + key: PrivateKey, + amount: string, +): Promise => { + const op: Operation = [ + 'convert', + { + owner, + amount, + requestid: new Date().getTime() >>> 0, + }, + ]; + + return hiveClient.broadcast.sendOperations([op], key); +}; export const convertHot = (owner: string, amount: string) => { - - const op: Operation = ['convert', { - owner, - amount, - requestid: new Date().getTime() >>> 0 - }]; - - const params: Parameters = {callback: `https://ecency.com/@${owner}/wallet`}; - return hs.sendOperation(op, params, () => { - }); -} + const op: Operation = [ + 'convert', + { + owner, + amount, + requestid: new Date().getTime() >>> 0, + }, + ]; + + const params: Parameters = {callback: `https://ecency.com/@${owner}/wallet`}; + return hs.sendOperation(op, params, () => {}); +}; export const convertKc = (owner: string, amount: string) => { - const op: Operation = [ - 'convert', - { - owner, - amount, - requestid: new Date().getTime() >>> 0 - } - ] - - return keychain.broadcast(owner, [op], "Active"); -} - -export const transferFromSavings = (from: string, key: PrivateKey, to: string, amount: string, memo: string): Promise => { - const op: Operation = [ - 'transfer_from_savings', - { - from, - to, - amount, - memo, - request_id: new Date().getTime() >>> 0 - } - ] - - return hiveClient.broadcast.sendOperations([op], key); -} - -export const transferFromSavingsHot = (from: string, to: string, amount: string, memo: string) => { - - const op: Operation = ['transfer_from_savings', { - from, - to, - amount, - memo, - request_id: new Date().getTime() >>> 0 - }]; - - const params: Parameters = {callback: `https://ecency.com/@${from}/wallet`}; - return hs.sendOperation(op, params, () => { - }); -} - -export const transferFromSavingsKc = (from: string, to: string, amount: string, memo: string) => { - const op: Operation = [ - 'transfer_from_savings', - { - from, - to, - amount, - memo, - request_id: new Date().getTime() >>> 0 - } - ] - - return keychain.broadcast(from, [op], "Active"); -} - -export const transferToVesting = (from: string, key: PrivateKey, to: string, amount: string): Promise => { - const op: Operation = [ - 'transfer_to_vesting', - { - from, - to, - amount - } - ] - - return hiveClient.broadcast.sendOperations([op], key); -} + const op: Operation = [ + 'convert', + { + owner, + amount, + requestid: new Date().getTime() >>> 0, + }, + ]; + + return keychain.broadcast(owner, [op], 'Active'); +}; -export const transferToVestingHot = (from: string, to: string, amount: string) => { +export const transferFromSavings = ( + from: string, + key: PrivateKey, + to: string, + amount: string, + memo: string, +): Promise => { + const op: Operation = [ + 'transfer_from_savings', + { + from, + to, + amount, + memo, + request_id: new Date().getTime() >>> 0, + }, + ]; + + return hiveClient.broadcast.sendOperations([op], key); +}; - const op: Operation = ['transfer_to_vesting', { - from, - to, - amount - }]; +export const transferFromSavingsHot = ( + from: string, + to: string, + amount: string, + memo: string, +) => { + const op: Operation = [ + 'transfer_from_savings', + { + from, + to, + amount, + memo, + request_id: new Date().getTime() >>> 0, + }, + ]; + + const params: Parameters = {callback: `https://ecency.com/@${from}/wallet`}; + return hs.sendOperation(op, params, () => {}); +}; - const params: Parameters = {callback: `https://ecency.com/@${from}/wallet`}; - return hs.sendOperation(op, params, () => { - }); -} +export const transferFromSavingsKc = ( + from: string, + to: string, + amount: string, + memo: string, +) => { + const op: Operation = [ + 'transfer_from_savings', + { + from, + to, + amount, + memo, + request_id: new Date().getTime() >>> 0, + }, + ]; + + return keychain.broadcast(from, [op], 'Active'); +}; -export const transferToVestingKc = (from: string, to: string, amount: string) => { - const op: Operation = [ - 'transfer_to_vesting', - { - from, - to, - amount - } - ] - - return keychain.broadcast(from, [op], "Active"); -} +export const transferToVesting = ( + from: string, + key: PrivateKey, + to: string, + amount: string, +): Promise => { + const op: Operation = [ + 'transfer_to_vesting', + { + from, + to, + amount, + }, + ]; + + return hiveClient.broadcast.sendOperations([op], key); +}; -export const delegateVestingShares = (delegator: string, key: PrivateKey, delegatee: string, vestingShares: string): Promise => { - const op: Operation = [ - 'delegate_vesting_shares', - { - delegator, - delegatee, - vesting_shares: vestingShares - } - ] - - return hiveClient.broadcast.sendOperations([op], key); -} +export const transferToVestingHot = ( + from: string, + to: string, + amount: string, +) => { + const op: Operation = [ + 'transfer_to_vesting', + { + from, + to, + amount, + }, + ]; + + const params: Parameters = {callback: `https://ecency.com/@${from}/wallet`}; + return hs.sendOperation(op, params, () => {}); +}; -export const delegateVestingSharesHot = (delegator: string, delegatee: string, vestingShares: string) => { - const op: Operation = ['delegate_vesting_shares', { - delegator, - delegatee, - vesting_shares: vestingShares - }]; +export const transferToVestingKc = ( + from: string, + to: string, + amount: string, +) => { + const op: Operation = [ + 'transfer_to_vesting', + { + from, + to, + amount, + }, + ]; + + return keychain.broadcast(from, [op], 'Active'); +}; - const params: Parameters = {callback: `https://ecency.com/@${delegator}/wallet`}; - return hs.sendOperation(op, params, () => { - }); -} +export const delegateVestingShares = ( + delegator: string, + key: PrivateKey, + delegatee: string, + vestingShares: string, +): Promise => { + const op: Operation = [ + 'delegate_vesting_shares', + { + delegator, + delegatee, + vesting_shares: vestingShares, + }, + ]; + + return hiveClient.broadcast.sendOperations([op], key); +}; -export const delegateVestingSharesKc = (delegator: string, delegatee: string, vestingShares: string) => { - const op: Operation = [ - 'delegate_vesting_shares', - { - delegator, - delegatee, - vesting_shares: vestingShares - } - ] - - return keychain.broadcast(delegator, [op], "Active"); -} +export const delegateVestingSharesHot = ( + delegator: string, + delegatee: string, + vestingShares: string, +) => { + const op: Operation = [ + 'delegate_vesting_shares', + { + delegator, + delegatee, + vesting_shares: vestingShares, + }, + ]; + + const params: Parameters = { + callback: `https://ecency.com/@${delegator}/wallet`, + }; + return hs.sendOperation(op, params, () => {}); +}; -export const withdrawVesting = (account: string, key: PrivateKey, vestingShares: string): Promise => { - const op: Operation = [ - 'withdraw_vesting', - { - account, - vesting_shares: vestingShares - } - ] +export const delegateVestingSharesKc = ( + delegator: string, + delegatee: string, + vestingShares: string, +) => { + const op: Operation = [ + 'delegate_vesting_shares', + { + delegator, + delegatee, + vesting_shares: vestingShares, + }, + ]; + + return keychain.broadcast(delegator, [op], 'Active'); +}; - return hiveClient.broadcast.sendOperations([op], key); -} +export const withdrawVesting = ( + account: string, + key: PrivateKey, + vestingShares: string, +): Promise => { + const op: Operation = [ + 'withdraw_vesting', + { + account, + vesting_shares: vestingShares, + }, + ]; + + return hiveClient.broadcast.sendOperations([op], key); +}; export const withdrawVestingHot = (account: string, vestingShares: string) => { - const op: Operation = ['withdraw_vesting', { - account, - vesting_shares: vestingShares - }]; - - const params: Parameters = {callback: `https://ecency.com/@${account}/wallet`}; - return hs.sendOperation(op, params, () => { - }); -} + const op: Operation = [ + 'withdraw_vesting', + { + account, + vesting_shares: vestingShares, + }, + ]; + + const params: Parameters = { + callback: `https://ecency.com/@${account}/wallet`, + }; + return hs.sendOperation(op, params, () => {}); +}; export const withdrawVestingKc = (account: string, vestingShares: string) => { - const op: Operation = [ - 'withdraw_vesting', - { - account, - vesting_shares: vestingShares - } - ] - - return keychain.broadcast(account, [op], "Active"); -} - -export const setWithdrawVestingRoute = (from: string, key: PrivateKey, to: string, percent: number, autoVest: boolean): Promise => { - const op: Operation = [ - 'set_withdraw_vesting_route', - { - from_account: from, - to_account: to, - percent, - auto_vest: autoVest - } - ] - - return hiveClient.broadcast.sendOperations([op], key); -} - -export const setWithdrawVestingRouteHot = (from: string, to: string, percent: number, autoVest: boolean) => { - const op: Operation = ['set_withdraw_vesting_route', { - from_account: from, - to_account: to, - percent, - auto_vest: autoVest - }]; - - const params: Parameters = {callback: `https://ecency.com/@${from}/wallet`}; - return hs.sendOperation(op, params, () => { - }); -} + const op: Operation = [ + 'withdraw_vesting', + { + account, + vesting_shares: vestingShares, + }, + ]; + + return keychain.broadcast(account, [op], 'Active'); +}; -export const setWithdrawVestingRouteKc = (from: string, to: string, percent: number, autoVest: boolean) => { - const op: Operation = [ - 'set_withdraw_vesting_route', - { - from_account: from, - to_account: to, - percent, - auto_vest: autoVest - } - ] - - return keychain.broadcast(from, [op], "Active"); -} +export const setWithdrawVestingRoute = ( + from: string, + key: PrivateKey, + to: string, + percent: number, + autoVest: boolean, +): Promise => { + const op: Operation = [ + 'set_withdraw_vesting_route', + { + from_account: from, + to_account: to, + percent, + auto_vest: autoVest, + }, + ]; + + return hiveClient.broadcast.sendOperations([op], key); +}; -export const witnessVote = (account: string, key: PrivateKey, witness: string, approve: boolean): Promise => { - const op: Operation = [ - 'account_witness_vote', - { - account, - witness, - approve - } - ] - - return hiveClient.broadcast.sendOperations([op], key); -} +export const setWithdrawVestingRouteHot = ( + from: string, + to: string, + percent: number, + autoVest: boolean, +) => { + const op: Operation = [ + 'set_withdraw_vesting_route', + { + from_account: from, + to_account: to, + percent, + auto_vest: autoVest, + }, + ]; + + const params: Parameters = {callback: `https://ecency.com/@${from}/wallet`}; + return hs.sendOperation(op, params, () => {}); +}; -export const witnessVoteHot = (account: string, witness: string, approve: boolean) => { - const params = { - account, - witness, - approve, - } +export const setWithdrawVestingRouteKc = ( + from: string, + to: string, + percent: number, + autoVest: boolean, +) => { + const op: Operation = [ + 'set_withdraw_vesting_route', + { + from_account: from, + to_account: to, + percent, + auto_vest: autoVest, + }, + ]; + + return keychain.broadcast(from, [op], 'Active'); +}; - hotSign("account-witness-vote", params, "witnesses"); -} +export const witnessVote = ( + account: string, + key: PrivateKey, + witness: string, + approve: boolean, +): Promise => { + const op: Operation = [ + 'account_witness_vote', + { + account, + witness, + approve, + }, + ]; + + return hiveClient.broadcast.sendOperations([op], key); +}; -export const witnessVoteKc = (account: string, witness: string, approve: boolean) => { - return keychain.witnessVote(account, witness, approve); -} +export const witnessVoteHot = ( + account: string, + witness: string, + approve: boolean, +) => { + const params = { + account, + witness, + approve, + }; + + hotSign('account-witness-vote', params, 'witnesses'); +}; -export const witnessProxy = (account: string, key: PrivateKey, proxy: string): Promise => { - const op: Operation = [ - 'account_witness_proxy', - { - account, - proxy - } - ] +export const witnessVoteKc = ( + account: string, + witness: string, + approve: boolean, +) => { + return keychain.witnessVote(account, witness, approve); +}; - return hiveClient.broadcast.sendOperations([op], key); -} +export const witnessProxy = ( + account: string, + key: PrivateKey, + proxy: string, +): Promise => { + const op: Operation = [ + 'account_witness_proxy', + { + account, + proxy, + }, + ]; + + return hiveClient.broadcast.sendOperations([op], key); +}; export const witnessProxyHot = (account: string, proxy: string) => { - const params = { - account, - proxy - } + const params = { + account, + proxy, + }; - hotSign("account-witness-proxy", params, "witnesses"); -} + hotSign('account-witness-proxy', params, 'witnesses'); +}; export const witnessProxyKc = (account: string, witness: string) => { - return keychain.witnessProxy(account, witness); -} - -export const proposalVote = (account: string, key: PrivateKey, proposal: number, approve: boolean): Promise => { - const op: Operation = [ - 'update_proposal_votes', - { - voter: account, - proposal_ids: [proposal], - approve, - extensions: [] - } - ] - - return hiveClient.broadcast.sendOperations([op], key); -} - -export const proposalVoteHot = (account: string, proposal: number, approve: boolean) => { - const params = { - account, - proposal_ids: JSON.stringify( - [proposal] - ), - approve, - } - - hotSign("update-proposal-votes", params, "proposals"); -} - -export const proposalVoteKc = (account: string, proposal: number, approve: boolean) => { - const op: Operation = [ - 'update_proposal_votes', - { - voter: account, - proposal_ids: [proposal], - approve, - extensions: [] - } - ] - - return keychain.broadcast(account, [op], "Active"); - -} - -export const subscribe = (username: string, community: string): Promise => { - const json = [ - 'subscribe', {community} - ]; - - return broadcastPostingJSON(username, "community", json); -} - -export const unSubscribe = (username: string, community: string): Promise => { - const json = [ - 'unsubscribe', {community} - ] - - return broadcastPostingJSON(username, "community", json); -} - -export const promote = (key: PrivateKey, user: string, author: string, permlink: string, duration: number): Promise => { - - const json = JSON.stringify({ - user, - author, - permlink, - duration - }); - - const op = { - id: 'esteem_promote', - json, - required_auths: [user], - required_posting_auths: [] - }; + return keychain.witnessProxy(account, witness); +}; - return hiveClient.broadcast.json(op, key); -} +export const proposalVote = ( + account: string, + key: PrivateKey, + proposal: number, + approve: boolean, +): Promise => { + const op: Operation = [ + 'update_proposal_votes', + { + voter: account, + proposal_ids: [proposal], + approve, + extensions: [], + }, + ]; + + return hiveClient.broadcast.sendOperations([op], key); +}; -export const promoteHot = (user: string, author: string, permlink: string, duration: number) => { - const params = { - authority: "active", - required_auths: `["${user}"]`, - required_posting_auths: "[]", - id: "esteem_promote", - json: JSON.stringify({ - user, - author, - permlink, - duration - }) - } +export const proposalVoteHot = ( + account: string, + proposal: number, + approve: boolean, +) => { + const params = { + account, + proposal_ids: JSON.stringify([proposal]), + approve, + }; + + hotSign('update-proposal-votes', params, 'proposals'); +}; - hotSign("custom-json", params, `@${user}/points`); -} +export const proposalVoteKc = ( + account: string, + proposal: number, + approve: boolean, +) => { + const op: Operation = [ + 'update_proposal_votes', + { + voter: account, + proposal_ids: [proposal], + approve, + extensions: [], + }, + ]; + + return keychain.broadcast(account, [op], 'Active'); +}; -export const promoteKc = (user: string, author: string, permlink: string, duration: number) => { - const json = JSON.stringify({ - user, - author, - permlink, - duration - }); +export const subscribe = ( + username: string, + community: string, +): Promise => { + const json = ['subscribe', {community}]; - return keychain.customJson(user, "esteem_promote", "Active", json, "Promote"); -} + return broadcastPostingJSON(username, 'community', json); +}; -export const boost = (key: PrivateKey, user: string, author: string, permlink: string, amount: string): Promise => { - const json = JSON.stringify({ - user, - author, - permlink, - amount - }); - - const op = { - id: 'esteem_boost', - json, - required_auths: [user], - required_posting_auths: [] - }; +export const unSubscribe = ( + username: string, + community: string, +): Promise => { + const json = ['unsubscribe', {community}]; - return hiveClient.broadcast.json(op, key); -} + return broadcastPostingJSON(username, 'community', json); +}; -export const boostHot = (user: string, author: string, permlink: string, amount: string) => { - const params = { - authority: "active", - required_auths: `["${user}"]`, - required_posting_auths: "[]", - id: "esteem_boost", - json: JSON.stringify({ - user, - author, - permlink, - amount - }) - } +export const promote = ( + key: PrivateKey, + user: string, + author: string, + permlink: string, + duration: number, +): Promise => { + const json = JSON.stringify({ + user, + author, + permlink, + duration, + }); + + const op = { + id: 'esteem_promote', + json, + required_auths: [user], + required_posting_auths: [], + }; + + return hiveClient.broadcast.json(op, key); +}; - hotSign("custom-json", params, `@${user}/points`); -} +export const promoteHot = ( + user: string, + author: string, + permlink: string, + duration: number, +) => { + const params = { + authority: 'active', + required_auths: `["${user}"]`, + required_posting_auths: '[]', + id: 'esteem_promote', + json: JSON.stringify({ + user, + author, + permlink, + duration, + }), + }; + + hotSign('custom-json', params, `@${user}/points`); +}; -export const boostKc = (user: string, author: string, permlink: string, amount: string) => { - const json = JSON.stringify({ - user, - author, - permlink, - amount - }); +export const promoteKc = ( + user: string, + author: string, + permlink: string, + duration: number, +) => { + const json = JSON.stringify({ + user, + author, + permlink, + duration, + }); + + return keychain.customJson(user, 'esteem_promote', 'Active', json, 'Promote'); +}; - return keychain.customJson(user, "esteem_boost", "Active", json, "Boost"); -} +export const boost = ( + key: PrivateKey, + user: string, + author: string, + permlink: string, + amount: string, +): Promise => { + const json = JSON.stringify({ + user, + author, + permlink, + amount, + }); + + const op = { + id: 'esteem_boost', + json, + required_auths: [user], + required_posting_auths: [], + }; + + return hiveClient.broadcast.json(op, key); +}; -export const communityRewardsRegister = (key: PrivateKey, name: string): Promise => { - const json = JSON.stringify({ - name, - }); +export const boostHot = ( + user: string, + author: string, + permlink: string, + amount: string, +) => { + const params = { + authority: 'active', + required_auths: `["${user}"]`, + required_posting_auths: '[]', + id: 'esteem_boost', + json: JSON.stringify({ + user, + author, + permlink, + amount, + }), + }; + + hotSign('custom-json', params, `@${user}/points`); +}; - const op = { - id: 'esteem_registration', - json, - required_auths: [name], - required_posting_auths: [] - }; +export const boostKc = ( + user: string, + author: string, + permlink: string, + amount: string, +) => { + const json = JSON.stringify({ + user, + author, + permlink, + amount, + }); + + return keychain.customJson(user, 'esteem_boost', 'Active', json, 'Boost'); +}; - return hiveClient.broadcast.json(op, key); -} +export const communityRewardsRegister = ( + key: PrivateKey, + name: string, +): Promise => { + const json = JSON.stringify({ + name, + }); + + const op = { + id: 'esteem_registration', + json, + required_auths: [name], + required_posting_auths: [], + }; + + return hiveClient.broadcast.json(op, key); +}; export const communityRewardsRegisterHot = (name: string) => { - const params = { - authority: "active", - required_auths: `["${name}"]`, - required_posting_auths: "[]", - id: "esteem_registration", - json: JSON.stringify({ - name - }) - } - - hotSign("custom-json", params, `created/${name}`); -} + const params = { + authority: 'active', + required_auths: `["${name}"]`, + required_posting_auths: '[]', + id: 'esteem_registration', + json: JSON.stringify({ + name, + }), + }; + + hotSign('custom-json', params, `created/${name}`); +}; export const communityRewardsRegisterKc = (name: string) => { - const json = JSON.stringify({ - name - }); - - return keychain.customJson(name, "esteem_registration", "Active", json, "Community Registration"); -} - -export const updateProfile = (account: Account, newProfile: { - name: string, - about: string, - website: string, - location: string, - cover_image: string, - profile_image: string, -}): Promise => { - const params = { - account: account.name, - json_metadata: '', - posting_json_metadata: JSON.stringify({profile: {...newProfile, version: 2}}), - extensions: [] - }; - - const opArray: Operation[] = [["account_update2", params]]; - - return broadcastPostingOperations(account.name, opArray); -} - -export const grantPostingPermission = (key: PrivateKey, account: Account, pAccount: string) => { - if (!account.__loaded) { - throw "posting|memo_key|json_metadata required with account instance"; - } + const json = JSON.stringify({ + name, + }); + + return keychain.customJson( + name, + 'esteem_registration', + 'Active', + json, + 'Community Registration', + ); +}; - const newPosting = Object.assign( - {}, - {...account.posting}, - { - account_auths: [ - ...account.posting.account_auths, - [pAccount, account.posting.weight_threshold] - ] - } - ); - - // important! - newPosting.account_auths.sort((a, b) => (a[0] > b[0] ? 1 : -1)); - - return hiveClient.broadcast.updateAccount({ - account: account.name, - posting: newPosting, - active: undefined, - memo_key: account.memo_key, - json_metadata: account.json_metadata - }, key); -}; - -export const revokePostingPermission = (key: PrivateKey, account: Account, pAccount: string) => { - if (!account.__loaded) { - throw "posting|memo_key|json_metadata required with account instance"; - } +export const updateProfile = ( + account: Account, + newProfile: { + name: string; + about: string; + website: string; + location: string; + cover_image: string; + profile_image: string; + }, +): Promise => { + const params = { + account: account.name, + json_metadata: '', + posting_json_metadata: JSON.stringify({ + profile: {...newProfile, version: 2}, + }), + extensions: [], + }; + + const opArray: Operation[] = [['account_update2', params]]; + + return broadcastPostingOperations(account.name, opArray); +}; - const newPosting = Object.assign( - {}, - {...account.posting}, - { - account_auths: account.posting.account_auths.filter(x => x[0] !== pAccount) - } - ); - - return hiveClient.broadcast.updateAccount( - { - account: account.name, - posting: newPosting, - memo_key: account.memo_key, - json_metadata: account.json_metadata - }, - key - ); -}; - -export const setUserRole = (username: string, community: string, account: string, role: string): Promise => { - const json = [ - 'setRole', {community, account, role} - ] - - return broadcastPostingJSON(username, "community", json); -} +export const grantPostingPermission = ( + key: PrivateKey, + account: Account, + pAccount: string, +) => { + if (!account.__loaded) { + throw 'posting|memo_key|json_metadata required with account instance'; + } + + const newPosting = Object.assign( + {}, + {...account.posting}, + { + account_auths: [ + ...account.posting.account_auths, + [pAccount, account.posting.weight_threshold], + ], + }, + ); + + // important! + newPosting.account_auths.sort((a, b) => (a[0] > b[0] ? 1 : -1)); + + return hiveClient.broadcast.updateAccount( + { + account: account.name, + posting: newPosting, + active: undefined, + memo_key: account.memo_key, + json_metadata: account.json_metadata, + }, + key, + ); +}; -export const updateCommunity = (username: string, community: string, props: { title: string, about: string, lang: string, description: string, flag_text: string, is_nsfw: boolean }): Promise => { - const json = [ - 'updateProps', {community, props} - ]; +export const revokePostingPermission = ( + key: PrivateKey, + account: Account, + pAccount: string, +) => { + if (!account.__loaded) { + throw 'posting|memo_key|json_metadata required with account instance'; + } + + const newPosting = Object.assign( + {}, + {...account.posting}, + { + account_auths: account.posting.account_auths.filter( + x => x[0] !== pAccount, + ), + }, + ); + + return hiveClient.broadcast.updateAccount( + { + account: account.name, + posting: newPosting, + memo_key: account.memo_key, + json_metadata: account.json_metadata, + }, + key, + ); +}; - return broadcastPostingJSON(username, "community", json); -} +export const setUserRole = ( + username: string, + community: string, + account: string, + role: string, +): Promise => { + const json = ['setRole', {community, account, role}]; -export const pinPost = (username: string, community: string, account: string, permlink: string, pin: boolean): Promise => { - const json = [ - pin ? 'pinPost' : 'unpinPost', {community, account, permlink} - ] + return broadcastPostingJSON(username, 'community', json); +}; - return broadcastPostingJSON(username, "community", json); -} +export const updateCommunity = ( + username: string, + community: string, + props: { + title: string; + about: string; + lang: string; + description: string; + flag_text: string; + is_nsfw: boolean; + }, +): Promise => { + const json = ['updateProps', {community, props}]; -export const mutePost = (username: string, community: string, account: string, permlink: string, notes: string, mute: boolean): Promise => { - const json = [ - mute ? 'mutePost' : 'unmutePost', {community, account, permlink, notes} - ]; + return broadcastPostingJSON(username, 'community', json); +}; - return broadcastPostingJSON(username, "community", json); -} +export const pinPost = ( + username: string, + community: string, + account: string, + permlink: string, + pin: boolean, +): Promise => { + const json = [pin ? 'pinPost' : 'unpinPost', {community, account, permlink}]; -export const hiveNotifySetLastRead = (username: string): Promise => { - const now = new Date().toISOString(); - const date = now.split(".")[0]; + return broadcastPostingJSON(username, 'community', json); +}; - const params = { - id: "notify", - required_auths: [], - required_posting_auths: [username], - json: JSON.stringify(['setLastRead', {date}]) - } - const params1 = { - id: "ecency_notify", - required_auths: [], - required_posting_auths: [username], - json: JSON.stringify(['setLastRead', {date}]) - } +export const mutePost = ( + username: string, + community: string, + account: string, + permlink: string, + notes: string, + mute: boolean, +): Promise => { + const json = [ + mute ? 'mutePost' : 'unmutePost', + {community, account, permlink, notes}, + ]; - const opArray: Operation[] = [['custom_json', params], ['custom_json', params1]]; + return broadcastPostingJSON(username, 'community', json); +}; - return broadcastPostingOperations(username, opArray); -} +export const hiveNotifySetLastRead = ( + username: string, +): Promise => { + const now = new Date().toISOString(); + const date = now.split('.')[0]; + + const params = { + id: 'notify', + required_auths: [], + required_posting_auths: [username], + json: JSON.stringify(['setLastRead', {date}]), + }; + const params1 = { + id: 'ecency_notify', + required_auths: [], + required_posting_auths: [username], + json: JSON.stringify(['setLastRead', {date}]), + }; + + const opArray: Operation[] = [ + ['custom_json', params], + ['custom_json', params1], + ]; + + return broadcastPostingOperations(username, opArray); +}; -export const updatePassword = (update: AccountUpdateOperation[1], ownerKey: PrivateKey): Promise => hiveClient.broadcast.updateAccount(update, ownerKey) +export const updatePassword = ( + update: AccountUpdateOperation[1], + ownerKey: PrivateKey, +): Promise => + hiveClient.broadcast.updateAccount(update, ownerKey); diff --git a/src/common/api/private-api.ts b/src/common/api/private-api.ts index 9218a5d6112..633f15e6852 100644 --- a/src/common/api/private-api.ts +++ b/src/common/api/private-api.ts @@ -1,391 +1,586 @@ -import axios from "axios"; +import axios from 'axios'; -import {PointTransaction} from "../store/points/types"; -import {ApiNotification, NotificationFilter} from "../store/notifications/types"; -import {Entry} from "../store/entries/types"; +import {PointTransaction} from '../store/points/types'; +import { + ApiNotification, + NotificationFilter, +} from '../store/notifications/types'; +import {Entry} from '../store/entries/types'; -import {getAccessToken} from "../helper/user-token"; +import {getAccessToken} from '../helper/user-token'; -import {apiBase} from "./helper"; +import {apiBase} from './helper'; -import {AppWindow} from "../../client/window"; +import {AppWindow} from '../../client/window'; declare var window: AppWindow; export interface ReceivedVestingShare { - delegatee: string; - delegator: string; - timestamp: string; - vesting_shares: string; + delegatee: string; + delegator: string; + timestamp: string; + vesting_shares: string; } -export const getReceivedVestingShares = (username: string): Promise => - axios.get(apiBase(`/private-api/received-vesting/${username}`)).then((resp) => resp.data.list); - +export const getReceivedVestingShares = ( + username: string, +): Promise => + axios + .get(apiBase(`/private-api/received-vesting/${username}`)) + .then(resp => resp.data.list); export interface RewardedCommunity { - start_date: string; - total_rewards: string; - name: string; + start_date: string; + total_rewards: string; + name: string; } export const getRewardedCommunities = (): Promise => - axios.get(apiBase(`/private-api/rewarded-communities`)).then((resp) => resp.data); + axios + .get(apiBase(`/private-api/rewarded-communities`)) + .then(resp => resp.data); export interface LeaderBoardItem { - _id: string; - count: number; - points: string + _id: string; + count: number; + points: string; } -export type LeaderBoardDuration = "day" | "week" | "month"; +export type LeaderBoardDuration = 'day' | 'week' | 'month'; -export const getLeaderboard = (duration: LeaderBoardDuration): Promise => { - return axios.get(apiBase(`/private-api/leaderboard/${duration}`)).then(resp => resp.data); +export const getLeaderboard = ( + duration: LeaderBoardDuration, +): Promise => { + return axios + .get(apiBase(`/private-api/leaderboard/${duration}`)) + .then(resp => resp.data); }; export interface CurationItem { - efficiency: number; - account: string; - vests: number; - votes: number; - uniques: number; + efficiency: number; + account: string; + vests: number; + votes: number; + uniques: number; } -export type CurationDuration = "day" | "week" | "month"; +export type CurationDuration = 'day' | 'week' | 'month'; -export const getCuration = (duration: CurationDuration): Promise => { - return axios.get(apiBase(`/private-api/curation/${duration}`)).then(resp => resp.data); +export const getCuration = ( + duration: CurationDuration, +): Promise => { + return axios + .get(apiBase(`/private-api/curation/${duration}`)) + .then(resp => resp.data); }; -export const signUp = (username: string, email: string, referral: string): Promise => - axios - .post(apiBase(`/private-api/account-create`), { - username: username, - email: email, - referral: referral - }) - .then(resp => { - return resp; - }); +export const signUp = ( + username: string, + email: string, + referral: string, +): Promise => + axios + .post(apiBase(`/private-api/account-create`), { + username: username, + email: email, + referral: referral, + }) + .then(resp => { + return resp; + }); export const subscribeEmail = (email: string): Promise => - axios - .post(apiBase(`/private-api/subscribe`), { - email: email - }) - .then(resp => { - return resp; - }); - -export const usrActivity = (username: string, ty: number, bl: string | number = '', tx: string | number = '') => { - if (!window.usePrivate) { - return new Promise((resolve) => resolve(null)); - } - - const params: { - code: string | undefined; - ty: number; - bl?: string | number; - tx?: string | number; - } = {code: getAccessToken(username), ty}; - - if (bl) params.bl = bl; - if (tx) params.tx = tx; + axios + .post(apiBase(`/private-api/subscribe`), { + email: email, + }) + .then(resp => { + return resp; + }); - return axios.post(apiBase(`/private-api/usr-activity`), params); +export const usrActivity = ( + username: string, + ty: number, + bl: string | number = '', + tx: string | number = '', +) => { + if (!window.usePrivate) { + return new Promise(resolve => resolve(null)); + } + + const params: { + code: string | undefined; + ty: number; + bl?: string | number; + tx?: string | number; + } = {code: getAccessToken(username), ty}; + + if (bl) params.bl = bl; + if (tx) params.tx = tx; + + return axios.post(apiBase(`/private-api/usr-activity`), params); }; -export const getNotifications = (username: string, filter: NotificationFilter | null, since: string | null = null): Promise => { - - const data: { code: string | undefined; filter?: string, since?: string } = {code: getAccessToken(username)}; - - if (filter) { - data.filter = filter; - } - - if (since) { - data.since = since; - } - - return axios.post(apiBase(`/private-api/notifications`), data).then(resp => resp.data); +export const getNotifications = ( + username: string, + filter: NotificationFilter | null, + since: string | null = null, +): Promise => { + const data: {code: string | undefined; filter?: string; since?: string} = { + code: getAccessToken(username), + }; + + if (filter) { + data.filter = filter; + } + + if (since) { + data.since = since; + } + + return axios + .post(apiBase(`/private-api/notifications`), data) + .then(resp => resp.data); }; -export const getCurrencyTokenRate = (currency:string, token:string): Promise => - axios.get(apiBase(`/private-api/market-data/${currency==="hbd" ? "usd" : currency}/${token}`)).then((resp:any) => resp.data) - -export const getUnreadNotificationCount = (username: string): Promise => { - const data = {code: getAccessToken(username)}; - - return data.code ? axios +export const getCurrencyTokenRate = ( + currency: string, + token: string, +): Promise => + axios + .get( + apiBase( + `/private-api/market-data/${ + currency === 'hbd' ? 'usd' : currency + }/${token}`, + ), + ) + .then((resp: any) => resp.data); + +export const getUnreadNotificationCount = ( + username: string, +): Promise => { + const data = {code: getAccessToken(username)}; + + return data.code + ? axios .post(apiBase(`/private-api/notifications/unread`), data) - .then(resp => resp.data.count) : Promise.resolve(0); -} - -export const markNotifications = (username: string, id: string | null = null) => { - const data: { code: string | undefined; id?: string } = {code: getAccessToken(username)} - if (id) { - data.id = id; - } + .then(resp => resp.data.count) + : Promise.resolve(0); +}; - return axios.post(apiBase(`/private-api/notifications/mark`), data); +export const markNotifications = ( + username: string, + id: string | null = null, +) => { + const data: {code: string | undefined; id?: string} = { + code: getAccessToken(username), + }; + if (id) { + data.id = id; + } + + return axios.post(apiBase(`/private-api/notifications/mark`), data); }; export interface UserImage { - created: string - timestamp: number - url: string - _id: string + created: string; + timestamp: number; + url: string; + _id: string; } export const getImages = (username: string): Promise => { - const data = {code: getAccessToken(username)}; - return axios.post(apiBase(`/private-api/images`), data).then(resp => resp.data); -} + const data = {code: getAccessToken(username)}; + return axios + .post(apiBase(`/private-api/images`), data) + .then(resp => resp.data); +}; -export const deleteImage = (username: string, imageID: string): Promise => { - const data = {code: getAccessToken(username), id: imageID}; - return axios.post(apiBase(`/private-api/images-delete`), data).then(resp => resp.data); -} +export const deleteImage = ( + username: string, + imageID: string, +): Promise => { + const data = {code: getAccessToken(username), id: imageID}; + return axios + .post(apiBase(`/private-api/images-delete`), data) + .then(resp => resp.data); +}; export const addImage = (username: string, url: string): Promise => { - const data = {code: getAccessToken(username), url: url}; - return axios.post(apiBase(`/private-api/images-add`), data).then(resp => resp.data); -} + const data = {code: getAccessToken(username), url: url}; + return axios + .post(apiBase(`/private-api/images-add`), data) + .then(resp => resp.data); +}; export interface Draft { - body: string - created: string - post_type: string - tags: string - timestamp: number - title: string - _id: string + body: string; + created: string; + post_type: string; + tags: string; + timestamp: number; + title: string; + _id: string; } export const getDrafts = (username: string): Promise => { - const data = {code: getAccessToken(username)}; - return axios.post(apiBase(`/private-api/drafts`), data).then(resp => resp.data); -} + const data = {code: getAccessToken(username)}; + return axios + .post(apiBase(`/private-api/drafts`), data) + .then(resp => resp.data); +}; -export const addDraft = (username: string, title: string, body: string, tags: string): Promise<{ drafts: Draft[] }> => { - const data = {code: getAccessToken(username), title, body, tags}; - return axios.post(apiBase(`/private-api/drafts-add`), data).then(resp => resp.data); -} +export const addDraft = ( + username: string, + title: string, + body: string, + tags: string, +): Promise<{drafts: Draft[]}> => { + const data = {code: getAccessToken(username), title, body, tags}; + return axios + .post(apiBase(`/private-api/drafts-add`), data) + .then(resp => resp.data); +}; -export const updateDraft = (username: string, draftId: string, title: string, body: string, tags: string): Promise => { - const data = {code: getAccessToken(username), id: draftId, title, body, tags}; - return axios.post(apiBase(`/private-api/drafts-update`), data).then(resp => resp.data); -} +export const updateDraft = ( + username: string, + draftId: string, + title: string, + body: string, + tags: string, +): Promise => { + const data = {code: getAccessToken(username), id: draftId, title, body, tags}; + return axios + .post(apiBase(`/private-api/drafts-update`), data) + .then(resp => resp.data); +}; -export const deleteDraft = (username: string, draftId: string): Promise => { - const data = {code: getAccessToken(username), id: draftId}; - return axios.post(apiBase(`/private-api/drafts-delete`), data).then(resp => resp.data); -} +export const deleteDraft = ( + username: string, + draftId: string, +): Promise => { + const data = {code: getAccessToken(username), id: draftId}; + return axios + .post(apiBase(`/private-api/drafts-delete`), data) + .then(resp => resp.data); +}; export interface Schedule { - _id: string; - username: string; - permlink: string; - title: string; - body: string; - tags: string[]; - tags_arr: string; - schedule: string; - original_schedule: string; - reblog: boolean; - status: 1 | 2 | 3 | 4; - message: string | null + _id: string; + username: string; + permlink: string; + title: string; + body: string; + tags: string[]; + tags_arr: string; + schedule: string; + original_schedule: string; + reblog: boolean; + status: 1 | 2 | 3 | 4; + message: string | null; } export const getSchedules = (username: string): Promise => { - const data = {code: getAccessToken(username)}; - return axios.post(apiBase(`/private-api/schedules`), data).then(resp => resp.data); -} + const data = {code: getAccessToken(username)}; + return axios + .post(apiBase(`/private-api/schedules`), data) + .then(resp => resp.data); +}; -export const addSchedule = (username: string, permlink: string, title: string, body: string, meta: {}, options: {}, schedule: string, reblog: boolean): Promise => { - const data = {code: getAccessToken(username), permlink, title, body, meta, options, schedule, reblog} - return axios.post(apiBase(`/private-api/schedules-add`), data).then(resp => resp.data); -} +export const addSchedule = ( + username: string, + permlink: string, + title: string, + body: string, + meta: {}, + options: {}, + schedule: string, + reblog: boolean, +): Promise => { + const data = { + code: getAccessToken(username), + permlink, + title, + body, + meta, + options, + schedule, + reblog, + }; + return axios + .post(apiBase(`/private-api/schedules-add`), data) + .then(resp => resp.data); +}; export const deleteSchedule = (username: string, id: string): Promise => { - const data = {code: getAccessToken(username), id}; - return axios.post(apiBase(`/private-api/schedules-delete`), data).then(resp => resp.data); -} + const data = {code: getAccessToken(username), id}; + return axios + .post(apiBase(`/private-api/schedules-delete`), data) + .then(resp => resp.data); +}; export const moveSchedule = (username: string, id: string): Promise => { - const data = {code: getAccessToken(username), id}; - return axios.post(apiBase(`/private-api/schedules-move`), data).then(resp => resp.data); -} + const data = {code: getAccessToken(username), id}; + return axios + .post(apiBase(`/private-api/schedules-move`), data) + .then(resp => resp.data); +}; export interface Bookmark { - _id: string, - author: string, - permlink: string, - timestamp: number, - created: string + _id: string; + author: string; + permlink: string; + timestamp: number; + created: string; } export const getBookmarks = (username: string): Promise => { - const data = {code: getAccessToken(username)}; - return axios.post(apiBase(`/private-api/bookmarks`), data).then(resp => resp.data); -} + const data = {code: getAccessToken(username)}; + return axios + .post(apiBase(`/private-api/bookmarks`), data) + .then(resp => resp.data); +}; -export const addBookmark = (username: string, author: string, permlink: string): Promise<{ bookmarks: Bookmark[] }> => { - const data = {code: getAccessToken(username), author, permlink}; - return axios.post(apiBase(`/private-api/bookmarks-add`), data).then(resp => resp.data); -} +export const addBookmark = ( + username: string, + author: string, + permlink: string, +): Promise<{bookmarks: Bookmark[]}> => { + const data = {code: getAccessToken(username), author, permlink}; + return axios + .post(apiBase(`/private-api/bookmarks-add`), data) + .then(resp => resp.data); +}; -export const deleteBookmark = (username: string, bookmarkId: string): Promise => { - const data = {code: getAccessToken(username), id: bookmarkId}; - return axios.post(apiBase(`/private-api/bookmarks-delete`), data).then(resp => resp.data); -} +export const deleteBookmark = ( + username: string, + bookmarkId: string, +): Promise => { + const data = {code: getAccessToken(username), id: bookmarkId}; + return axios + .post(apiBase(`/private-api/bookmarks-delete`), data) + .then(resp => resp.data); +}; export interface Favorite { - _id: string, - account: string, - timestamp: number, + _id: string; + account: string; + timestamp: number; } export const getFavorites = (username: string): Promise => { - const data = {code: getAccessToken(username)}; - return axios.post(apiBase(`/private-api/favorites`), data).then(resp => resp.data); -} + const data = {code: getAccessToken(username)}; + return axios + .post(apiBase(`/private-api/favorites`), data) + .then(resp => resp.data); +}; -export const checkFavorite = (username: string, account: string): Promise => { - const data = {code: getAccessToken(username), account}; - return axios.post(apiBase(`/private-api/favorites-check`), data).then(resp => resp.data); -} +export const checkFavorite = ( + username: string, + account: string, +): Promise => { + const data = {code: getAccessToken(username), account}; + return axios + .post(apiBase(`/private-api/favorites-check`), data) + .then(resp => resp.data); +}; -export const addFavorite = (username: string, account: string): Promise<{ favorites: Favorite[] }> => { - const data = {code: getAccessToken(username), account}; - return axios.post(apiBase(`/private-api/favorites-add`), data).then(resp => resp.data); -} +export const addFavorite = ( + username: string, + account: string, +): Promise<{favorites: Favorite[]}> => { + const data = {code: getAccessToken(username), account}; + return axios + .post(apiBase(`/private-api/favorites-add`), data) + .then(resp => resp.data); +}; -export const deleteFavorite = (username: string, account: string): Promise => { - const data = {code: getAccessToken(username), account}; - return axios.post(apiBase(`/private-api/favorites-delete`), data).then(resp => resp.data); -} +export const deleteFavorite = ( + username: string, + account: string, +): Promise => { + const data = {code: getAccessToken(username), account}; + return axios + .post(apiBase(`/private-api/favorites-delete`), data) + .then(resp => resp.data); +}; export interface Fragment { - id: string; - title: string; - body: string; - created: string; - modified: string; + id: string; + title: string; + body: string; + created: string; + modified: string; } export const getFragments = (username: string): Promise => { - const data = {code: getAccessToken(username)}; - return axios.post(apiBase(`/private-api/fragments`), data).then(resp => resp.data); -} + const data = {code: getAccessToken(username)}; + return axios + .post(apiBase(`/private-api/fragments`), data) + .then(resp => resp.data); +}; -export const addFragment = (username: string, title: string, body: string): Promise<{ fragments: Fragment[] }> => { - const data = {code: getAccessToken(username), title, body}; - return axios.post(apiBase(`/private-api/fragments-add`), data).then(resp => resp.data); -} +export const addFragment = ( + username: string, + title: string, + body: string, +): Promise<{fragments: Fragment[]}> => { + const data = {code: getAccessToken(username), title, body}; + return axios + .post(apiBase(`/private-api/fragments-add`), data) + .then(resp => resp.data); +}; -export const updateFragment = (username: string, fragmentId: string, title: string, body: string): Promise => { - const data = {code: getAccessToken(username), id: fragmentId, title, body}; - return axios.post(apiBase(`/private-api/fragments-update`), data).then(resp => resp.data); -} +export const updateFragment = ( + username: string, + fragmentId: string, + title: string, + body: string, +): Promise => { + const data = {code: getAccessToken(username), id: fragmentId, title, body}; + return axios + .post(apiBase(`/private-api/fragments-update`), data) + .then(resp => resp.data); +}; -export const deleteFragment = (username: string, fragmentId: string): Promise => { - const data = {code: getAccessToken(username), id: fragmentId}; - return axios.post(apiBase(`/private-api/fragments-delete`), data).then(resp => resp.data); -} +export const deleteFragment = ( + username: string, + fragmentId: string, +): Promise => { + const data = {code: getAccessToken(username), id: fragmentId}; + return axios + .post(apiBase(`/private-api/fragments-delete`), data) + .then(resp => resp.data); +}; -export const getPoints = (username: string): Promise<{ - points: string; - unclaimed_points: string; +export const getPoints = ( + username: string, +): Promise<{ + points: string; + unclaimed_points: string; }> => { - if (window.usePrivate) { - const data = {username}; - return axios.post(apiBase(`/private-api/points`), data).then(resp => resp.data); - } - - return new Promise((resolve) => { - resolve({ - points: "0.000", - unclaimed_points: "0.000" - }) + if (window.usePrivate) { + const data = {username}; + return axios + .post(apiBase(`/private-api/points`), data) + .then(resp => resp.data); + } + + return new Promise(resolve => { + resolve({ + points: '0.000', + unclaimed_points: '0.000', }); -} - -export const getPointTransactions = (username: string, type?: number): Promise => { - if (window.usePrivate) { - const data = {username, type}; - return axios.post(apiBase(`/private-api/point-list`), data).then(resp => resp.data); - } + }); +}; - return new Promise((resolve) => { - resolve([]); - }); -} +export const getPointTransactions = ( + username: string, + type?: number, +): Promise => { + if (window.usePrivate) { + const data = {username, type}; + return axios + .post(apiBase(`/private-api/point-list`), data) + .then(resp => resp.data); + } + + return new Promise(resolve => { + resolve([]); + }); +}; export const claimPoints = (username: string): Promise => { - const data = {code: getAccessToken(username)}; - return axios.post(apiBase(`/private-api/points-claim`), data).then(resp => resp.data); -} + const data = {code: getAccessToken(username)}; + return axios + .post(apiBase(`/private-api/points-claim`), data) + .then(resp => resp.data); +}; -export const calcPoints = (username: string, amount: string): Promise<{ usd: number, estm: number }> => { - const data = {code: getAccessToken(username), amount}; - return axios.post(apiBase(`/private-api/points-calc`), data).then(resp => resp.data); -} +export const calcPoints = ( + username: string, + amount: string, +): Promise<{usd: number; estm: number}> => { + const data = {code: getAccessToken(username), amount}; + return axios + .post(apiBase(`/private-api/points-calc`), data) + .then(resp => resp.data); +}; export interface PromotePrice { - duration: number, - price: number + duration: number; + price: number; } export const getPromotePrice = (username: string): Promise => { - const data = {code: getAccessToken(username)}; - return axios.post(apiBase(`/private-api/promote-price`), data).then(resp => resp.data); -} + const data = {code: getAccessToken(username)}; + return axios + .post(apiBase(`/private-api/promote-price`), data) + .then(resp => resp.data); +}; -export const getPromotedPost = (username: string, author: string, permlink: string): Promise<{ author: string, permlink: string } | ''> => { - const data = {code: getAccessToken(username), author, permlink}; - return axios.post(apiBase(`/private-api/promoted-post`), data).then(resp => resp.data); -} +export const getPromotedPost = ( + username: string, + author: string, + permlink: string, +): Promise<{author: string; permlink: string} | ''> => { + const data = {code: getAccessToken(username), author, permlink}; + return axios + .post(apiBase(`/private-api/promoted-post`), data) + .then(resp => resp.data); +}; export const getBoostOptions = (username: string): Promise => { - const data = {code: getAccessToken(username)}; - return axios.post(apiBase(`/private-api/boost-options`), data).then(resp => resp.data); -} + const data = {code: getAccessToken(username)}; + return axios + .post(apiBase(`/private-api/boost-options`), data) + .then(resp => resp.data); +}; -export const getBoostedPost = (username: string, author: string, permlink: string): Promise<{ author: string, permlink: string } | ''> => { - const data = {code: getAccessToken(username), author, permlink}; - return axios.post(apiBase(`/private-api/boosted-post`), data).then(resp => resp.data); -} +export const getBoostedPost = ( + username: string, + author: string, + permlink: string, +): Promise<{author: string; permlink: string} | ''> => { + const data = {code: getAccessToken(username), author, permlink}; + return axios + .post(apiBase(`/private-api/boosted-post`), data) + .then(resp => resp.data); +}; export interface CommentHistoryListItem { - title: string; - body: string; - tags: string[]; - timestamp: string; - v: number; + title: string; + body: string; + tags: string[]; + timestamp: string; + v: number; } interface CommentHistory { - meta: { - count: number; - }, - list: CommentHistoryListItem[]; -} - -export const commentHistory = (author: string, permlink: string, onlyMeta: boolean = false): Promise => { - const data = {author, permlink, onlyMeta: onlyMeta ? '1' : ''}; - return axios.post(apiBase(`/private-api/comment-history`), data).then(resp => resp.data); -} + meta: { + count: number; + }; + list: CommentHistoryListItem[]; +} + +export const commentHistory = ( + author: string, + permlink: string, + onlyMeta: boolean = false, +): Promise => { + const data = {author, permlink, onlyMeta: onlyMeta ? '1' : ''}; + return axios + .post(apiBase(`/private-api/comment-history`), data) + .then(resp => resp.data); +}; export const getPromotedEntries = (): Promise => { - if (window.usePrivate) { - return axios.get(apiBase(`/private-api/promoted-entries`)).then((resp) => resp.data); - } - - return new Promise(resolve => resolve([])); -} - - - + if (window.usePrivate) { + return axios + .get(apiBase(`/private-api/promoted-entries`)) + .then(resp => resp.data); + } + return new Promise(resolve => resolve([])); +}; diff --git a/src/common/api/search-api.ts b/src/common/api/search-api.ts index b188f98b510..79982bdc57b 100644 --- a/src/common/api/search-api.ts +++ b/src/common/api/search-api.ts @@ -1,88 +1,126 @@ -import axios from "axios"; -import { dataLimit } from "./bridge"; +import axios from 'axios'; +import {dataLimit} from './bridge'; -import {apiBase} from "./helper"; +import {apiBase} from './helper'; export interface SearchResult { - id: number; - title: string; - title_marked: string | null; - category: string; - author: string; - permlink: string; - author_rep: number | string; - children: number; - body: string; - body_marked: string | null; - img_url: string; - created_at: string; - payout: number; - total_votes: number; - up_votes: number; - tags: string[]; - depth: number; - app: string; + id: number; + title: string; + title_marked: string | null; + category: string; + author: string; + permlink: string; + author_rep: number | string; + children: number; + body: string; + body_marked: string | null; + img_url: string; + created_at: string; + payout: number; + total_votes: number; + up_votes: number; + tags: string[]; + depth: number; + app: string; } export interface SearchResponse { - hits: number; - results: SearchResult[]; - scroll_id?: string; - took: number; + hits: number; + results: SearchResult[]; + scroll_id?: string; + took: number; } -export const search = (q: string, sort: string, hideLow: string, since?: string, scroll_id?: string): Promise => { - const data: { q: string, sort: string, hide_low: string, since?: string, scroll_id?: string } = {q, sort, hide_low: hideLow}; +export const search = ( + q: string, + sort: string, + hideLow: string, + since?: string, + scroll_id?: string, +): Promise => { + const data: { + q: string; + sort: string; + hide_low: string; + since?: string; + scroll_id?: string; + } = {q, sort, hide_low: hideLow}; - if (since) data.since = since; - if (scroll_id) data.scroll_id = scroll_id; + if (since) data.since = since; + if (scroll_id) data.scroll_id = scroll_id; - return axios.post(apiBase(`/search-api/search`), data).then(resp => resp.data); -} + return axios + .post(apiBase(`/search-api/search`), data) + .then(resp => resp.data); +}; export interface FriendSearchResult { - name: string; - full_name: string; - reputation: number + name: string; + full_name: string; + reputation: number; } -export const searchFollower = (following: string, q: string): Promise => { - const data = {following, q}; +export const searchFollower = ( + following: string, + q: string, +): Promise => { + const data = {following, q}; - return axios.post(apiBase(`/search-api/search-follower`), data).then(resp => resp.data); -} + return axios + .post(apiBase(`/search-api/search-follower`), data) + .then(resp => resp.data); +}; -export const searchFollowing = (follower: string, q: string): Promise => { - const data = {follower, q}; +export const searchFollowing = ( + follower: string, + q: string, +): Promise => { + const data = {follower, q}; - return axios.post(apiBase(`/search-api/search-following`), data).then(resp => resp.data); -} + return axios + .post(apiBase(`/search-api/search-following`), data) + .then(resp => resp.data); +}; export interface AccountSearchResult { - name: string; - full_name: string; - about: string; - reputation: number + name: string; + full_name: string; + about: string; + reputation: number; } -export const searchAccount = (q: string = "", limit: number = dataLimit, random: number = 1): Promise => { - const data = {q, limit, random}; +export const searchAccount = ( + q: string = '', + limit: number = dataLimit, + random: number = 1, +): Promise => { + const data = {q, limit, random}; - return axios.post(apiBase(`/search-api/search-account`), data).then(resp => resp.data); -} + return axios + .post(apiBase(`/search-api/search-account`), data) + .then(resp => resp.data); +}; export interface TagSearchResult { - tag: string; - repeat: number; + tag: string; + repeat: number; } -export const searchTag = (q: string = "", limit: number = dataLimit, random: number = 0): Promise => { - const data = {q, limit, random}; +export const searchTag = ( + q: string = '', + limit: number = dataLimit, + random: number = 0, +): Promise => { + const data = {q, limit, random}; - return axios.post(apiBase(`/search-api/search-tag`), data).then(resp => resp.data); -} + return axios + .post(apiBase(`/search-api/search-tag`), data) + .then(resp => resp.data); +}; export const searchPath = (username: string, q: string): Promise => { - const data = {q}; - return axios.post(apiBase(`/search-api/search-path`), data).then(resp => resp.data); -} + const data = {q}; + return axios + .post(apiBase(`/search-api/search-path`), data) + .then(resp => resp.data); +}; diff --git a/src/common/app.tsx b/src/common/app.tsx index 3a985d23260..8a25a0d6472 100644 --- a/src/common/app.tsx +++ b/src/common/app.tsx @@ -1,92 +1,250 @@ -import React, { useEffect } from "react"; -import {Route, Switch} from "react-router-dom"; +import React, {useEffect} from 'react'; +import {Route, Switch} from 'react-router-dom'; -import EntryIndexContainer from "./pages/entry-index"; -import ProfileContainer from "./pages/profile"; -import EntryContainer from "./pages/entry"; -import CommunitiesContainer, {CommunityCreateContainer, CommunityCreateHSContainer} from "./pages/communities"; -import CommunityContainer from "./pages/community"; -import DiscoverContainer from "./pages/discover"; -import {SearchPageContainer, SearchMorePageContainer} from "./pages/search"; -import WitnessesContainer from "./pages/witnesses"; -import {ProposalsIndexContainer, ProposalDetailContainer} from "./pages/proposals"; -import AuthContainer from "./pages/auth"; -import SubmitContainer from "./pages/submit"; -import MarketPage from "./pages/market"; -import SignUpContainer from "./pages/sign-up"; -import NotFound from "./components/404"; +import EntryIndexContainer from './pages/entry-index'; +import ProfileContainer from './pages/profile'; +import EntryContainer from './pages/entry'; +import CommunitiesContainer, { + CommunityCreateContainer, + CommunityCreateHSContainer, +} from './pages/communities'; +import CommunityContainer from './pages/community'; +import DiscoverContainer from './pages/discover'; +import {SearchPageContainer, SearchMorePageContainer} from './pages/search'; +import WitnessesContainer from './pages/witnesses'; +import { + ProposalsIndexContainer, + ProposalDetailContainer, +} from './pages/proposals'; +import AuthContainer from './pages/auth'; +import SubmitContainer from './pages/submit'; +import MarketPage from './pages/market'; +import SignUpContainer from './pages/sign-up'; +import NotFound from './components/404'; -import Tracker from "./tracker"; +import Tracker from './tracker'; import { - AboutPageContainer, - GuestPostPageContainer, - ContributePageContainer, - PrivacyPageContainer, - WhitePaperPageContainer, - TosPageContainer, - FaqPageContainer, - ContributorsPageContainer -} from "./pages/static"; + AboutPageContainer, + GuestPostPageContainer, + ContributePageContainer, + PrivacyPageContainer, + WhitePaperPageContainer, + TosPageContainer, + FaqPageContainer, + ContributorsPageContainer, +} from './pages/static'; -import routes from "./routes"; +import routes from './routes'; import * as ls from './util/local-storage'; -import i18n from "i18next"; -import { pageMapDispatchToProps, pageMapStateToProps } from "./pages/common"; -import { connect } from "react-redux"; +import i18n from 'i18next'; +import {pageMapDispatchToProps, pageMapStateToProps} from './pages/common'; +import {connect} from 'react-redux'; -const App = ({ setLang }: any) => { - useEffect(() => { - let pathname = window.location.pathname; - if(pathname !== '/faq'){ - const currentLang = ls.get("current-language"); - if(currentLang){ - setLang(currentLang); - i18n.changeLanguage(currentLang) - } - } - },[]); +const App = ({setLang}: any) => { + useEffect(() => { + let pathname = window.location.pathname; + if (pathname !== '/faq') { + const currentLang = ls.get('current-language'); + if (currentLang) { + setLang(currentLang); + i18n.changeLanguage(currentLang); + } + } + }, []); - return ( - <> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); }; export default connect(pageMapStateToProps, pageMapDispatchToProps)(App); diff --git a/src/common/components/404/index.tsx b/src/common/components/404/index.tsx index 9297fdf88ee..20de26c39ba 100644 --- a/src/common/components/404/index.tsx +++ b/src/common/components/404/index.tsx @@ -1,85 +1,96 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import {History} from "history"; +import {History} from 'history'; -import {Link} from "react-router-dom"; +import {Link} from 'react-router-dom'; -import Meta from "../meta"; -import { Global } from "../../store/global/types"; -import isElectron from "../../util/is-electron"; +import Meta from '../meta'; +import {Global} from '../../store/global/types'; +import isElectron from '../../util/is-electron'; -const logoCircle = require("../../img/logo-circle.svg"); +const logoCircle = require('../../img/logo-circle.svg'); interface Props { - history: History; - global: Global; + history: History; + global: Global; } interface State { - loaded: boolean + loaded: boolean; } export class NotFound extends Component { - state: State = { - loaded: false - } + state: State = { + loaded: false, + }; - componentDidMount() { - this.setState({loaded: true}); - } + componentDidMount() { + this.setState({loaded: true}); + } - goBack = () => { - const {history} = this.props; + goBack = () => { + const {history} = this.props; - history.goBack(); - }; + history.goBack(); + }; - render() { - const {loaded} = this.state; - if (!loaded) { - return '' - } - - const metaProps = { - title: "404", - }; - - const {history, global} = this.props; - - // @ts-ignore make ide happy. code compiles without error. - const entries = history.entries || {} - // @ts-ignore - const index = history.index || 0; - - const canGoBack = !!entries[index - 1]; - - return ( - <> - -
- Ecency -

This page doesn't exist.

-

- {canGoBack && { - e.preventDefault(); - this.goBack(); - }}>Back} - Home - New posts - Hot posts - Trending posts -

-
- - ); + render() { + const {loaded} = this.state; + if (!loaded) { + return ''; } + + const metaProps = { + title: '404', + }; + + const {history, global} = this.props; + + // @ts-ignore make ide happy. code compiles without error. + const entries = history.entries || {}; + // @ts-ignore + const index = history.index || 0; + + const canGoBack = !!entries[index - 1]; + + return ( + <> + +
+ Ecency +

This page doesn't exist.

+

+ {canGoBack && ( + { + e.preventDefault(); + this.goBack(); + }} + > + Back + + )} + Home + New posts + Hot posts + Trending posts +

+
+ + ); + } } export default (p: Props) => { - const props = { - history: p.history, - global: p.global - } + const props = { + history: p.history, + global: p.global, + }; - return -} + return ; +}; diff --git a/src/common/components/add-image-mobile/index.spec.tsx b/src/common/components/add-image-mobile/index.spec.tsx index 5a7c56a3f45..4c698e57ae3 100644 --- a/src/common/components/add-image-mobile/index.spec.tsx +++ b/src/common/components/add-image-mobile/index.spec.tsx @@ -1,86 +1,91 @@ -import * as React from "react"; +import * as React from 'react'; -import renderer from "react-test-renderer"; +import renderer from 'react-test-renderer'; -import {AddImage} from "./index"; +import {AddImage} from './index'; -import {activeUserInstance, globalInstance, allOver} from "../../helper/test-helper"; +import { + activeUserInstance, + globalInstance, + allOver, +} from '../../helper/test-helper'; -let TEST_MODE = 0 +let TEST_MODE = 0; -jest.mock("../../api/private-api", () => ({ - getImages: () => - new Promise((resolve) => { - if (TEST_MODE === 0) { - resolve([{ - "created": "Sat Aug 08 2020 12:50:55 GMT+0200 (Central European Summer Time)", - "url": "https://images.ecency.com/DQmSoXUteHvx1evzu27Xn5xbf6Mrn29L9Swn2yH2h4keuSQ/test-3.jpg", - "_id": "5f2e838fbaede01c77aa13a1", - "timestamp": 1596883855848 - }, { - "created": "Sat Aug 08 2020 12:57:47 GMT+0200 (Central European Summer Time)", - "url": "https://images.ecency.com/DQmYFWxjApdbdFVYdG6RMGh1vHQdAsAek38ePF3UsRbUYJv/test-10.jpg", - "_id": "5f2e852bbaede01c77aa13a2", - "timestamp": 1596884267539 - }, { - "created": "Sat Aug 08 2020 12:57:53 GMT+0200 (Central European Summer Time)", - "url": "https://images.ecency.com/DQmRJdj2PZmKHz3zZ31CUC6fpHuQaxsQCvpEp15rX3RpTFG/test-9.jpg", - "_id": "5f2e8531baede01c77aa13a3", - "timestamp": 1596884273695 - }, { - "created": "Sat Aug 08 2020 12:58:15 GMT+0200 (Central European Summer Time)", - "url": "https://images.ecency.com/DQmce1GReq9pwLiEHLsf2hKhAmouZnNSgnH95udH4g2VzAH/test-8.jpg", - "_id": "5f2e8547baede01c77aa13a4", - "timestamp": 1596884295409 - }]) - } +jest.mock('../../api/private-api', () => ({ + getImages: () => + new Promise(resolve => { + if (TEST_MODE === 0) { + resolve([ + { + created: + 'Sat Aug 08 2020 12:50:55 GMT+0200 (Central European Summer Time)', + url: 'https://images.ecency.com/DQmSoXUteHvx1evzu27Xn5xbf6Mrn29L9Swn2yH2h4keuSQ/test-3.jpg', + _id: '5f2e838fbaede01c77aa13a1', + timestamp: 1596883855848, + }, + { + created: + 'Sat Aug 08 2020 12:57:47 GMT+0200 (Central European Summer Time)', + url: 'https://images.ecency.com/DQmYFWxjApdbdFVYdG6RMGh1vHQdAsAek38ePF3UsRbUYJv/test-10.jpg', + _id: '5f2e852bbaede01c77aa13a2', + timestamp: 1596884267539, + }, + { + created: + 'Sat Aug 08 2020 12:57:53 GMT+0200 (Central European Summer Time)', + url: 'https://images.ecency.com/DQmRJdj2PZmKHz3zZ31CUC6fpHuQaxsQCvpEp15rX3RpTFG/test-9.jpg', + _id: '5f2e8531baede01c77aa13a3', + timestamp: 1596884273695, + }, + { + created: + 'Sat Aug 08 2020 12:58:15 GMT+0200 (Central European Summer Time)', + url: 'https://images.ecency.com/DQmce1GReq9pwLiEHLsf2hKhAmouZnNSgnH95udH4g2VzAH/test-8.jpg', + _id: '5f2e8547baede01c77aa13a4', + timestamp: 1596884295409, + }, + ]); + } - if (TEST_MODE === 1) { - resolve([]); - } - - }), + if (TEST_MODE === 1) { + resolve([]); + } + }), })); const defProps = { - global: globalInstance, - activeUser: {...activeUserInstance}, - onHide: () => { - }, - onPick: () => { - }, - onGallery: () => { - }, - onUpload: () => { - } + global: globalInstance, + activeUser: {...activeUserInstance}, + onHide: () => {}, + onPick: () => {}, + onGallery: () => {}, + onUpload: () => {}, }; - -it("(1) Default render.", async () => { - const component = renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); +it('(1) Default render.', async () => { + const component = renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); -it("(2) usePrivate = 1", async () => { - const props = { - ...defProps, - global: { - ...globalInstance, - usePrivate: false - } - } - const component = renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); +it('(2) usePrivate = 1', async () => { + const props = { + ...defProps, + global: { + ...globalInstance, + usePrivate: false, + }, + }; + const component = renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); -it("(3) Empty gallery.", async () => { - TEST_MODE = 1; +it('(3) Empty gallery.', async () => { + TEST_MODE = 1; - const component = renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + const component = renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); - - diff --git a/src/common/components/add-image-mobile/index.tsx b/src/common/components/add-image-mobile/index.tsx index 86544586134..f26af97e2d1 100644 --- a/src/common/components/add-image-mobile/index.tsx +++ b/src/common/components/add-image-mobile/index.tsx @@ -1,155 +1,179 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import {Button, Modal} from "react-bootstrap"; +import {Button, Modal} from 'react-bootstrap'; -import {proxifyImageSrc, setProxyBase} from "@ecency/render-helper"; +import {proxifyImageSrc, setProxyBase} from '@ecency/render-helper'; -import {Global} from "../../store/global/types"; -import {ActiveUser} from "../../store/active-user/types"; +import {Global} from '../../store/global/types'; +import {ActiveUser} from '../../store/active-user/types'; -import BaseComponent from "../base"; -import LinearProgress from "../linear-progress"; +import BaseComponent from '../base'; +import LinearProgress from '../linear-progress'; -import {getImages, UserImage} from "../../api/private-api"; +import {getImages, UserImage} from '../../api/private-api'; -import {error} from "../feedback"; +import {error} from '../feedback'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import _c from "../../util/fix-class-names" - -import defaults from "../../constants/defaults.json"; +import _c from '../../util/fix-class-names'; +import defaults from '../../constants/defaults.json'; setProxyBase(defaults.imageServer); interface Props { - global: Global; - activeUser: ActiveUser | null; - onHide: () => void; - onPick: (url: string) => void; - onGallery: () => void; - onUpload: () => void; + global: Global; + activeUser: ActiveUser | null; + onHide: () => void; + onPick: (url: string) => void; + onGallery: () => void; + onUpload: () => void; } interface State { - loading: boolean, - items: UserImage[] + loading: boolean; + items: UserImage[]; } export class AddImage extends BaseComponent { - state: State = { - loading: true, - items: [] - } + state: State = { + loading: true, + items: [], + }; - componentDidMount() { - this.fetch(); - } + componentDidMount() { + this.fetch(); + } - fetch = () => { - const {activeUser, global} = this.props; - - if (!global.usePrivate) { - this.stateSet({loading: false}); - return; - } - - this.stateSet({loading: true}); - getImages(activeUser?.username!).then(items => { - this.stateSet({items: this.sort(items).slice(0, 3), loading: false}); - }).catch(() => { - this.stateSet({loading: false}); - error(_t('g.server-error')); - }) - } + fetch = () => { + const {activeUser, global} = this.props; - sort = (items: UserImage[]) => - items.sort((a, b) => { - return new Date(b.created).getTime() > new Date(a.created).getTime() ? 1 : -1; - }); - - upload = () => { - this.props.onUpload(); + if (!global.usePrivate) { + this.stateSet({loading: false}); + return; } - gallery = () => { - this.props.onGallery() + this.stateSet({loading: true}); + getImages(activeUser?.username!) + .then(items => { + this.stateSet({items: this.sort(items).slice(0, 3), loading: false}); + }) + .catch(() => { + this.stateSet({loading: false}); + error(_t('g.server-error')); + }); + }; + + sort = (items: UserImage[]) => + items.sort((a, b) => { + return new Date(b.created).getTime() > new Date(a.created).getTime() + ? 1 + : -1; + }); + + upload = () => { + this.props.onUpload(); + }; + + gallery = () => { + this.props.onGallery(); + }; + + itemClicked = (item: UserImage) => { + this.props.onPick(item.url); + }; + + render() { + const {global} = this.props; + const {items, loading} = this.state; + + if (loading) { + return ( +
+ +
+ ); } - itemClicked = (item: UserImage) => { - this.props.onPick(item.url); + const btnGallery = global.usePrivate ? ( + + ) : null; + const btnUpload = ( + + ); + + if (items.length === 0) { + return ( +
+
+
{btnUpload}
+
+ ); } - render() { - const {global} = this.props; - const {items, loading} = this.state; - - if (loading) { - return
- -
- } - - const btnGallery = global.usePrivate ? : null; - const btnUpload = ; - - if (items.length === 0) { - return
-
-
- {btnUpload} -
-
- } - - return
-
- {items.length > 0 && ( - <> -
- {_t('add-image-mobile.recent-title')} -
-
- {items.map(item => { - const src = proxifyImageSrc(item.url, 600, 500, global.canUseWebp ? 'webp' : 'match') - return
{ - this.itemClicked(item); - }}/> - })} -
- - )} -
-
- {btnGallery} - {btnUpload} -
+ return ( +
+
+ {items.length > 0 && ( + <> +
+ {_t('add-image-mobile.recent-title')} +
+
+ {items.map(item => { + const src = proxifyImageSrc( + item.url, + 600, + 500, + global.canUseWebp ? 'webp' : 'match', + ); + return ( +
{ + this.itemClicked(item); + }} + /> + ); + })} +
+ + )}
- } +
+ {btnGallery} + {btnUpload} +
+
+ ); + } } - export default class AddImageDialog extends Component { - hide = () => { - const {onHide} = this.props; - onHide(); - } - - render() { - return ( - - - {_t('add-image-mobile.title')} - - - - - - ); - } + hide = () => { + const {onHide} = this.props; + onHide(); + }; + + render() { + return ( + + + {_t('add-image-mobile.title')} + + + + + + ); + } } diff --git a/src/common/components/add-image/index.spec.tsx b/src/common/components/add-image/index.spec.tsx index 811d449f162..fb7b5bc6932 100644 --- a/src/common/components/add-image/index.spec.tsx +++ b/src/common/components/add-image/index.spec.tsx @@ -1,20 +1,15 @@ -import React from "react"; +import React from 'react'; -import {AddImage} from "./index"; +import {AddImage} from './index'; -import TestRenderer from "react-test-renderer"; +import TestRenderer from 'react-test-renderer'; -it("(1) Default render", async () => { - const props = { - onHide: () => { - }, - onSubmit: () => { - } - }; +it('(1) Default render', async () => { + const props = { + onHide: () => {}, + onSubmit: () => {}, + }; - const renderer = TestRenderer.create(); - expect(renderer.toJSON()).toMatchSnapshot(); + const renderer = TestRenderer.create(); + expect(renderer.toJSON()).toMatchSnapshot(); }); - - - diff --git a/src/common/components/add-image/index.tsx b/src/common/components/add-image/index.tsx index 13657d5e133..597b7ac6bcb 100644 --- a/src/common/components/add-image/index.tsx +++ b/src/common/components/add-image/index.tsx @@ -1,102 +1,120 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import {Button, Form, FormControl, Modal} from "react-bootstrap"; +import {Button, Form, FormControl, Modal} from 'react-bootstrap'; -import {_t} from "../../i18n"; -import { handleInvalid, handleOnInput } from "../../util/input-util"; +import {_t} from '../../i18n'; +import {handleInvalid, handleOnInput} from '../../util/input-util'; interface Props { - onHide: () => void; - onSubmit: (text: string, link: string) => void; + onHide: () => void; + onSubmit: (text: string, link: string) => void; } interface State { - text: string; - link: string; + text: string; + link: string; } export class AddImage extends Component { - state: State = { - text: "", - link: "" - } + state: State = { + text: '', + link: '', + }; - form = React.createRef(); + form = React.createRef(); - textChanged = (e: React.ChangeEvent): void => { - this.setState({text: e.target.value}); - } + textChanged = ( + e: React.ChangeEvent, + ): void => { + this.setState({text: e.target.value}); + }; - linkChanged = (e: React.ChangeEvent): void => { - this.setState({link: e.target.value}); - } + linkChanged = ( + e: React.ChangeEvent, + ): void => { + this.setState({link: e.target.value}); + }; - render() { - const {text, link} = this.state; + render() { + const {text, link} = this.state; - return
-
{ - e.preventDefault(); - e.stopPropagation(); + return ( +
+ { + e.preventDefault(); + e.stopPropagation(); - if (!this.form.current?.checkValidity()) { - return; - } + if (!this.form.current?.checkValidity()) { + return; + } - const {text, link} = this.state; - const {onSubmit} = this.props; - onSubmit(text, link); - }}> - - handleInvalid(e, 'add-image.', 'validation-text')} - onInput={handleOnInput} - /> - - - handleInvalid(e, 'add-image.', 'validation-image')} - onInput={handleOnInput} - /> - -
- -
- -
- } + const {text, link} = this.state; + const {onSubmit} = this.props; + onSubmit(text, link); + }} + > + + + handleInvalid(e, 'add-image.', 'validation-text') + } + onInput={handleOnInput} + /> + + + + handleInvalid(e, 'add-image.', 'validation-image') + } + onInput={handleOnInput} + /> + +
+ +
+ +
+ ); + } } - export default class AddImageDialog extends Component { - hide = () => { - const {onHide} = this.props; - onHide(); - } + hide = () => { + const {onHide} = this.props; + onHide(); + }; - render() { - return ( - - - {_t('add-image.title')} - - - - - - ); - } + render() { + return ( + + + {_t('add-image.title')} + + + + + + ); + } } diff --git a/src/common/components/add-link/index.spec.tsx b/src/common/components/add-link/index.spec.tsx index 9c3e667cfba..c8f90701de0 100644 --- a/src/common/components/add-link/index.spec.tsx +++ b/src/common/components/add-link/index.spec.tsx @@ -1,20 +1,15 @@ -import React from "react"; +import React from 'react'; -import {AddLink} from "./index"; +import {AddLink} from './index'; -import TestRenderer from "react-test-renderer"; +import TestRenderer from 'react-test-renderer'; -it("(1) Default render", async () => { - const props = { - onHide: () => { - }, - onSubmit: () => { - } - }; +it('(1) Default render', async () => { + const props = { + onHide: () => {}, + onSubmit: () => {}, + }; - const renderer = TestRenderer.create(); - expect(renderer.toJSON()).toMatchSnapshot(); + const renderer = TestRenderer.create(); + expect(renderer.toJSON()).toMatchSnapshot(); }); - - - diff --git a/src/common/components/add-link/index.tsx b/src/common/components/add-link/index.tsx index 32897fa0712..b41565e6c91 100644 --- a/src/common/components/add-link/index.tsx +++ b/src/common/components/add-link/index.tsx @@ -1,118 +1,139 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import {Button, Form, FormControl, Modal} from "react-bootstrap"; +import {Button, Form, FormControl, Modal} from 'react-bootstrap'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import {readClipboard} from "../../util/clipboard"; +import {readClipboard} from '../../util/clipboard'; -import {parseUrl} from "../../util/misc"; -import { handleInvalid, handleOnInput } from "../../util/input-util"; +import {parseUrl} from '../../util/misc'; +import {handleInvalid, handleOnInput} from '../../util/input-util'; interface Props { - onHide: () => void; - onSubmit: (text: string, link: string) => void; + onHide: () => void; + onSubmit: (text: string, link: string) => void; } interface State { - text: string; - link: string; + text: string; + link: string; } export class AddLink extends Component { - state: State = { - text: "", - link: "https://" - } - - componentDidMount(){ - this.handleClipboard() - } - - handleClipboard = async() => { - const clipboard = await readClipboard(); - - if (clipboard && (clipboard.startsWith("https://") || clipboard.startsWith("http://"))) { - this.setState({ link: clipboard }) - } - } - - form = React.createRef(); - - textChanged = (e: React.ChangeEvent): void => { - this.setState({text: e.target.value}); - } - - linkChanged = (e: React.ChangeEvent): void => { - this.setState({link: e.target.value}); - } - - render() { - const {text, link} = this.state; - - return
-
{ - e.preventDefault(); - e.stopPropagation(); - - if (!this.form.current?.checkValidity()) { - return; - } - - const {text, link} = this.state; - const {onSubmit} = this.props; - onSubmit(text, link); - }}> - - handleInvalid(e, 'add-link.', 'validation-text')} - onInput={handleOnInput} - /> - - - handleInvalid(e, 'add-link.', 'validation-link')} - onInput={handleOnInput} - /> - -
- -
-
-
+ state: State = { + text: '', + link: 'https://', + }; + + componentDidMount() { + this.handleClipboard(); + } + + handleClipboard = async () => { + const clipboard = await readClipboard(); + + if ( + clipboard && + (clipboard.startsWith('https://') || clipboard.startsWith('http://')) + ) { + this.setState({link: clipboard}); } + }; + + form = React.createRef(); + + textChanged = ( + e: React.ChangeEvent, + ): void => { + this.setState({text: e.target.value}); + }; + + linkChanged = ( + e: React.ChangeEvent, + ): void => { + this.setState({link: e.target.value}); + }; + + render() { + const {text, link} = this.state; + + return ( +
+
{ + e.preventDefault(); + e.stopPropagation(); + + if (!this.form.current?.checkValidity()) { + return; + } + + const {text, link} = this.state; + const {onSubmit} = this.props; + onSubmit(text, link); + }} + > + + + handleInvalid(e, 'add-link.', 'validation-text') + } + onInput={handleOnInput} + /> + + + + handleInvalid(e, 'add-link.', 'validation-link') + } + onInput={handleOnInput} + /> + +
+ +
+
+
+ ); + } } - export default class AddLinkDialog extends Component { - hide = () => { - const {onHide} = this.props; - onHide(); - } - - render() { - return ( - - - {_t('add-link.title')} - - - - - - ); - } + hide = () => { + const {onHide} = this.props; + onHide(); + }; + + render() { + return ( + + + {_t('add-link.title')} + + + + + + ); + } } diff --git a/src/common/components/alink/index.tsx b/src/common/components/alink/index.tsx index 0df3da0d1ce..7831a8c97ef 100644 --- a/src/common/components/alink/index.tsx +++ b/src/common/components/alink/index.tsx @@ -1,18 +1,11 @@ import React from 'react'; -import { Link as LinkImport} from 'react-router-dom'; - -const Link = (oprops: any) => ( - /^https?:\/\//.test(oprops.to) - ? ( - - ) : ( - - ) +import {Link as LinkImport} from 'react-router-dom'; + +const Link = (oprops: any) => + /^https?:\/\//.test(oprops.to) ? ( + + ) : ( + ); export default Link; diff --git a/src/common/components/author-info-card/index.tsx b/src/common/components/author-info-card/index.tsx index 81b3b27e018..26e03887e53 100644 --- a/src/common/components/author-info-card/index.tsx +++ b/src/common/components/author-info-card/index.tsx @@ -1,16 +1,16 @@ -import React, { useEffect, useState } from "react"; -import { match } from "react-router"; -import { getAccountFull } from "../../api/hive"; -import accountReputation from "../../helper/account-reputation"; -import { PageProps } from "../../pages/common"; -import { Entry } from "../../store/entries/types"; -import truncate from "../../util/truncate"; -import BookmarkBtn from "../bookmark-btn"; -import FavoriteBtn from "../favorite-btn"; -import FollowControls from "../follow-controls"; -import ProfileLink from "../profile-link"; -import { Skeleton } from "../skeleton"; -import UserAvatar from "../user-avatar"; +import React, {useEffect, useState} from 'react'; +import {match} from 'react-router'; +import {getAccountFull} from '../../api/hive'; +import accountReputation from '../../helper/account-reputation'; +import {PageProps} from '../../pages/common'; +import {Entry} from '../../store/entries/types'; +import truncate from '../../util/truncate'; +import BookmarkBtn from '../bookmark-btn'; +import FavoriteBtn from '../favorite-btn'; +import FollowControls from '../follow-controls'; +import ProfileLink from '../profile-link'; +import {Skeleton} from '../skeleton'; +import UserAvatar from '../user-avatar'; interface MatchParams { category: string; @@ -23,100 +23,103 @@ interface Props extends PageProps { } const AuthorInfoCard = (props: Props) => { - const reputation = accountReputation(props?.entry?.author_reputation); - const { username } = props?.match?.params; - const author = username.replace("@", ""); + const {username} = props?.match?.params; + const author = username.replace('@', ''); const [authorInfo, setAuthorInfo] = useState({ - name: "", - about: "", + name: '', + about: '', }); - const [loading, setLoading] = useState(false) + const [loading, setLoading] = useState(false); let _isMounted = false; useEffect(() => { _isMounted = true; !props?.global?.isMobile && getAuthorInfo(); return () => { - _isMounted = false - } + _isMounted = false; + }; }, []); // For fetching authors about and display name information const getAuthorInfo = async () => { - setLoading(true) + setLoading(true); const _authorInfo = (await getAccountFull(author))?.profile; - _isMounted && setAuthorInfo({ - name: _authorInfo?.name || "", - about: _authorInfo?.about || _authorInfo?.location || "", - }); - setLoading(false) + _isMounted && + setAuthorInfo({ + name: _authorInfo?.name || '', + about: _authorInfo?.about || _authorInfo?.location || '', + }); + setLoading(false); }; - - return loading ? -
-
- - + + return loading ? ( +
+
+ + +
+ +
- - -
: ( -
-
- + ) : ( +
+
+ {ProfileLink({ ...props, username: props?.entry?.author, children: ( -
+
{UserAvatar({ ...props, username: props?.entry?.author, - size: "medium", + size: 'medium', })}
), })} - -
+ +
{ProfileLink({ ...props, username: props?.entry?.author, children: ( -
- +
+ - {!isNaN(reputation) && ({reputation})} + {!isNaN(reputation) && ( + ({reputation}) + )}
), })}
-
-
-
{authorInfo?.name}
+
+
+
{authorInfo?.name}
{authorInfo?.about && null !== authorInfo?.about && ( -

{`${truncate( +

{`${truncate( authorInfo?.about, - 130 + 130, )}`}

)}
-
+
{props?.entry?.author && ( )} diff --git a/src/common/components/base/index.tsx b/src/common/components/base/index.tsx index 1a34e4322eb..0698d6d4c53 100644 --- a/src/common/components/base/index.tsx +++ b/src/common/components/base/index.tsx @@ -1,15 +1,18 @@ -import {Component} from "react"; +import {Component} from 'react'; export default class BaseComponent

extends Component { - _mounted: boolean = true; + _mounted: boolean = true; - componentWillUnmount() { - this._mounted = false; - } + componentWillUnmount() { + this._mounted = false; + } - stateSet = (state: Pick, callback?: () => void): void => { - if (this._mounted) { - this.setState(state, callback); - } - }; + stateSet = ( + state: Pick, + callback?: () => void, + ): void => { + if (this._mounted) { + this.setState(state, callback); + } + }; } diff --git a/src/common/components/beneficiary-editor/index.spec.tsx b/src/common/components/beneficiary-editor/index.spec.tsx index a8c835922e0..865f11710c7 100644 --- a/src/common/components/beneficiary-editor/index.spec.tsx +++ b/src/common/components/beneficiary-editor/index.spec.tsx @@ -1,44 +1,46 @@ -import React from "react"; +import React from 'react'; -import BeneficiaryEditorDialog, {DialogBody} from "./index"; +import BeneficiaryEditorDialog, {DialogBody} from './index'; -import TestRenderer from "react-test-renderer"; +import TestRenderer from 'react-test-renderer'; const defProps = { - list: [{ - account: "foo", - weight: 1000 - }], - onAdd: () => { + list: [ + { + account: 'foo', + weight: 1000, }, - onDelete: () => { - } -} + ], + onAdd: () => {}, + onDelete: () => {}, +}; -it("(1) Default render", () => { - const props = { - ...defProps, - list: [], - } - const renderer = TestRenderer.create(); - expect(renderer.toJSON()).toMatchSnapshot(); +it('(1) Default render', () => { + const props = { + ...defProps, + list: [], + }; + const renderer = TestRenderer.create(); + expect(renderer.toJSON()).toMatchSnapshot(); }); -it("(2) Default render with author", () => { - const renderer = TestRenderer.create(); - expect(renderer.toJSON()).toMatchSnapshot(); +it('(2) Default render with author', () => { + const renderer = TestRenderer.create( + , + ); + expect(renderer.toJSON()).toMatchSnapshot(); }); -it("(3) DialogBody", () => { - const renderer = TestRenderer.create(); - expect(renderer.toJSON()).toMatchSnapshot(); +it('(3) DialogBody', () => { + const renderer = TestRenderer.create(); + expect(renderer.toJSON()).toMatchSnapshot(); }); -it("(4) DialogBody with author", () => { - const props = { - ...defProps, - author: "bar" - }; - const renderer = TestRenderer.create(); - expect(renderer.toJSON()).toMatchSnapshot(); +it('(4) DialogBody with author', () => { + const props = { + ...defProps, + author: 'bar', + }; + const renderer = TestRenderer.create(); + expect(renderer.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/beneficiary-editor/index.tsx b/src/common/components/beneficiary-editor/index.tsx index ab9b062ad66..ede03c45327 100644 --- a/src/common/components/beneficiary-editor/index.tsx +++ b/src/common/components/beneficiary-editor/index.tsx @@ -1,199 +1,256 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import {Button, Modal, Form, InputGroup, FormControl} from "react-bootstrap"; +import {Button, Modal, Form, InputGroup, FormControl} from 'react-bootstrap'; -import BaseComponent from "../base"; -import {error} from "../feedback"; +import BaseComponent from '../base'; +import {error} from '../feedback'; -import {BeneficiaryRoute} from "../../api/operations"; +import {BeneficiaryRoute} from '../../api/operations'; -import {getAccount} from "../../api/hive"; +import {getAccount} from '../../api/hive'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import {plusSvg, deleteForeverSvg, accountMultipleSvg} from "../../img/svg"; -import { handleInvalid, handleOnInput } from "../../util/input-util"; +import {plusSvg, deleteForeverSvg, accountMultipleSvg} from '../../img/svg'; +import {handleInvalid, handleOnInput} from '../../util/input-util'; interface Props { - author?: string; - list: BeneficiaryRoute[]; - onAdd: (item: BeneficiaryRoute) => void; - onDelete: (username: string) => void; + author?: string; + list: BeneficiaryRoute[]; + onAdd: (item: BeneficiaryRoute) => void; + onDelete: (username: string) => void; } interface DialogBodyState { - username: string, - percentage: string, - inProgress: boolean + username: string; + percentage: string; + inProgress: boolean; } export class DialogBody extends BaseComponent { - state: DialogBodyState = { - username: "", - percentage: "", - inProgress: false - } + state: DialogBodyState = { + username: '', + percentage: '', + inProgress: false, + }; - form = React.createRef(); + form = React.createRef(); - usernameChanged = (e: React.ChangeEvent): void => { - const username = e.target.value.trim().toLowerCase(); - this.stateSet({username}); - } + usernameChanged = ( + e: React.ChangeEvent, + ): void => { + const username = e.target.value.trim().toLowerCase(); + this.stateSet({username}); + }; - percentageChanged = (e: React.ChangeEvent): void => { - this.stateSet({percentage: e.target.value}); - } + percentageChanged = ( + e: React.ChangeEvent, + ): void => { + this.stateSet({percentage: e.target.value}); + }; - render() { - const {list, author} = this.props; - const {username, percentage, inProgress} = this.state; + render() { + const {list, author} = this.props; + const {username, percentage, inProgress} = this.state; - const used = list.reduce((a, b) => a + b.weight / 100, 0); - const available = 100 - used; + const used = list.reduce((a, b) => a + b.weight / 100, 0); + const available = 100 - used; - return

{ - e.preventDefault(); - e.stopPropagation(); + return ( + { + e.preventDefault(); + e.stopPropagation(); - if (!this.form.current?.checkValidity()) { - return; - } + if (!this.form.current?.checkValidity()) { + return; + } + + const {onAdd, list} = this.props; + const {username, percentage} = this.state; - const {onAdd, list} = this.props; - const {username, percentage} = this.state; + if (list.find(x => x.account === username) !== undefined) { + error(_t('beneficiary-editor.user-exists-error', {n: username})); + return; + } - if (list.find(x => x.account === username) !== undefined) { - error(_t("beneficiary-editor.user-exists-error", {n: username})); + this.stateSet({inProgress: true}); + getAccount(username) + .then(r => { + if (!r) { + error(_t('beneficiary-editor.user-error', {n: username})); return; - } - - this.stateSet({inProgress: true}); - getAccount(username).then((r) => { - if (!r) { - error(_t("beneficiary-editor.user-error", {n: username})); - return; - } - - onAdd({ - account: username, - weight: Number(percentage) * 100 - }); - - this.stateSet({username: "", percentage: ""}); - }).finally(() => this.stateSet({inProgress: false})); - }}> -
- - - - - - - - - {(author && available > 0) && ( - - - - - )} - - - - - - {list.map(x => { - return - - - - - })} - -
{_t("beneficiary-editor.username")}{_t("beneficiary-editor.reward")} -
{`@${author}`}{`${available}%`} -
- - - @ - - handleInvalid(e, 'beneficiary-editor.', 'validation-username')} - onInput={handleOnInput} - onChange={this.usernameChanged} - /> - - - - handleInvalid(e, 'beneficiary-editor.', 'validation-percentage')} - onInput={handleOnInput} - /> - - % - - -
{`@${x.account}`}{`${x.weight / 100}%`}
-
-
; - } + } + + onAdd({ + account: username, + weight: Number(percentage) * 100, + }); + + this.stateSet({username: '', percentage: ''}); + }) + .finally(() => this.stateSet({inProgress: false})); + }} + > +
+ + + + + + + + + {author && available > 0 && ( + + + + + )} + + + + + + {list.map(x => { + return ( + + + + + + ); + })} + +
{_t('beneficiary-editor.username')}{_t('beneficiary-editor.reward')} +
{`@${author}`}{`${available}%`} +
+ + + @ + + + handleInvalid( + e, + 'beneficiary-editor.', + 'validation-username', + ) + } + onInput={handleOnInput} + onChange={this.usernameChanged} + /> + + + + + handleInvalid( + e, + 'beneficiary-editor.', + 'validation-percentage', + ) + } + onInput={handleOnInput} + /> + + % + + + + +
{`@${x.account}`}{`${x.weight / 100}%`} + +
+
+ + ); + } } interface State { - visible: boolean + visible: boolean; } export default class BeneficiaryEditorDialog extends Component { - state: State = { - visible: false - } - - toggle = () => { - const {visible} = this.state; - this.setState({visible: !visible}); - } - - render() { - const {list} = this.props; - const {visible} = this.state; - - const btnLabel = list.length > 0 ? _t("beneficiary-editor.btn-label-n", {n: list.length}) : _t("beneficiary-editor.btn-label"); - - return <> - - - {visible && ( - - - {_t("beneficiary-editor.title")} - - - - - - - - - )} - ; - } + state: State = { + visible: false, + }; + + toggle = () => { + const {visible} = this.state; + this.setState({visible: !visible}); + }; + + render() { + const {list} = this.props; + const {visible} = this.state; + + const btnLabel = + list.length > 0 + ? _t('beneficiary-editor.btn-label-n', {n: list.length}) + : _t('beneficiary-editor.btn-label'); + + return ( + <> + + + {visible && ( + + + {_t('beneficiary-editor.title')} + + + + + + + + + )} + + ); + } } diff --git a/src/common/components/bookmark-btn/index.spec.tsx b/src/common/components/bookmark-btn/index.spec.tsx index ccb86f167a8..019307961c0 100644 --- a/src/common/components/bookmark-btn/index.spec.tsx +++ b/src/common/components/bookmark-btn/index.spec.tsx @@ -1,77 +1,74 @@ -import React from "react"; - -import {BookmarkBtn} from "./index"; - -import TestRenderer from "react-test-renderer"; - -import {entryInstance1, UiInstance, activeUserInstance, allOver} from "../../helper/test-helper"; - -let TEST_MODE = 0 - -jest.mock("../../api/private-api", () => ({ - getBookmarks: () => - new Promise((resolve) => { - if (TEST_MODE === 0) { - resolve([]); - } - - if (TEST_MODE === 1) { - resolve([ - { - _id: "314123", - author: "good-karma", - permlink: "awesome-hive", - } - ]); - } - }), +import React from 'react'; + +import {BookmarkBtn} from './index'; + +import TestRenderer from 'react-test-renderer'; + +import { + entryInstance1, + UiInstance, + activeUserInstance, + allOver, +} from '../../helper/test-helper'; + +let TEST_MODE = 0; + +jest.mock('../../api/private-api', () => ({ + getBookmarks: () => + new Promise(resolve => { + if (TEST_MODE === 0) { + resolve([]); + } + + if (TEST_MODE === 1) { + resolve([ + { + _id: '314123', + author: 'good-karma', + permlink: 'awesome-hive', + }, + ]); + } + }), })); const defProps = { - entry: {...entryInstance1}, - activeUser: null, - users: [], - ui: UiInstance, - setActiveUser: () => { - }, - updateActiveUser: () => { - }, - deleteUser: () => { - }, - toggleUIProp: () => { - - } + entry: {...entryInstance1}, + activeUser: null, + users: [], + ui: UiInstance, + setActiveUser: () => {}, + updateActiveUser: () => {}, + deleteUser: () => {}, + toggleUIProp: () => {}, }; -it("(1) No active user", () => { - const props = {...defProps}; - const renderer = TestRenderer.create(); - expect(renderer.toJSON()).toMatchSnapshot(); +it('(1) No active user', () => { + const props = {...defProps}; + const renderer = TestRenderer.create(); + expect(renderer.toJSON()).toMatchSnapshot(); }); +it('(2) Not bookmarked', async () => { + const props = { + ...defProps, + activeUser: {...activeUserInstance}, + }; -it("(2) Not bookmarked", async () => { - const props = { - ...defProps, - activeUser: {...activeUserInstance} - }; - - const component = TestRenderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + const component = TestRenderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); +it('(3) Bookmarked', async () => { + TEST_MODE = 1; -it("(3) Bookmarked", async () => { - - TEST_MODE = 1; - - const props = { - ...defProps, - activeUser: {...activeUserInstance} - }; + const props = { + ...defProps, + activeUser: {...activeUserInstance}, + }; - const component = TestRenderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + const component = TestRenderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/bookmark-btn/index.tsx b/src/common/components/bookmark-btn/index.tsx index 39e8a01c38f..8a2967700c1 100644 --- a/src/common/components/bookmark-btn/index.tsx +++ b/src/common/components/bookmark-btn/index.tsx @@ -1,150 +1,173 @@ -import React from "react"; -import {Entry} from "../../store/entries/types"; -import {ActiveUser} from "../../store/active-user/types"; +import React from 'react'; +import {Entry} from '../../store/entries/types'; +import {ActiveUser} from '../../store/active-user/types'; -import {getBookmarks, addBookmark, deleteBookmark} from "../../api/private-api"; +import {getBookmarks, addBookmark, deleteBookmark} from '../../api/private-api'; -import BaseComponent from "../base"; -import LoginRequired from "../login-required"; -import {User} from "../../store/users/types"; -import {ToggleType, UI} from "../../store/ui/types"; -import {Account} from "../../store/accounts/types"; -import Tooltip from "../tooltip"; -import {success, error} from "../feedback"; +import BaseComponent from '../base'; +import LoginRequired from '../login-required'; +import {User} from '../../store/users/types'; +import {ToggleType, UI} from '../../store/ui/types'; +import {Account} from '../../store/accounts/types'; +import Tooltip from '../tooltip'; +import {success, error} from '../feedback'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import _c from "../../util/fix-class-names"; +import _c from '../../util/fix-class-names'; -import {bookmarkOutlineSvg, bookmarkSvg} from "../../img/svg"; +import {bookmarkOutlineSvg, bookmarkSvg} from '../../img/svg'; export interface Props { - entry: Entry; - activeUser: ActiveUser | null; - users: User[]; - ui: UI; - setActiveUser: (username: string | null) => void; - updateActiveUser: (data?: Account) => void; - deleteUser: (username: string) => void; - toggleUIProp: (what: ToggleType) => void; + entry: Entry; + activeUser: ActiveUser | null; + users: User[]; + ui: UI; + setActiveUser: (username: string | null) => void; + updateActiveUser: (data?: Account) => void; + deleteUser: (username: string) => void; + toggleUIProp: (what: ToggleType) => void; } export interface State { - bookmarkId: string | null; - inProgress: boolean + bookmarkId: string | null; + inProgress: boolean; } export class BookmarkBtn extends BaseComponent { - state: State = { - bookmarkId: null, - inProgress: false + state: State = { + bookmarkId: null, + inProgress: false, + }; + + componentDidMount() { + this.detect(); + } + + componentDidUpdate(prevProps: Readonly) { + const {activeUser, entry} = this.props; + if ( + // active user changed + activeUser?.username !== prevProps.activeUser?.username || + // or entry changed + !( + entry.author === prevProps.entry.author && + entry.permlink === prevProps.entry.permlink + ) + ) { + this.detect(); } + } - componentDidMount() { - this.detect(); - } - - componentDidUpdate(prevProps: Readonly) { - const {activeUser, entry} = this.props; - if ( - // active user changed - (activeUser?.username !== prevProps.activeUser?.username) || - // or entry changed - (!(entry.author === prevProps.entry.author && entry.permlink === prevProps.entry.permlink)) - ) { - this.detect(); - } + detect = () => { + const {entry, activeUser} = this.props; + if (!activeUser) { + this.stateSet({bookmarked: false}); + return; } - detect = () => { - const {entry, activeUser} = this.props; - if (!activeUser) { - this.stateSet({bookmarked: false}); - return; + this.stateSet({inProgress: true}); + getBookmarks(activeUser.username) + .then(r => { + const bookmark = r.find( + x => x.author === entry.author && x.permlink == entry.permlink, + ); + if (bookmark) { + this.stateSet({bookmarkId: bookmark._id}); + } else { + this.stateSet({bookmarkId: null}); } - - this.stateSet({inProgress: true}); - getBookmarks(activeUser.username).then(r => { - const bookmark = r.find(x => x.author === entry.author && x.permlink == entry.permlink); - if (bookmark) { - this.stateSet({bookmarkId: bookmark._id}); - } else { - this.stateSet({bookmarkId: null}); - } - }).finally(() => this.stateSet({inProgress: false})); + }) + .finally(() => this.stateSet({inProgress: false})); + }; + + add = () => { + const {activeUser, entry} = this.props; + this.stateSet({inProgress: true}); + addBookmark(activeUser?.username!, entry.author, entry.permlink) + .then(() => { + this.detect(); + success(_t('bookmark-btn.added')); + }) + .catch(() => error(_t('g.server-error'))) + .finally(() => this.stateSet({inProgress: false})); + }; + + delete = () => { + const {activeUser} = this.props; + const {bookmarkId} = this.state; + + if (!bookmarkId) { + return; } - add = () => { - const {activeUser, entry} = this.props; - this.stateSet({inProgress: true}) - addBookmark(activeUser?.username!, entry.author, entry.permlink) - .then(() => { - this.detect(); - success(_t('bookmark-btn.added')); - }) - .catch(() => error(_t('g.server-error'))) - .finally(() => this.stateSet({inProgress: false})) + this.stateSet({inProgress: true}); + deleteBookmark(activeUser?.username!, bookmarkId) + .then(() => { + this.detect(); + success(_t('bookmark-btn.deleted')); + }) + .catch(() => error(_t('g.server-error'))) + .finally(() => this.stateSet({inProgress: false})); + }; + + render() { + const {activeUser} = this.props; + + if (!activeUser) { + return LoginRequired({ + ...this.props, + children: ( +
+ + {bookmarkOutlineSvg} + +
+ ), + }); } - delete = () => { - const {activeUser} = this.props; - const {bookmarkId} = this.state; - - if (!bookmarkId) { - return; - } - - this.stateSet({inProgress: true}); - deleteBookmark(activeUser?.username!, bookmarkId) - .then(() => { - this.detect(); - success(_t('bookmark-btn.deleted')); - }) - .catch(() => error(_t('g.server-error'))) - .finally(() => this.stateSet({inProgress: false})) + const {bookmarkId, inProgress} = this.state; + + if (bookmarkId) { + return ( +
+ + {bookmarkSvg} + +
+ ); } - render() { - const {activeUser} = this.props; - - if (!activeUser) { - return LoginRequired({ - ...this.props, - children:
- {bookmarkOutlineSvg} -
- }) - } - - const {bookmarkId, inProgress} = this.state; - - if (bookmarkId) { - return ( -
- {bookmarkSvg} -
- ); - } - - return ( -
- {bookmarkOutlineSvg} -
- ); - } + return ( +
+ + {bookmarkOutlineSvg} + +
+ ); + } } export default (p: Props) => { - const props: Props = { - entry: p.entry, - activeUser: p.activeUser, - users: p.users, - ui: p.ui, - setActiveUser: p.setActiveUser, - updateActiveUser: p.updateActiveUser, - deleteUser: p.deleteUser, - toggleUIProp: p.toggleUIProp - } - - return -} + const props: Props = { + entry: p.entry, + activeUser: p.activeUser, + users: p.users, + ui: p.ui, + setActiveUser: p.setActiveUser, + updateActiveUser: p.updateActiveUser, + deleteUser: p.deleteUser, + toggleUIProp: p.toggleUIProp, + }; + + return ; +}; diff --git a/src/common/components/bookmarks/index.spec.tsx b/src/common/components/bookmarks/index.spec.tsx index c139291a45c..68a8a92c8c0 100644 --- a/src/common/components/bookmarks/index.spec.tsx +++ b/src/common/components/bookmarks/index.spec.tsx @@ -1,141 +1,151 @@ import React from 'react'; -import renderer from "react-test-renderer"; +import renderer from 'react-test-renderer'; -import {createBrowserHistory} from "history"; +import {createBrowserHistory} from 'history'; import {Bookmarks, Favorites} from './index'; -import {globalInstance, activeUserInstance, allOver} from "../../helper/test-helper"; - -let TEST_MODE = 0 - -jest.mock("../../api/private-api", () => ({ - getBookmarks: () => - new Promise((resolve) => { - if (TEST_MODE === 0) { - resolve([]); - } - - if (TEST_MODE === 1) { - resolve([{ - "author": "tarazkp", - "permlink": "she-ll-be-apples", - "created": "Wed Aug 12 2020 15:31:29 GMT+0200 (Central European Summer Time)", - "_id": "5f33ef31baede01c77aa1809", - "timestamp": 1597239089185 - }, { - "author": "bluemoon", - "permlink": "on-an-island", - "created": "Wed Aug 12 2020 16:18:50 GMT+0200 (Central European Summer Time)", - "_id": "5f33fa4abaede01c77aa1825", - "timestamp": 1597241930103 - }, { - "author": "acidyo", - "permlink": "dissolution-f2p-crypto-futuristic-fps", - "created": "Wed Aug 12 2020 16:19:29 GMT+0200 (Central European Summer Time)", - "_id": "5f33fa71baede01c77aa1826", - "timestamp": 1597241969917 - }, { - "author": "johnvibes", - "permlink": "after-multiple-ft-hood-soldiers-murdered-2-more-soldiers-arrested-in-child-trafficking-sting", - "created": "Wed Aug 12 2020 16:20:37 GMT+0200 (Central European Summer Time)", - "_id": "5f33fab5baede01c77aa182c", - "timestamp": 1597242037781 - }, { - "author": "kommienezuspadt", - "permlink": "iexnncxb", - "created": "Wed Aug 12 2020 16:20:45 GMT+0200 (Central European Summer Time)", - "_id": "5f33fabdbaede01c77aa182d", - "timestamp": 1597242045183 - }]) - } - }), - - getFavorites: () => - new Promise((resolve) => { - if (TEST_MODE === 0) { - resolve([]); - } - - if (TEST_MODE === 1) { - resolve([ - { - "account": "kommienezuspadt", - "_id": "5f355a2cbaede01c77aa1954", - "timestamp": 1597332012551 - }, { - "account": "purepinay", - "_id": "5f35622bbaede01c77aa1966", - "timestamp": 1597334059308 - }]) - } - }) +import { + globalInstance, + activeUserInstance, + allOver, +} from '../../helper/test-helper'; + +let TEST_MODE = 0; + +jest.mock('../../api/private-api', () => ({ + getBookmarks: () => + new Promise(resolve => { + if (TEST_MODE === 0) { + resolve([]); + } + + if (TEST_MODE === 1) { + resolve([ + { + author: 'tarazkp', + permlink: 'she-ll-be-apples', + created: + 'Wed Aug 12 2020 15:31:29 GMT+0200 (Central European Summer Time)', + _id: '5f33ef31baede01c77aa1809', + timestamp: 1597239089185, + }, + { + author: 'bluemoon', + permlink: 'on-an-island', + created: + 'Wed Aug 12 2020 16:18:50 GMT+0200 (Central European Summer Time)', + _id: '5f33fa4abaede01c77aa1825', + timestamp: 1597241930103, + }, + { + author: 'acidyo', + permlink: 'dissolution-f2p-crypto-futuristic-fps', + created: + 'Wed Aug 12 2020 16:19:29 GMT+0200 (Central European Summer Time)', + _id: '5f33fa71baede01c77aa1826', + timestamp: 1597241969917, + }, + { + author: 'johnvibes', + permlink: + 'after-multiple-ft-hood-soldiers-murdered-2-more-soldiers-arrested-in-child-trafficking-sting', + created: + 'Wed Aug 12 2020 16:20:37 GMT+0200 (Central European Summer Time)', + _id: '5f33fab5baede01c77aa182c', + timestamp: 1597242037781, + }, + { + author: 'kommienezuspadt', + permlink: 'iexnncxb', + created: + 'Wed Aug 12 2020 16:20:45 GMT+0200 (Central European Summer Time)', + _id: '5f33fabdbaede01c77aa182d', + timestamp: 1597242045183, + }, + ]); + } + }), + + getFavorites: () => + new Promise(resolve => { + if (TEST_MODE === 0) { + resolve([]); + } + + if (TEST_MODE === 1) { + resolve([ + { + account: 'kommienezuspadt', + _id: '5f355a2cbaede01c77aa1954', + timestamp: 1597332012551, + }, + { + account: 'purepinay', + _id: '5f35622bbaede01c77aa1966', + timestamp: 1597334059308, + }, + ]); + } + }), })); it('(1) Bookmarks - No data.', async () => { - const props = { - history: createBrowserHistory(), - global: globalInstance, - activeUser: {...activeUserInstance}, - onHide: () => { - } - }; - - const component = await renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + const props = { + history: createBrowserHistory(), + global: globalInstance, + activeUser: {...activeUserInstance}, + onHide: () => {}, + }; + + const component = await renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); it('(2) Bookmarks - Test with data.', async () => { - TEST_MODE = 1; - - const props = { - history: createBrowserHistory(), - global: globalInstance, - activeUser: {...activeUserInstance}, - onHide: () => { - } - }; - - const component = await renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + TEST_MODE = 1; + + const props = { + history: createBrowserHistory(), + global: globalInstance, + activeUser: {...activeUserInstance}, + onHide: () => {}, + }; + + const component = await renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); it('(3) Favorites - No data.', async () => { - TEST_MODE = 0; - - const props = { - history: createBrowserHistory(), - global: globalInstance, - activeUser: {...activeUserInstance}, - addAccount: () => { - }, - onHide: () => { - } - }; - - const component = await renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + TEST_MODE = 0; + + const props = { + history: createBrowserHistory(), + global: globalInstance, + activeUser: {...activeUserInstance}, + addAccount: () => {}, + onHide: () => {}, + }; + + const component = await renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); - it('(4) Favorites - Test with data.', async () => { - TEST_MODE = 1; - - const props = { - history: createBrowserHistory(), - global: globalInstance, - activeUser: {...activeUserInstance}, - addAccount: () => { - }, - onHide: () => { - } - }; - - const component = await renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + TEST_MODE = 1; + + const props = { + history: createBrowserHistory(), + global: globalInstance, + activeUser: {...activeUserInstance}, + addAccount: () => {}, + onHide: () => {}, + }; + + const component = await renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); - diff --git a/src/common/components/bookmarks/index.tsx b/src/common/components/bookmarks/index.tsx index 1e2d077cd16..2364a28d617 100644 --- a/src/common/components/bookmarks/index.tsx +++ b/src/common/components/bookmarks/index.tsx @@ -1,234 +1,274 @@ -import React, {Component} from "react"; -import {Modal} from "react-bootstrap"; +import React, {Component} from 'react'; +import {Modal} from 'react-bootstrap'; -import {History} from "history"; +import {History} from 'history'; -import {Global} from "../../store/global/types"; -import {ActiveUser} from "../../store/active-user/types"; -import {Account} from "../../store/accounts/types"; +import {Global} from '../../store/global/types'; +import {ActiveUser} from '../../store/active-user/types'; +import {Account} from '../../store/accounts/types'; -import BaseComponent from "../base"; -import EntryLink from "../entry-link"; -import ProfileLink from "../profile-link"; -import UserAvatar from "../user-avatar"; -import LinearProgress from "../linear-progress"; -import {error} from "../feedback"; +import BaseComponent from '../base'; +import EntryLink from '../entry-link'; +import ProfileLink from '../profile-link'; +import UserAvatar from '../user-avatar'; +import LinearProgress from '../linear-progress'; +import {error} from '../feedback'; -import {getBookmarks, Bookmark, getFavorites, Favorite} from "../../api/private-api"; - -import {_t} from "../../i18n"; +import { + getBookmarks, + Bookmark, + getFavorites, + Favorite, +} from '../../api/private-api'; +import {_t} from '../../i18n'; interface BookmarksProps { - history: History; - global: Global; - activeUser: ActiveUser | null; - onHide: () => void; + history: History; + global: Global; + activeUser: ActiveUser | null; + onHide: () => void; } interface BookmarksState { - loading: boolean, - items: Bookmark[] + loading: boolean; + items: Bookmark[]; } export class Bookmarks extends BaseComponent { - state: BookmarksState = { - loading: true, - items: [] - } - - componentDidMount() { - this.fetch(); - } - - fetch = () => { - const {activeUser} = this.props; - - this.stateSet({loading: true}); - getBookmarks(activeUser?.username!).then(items => { - const sorted = items.sort((a, b) => b.timestamp > a.timestamp ? 1 : -1); - this.stateSet({items: sorted, loading: false}); - }).catch(() => { - this.stateSet({loading: false}); - error(_t('g.server-error')); - }) - } - - - render() { - const {items, loading} = this.state; - - return
- {loading && } - {items.length > 0 && ( -
-
- {items.map(item => { - return
- {EntryLink({ - ...this.props, - entry: { - category: "foo", - author: item.author, - permlink: item.permlink, - }, - afterClick: () => { - const {onHide} = this.props; - onHide(); - }, - children:
- {UserAvatar({ - ...this.props, - username: item.author, - size: "medium" - })} -
- {item.author} - {item.permlink} -
-
- })} -
- })} -
-
- )} - {(!loading && items.length === 0) && ( -
- {_t('g.empty-list')} -
- )} -
- } + state: BookmarksState = { + loading: true, + items: [], + }; + + componentDidMount() { + this.fetch(); + } + + fetch = () => { + const {activeUser} = this.props; + + this.stateSet({loading: true}); + getBookmarks(activeUser?.username!) + .then(items => { + const sorted = items.sort((a, b) => + b.timestamp > a.timestamp ? 1 : -1, + ); + this.stateSet({items: sorted, loading: false}); + }) + .catch(() => { + this.stateSet({loading: false}); + error(_t('g.server-error')); + }); + }; + + render() { + const {items, loading} = this.state; + + return ( +
+ {loading && } + {items.length > 0 && ( +
+
+ {items.map(item => { + return ( +
+ {EntryLink({ + ...this.props, + entry: { + category: 'foo', + author: item.author, + permlink: item.permlink, + }, + afterClick: () => { + const {onHide} = this.props; + onHide(); + }, + children: ( +
+ {UserAvatar({ + ...this.props, + username: item.author, + size: 'medium', + })} +
+ + {item.author} + + {item.permlink} +
+
+ ), + })} +
+ ); + })} +
+
+ )} + {!loading && items.length === 0 && ( +
{_t('g.empty-list')}
+ )} +
+ ); + } } - interface FavoritesProps { - history: History; - global: Global; - activeUser: ActiveUser | null; - addAccount: (data: Account) => void; - onHide: () => void; + history: History; + global: Global; + activeUser: ActiveUser | null; + addAccount: (data: Account) => void; + onHide: () => void; } interface FavoritesState { - loading: boolean, - items: Favorite[] + loading: boolean; + items: Favorite[]; } export class Favorites extends BaseComponent { - state: FavoritesState = { - loading: true, - items: [] - } - - componentDidMount() { - this.fetch(); - } - - fetch = () => { - const {activeUser} = this.props; - - this.stateSet({loading: true}); - getFavorites(activeUser?.username!).then(items => { - const sorted = items.sort((a, b) => b.timestamp > a.timestamp ? 1 : -1); - this.stateSet({items: sorted, loading: false}); - }).catch(() => { - this.stateSet({loading: false}); - error(_t('g.server-error')); - }) - } - - render() { - const {items, loading} = this.state; - - return
- {loading && } - {items.length > 0 && ( -
-
- {items.map(item => { - return
- {ProfileLink({ - ...this.props, - username: item.account, - afterClick: () => { - const {onHide} = this.props; - onHide(); - }, - children:
- {UserAvatar({ - ...this.props, - username: item.account, - size: "medium" - })} -
- {item.account} -
-
- })} -
- })} -
-
- )} - {(!loading && items.length === 0) && ( -
- {_t('g.empty-list')} -
- )} -
- } + state: FavoritesState = { + loading: true, + items: [], + }; + + componentDidMount() { + this.fetch(); + } + + fetch = () => { + const {activeUser} = this.props; + + this.stateSet({loading: true}); + getFavorites(activeUser?.username!) + .then(items => { + const sorted = items.sort((a, b) => + b.timestamp > a.timestamp ? 1 : -1, + ); + this.stateSet({items: sorted, loading: false}); + }) + .catch(() => { + this.stateSet({loading: false}); + error(_t('g.server-error')); + }); + }; + + render() { + const {items, loading} = this.state; + + return ( +
+ {loading && } + {items.length > 0 && ( +
+
+ {items.map(item => { + return ( +
+ {ProfileLink({ + ...this.props, + username: item.account, + afterClick: () => { + const {onHide} = this.props; + onHide(); + }, + children: ( +
+ {UserAvatar({ + ...this.props, + username: item.account, + size: 'medium', + })} +
+ + {item.account} + +
+
+ ), + })} +
+ ); + })} +
+
+ )} + {!loading && items.length === 0 && ( +
{_t('g.empty-list')}
+ )} +
+ ); + } } - interface DialogProps { - history: History; - global: Global; - activeUser: ActiveUser | null; - addAccount: (data: Account) => void; - onHide: () => void; + history: History; + global: Global; + activeUser: ActiveUser | null; + addAccount: (data: Account) => void; + onHide: () => void; } -type DialogSection = "bookmarks" | "favorites" +type DialogSection = 'bookmarks' | 'favorites'; interface DialogState { - section: DialogSection + section: DialogSection; } -export default class BookmarksDialog extends Component { - state: DialogState = { - section: "bookmarks" - } - - changeSection = (section: DialogSection) => { - this.setState({section}); - } - - hide = () => { - const {onHide} = this.props; - onHide(); - } - - render() { - const {section} = this.state; - - return ( - - - -
-
{ - this.changeSection("bookmarks"); - }}>{_t("bookmarks.title")}
-
{ - this.changeSection("favorites"); - }}>{_t("favorites.title")}
-
- {section === "bookmarks" && } - {section === "favorites" && } -
-
- ); - } +export default class BookmarksDialog extends Component< + DialogProps, + DialogState +> { + state: DialogState = { + section: 'bookmarks', + }; + + changeSection = (section: DialogSection) => { + this.setState({section}); + }; + + hide = () => { + const {onHide} = this.props; + onHide(); + }; + + render() { + const {section} = this.state; + + return ( + + + +
+
{ + this.changeSection('bookmarks'); + }} + > + {_t('bookmarks.title')} +
+
{ + this.changeSection('favorites'); + }} + > + {_t('favorites.title')} +
+
+ {section === 'bookmarks' && } + {section === 'favorites' && } +
+
+ ); + } } diff --git a/src/common/components/boost/index.spec.tsx b/src/common/components/boost/index.spec.tsx index 5ec18d18b2a..86e5730a9f7 100644 --- a/src/common/components/boost/index.spec.tsx +++ b/src/common/components/boost/index.spec.tsx @@ -1,112 +1,104 @@ -import React from "react"; +import React from 'react'; -import {Boost} from "./index"; +import {Boost} from './index'; -import TestRenderer from "react-test-renderer"; +import TestRenderer from 'react-test-renderer'; -import {dynamicPropsIntance1, globalInstance, entryInstance1, allOver} from "../../helper/test-helper"; +import { + dynamicPropsIntance1, + globalInstance, + entryInstance1, + allOver, +} from '../../helper/test-helper'; -jest.mock("../../api/private-api", () => ({ - getBoostOptions: () => - new Promise((resolve) => { - resolve([150, 200, 250, 300, 350, 400, 450, 500, 550]); - }), +jest.mock('../../api/private-api', () => ({ + getBoostOptions: () => + new Promise(resolve => { + resolve([150, 200, 250, 300, 350, 400, 450, 500, 550]); + }), })); +it('(1) Default render', async () => { + const props = { + global: globalInstance, + dynamicProps: dynamicPropsIntance1, + activeUser: { + username: 'foo', + data: { + name: 'foo', + balance: '12.234 HIVE', + hbd_balance: '4321.212', + savings_balance: '2123.000 HIVE', + }, + points: { + points: '500.000', + uPoints: '0.000', + }, + }, + signingKey: '', + updateActiveUser: () => {}, + setSigningKey: () => {}, + onHide: () => {}, + }; -it("(1) Default render", async () => { - const props = { - global: globalInstance, - dynamicProps: dynamicPropsIntance1, - activeUser: { - username: 'foo', - data: { - name: 'foo', - balance: '12.234 HIVE', - hbd_balance: '4321.212', - savings_balance: '2123.000 HIVE' - }, - points: { - points: "500.000", - uPoints: "0.000" - } - }, - signingKey: '', - updateActiveUser: () => { - }, - setSigningKey: () => { - }, - onHide: () => { - } - }; - - const renderer = await TestRenderer.create(); - await allOver(); - expect(renderer.toJSON()).toMatchSnapshot(); + const renderer = await TestRenderer.create(); + await allOver(); + expect(renderer.toJSON()).toMatchSnapshot(); }); +it('(2) Insufficient Funds', async () => { + const props = { + global: globalInstance, + dynamicProps: dynamicPropsIntance1, + activeUser: { + username: 'foo', + data: { + name: 'foo', + balance: '12.234 HIVE', + hbd_balance: '4321.212', + savings_balance: '2123.000 HIVE', + }, + points: { + points: '10.000', + uPoints: '0.000', + }, + }, + signingKey: '', + updateActiveUser: () => {}, + setSigningKey: () => {}, + onHide: () => {}, + }; -it("(2) Insufficient Funds", async () => { - const props = { - global: globalInstance, - dynamicProps: dynamicPropsIntance1, - activeUser: { - username: 'foo', - data: { - name: 'foo', - balance: '12.234 HIVE', - hbd_balance: '4321.212', - savings_balance: '2123.000 HIVE' - }, - points: { - points: "10.000", - uPoints: "0.000" - } - }, - signingKey: '', - updateActiveUser: () => { - }, - setSigningKey: () => { - }, - onHide: () => { - - } - }; - - const renderer = await TestRenderer.create(); - await allOver(); - expect(renderer.toJSON()).toMatchSnapshot(); + const renderer = await TestRenderer.create(); + await allOver(); + expect(renderer.toJSON()).toMatchSnapshot(); }); +it('(2) With entry', async () => { + const props = { + global: globalInstance, + dynamicProps: dynamicPropsIntance1, + activeUser: { + username: 'foo', + data: { + name: 'foo', + balance: '12.234 HIVE', + hbd_balance: '4321.212', + savings_balance: '2123.000 HIVE', + }, + points: { + points: '500.000', + uPoints: '0.000', + }, + }, + signingKey: '', + entry: entryInstance1, + updateActiveUser: () => {}, + setSigningKey: () => {}, + onHide: () => {}, + }; -it("(2) With entry", async () => { - const props = { - global: globalInstance, - dynamicProps: dynamicPropsIntance1, - activeUser: { - username: 'foo', - data: { - name: 'foo', - balance: '12.234 HIVE', - hbd_balance: '4321.212', - savings_balance: '2123.000 HIVE' - }, - points: { - points: "500.000", - uPoints: "0.000" - } - }, - signingKey: '', - entry: entryInstance1, - updateActiveUser: () => { - }, - setSigningKey: () => { - }, - onHide: () => { - } - }; - - const renderer = await TestRenderer.create(); - await allOver(); - expect(renderer.toJSON()).toMatchSnapshot(); + const renderer = await TestRenderer.create(); + await allOver(); + expect(renderer.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/boost/index.tsx b/src/common/components/boost/index.tsx index 7d3d320d2fa..2203d6aab98 100644 --- a/src/common/components/boost/index.tsx +++ b/src/common/components/boost/index.tsx @@ -1,375 +1,470 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import isEqual from "react-fast-compare"; +import isEqual from 'react-fast-compare'; -import {Button, Col, Form, FormControl, Modal, Row} from "react-bootstrap"; +import {Button, Col, Form, FormControl, Modal, Row} from 'react-bootstrap'; -import {PrivateKey} from "@hiveio/dhive"; +import {PrivateKey} from '@hiveio/dhive'; -import {Global} from "../../store/global/types"; -import {Account} from "../../store/accounts/types"; -import {DynamicProps} from "../../store/dynamic-props/types"; -import {ActiveUser} from "../../store/active-user/types"; -import {Entry} from "../../store/entries/types"; +import {Global} from '../../store/global/types'; +import {Account} from '../../store/accounts/types'; +import {DynamicProps} from '../../store/dynamic-props/types'; +import {ActiveUser} from '../../store/active-user/types'; +import {Entry} from '../../store/entries/types'; -import BaseComponent from "../base"; -import LinearProgress from "../linear-progress"; -import SuggestionList from "../suggestion-list"; -import KeyOrHot from "../key-or-hot"; -import {error} from "../feedback"; +import BaseComponent from '../base'; +import LinearProgress from '../linear-progress'; +import SuggestionList from '../suggestion-list'; +import KeyOrHot from '../key-or-hot'; +import {error} from '../feedback'; -import {getPost} from "../../api/bridge"; -import {getBoostOptions, getBoostedPost} from "../../api/private-api"; -import {searchPath} from "../../api/search-api"; -import {boost, boostHot, boostKc, formatError} from "../../api/operations"; +import {getPost} from '../../api/bridge'; +import {getBoostOptions, getBoostedPost} from '../../api/private-api'; +import {searchPath} from '../../api/search-api'; +import {boost, boostHot, boostKc, formatError} from '../../api/operations'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import _c from "../../util/fix-class-names"; -import formattedNumber from "../../util/formatted-number"; - -import {checkAllSvg} from "../../img/svg"; +import _c from '../../util/fix-class-names'; +import formattedNumber from '../../util/formatted-number'; +import {checkAllSvg} from '../../img/svg'; interface Props { - global: Global; - dynamicProps: DynamicProps; - activeUser: ActiveUser; - signingKey: string; - entry?: Entry; - updateActiveUser: (data?: Account) => void; - setSigningKey: (key: string) => void; - onHide: () => void; + global: Global; + dynamicProps: DynamicProps; + activeUser: ActiveUser; + signingKey: string; + entry?: Entry; + updateActiveUser: (data?: Account) => void; + setSigningKey: (key: string) => void; + onHide: () => void; } interface State { - balanceError: string; - path: string; - postError: string; - paths: string[]; - options: number[]; - amount: number; - inProgress: boolean; - step: 1 | 2 | 3; + balanceError: string; + path: string; + postError: string; + paths: string[]; + options: number[]; + amount: number; + inProgress: boolean; + step: 1 | 2 | 3; } - -const pathComponents = (p: string): string[] => p.replace("@", "").split("/"); +const pathComponents = (p: string): string[] => p.replace('@', '').split('/'); export class Boost extends BaseComponent { - state: State = { - balanceError: "", - path: "", - postError: "", - paths: [], - options: [], - amount: 0, - inProgress: true, - step: 1 - } - - _timer: any = null; - - componentDidMount() { - this.init().then(() => { - const {updateActiveUser} = this.props; - updateActiveUser(); - }).then(() => { - const {entry} = this.props; - - if (entry) { - this.stateSet({path: `${entry.author}/${entry.permlink}`}); - } - }) - } - - componentDidUpdate(prevProps: Readonly) { - if (!isEqual(this.props.activeUser.points, prevProps.activeUser.points)) { - this.checkBalance(); + state: State = { + balanceError: '', + path: '', + postError: '', + paths: [], + options: [], + amount: 0, + inProgress: true, + step: 1, + }; + + _timer: any = null; + + componentDidMount() { + this.init() + .then(() => { + const {updateActiveUser} = this.props; + updateActiveUser(); + }) + .then(() => { + const {entry} = this.props; + + if (entry) { + this.stateSet({path: `${entry.author}/${entry.permlink}`}); } - } + }); + } - init = () => { - const {activeUser} = this.props; - - return getBoostOptions(activeUser.username).then(r => { - this.stateSet({options: r, amount: r[0], inProgress: false}, () => { - this.checkBalance(); - }); - }).catch(() => { - error(_t('g.server-error')); - }); + componentDidUpdate(prevProps: Readonly) { + if (!isEqual(this.props.activeUser.points, prevProps.activeUser.points)) { + this.checkBalance(); } + } - pathChanged = (e: React.ChangeEvent) => { - const path = e.target.value; - this.stateSet({path, postError: ''}); - - clearTimeout(this._timer); + init = () => { + const {activeUser} = this.props; - if (path.trim().length < 3) { - this.stateSet({paths: []}); - return; - } - - const {activeUser} = this.props; - - this._timer = setTimeout( - () => - searchPath(activeUser.username, path).then(resp => { - this.stateSet({paths: resp}); - }), - 500 - ); - } - - pathSelected = (path: string) => { - this.stateSet({path: path, paths: []}); - } - - checkBalance = () => { - const {activeUser} = this.props; - const {amount} = this.state; - - const balanceError = parseFloat(activeUser.points.points) < amount ? _t('trx-common.insufficient-funds') : ""; - - this.stateSet({balanceError}); - }; - - isValidPath = (p: string) => { - if (p.indexOf("/") === -1) { - return; - } - - const [author, permlink] = pathComponents(p); - return author.length >= 3 && permlink.length >= 3; - }; - - sliderChanged = (e: React.ChangeEvent) => { - const amount = Number(e.target.value); - this.stateSet({amount}, () => { - this.checkBalance(); + return getBoostOptions(activeUser.username) + .then(r => { + this.stateSet({options: r, amount: r[0], inProgress: false}, () => { + this.checkBalance(); }); + }) + .catch(() => { + error(_t('g.server-error')); + }); + }; + + pathChanged = ( + e: React.ChangeEvent, + ) => { + const path = e.target.value; + this.stateSet({path, postError: ''}); + + clearTimeout(this._timer); + + if (path.trim().length < 3) { + this.stateSet({paths: []}); + return; } - next = async () => { - const {activeUser} = this.props; - const {path} = this.state; + const {activeUser} = this.props; - const [author, permlink] = pathComponents(path); + this._timer = setTimeout( + () => + searchPath(activeUser.username, path).then(resp => { + this.stateSet({paths: resp}); + }), + 500, + ); + }; - this.stateSet({inProgress: true}); + pathSelected = (path: string) => { + this.stateSet({path: path, paths: []}); + }; - // Check if post is valid - let post: Entry | null; - try { - post = await getPost(author, permlink); - } catch (e) { - post = null; - } + checkBalance = () => { + const {activeUser} = this.props; + const {amount} = this.state; - if (!post) { - this.stateSet({postError: _t("redeem-common.post-error"), inProgress: false}); - return; - } + const balanceError = + parseFloat(activeUser.points.points) < amount + ? _t('trx-common.insufficient-funds') + : ''; - // Check if the post already boosted - const boosted = await getBoostedPost(activeUser.username, author, permlink); - if (boosted) { - this.stateSet({postError: _t("redeem-common.post-error-exists"), inProgress: false}); - return; - } + this.stateSet({balanceError}); + }; - this.stateSet({inProgress: false, step: 2}); + isValidPath = (p: string) => { + if (p.indexOf('/') === -1) { + return; } - sign = (key: PrivateKey) => { - const {activeUser} = this.props; - const {path, amount} = this.state; - const [author, permlink] = pathComponents(path); - - this.setState({inProgress: true}); - boost(key, activeUser.username, author, permlink, `${amount}.000`).then(() => { - this.stateSet({step: 3}); - }).catch(err => { - error(formatError(err)); - }).finally(() => { - this.setState({inProgress: false}); - }); + const [author, permlink] = pathComponents(p); + return author.length >= 3 && permlink.length >= 3; + }; + + sliderChanged = ( + e: React.ChangeEvent, + ) => { + const amount = Number(e.target.value); + this.stateSet({amount}, () => { + this.checkBalance(); + }); + }; + + next = async () => { + const {activeUser} = this.props; + const {path} = this.state; + + const [author, permlink] = pathComponents(path); + + this.stateSet({inProgress: true}); + + // Check if post is valid + let post: Entry | null; + try { + post = await getPost(author, permlink); + } catch (e) { + post = null; } - signKs = () => { - const {activeUser} = this.props; - const {path, amount} = this.state; - const [author, permlink] = pathComponents(path); - - this.setState({inProgress: true}); - boostKc(activeUser.username, author, permlink, `${amount}.000`).then(() => { - this.stateSet({step: 3}); - }).catch(err => { - error(formatError(err)); - }).finally(() => { - this.setState({inProgress: false}); - }); + if (!post) { + this.stateSet({ + postError: _t('redeem-common.post-error'), + inProgress: false, + }); + return; } - hotSign = () => { - const {activeUser, onHide} = this.props; - const {path, amount} = this.state; - const [author, permlink] = pathComponents(path); - - boostHot(activeUser.username, author, permlink, `${amount}.000`); - onHide(); + // Check if the post already boosted + const boosted = await getBoostedPost(activeUser.username, author, permlink); + if (boosted) { + this.stateSet({ + postError: _t('redeem-common.post-error-exists'), + inProgress: false, + }); + return; } - finish = () => { - const {onHide} = this.props; - onHide(); + this.stateSet({inProgress: false, step: 2}); + }; + + sign = (key: PrivateKey) => { + const {activeUser} = this.props; + const {path, amount} = this.state; + const [author, permlink] = pathComponents(path); + + this.setState({inProgress: true}); + boost(key, activeUser.username, author, permlink, `${amount}.000`) + .then(() => { + this.stateSet({step: 3}); + }) + .catch(err => { + error(formatError(err)); + }) + .finally(() => { + this.setState({inProgress: false}); + }); + }; + + signKs = () => { + const {activeUser} = this.props; + const {path, amount} = this.state; + const [author, permlink] = pathComponents(path); + + this.setState({inProgress: true}); + boostKc(activeUser.username, author, permlink, `${amount}.000`) + .then(() => { + this.stateSet({step: 3}); + }) + .catch(err => { + error(formatError(err)); + }) + .finally(() => { + this.setState({inProgress: false}); + }); + }; + + hotSign = () => { + const {activeUser, onHide} = this.props; + const {path, amount} = this.state; + const [author, permlink] = pathComponents(path); + + boostHot(activeUser.username, author, permlink, `${amount}.000`); + onHide(); + }; + + finish = () => { + const {onHide} = this.props; + onHide(); + }; + + pointsToSbd = (points: number) => { + //const {dynamicProps} = this.props; + //const {base, quote} = dynamicProps; + return points / 150; //* 0.01 * (base / quote); + }; + + render() { + const {activeUser} = this.props; + + const { + balanceError, + path, + postError, + amount, + paths, + options, + inProgress, + step, + } = this.state; + + const canSubmit = !postError && !balanceError && this.isValidPath(path); + + let sliderMin = 0; + let sliderMax = 10; + let sliderStep = 1; + + if (options.length > 1) { + sliderMin = options[0]; + sliderMax = options[options.length - 1]; + sliderStep = options[1] - options[0]; } - pointsToSbd = (points: number) => { - //const {dynamicProps} = this.props; - //const {base, quote} = dynamicProps; - return points / 150; //* 0.01 * (base / quote); - }; - - render() { - const {activeUser} = this.props; - - const {balanceError, path, postError, amount, paths, options, inProgress, step} = this.state; - - const canSubmit = !postError && !balanceError && this.isValidPath(path); - - let sliderMin = 0; - let sliderMax = 10; - let sliderStep = 1 - - if (options.length > 1) { - sliderMin = options[0]; - sliderMax = options[options.length - 1]; - sliderStep = options[1] - options[0]; - } - - return
- {step === 1 && ( -
-
-
1
-
-
{_t('boost.title')}
-
{_t('boost.sub-title')}
-
-
- {inProgress && } -
- - {_t('redeem-common.balance')} - - - {balanceError && {balanceError}} - - - - {_t('redeem-common.post')} - - i} onSelect={this.pathSelected}> - - - {postError && {postError}} - {!postError && {_t('redeem-common.post-hint')}} - - - - {_t('boost.amount')} - -
-
- {formattedNumber(this.pointsToSbd(amount), {fractionDigits: 3, suffix: '$'})} - {amount} POINTS -
- - {_t('boost.slider-hint')} -
- -
- - - - - - + return ( +
+ {step === 1 && ( +
+
+
1
+
+
{_t('boost.title')}
+
{_t('boost.sub-title')}
+
+
+ {inProgress && } +
+ + + {_t('redeem-common.balance')} + + + + {balanceError && ( + + {balanceError} + + )} + + + + + {_t('redeem-common.post')} + + + i} + onSelect={this.pathSelected} + > + + + {postError && ( + {postError} + )} + {!postError && ( + + {_t('redeem-common.post-hint')} + + )} + + + + + {_t('boost.amount')} + + +
+
+ {formattedNumber(this.pointsToSbd(amount), { + fractionDigits: 3, + suffix: '$', + })} + {amount} POINTS
+ + + {_t('boost.slider-hint')} + +
+ +
+ + + + + + +
+
+ )} + + {step === 2 && ( +
+
+
2
+
+
{_t('trx-common.sign-title')}
+
+ {_t('trx-common.sign-sub-title')}
- )} - - {step === 2 && ( -
-
-
2
-
-
{_t('trx-common.sign-title')}
-
{_t('trx-common.sign-sub-title')}
-
-
- {inProgress && } -
- {KeyOrHot({ - ...this.props, - inProgress, - onKey: this.sign, - onHot: this.hotSign, - onKc: this.signKs - })} -
+
+
+ {inProgress && } +
+ {KeyOrHot({ + ...this.props, + inProgress, + onKey: this.sign, + onHot: this.hotSign, + onKc: this.signKs, + })} +
+
+ )} + + {step === 3 && ( +
+
+
3
+
+
+ {_t('trx-common.success-title')}
- )} - - {step === 3 && ( -
-
-
3
-
-
{_t('trx-common.success-title')}
-
{_t('trx-common.success-sub-title')}
-
-
- {inProgress && } -
-

- {checkAllSvg} {_t("redeem-common.success-message")} -

-
- -
-
+
+ {_t('trx-common.success-sub-title')}
- )} -
- } +
+
+ {inProgress && } +
+

+ {checkAllSvg}{' '} + {_t('redeem-common.success-message')} +

+
+ +
+
+
+ )} +
+ ); + } } export default class BoostDialog extends Component { - render() { - const {onHide} = this.props; - return ( - - - - - - - ); - } + render() { + const {onHide} = this.props; + return ( + + + + + + + ); + } } diff --git a/src/common/components/buy-sell-hive/index.tsx b/src/common/components/buy-sell-hive/index.tsx index be9494c5077..8156bd3caf4 100644 --- a/src/common/components/buy-sell-hive/index.tsx +++ b/src/common/components/buy-sell-hive/index.tsx @@ -1,18 +1,18 @@ -import React, { Component } from "react"; -import { addUser } from "../../store/users"; -import { setActiveUser, updateActiveUser } from "../../store/active-user"; -import { setSigningKey } from "../../store/signing-key"; -import { addAccount } from "../../store/accounts"; +import React, {Component} from 'react'; +import {addUser} from '../../store/users'; +import {setActiveUser, updateActiveUser} from '../../store/active-user'; +import {setSigningKey} from '../../store/signing-key'; +import {addAccount} from '../../store/accounts'; -import { Modal, Button } from "react-bootstrap"; +import {Modal, Button} from 'react-bootstrap'; -import { Global } from "../../store/global/types"; -import { ActiveUser } from "../../store/active-user/types"; +import {Global} from '../../store/global/types'; +import {ActiveUser} from '../../store/active-user/types'; -import BaseComponent from "../base"; -import { error } from "../feedback"; +import BaseComponent from '../base'; +import {error} from '../feedback'; -import { getAccountFull } from "../../api/hive"; +import {getAccountFull} from '../../api/hive'; import { formatError, @@ -22,14 +22,14 @@ import { limitOrderCancelKc, limitOrderCancelHot, limitOrderCancel, -} from "../../api/operations"; +} from '../../api/operations'; -import { _t } from "../../i18n"; -import KeyOrHot from "../key-or-hot"; -import { AnyAction, bindActionCreators, Dispatch } from "redux"; -import { connect } from "react-redux"; -import { AppState } from "../../store"; -import { PrivateKey } from "@hiveio/dhive"; +import {_t} from '../../i18n'; +import KeyOrHot from '../key-or-hot'; +import {AnyAction, bindActionCreators, Dispatch} from 'redux'; +import {connect} from 'react-redux'; +import {AppState} from '../../store'; +import {PrivateKey} from '@hiveio/dhive'; export enum TransactionType { None = 0, @@ -41,7 +41,7 @@ export enum TransactionType { interface Props { type: TransactionType; onHide: () => void; - values?: { total: number; amount: number; price: number; available: number }; + values?: {total: number; amount: number; price: number; available: number}; global: Global; activeUser: ActiveUser; addAccount: (arg: any) => void; @@ -67,82 +67,83 @@ export class BuySellHive extends BaseComponent { } updateAll = (a: any) => { - const { addAccount, updateActiveUser, onTransactionSuccess } = - this.props; + const {addAccount, updateActiveUser, onTransactionSuccess} = this.props; // refresh addAccount(a); // update active updateActiveUser(a); - this.setState({ inProgress: false,step: 3 }); + this.setState({inProgress: false, step: 3}); onTransactionSuccess(); }; promiseCheck = (p: any) => { - const { onHide, } = this.props; - p && p.then(() => getAccountFull(this.props.activeUser!.username)) - .then((a: any) => this.updateAll(a)) - .catch((err: any) => { - error(formatError(err)); - this.setState({ inProgress: false }); - onHide(); - }); + const {onHide} = this.props; + p && + p + .then(() => getAccountFull(this.props.activeUser!.username)) + .then((a: any) => this.updateAll(a)) + .catch((err: any) => { + error(formatError(err)); + this.setState({inProgress: false}); + onHide(); + }); }; sign = (key: PrivateKey) => { - this.setState({ inProgress: true }); - const { activeUser, Ttype, orderid } = this.props; + this.setState({inProgress: true}); + const {activeUser, Ttype, orderid} = this.props; if (Ttype === TransactionType.Cancel && orderid) { this.promiseCheck(limitOrderCancel(activeUser!.username, key, orderid)); } else { const { - values: { total, amount }, + values: {total, amount}, } = this.props; this.promiseCheck( - limitOrderCreate(activeUser!.username, key, total, amount, Ttype) + limitOrderCreate(activeUser!.username, key, total, amount, Ttype), ); } }; signHs = () => { - this.setState({ inProgress: true }); - const { activeUser, Ttype, orderid } = this.props; + this.setState({inProgress: true}); + const {activeUser, Ttype, orderid} = this.props; if (Ttype === TransactionType.Cancel && orderid) { this.promiseCheck(limitOrderCancelHot(activeUser!.username, orderid)); } else { const { - values: { total, amount }, + values: {total, amount}, } = this.props; this.promiseCheck( - limitOrderCreateHot(activeUser!.username, total, amount, Ttype) + limitOrderCreateHot(activeUser!.username, total, amount, Ttype), ); } }; signKc = () => { - this.setState({ inProgress: true }); - const { activeUser, Ttype, orderid } = this.props; + this.setState({inProgress: true}); + const {activeUser, Ttype, orderid} = this.props; if (Ttype === TransactionType.Cancel && orderid) { this.promiseCheck(limitOrderCancelKc(activeUser!.username, orderid)); } else { const { - values: { total, amount }, + values: {total, amount}, } = this.props; this.promiseCheck( - limitOrderCreateKc(activeUser!.username, total, amount, Ttype) + limitOrderCreateKc(activeUser!.username, total, amount, Ttype), ); } }; finish = () => { - const { onHide, onTransactionSuccess } = this.props; + const {onHide, onTransactionSuccess} = this.props; onTransactionSuccess(); onHide(); - } + }; render() { - const { step, inProgress } = this.state; + const {step, inProgress} = this.state; const { - values: { amount, price, total, available } = { + values: {amount, price, total, available} = { amount: 0, price: 0, total: 0, @@ -154,34 +155,41 @@ export class BuySellHive extends BaseComponent { signingKey, setSigningKey, Ttype, - orderid + orderid, } = this.props; const formHeader1 = ( -
-
{step}
-
-
{_t("transfer.confirm-title")}
-
{_t("transfer.confirm-sub-title")}
+
+
{step}
+
+
{_t('transfer.confirm-title')}
+
{_t('transfer.confirm-sub-title')}
); if (step === 1) { return ( -
+
{formHeader1} -
+
{Ttype === TransactionType.Cancel ? ( -
- {_t("market.confirm-cancel", {orderid:orderid})} +
+ {_t('market.confirm-cancel', {orderid: orderid})}
) : ( -
+
{available < total - ? _t("market.transaction-low") + ? _t('market.transaction-low') : TransactionType.Buy - ? _t("market.confirm-buy", {amount, price, total, balance: parseFloat(available - total as any).toFixed(3)}) + ? _t('market.confirm-buy', { + amount, + price, + total, + balance: parseFloat((available - total) as any).toFixed( + 3, + ), + }) : ``}
)} @@ -189,18 +197,18 @@ export class BuySellHive extends BaseComponent { {available < total ? ( <> ) : ( -
-
-
@@ -211,9 +219,9 @@ export class BuySellHive extends BaseComponent { if (step === 2) { return ( -
+
{formHeader1} -
+
{KeyOrHot({ global, activeUser, @@ -224,16 +232,16 @@ export class BuySellHive extends BaseComponent { onKey: this.sign, onKc: this.signKc, })} -

+

{ + href='#' + onClick={e => { e.preventDefault(); - this.stateSet({ step: 1 }); + this.stateSet({step: 1}); }} > - {_t("g.back")} + {_t('g.back')}

@@ -242,33 +250,30 @@ export class BuySellHive extends BaseComponent { } if (step === 3) { - - const formHeader4 =
-
{step}
-
-
- {_t('trx-common.success-title')} -
-
- {_t('trx-common.success-sub-title')} -
+ const formHeader4 = ( +
+
{step}
+
+
{_t('trx-common.success-title')}
+
+ {_t('trx-common.success-sub-title')} +
-
; +
+ ); return ( -
- {formHeader4} -
-
- {_t("market.transaction-succeeded")} +
+ {formHeader4} +
+
+ {_t('market.transaction-succeeded')}
-
- - +
+ +
+
-
); } @@ -278,7 +283,7 @@ export class BuySellHive extends BaseComponent { class BuySellHiveDialog extends Component { render() { - const { onHide } = this.props; + const {onHide} = this.props; return ( { centered={true} onHide={onHide} keyboard={false} - className="transfer-dialog modal-thin-header" - size="lg" + className='transfer-dialog modal-thin-header' + size='lg' > @@ -311,7 +316,7 @@ const mapDispatchToProps = (dispatch: Dispatch) => addAccount, setSigningKey, }, - dispatch + dispatch, ); export default connect(mapStateToProps, mapDispatchToProps)(BuySellHiveDialog); diff --git a/src/common/components/chart-stats/index.spec.tsx b/src/common/components/chart-stats/index.spec.tsx index b1731dcf896..4088e1a0bde 100644 --- a/src/common/components/chart-stats/index.spec.tsx +++ b/src/common/components/chart-stats/index.spec.tsx @@ -1,36 +1,36 @@ -import React from "react"; +import React from 'react'; -import {ChartStats} from "./index"; +import {ChartStats} from './index'; -import TestRenderer from "react-test-renderer"; +import TestRenderer from 'react-test-renderer'; -import {allOver} from "../../helper/test-helper"; +import {allOver} from '../../helper/test-helper'; -it("(1) Default render", async () => { - const props = { - loading: false, - data: { - hbd_volume: "dummy value", - highest_bid: "dummy value", - hive_volume: "dummy value", - latest: "dummy value", - lowest_ask: "dummy value", - percent_change: "dummy value", - } - }; +it('(1) Default render', async () => { + const props = { + loading: false, + data: { + hbd_volume: 'dummy value', + highest_bid: 'dummy value', + hive_volume: 'dummy value', + latest: 'dummy value', + lowest_ask: 'dummy value', + percent_change: 'dummy value', + }, + }; - const renderer = await TestRenderer.create(); - await allOver(); - expect(renderer.toJSON()).toMatchSnapshot(); + const renderer = await TestRenderer.create(); + await allOver(); + expect(renderer.toJSON()).toMatchSnapshot(); }); -it("(2) Render with loading", async () => { - const props = { - loading: true, - data: null - }; +it('(2) Render with loading', async () => { + const props = { + loading: true, + data: null, + }; - const renderer = await TestRenderer.create(); - await allOver(); - expect(renderer.toJSON()).toMatchSnapshot(); -}); \ No newline at end of file + const renderer = await TestRenderer.create(); + await allOver(); + expect(renderer.toJSON()).toMatchSnapshot(); +}); diff --git a/src/common/components/chart-stats/index.tsx b/src/common/components/chart-stats/index.tsx index 3bc0c901915..19f64a23633 100644 --- a/src/common/components/chart-stats/index.tsx +++ b/src/common/components/chart-stats/index.tsx @@ -1,54 +1,94 @@ import React from 'react'; -import { Table } from 'react-bootstrap'; -import { MarketStatistics } from '../../api/hive'; -import { _t } from '../../i18n'; -import { isMobile } from '../../util/is-mobile'; -import { Skeleton } from '../skeleton'; +import {Table} from 'react-bootstrap'; +import {MarketStatistics} from '../../api/hive'; +import {_t} from '../../i18n'; +import {isMobile} from '../../util/is-mobile'; +import {Skeleton} from '../skeleton'; interface Props { - loading: boolean; - data: MarketStatistics | null; + loading: boolean; + data: MarketStatistics | null; } -export const ChartStats = ({loading, data}: Props) =>{ - - return loading ? - - - - - - - - - - - - - - - - - - - -
: - - - - - - - - - - - - - - - - - - -
{_t("market.last-price")}{_t("market.volume")}{_t("market.bid")}{_t("market.ask")}{_t("market.spread")}
${data ? parseFloat(data!.latest!).toFixed(6) : null} (+0.00%)${data? parseFloat(data!.hbd_volume)!.toFixed(2):null}${data? parseFloat(data!.highest_bid)!.toFixed(6):null}${data? parseFloat(data!.lowest_ask)!.toFixed(6):null}{data? ((200 * (parseFloat(data.lowest_ask) - parseFloat(data.highest_bid))) / (parseFloat(data.highest_bid) + parseFloat(data.lowest_ask))).toFixed(3) : null}%
-} \ No newline at end of file +export const ChartStats = ({loading, data}: Props) => { + return loading ? ( + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ + + + + + + + + +
+ ) : ( + + + + + + + + + + + + + + + + + + + +
{_t('market.last-price')}{_t('market.volume')}{_t('market.bid')}{_t('market.ask')}{_t('market.spread')}
+ ${data ? parseFloat(data!.latest!).toFixed(6) : null} ( + +0.00%) + ${data ? parseFloat(data!.hbd_volume)!.toFixed(2) : null}${data ? parseFloat(data!.highest_bid)!.toFixed(6) : null}${data ? parseFloat(data!.lowest_ask)!.toFixed(6) : null} + {data + ? ( + (200 * + (parseFloat(data.lowest_ask) - + parseFloat(data.highest_bid))) / + (parseFloat(data.highest_bid) + parseFloat(data.lowest_ask)) + ).toFixed(3) + : null} + % +
+ ); +}; diff --git a/src/common/components/clickaway-listener/index.spec.tsx b/src/common/components/clickaway-listener/index.spec.tsx index 243e336d88a..f58f582743c 100644 --- a/src/common/components/clickaway-listener/index.spec.tsx +++ b/src/common/components/clickaway-listener/index.spec.tsx @@ -1,21 +1,23 @@ -import React from "react"; -import ClickAwayListener from "./index"; -import TestRenderer from "react-test-renderer"; +import React from 'react'; +import ClickAwayListener from './index'; +import TestRenderer from 'react-test-renderer'; - -it("(1) triggers onClickAway", async () => { - const onClickaway = () => { - return null - } - let ComponentToTest = () => +it('(1) triggers onClickAway', async () => { + const onClickaway = () => { + return null; + }; + let ComponentToTest = () => (
- - - - + + + +
+ ); - const renderer = TestRenderer.create(); - renderer.root.findByProps({ id: "inside" }).props.onClick(); - expect(renderer.root.findByProps({ id: "inside" })).toBeFalsy; + const renderer = TestRenderer.create(); + renderer.root.findByProps({id: 'inside'}).props.onClick(); + expect(renderer.root.findByProps({id: 'inside'})).toBeFalsy; }); diff --git a/src/common/components/clickaway-listener/index.tsx b/src/common/components/clickaway-listener/index.tsx index 6c7bd4e85dd..c9370a4b62a 100644 --- a/src/common/components/clickaway-listener/index.tsx +++ b/src/common/components/clickaway-listener/index.tsx @@ -1,17 +1,17 @@ -import React, { Component } from 'react'; +import React, {Component} from 'react'; /** * Component that alerts if you click outside of it */ interface Props { - children:any; - onClickAway:()=>void; - className?:string; + children: any; + onClickAway: () => void; + className?: string; } export default class ClickAwayListener extends Component { - wrapperRef: any; - constructor(props:Props){ + wrapperRef: any; + constructor(props: Props) { super(props); this.setWrapperRef = this.setWrapperRef.bind(this); this.handleClickOutside = this.handleClickOutside.bind(this); @@ -28,20 +28,24 @@ export default class ClickAwayListener extends Component { /** * Set the wrapper ref */ - setWrapperRef(node:any) { + setWrapperRef(node: any) { this.wrapperRef = node; } /** * Alert if clicked on outside of element */ - handleClickOutside(event:any) { + handleClickOutside(event: any) { if (this.wrapperRef && !this.wrapperRef.contains(event.target)) { - this.props.onClickAway(); + this.props.onClickAway(); } } render() { - return
{this.props.children}
; + return ( +
+ {this.props.children} +
+ ); } } diff --git a/src/common/components/comment/index.spec.tsx b/src/common/components/comment/index.spec.tsx index 6cd90209a9c..c4247001272 100644 --- a/src/common/components/comment/index.spec.tsx +++ b/src/common/components/comment/index.spec.tsx @@ -1,57 +1,59 @@ -import React from "react"; +import React from 'react'; -import Comment from "./index"; +import Comment from './index'; -import {UiInstance, globalInstance, entryInstance1} from "../../helper/test-helper"; +import { + UiInstance, + globalInstance, + entryInstance1, +} from '../../helper/test-helper'; -import renderer from "react-test-renderer"; +import renderer from 'react-test-renderer'; -import emojiData from "../../../../public/emoji.json"; +import emojiData from '../../../../public/emoji.json'; -jest.mock("../../api/misc", () => ({ - getEmojiData: () => - new Promise((resolve) => { - resolve(emojiData); - }), +jest.mock('../../api/misc', () => ({ + getEmojiData: () => + new Promise(resolve => { + resolve(emojiData); + }), })); const defProps = { - defText: '', - submitText: 'Reply', - users: [], - global: globalInstance, - activeUser: null, - ui: UiInstance, - entry: entryInstance1, - inProgress: false, - isCommented: false, - cancellable: false, - autoFocus: true, - inputRef: null, - setActiveUser: () => { - }, - updateActiveUser: () => { - }, - deleteUser: () => { - }, - onSubmit: () => {}, - onCancel: () => {}, - toggleUIProp: () => {}, - resetSelection: () => {} + defText: '', + submitText: 'Reply', + users: [], + global: globalInstance, + activeUser: null, + ui: UiInstance, + entry: entryInstance1, + inProgress: false, + isCommented: false, + cancellable: false, + autoFocus: true, + inputRef: null, + setActiveUser: () => {}, + updateActiveUser: () => {}, + deleteUser: () => {}, + onSubmit: () => {}, + onCancel: () => {}, + toggleUIProp: () => {}, + resetSelection: () => {}, }; -it("(1) Default render", () => { - const props = {...defProps}; +it('(1) Default render', () => { + const props = {...defProps}; - const component = renderer.create(); - expect(component.toJSON()).toMatchSnapshot(); + const component = renderer.create(); + expect(component.toJSON()).toMatchSnapshot(); }); +it('(2) Cancellable, in progress', () => { + const props = { + ...{inProgress: true, cancellable: true, defText: 'foo'}, + ...defProps, + }; -it("(2) Cancellable, in progress", () => { - const props = {...{inProgress: true, cancellable: true, defText: 'foo'}, ...defProps}; - - const component = renderer.create(); - expect(component.toJSON()).toMatchSnapshot(); + const component = renderer.create(); + expect(component.toJSON()).toMatchSnapshot(); }); - diff --git a/src/common/components/comment/index.tsx b/src/common/components/comment/index.tsx index 7313b7c4046..13e7391dc20 100644 --- a/src/common/components/comment/index.tsx +++ b/src/common/components/comment/index.tsx @@ -1,203 +1,240 @@ -import React, {Component, Ref} from "react"; +import React, {Component, Ref} from 'react'; -import {Form, FormControl, Button, Spinner} from "react-bootstrap"; +import {Form, FormControl, Button, Spinner} from 'react-bootstrap'; -import {User} from "../../store/users/types"; -import {ActiveUser} from "../../store/active-user/types"; -import {Account} from "../../store/accounts/types"; -import {UI, ToggleType} from "../../store/ui/types"; -import {Entry} from "../../store/entries/types"; +import {User} from '../../store/users/types'; +import {ActiveUser} from '../../store/active-user/types'; +import {Account} from '../../store/accounts/types'; +import {UI, ToggleType} from '../../store/ui/types'; +import {Entry} from '../../store/entries/types'; -import EditorToolbar from "../editor-toolbar"; -import LoginRequired from "../login-required"; +import EditorToolbar from '../editor-toolbar'; +import LoginRequired from '../login-required'; -import defaults from "../../constants/defaults.json"; +import defaults from '../../constants/defaults.json'; -import {renderPostBody, setProxyBase} from "@ecency/render-helper"; +import {renderPostBody, setProxyBase} from '@ecency/render-helper'; setProxyBase(defaults.imageServer); -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; import {Global} from '../../store/global/types'; -import * as ls from "../../util/local-storage"; +import * as ls from '../../util/local-storage'; interface PreviewProps { - text: string; - global: Global; + text: string; + global: Global; } - export class CommentPreview extends Component { - shouldComponentUpdate(nextProps: Readonly): boolean { - return this.props.text !== nextProps.text - } - - render() { - const {text, global} = this.props; + shouldComponentUpdate(nextProps: Readonly): boolean { + return this.props.text !== nextProps.text; + } - if (text === '') { - return null; - } + render() { + const {text, global} = this.props; - return
-
{_t('comment.preview')}
-
-
; + if (text === '') { + return null; } + + return ( +
+
{_t('comment.preview')}
+
+
+ ); + } } interface Props { - defText: string; - submitText: string; - users: User[]; - activeUser: ActiveUser | null; - ui: UI; - global: Global; - entry: Entry; - inProgress?: boolean; - isCommented?: boolean; - cancellable?: boolean; - autoFocus?: boolean; - setActiveUser: (username: string | null) => void; - updateActiveUser: (data?: Account) => void; - deleteUser: (username: string) => void; - toggleUIProp: (what: ToggleType) => void; - onSubmit: (text: string) => void; - resetSelection?: () => void; - onCancel?: () => void; - inputRef?: Ref; + defText: string; + submitText: string; + users: User[]; + activeUser: ActiveUser | null; + ui: UI; + global: Global; + entry: Entry; + inProgress?: boolean; + isCommented?: boolean; + cancellable?: boolean; + autoFocus?: boolean; + setActiveUser: (username: string | null) => void; + updateActiveUser: (data?: Account) => void; + deleteUser: (username: string) => void; + toggleUIProp: (what: ToggleType) => void; + onSubmit: (text: string) => void; + resetSelection?: () => void; + onCancel?: () => void; + inputRef?: Ref; } interface State { - text: string, - preview: string, - showEmoji: boolean + text: string; + preview: string; + showEmoji: boolean; } export class Comment extends Component { - state: State = { - text: '', - preview: '', - showEmoji: false + state: State = { + text: '', + preview: '', + showEmoji: false, + }; + + _updateTimer: any = null; + + componentDidMount(): void { + const {defText} = this.props; + this.setState({text: defText || '', preview: defText || ''}); + } + + componentDidUpdate(prevProps: Readonly): void { + const {defText, resetSelection, isCommented} = this.props; + const {text} = this.state; + if (defText !== prevProps.defText && !prevProps.defText) { + let commentText = text ? text + '\n' + defText : defText; + this.setState({text: commentText || '', preview: commentText || ''}); + if (resetSelection) resetSelection(); + this.updateLsCommentDraft(commentText); } - - _updateTimer: any = null; - - componentDidMount(): void { - const {defText} = this.props; - this.setState({text: defText || "", preview: defText || ""}); - } - - componentDidUpdate(prevProps: Readonly): void { - const {defText, resetSelection, isCommented} = this.props; - const {text} = this.state; - if ((defText !== prevProps.defText) && !prevProps.defText) { - let commentText = text ? text + '\n' + defText : defText - this.setState({text: commentText || "", preview: commentText || ""}); - if (resetSelection) resetSelection() - this.updateLsCommentDraft(commentText) - } - if(prevProps.isCommented && !isCommented) { - this.setState({text: "", preview: ""}) - } + if (prevProps.isCommented && !isCommented) { + this.setState({text: '', preview: ''}); } - - updateLsCommentDraft = (text: string) => { - const {entry} = this.props - ls.set(`reply_draft_${entry.author}_${entry.permlink}`, text); + } + + updateLsCommentDraft = (text: string) => { + const {entry} = this.props; + ls.set(`reply_draft_${entry.author}_${entry.permlink}`, text); + }; + + textChanged = ( + e: React.ChangeEvent, + ): void => { + const {value: text} = e.target; + this.setState({text}, () => { + this.updateLsCommentDraft(text); + this.updatePreview(); + }); + }; + + updatePreview = (): void => { + if (this._updateTimer) { + clearTimeout(this._updateTimer); + this._updateTimer = null; } - textChanged = (e: React.ChangeEvent): void => { - const {value: text} = e.target; - this.setState({text}, () => { - this.updateLsCommentDraft(text) - this.updatePreview(); - }); - }; - - updatePreview = (): void => { - if (this._updateTimer) { - clearTimeout(this._updateTimer); - this._updateTimer = null; - } - - this._updateTimer = setTimeout(() => { - const {text} = this.state; - this.setState({preview: text || ""}); - }, 500); - }; - - submit = () => { - const {text} = this.state; - const {onSubmit} = this.props; - onSubmit(text); - } - - cancel = () => { - const {onCancel} = this.props; - if (onCancel) onCancel(); - } - - render() { - const {inProgress, cancellable, autoFocus, submitText, inputRef} = this.props; - const {text, preview, showEmoji} = this.state; - return ( - <> -
!showEmoji && this.setState({showEmoji: true})}> - {EditorToolbar({...this.props, sm: true, showEmoji})} -
- 20 ? 'expanded' : ''}`} - as="textarea" - placeholder={_t("comment.body-placeholder")} - value={text} - onChange={this.textChanged} - disabled={inProgress} - autoFocus={autoFocus} - rows={text.split(/\r\n|\r|\n/).length} - ref={inputRef} - /> -
-
- {cancellable && ( - - )} - {LoginRequired({ - ...this.props, - children: - })} -
- -
- - ); - } + this._updateTimer = setTimeout(() => { + const {text} = this.state; + this.setState({preview: text || ''}); + }, 500); + }; + + submit = () => { + const {text} = this.state; + const {onSubmit} = this.props; + onSubmit(text); + }; + + cancel = () => { + const {onCancel} = this.props; + if (onCancel) onCancel(); + }; + + render() { + const {inProgress, cancellable, autoFocus, submitText, inputRef} = + this.props; + const {text, preview, showEmoji} = this.state; + return ( + <> +
!showEmoji && this.setState({showEmoji: true})} + > + {EditorToolbar({...this.props, sm: true, showEmoji})} +
+ 20 ? 'expanded' : '' + }`} + as='textarea' + placeholder={_t('comment.body-placeholder')} + value={text} + onChange={this.textChanged} + disabled={inProgress} + autoFocus={autoFocus} + rows={text.split(/\r\n|\r|\n/).length} + ref={inputRef} + /> +
+
+ {cancellable && ( + + )} + {LoginRequired({ + ...this.props, + children: ( + + ), + })} +
+ +
+ + ); + } } export default (p: Props) => { - const props: Props = { - defText: p.defText, - submitText: p.submitText, - users: p.users, - activeUser: p.activeUser, - ui: p.ui, - global: p.global, - entry: p.entry, - inProgress: p.inProgress, - isCommented: p.isCommented, - cancellable: p.cancellable, - autoFocus: p.autoFocus, - setActiveUser: p.setActiveUser, - updateActiveUser: p.updateActiveUser, - deleteUser: p.deleteUser, - toggleUIProp: p.toggleUIProp, - onSubmit: p.onSubmit, - resetSelection: p.resetSelection, - onCancel: p.onCancel, - inputRef: p.inputRef, - } - - return -} + const props: Props = { + defText: p.defText, + submitText: p.submitText, + users: p.users, + activeUser: p.activeUser, + ui: p.ui, + global: p.global, + entry: p.entry, + inProgress: p.inProgress, + isCommented: p.isCommented, + cancellable: p.cancellable, + autoFocus: p.autoFocus, + setActiveUser: p.setActiveUser, + updateActiveUser: p.updateActiveUser, + deleteUser: p.deleteUser, + toggleUIProp: p.toggleUIProp, + onSubmit: p.onSubmit, + resetSelection: p.resetSelection, + onCancel: p.onCancel, + inputRef: p.inputRef, + }; + + return ; +}; diff --git a/src/common/components/community-activities/index.spec.tsx b/src/common/components/community-activities/index.spec.tsx index da703bafa66..556c1442980 100644 --- a/src/common/components/community-activities/index.spec.tsx +++ b/src/common/components/community-activities/index.spec.tsx @@ -1,67 +1,70 @@ import React from 'react'; -import renderer from "react-test-renderer"; +import renderer from 'react-test-renderer'; -import {createBrowserHistory} from "history"; +import {createBrowserHistory} from 'history'; import {Activities} from './index'; -import {globalInstance, communityInstance1, allOver} from "../../helper/test-helper"; +import { + globalInstance, + communityInstance1, + allOver, +} from '../../helper/test-helper'; -jest.mock("moment", () => () => ({ - fromNow: () => "3 days ago", +jest.mock('moment', () => () => ({ + fromNow: () => '3 days ago', })); -jest.mock("../../api/bridge", () => ({ - getAccountNotifications: () => - new Promise((resolve) => { - resolve([ - { - date: "2020-09-09T04:49:00", - id: 82401222, - msg: "@foo pin @foo/bar-permlink", - score: 35, - type: "pin_post", - url: "@foo/bar-permlink" - }, - { - date: "2020-09-08T12:57:57", - id: 82366223, - msg: "@foo set @bar member", - score: 35, - type: "set_role", - url: "trending/hive-183952", - }, { - date: "2020-09-08T12:53:36", - id: 82366013, - msg: "@foo unpin @foo/bar-permlink", - score: 35, - type: "unpin_post", - url: "@foo/bar-permlink", - }, { - date: "2020-09-08T02:38:57", - id: 82345318, - msg: "@foo subscribed to Bar", - score: 35, - type: "subscribe", - url: "trending/hive-125125", - } - ]); - }) +jest.mock('../../api/bridge', () => ({ + getAccountNotifications: () => + new Promise(resolve => { + resolve([ + { + date: '2020-09-09T04:49:00', + id: 82401222, + msg: '@foo pin @foo/bar-permlink', + score: 35, + type: 'pin_post', + url: '@foo/bar-permlink', + }, + { + date: '2020-09-08T12:57:57', + id: 82366223, + msg: '@foo set @bar member', + score: 35, + type: 'set_role', + url: 'trending/hive-183952', + }, + { + date: '2020-09-08T12:53:36', + id: 82366013, + msg: '@foo unpin @foo/bar-permlink', + score: 35, + type: 'unpin_post', + url: '@foo/bar-permlink', + }, + { + date: '2020-09-08T02:38:57', + id: 82345318, + msg: '@foo subscribed to Bar', + score: 35, + type: 'subscribe', + url: 'trending/hive-125125', + }, + ]); + }), })); it('(1) Default render - With data.', async () => { - const props = { - history: createBrowserHistory(), - global: globalInstance, - community: {...communityInstance1}, - addAccount: () => { - }, - onHide: () => { - } - }; + const props = { + history: createBrowserHistory(), + global: globalInstance, + community: {...communityInstance1}, + addAccount: () => {}, + onHide: () => {}, + }; - const component = await renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + const component = await renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); - diff --git a/src/common/components/community-activities/index.tsx b/src/common/components/community-activities/index.tsx index b5917e3fc65..9fead48e88a 100644 --- a/src/common/components/community-activities/index.tsx +++ b/src/common/components/community-activities/index.tsx @@ -1,187 +1,211 @@ -import React, {Component, Fragment} from "react"; +import React, {Component, Fragment} from 'react'; -import {Button} from "react-bootstrap"; +import {Button} from 'react-bootstrap'; -import {History} from "history"; +import {History} from 'history'; -import moment from "moment"; +import moment from 'moment'; -import {Global} from "../../store/global/types"; -import {Account} from "../../store/accounts/types"; -import {Community} from "../../store/communities/types"; +import {Global} from '../../store/global/types'; +import {Account} from '../../store/accounts/types'; +import {Community} from '../../store/communities/types'; -import BaseComponent from "../base"; -import ProfileLink from "../profile-link"; -import EntryLink from "../entry-link"; -import UserAvatar from "../user-avatar"; -import LinearProgress from "../linear-progress"; -import {error} from "../feedback"; +import BaseComponent from '../base'; +import ProfileLink from '../profile-link'; +import EntryLink from '../entry-link'; +import UserAvatar from '../user-avatar'; +import LinearProgress from '../linear-progress'; +import {error} from '../feedback'; -import {getAccountNotifications, AccountNotification} from "../../api/bridge"; +import {getAccountNotifications, AccountNotification} from '../../api/bridge'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import parseDate from "../../helper/parse-date"; +import parseDate from '../../helper/parse-date'; interface ListItemProps { - history: History; - global: Global; - notification: AccountNotification; - addAccount: (data: Account) => void; + history: History; + global: Global; + notification: AccountNotification; + addAccount: (data: Account) => void; } class ListItem extends Component { - shouldComponentUpdate(): boolean { - return false; - } - - formatMessage = (patterns: string[]): JSX.Element => { - const {notification} = this.props; - const {msg} = notification; + shouldComponentUpdate(): boolean { + return false; + } - const parts = msg.split(new RegExp(`(${patterns.join('|')})`, 'gi')); + formatMessage = (patterns: string[]): JSX.Element => { + const {notification} = this.props; + const {msg} = notification; - return <>{parts.map((part, i) => { + const parts = msg.split(new RegExp(`(${patterns.join('|')})`, 'gi')); - if (part.trim() === '') { - return null; - } - - if (patterns.includes(part.toLowerCase())) { - - // post link - if (part.includes("/")) { - const s = part.split("/") - return {EntryLink({ - ...this.props, - entry: { - category: "post", - author: s[0].replace("@", ""), - permlink: s[1] - }, - children: <>{part} - })} - } - - // user link - return {ProfileLink({ + return ( + <> + {parts.map((part, i) => { + if (part.trim() === '') { + return null; + } + + if (patterns.includes(part.toLowerCase())) { + // post link + if (part.includes('/')) { + const s = part.split('/'); + return ( + + {EntryLink({ ...this.props, - username: part.replace("@", ""), - children: <>{part} - })} + entry: { + category: 'post', + author: s[0].replace('@', ''), + permlink: s[1], + }, + children: <>{part}, + })} + + ); } - return {part} - })}; + // user link + return ( + + {ProfileLink({ + ...this.props, + username: part.replace('@', ''), + children: <>{part}, + })} + + ); + } + + return {part}; + })} + + ); + }; + + render() { + const {notification} = this.props; + let mentions = notification.msg.match(/@[\w.\d-]+/gi); + if (!mentions) { + return null; } - render() { - const {notification} = this.props; - let mentions = notification.msg.match(/@[\w.\d-]+/gi) - if (!mentions) { - return null; - } + let formatPatterns = []; - let formatPatterns = []; - - // @username/permlink - if (notification.url.startsWith('@')) { - formatPatterns.push(notification.url); - } - - // @usernames - formatPatterns = [...formatPatterns, ...mentions]; - - const username = mentions[0].replace('@', ''); - const msg = this.formatMessage(formatPatterns); - const date = moment(parseDate(notification.date)); - - return
-
- {ProfileLink({ - ...this.props, - username, - children: <>{UserAvatar({...this.props, username, size: "medium"})} - })} -
-
-
{msg}
-
{date.fromNow()}
-
-
; + // @username/permlink + if (notification.url.startsWith('@')) { + formatPatterns.push(notification.url); } + + // @usernames + formatPatterns = [...formatPatterns, ...mentions]; + + const username = mentions[0].replace('@', ''); + const msg = this.formatMessage(formatPatterns); + const date = moment(parseDate(notification.date)); + + return ( +
+
+ {ProfileLink({ + ...this.props, + username, + children: ( + <>{UserAvatar({...this.props, username, size: 'medium'})} + ), + })} +
+
+
{msg}
+
{date.fromNow()}
+
+
+ ); + } } interface Props { - history: History; - global: Global; - community: Community; - addAccount: (data: Account) => void; + history: History; + global: Global; + community: Community; + addAccount: (data: Account) => void; } interface State { - loading: boolean; - items: AccountNotification[]; - hasMore: boolean + loading: boolean; + items: AccountNotification[]; + hasMore: boolean; } export class Activities extends BaseComponent { - state: State = { - loading: true, - items: [], - hasMore: false - } - - componentDidMount() { - this.fetch(); - } - - fetch = () => { - const limit = 50; - const {items} = this.state; - const {community} = this.props; - - const lastId = items.length > 0 ? items[items.length - 1].id : null; - - this.setState({loading: true}); - getAccountNotifications(community.name, lastId, limit).then(r => { - if (r) { - const newItems = [...items, ...r] - this.stateSet({items: newItems, hasMore: r.length === limit}); - } - }).catch(() => { - error(_t('g.server-error')); - }).finally(() => { - this.stateSet({loading: false}); - }); - } - - render() { - const {items, loading, hasMore} = this.state; - - return
- {loading && } -
-
- {items.length > 0 && items.map((item, i) => )} -
-
- {hasMore && (
- -
)} + state: State = { + loading: true, + items: [], + hasMore: false, + }; + + componentDidMount() { + this.fetch(); + } + + fetch = () => { + const limit = 50; + const {items} = this.state; + const {community} = this.props; + + const lastId = items.length > 0 ? items[items.length - 1].id : null; + + this.setState({loading: true}); + getAccountNotifications(community.name, lastId, limit) + .then(r => { + if (r) { + const newItems = [...items, ...r]; + this.stateSet({items: newItems, hasMore: r.length === limit}); + } + }) + .catch(() => { + error(_t('g.server-error')); + }) + .finally(() => { + this.stateSet({loading: false}); + }); + }; + + render() { + const {items, loading, hasMore} = this.state; + + return ( +
+ {loading && } +
+
+ {items.length > 0 && + items.map((item, i) => ( + + ))} +
- } + {hasMore && ( +
+ +
+ )} +
+ ); + } } export default (p: Props) => { - const props: Props = { - history: p.history, - global: p.global, - community: p.community, - addAccount: p.addAccount - } - - return -} + const props: Props = { + history: p.history, + global: p.global, + community: p.community, + addAccount: p.addAccount, + }; + + return ; +}; diff --git a/src/common/components/community-card/index.spec.tsx b/src/common/components/community-card/index.spec.tsx index 45aa9e2172e..12510bc73a6 100644 --- a/src/common/components/community-card/index.spec.tsx +++ b/src/common/components/community-card/index.spec.tsx @@ -1,87 +1,84 @@ -import React from "react"; +import React from 'react'; -import renderer from "react-test-renderer"; -import {createBrowserHistory} from "history"; -import {StaticRouter} from "react-router-dom"; +import renderer from 'react-test-renderer'; +import {createBrowserHistory} from 'history'; +import {StaticRouter} from 'react-router-dom'; -import {CommunityCard} from "./index"; +import {CommunityCard} from './index'; -import {communityInstance1, globalInstance, activeUserMaker} from "../../helper/test-helper"; +import { + communityInstance1, + globalInstance, + activeUserMaker, +} from '../../helper/test-helper'; -it("(1) Default render", () => { - const props = { - history: createBrowserHistory(), - global: globalInstance, - community: {...communityInstance1}, - account: { - name: communityInstance1.name - }, - users: [], - signingKey: "", - activeUser: null, - setSigningKey: () => { - }, - addAccount: () => { - }, - addCommunity: () => { - }, - }; +it('(1) Default render', () => { + const props = { + history: createBrowserHistory(), + global: globalInstance, + community: {...communityInstance1}, + account: { + name: communityInstance1.name, + }, + users: [], + signingKey: '', + activeUser: null, + setSigningKey: () => {}, + addAccount: () => {}, + addCommunity: () => {}, + }; - const component = renderer.create(); - expect(component.toJSON()).toMatchSnapshot(); + const component = renderer.create(); + expect(component.toJSON()).toMatchSnapshot(); }); -it("(2) Should show edit buttons with nsfw label", () => { - const props = { - history: createBrowserHistory(), - global: globalInstance, - community: {...communityInstance1, is_nsfw: true}, - account: { - name: communityInstance1.name - }, - users: [], - signingKey: "", - activeUser: activeUserMaker("hive-148441"), - setSigningKey: () => { - }, - addAccount: () => { - }, - addCommunity: () => { - }, - }; +it('(2) Should show edit buttons with nsfw label', () => { + const props = { + history: createBrowserHistory(), + global: globalInstance, + community: {...communityInstance1, is_nsfw: true}, + account: { + name: communityInstance1.name, + }, + users: [], + signingKey: '', + activeUser: activeUserMaker('hive-148441'), + setSigningKey: () => {}, + addAccount: () => {}, + addCommunity: () => {}, + }; - const component = renderer.create( - - - ); - expect(component.toJSON()).toMatchSnapshot(); + const component = renderer.create( + + + , + ); + expect(component.toJSON()).toMatchSnapshot(); }); -it("(3) usePrivate = false", () => { - const props = { - history: createBrowserHistory(), - global: { - ...globalInstance, - usePrivate: false - }, - community: {...communityInstance1, is_nsfw: true}, - account: { - name: communityInstance1.name - }, - users: [], - signingKey: "", - activeUser: activeUserMaker("hive-148441"), - setSigningKey: () => { - }, - addAccount: () => { - }, - addCommunity: () => { - }, - }; +it('(3) usePrivate = false', () => { + const props = { + history: createBrowserHistory(), + global: { + ...globalInstance, + usePrivate: false, + }, + community: {...communityInstance1, is_nsfw: true}, + account: { + name: communityInstance1.name, + }, + users: [], + signingKey: '', + activeUser: activeUserMaker('hive-148441'), + setSigningKey: () => {}, + addAccount: () => {}, + addCommunity: () => {}, + }; - const component = renderer.create( - - - ); - expect(component.toJSON()).toMatchSnapshot(); + const component = renderer.create( + + + , + ); + expect(component.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/community-card/index.tsx b/src/common/components/community-card/index.tsx index 0993e2801bc..28104b9149c 100644 --- a/src/common/components/community-card/index.tsx +++ b/src/common/components/community-card/index.tsx @@ -1,315 +1,422 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import {Button, Modal} from "react-bootstrap"; +import {Button, Modal} from 'react-bootstrap'; -import {History} from "history"; +import {History} from 'history'; -import {Link} from "react-router-dom"; +import {Link} from 'react-router-dom'; -import isEqual from "react-fast-compare"; +import isEqual from 'react-fast-compare'; -import {Global} from "../../store/global/types"; -import {Account, FullAccount} from "../../store/accounts/types"; -import {Community, roleMap, ROLES} from "../../store/communities/types"; -import {ActiveUser} from "../../store/active-user/types"; -import {User} from "../../store/users/types"; +import {Global} from '../../store/global/types'; +import {Account, FullAccount} from '../../store/accounts/types'; +import {Community, roleMap, ROLES} from '../../store/communities/types'; +import {ActiveUser} from '../../store/active-user/types'; +import {User} from '../../store/users/types'; -import BaseComponent from "../base"; -import UserAvatar from "../user-avatar"; -import ProfileLink from "../profile-link"; -import CommunitySettings from "../community-settings"; -import CommunityRewardsRegistrationDialog from "../community-rewards-registration"; -import ImageUploadDialog from "../image-upload"; -import Tooltip from "../tooltip"; -import {error, success} from "../feedback"; +import BaseComponent from '../base'; +import UserAvatar from '../user-avatar'; +import ProfileLink from '../profile-link'; +import CommunitySettings from '../community-settings'; +import CommunityRewardsRegistrationDialog from '../community-rewards-registration'; +import ImageUploadDialog from '../image-upload'; +import Tooltip from '../tooltip'; +import {error, success} from '../feedback'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import {getAccount} from "../../api/hive"; -import {updateProfile} from "../../api/operations"; +import {getAccount} from '../../api/hive'; +import {updateProfile} from '../../api/operations'; -import ln2list from "../../util/nl2list"; - -import {accountGroupSvg, informationOutlineSvg, scriptTextOutlineSvg, pencilOutlineSvg} from "../../img/svg"; +import ln2list from '../../util/nl2list'; +import { + accountGroupSvg, + informationOutlineSvg, + scriptTextOutlineSvg, + pencilOutlineSvg, +} from '../../img/svg'; interface EditPicProps { - activeUser: ActiveUser; - community?: Community; - account: FullAccount; - addAccount: (data: Account) => void; - onUpdate: () => void + activeUser: ActiveUser; + community?: Community; + account: FullAccount; + addAccount: (data: Account) => void; + onUpdate: () => void; } interface EditPicState { - account: Account | null; - dialog: boolean; - inProgress: boolean; + account: Account | null; + dialog: boolean; + inProgress: boolean; } export class EditPic extends BaseComponent { - state: EditPicState = { - account: null, - dialog: false, - inProgress: false - } - - toggleDialog = () => { - const {dialog} = this.state; - this.stateSet({dialog: !dialog}) - } - - save = (url: string) => { - const {account} = this.props; - if (account.profile?.profile_image === url) { - this.toggleDialog(); - return; - } - - this.stateSet({inProgress: true}); - - const {addAccount, onUpdate} = this.props; - const {profile} = account; - - const newProfile = { - name: profile?.name || '', - about: profile?.about || '', - cover_image: profile?.cover_image || '', - profile_image: url, - website: profile?.website || '', - location: profile?.location || '', - }; - - updateProfile(account, newProfile).then(r => { - success(_t('community-card.profile-image-updated')); - return getAccount(account.name); - }).then((account) => { - // update reducer - addAccount(account); - - // close dialog - this.toggleDialog(); - onUpdate(); - }).catch(() => { - error(_t('g.server-error')); - }).finally(() => { - this.stateSet({inProgress: false}); - }); + state: EditPicState = { + account: null, + dialog: false, + inProgress: false, + }; + + toggleDialog = () => { + const {dialog} = this.state; + this.stateSet({dialog: !dialog}); + }; + + save = (url: string) => { + const {account} = this.props; + if (account.profile?.profile_image === url) { + this.toggleDialog(); + return; } - render() { - const {activeUser, account} = this.props; - const {dialog, inProgress} = this.state; - - return <> - -
{pencilOutlineSvg}
-
- {dialog && ( - - )} - - } + this.stateSet({inProgress: true}); + + const {addAccount, onUpdate} = this.props; + const {profile} = account; + + const newProfile = { + name: profile?.name || '', + about: profile?.about || '', + cover_image: profile?.cover_image || '', + profile_image: url, + website: profile?.website || '', + location: profile?.location || '', + }; + + updateProfile(account, newProfile) + .then(r => { + success(_t('community-card.profile-image-updated')); + return getAccount(account.name); + }) + .then(account => { + // update reducer + addAccount(account); + + // close dialog + this.toggleDialog(); + onUpdate(); + }) + .catch(() => { + error(_t('g.server-error')); + }) + .finally(() => { + this.stateSet({inProgress: false}); + }); + }; + + render() { + const {activeUser, account} = this.props; + const {dialog, inProgress} = this.state; + + return ( + <> + +
+ {pencilOutlineSvg} +
+
+ {dialog && ( + + )} + + ); + } } interface Props { - history: History; - global: Global; - community: Community; - account: Account; - users: User[]; - activeUser: ActiveUser | null; - signingKey: string; - setSigningKey: (key: string) => void; - addAccount: (data: Account) => void; - addCommunity: (data: Community) => void; + history: History; + global: Global; + community: Community; + account: Account; + users: User[]; + activeUser: ActiveUser | null; + signingKey: string; + setSigningKey: (key: string) => void; + addAccount: (data: Account) => void; + addCommunity: (data: Community) => void; } interface DialogInfo { - title: string; - content: JSX.Element | null; + title: string; + content: JSX.Element | null; } interface State { - info: DialogInfo | null; - settings: boolean; - rewards: boolean; - useNewImage: boolean; + info: DialogInfo | null; + settings: boolean; + rewards: boolean; + useNewImage: boolean; } export class CommunityCard extends Component { - state: State = { - info: null, - settings: false, - rewards: false, - useNewImage: false - } - - shouldComponentUpdate(nextProps: Readonly, nextState: Readonly): boolean { - return !isEqual(this.props.community, nextProps.community) - || !isEqual(this.props.users, nextProps.users) - || !isEqual(this.props.account, nextProps.account) - || !isEqual(this.props.activeUser?.username, nextProps.activeUser?.username) - || !isEqual(this.state, nextState) - } - - toggleInfo = (info: DialogInfo | null) => { - this.setState({info}); - } - - toggleSettings = () => { - const {settings} = this.state; - this.setState({settings: !settings}); - } - - toggleRewards = () => { - const {rewards} = this.state; - this.setState({rewards: !rewards}); - } - - render() { - const {info, settings, rewards, useNewImage} = this.state; - const {global, community, activeUser, users, account} = this.props; - - const role = community.team.find(x => x[0] === activeUser?.username); - const roleInTeam = role ? role[1] : null; - const canEditTeam = !!(roleInTeam && roleMap[roleInTeam]); - const canEditCommunity = !!(roleInTeam && [ROLES.OWNER.toString(), ROLES.ADMIN.toString()].includes(roleInTeam)); - - const description: JSX.Element | null = community.description.trim() !== "" ? - <>{ln2list(community.description).map((x, i) => ( -

{x}

- ))} : null; - - const rules: JSX.Element | null = community.flag_text.trim() !== "" ? - <>{ln2list(community.flag_text).map((x, i) => ( -

{'- '}{x}

- ))} : null; - - const team: JSX.Element = <>{ - community.team.map((m, i) => { - if (m[0].startsWith("hive-")) { - return null; - } - - return ( -
- {ProfileLink({...this.props, username: m[0], children: {`@${m[0]}`}})} - {m[1]} - {m[2] !== "" && {m[2]}} -
- ); - })}; - - const canUpdatePic = activeUser && !!users.find(x => x.username === community.name); - - return ( -
-
- {canUpdatePic && ( { - this.setState({useNewImage: true}); - }}/>)} - {UserAvatar({ - ...this.props, - username: community.name, - size: "xLarge", - src: account.__loaded && useNewImage ? account.profile?.profile_image : undefined - })} -
-
-

-
{community.title}
-

-
{community.about}
- {community.is_nsfw && nsfw} -
-
- {description && ( -
-
{ - this.toggleInfo({title: _t('community-card.description'), content: description}); - }}> - {informationOutlineSvg} {_t('community-card.description')} -
-
{description}
-
- )} - {rules && ( -
-
{ - this.toggleInfo({title: _t('community-card.rules'), content: rules}); - }}> - {scriptTextOutlineSvg} {_t('community-card.rules')} -
-
{rules}
-
- )} -
-
{ - this.toggleInfo({title: _t('community-card.team'), content: team}); - }}> - {accountGroupSvg} {_t('community-card.team')} -
-
{team}
-
-
- - {(canEditCommunity || canEditTeam) && ( -
- {canEditCommunity && (

- -

)} - {canEditTeam && (

- {_t('community-card.edit-team')} -

)} -
- )} - {(global.usePrivate && roleInTeam === ROLES.OWNER.toString()) && ( -

{ - e.preventDefault(); - this.toggleRewards(); - }}>{_t('community-card.community-rewards')}

- )} - {info && ( - { - this.toggleInfo(null); - }} animation={false} className="community-info-dialog"> - - {info.title} - -
{info.content}
-
- )} - - {settings && } - - {rewards && } + state: State = { + info: null, + settings: false, + rewards: false, + useNewImage: false, + }; + + shouldComponentUpdate( + nextProps: Readonly, + nextState: Readonly, + ): boolean { + return ( + !isEqual(this.props.community, nextProps.community) || + !isEqual(this.props.users, nextProps.users) || + !isEqual(this.props.account, nextProps.account) || + !isEqual( + this.props.activeUser?.username, + nextProps.activeUser?.username, + ) || + !isEqual(this.state, nextState) + ); + } + + toggleInfo = (info: DialogInfo | null) => { + this.setState({info}); + }; + + toggleSettings = () => { + const {settings} = this.state; + this.setState({settings: !settings}); + }; + + toggleRewards = () => { + const {rewards} = this.state; + this.setState({rewards: !rewards}); + }; + + render() { + const {info, settings, rewards, useNewImage} = this.state; + const {global, community, activeUser, users, account} = this.props; + + const role = community.team.find(x => x[0] === activeUser?.username); + const roleInTeam = role ? role[1] : null; + const canEditTeam = !!(roleInTeam && roleMap[roleInTeam]); + const canEditCommunity = !!( + roleInTeam && + [ROLES.OWNER.toString(), ROLES.ADMIN.toString()].includes(roleInTeam) + ); + + const description: JSX.Element | null = + community.description.trim() !== '' ? ( + <> + {ln2list(community.description).map((x, i) => ( +

{x}

+ ))} + + ) : null; + + const rules: JSX.Element | null = + community.flag_text.trim() !== '' ? ( + <> + {ln2list(community.flag_text).map((x, i) => ( +

+ {'- '} + {x} +

+ ))} + + ) : null; + + const team: JSX.Element = ( + <> + {community.team.map((m, i) => { + if (m[0].startsWith('hive-')) { + return null; + } + + return ( +
+ {ProfileLink({ + ...this.props, + username: m[0], + children: {`@${m[0]}`}, + })} + {m[1]} + {m[2] !== '' && {m[2]}}
- ); - } + ); + })} + + ); + + const canUpdatePic = + activeUser && !!users.find(x => x.username === community.name); + + return ( +
+
+ {canUpdatePic && ( + { + this.setState({useNewImage: true}); + }} + /> + )} + {UserAvatar({ + ...this.props, + username: community.name, + size: 'xLarge', + src: + account.__loaded && useNewImage + ? account.profile?.profile_image + : undefined, + })} +
+
+

+
{community.title}
+

+
{community.about}
+ {community.is_nsfw && nsfw} +
+
+ {description && ( +
+
{ + this.toggleInfo({ + title: _t('community-card.description'), + content: description, + }); + }} + > + {informationOutlineSvg} {_t('community-card.description')} +
+
{description}
+
+ )} + {rules && ( +
+
{ + this.toggleInfo({ + title: _t('community-card.rules'), + content: rules, + }); + }} + > + {scriptTextOutlineSvg} {_t('community-card.rules')} +
+
{rules}
+
+ )} +
+
{ + this.toggleInfo({ + title: _t('community-card.team'), + content: team, + }); + }} + > + {accountGroupSvg} {_t('community-card.team')} +
+
{team}
+
+
+ + {(canEditCommunity || canEditTeam) && ( +
+ {canEditCommunity && ( +

+ +

+ )} + {canEditTeam && ( +

+ + {_t('community-card.edit-team')} + +

+ )} +
+ )} + {global.usePrivate && roleInTeam === ROLES.OWNER.toString() && ( +

+ { + e.preventDefault(); + this.toggleRewards(); + }} + > + {_t('community-card.community-rewards')} + +

+ )} + {info && ( + { + this.toggleInfo(null); + }} + animation={false} + className='community-info-dialog' + > + + {info.title} + + +
{info.content}
+
+
+ )} + + {settings && ( + + )} + + {rewards && ( + + )} +
+ ); + } } export default (p: Props) => { - const props: Props = { - history: p.history, - global: p.global, - community: p.community, - account: p.account, - users: p.users, - signingKey: p.signingKey, - setSigningKey: p.setSigningKey, - activeUser: p.activeUser, - addAccount: p.addAccount, - addCommunity: p.addCommunity - } - - return ; -} - + const props: Props = { + history: p.history, + global: p.global, + community: p.community, + account: p.account, + users: p.users, + signingKey: p.signingKey, + setSigningKey: p.setSigningKey, + activeUser: p.activeUser, + addAccount: p.addAccount, + addCommunity: p.addCommunity, + }; + + return ; +}; diff --git a/src/common/components/community-cover/index.spec.tsx b/src/common/components/community-cover/index.spec.tsx index af273c0e68c..5708509c1a2 100644 --- a/src/common/components/community-cover/index.spec.tsx +++ b/src/common/components/community-cover/index.spec.tsx @@ -1,113 +1,107 @@ -import React from "react"; -import {createBrowserHistory} from "history"; -import renderer from "react-test-renderer"; +import React from 'react'; +import {createBrowserHistory} from 'history'; +import renderer from 'react-test-renderer'; -import CommunityCover from "./index"; +import CommunityCover from './index'; +import {Theme} from '../../store/global/types'; +import {Account} from '../../store/accounts/types'; -import {Theme} from "../../store/global/types"; -import {Account} from "../../store/accounts/types"; +import { + globalInstance, + UiInstance, + communityInstance1, + fullAccountInstance, +} from '../../helper/test-helper'; -import {globalInstance, UiInstance, communityInstance1, fullAccountInstance} from "../../helper/test-helper"; - -jest.mock("../../constants/defaults.json", () => ({ - imageServer: "https://images.ecency.com", +jest.mock('../../constants/defaults.json', () => ({ + imageServer: 'https://images.ecency.com', })); -jest.mock("../../api/hive", () => ({ - getFollowing: () => - new Promise((resolve) => { - resolve([]); - }), +jest.mock('../../api/hive', () => ({ + getFollowing: () => + new Promise(resolve => { + resolve([]); + }), })); - const defProps = { - history: createBrowserHistory(), - global: {...globalInstance}, - community: communityInstance1, - users: [], - activeUser: null, - subscriptions: [], - ui: UiInstance, - setActiveUser: () => { - }, - updateActiveUser: () => { - }, - deleteUser: () => { - }, - toggleUIProp: () => { - }, - updateSubscriptions: () => { - }, - addAccount: () => { - }, + history: createBrowserHistory(), + global: {...globalInstance}, + community: communityInstance1, + users: [], + activeUser: null, + subscriptions: [], + ui: UiInstance, + setActiveUser: () => {}, + updateActiveUser: () => {}, + deleteUser: () => {}, + toggleUIProp: () => {}, + updateSubscriptions: () => {}, + addAccount: () => {}, }; -it("(1) Render with loaded account object", () => { - const account: Account = { - ...fullAccountInstance, - name: "user1", - profile: { - cover_image: "https://img.esteem.app/rwd380.jpg", - } - }; - - const props = { - ...defProps, - account, - }; - - const component = renderer.create(); - expect(component.toJSON()).toMatchSnapshot(); -}); +it('(1) Render with loaded account object', () => { + const account: Account = { + ...fullAccountInstance, + name: 'user1', + profile: { + cover_image: 'https://img.esteem.app/rwd380.jpg', + }, + }; + const props = { + ...defProps, + account, + }; -it("(2) Render with not loaded account object", () => { - const account: Account = { - name: "user1", - }; + const component = renderer.create(); + expect(component.toJSON()).toMatchSnapshot(); +}); - const props = { - ...defProps, - account, - }; +it('(2) Render with not loaded account object', () => { + const account: Account = { + name: 'user1', + }; - const component = renderer.create(); - expect(component.toJSON()).toMatchSnapshot(); -}); + const props = { + ...defProps, + account, + }; + const component = renderer.create(); + expect(component.toJSON()).toMatchSnapshot(); +}); -it("(3) No bg image - Day theme", () => { - const account: Account = { - ...fullAccountInstance, - name: "user1", - profile: {}, - }; +it('(3) No bg image - Day theme', () => { + const account: Account = { + ...fullAccountInstance, + name: 'user1', + profile: {}, + }; - const props = { - ...defProps, - account, - }; + const props = { + ...defProps, + account, + }; - const component = renderer.create(); - expect(component.toJSON()).toMatchSnapshot(); + const component = renderer.create(); + expect(component.toJSON()).toMatchSnapshot(); }); -it("(4) No bg image - Night theme", () => { - const account: Account = { - ...fullAccountInstance, - name: "user1", - profile: {} - }; - - const props = { - ...defProps, - global: {...globalInstance, ...{theme: Theme.night}}, - account, - }; - - const component = renderer.create(); - expect(component.toJSON()).toMatchSnapshot(); +it('(4) No bg image - Night theme', () => { + const account: Account = { + ...fullAccountInstance, + name: 'user1', + profile: {}, + }; + + const props = { + ...defProps, + global: {...globalInstance, ...{theme: Theme.night}}, + account, + }; + + const component = renderer.create(); + expect(component.toJSON()).toMatchSnapshot(); }); - diff --git a/src/common/components/community-cover/index.tsx b/src/common/components/community-cover/index.tsx index c2d34e8a03a..7cb549ed022 100644 --- a/src/common/components/community-cover/index.tsx +++ b/src/common/components/community-cover/index.tsx @@ -1,232 +1,255 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import {History} from "history"; +import {History} from 'history'; -import isEqual from "react-fast-compare"; +import isEqual from 'react-fast-compare'; -import {Global} from "../../store/global/types"; -import {User} from "../../store/users/types"; -import {ActiveUser} from "../../store/active-user/types"; -import {Account, FullAccount} from "../../store/accounts/types"; -import {UI, ToggleType} from "../../store/ui/types"; -import {Community} from "../../store/communities/types"; -import {Subscription} from "../../store/subscriptions/types"; +import {Global} from '../../store/global/types'; +import {User} from '../../store/users/types'; +import {ActiveUser} from '../../store/active-user/types'; +import {Account, FullAccount} from '../../store/accounts/types'; +import {UI, ToggleType} from '../../store/ui/types'; +import {Community} from '../../store/communities/types'; +import {Subscription} from '../../store/subscriptions/types'; -import defaults from "../../constants/defaults.json"; +import defaults from '../../constants/defaults.json'; -import {proxifyImageSrc, setProxyBase} from "@ecency/render-helper"; +import {proxifyImageSrc, setProxyBase} from '@ecency/render-helper'; setProxyBase(defaults.imageServer); -import BaseComponent from "../base"; -import SubscriptionBtn from "../subscription-btn"; -import CommunityPostBtn from "../community-post-btn"; -import Tooltip from "../tooltip"; -import ImageUploadDialog from "../image-upload"; +import BaseComponent from '../base'; +import SubscriptionBtn from '../subscription-btn'; +import CommunityPostBtn from '../community-post-btn'; +import Tooltip from '../tooltip'; +import ImageUploadDialog from '../image-upload'; -import formattedNumber from "../../util/formatted-number"; +import formattedNumber from '../../util/formatted-number'; -import {updateProfile} from "../../api/operations"; -import {error, success} from "../feedback"; -import {getAccount} from "../../api/hive"; +import {updateProfile} from '../../api/operations'; +import {error, success} from '../feedback'; +import {getAccount} from '../../api/hive'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import {pencilOutlineSvg} from "../../img/svg"; +import {pencilOutlineSvg} from '../../img/svg'; -const coverFallbackDay = require("../../img/cover-fallback-day.png"); -const coverFallbackNight = require("../../img/cover-fallback-night.png"); +const coverFallbackDay = require('../../img/cover-fallback-day.png'); +const coverFallbackNight = require('../../img/cover-fallback-night.png'); interface EditCoverImageProps { - activeUser: ActiveUser; - community: Community; - account: FullAccount; - addAccount: (data: Account) => void; + activeUser: ActiveUser; + community: Community; + account: FullAccount; + addAccount: (data: Account) => void; } interface EditCoverImageState { - dialog: boolean; - inProgress: boolean; + dialog: boolean; + inProgress: boolean; } -class EditCoverImage extends BaseComponent { - state: EditCoverImageState = { - dialog: false, - inProgress: false +class EditCoverImage extends BaseComponent< + EditCoverImageProps, + EditCoverImageState +> { + state: EditCoverImageState = { + dialog: false, + inProgress: false, + }; + + toggleDialog = () => { + const {dialog} = this.state; + this.stateSet({dialog: !dialog}); + }; + + save = (url: string) => { + const {account} = this.props; + if (account.profile?.cover_image === url) { + this.toggleDialog(); + return; } - toggleDialog = () => { - const {dialog} = this.state; - this.stateSet({dialog: !dialog}) - } - - save = (url: string) => { - const {account} = this.props; - if (account.profile?.cover_image === url) { - this.toggleDialog(); - return; - } - - this.stateSet({inProgress: true}); - - const {addAccount} = this.props; - const {profile} = account; - - const newProfile = { - name: profile?.name || '', - about: profile?.about || '', - cover_image: url, - profile_image: profile?.profile_image || '', - website: profile?.website || '', - location: profile?.location || '', - }; - - updateProfile(account, newProfile).then(r => { - success(_t('community-cover.cover-image-updated')); - return getAccount(account.name); - }).then((account) => { - // update reducer - addAccount(account); - - // close dialog - this.toggleDialog(); - }).catch(() => { - error(_t('g.server-error')); - }).finally(() => { - this.stateSet({inProgress: false}); - }); - } - - render() { - const {activeUser, account} = this.props; - const {dialog, inProgress} = this.state; - - return <> - -
{pencilOutlineSvg}
-
- {dialog && ( - - )} - - } + this.stateSet({inProgress: true}); + + const {addAccount} = this.props; + const {profile} = account; + + const newProfile = { + name: profile?.name || '', + about: profile?.about || '', + cover_image: url, + profile_image: profile?.profile_image || '', + website: profile?.website || '', + location: profile?.location || '', + }; + + updateProfile(account, newProfile) + .then(r => { + success(_t('community-cover.cover-image-updated')); + return getAccount(account.name); + }) + .then(account => { + // update reducer + addAccount(account); + + // close dialog + this.toggleDialog(); + }) + .catch(() => { + error(_t('g.server-error')); + }) + .finally(() => { + this.stateSet({inProgress: false}); + }); + }; + + render() { + const {activeUser, account} = this.props; + const {dialog, inProgress} = this.state; + + return ( + <> + +
+ {pencilOutlineSvg} +
+
+ {dialog && ( + + )} + + ); + } } - interface Props { - history: History; - global: Global; - community: Community; - account: Account; - users: User[]; - activeUser: ActiveUser | null; - subscriptions: Subscription[]; - ui: UI; - setActiveUser: (username: string | null) => void; - updateActiveUser: (data?: Account) => void; - deleteUser: (username: string) => void; - toggleUIProp: (what: ToggleType) => void; - updateSubscriptions: (list: Subscription[]) => void; - addAccount: (data: Account) => void; + history: History; + global: Global; + community: Community; + account: Account; + users: User[]; + activeUser: ActiveUser | null; + subscriptions: Subscription[]; + ui: UI; + setActiveUser: (username: string | null) => void; + updateActiveUser: (data?: Account) => void; + deleteUser: (username: string) => void; + toggleUIProp: (what: ToggleType) => void; + updateSubscriptions: (list: Subscription[]) => void; + addAccount: (data: Account) => void; } export class CommunityCover extends Component { + shouldComponentUpdate(nextProps: Readonly): boolean { + return ( + !isEqual(this.props.global, nextProps.global) || + !isEqual(this.props.community, nextProps.community) || + !isEqual(this.props.subscriptions, nextProps.subscriptions) || + !isEqual(this.props.account, nextProps.account) || + !isEqual(this.props.users, nextProps.users) || + !isEqual(this.props.activeUser, nextProps.activeUser) || + !isEqual(this.props.ui, nextProps.ui) + ); + } + + render() { + const {global, account, community, activeUser, users} = this.props; + + let bgImage = ''; + + if (account.__loaded) { + bgImage = global.theme === 'day' ? coverFallbackDay : coverFallbackNight; + if (account.profile?.cover_image) { + bgImage = proxifyImageSrc( + account.profile.cover_image, + 0, + 0, + global.canUseWebp ? 'webp' : 'match', + ); + } + } - shouldComponentUpdate(nextProps: Readonly): boolean { - return !isEqual(this.props.global, nextProps.global) - || !isEqual(this.props.community, nextProps.community) - || !isEqual(this.props.subscriptions, nextProps.subscriptions) - || !isEqual(this.props.account, nextProps.account) - || !isEqual(this.props.users, nextProps.users) - || !isEqual(this.props.activeUser, nextProps.activeUser) - || !isEqual(this.props.ui, nextProps.ui) + let style = {}; + if (bgImage) { + style = {backgroundImage: `url('${bgImage}')`}; } - render() { - const {global, account, community, activeUser, users} = this.props; - - let bgImage = ""; - - if (account.__loaded) { - bgImage = global.theme === "day" ? coverFallbackDay : coverFallbackNight; - if (account.profile?.cover_image) { - bgImage = proxifyImageSrc(account.profile.cover_image, 0, 0, global.canUseWebp ? 'webp' : 'match'); - } - } - - let style = {}; - if (bgImage) { - style = {backgroundImage: `url('${bgImage}')`}; - } - - const subscribers = formattedNumber(community.subscribers, {fractionDigits: 0}); - const rewards = formattedNumber(community.sum_pending, {fractionDigits: 0}); - const authors = formattedNumber(community.num_authors, {fractionDigits: 0}); - - const canUpdateCoverImage = activeUser && !!users.find(x => x.username === community.name); - - return ( -
-
-
-
-
{subscribers}
-
{_t('community.subscribers')}
-
-
-
{"$"} {rewards}
-
{_t('community-cover.rewards')}
-
-
-
{authors}
-
{_t('community-cover.authors')}
-
- {community.lang.trim() !== "" && ( -
-
- {community.lang.toUpperCase()} -
-
- {_t('community-cover.lang')} -
-
- )} -
- -
- - {CommunityPostBtn({...this.props})} -
- {canUpdateCoverImage && ()} + const subscribers = formattedNumber(community.subscribers, { + fractionDigits: 0, + }); + const rewards = formattedNumber(community.sum_pending, {fractionDigits: 0}); + const authors = formattedNumber(community.num_authors, {fractionDigits: 0}); + + const canUpdateCoverImage = + activeUser && !!users.find(x => x.username === community.name); + + return ( +
+
+
+
+
{subscribers}
+
{_t('community.subscribers')}
+
+
+
+ {'$'} {rewards}
- ); - } +
{_t('community-cover.rewards')}
+
+
+
{authors}
+
{_t('community-cover.authors')}
+
+ {community.lang.trim() !== '' && ( +
+
{community.lang.toUpperCase()}
+
{_t('community-cover.lang')}
+
+ )} +
+ +
+ + {CommunityPostBtn({...this.props})} +
+ {canUpdateCoverImage && ( + + )} +
+ ); + } } export default (p: Props) => { - const props: Props = { - history: p.history, - global: p.global, - community: p.community, - account: p.account, - users: p.users, - activeUser: p.activeUser, - subscriptions: p.subscriptions, - ui: p.ui, - setActiveUser: p.setActiveUser, - updateActiveUser: p.updateActiveUser, - deleteUser: p.deleteUser, - toggleUIProp: p.toggleUIProp, - updateSubscriptions: p.updateSubscriptions, - addAccount: p.addAccount - } - - return -} + const props: Props = { + history: p.history, + global: p.global, + community: p.community, + account: p.account, + users: p.users, + activeUser: p.activeUser, + subscriptions: p.subscriptions, + ui: p.ui, + setActiveUser: p.setActiveUser, + updateActiveUser: p.updateActiveUser, + deleteUser: p.deleteUser, + toggleUIProp: p.toggleUIProp, + updateSubscriptions: p.updateSubscriptions, + addAccount: p.addAccount, + }; + + return ; +}; diff --git a/src/common/components/community-list-item/index.spec.tsx b/src/common/components/community-list-item/index.spec.tsx index 67ed605aba2..b582fefbe01 100644 --- a/src/common/components/community-list-item/index.spec.tsx +++ b/src/common/components/community-list-item/index.spec.tsx @@ -1,43 +1,41 @@ -import React from "react"; +import React from 'react'; -import CommunityListItem from "./index"; -import TestRenderer from "react-test-renderer"; +import CommunityListItem from './index'; +import TestRenderer from 'react-test-renderer'; -import {createBrowserHistory} from "history"; -import {StaticRouter} from "react-router-dom"; +import {createBrowserHistory} from 'history'; +import {StaticRouter} from 'react-router-dom'; -import {communityInstance1, UiInstance, globalInstance} from "../../helper/test-helper"; +import { + communityInstance1, + UiInstance, + globalInstance, +} from '../../helper/test-helper'; -it("(1) Default render", () => { - const props = { - history: createBrowserHistory(), - global: globalInstance, - users: [], - activeUser: null, - community: {...communityInstance1}, - ui: UiInstance, - subscriptions: [], - setActiveUser: () => { - }, - updateActiveUser: () => { - }, - deleteUser: () => { - }, - toggleUIProp: () => { - }, - addAccount: () => { - }, - updateSubscriptions: () => { - } - }; +it('(1) Default render', () => { + const props = { + history: createBrowserHistory(), + global: globalInstance, + users: [], + activeUser: null, + community: {...communityInstance1}, + ui: UiInstance, + subscriptions: [], + setActiveUser: () => {}, + updateActiveUser: () => {}, + deleteUser: () => {}, + toggleUIProp: () => {}, + addAccount: () => {}, + updateSubscriptions: () => {}, + }; - const comp = ; + const comp = ; - const renderer = TestRenderer.create( - - {comp} - - ); + const renderer = TestRenderer.create( + + {comp} + , + ); - expect(renderer.toJSON()).toMatchSnapshot(); + expect(renderer.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/community-list-item/index.tsx b/src/common/components/community-list-item/index.tsx index 65da5ba1647..76aace21aec 100644 --- a/src/common/components/community-list-item/index.tsx +++ b/src/common/components/community-list-item/index.tsx @@ -1,110 +1,125 @@ -import React, {Component, Fragment} from "react"; +import React, {Component, Fragment} from 'react'; -import {History} from "history"; -import {Link} from "react-router-dom"; +import {History} from 'history'; +import {Link} from 'react-router-dom'; -import isEqual from "react-fast-compare"; +import isEqual from 'react-fast-compare'; -import {Account} from "../../store/accounts/types"; -import {Community} from "../../store/communities/types"; -import {Subscription} from "../../store/subscriptions/types"; -import {User} from "../../store/users/types"; -import {ActiveUser} from "../../store/active-user/types"; -import {ToggleType, UI} from "../../store/ui/types"; +import {Account} from '../../store/accounts/types'; +import {Community} from '../../store/communities/types'; +import {Subscription} from '../../store/subscriptions/types'; +import {User} from '../../store/users/types'; +import {ActiveUser} from '../../store/active-user/types'; +import {ToggleType, UI} from '../../store/ui/types'; +import ProfileLink from '../../components/profile-link'; +import SubscriptionBtn from '../subscription-btn'; +import UserAvatar from '../user-avatar'; +import {Global} from '../../store/global/types'; -import ProfileLink from "../../components/profile-link"; -import SubscriptionBtn from "../subscription-btn"; -import UserAvatar from "../user-avatar"; -import {Global} from "../../store/global/types"; +import {makePath} from '../tag'; -import {makePath} from "../tag"; +import defaults from '../../constants/defaults.json'; -import defaults from "../../constants/defaults.json"; +import {_t} from '../../i18n'; -import {_t} from "../../i18n"; - -import formattedNumber from "../../util/formatted-number"; +import formattedNumber from '../../util/formatted-number'; interface Props { - history: History; - global: Global; - users: User[]; - activeUser: ActiveUser | null; - community: Community; - ui: UI; - subscriptions: Subscription[]; - setActiveUser: (username: string | null) => void; - updateActiveUser: (data?: Account) => void; - deleteUser: (username: string) => void; - toggleUIProp: (what: ToggleType) => void; - addAccount: (data: Account) => void; - updateSubscriptions: (list: Subscription[]) => void; + history: History; + global: Global; + users: User[]; + activeUser: ActiveUser | null; + community: Community; + ui: UI; + subscriptions: Subscription[]; + setActiveUser: (username: string | null) => void; + updateActiveUser: (data?: Account) => void; + deleteUser: (username: string) => void; + toggleUIProp: (what: ToggleType) => void; + addAccount: (data: Account) => void; + updateSubscriptions: (list: Subscription[]) => void; } export class CommunityListItem extends Component { - shouldComponentUpdate(nextProps: Readonly): boolean { - return !isEqual(this.props.community, nextProps.community) || - !isEqual(this.props.subscriptions, nextProps.subscriptions) || - !isEqual(this.props.activeUser?.username, nextProps.activeUser?.username) - } - - render() { - const {community} = this.props; - - const nOpts = {fractionDigits: 0}; - const subscribers = formattedNumber(community.subscribers, nOpts); - const authors = formattedNumber(community.num_authors, nOpts); - const posts = formattedNumber(community.num_pending, nOpts); - - return ( -
-
-

- {UserAvatar({...this.props, username: community.name, size: "medium"})} - {community.title} -

-
{community.about}
-
-
{_t("communities.n-subscribers", {n: subscribers})}
-
{_t("communities.n-authors", {n: authors})}
-
{_t("communities.n-posts", {n: posts})}
-
- {community.admins && ( -
- {_t("communities.admins")} - {community.admins.map((x, i) => ( - - {ProfileLink({...this.props, username: x, children: {x}})} - - ))} -
- )} -
-
- -
+ shouldComponentUpdate(nextProps: Readonly): boolean { + return ( + !isEqual(this.props.community, nextProps.community) || + !isEqual(this.props.subscriptions, nextProps.subscriptions) || + !isEqual(this.props.activeUser?.username, nextProps.activeUser?.username) + ); + } + + render() { + const {community} = this.props; + + const nOpts = {fractionDigits: 0}; + const subscribers = formattedNumber(community.subscribers, nOpts); + const authors = formattedNumber(community.num_authors, nOpts); + const posts = formattedNumber(community.num_pending, nOpts); + + return ( +
+
+

+ {UserAvatar({ + ...this.props, + username: community.name, + size: 'medium', + })} + + {community.title} + +

+
{community.about}
+
+
+ {_t('communities.n-subscribers', {n: subscribers})}
- ); - } +
+ {_t('communities.n-authors', {n: authors})} +
+
{_t('communities.n-posts', {n: posts})}
+
+ {community.admins && ( +
+ {_t('communities.admins')} + {community.admins.map((x, i) => ( + + {ProfileLink({ + ...this.props, + username: x, + children: {x}, + })} + + ))} +
+ )} +
+
+ +
+
+ ); + } } export default (p: Props) => { - const props: Props = { - history: p.history, - global: p.global, - users: p.users, - activeUser: p.activeUser, - community: p.community, - ui: p.ui, - subscriptions: p.subscriptions, - setActiveUser: p.setActiveUser, - updateActiveUser: p.updateActiveUser, - deleteUser: p.deleteUser, - toggleUIProp: p.toggleUIProp, - addAccount: p.addAccount, - updateSubscriptions: p.updateSubscriptions - } - - return ; -} + const props: Props = { + history: p.history, + global: p.global, + users: p.users, + activeUser: p.activeUser, + community: p.community, + ui: p.ui, + subscriptions: p.subscriptions, + setActiveUser: p.setActiveUser, + updateActiveUser: p.updateActiveUser, + deleteUser: p.deleteUser, + toggleUIProp: p.toggleUIProp, + addAccount: p.addAccount, + updateSubscriptions: p.updateSubscriptions, + }; + + return ; +}; diff --git a/src/common/components/community-menu/index.spec.tsx b/src/common/components/community-menu/index.spec.tsx index cd8f593adfa..9eed0d96408 100644 --- a/src/common/components/community-menu/index.spec.tsx +++ b/src/common/components/community-menu/index.spec.tsx @@ -1,93 +1,90 @@ -import React from "react"; +import React from 'react'; -import {StaticRouter} from "react-router-dom"; +import {StaticRouter} from 'react-router-dom'; -import {createLocation, createBrowserHistory} from "history"; +import {createLocation, createBrowserHistory} from 'history'; -import CommunityMenu from "./index"; -import TestRenderer from "react-test-renderer"; +import CommunityMenu from './index'; +import TestRenderer from 'react-test-renderer'; -import {globalInstance, communityInstance1} from "../../helper/test-helper"; +import {globalInstance, communityInstance1} from '../../helper/test-helper'; const defProps = { - history: createBrowserHistory(), - location: createLocation({}), - global: {...globalInstance}, - community: {...communityInstance1}, - toggleListStyle: () => { + history: createBrowserHistory(), + location: createLocation({}), + global: {...globalInstance}, + community: {...communityInstance1}, + toggleListStyle: () => {}, +}; + +it('(1) Default render', () => { + const props = { + ...defProps, + ...{ + match: { + path: '...', + url: '/trending/hive-125125', + isExact: true, + params: {filter: 'trending', name: 'hive-125125'}, + }, }, -} - -it("(1) Default render", () => { - const props = { - ...defProps, - ...{ - match: { - path: "...", - url: "/trending/hive-125125", - isExact: true, - params: {filter: "trending", name: "hive-125125"} - }, - } - }; - - const comp = ; - - const renderer = TestRenderer.create( - - {comp} - - ); - - expect(renderer.toJSON()).toMatchSnapshot(); + }; + + const comp = ; + + const renderer = TestRenderer.create( + + {comp} + , + ); + + expect(renderer.toJSON()).toMatchSnapshot(); }); +it('(2) Hot filter', () => { + const props = { + ...defProps, + ...{ + match: { + path: '...', + url: '/hot/hive-125125', + isExact: true, + params: {filter: 'hot', name: 'hive-125125'}, + }, + }, + }; + + const comp = ; -it("(2) Hot filter", () => { - const props = { - ...defProps, - ...{ - match: { - path: "...", - url: "/hot/hive-125125", - isExact: true, - params: {filter: "hot", name: "hive-125125"} - }, - } - }; - - const comp = ; - - const renderer = TestRenderer.create( - - {comp} - - ); - - expect(renderer.toJSON()).toMatchSnapshot(); + const renderer = TestRenderer.create( + + {comp} + , + ); + + expect(renderer.toJSON()).toMatchSnapshot(); }); +it('(3) In section', () => { + const props = { + ...defProps, + ...{ + match: { + path: '...', + url: '/activities/hive-125125', + isExact: true, + params: {filter: 'hot', name: 'hive-125125'}, + }, + }, + }; + + const comp = ; + + const renderer = TestRenderer.create( + + {comp} + , + ); -it("(3) In section", () => { - const props = { - ...defProps, - ...{ - match: { - path: "...", - url: "/activities/hive-125125", - isExact: true, - params: {filter: "hot", name: "hive-125125"} - }, - } - }; - - const comp = ; - - const renderer = TestRenderer.create( - - {comp} - - ); - - expect(renderer.toJSON()).toMatchSnapshot(); + expect(renderer.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/community-menu/index.tsx b/src/common/components/community-menu/index.tsx index 3abc83e28b7..40eb7ca02fc 100644 --- a/src/common/components/community-menu/index.tsx +++ b/src/common/components/community-menu/index.tsx @@ -1,103 +1,142 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import {History, Location} from "history"; +import {History, Location} from 'history'; -import {Link} from "react-router-dom"; -import {match} from "react-router"; -import isEqual from "react-fast-compare"; +import {Link} from 'react-router-dom'; +import {match} from 'react-router'; +import isEqual from 'react-fast-compare'; -import {EntryFilter, Global} from "../../store/global/types"; -import {Community} from "../../store/communities/types"; +import {EntryFilter, Global} from '../../store/global/types'; +import {Community} from '../../store/communities/types'; -import ListStyleToggle from "../list-style-toggle/index"; -import DropDown, {MenuItem} from "../dropdown"; +import ListStyleToggle from '../list-style-toggle/index'; +import DropDown, {MenuItem} from '../dropdown'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import _c from "../../util/fix-class-names"; +import _c from '../../util/fix-class-names'; interface MatchParams { - filter: string; - name: string; + filter: string; + name: string; } interface Props { - history: History; - location: Location; - match: match; - global: Global; - community: Community; - toggleListStyle: (view: string | null) => void; + history: History; + location: Location; + match: match; + global: Global; + community: Community; + toggleListStyle: (view: string | null) => void; } export class CommunityMenu extends Component { - shouldComponentUpdate(nextProps: Readonly): boolean { - return !isEqual(this.props.location, nextProps.location) - || !isEqual(this.props.global, nextProps.global) - } - - render() { - const {community, match} = this.props; - const {filter, name} = match.params; - - const menuConfig: { - history: History, - label: string, - items: MenuItem[] - } = { - history: this.props.history, - label: filter === EntryFilter.trending ? _t('community.posts') : _t(`entry-filter.filter-${filter}`), - items: [ - ...[EntryFilter.trending, EntryFilter.hot, EntryFilter.created, EntryFilter.payout, EntryFilter.muted].map((x) => { - return { - label: _t(`entry-filter.filter-${x}`), - href: `/${x}/${community.name}`, - active: filter === x, - }; - }), - ], - }; - - return ( -
-
- <> - - - -
- {menuConfig.items.map(menuItem => - - {menuItem.label} - )} -
- - - - {_t('community.subscribers')} - - - {_t('community.activities')} - -
- - {EntryFilter[filter] &&
- -
} + shouldComponentUpdate(nextProps: Readonly): boolean { + return ( + !isEqual(this.props.location, nextProps.location) || + !isEqual(this.props.global, nextProps.global) + ); + } + + render() { + const {community, match} = this.props; + const {filter, name} = match.params; + + const menuConfig: { + history: History; + label: string; + items: MenuItem[]; + } = { + history: this.props.history, + label: + filter === EntryFilter.trending + ? _t('community.posts') + : _t(`entry-filter.filter-${filter}`), + items: [ + ...[ + EntryFilter.trending, + EntryFilter.hot, + EntryFilter.created, + EntryFilter.payout, + EntryFilter.muted, + ].map(x => { + return { + label: _t(`entry-filter.filter-${x}`), + href: `/${x}/${community.name}`, + active: filter === x, + }; + }), + ], + }; + + return ( +
+
+ <> + + + +
+ {menuConfig.items.map(menuItem => ( + + {menuItem.label} + + ))}
- ); - } + + + + {_t('community.subscribers')} + + + {_t('community.activities')} + +
+ + {EntryFilter[filter] && ( +
+ +
+ )} +
+ ); + } } export default (p: Props) => { - const props: Props = { - history: p.history, - location: p.location, - match: p.match, - global: p.global, - community: p.community, - toggleListStyle: p.toggleListStyle - } - - return -} + const props: Props = { + history: p.history, + location: p.location, + match: p.match, + global: p.global, + community: p.community, + toggleListStyle: p.toggleListStyle, + }; + + return ; +}; diff --git a/src/common/components/community-post-btn/index.spec.tsx b/src/common/components/community-post-btn/index.spec.tsx index 15369653fff..ba1bc310171 100644 --- a/src/common/components/community-post-btn/index.spec.tsx +++ b/src/common/components/community-post-btn/index.spec.tsx @@ -1,19 +1,19 @@ -import React from "react"; -import renderer from "react-test-renderer"; -import {createBrowserHistory} from "history"; +import React from 'react'; +import renderer from 'react-test-renderer'; +import {createBrowserHistory} from 'history'; -import CommunityPostBtn from "./index"; +import CommunityPostBtn from './index'; -import {communityInstance1} from "../../helper/test-helper"; +import {communityInstance1} from '../../helper/test-helper'; const defProps = { - history: createBrowserHistory(), - community: {...communityInstance1}, - buttonProps: {block: true} + history: createBrowserHistory(), + community: {...communityInstance1}, + buttonProps: {block: true}, }; -it("(1) Default render", () => { - const props = {...defProps}; - const component = renderer.create(); - expect(component.toJSON()).toMatchSnapshot(); +it('(1) Default render', () => { + const props = {...defProps}; + const component = renderer.create(); + expect(component.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/community-post-btn/index.tsx b/src/common/components/community-post-btn/index.tsx index 05433d10ee1..0924813db39 100644 --- a/src/common/components/community-post-btn/index.tsx +++ b/src/common/components/community-post-btn/index.tsx @@ -1,34 +1,34 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import {History} from "history"; +import {History} from 'history'; -import {Button} from "react-bootstrap"; +import {Button} from 'react-bootstrap'; -import {Community} from "../../store/communities/types"; +import {Community} from '../../store/communities/types'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; interface Props { - history: History; - community: Community; + history: History; + community: Community; } export class CommunityPostBtn extends Component { - clicked = () => { - const {community, history} = this.props; - history.push(`/submit?com=${community.name}`); - } - - render() { - return - } + clicked = () => { + const {community, history} = this.props; + history.push(`/submit?com=${community.name}`); + }; + + render() { + return ; + } } export default (p: Props) => { - const props: Props = { - history: p.history, - community: p.community - } + const props: Props = { + history: p.history, + community: p.community, + }; - return -} + return ; +}; diff --git a/src/common/components/community-rewards-registration/index.spec.tsx b/src/common/components/community-rewards-registration/index.spec.tsx index 1a1b559b52b..730dd24f84d 100644 --- a/src/common/components/community-rewards-registration/index.spec.tsx +++ b/src/common/components/community-rewards-registration/index.spec.tsx @@ -1,78 +1,92 @@ -import React from "react"; +import React from 'react'; -import {CommunityRewardsRegistration} from "./index"; +import {CommunityRewardsRegistration} from './index'; -import TestRenderer from "react-test-renderer"; +import TestRenderer from 'react-test-renderer'; -import {globalInstance, communityInstance1, activeUserMaker, allOver} from "../../helper/test-helper"; +import { + globalInstance, + communityInstance1, + activeUserMaker, + allOver, +} from '../../helper/test-helper'; let MOCK_MODE = 1; -jest.mock("../../api/private-api", () => ({ - getRewardedCommunities: () => - new Promise((resolve) => { - if (MOCK_MODE === 1) { - resolve([]); - } +jest.mock('../../api/private-api', () => ({ + getRewardedCommunities: () => + new Promise(resolve => { + if (MOCK_MODE === 1) { + resolve([]); + } - if (MOCK_MODE === 2) { - resolve([{ - name: communityInstance1.name - }]); - } - }), + if (MOCK_MODE === 2) { + resolve([ + { + name: communityInstance1.name, + }, + ]); + } + }), })); const defProps = { - global: globalInstance, - community: communityInstance1, - activeUser: activeUserMaker(communityInstance1.name), - signingKey: '', - setSigningKey: () => { - }, - onHide: () => { - } + global: globalInstance, + community: communityInstance1, + activeUser: activeUserMaker(communityInstance1.name), + signingKey: '', + setSigningKey: () => {}, + onHide: () => {}, }; - -it("(1) Default render", async () => { - const renderer = TestRenderer.create(); - await allOver(); - expect(renderer.toJSON()).toMatchSnapshot(); +it('(1) Default render', async () => { + const renderer = TestRenderer.create( + , + ); + await allOver(); + expect(renderer.toJSON()).toMatchSnapshot(); }); it("(2) Min subscribers count doesn't match", async () => { - const props = { - ...defProps, - community: { - ...communityInstance1, - subscribers: 99 - }, - } - const renderer = TestRenderer.create(); - await allOver(); - expect(renderer.toJSON()).toMatchSnapshot(); + const props = { + ...defProps, + community: { + ...communityInstance1, + subscribers: 99, + }, + }; + const renderer = TestRenderer.create( + , + ); + await allOver(); + expect(renderer.toJSON()).toMatchSnapshot(); }); -it("(3) Already registered", async () => { - MOCK_MODE = 2 - const renderer = TestRenderer.create(); - await allOver(); - expect(renderer.toJSON()).toMatchSnapshot(); +it('(3) Already registered', async () => { + MOCK_MODE = 2; + const renderer = TestRenderer.create( + , + ); + await allOver(); + expect(renderer.toJSON()).toMatchSnapshot(); }); -it("(4) Form screen", async () => { - const component = TestRenderer.create(); - await allOver(); - const instance: any = component.getInstance(); - instance.setState({form: true}); - expect(component.toJSON()).toMatchSnapshot(); +it('(4) Form screen', async () => { + const component = TestRenderer.create( + , + ); + await allOver(); + const instance: any = component.getInstance(); + instance.setState({form: true}); + expect(component.toJSON()).toMatchSnapshot(); }); -it("(5) Success screen", async () => { - const component = TestRenderer.create(); - await allOver(); - const instance: any = component.getInstance(); - instance.setState({done: true}); - expect(component.toJSON()).toMatchSnapshot(); +it('(5) Success screen', async () => { + const component = TestRenderer.create( + , + ); + await allOver(); + const instance: any = component.getInstance(); + instance.setState({done: true}); + expect(component.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/community-rewards-registration/index.tsx b/src/common/components/community-rewards-registration/index.tsx index f2f7fbc09f2..524bff473d0 100644 --- a/src/common/components/community-rewards-registration/index.tsx +++ b/src/common/components/community-rewards-registration/index.tsx @@ -1,165 +1,206 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import {Modal, Button} from "react-bootstrap"; +import {Modal, Button} from 'react-bootstrap'; -import {PrivateKey} from "@hiveio/dhive"; +import {PrivateKey} from '@hiveio/dhive'; -import {Global} from "../../store/global/types"; -import {Community} from "../../store/communities/types"; -import {ActiveUser} from "../../store/active-user/types"; +import {Global} from '../../store/global/types'; +import {Community} from '../../store/communities/types'; +import {ActiveUser} from '../../store/active-user/types'; +import BaseComponent from '../base'; +import {error} from '../feedback'; +import KeyOrHot from '../key-or-hot'; +import LinearProgress from '../linear-progress'; -import BaseComponent from "../base"; -import {error} from "../feedback"; -import KeyOrHot from "../key-or-hot"; -import LinearProgress from "../linear-progress"; - -import {communityRewardsRegister, communityRewardsRegisterHot, communityRewardsRegisterKc, formatError} from "../../api/operations"; -import {getRewardedCommunities} from "../../api/private-api"; - -import {_t} from "../../i18n"; +import { + communityRewardsRegister, + communityRewardsRegisterHot, + communityRewardsRegisterKc, + formatError, +} from '../../api/operations'; +import {getRewardedCommunities} from '../../api/private-api'; +import {_t} from '../../i18n'; interface Props { - global: Global; - community: Community; - activeUser: ActiveUser; - signingKey: string; - setSigningKey: (key: string) => void; - onHide: () => void; + global: Global; + community: Community; + activeUser: ActiveUser; + signingKey: string; + setSigningKey: (key: string) => void; + onHide: () => void; } interface State { - loading: boolean, - inProgress: boolean; - registered: boolean; - form: boolean; - done: boolean; + loading: boolean; + inProgress: boolean; + registered: boolean; + form: boolean; + done: boolean; } - export class CommunityRewardsRegistration extends BaseComponent { - state: State = { - loading: true, - inProgress: false, - registered: false, - form: false, - done: false, - } - - componentDidMount() { - const {community} = this.props; - - getRewardedCommunities().then(r => { - const registered = !!r.find(x => x.name === community.name); - this.stateSet({registered}); - }).finally(() => { - this.stateSet({loading: false}); - }) - } - - next = () => { - this.stateSet({form: true}) + state: State = { + loading: true, + inProgress: false, + registered: false, + form: false, + done: false, + }; + + componentDidMount() { + const {community} = this.props; + + getRewardedCommunities() + .then(r => { + const registered = !!r.find(x => x.name === community.name); + this.stateSet({registered}); + }) + .finally(() => { + this.stateSet({loading: false}); + }); + } + + next = () => { + this.stateSet({form: true}); + }; + + sign = (key: PrivateKey) => { + const {community} = this.props; + + this.stateSet({inProgress: true}); + communityRewardsRegister(key, community.name) + .then(r => { + this.stateSet({done: true}); + }) + .catch(err => { + error(formatError(err)); + }) + .finally(() => { + this.setState({inProgress: false}); + }); + }; + + hotSign = () => { + const {community} = this.props; + communityRewardsRegisterHot(community.name); + this.hide(); + }; + + signKs = () => { + const {community} = this.props; + + this.stateSet({inProgress: true}); + communityRewardsRegisterKc(community.name) + .then(r => { + this.stateSet({done: true}); + }) + .catch(err => { + error(formatError(err)); + }) + .finally(() => { + this.setState({inProgress: false}); + }); + }; + + hide = () => { + const {onHide} = this.props; + onHide(); + }; + + render() { + const {community} = this.props; + const {inProgress, loading, registered, form, done} = this.state; + + if (loading) { + return ; } - sign = (key: PrivateKey) => { - const {community} = this.props; - - this.stateSet({inProgress: true}); - communityRewardsRegister(key, community.name).then(r => { - this.stateSet({done: true}); - }).catch(err => { - error(formatError(err)); - }).finally(() => { - this.setState({inProgress: false}); - }); - } - - hotSign = () => { - const {community} = this.props; - communityRewardsRegisterHot(community.name); - this.hide(); + const btnClose = ( + + ); + const btnNext = ( + + ); + + if (done) { + return ( +
+

+ {_t('community-rewards-registration.done-body-text')} +

+ {btnClose} +
+ ); } - signKs = () => { - const {community} = this.props; - - this.stateSet({inProgress: true}); - communityRewardsRegisterKc(community.name).then(r => { - this.stateSet({done: true}); - }).catch(err => { - error(formatError(err)); - }).finally(() => { - this.setState({inProgress: false}); - }); + if (form) { + return ( +
+ {KeyOrHot({ + ...this.props, + inProgress, + onKey: this.sign, + onHot: this.hotSign, + onKc: this.signKs, + })} +
+ ); } - hide = () => { - const {onHide} = this.props; - onHide(); + if (registered) { + return ( +
+

+ {_t('community-rewards-registration.conflict-body-text')} +

+ {btnClose} +
+ ); } - render() { - const {community} = this.props; - const {inProgress, loading, registered, form, done} = this.state; - - if (loading) { - return - } - - const btnClose = ; - const btnNext = ; - - if (done) { - return
-

{_t("community-rewards-registration.done-body-text")}

- {btnClose} -
- } - - if (form) { - return
- {KeyOrHot({ - ...this.props, - inProgress, - onKey: this.sign, - onHot: this.hotSign, - onKc: this.signKs - })} -
- } - - if (registered) { - return
-

{_t("community-rewards-registration.conflict-body-text")}

- {btnClose} -
- } - - if (community.subscribers < 100) { - return
-

{_t("community-rewards-registration.min-required-body-text")}

- {btnClose} -
- } - - return
-

{_t("community-rewards-registration.body-text")}

- {btnNext} + if (community.subscribers < 100) { + return ( +
+

+ {_t('community-rewards-registration.min-required-body-text')} +

+ {btnClose}
+ ); } + + return ( +
+

{_t('community-rewards-registration.body-text')}

+ {btnNext} +
+ ); + } } export default class CommunityRewardsRegistrationDialog extends Component { - render() { - const {onHide} = this.props; - return ( - - - - - - - ); - } + render() { + const {onHide} = this.props; + return ( + + + + + + + ); + } } diff --git a/src/common/components/community-role-edit/index.spec.tsx b/src/common/components/community-role-edit/index.spec.tsx index 06da03b7d73..4ecd98080b9 100644 --- a/src/common/components/community-role-edit/index.spec.tsx +++ b/src/common/components/community-role-edit/index.spec.tsx @@ -1,27 +1,29 @@ -import React from "react"; +import React from 'react'; -import renderer from "react-test-renderer"; +import renderer from 'react-test-renderer'; -import {CommunityRoleEdit} from "./index"; +import {CommunityRoleEdit} from './index'; -import {roleMap} from "../../store/communities/types"; +import {roleMap} from '../../store/communities/types'; -import {communityInstance1, globalInstance, activeUserMaker} from "../../helper/test-helper"; +import { + communityInstance1, + globalInstance, + activeUserMaker, +} from '../../helper/test-helper'; -it("(1) Render", () => { - const props = { - global: globalInstance, - community: {...communityInstance1}, - activeUser: activeUserMaker("hive-148441"), - user: 'foo', - role: 'mod', - roles: roleMap["owner"], - addCommunity: () => { - }, - onHide: () => { - } - }; +it('(1) Render', () => { + const props = { + global: globalInstance, + community: {...communityInstance1}, + activeUser: activeUserMaker('hive-148441'), + user: 'foo', + role: 'mod', + roles: roleMap['owner'], + addCommunity: () => {}, + onHide: () => {}, + }; - const component = renderer.create(); - expect(component.toJSON()).toMatchSnapshot(); + const component = renderer.create(); + expect(component.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/community-role-edit/index.tsx b/src/common/components/community-role-edit/index.tsx index abe22dda30e..ac8db0bc4cb 100644 --- a/src/common/components/community-role-edit/index.tsx +++ b/src/common/components/community-role-edit/index.tsx @@ -1,166 +1,200 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import {Modal, Form, Row, Col, InputGroup, FormControl, Button} from "react-bootstrap"; +import { + Modal, + Form, + Row, + Col, + InputGroup, + FormControl, + Button, +} from 'react-bootstrap'; -import {Global} from "../../store/global/types"; -import {Community, CommunityTeam} from "../../store/communities/types"; -import {Account} from '../../store/accounts/types' -import {ActiveUser} from "../../store/active-user/types"; +import {Global} from '../../store/global/types'; +import {Community, CommunityTeam} from '../../store/communities/types'; +import {Account} from '../../store/accounts/types'; +import {ActiveUser} from '../../store/active-user/types'; -import BaseComponent from "../base"; -import LinearProgress from "../linear-progress"; -import {error} from "../feedback"; +import BaseComponent from '../base'; +import LinearProgress from '../linear-progress'; +import {error} from '../feedback'; -import {getAccount} from "../../api/hive"; +import {getAccount} from '../../api/hive'; -import {clone} from "../../store/util"; +import {clone} from '../../store/util'; -import { - setUserRole, - formatError -} from "../../api/operations"; +import {setUserRole, formatError} from '../../api/operations'; -import {_t} from "../../i18n"; -import {Tsx} from "../../i18n/helper"; +import {_t} from '../../i18n'; +import {Tsx} from '../../i18n/helper'; interface Props { - global: Global; - community: Community; - activeUser: ActiveUser; - user: string; - role: string; - roles: string[]; - addCommunity: (data: Community) => void; - onHide: () => void; + global: Global; + community: Community; + activeUser: ActiveUser; + user: string; + role: string; + roles: string[]; + addCommunity: (data: Community) => void; + onHide: () => void; } interface State { - user: string; - role: string; - userError: string; - inProgress: boolean; + user: string; + role: string; + userError: string; + inProgress: boolean; } export class CommunityRoleEdit extends BaseComponent { - state: State = { - user: this.props.user, - role: this.props.role || this.props.roles[0], - userError: '', - inProgress: false + state: State = { + user: this.props.user, + role: this.props.role || this.props.roles[0], + userError: '', + inProgress: false, + }; + + _input = React.createRef(); + + userChanged = ( + e: React.ChangeEvent, + ) => { + const {value: user} = e.target; + this.stateSet({user}); + }; + + roleChanged = ( + e: React.ChangeEvent, + ) => { + const {value: role} = e.target; + this.stateSet({role}); + }; + + submit = async () => { + const {user, role} = this.state; + const {community, activeUser, addCommunity, onHide} = this.props; + + if (user.trim() === '') { + this._input.current?.focus(); + return false; + } + + this.stateSet({inProgress: true, userError: ''}); + + let userData: Account | null | undefined; + + try { + userData = await getAccount(user); + } catch (e) { + userData = null; } - _input = React.createRef(); - - userChanged = (e: React.ChangeEvent) => { - const {value: user} = e.target; - this.stateSet({user}); - }; - - roleChanged = (e: React.ChangeEvent) => { - const {value: role} = e.target; - this.stateSet({role}); - }; - - submit = async () => { - const {user, role} = this.state; - const {community, activeUser, addCommunity, onHide} = this.props; - - if (user.trim() === '') { - this._input.current?.focus(); - return false; - } - - this.stateSet({inProgress: true, userError: ''}); - - let userData: Account | null | undefined; - - try { - userData = await getAccount(user); - } catch (e) { - userData = null; - } - - if (!userData) { - this.stateSet({inProgress: false, userError: _t("community-role-edit.user-not-found")}); - } - - return setUserRole(activeUser.username, community.name, user, role) - .then(() => { - const team: CommunityTeam = clone(community.team); - const nTeam = team.find(x => x[0] === user) === undefined - ? [...team, [user, role, ""]] - : team.map(x => x[0] === user ? [x[0], role, x[2]] : x); - const nCom: Community = {...clone(community), team: nTeam}; - addCommunity(nCom); - onHide(); - }) - .catch(err => error(formatError(err))) - .finally(() => this.stateSet({inProgress: false})); + if (!userData) { + this.stateSet({ + inProgress: false, + userError: _t('community-role-edit.user-not-found'), + }); } - render() { - const {roles} = this.props; - const {user, role, userError, inProgress} = this.state; - - return
- {inProgress && } -
- - - {_t("community-role-edit.username")} - - - - - @ - - - - {userError && {userError}} - - - - - {_t("community-role-edit.role")} - - - - {roles.map((r, i) => )} - - - -
- -
-
-
+ return setUserRole(activeUser.username, community.name, user, role) + .then(() => { + const team: CommunityTeam = clone(community.team); + const nTeam = + team.find(x => x[0] === user) === undefined + ? [...team, [user, role, '']] + : team.map(x => (x[0] === user ? [x[0], role, x[2]] : x)); + const nCom: Community = {...clone(community), team: nTeam}; + addCommunity(nCom); + onHide(); + }) + .catch(err => error(formatError(err))) + .finally(() => this.stateSet({inProgress: false})); + }; + + render() { + const {roles} = this.props; + const {user, role, userError, inProgress} = this.state; + + return ( +
+ {inProgress && } +
+ + + {_t('community-role-edit.username')} + + + + + @ + + + + {userError && ( + {userError} + )} + + + + + {_t('community-role-edit.role')} + + + + {roles.map((r, i) => ( + + ))} + + + +
+ +
- } + +
+ +
+ ); + } } export default class CommunityRoleEditDialog extends Component { - render() { - const {onHide} = this.props; - return ( - - - {_t("community-role-edit.title")} - - - - - - ); - } + render() { + const {onHide} = this.props; + return ( + + + {_t('community-role-edit.title')} + + + + + + ); + } } diff --git a/src/common/components/community-roles/index.spec.tsx b/src/common/components/community-roles/index.spec.tsx index bb29dda8ec1..f2324a928bb 100644 --- a/src/common/components/community-roles/index.spec.tsx +++ b/src/common/components/community-roles/index.spec.tsx @@ -1,63 +1,57 @@ -import React from "react"; - -import renderer from "react-test-renderer"; -import {createBrowserHistory, createLocation} from "history"; - -import {CommunityRoles} from "./index"; - -import {communityInstance1, globalInstance, activeUserMaker} from "../../helper/test-helper"; - -it("(1) Default render", () => { - const props = { - history: createBrowserHistory(), - location: createLocation({}), - global: globalInstance, - community: {...communityInstance1}, - activeUser: null, - addAccount: () => { - }, - addCommunity: () => { - - } - }; - - const component = renderer.create(); - expect(component.toJSON()).toMatchSnapshot(); +import React from 'react'; + +import renderer from 'react-test-renderer'; +import {createBrowserHistory, createLocation} from 'history'; + +import {CommunityRoles} from './index'; + +import { + communityInstance1, + globalInstance, + activeUserMaker, +} from '../../helper/test-helper'; + +it('(1) Default render', () => { + const props = { + history: createBrowserHistory(), + location: createLocation({}), + global: globalInstance, + community: {...communityInstance1}, + activeUser: null, + addAccount: () => {}, + addCommunity: () => {}, + }; + + const component = renderer.create(); + expect(component.toJSON()).toMatchSnapshot(); }); - -it("(2) Should not show add user button", () => { - const props = { - history: createBrowserHistory(), - location: createLocation({}), - global: globalInstance, - community: {...communityInstance1}, - activeUser: activeUserMaker("foo"), - addAccount: () => { - }, - addCommunity: () => { - - } - }; - - const component = renderer.create(); - expect(component.toJSON()).toMatchSnapshot(); +it('(2) Should not show add user button', () => { + const props = { + history: createBrowserHistory(), + location: createLocation({}), + global: globalInstance, + community: {...communityInstance1}, + activeUser: activeUserMaker('foo'), + addAccount: () => {}, + addCommunity: () => {}, + }; + + const component = renderer.create(); + expect(component.toJSON()).toMatchSnapshot(); }); -it("(3) Should show add user button", () => { - const props = { - history: createBrowserHistory(), - location: createLocation({}), - global: globalInstance, - community: {...communityInstance1}, - activeUser: activeUserMaker("hive-148441"), - addAccount: () => { - }, - addCommunity: () => { - - } - }; - - const component = renderer.create(); - expect(component.toJSON()).toMatchSnapshot(); +it('(3) Should show add user button', () => { + const props = { + history: createBrowserHistory(), + location: createLocation({}), + global: globalInstance, + community: {...communityInstance1}, + activeUser: activeUserMaker('hive-148441'), + addAccount: () => {}, + addCommunity: () => {}, + }; + + const component = renderer.create(); + expect(component.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/community-roles/index.tsx b/src/common/components/community-roles/index.tsx index e8778bc902f..374dbbe2ff8 100644 --- a/src/common/components/community-roles/index.tsx +++ b/src/common/components/community-roles/index.tsx @@ -1,122 +1,162 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import {History, Location} from "history"; +import {History, Location} from 'history'; -import isEqual from "react-fast-compare"; +import isEqual from 'react-fast-compare'; -import {Button} from "react-bootstrap"; +import {Button} from 'react-bootstrap'; -import {Global} from "../../store/global/types"; -import {Community, roleMap} from "../../store/communities/types"; -import {Account} from "../../store/accounts/types"; -import {ActiveUser} from "../../store/active-user/types"; +import {Global} from '../../store/global/types'; +import {Community, roleMap} from '../../store/communities/types'; +import {Account} from '../../store/accounts/types'; +import {ActiveUser} from '../../store/active-user/types'; -import ProfileLink from "../profile-link"; -import UserAvatar from "../user-avatar"; -import CommunityRoleEditDialog from "../community-role-edit"; +import ProfileLink from '../profile-link'; +import UserAvatar from '../user-avatar'; +import CommunityRoleEditDialog from '../community-role-edit'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; interface Props { - history: History; - location: Location; - global: Global; - community: Community; - activeUser: ActiveUser | null; - addAccount: (data: Account) => void; - addCommunity: (data: Community) => void; + history: History; + location: Location; + global: Global; + community: Community; + activeUser: ActiveUser | null; + addAccount: (data: Account) => void; + addCommunity: (data: Community) => void; } interface State { - dialog: boolean, - dialogUser: string, - dialogRole: string + dialog: boolean; + dialogUser: string; + dialogRole: string; } - export class CommunityRoles extends Component { - state: State = { - dialog: false, - dialogUser: '', - dialogRole: '', - } - - shouldComponentUpdate(nextProps: Readonly, nextState: Readonly): boolean { - return !isEqual(this.props.community, nextProps.community) - || !isEqual(this.props.activeUser, nextProps.activeUser) - || !isEqual(this.state, nextProps.activeUser) - } - - showDialog = (user: string = '', role: string = '') => { - this.setState({dialog: true, dialogUser: user, dialogRole: role}); - } - - hideDialog = () => { - this.setState({dialog: false, dialogUser: '', dialogRole: ''}); - } - - render() { - const {dialog, dialogUser, dialogRole} = this.state; - const {community, activeUser} = this.props; - - const role = community.team.find(x => x[0] === activeUser?.username); - const roleInTeam = role ? role[1] : null; - - const roles = roleInTeam ? roleMap[roleInTeam] : []; - - return ( -
-

{_t('community.roles-title')}

- - - - - - - - - - {community.team.map((t, i) => { - const [username, role, title] = t; - const canEdit = roles && roles.includes(role); - return - - - - + state: State = { + dialog: false, + dialogUser: '', + dialogRole: '', + }; + + shouldComponentUpdate( + nextProps: Readonly, + nextState: Readonly, + ): boolean { + return ( + !isEqual(this.props.community, nextProps.community) || + !isEqual(this.props.activeUser, nextProps.activeUser) || + !isEqual(this.state, nextProps.activeUser) + ); + } + + showDialog = (user: string = '', role: string = '') => { + this.setState({dialog: true, dialogUser: user, dialogRole: role}); + }; + + hideDialog = () => { + this.setState({dialog: false, dialogUser: '', dialogRole: ''}); + }; + + render() { + const {dialog, dialogUser, dialogRole} = this.state; + const {community, activeUser} = this.props; + + const role = community.team.find(x => x[0] === activeUser?.username); + const roleInTeam = role ? role[1] : null; + + const roles = roleInTeam ? roleMap[roleInTeam] : []; + + return ( +
+

{_t('community.roles-title')}

+
{_t('community.roles-account')}{_t('community.roles-role')}{_t('community.roles-account-title')}
- {ProfileLink({ - ...this.props, - username, - children: {UserAvatar({...this.props, username, size: "medium"})} {username} - })}{canEdit ? { - e.preventDefault(); - this.showDialog(username, role); - }}>{role} : role}{title}
+ + + + + + + + + {community.team.map((t, i) => { + const [username, role, title] = t; + const canEdit = roles && roles.includes(role); + return ( + + -
{_t('community.roles-account')}{_t('community.roles-role')}{_t('community.roles-account-title')}
+ {ProfileLink({ + ...this.props, + username, + children: ( + + {UserAvatar({ + ...this.props, + username, + size: 'medium', + })}{' '} + {username} + + ), })} -
- {roles.length > 0 && } - - {dialog && ( - - )} -
- ); - } + + + {canEdit ? ( + { + e.preventDefault(); + this.showDialog(username, role); + }} + > + {role} + + ) : ( + role + )} + + {title} + + ); + })} + + + {roles.length > 0 && ( + + )} + + {dialog && ( + + )} +
+ ); + } } export default (p: Props) => { - const props: Props = { - history: p.history, - location: p.location, - global: p.global, - community: p.community, - activeUser: p.activeUser, - addAccount: p.addAccount, - addCommunity: p.addCommunity - } - - return -} + const props: Props = { + history: p.history, + location: p.location, + global: p.global, + community: p.community, + activeUser: p.activeUser, + addAccount: p.addAccount, + addCommunity: p.addCommunity, + }; + + return ; +}; diff --git a/src/common/components/community-selector/index.spec.tsx b/src/common/components/community-selector/index.spec.tsx index e767b196c16..be82a8ed4cd 100644 --- a/src/common/components/community-selector/index.spec.tsx +++ b/src/common/components/community-selector/index.spec.tsx @@ -1,101 +1,101 @@ -import React from "react"; -import renderer from "react-test-renderer"; +import React from 'react'; +import renderer from 'react-test-renderer'; -import {CommunitySelector, Browser} from "./index" +import {CommunitySelector, Browser} from './index'; -import {globalInstance, activeUserMaker, allOver} from "../../helper/test-helper"; +import { + globalInstance, + activeUserMaker, + allOver, +} from '../../helper/test-helper'; let MOCK_MODE_1 = 1; -jest.mock("../../api/bridge", () => ({ - getCommunity: (name: string) => - new Promise((resolve) => { +jest.mock('../../api/bridge', () => ({ + getCommunity: (name: string) => + new Promise(resolve => { + if (name === 'hive-125125') { + resolve({ + name: 'hive-125125', + title: 'Ecency', + }); + return; + } - if (name === "hive-125125") { - resolve({ - name: "hive-125125", - title: "Ecency" - }); - return; - } - - resolve(null); - }), - getSubscriptions: () => new Promise((resolve) => { - resolve([ - ["hive-125125", "Ecency"], - ["hive-131131", "Foo"], - ["hive-145145", "Bar"] - ]); - }), - } - -)); + resolve(null); + }), + getSubscriptions: () => + new Promise(resolve => { + resolve([ + ['hive-125125', 'Ecency'], + ['hive-131131', 'Foo'], + ['hive-145145', 'Bar'], + ]); + }), +})); const defProps = { - global: globalInstance, - activeUser: activeUserMaker("foo"), - onSelect: () => { - } -} + global: globalInstance, + activeUser: activeUserMaker('foo'), + onSelect: () => {}, +}; it('(1) Empty tags.', async () => { - const props = { - ...defProps, - tags: [] - } - const component = renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + const props = { + ...defProps, + tags: [], + }; + const component = renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); it('(2) Tags with no community', async () => { - const props = { - ...defProps, - tags: ["foo", "bar"] - } - const component = renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + const props = { + ...defProps, + tags: ['foo', 'bar'], + }; + const component = renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); it('(3) Tags with community. but in the end', async () => { - const props = { - ...defProps, - tags: ["foo", "bar", "hive-125125"] - } - const component = renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + const props = { + ...defProps, + tags: ['foo', 'bar', 'hive-125125'], + }; + const component = renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); it('(4) Tags with community.', async () => { - const props = { - ...defProps, - tags: ["hive-125125", "foo", "bar"] - } - const component = renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + const props = { + ...defProps, + tags: ['hive-125125', 'foo', 'bar'], + }; + const component = renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); it('(5) Tags with community. But not valid', async () => { - const props = { - ...defProps, - tags: ["hive-122122", "foo", "bar"] - } - const component = renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + const props = { + ...defProps, + tags: ['hive-122122', 'foo', 'bar'], + }; + const component = renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); it('(6) Browser', async () => { - const props = { - ...defProps, - onHide: () => { - } - } - const component = renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + const props = { + ...defProps, + onHide: () => {}, + }; + const component = renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/community-selector/index.tsx b/src/common/components/community-selector/index.tsx index dd81e6763d1..d99fb6afffe 100644 --- a/src/common/components/community-selector/index.tsx +++ b/src/common/components/community-selector/index.tsx @@ -1,287 +1,369 @@ -import React from "react"; +import React from 'react'; -import {Modal} from "react-bootstrap"; +import {Modal} from 'react-bootstrap'; -import {FormControl} from "react-bootstrap"; +import {FormControl} from 'react-bootstrap'; -import isEqual from "react-fast-compare"; +import isEqual from 'react-fast-compare'; -import {ActiveUser} from "../../store/active-user/types"; -import {Community} from "../../store/communities/types"; -import {Global} from "../../store/global/types"; -import {Subscription} from "../../store/subscriptions/types"; +import {ActiveUser} from '../../store/active-user/types'; +import {Community} from '../../store/communities/types'; +import {Global} from '../../store/global/types'; +import {Subscription} from '../../store/subscriptions/types'; -import BaseComponent from "../base"; -import UserAvatar from "../user-avatar/index"; +import BaseComponent from '../base'; +import UserAvatar from '../user-avatar/index'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import isCommunity from "../../helper/is-community"; +import isCommunity from '../../helper/is-community'; -import {getCommunities, getCommunity, getSubscriptions} from "../../api/bridge"; +import {getCommunities, getCommunity, getSubscriptions} from '../../api/bridge'; -import {menuDownSvg} from "../../img/svg"; +import {menuDownSvg} from '../../img/svg'; interface BrowserProps { - global: Global; - activeUser: ActiveUser; - onSelect: (name: string | null) => void; - onHide: () => void; + global: Global; + activeUser: ActiveUser; + onSelect: (name: string | null) => void; + onHide: () => void; } interface BrowserState { - subscriptions: Subscription[], - query: string, - results: Community[] + subscriptions: Subscription[]; + query: string; + results: Community[]; } export class Browser extends BaseComponent { - state: BrowserState = { - subscriptions: [], - query: "", - results: [], + state: BrowserState = { + subscriptions: [], + query: '', + results: [], + }; + + _timer: any = null; + + componentDidMount() { + this.fetchSubscriptions().then(); + document.getElementById('search-communities-input')?.focus(); + } + + fetchSubscriptions = () => { + const {activeUser} = this.props; + return getSubscriptions(activeUser.username).then(subscriptions => { + if (subscriptions) { + this.stateSet({subscriptions}); + } + }); + }; + + queryChanged = ( + e: React.ChangeEvent, + ) => { + if (this._timer) { + clearTimeout(this._timer); + this._timer = null; } - _timer: any = null; - - componentDidMount() { - this.fetchSubscriptions().then(); - document.getElementById("search-communities-input")?.focus() - } - - fetchSubscriptions = () => { - const {activeUser} = this.props; - return getSubscriptions(activeUser.username).then(subscriptions => { - if (subscriptions) { - this.stateSet({subscriptions}) - } - }) - } - - queryChanged = (e: React.ChangeEvent) => { - if (this._timer) { - clearTimeout(this._timer); - this._timer = null; - } - - this.stateSet({query: e.target.value}, () => { - this._timer = setTimeout(() => { - this.search(); - }, 200); - }); - } - - search = () => { - const {query} = this.state; - - getCommunities("", 14, query, "rank") - .then((results) => { - if (results) { - this.stateSet({results}); - } - }) - } - - render() { - const {activeUser, onSelect, onHide} = this.props; - const {subscriptions, results, query} = this.state; - - const search =
- -
- - if (query) { - return
- {search} - -
-
- {results.length > 0 && ( - <> - {results.map(x => { - e.preventDefault(); - onSelect(x.name); - onHide(); - }}> -
- {UserAvatar({...this.props, username: x.name, size: "small"})} -
- {x.title} -
-
-
)} - - )} - {results.length === 0 &&
{_t("g.empty-list")}
} -
-
-
- } - - return
- {search} - -
-
- { + this.stateSet({query: e.target.value}, () => { + this._timer = setTimeout(() => { + this.search(); + }, 200); + }); + }; + + search = () => { + const {query} = this.state; + + getCommunities('', 14, query, 'rank').then(results => { + if (results) { + this.stateSet({results}); + } + }); + }; + + render() { + const {activeUser, onSelect, onHide} = this.props; + const {subscriptions, results, query} = this.state; + + const search = ( +
+ +
+ ); + + if (query) { + return ( +
+
+ ); } + + return ( + + ); + } } interface Props { - global: Global; - activeUser: ActiveUser; - tags: string[], - onSelect: (prev: string | null, next: string | null) => void; + global: Global; + activeUser: ActiveUser; + tags: string[]; + onSelect: (prev: string | null, next: string | null) => void; } interface State { - community: Community | null; - visible: boolean; - picked: boolean + community: Community | null; + visible: boolean; + picked: boolean; } export class CommunitySelector extends BaseComponent { - state: State = { - community: null, - visible: false, - picked: false + state: State = { + community: null, + visible: false, + picked: false, + }; + + componentDidMount() { + this.detectCommunity().then(); + } + + componentDidUpdate(prevProps: Readonly, prevState: Readonly) { + if (!isEqual(this.props.tags, prevProps.tags)) { + if (this.props.tags.length > 0) { + this.setState({picked: false}); + } + this.detectCommunity().then(); } + } - componentDidMount() { - this.detectCommunity().then(); - } + extractCommunityName = (): string | null => { + const {tags} = this.props; + const [tag] = tags; - componentDidUpdate(prevProps: Readonly, prevState: Readonly) { - if (!isEqual(this.props.tags, prevProps.tags)) { - if(this.props.tags.length > 0){ - this.setState({ picked: false }); - } - this.detectCommunity().then(); - } + if (!tag) { + return null; } - extractCommunityName = (): string | null => { - const {tags} = this.props; - const [tag,] = tags; - - if (!tag) { - return null; - } - - if (!isCommunity(tag)) { - return null; - } - - return tag; + if (!isCommunity(tag)) { + return null; } - detectCommunity = async () => { - const name = this.extractCommunityName(); + return tag; + }; - if (!name) { - this.stateSet({community: null}); - return - } + detectCommunity = async () => { + const name = this.extractCommunityName(); - let community: Community | null; + if (!name) { + this.stateSet({community: null}); + return; + } - try { - community = await getCommunity(name); - } catch (e) { - community = null; - } + let community: Community | null; - this.stateSet({community}); + try { + community = await getCommunity(name); + } catch (e) { + community = null; } - toggle = () => { - const {visible} = this.state; - this.stateSet({visible: !visible}); - } + this.stateSet({community}); + }; - render() { - const {activeUser, tags, onSelect} = this.props; - const {community, visible, picked} = this.state; - - let content; - if (community) { - content = <> - {UserAvatar({...this.props, username: community.name, size: "small"})} - {community.title} {menuDownSvg} - ; - } else { - - if (tags.length > 0 || picked) { - content = <> - {UserAvatar({...this.props, username: activeUser.username, size: "small"})} - {_t("community-selector.my-blog")} {menuDownSvg} - - } else { - content = <>{_t("community-selector.choose")} {menuDownSvg} - } - } + toggle = () => { + const {visible} = this.state; + this.stateSet({visible: !visible}); + }; - return <> - { - e.preventDefault(); - this.toggle(); - }}>{content} - - {visible && ( - - - - - { - const prev = this.extractCommunityName(); - onSelect(prev, name); - this.stateSet({picked: true}); - }}/> - - - )} + render() { + const {activeUser, tags, onSelect} = this.props; + const {community, visible, picked} = this.state; + + let content; + if (community) { + content = ( + <> + {UserAvatar({...this.props, username: community.name, size: 'small'})} + {community.title} {menuDownSvg} + ); + } else { + if (tags.length > 0 || picked) { + content = ( + <> + {UserAvatar({ + ...this.props, + username: activeUser.username, + size: 'small', + })} + {_t('community-selector.my-blog')}{' '} + {menuDownSvg} + + ); + } else { + content = ( + <> + {_t('community-selector.choose')}{' '} + {menuDownSvg} + + ); + } } + + return ( + <> + { + e.preventDefault(); + this.toggle(); + }} + > + {content} + + + {visible && ( + + + + + { + const prev = this.extractCommunityName(); + onSelect(prev, name); + this.stateSet({picked: true}); + }} + /> + + + )} + + ); + } } export default (p: Props) => { - const props: Props = { - global: p.global, - activeUser: p.activeUser, - tags: p.tags, - onSelect: p.onSelect - }; - - return -} + const props: Props = { + global: p.global, + activeUser: p.activeUser, + tags: p.tags, + onSelect: p.onSelect, + }; + + return ; +}; diff --git a/src/common/components/community-settings/index.spec.tsx b/src/common/components/community-settings/index.spec.tsx index 6986e2aa96c..765573d1ae1 100644 --- a/src/common/components/community-settings/index.spec.tsx +++ b/src/common/components/community-settings/index.spec.tsx @@ -1,22 +1,24 @@ -import React from "react"; +import React from 'react'; -import renderer from "react-test-renderer"; +import renderer from 'react-test-renderer'; -import {CommunitySettings} from "./index"; +import {CommunitySettings} from './index'; -import {communityInstance1, globalInstance, activeUserMaker} from "../../helper/test-helper"; +import { + communityInstance1, + globalInstance, + activeUserMaker, +} from '../../helper/test-helper'; -it("(1) Render", () => { - const props = { - global: globalInstance, - community: {...communityInstance1}, - activeUser: activeUserMaker("foo"), - addCommunity: () => { - }, - onHide: () => { - } - }; +it('(1) Render', () => { + const props = { + global: globalInstance, + community: {...communityInstance1}, + activeUser: activeUserMaker('foo'), + addCommunity: () => {}, + onHide: () => {}, + }; - const component = renderer.create(); - expect(component.toJSON()).toMatchSnapshot(); + const component = renderer.create(); + expect(component.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/community-settings/index.tsx b/src/common/components/community-settings/index.tsx index 20bffec9260..b0d364bc961 100644 --- a/src/common/components/community-settings/index.tsx +++ b/src/common/components/community-settings/index.tsx @@ -1,309 +1,353 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import {Modal, Form, Row, Col, InputGroup, FormControl, Button} from "react-bootstrap"; +import { + Modal, + Form, + Row, + Col, + InputGroup, + FormControl, + Button, +} from 'react-bootstrap'; -import {Global} from "../../store/global/types"; -import {Community} from "../../store/communities/types"; -import {ActiveUser} from "../../store/active-user/types"; +import {Global} from '../../store/global/types'; +import {Community} from '../../store/communities/types'; +import {ActiveUser} from '../../store/active-user/types'; -import {clone} from "../../store/util"; -import cleanString from "../../util/clean-string"; +import {clone} from '../../store/util'; +import cleanString from '../../util/clean-string'; -import BaseComponent from "../base"; -import LinearProgress from "../linear-progress"; -import {error} from "../feedback"; +import BaseComponent from '../base'; +import LinearProgress from '../linear-progress'; +import {error} from '../feedback'; -import { - updateCommunity, - formatError -} from "../../api/operations"; +import {updateCommunity, formatError} from '../../api/operations'; -import {_t} from "../../i18n"; -import { handleInvalid, handleOnInput } from "../../util/input-util"; +import {_t} from '../../i18n'; +import {handleInvalid, handleOnInput} from '../../util/input-util'; const langOpts = [ - {id: "af", name: "Afrikaans"}, - {id: "sq", name: "Albanian"}, - {id: "am", name: "Amharic"}, - {id: "ar", name: "Arabic"}, - {id: "hy", name: "Armenian"}, - {id: "az", name: "Azerbaijani"}, - {id: "eu", name: "Basque"}, - {id: "be", name: "Belarusian"}, - {id: "bn", name: "Bengali"}, - {id: "bs", name: "Bosnian"}, - {id: "bg", name: "Bulgarian"}, - {id: "my", name: "Burmese"}, - {id: "ca", name: "Catalan"}, - {id: "ny", name: "Chewa"}, - {id: "zh", name: "Chinese"}, - {id: "co", name: "Corsican"}, - {id: "hr", name: "Croatian"}, - {id: "cs", name: "Czech"}, - {id: "da", name: "Danish"}, - {id: "nl", name: "Dutch"}, - {id: "en", name: "English"}, - {id: "eo", name: "Esperanto"}, - {id: "et", name: "Estonian"}, - {id: "fi", name: "Finnish"}, - {id: "fr", name: "French"}, - {id: "gl", name: "Galician"}, - {id: "ka", name: "Georgian"}, - {id: "de", name: "German"}, - {id: "el", name: "Greek"}, - {id: "gu", name: "Gujarati"}, - {id: "ht", name: "Haitian Creole"}, - {id: "ha", name: "Hausa"}, - {id: "he", name: "Hebrew"}, - {id: "hi", name: "Hindi"}, - {id: "hu", name: "Hungarian"}, - {id: "is", name: "Icelandic"}, - {id: "ig", name: "Igbo"}, - {id: "id", name: "Indonesian"}, - {id: "ga", name: "Irish"}, - {id: "it", name: "Italian"}, - {id: "ja", name: "Japanese"}, - {id: "jv", name: "Javanese"}, - {id: "kn", name: "Kannada"}, - {id: "kk", name: "Kazakh"}, - {id: "rw", name: "Kinyarwanda"}, - {id: "ko", name: "Korean"}, - {id: "ku", name: "Kurdish"}, - {id: "ky", name: "Kyrgyz"}, - {id: "lo", name: "Lao"}, - {id: "la", name: "Latin"}, - {id: "lv", name: "Latvian"}, - {id: "lt", name: "Lithuanian"}, - {id: "lb", name: "Luxembourgish"}, - {id: "mk", name: "Macedonian"}, - {id: "mg", name: "Malagasy"}, - {id: "ms", name: "Malay"}, - {id: "ml", name: "Malayalam"}, - {id: "mt", name: "Maltese"}, - {id: "mi", name: "Maori"}, - {id: "mr", name: "Marathi"}, - {id: "mn", name: "Mongolian"}, - {id: "ne", name: "Nepali"}, - {id: "nb", name: "Norwegian (Bokmål)"}, - {id: "ps", name: "Pashto"}, - {id: "fa", name: "Persian"}, - {id: "pl", name: "Polish"}, - {id: "pt", name: "Portuguese"}, - {id: "pa", name: "Punjabi (Gurmukhi)"}, - {id: "ro", name: "Romanian"}, - {id: "ru", name: "Russian"}, - {id: "sm", name: "Samoan"}, - {id: "sr", name: "Serbian"}, - {id: "sn", name: "Shona"}, - {id: "sd", name: "Sindhi"}, - {id: "si", name: "Sinhala"}, - {id: "sk", name: "Slovak"}, - {id: "sl", name: "Slovenian"}, - {id: "so", name: "Somali"}, - {id: "es", name: "Spanish"}, - {id: "su", name: "Sundanese"}, - {id: "sw", name: "Swahili"}, - {id: "sv", name: "Swedish"}, - {id: "tg", name: "Tajik"}, - {id: "ta", name: "Tamil"}, - {id: "tt", name: "Tatar"}, - {id: "te", name: "Telugu"}, - {id: "th", name: "Thai"}, - {id: "tr", name: "Turkish"}, - {id: "tk", name: "Turkmen"}, - {id: "uk", name: "Ukrainian"}, - {id: "ur", name: "Urdu"}, - {id: "ug", name: "Uyghur"}, - {id: "uz", name: "Uzbek"}, - {id: "vi", name: "Vietnamese"}, - {id: "cy", name: "Welsh"}, - {id: "xh", name: "Xhosa"}, - {id: "yi", name: "Yiddish"}, - {id: "yo", name: "Yoruba"}, - {id: "zu", name: "Zulu"}, + {id: 'af', name: 'Afrikaans'}, + {id: 'sq', name: 'Albanian'}, + {id: 'am', name: 'Amharic'}, + {id: 'ar', name: 'Arabic'}, + {id: 'hy', name: 'Armenian'}, + {id: 'az', name: 'Azerbaijani'}, + {id: 'eu', name: 'Basque'}, + {id: 'be', name: 'Belarusian'}, + {id: 'bn', name: 'Bengali'}, + {id: 'bs', name: 'Bosnian'}, + {id: 'bg', name: 'Bulgarian'}, + {id: 'my', name: 'Burmese'}, + {id: 'ca', name: 'Catalan'}, + {id: 'ny', name: 'Chewa'}, + {id: 'zh', name: 'Chinese'}, + {id: 'co', name: 'Corsican'}, + {id: 'hr', name: 'Croatian'}, + {id: 'cs', name: 'Czech'}, + {id: 'da', name: 'Danish'}, + {id: 'nl', name: 'Dutch'}, + {id: 'en', name: 'English'}, + {id: 'eo', name: 'Esperanto'}, + {id: 'et', name: 'Estonian'}, + {id: 'fi', name: 'Finnish'}, + {id: 'fr', name: 'French'}, + {id: 'gl', name: 'Galician'}, + {id: 'ka', name: 'Georgian'}, + {id: 'de', name: 'German'}, + {id: 'el', name: 'Greek'}, + {id: 'gu', name: 'Gujarati'}, + {id: 'ht', name: 'Haitian Creole'}, + {id: 'ha', name: 'Hausa'}, + {id: 'he', name: 'Hebrew'}, + {id: 'hi', name: 'Hindi'}, + {id: 'hu', name: 'Hungarian'}, + {id: 'is', name: 'Icelandic'}, + {id: 'ig', name: 'Igbo'}, + {id: 'id', name: 'Indonesian'}, + {id: 'ga', name: 'Irish'}, + {id: 'it', name: 'Italian'}, + {id: 'ja', name: 'Japanese'}, + {id: 'jv', name: 'Javanese'}, + {id: 'kn', name: 'Kannada'}, + {id: 'kk', name: 'Kazakh'}, + {id: 'rw', name: 'Kinyarwanda'}, + {id: 'ko', name: 'Korean'}, + {id: 'ku', name: 'Kurdish'}, + {id: 'ky', name: 'Kyrgyz'}, + {id: 'lo', name: 'Lao'}, + {id: 'la', name: 'Latin'}, + {id: 'lv', name: 'Latvian'}, + {id: 'lt', name: 'Lithuanian'}, + {id: 'lb', name: 'Luxembourgish'}, + {id: 'mk', name: 'Macedonian'}, + {id: 'mg', name: 'Malagasy'}, + {id: 'ms', name: 'Malay'}, + {id: 'ml', name: 'Malayalam'}, + {id: 'mt', name: 'Maltese'}, + {id: 'mi', name: 'Maori'}, + {id: 'mr', name: 'Marathi'}, + {id: 'mn', name: 'Mongolian'}, + {id: 'ne', name: 'Nepali'}, + {id: 'nb', name: 'Norwegian (Bokmål)'}, + {id: 'ps', name: 'Pashto'}, + {id: 'fa', name: 'Persian'}, + {id: 'pl', name: 'Polish'}, + {id: 'pt', name: 'Portuguese'}, + {id: 'pa', name: 'Punjabi (Gurmukhi)'}, + {id: 'ro', name: 'Romanian'}, + {id: 'ru', name: 'Russian'}, + {id: 'sm', name: 'Samoan'}, + {id: 'sr', name: 'Serbian'}, + {id: 'sn', name: 'Shona'}, + {id: 'sd', name: 'Sindhi'}, + {id: 'si', name: 'Sinhala'}, + {id: 'sk', name: 'Slovak'}, + {id: 'sl', name: 'Slovenian'}, + {id: 'so', name: 'Somali'}, + {id: 'es', name: 'Spanish'}, + {id: 'su', name: 'Sundanese'}, + {id: 'sw', name: 'Swahili'}, + {id: 'sv', name: 'Swedish'}, + {id: 'tg', name: 'Tajik'}, + {id: 'ta', name: 'Tamil'}, + {id: 'tt', name: 'Tatar'}, + {id: 'te', name: 'Telugu'}, + {id: 'th', name: 'Thai'}, + {id: 'tr', name: 'Turkish'}, + {id: 'tk', name: 'Turkmen'}, + {id: 'uk', name: 'Ukrainian'}, + {id: 'ur', name: 'Urdu'}, + {id: 'ug', name: 'Uyghur'}, + {id: 'uz', name: 'Uzbek'}, + {id: 'vi', name: 'Vietnamese'}, + {id: 'cy', name: 'Welsh'}, + {id: 'xh', name: 'Xhosa'}, + {id: 'yi', name: 'Yiddish'}, + {id: 'yo', name: 'Yoruba'}, + {id: 'zu', name: 'Zulu'}, ]; interface Props { - global: Global; - community: Community; - activeUser: ActiveUser; - addCommunity: (data: Community) => void; - onHide: () => void; + global: Global; + community: Community; + activeUser: ActiveUser; + addCommunity: (data: Community) => void; + onHide: () => void; } interface State { - title: string; - about: string; - lang: string; - description: string; - flag_text: string; - is_nsfw: boolean; - inProgress: boolean; + title: string; + about: string; + lang: string; + description: string; + flag_text: string; + is_nsfw: boolean; + inProgress: boolean; } const pureState = (props: Props): State => { - const {community: c} = props; + const {community: c} = props; - return { - title: cleanString(c.title), - about: cleanString(c.about), - lang: c.lang || "en", - description: cleanString(c.description), - flag_text: cleanString(c.flag_text), - is_nsfw: c.is_nsfw, - inProgress: false - } -} + return { + title: cleanString(c.title), + about: cleanString(c.about), + lang: c.lang || 'en', + description: cleanString(c.description), + flag_text: cleanString(c.flag_text), + is_nsfw: c.is_nsfw, + inProgress: false, + }; +}; export class CommunitySettings extends BaseComponent { - state: State = pureState(this.props); + state: State = pureState(this.props); - form = React.createRef(); + form = React.createRef(); - onChange = (e: React.ChangeEvent): void => { - const {target: el} = e; - const key = el.name; - const val = el.hasOwnProperty("checked") ? el.checked : cleanString(el.value); + onChange = ( + e: React.ChangeEvent, + ): void => { + const {target: el} = e; + const key = el.name; + const val = el.hasOwnProperty('checked') + ? el.checked + : cleanString(el.value); - // @ts-ignore - this.stateSet({[key]: val}); - } + // @ts-ignore + this.stateSet({[key]: val}); + }; - submit = () => { - const {activeUser, community, addCommunity, onHide} = this.props; - const {title, about, description, lang, flag_text, is_nsfw} = this.state; - const newProps = {title, about, description, lang, flag_text, is_nsfw}; + submit = () => { + const {activeUser, community, addCommunity, onHide} = this.props; + const {title, about, description, lang, flag_text, is_nsfw} = this.state; + const newProps = {title, about, description, lang, flag_text, is_nsfw}; - this.stateSet({inProgress: true}); - return updateCommunity(activeUser.username, community.name, newProps) - .then(() => { - const nCom: Community = {...clone(community), ...newProps}; - addCommunity(nCom); - onHide(); - }) - .catch(err => error(formatError(err))) - .finally(() => this.stateSet({inProgress: false})); - } + this.stateSet({inProgress: true}); + return updateCommunity(activeUser.username, community.name, newProps) + .then(() => { + const nCom: Community = {...clone(community), ...newProps}; + addCommunity(nCom); + onHide(); + }) + .catch(err => error(formatError(err))) + .finally(() => this.stateSet({inProgress: false})); + }; - render() { - const {title, about, lang, description, flag_text, is_nsfw, inProgress} = this.state; + render() { + const {title, about, lang, description, flag_text, is_nsfw, inProgress} = + this.state; - return
- {inProgress && } + return ( +
+ {inProgress && } -
{ - e.preventDefault(); - e.stopPropagation(); + { + e.preventDefault(); + e.stopPropagation(); - if (!this.form.current?.checkValidity()) { - return; - } + if (!this.form.current?.checkValidity()) { + return; + } - this.submit().then(); - }}> - - {_t('community-settings.title')} - - - handleInvalid(e, 'community-settings.', 'validation-title')} - onInput={handleOnInput} - /> - - - - - {_t('community-settings.about')} - - - - - - - - {_t('community-settings.lang')} - - - - {langOpts.map((l, k) => )} - - - - - - {_t('community-settings.description')} - - - - - - - - {_t('community-settings.rules')} - - - - - {_t('community-settings.rules-help')} - - - - - - - -
- -
-
-
- } + this.submit().then(); + }} + > + + + {_t('community-settings.title')} + + + + + handleInvalid(e, 'community-settings.', 'validation-title') + } + onInput={handleOnInput} + /> + + + + + + {_t('community-settings.about')} + + + + + + + + + + {_t('community-settings.lang')} + + + + + {langOpts.map((l, k) => ( + + ))} + + + + + + + {_t('community-settings.description')} + + + + + + + + + + {_t('community-settings.rules')} + + + + + + {_t('community-settings.rules-help')} + + + + + + + +
+ +
+ +
+ ); + } } export default class CommunitySettingsDialog extends Component { - render() { - const {onHide} = this.props; - return ( - - - {_t('community-settings.dialog-title')} - - - - - - ); - } + render() { + const {onHide} = this.props; + return ( + + + {_t('community-settings.dialog-title')} + + + + + + ); + } } diff --git a/src/common/components/community-subscribers/index.spec.tsx b/src/common/components/community-subscribers/index.spec.tsx index 785ccd1ac67..6671d8fccd6 100644 --- a/src/common/components/community-subscribers/index.spec.tsx +++ b/src/common/components/community-subscribers/index.spec.tsx @@ -1,66 +1,68 @@ import React from 'react'; -import renderer from "react-test-renderer"; +import renderer from 'react-test-renderer'; -import {createBrowserHistory} from "history"; +import {createBrowserHistory} from 'history'; import {Subscribers} from './index'; -import {globalInstance, communityInstance1, activeUserMaker, allOver} from "../../helper/test-helper"; +import { + globalInstance, + communityInstance1, + activeUserMaker, + allOver, +} from '../../helper/test-helper'; -jest.mock("../../api/bridge", () => ({ - getSubscribers: () => - new Promise((resolve) => { - resolve([ - ["foo", "guest", null, "2020-09-09 04:37:54"], - ["bar", "guest", null, "2020-09-09 04:37:54"], - ["baz", "guest", null, "2020-09-09 04:37:54"], - ["lorem", "guest", null, "2020-09-09 04:37:54"], - ["ipsum", "guest", null, "2020-09-09 04:37:54"], - ["dolor", "guest", null, "2020-09-09 04:37:54"], - ]); - }) +jest.mock('../../api/bridge', () => ({ + getSubscribers: () => + new Promise(resolve => { + resolve([ + ['foo', 'guest', null, '2020-09-09 04:37:54'], + ['bar', 'guest', null, '2020-09-09 04:37:54'], + ['baz', 'guest', null, '2020-09-09 04:37:54'], + ['lorem', 'guest', null, '2020-09-09 04:37:54'], + ['ipsum', 'guest', null, '2020-09-09 04:37:54'], + ['dolor', 'guest', null, '2020-09-09 04:37:54'], + ]); + }), })); -jest.mock("../../api/hive", () => ({ - getAccounts: () => - new Promise((resolve) => { - resolve([ - {name: "bluemist", reputation: 74.1}, - {name: "foo", reputation: 70.8}, - {name: "bar", reputation: 65.2}, - {name: "baz", reputation: 68.9}, - {name: "lorem", reputation: 51.6}, - {name: "ipsum", reputation: 75.5}, - {name: "dolor", reputation: 42.4} - ]); - }) +jest.mock('../../api/hive', () => ({ + getAccounts: () => + new Promise(resolve => { + resolve([ + {name: 'bluemist', reputation: 74.1}, + {name: 'foo', reputation: 70.8}, + {name: 'bar', reputation: 65.2}, + {name: 'baz', reputation: 68.9}, + {name: 'lorem', reputation: 51.6}, + {name: 'ipsum', reputation: 75.5}, + {name: 'dolor', reputation: 42.4}, + ]); + }), })); const defProps = { - history: createBrowserHistory(), - global: globalInstance, - community: {...communityInstance1}, - activeUser: null, - addAccount: () => { - }, - addCommunity: () => { - } -} + history: createBrowserHistory(), + global: globalInstance, + community: {...communityInstance1}, + activeUser: null, + addAccount: () => {}, + addCommunity: () => {}, +}; it('(1) Default render.', async () => { - const component = await renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + const component = await renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); it('(2) Active user.', async () => { - const props = { - ...defProps, - activeUser: activeUserMaker("bluemist") - } - const component = await renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + const props = { + ...defProps, + activeUser: activeUserMaker('bluemist'), + }; + const component = await renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); - diff --git a/src/common/components/community-subscribers/index.tsx b/src/common/components/community-subscribers/index.tsx index 70018cfea68..7598b1cf39a 100644 --- a/src/common/components/community-subscribers/index.tsx +++ b/src/common/components/community-subscribers/index.tsx @@ -1,180 +1,221 @@ -import React from "react"; +import React from 'react'; -import isEqual from "react-fast-compare"; +import isEqual from 'react-fast-compare'; -import {History} from "history"; +import {History} from 'history'; -import {Global} from "../../store/global/types"; -import {Account} from "../../store/accounts/types"; -import {Community, roleMap} from "../../store/communities/types"; -import {Subscription} from "../../store/subscriptions/types"; -import {ActiveUser} from "../../store/active-user/types"; +import {Global} from '../../store/global/types'; +import {Account} from '../../store/accounts/types'; +import {Community, roleMap} from '../../store/communities/types'; +import {Subscription} from '../../store/subscriptions/types'; +import {ActiveUser} from '../../store/active-user/types'; -import BaseComponent from "../base"; -import ProfileLink from "../profile-link"; -import UserAvatar from "../user-avatar"; -import LinearProgress from "../linear-progress"; -import CommunityRoleEditDialog from "../community-role-edit"; -import {error} from "../feedback"; +import BaseComponent from '../base'; +import ProfileLink from '../profile-link'; +import UserAvatar from '../user-avatar'; +import LinearProgress from '../linear-progress'; +import CommunityRoleEditDialog from '../community-role-edit'; +import {error} from '../feedback'; -import accountReputation from "../../helper/account-reputation"; +import accountReputation from '../../helper/account-reputation'; -import {getAccounts} from "../../api/hive"; -import {getSubscribers} from "../../api/bridge"; +import {getAccounts} from '../../api/hive'; +import {getSubscribers} from '../../api/bridge'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import {pencilOutlineSvg} from "../../img/svg"; +import {pencilOutlineSvg} from '../../img/svg'; interface MinifiedAccount { - name: string, - reputation: string | number; + name: string; + reputation: string | number; } interface Props { - history: History; - global: Global; - community: Community; - activeUser: ActiveUser | null; - addAccount: (data: Account) => void; - addCommunity: (data: Community) => void; + history: History; + global: Global; + community: Community; + activeUser: ActiveUser | null; + addAccount: (data: Account) => void; + addCommunity: (data: Community) => void; } interface State { - loading: boolean; - subscribers: Subscription[]; - editingSubscriber: Subscription | null; - accounts: MinifiedAccount[]; + loading: boolean; + subscribers: Subscription[]; + editingSubscriber: Subscription | null; + accounts: MinifiedAccount[]; } export class Subscribers extends BaseComponent { - state: State = { - loading: true, - subscribers: [], - editingSubscriber: null, - accounts: [] + state: State = { + loading: true, + subscribers: [], + editingSubscriber: null, + accounts: [], + }; + + componentDidMount() { + this.fetch().then(); + } + + componentDidUpdate( + prevProps: Readonly, + prevState: Readonly, + snapshot?: any, + ) { + // re-fetch once community updated. (on role update of a particular subscriber) + if ( + !isEqual(this.props.community, prevProps.community) && + !this.state.loading + ) { + this.fetch().then(); } - - componentDidMount() { - this.fetch().then(); - } - - componentDidUpdate(prevProps: Readonly, prevState: Readonly, snapshot?: any) { - // re-fetch once community updated. (on role update of a particular subscriber) - if (!isEqual(this.props.community, prevProps.community) && !this.state.loading) { - this.fetch().then(); + } + + fetch = () => { + const {community} = this.props; + return getSubscribers(community.name) + .then(resp => { + if (resp) { + // merge subscribers & community team + const subscribers = [ + ...community.team.filter(x => !x[0].startsWith('hive-')), + ...resp.filter( + x => community.team.find(y => x[0] === y[0]) === undefined, + ), + ]; + + const usernames = subscribers.map(x => x[0]); + + return getAccounts(usernames).then(accounts => { + const minifiedAccounts: MinifiedAccount[] = accounts.map(x => ({ + name: x.name, + reputation: x.reputation, + })); + this.stateSet({subscribers, accounts: minifiedAccounts}); + }); } + return null; + }) + .catch(() => { + error(_t('g.server-error')); + }) + .finally(() => { + this.stateSet({loading: false}); + }); + }; + + render() { + const {subscribers, accounts, editingSubscriber, loading} = this.state; + + if (loading) { + return ( +
+ +
+ ); } - fetch = () => { - const {community} = this.props; - return getSubscribers(community.name).then(resp => { - if (resp) { - // merge subscribers & community team - const subscribers = [ - ...community.team.filter(x => !x[0].startsWith("hive-")), - ...resp.filter(x => community.team.find(y => x[0] === y[0]) === undefined) - ]; - - const usernames = subscribers.map(x => x[0]); - - return getAccounts(usernames).then(accounts => { - const minifiedAccounts: MinifiedAccount[] = accounts.map(x => ({name: x.name, reputation: x.reputation})); - this.stateSet({subscribers, accounts: minifiedAccounts}); - }); - } - return null; - }).catch(() => { - error(_t('g.server-error')); - }).finally(() => { - this.stateSet({loading: false}); - }) - } - - render() { - const {subscribers, accounts, editingSubscriber, loading} = this.state; - - if (loading) { - return
- -
- } - - const {community, activeUser} = this.props; - - const role = community.team.find(x => x[0] === activeUser?.username); - const roleInTeam = role ? role[1] : null; - const canEditTeam = !!(roleInTeam && roleMap[roleInTeam]); - const roles = roleInTeam ? roleMap[roleInTeam] : []; - - return
- {subscribers.length > 0 && ( -
-
- {subscribers.map((item, i) => { - const [username, role] = item; - const account = accounts.find(x => x.name === username); - const canEditRole = roles && roles.includes(role); - - return
-
- {ProfileLink({ - ...this.props, - username, - children: <>{UserAvatar({...this.props, username, size: "small"})} - })} -
- {ProfileLink({ - ...this.props, - username, - children: {username} - })} - {(account?.reputation !== undefined) && {accountReputation(account.reputation)}} -
-
- {canEditTeam && ( - - )} -
+ const {community, activeUser} = this.props; + + const role = community.team.find(x => x[0] === activeUser?.username); + const roleInTeam = role ? role[1] : null; + const canEditTeam = !!(roleInTeam && roleMap[roleInTeam]); + const roles = roleInTeam ? roleMap[roleInTeam] : []; + + return ( +
+ {subscribers.length > 0 && ( +
+
+ {subscribers.map((item, i) => { + const [username, role] = item; + const account = accounts.find(x => x.name === username); + const canEditRole = roles && roles.includes(role); + + return ( +
+
+ {ProfileLink({ + ...this.props, + username, + children: ( + <> + {UserAvatar({ + ...this.props, + username, + size: 'small', + })} + + ), + })} +
+ {ProfileLink({ + ...this.props, + username, + children: ( + {username} + ), })} + {account?.reputation !== undefined && ( + + {accountReputation(account.reputation)} + + )} +
-
- )} - - {editingSubscriber && ( - { - this.stateSet({editingSubscriber: null}) - }} - /> - )} -
- } + {canEditTeam && ( + + )} +
+ ); + })} +
+
+ )} + + {editingSubscriber && ( + { + this.stateSet({editingSubscriber: null}); + }} + /> + )} +
+ ); + } } - export default (p: Props) => { - const props: Props = { - history: p.history, - global: p.global, - community: p.community, - activeUser: p.activeUser, - addAccount: p.addAccount, - addCommunity: p.addCommunity - } - - return -} - + const props: Props = { + history: p.history, + global: p.global, + community: p.community, + activeUser: p.activeUser, + addAccount: p.addAccount, + addCommunity: p.addCommunity, + }; + + return ; +}; diff --git a/src/common/components/contributors/index.tsx b/src/common/components/contributors/index.tsx index 0ffc1aab33b..6e370f86a9f 100644 --- a/src/common/components/contributors/index.tsx +++ b/src/common/components/contributors/index.tsx @@ -1,93 +1,98 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import contributors from "../../constants/contributors.json"; -import ProfileLink from "../profile-link"; -import UserAvatar from "../user-avatar"; +import contributors from '../../constants/contributors.json'; +import ProfileLink from '../profile-link'; +import UserAvatar from '../user-avatar'; -import {History} from "history"; -import {Global} from "../../store/global/types"; -import {Account} from "../../store/accounts/types"; +import {History} from 'history'; +import {Global} from '../../store/global/types'; +import {Account} from '../../store/accounts/types'; -import {_t} from "../../i18n"; -import {Tsx} from "../../i18n/helper"; -import _ from "lodash"; +import {_t} from '../../i18n'; +import {Tsx} from '../../i18n/helper'; +import _ from 'lodash'; interface Props { - history: History; - global: Global; - addAccount: (data: Account) => void; + history: History; + global: Global; + addAccount: (data: Account) => void; } interface State { - mounted: boolean, - contributors: { name:string, contributes: string[] } [] + mounted: boolean; + contributors: {name: string; contributes: string[]}[]; } export class Contributors extends Component { - constructor(props: Props){ - super(props); - this.state = { - mounted: false, - contributors: [] - } - } + constructor(props: Props) { + super(props); + this.state = { + mounted: false, + contributors: [], + }; + } - componentDidMount(){ - this.setState({ - mounted: true, - contributors: _.shuffle(contributors) - }) - } + componentDidMount() { + this.setState({ + mounted: true, + contributors: _.shuffle(contributors), + }); + } - render() { - const {mounted, contributors} = this.state; - return mounted ? ( -
-
-
-

- {_t('contributors.title')} -

-
-
-
- {contributors.map(c => { - const username = c.name; - return
-
- {ProfileLink({ - ...this.props, - username, - children: <>{UserAvatar({...this.props, username, size: "small"})} - })} - -
- {ProfileLink({ - ...this.props, - username, - children: {username} - })} -
-
-
- {c.contributes.join(", ")} -
-
- })} + render() { + const {mounted, contributors} = this.state; + return mounted ? ( +
+
+
+

{_t('contributors.title')}

+ +
+ +
+
+ {contributors.map(c => { + const username = c.name; + return ( +
+
+ {ProfileLink({ + ...this.props, + username, + children: ( + <> + {UserAvatar({...this.props, username, size: 'small'})} + + ), + })} +
+ {ProfileLink({ + ...this.props, + username, + children: ( + {username} + ), + })}
+
+
{c.contributes.join(', ')}
-
- ) : null; - } + ); + })} +
+
+
+ ) : null; + } } export default (p: Props) => { - const props = { - history: p.history, - global: p.global, - addAccount: p.addAccount - } + const props = { + history: p.history, + global: p.global, + addAccount: p.addAccount, + }; - return -} + return ; +}; diff --git a/src/common/components/cross-post/index.spec.tsx b/src/common/components/cross-post/index.spec.tsx index 8584e7b0f11..3b1f42aa2cf 100644 --- a/src/common/components/cross-post/index.spec.tsx +++ b/src/common/components/cross-post/index.spec.tsx @@ -1,61 +1,60 @@ -import React from "react"; +import React from 'react'; -import {CrossPost} from "./index"; +import {CrossPost} from './index'; -import TestRenderer from "react-test-renderer"; +import TestRenderer from 'react-test-renderer'; -import {globalInstance, entryInstance1, activeUserMaker, allOver} from "../../helper/test-helper"; +import { + globalInstance, + entryInstance1, + activeUserMaker, + allOver, +} from '../../helper/test-helper'; let MOCK = 1; -jest.mock("../../api/bridge", () => ({ - getSubscriptions: () => - new Promise((resolve) => { - if (MOCK === 1) { - resolve( - [ - ["hive-125125", "Ecency"], - ["hive-252252", "Foo"] - ] - ); - } - - if (MOCK === 2) { - resolve([]); - } - }), +jest.mock('../../api/bridge', () => ({ + getSubscriptions: () => + new Promise(resolve => { + if (MOCK === 1) { + resolve([ + ['hive-125125', 'Ecency'], + ['hive-252252', 'Foo'], + ]); + } + + if (MOCK === 2) { + resolve([]); + } + }), })); - -it("(1) Default render", async () => { - const props = { - global: globalInstance, - activeUser: activeUserMaker("foo"), - entry: entryInstance1, - onSuccess: () => { - }, - onHide: () => { - } - }; - - const renderer = TestRenderer.create(); - await allOver(); - expect(renderer.toJSON()).toMatchSnapshot(); +it('(1) Default render', async () => { + const props = { + global: globalInstance, + activeUser: activeUserMaker('foo'), + entry: entryInstance1, + onSuccess: () => {}, + onHide: () => {}, + }; + + const renderer = TestRenderer.create(); + await allOver(); + expect(renderer.toJSON()).toMatchSnapshot(); }); -it("(2) No subscription", async () => { - MOCK = 2; - const props = { - global: globalInstance, - activeUser: activeUserMaker("foo"), - entry: entryInstance1, - onSuccess: () => { - }, - onHide: () => { - } - }; - - const renderer = TestRenderer && TestRenderer.create(); - await allOver(); - expect(renderer.toJSON()).toMatchSnapshot(); +it('(2) No subscription', async () => { + MOCK = 2; + const props = { + global: globalInstance, + activeUser: activeUserMaker('foo'), + entry: entryInstance1, + onSuccess: () => {}, + onHide: () => {}, + }; + + const renderer = + TestRenderer && TestRenderer.create(); + await allOver(); + expect(renderer.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/cross-post/index.tsx b/src/common/components/cross-post/index.tsx index 416cb8cfa10..5b377fe09e9 100644 --- a/src/common/components/cross-post/index.tsx +++ b/src/common/components/cross-post/index.tsx @@ -1,162 +1,219 @@ -import BaseComponent from "../base"; -import React, {Component} from "react"; +import BaseComponent from '../base'; +import React, {Component} from 'react'; -import {Button, Form, FormControl, Modal} from "react-bootstrap"; +import {Button, Form, FormControl, Modal} from 'react-bootstrap'; -import {Entry} from "../../store/entries/types"; -import {ActiveUser} from "../../store/active-user/types"; +import {Entry} from '../../store/entries/types'; +import {ActiveUser} from '../../store/active-user/types'; -import {error, success} from "../feedback"; -import SuggestionList from "../suggestion-list"; +import {error, success} from '../feedback'; +import SuggestionList from '../suggestion-list'; -import {comment, formatError} from "../../api/operations"; -import {getSubscriptions} from "../../api/bridge"; +import {comment, formatError} from '../../api/operations'; +import {getSubscriptions} from '../../api/bridge'; -import {makeCommentOptions, makeApp} from "../../helper/posting"; -import {makeCrossPostMessage} from "../../helper/cross-post"; +import {makeCommentOptions, makeApp} from '../../helper/posting'; +import {makeCrossPostMessage} from '../../helper/cross-post'; -import {_t} from "../../i18n"; - -import {version} from "../../../../package.json"; +import {_t} from '../../i18n'; +import {version} from '../../../../package.json'; interface Props { - activeUser: ActiveUser; - entry: Entry; - onSuccess: (community: string) => void; - onHide: () => void; + activeUser: ActiveUser; + entry: Entry; + onSuccess: (community: string) => void; + onHide: () => void; } interface State { - communities: { - id: string; - name: string; - }[]; - community: string; - message: string; - posting: boolean; - loading: boolean; + communities: { + id: string; + name: string; + }[]; + community: string; + message: string; + posting: boolean; + loading: boolean; } export class CrossPost extends BaseComponent { - state: State = { - communities: [], - community: "", - message: "", - posting: false, - loading: true - } - - componentDidMount() { - const {activeUser} = this.props; - getSubscriptions(activeUser.username).then(r => { - if (r) { - const communities = r.map((x) => ({id: x[0], name: x[1]})); - this.stateSet({communities}); - } - }).finally(() => { - this.stateSet({loading: false}); - }); - } - - hide = () => { - this.props.onHide(); - } - - communityChanged = (e: React.ChangeEvent) => { - this.stateSet({community: e.target.value}); - } - - messageChanged = (e: React.ChangeEvent) => { - this.stateSet({message: e.target.value}) - } - - submit = () => { - const {entry, activeUser} = this.props; - const {community, communities, message} = this.state; - - const theCommunity = communities.find(x => x.name.toLowerCase() === community.toLowerCase()); - if (!theCommunity) { - return; - } - - const {title} = entry; - const author = activeUser.username; - const permlink = `${entry.permlink}-${theCommunity.id}`; - - const body = makeCrossPostMessage(entry, author, message); - const jsonMeta = { - app: makeApp(version), - tags: ["cross-post"], - original_author: entry.author, - original_permlink: entry.permlink + state: State = { + communities: [], + community: '', + message: '', + posting: false, + loading: true, + }; + + componentDidMount() { + const {activeUser} = this.props; + getSubscriptions(activeUser.username) + .then(r => { + if (r) { + const communities = r.map(x => ({id: x[0], name: x[1]})); + this.stateSet({communities}); } - - const options = { - ...makeCommentOptions(author, permlink, "dp"), - allow_curation_rewards: false - }; - - this.stateSet({posting: true}); - comment(author, "", theCommunity.id, permlink, title, body, jsonMeta, options) - .then(() => { - success(_t("cross-post.published")); - this.props.onSuccess(theCommunity.id); - }) - .catch((e) => { - error(formatError(e)); - }) - .finally(() => { - this.stateSet({posting: false}); - }); + }) + .finally(() => { + this.stateSet({loading: false}); + }); + } + + hide = () => { + this.props.onHide(); + }; + + communityChanged = ( + e: React.ChangeEvent, + ) => { + this.stateSet({community: e.target.value}); + }; + + messageChanged = ( + e: React.ChangeEvent, + ) => { + this.stateSet({message: e.target.value}); + }; + + submit = () => { + const {entry, activeUser} = this.props; + const {community, communities, message} = this.state; + + const theCommunity = communities.find( + x => x.name.toLowerCase() === community.toLowerCase(), + ); + if (!theCommunity) { + return; } - communitySelected = (item: any) => { - this.stateSet({community: item.name}); + const {title} = entry; + const author = activeUser.username; + const permlink = `${entry.permlink}-${theCommunity.id}`; + + const body = makeCrossPostMessage(entry, author, message); + const jsonMeta = { + app: makeApp(version), + tags: ['cross-post'], + original_author: entry.author, + original_permlink: entry.permlink, + }; + + const options = { + ...makeCommentOptions(author, permlink, 'dp'), + allow_curation_rewards: false, + }; + + this.stateSet({posting: true}); + comment( + author, + '', + theCommunity.id, + permlink, + title, + body, + jsonMeta, + options, + ) + .then(() => { + success(_t('cross-post.published')); + this.props.onSuccess(theCommunity.id); + }) + .catch(e => { + error(formatError(e)); + }) + .finally(() => { + this.stateSet({posting: false}); + }); + }; + + communitySelected = (item: any) => { + this.stateSet({community: item.name}); + }; + + render() { + const {communities, community, message, posting, loading} = this.state; + + const suggestions = communities.filter( + x => x.name.toLowerCase().indexOf(community.toLowerCase()) !== -1, + ); + const theCommunity = communities.find( + x => x.name.toLowerCase() === community.toLowerCase(), + ); + const canSubmit = theCommunity && message.trim() !== ''; + + if (!loading && communities.length === 0) { + return ( + {_t('cross-post.no-subscription')} + ); } - render() { - const {communities, community, message, posting, loading} = this.state; - - const suggestions = communities.filter(x => x.name.toLowerCase().indexOf(community.toLowerCase()) !== -1); - const theCommunity = communities.find(x => x.name.toLowerCase() === community.toLowerCase()); - const canSubmit = theCommunity && message.trim() !== ""; - - if (!loading && communities.length === 0) { - return {_t("cross-post.no-subscription")}; - } - - return <> - - x.name}> - - - - - - -

{_t("cross-post.info")}

-
- - -
- - } + return ( + <> + + x.name} + > + + + + + + +

{_t('cross-post.info')}

+
+ + +
+ + ); + } } export default class CrossPostDialog extends Component { - render() { - const {onHide} = this.props; - - return - - {_t("cross-post.title")} - - - - - - } + render() { + const {onHide} = this.props; + + return ( + + + {_t('cross-post.title')} + + + + + + ); + } } diff --git a/src/common/components/curation/index.spec.tsx b/src/common/components/curation/index.spec.tsx index f58852890bb..6fdd3dd239f 100644 --- a/src/common/components/curation/index.spec.tsx +++ b/src/common/components/curation/index.spec.tsx @@ -1,36 +1,37 @@ import React from 'react'; -import {create, act} from "react-test-renderer"; -import {createBrowserHistory} from "history"; +import {create, act} from 'react-test-renderer'; +import {createBrowserHistory} from 'history'; -import {globalInstance, allOver, dynamicPropsIntance1} from "../../helper/test-helper"; +import { + globalInstance, + allOver, + dynamicPropsIntance1, +} from '../../helper/test-helper'; import {Curation} from './index'; -jest.mock("../../api/private-api", () => ({ - getCuration: (duration: string) => - new Promise((resolve) => { - resolve([ - {"account": "foo", "votes": 42, "vests": "121.325"}, - {"account": "bar", "votes": 40, "vests": "60.040"}, - {"account": "baz", "votes": 26, "vests": "44.707"}, - {"account": "zoo", "votes": 22, "vests": "55.040"} - ]); - }), +jest.mock('../../api/private-api', () => ({ + getCuration: (duration: string) => + new Promise(resolve => { + resolve([ + {account: 'foo', votes: 42, vests: '121.325'}, + {account: 'bar', votes: 40, vests: '60.040'}, + {account: 'baz', votes: 26, vests: '44.707'}, + {account: 'zoo', votes: 22, vests: '55.040'}, + ]); + }), })); - it('(1) Render with data.', async () => { + const props = { + global: globalInstance, + history: createBrowserHistory(), + dynamicProps: dynamicPropsIntance1, + addAccount: () => {}, + }; - const props = { - global: globalInstance, - history: createBrowserHistory(), - dynamicProps: dynamicPropsIntance1, - addAccount: () => { - } - }; - - // render the component - const root = await create() - // make assertions on root - expect(root.toJSON()).toMatchSnapshot(); + // render the component + const root = await create(); + // make assertions on root + expect(root.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/curation/index.tsx b/src/common/components/curation/index.tsx index 26db6572c3f..d2817ff5761 100644 --- a/src/common/components/curation/index.tsx +++ b/src/common/components/curation/index.tsx @@ -1,170 +1,179 @@ -import React, {useState, useEffect} from "react"; -import {History} from "history"; +import React, {useState, useEffect} from 'react'; +import {History} from 'history'; -import {Global} from "../../store/global/types"; -import {Account} from "../../store/accounts/types"; -import {DynamicProps} from "../../store/dynamic-props/types"; +import {Global} from '../../store/global/types'; +import {Account} from '../../store/accounts/types'; +import {DynamicProps} from '../../store/dynamic-props/types'; -import UserAvatar from "../user-avatar"; -import ProfileLink from "../profile-link" +import UserAvatar from '../user-avatar'; +import ProfileLink from '../profile-link'; -import {getCuration, CurationDuration, CurationItem} from "../../api/private-api"; +import { + getCuration, + CurationDuration, + CurationItem, +} from '../../api/private-api'; -import {informationVariantSvg} from "../../img/svg"; -import DropDown from "../dropdown"; -import { OverlayTrigger, Tooltip } from "react-bootstrap"; -import LinearProgress from "../linear-progress"; +import {informationVariantSvg} from '../../img/svg'; +import DropDown from '../dropdown'; +import {OverlayTrigger, Tooltip} from 'react-bootstrap'; +import LinearProgress from '../linear-progress'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import _c from "../../util/fix-class-names" -import {vestsToHp} from "../../helper/vesting"; -import formattedNumber from "../../util/formatted-number"; -import { getAccounts } from '../../api/hive'; +import _c from '../../util/fix-class-names'; +import {vestsToHp} from '../../helper/vesting'; +import formattedNumber from '../../util/formatted-number'; +import {getAccounts} from '../../api/hive'; interface Props { - global: Global; - history: History; - dynamicProps: DynamicProps; - addAccount: (data: Account) => void; + global: Global; + history: History; + dynamicProps: DynamicProps; + addAccount: (data: Account) => void; } export const Curation = (props: Props) => { - - const [data, setData] = useState([] as CurationItem[]); - const [period, setPeriod] = useState('day' as CurationDuration); - const [loading, setLoading] = useState(true); - const [isMounted, setIsMounted] = useState(false); - - useEffect(() => { - setIsMounted(true); - fetch(period); - return () => { - setIsMounted(false); - } - }, []); - - const compare = (a: CurationItem, b: CurationItem) => { - return b.efficiency - a.efficiency; - } - - const fetch = async(f: CurationDuration) => { - setLoading(true); - setData([] as CurationItem[]); - const dataa = await getCuration(f); - const accounts = dataa.map((item) => item.account); - const ress = await getAccounts(accounts); - - for (let index = 0; index < ress.length; index++) { - const element = ress[index]; - const curator = dataa[index]; - const effectiveVest: number = parseFloat(element.vesting_shares) + parseFloat(element.received_vesting_shares) - parseFloat(element.delegated_vesting_shares) - parseFloat(element.vesting_withdraw_rate); - curator.efficiency = curator.vests / effectiveVest; - } - dataa.sort(compare); - setData(dataa as CurationItem[]); - setLoading(false); - } - - const {dynamicProps} = props; - const {hivePerMVests} = dynamicProps; - - const menuItems = [ - ...["day", "week", "month"].map((f => { - return { - label: _t(`leaderboard.period-${f}`), - onClick: () => { - setPeriod(f as CurationDuration); - fetch(f as CurationDuration); - } - } - })) - ]; - - const dropDownConfig = { - history: props.history, - label: '', - items: menuItems + const [data, setData] = useState([] as CurationItem[]); + const [period, setPeriod] = useState('day' as CurationDuration); + const [loading, setLoading] = useState(true); + const [isMounted, setIsMounted] = useState(false); + + useEffect(() => { + setIsMounted(true); + fetch(period); + return () => { + setIsMounted(false); }; - - return ( -
-
-
- {_t('leaderboard.title-curators')} {loading ? "" : } + }, []); + + const compare = (a: CurationItem, b: CurationItem) => { + return b.efficiency - a.efficiency; + }; + + const fetch = async (f: CurationDuration) => { + setLoading(true); + setData([] as CurationItem[]); + const dataa = await getCuration(f); + const accounts = dataa.map(item => item.account); + const ress = await getAccounts(accounts); + + for (let index = 0; index < ress.length; index++) { + const element = ress[index]; + const curator = dataa[index]; + const effectiveVest: number = + parseFloat(element.vesting_shares) + + parseFloat(element.received_vesting_shares) - + parseFloat(element.delegated_vesting_shares) - + parseFloat(element.vesting_withdraw_rate); + curator.efficiency = curator.vests / effectiveVest; + } + dataa.sort(compare); + setData(dataa as CurationItem[]); + setLoading(false); + }; + + const {dynamicProps} = props; + const {hivePerMVests} = dynamicProps; + + const menuItems = [ + ...['day', 'week', 'month'].map(f => { + return { + label: _t(`leaderboard.period-${f}`), + onClick: () => { + setPeriod(f as CurationDuration); + fetch(f as CurationDuration); + }, + }; + }), + ]; + + const dropDownConfig = { + history: props.history, + label: '', + items: menuItems, + }; + + return ( +
+
+
+ {_t('leaderboard.title-curators')}{' '} + {loading ? '' : } +
+
{_t(`leaderboard.title-${period}`)}
+
+ {loading && } + {data.length > 0 && ( +
+
+ + + {_t('leaderboard.header-votes-tip')} + + } + > +
+ {informationVariantSvg} + {_t('leaderboard.header-votes')} +
+
+ {_t('leaderboard.header-reward')} +
+ + {data.map((r, i) => { + return ( +
+
{i + 1}
+
+ {ProfileLink({ + ...props, + username: r.account, + children: ( + + {UserAvatar({ + ...props, + size: 'medium', + username: r.account, + })} + + ), + })}
-
- {_t(`leaderboard.title-${period}`)} +
+ {ProfileLink({ + ...props, + username: r.account, + children: {r.account}, + })}
-
- {loading && } - {data.length > 0 && ( -
-
- - - {_t('leaderboard.header-votes-tip')} - - } - > -
- {informationVariantSvg} - - {_t('leaderboard.header-votes')} - -
-
- - {_t('leaderboard.header-reward')} - -
- - {data.map((r, i) => { - - return
-
{i + 1}
-
- {ProfileLink({ - ...props, - username: r.account, - children: {UserAvatar({...props, size: "medium", username: r.account})} - })} -
-
- {ProfileLink({ - ...props, - username: r.account, - children: {r.account} - })} -
-
- {r.votes} -
-
- {formattedNumber(vestsToHp(r.vests, hivePerMVests), {suffix: "HP"})} -
-
; - })} +
{r.votes}
+
+ {formattedNumber(vestsToHp(r.vests, hivePerMVests), { + suffix: 'HP', + })}
- )} - +
+ ); + })}
- ); -} - + )} +
+ ); +}; export default (p: Props) => { - const props: Props = { - global: p.global, - history: p.history, - dynamicProps: p.dynamicProps, - addAccount: p.addAccount - }; - - return -} + const props: Props = { + global: p.global, + history: p.history, + dynamicProps: p.dynamicProps, + addAccount: p.addAccount, + }; + + return ; +}; diff --git a/src/common/components/delegated-vesting/index.spec.tsx b/src/common/components/delegated-vesting/index.spec.tsx index f19840a5b87..c8fb08382fa 100644 --- a/src/common/components/delegated-vesting/index.spec.tsx +++ b/src/common/components/delegated-vesting/index.spec.tsx @@ -1,79 +1,80 @@ -import React from "react"; +import React from 'react'; -import {List} from "./index"; -import renderer from "react-test-renderer"; -import {createBrowserHistory} from "history"; +import {List} from './index'; +import renderer from 'react-test-renderer'; +import {createBrowserHistory} from 'history'; -import {entryInstance1, dynamicPropsIntance1, delegatedVestingInstance, globalInstance, activeUserMaker, allOver} from "../../helper/test-helper"; +import { + entryInstance1, + dynamicPropsIntance1, + delegatedVestingInstance, + globalInstance, + activeUserMaker, + allOver, +} from '../../helper/test-helper'; -jest.mock("../../constants/defaults.json", () => ({ - imageServer: "https://images.ecency.com", +jest.mock('../../constants/defaults.json', () => ({ + imageServer: 'https://images.ecency.com', })); let MOCK_MODE = 1; -jest.mock("../../api/hive", () => ({ - getVestingDelegations: () => - new Promise((resolve) => { - if (MOCK_MODE === 1) { - resolve(delegatedVestingInstance); - } +jest.mock('../../api/hive', () => ({ + getVestingDelegations: () => + new Promise(resolve => { + if (MOCK_MODE === 1) { + resolve(delegatedVestingInstance); + } - if (MOCK_MODE === 2) { - resolve([]); - } - }), + if (MOCK_MODE === 2) { + resolve([]); + } + }), })); const defaultProps = { - global: globalInstance, - history: createBrowserHistory(), - activeUser: null, - account: {name: "foo"}, - dynamicProps: dynamicPropsIntance1, - signingKey: '', - entry: {...entryInstance1}, - totalDelegated: '', - addAccount: () => { - }, - setSigningKey: () => { - }, - onHide: () => { - } + global: globalInstance, + history: createBrowserHistory(), + activeUser: null, + account: {name: 'foo'}, + dynamicProps: dynamicPropsIntance1, + signingKey: '', + entry: {...entryInstance1}, + totalDelegated: '', + addAccount: () => {}, + setSigningKey: () => {}, + onHide: () => {}, }; - -it("(1) Default render", async () => { - const component = renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); +it('(1) Default render', async () => { + const component = renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); -it("(2) With active user", async () => { - const props = { - ...defaultProps, - activeUser: activeUserMaker("bar") - } - const component = renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); +it('(2) With active user', async () => { + const props = { + ...defaultProps, + activeUser: activeUserMaker('bar'), + }; + const component = renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); - -it("(3) With delegator active user", async () => { - const props = { - ...defaultProps, - activeUser: activeUserMaker("foo") - } - const component = renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); +it('(3) With delegator active user', async () => { + const props = { + ...defaultProps, + activeUser: activeUserMaker('foo'), + }; + const component = renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); - -it("(4) Empty List", async () => { - MOCK_MODE = 2; - const component = renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); +it('(4) Empty List', async () => { + MOCK_MODE = 2; + const component = renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/delegated-vesting/index.tsx b/src/common/components/delegated-vesting/index.tsx index af10c96d271..40e4d01e860 100644 --- a/src/common/components/delegated-vesting/index.tsx +++ b/src/common/components/delegated-vesting/index.tsx @@ -1,261 +1,337 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import {History} from "history"; +import {History} from 'history'; -import {Form, Modal, Pagination} from "react-bootstrap"; +import {Form, Modal, Pagination} from 'react-bootstrap'; -import {Global} from "../../store/global/types"; -import {Account} from "../../store/accounts/types"; -import {DynamicProps} from "../../store/dynamic-props/types"; -import {ActiveUser} from "../../store/active-user/types"; +import {Global} from '../../store/global/types'; +import {Account} from '../../store/accounts/types'; +import {DynamicProps} from '../../store/dynamic-props/types'; +import {ActiveUser} from '../../store/active-user/types'; -import BaseComponent from "../base"; -import ProfileLink from "../profile-link"; -import UserAvatar from "../user-avatar"; -import LinearProgress from "../linear-progress"; -import Tooltip from "../tooltip"; -import KeyOrHotDialog from "../key-or-hot-dialog"; -import {error} from "../feedback"; +import BaseComponent from '../base'; +import ProfileLink from '../profile-link'; +import UserAvatar from '../user-avatar'; +import LinearProgress from '../linear-progress'; +import Tooltip from '../tooltip'; +import KeyOrHotDialog from '../key-or-hot-dialog'; +import {error} from '../feedback'; -import {DelegatedVestingShare, getVestingDelegations} from "../../api/hive"; +import {DelegatedVestingShare, getVestingDelegations} from '../../api/hive'; import { - delegateVestingShares, - delegateVestingSharesHot, - delegateVestingSharesKc, - formatError -} from "../../api/operations"; + delegateVestingShares, + delegateVestingSharesHot, + delegateVestingSharesKc, + formatError, +} from '../../api/operations'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import {vestsToHp} from "../../helper/vesting"; +import {vestsToHp} from '../../helper/vesting'; -import parseAsset from "../../helper/parse-asset"; +import parseAsset from '../../helper/parse-asset'; -import formattedNumber from "../../util/formatted-number"; +import formattedNumber from '../../util/formatted-number'; -import _c from "../../util/fix-class-names"; -import MyPagination from "../pagination"; +import _c from '../../util/fix-class-names'; +import MyPagination from '../pagination'; interface Props { - history: History; - global: Global; - activeUser: ActiveUser | null; - account: Account; - dynamicProps: DynamicProps; - signingKey: string; - addAccount: (data: Account) => void; - setSigningKey: (key: string) => void; - onHide: () => void; - searchText?: string; - totalDelegated: string; - setSubtitle?: (value: number) => void; + history: History; + global: Global; + activeUser: ActiveUser | null; + account: Account; + dynamicProps: DynamicProps; + signingKey: string; + addAccount: (data: Account) => void; + setSigningKey: (key: string) => void; + onHide: () => void; + searchText?: string; + totalDelegated: string; + setSubtitle?: (value: number) => void; } interface State { - loading: boolean; - inProgress: boolean; - data: DelegatedVestingShare[]; - searchData: DelegatedVestingShare[]; - hideList: boolean; - page: number; + loading: boolean; + inProgress: boolean; + data: DelegatedVestingShare[]; + searchData: DelegatedVestingShare[]; + hideList: boolean; + page: number; } export class List extends BaseComponent { - state: State = { - loading: false, - inProgress: false, - data: [], - searchData: [], - hideList: false, - page:1 - }; - - componentDidMount() { - this.fetch().then(); - } - - fetch = () => { - const {account, dynamicProps, totalDelegated, setSubtitle} = this.props; - this.setState({loading: true}); - let totalData: DelegatedVestingShare[] = []; - const {hivePerMVests} = dynamicProps; - - let getData = (account:string, start:string, limit:number) => { - return getVestingDelegations(account, start, limit) - .then((r) => { - totalData = totalData.concat(r); - if(r.length === limit){ - getData(account, r[limit-1].delegatee, limit) - } - else { - const sorted: DelegatedVestingShare[] = totalData.sort((a, b) => { - return parseAsset(b.vesting_shares).amount - parseAsset(a.vesting_shares).amount; - }); - - const totalDelegatedValue = sorted.reduce((n, item) => { - let parsedValue: any = parseAsset(item.vesting_shares).amount; - parsedValue = vestsToHp(parsedValue, hivePerMVests); - parsedValue = formattedNumber(parsedValue); - parsedValue = parsedValue.replace(/,/g,''); - parsedValue = parseFloat(parsedValue); - parsedValue = n + parsedValue; - return parsedValue}, 0) - - const totalDelegatedNumbered = parseFloat(totalDelegated.replace(" HP","").replace(",","")); - const toBeReturned = totalDelegatedNumbered - totalDelegatedValue; - setSubtitle && setSubtitle(Number(toBeReturned.toFixed(3))) - - this.setState({loading: false, data: [... new Set(sorted)]}) - } - }); + state: State = { + loading: false, + inProgress: false, + data: [], + searchData: [], + hideList: false, + page: 1, + }; + + componentDidMount() { + this.fetch().then(); + } + + fetch = () => { + const {account, dynamicProps, totalDelegated, setSubtitle} = this.props; + this.setState({loading: true}); + let totalData: DelegatedVestingShare[] = []; + const {hivePerMVests} = dynamicProps; + + let getData = (account: string, start: string, limit: number) => { + return getVestingDelegations(account, start, limit).then(r => { + totalData = totalData.concat(r); + if (r.length === limit) { + getData(account, r[limit - 1].delegatee, limit); + } else { + const sorted: DelegatedVestingShare[] = totalData.sort((a, b) => { + return ( + parseAsset(b.vesting_shares).amount - + parseAsset(a.vesting_shares).amount + ); + }); + + const totalDelegatedValue = sorted.reduce((n, item) => { + let parsedValue: any = parseAsset(item.vesting_shares).amount; + parsedValue = vestsToHp(parsedValue, hivePerMVests); + parsedValue = formattedNumber(parsedValue); + parsedValue = parsedValue.replace(/,/g, ''); + parsedValue = parseFloat(parsedValue); + parsedValue = n + parsedValue; + return parsedValue; + }, 0); + + const totalDelegatedNumbered = parseFloat( + totalDelegated.replace(' HP', '').replace(',', ''), + ); + const toBeReturned = totalDelegatedNumbered - totalDelegatedValue; + setSubtitle && setSubtitle(Number(toBeReturned.toFixed(3))); + + this.setState({loading: false, data: [...new Set(sorted)]}); } + }); + }; - return getData(account.name, "", 1000); + return getData(account.name, '', 1000); + }; + + componentDidUpdate(prevProps: Props) { + if ( + prevProps.searchText !== this.props.searchText && + this.props.searchText && + this.props.searchText.length > 0 + ) { + let filteredItems = this.state.data.filter(item => + item.delegatee + .toLocaleLowerCase() + .includes(this.props.searchText!.toLocaleLowerCase()), + ); + this.setState({searchData: filteredItems, page: 1}); } - - componentDidUpdate(prevProps: Props){ - if(prevProps.searchText !== this.props.searchText && this.props.searchText && this.props.searchText.length > 0){ - let filteredItems = this.state.data.filter(item => - item.delegatee.toLocaleLowerCase().includes(this.props.searchText!.toLocaleLowerCase())); - this.setState({ searchData: filteredItems, page: 1 }); - } + } + + render() { + const {loading, data, hideList, inProgress, searchData, page} = this.state; + const {dynamicProps, activeUser, account, searchText} = this.props; + const {hivePerMVests} = dynamicProps; + + if (loading) { + return ( +
+ +
+ ); } - render() { - const {loading, data, hideList, inProgress, searchData, page} = this.state; - const {dynamicProps, activeUser, account, searchText} = this.props; - const {hivePerMVests} = dynamicProps; - - if (loading) { - return (
- -
); - } - - let dataToShow = searchText && searchText.length > 0 ? searchData : data; - - const pageSize = 8; - const start = (page - 1) * pageSize; - const end = start + pageSize; - - const sliced = dataToShow.slice(start, end); - - return ( -
-
-
- {sliced.length === 0 &&
{_t("g.empty-list")}
} - {sliced.map(x => { - const vestingShares = parseAsset(x.vesting_shares).amount; - const {delegatee: username} = x; - - const deleteBtn = (activeUser && activeUser.username === account.name) ? KeyOrHotDialog({ - ...this.props, - activeUser: activeUser, - children: {_t("delegated-vesting.undelegate")}, - onToggle: () => { - const {hideList} = this.state; - this.setState({hideList: !hideList}); - }, - onKey: (key) => { - this.setState({inProgress: true}); - delegateVestingShares(activeUser.username, key, username, "0.000000 VESTS") - .then(() => this.fetch()) - .catch(err => error(formatError(err))) - .finally(() => this.setState({inProgress: false})) - }, - onHot: () => { - delegateVestingSharesHot(activeUser.username, username, "0.000000 VESTS"); - }, - onKc: () => { - this.setState({inProgress: true}); - delegateVestingSharesKc(activeUser.username, username, "0.000000 VESTS") - .then(() => this.fetch()) - .catch(err => error(formatError(err))) - .finally(() => this.setState({inProgress: false})) - } - }) : null; - - return
-
- {ProfileLink({ - ...this.props, - username, - children: <>{UserAvatar({...this.props, username: x.delegatee, size: "small"})} - })} -
- {ProfileLink({ - ...this.props, - username, - children: {username} - })} -
-
-
- - {formattedNumber(vestsToHp(vestingShares, hivePerMVests), {suffix: "HP"})} - - {deleteBtn} -
-
; - })} + let dataToShow = searchText && searchText.length > 0 ? searchData : data; + + const pageSize = 8; + const start = (page - 1) * pageSize; + const end = start + pageSize; + + const sliced = dataToShow.slice(start, end); + + return ( +
+
+
+ {sliced.length === 0 && ( +
{_t('g.empty-list')}
+ )} + {sliced.map(x => { + const vestingShares = parseAsset(x.vesting_shares).amount; + const {delegatee: username} = x; + + const deleteBtn = + activeUser && activeUser.username === account.name + ? KeyOrHotDialog({ + ...this.props, + activeUser: activeUser, + children: ( + + {_t('delegated-vesting.undelegate')} + + ), + onToggle: () => { + const {hideList} = this.state; + this.setState({hideList: !hideList}); + }, + onKey: key => { + this.setState({inProgress: true}); + delegateVestingShares( + activeUser.username, + key, + username, + '0.000000 VESTS', + ) + .then(() => this.fetch()) + .catch(err => error(formatError(err))) + .finally(() => this.setState({inProgress: false})); + }, + onHot: () => { + delegateVestingSharesHot( + activeUser.username, + username, + '0.000000 VESTS', + ); + }, + onKc: () => { + this.setState({inProgress: true}); + delegateVestingSharesKc( + activeUser.username, + username, + '0.000000 VESTS', + ) + .then(() => this.fetch()) + .catch(err => error(formatError(err))) + .finally(() => this.setState({inProgress: false})); + }, + }) + : null; + + return ( +
+
+ {ProfileLink({ + ...this.props, + username, + children: ( + <> + {UserAvatar({ + ...this.props, + username: x.delegatee, + size: 'small', + })} + + ), + })} +
+ {ProfileLink({ + ...this.props, + username, + children: ( + {username} + ), + })}
- { - this.setState({page}); - }}/> +
+
+ + + {formattedNumber( + vestsToHp(vestingShares, hivePerMVests), + {suffix: 'HP'}, + )} + + + {deleteBtn} +
-
- ); - } + ); + })} +
+ { + this.setState({page}); + }} + /> +
+
+ ); + } } interface DelegatedVestingState { - searchText: string; - subtitle: string; + searchText: string; + subtitle: string; } - -export default class DelegatedVesting extends Component { - state = { - searchText: '', - subtitle: '' - } - - render() { - const {onHide} = this.props; - const {subtitle, searchText} = this.state; - - return ( - <> - - - -
-
- {_t("delegated-vesting.title")} -
-
{subtitle}
-
-
-
- - - { - let text = e.target.value - this.setState({ searchText: e.target.value }); - }} - /> - - - this.setState({subtitle: value === 0 ? "" : `+${value} ${_t("delegated-vesting.subtitle")}`})}/> - -
- - ); - } +export default class DelegatedVesting extends Component< + Props, + DelegatedVestingState +> { + state = { + searchText: '', + subtitle: '', + }; + + render() { + const {onHide} = this.props; + const {subtitle, searchText} = this.state; + + return ( + <> + + + +
+
{_t('delegated-vesting.title')}
+
{subtitle}
+
+
+
+ + + { + let text = e.target.value; + this.setState({searchText: e.target.value}); + }} + /> + + + + this.setState({ + subtitle: + value === 0 + ? '' + : `+${value} ${_t('delegated-vesting.subtitle')}`, + }) + } + /> + +
+ + ); + } } diff --git a/src/common/components/detect-bottom/index.tsx b/src/common/components/detect-bottom/index.tsx index ac03bad3397..8a768a6c29e 100644 --- a/src/common/components/detect-bottom/index.tsx +++ b/src/common/components/detect-bottom/index.tsx @@ -1,28 +1,29 @@ import React from 'react'; interface Props { - onBottom: () => any + onBottom: () => any; } export default class DetectBottom extends React.Component { + componentDidMount() { + window.addEventListener('scroll', this.handleScroll); + } - componentDidMount() { - window.addEventListener('scroll', this.handleScroll); - } + componentWillUnmount() { + window.removeEventListener('scroll', this.handleScroll); + } - componentWillUnmount() { - window.removeEventListener('scroll', this.handleScroll); + handleScroll = () => { + const {onBottom} = this.props; + if ( + window.innerHeight + window.scrollY + 100 >= + document.body.offsetHeight + ) { + onBottom(); } + }; - - handleScroll = () => { - const {onBottom} = this.props; - if ((window.innerHeight + window.scrollY) + 100 >= document.body.offsetHeight) { - onBottom(); - } - }; - - render() { - return null; - } + render() { + return null; + } } diff --git a/src/common/components/discussion/index.tsx b/src/common/components/discussion/index.tsx index cd13dba1554..1cba82a0bc3 100644 --- a/src/common/components/discussion/index.tsx +++ b/src/common/components/discussion/index.tsx @@ -1,731 +1,877 @@ -import React, {Component, useState, useEffect} from "react"; +import React, {Component, useState, useEffect} from 'react'; -import {History, Location} from "history"; +import {History, Location} from 'history'; -import moment from "moment"; +import moment from 'moment'; -import {Button, Form, FormControl} from "react-bootstrap"; +import {Button, Form, FormControl} from 'react-bootstrap'; -import defaults from "../../constants/defaults.json"; +import defaults from '../../constants/defaults.json'; -import {renderPostBody, setProxyBase} from "@ecency/render-helper"; +import {renderPostBody, setProxyBase} from '@ecency/render-helper'; setProxyBase(defaults.imageServer); -import {Entry, EntryVote} from "../../store/entries/types"; -import {Account, FullAccount} from "../../store/accounts/types"; -import {Community, ROLES} from "../../store/communities/types"; -import {DynamicProps} from "../../store/dynamic-props/types"; -import {Global} from "../../store/global/types"; -import {User} from "../../store/users/types"; -import {ActiveUser} from "../../store/active-user/types"; -import {Discussion as DiscussionType, SortOrder} from "../../store/discussion/types"; -import {UI, ToggleType} from "../../store/ui/types"; - -import BaseComponent from "../base"; -import ProfileLink from "../profile-link"; -import EntryLink from "../entry-link"; -import UserAvatar from "../user-avatar"; -import EntryVoteBtn from "../entry-vote-btn/index"; -import EntryPayout from "../entry-payout/index"; -import EntryVotes from "../entry-votes"; -import LinearProgress from "../linear-progress"; -import Comment from "../comment" -import EntryDeleteBtn from "../entry-delete-btn"; -import MuteBtn from "../mute-btn"; -import LoginRequired from "../login-required"; - -import parseDate from "../../helper/parse-date"; - -import {_t} from "../../i18n"; - -import {comment, formatError} from "../../api/operations"; - -import * as ls from "../../util/local-storage"; - -import {createReplyPermlink, makeJsonMetaDataReply} from "../../helper/posting"; -import tempEntry from "../../helper/temp-entry"; - -import {error} from "../feedback"; - -import _c from "../../util/fix-class-names" - -import {commentSvg, pencilOutlineSvg, deleteForeverSvg, menuDownSvg, dotsHorizontal} from "../../img/svg"; - -import {version} from "../../../../package.json"; -import { getFollowing } from "../../api/hive"; -import { iteratorStream } from "@hiveio/dhive/lib/utils"; -import { Tsx } from "../../i18n/helper"; -import MyDropDown from "../dropdown"; -import { ProfilePopover } from "../profile-popover"; +import {Entry, EntryVote} from '../../store/entries/types'; +import {Account, FullAccount} from '../../store/accounts/types'; +import {Community, ROLES} from '../../store/communities/types'; +import {DynamicProps} from '../../store/dynamic-props/types'; +import {Global} from '../../store/global/types'; +import {User} from '../../store/users/types'; +import {ActiveUser} from '../../store/active-user/types'; +import { + Discussion as DiscussionType, + SortOrder, +} from '../../store/discussion/types'; +import {UI, ToggleType} from '../../store/ui/types'; + +import BaseComponent from '../base'; +import ProfileLink from '../profile-link'; +import EntryLink from '../entry-link'; +import UserAvatar from '../user-avatar'; +import EntryVoteBtn from '../entry-vote-btn/index'; +import EntryPayout from '../entry-payout/index'; +import EntryVotes from '../entry-votes'; +import LinearProgress from '../linear-progress'; +import Comment from '../comment'; +import EntryDeleteBtn from '../entry-delete-btn'; +import MuteBtn from '../mute-btn'; +import LoginRequired from '../login-required'; + +import parseDate from '../../helper/parse-date'; + +import {_t} from '../../i18n'; + +import {comment, formatError} from '../../api/operations'; + +import * as ls from '../../util/local-storage'; + +import {createReplyPermlink, makeJsonMetaDataReply} from '../../helper/posting'; +import tempEntry from '../../helper/temp-entry'; + +import {error} from '../feedback'; + +import _c from '../../util/fix-class-names'; + +import { + commentSvg, + pencilOutlineSvg, + deleteForeverSvg, + menuDownSvg, + dotsHorizontal, +} from '../../img/svg'; + +import {version} from '../../../../package.json'; +import {getFollowing} from '../../api/hive'; +import {iteratorStream} from '@hiveio/dhive/lib/utils'; +import {Tsx} from '../../i18n/helper'; +import MyDropDown from '../dropdown'; +import {ProfilePopover} from '../profile-popover'; interface ItemBodyProps { - entry: Entry; - global: Global; + entry: Entry; + global: Global; } export class ItemBody extends Component { - shouldComponentUpdate(nextProps: Readonly): boolean { - return this.props.entry.body !== nextProps.entry.body; - } + shouldComponentUpdate(nextProps: Readonly): boolean { + return this.props.entry.body !== nextProps.entry.body; + } - render() { - const {entry, global} = this.props; + render() { + const {entry, global} = this.props; - const renderedBody = {__html: renderPostBody(entry.body, false, global.canUseWebp)}; + const renderedBody = { + __html: renderPostBody(entry.body, false, global.canUseWebp), + }; - return
- } + return ( +
+ ); + } } interface ItemProps { - history: History; - location: Location; - global: Global; - dynamicProps: DynamicProps; - users: User[]; - activeUser: ActiveUser | null; - discussion: DiscussionType; - entry: Entry; - community: Community | null; - ui: UI; - addAccount: (data: Account) => void; - setActiveUser: (username: string | null) => void; - updateActiveUser: (data?: Account) => void; - deleteUser: (username: string) => void; - updateReply: (reply: Entry) => void; - addReply: (reply: Entry) => void; - deleteReply: (reply: Entry) => void; - toggleUIProp: (what: ToggleType) => void; + history: History; + location: Location; + global: Global; + dynamicProps: DynamicProps; + users: User[]; + activeUser: ActiveUser | null; + discussion: DiscussionType; + entry: Entry; + community: Community | null; + ui: UI; + addAccount: (data: Account) => void; + setActiveUser: (username: string | null) => void; + updateActiveUser: (data?: Account) => void; + deleteUser: (username: string) => void; + updateReply: (reply: Entry) => void; + addReply: (reply: Entry) => void; + deleteReply: (reply: Entry) => void; + toggleUIProp: (what: ToggleType) => void; } interface ItemState { - reply: boolean; - edit: boolean; - inProgress: boolean; - mutedData: string[]; - isHiddenPermitted: boolean; + reply: boolean; + edit: boolean; + inProgress: boolean; + mutedData: string[]; + isHiddenPermitted: boolean; } export const Item = (props: ItemProps) => { + const [reply, setReply] = useState(false); + const [edit, setEdit] = useState(false); + const [inProgress, setInProgress] = useState(false); + const [mutedData, setMutedData] = useState([] as string[]); + const [isMounted, setIsMounted] = useState(false); + const [lsDraft, setLsDraft] = useState(''); + + const { + entry, + updateReply, + activeUser, + addReply, + deleteReply, + global, + community, + location, + history, + } = props; + + useEffect(() => { + setIsMounted(true); + isMounted && fetchMutedUsers(); + checkLsDraft(); + return () => { + setIsMounted(false); + }; + }, []); - const [reply, setReply] = useState(false); - const [edit, setEdit] = useState(false); - const [inProgress, setInProgress] = useState(false); - const [mutedData, setMutedData] = useState([] as string[]); - const [isMounted, setIsMounted] = useState(false); - const [lsDraft, setLsDraft] = useState(""); - - const {entry, updateReply, activeUser, addReply, deleteReply, global, community, location, history} = props; - - useEffect(() => { - setIsMounted(true); - isMounted && fetchMutedUsers(); - checkLsDraft() - return () => { - setIsMounted(false); - } - }, []); - - useEffect(() => { - if (edit || reply) { - checkLsDraft() - } - }, [edit, reply]); - - const afterVote = (votes: EntryVote[], estimated: number) => { - const {payout} = entry; - const newPayout = payout + estimated; - - updateReply({ - ...entry, - active_votes: votes, - payout: newPayout, - pending_payout_value: String(newPayout) - }); + useEffect(() => { + if (edit || reply) { + checkLsDraft(); } - - const toggleReply = () => { - if (edit) { - return; - } - setReply(!reply); + }, [edit, reply]); + + const afterVote = (votes: EntryVote[], estimated: number) => { + const {payout} = entry; + const newPayout = payout + estimated; + + updateReply({ + ...entry, + active_votes: votes, + payout: newPayout, + pending_payout_value: String(newPayout), + }); + }; + + const toggleReply = () => { + if (edit) { + return; } + setReply(!reply); + }; - const toggleEdit = () => { - if (reply) { - return; - } - setEdit(!edit); + const toggleEdit = () => { + if (reply) { + return; } - - const checkLsDraft = () => { - let replyDraft = ls.get(`reply_draft_${entry?.author}_${entry?.permlink}`) - replyDraft = replyDraft && replyDraft.trim() || "" - setLsDraft(replyDraft) + setEdit(!edit); + }; + + const checkLsDraft = () => { + let replyDraft = ls.get(`reply_draft_${entry?.author}_${entry?.permlink}`); + replyDraft = (replyDraft && replyDraft.trim()) || ''; + setLsDraft(replyDraft); + }; + + const submitReply = (text: string) => { + if (!activeUser || !activeUser.data.__loaded) { + return; } - const submitReply = (text: string) => { - if (!activeUser || !activeUser.data.__loaded) { - return; - } + const {author: parentAuthor, permlink: parentPermlink} = entry; + const author = activeUser.username; + const permlink = createReplyPermlink(entry.author); - const {author: parentAuthor, permlink: parentPermlink} = entry; - const author = activeUser.username; - const permlink = createReplyPermlink(entry.author); - - const jsonMeta = makeJsonMetaDataReply( - entry.json_metadata.tags || ['ecency'], - version - ); - setInProgress(true); - - comment( - author, - parentAuthor, - parentPermlink, - permlink, - '', - text, - jsonMeta, - null, - true - ).then(() => { - const nReply = tempEntry({ - author: activeUser.data as FullAccount, - permlink, - parentAuthor, - parentPermlink, - title: '', - body: text, - tags: [] - }); - - // add new reply to store - addReply(nReply); - - // remove reply draft - ls.remove(`reply_draft_${entry.author}_${entry.permlink}`); - - // close comment box - toggleReply(); - - if (entry.children === 0 && isMounted) { - // Update parent comment. - const nParentReply: Entry = { - ...entry, - children: 1 - } - - updateReply(nParentReply); - } - }).catch((e) => { - console.log(e); - error(formatError(e)); - }).finally(() => { - setInProgress(false); - }); - } - - const _updateReply = (text: string) => { - const {permlink, parent_author: parentAuthor, parent_permlink: parentPermlink} = entry; - const jsonMeta = makeJsonMetaDataReply( - entry.json_metadata.tags || ['ecency'], - version - ); - setInProgress(true); - - comment( - activeUser?.username!, - parentAuthor!, - parentPermlink!, - permlink, - '', - text, - jsonMeta, - null, - ).then(() => { - const nReply: Entry = { - ...entry, - body: text - } - ls.remove(`reply_draft_${entry.author}_${entry.permlink}`); - - updateReply(nReply); // update store - toggleEdit(); // close comment box - }).catch((e) => { - error(formatError(e)); - }).finally(() => { - setInProgress(false); + const jsonMeta = makeJsonMetaDataReply( + entry.json_metadata.tags || ['ecency'], + version, + ); + setInProgress(true); + + comment( + author, + parentAuthor, + parentPermlink, + permlink, + '', + text, + jsonMeta, + null, + true, + ) + .then(() => { + const nReply = tempEntry({ + author: activeUser.data as FullAccount, + permlink, + parentAuthor, + parentPermlink, + title: '', + body: text, + tags: [], }); - } - - const deleted = () => { - deleteReply(entry); - } - const fetchMutedUsers = () => { - if(activeUser){ - getFollowing(activeUser.username, "", "ignore", 100).then(r => { - if (r) { - let filterList = r.map(user=>user.following); - isMounted && setMutedData(filterList as string[]); - } - }) - } - } + // add new reply to store + addReply(nReply); - const created = moment(parseDate(entry.created)); - const readMore = entry.children > 0 && entry.depth > 5; - const showSubList = !readMore && entry.children > 0; - const canEdit = activeUser && activeUser.username === entry.author; + // remove reply draft + ls.remove(`reply_draft_${entry.author}_${entry.permlink}`); - const canMute = activeUser && community ? !!community.team.find(m => { - return m[0] === activeUser.username && - [ROLES.OWNER.toString(), ROLES.ADMIN.toString(), ROLES.MOD.toString()].includes(m[1]) - }) : false; + // close comment box + toggleReply(); - const anchorId = `anchor-@${entry.author}/${entry.permlink}`; + if (entry.children === 0 && isMounted) { + // Update parent comment. + const nParentReply: Entry = { + ...entry, + children: 1, + }; - const selected = location.hash && location.hash.replace("#", "") === `@${entry.author}/${entry.permlink}`; - - let normalComponent = ( -
-
-
+ updateReply(nParentReply); + } + }) + .catch(e => { + console.log(e); + error(formatError(e)); + }) + .finally(() => { + setInProgress(false); + }); + }; + + const _updateReply = (text: string) => { + const { + permlink, + parent_author: parentAuthor, + parent_permlink: parentPermlink, + } = entry; + const jsonMeta = makeJsonMetaDataReply( + entry.json_metadata.tags || ['ecency'], + version, + ); + setInProgress(true); + + comment( + activeUser?.username!, + parentAuthor!, + parentPermlink!, + permlink, + '', + text, + jsonMeta, + null, + ) + .then(() => { + const nReply: Entry = { + ...entry, + body: text, + }; + ls.remove(`reply_draft_${entry.author}_${entry.permlink}`); + + updateReply(nReply); // update store + toggleEdit(); // close comment box + }) + .catch(e => { + error(formatError(e)); + }) + .finally(() => { + setInProgress(false); + }); + }; + + const deleted = () => { + deleteReply(entry); + }; + + const fetchMutedUsers = () => { + if (activeUser) { + getFollowing(activeUser.username, '', 'ignore', 100).then(r => { + if (r) { + let filterList = r.map(user => user.following); + isMounted && setMutedData(filterList as string[]); + } + }); + } + }; + + const created = moment(parseDate(entry.created)); + const readMore = entry.children > 0 && entry.depth > 5; + const showSubList = !readMore && entry.children > 0; + const canEdit = activeUser && activeUser.username === entry.author; + + const canMute = + activeUser && community + ? !!community.team.find(m => { + return ( + m[0] === activeUser.username && + [ + ROLES.OWNER.toString(), + ROLES.ADMIN.toString(), + ROLES.MOD.toString(), + ].includes(m[1]) + ); + }) + : false; + + const anchorId = `anchor-@${entry.author}/${entry.permlink}`; + + const selected = + location.hash && + location.hash.replace('#', '') === `@${entry.author}/${entry.permlink}`; + + let normalComponent = ( +
+
+
+
+
+
+ {ProfileLink({ + ...props, + username: entry.author, + children: ( + + {UserAvatar({...props, username: entry.author, size: 'medium'})} + + ), + })} +
+
+
+
+
-
-
- {ProfileLink({...props, username: entry.author, children: - - {UserAvatar({...props, username: entry.author, size: "medium"})} - - }) - } -
-
-
-
- -
- - {EntryLink({ - ...props, - entry, - children: - {created.fromNow()} - - })} + + {EntryLink({ + ...props, + entry, + children: ( + + {created.fromNow()} + + ), + })} +
+ {(() => { + let menuItems = [ + { + label: _t('g.edit'), + onClick: toggleEdit, + icon: pencilOutlineSvg, + }, + ]; + if ( + !(entry.is_paidout || entry.net_rshares > 0 || entry.children > 0) + ) { + let deleteItem = { + label: '', + onClick: () => {}, + icon: EntryDeleteBtn({ + ...props, + entry, + onSuccess: deleted, + children: ( + + {deleteForeverSvg} {_t('g.delete')} + + ), + }), + }; + menuItems.push(deleteItem); + } + + const menuConfig = { + history: history, + label: '', + icon: dotsHorizontal, + items: menuItems, + }; + + const entryIsMuted = mutedData.includes(entry.author); + const isComment = !!entry.parent_author; + const ownEntry = activeUser && activeUser.username === entry.author; + const isHidden = entry?.net_rshares < -500000000; // 1000 HP + const isMuted = + entry?.stats?.gray && + entry?.net_rshares >= 0 && + entry?.author_reputation >= 0; + const isLowReputation = + entry?.stats?.gray && + entry?.net_rshares >= 0 && + entry?.author_reputation < 0; + const mightContainMutedComments = + activeUser && entryIsMuted && !isComment && !ownEntry; + + return ( + <> + {isMuted && ( +
+ + + + + +
+ )} + + {isHidden && ( +
+ {_t('entry.hidden-warning')} +
+ )} + + {isLowReputation && ( +
+ {_t('entry.lowrep-warning')} +
+ )} + + {mightContainMutedComments && ( +
+ {_t('entry.comments-hidden')} +
+ )} + + +
+ {EntryVoteBtn({ + ...props, + entry, + afterVote: afterVote, + })} + {EntryPayout({ + ...props, + entry, + })} + {EntryVotes({ + ...props, + entry, + })} + + {_t('g.reply')} + + {community && + canMute && + MuteBtn({ + entry, + community: community!, + activeUser: activeUser!, + onSuccess: entry => { + updateReply(entry); + }, + })} + {canEdit && ( +
+
- {(() => { - let menuItems = [ - { - label: _t('g.edit'), - onClick: toggleEdit, - icon: pencilOutlineSvg - }, - ]; - if(!(entry.is_paidout || entry.net_rshares > 0 || entry.children > 0)){ - let deleteItem = { - label: "", - onClick: () => {}, - icon: EntryDeleteBtn({ - ...props, - entry, - onSuccess: deleted, - children: {deleteForeverSvg} {_t('g.delete')} - }) - }; - menuItems.push(deleteItem) - } - - const menuConfig = { - history: history, - label: '', - icon: dotsHorizontal, - items: menuItems - }; - - const entryIsMuted = mutedData.includes(entry.author); - const isComment = !!entry.parent_author; - const ownEntry = activeUser && activeUser.username === entry.author; - const isHidden = entry?.net_rshares < -500000000; // 1000 HP - const isMuted = entry?.stats?.gray && entry?.net_rshares >= 0 && entry?.author_reputation >= 0; - const isLowReputation = entry?.stats?.gray && entry?.net_rshares >= 0 && entry?.author_reputation < 0; - const mightContainMutedComments = activeUser && entryIsMuted && !isComment && !ownEntry; - - return <> - {isMuted && (
- -
)} - - {isHidden && (
- {_t('entry.hidden-warning')} -
)} - - {isLowReputation && (
- {_t('entry.lowrep-warning')} -
)} - - {mightContainMutedComments && (
- {_t('entry.comments-hidden')} -
)} - - -
- {EntryVoteBtn({ - ...props, - entry, - afterVote: afterVote - })} - {EntryPayout({ - ...props, - entry - })} - {EntryVotes({ - ...props, - entry - })} - - {_t("g.reply")} - - {(community && canMute) && MuteBtn({ - entry, - community: community!, - activeUser: activeUser!, - onSuccess: (entry) => { - updateReply(entry); - } - })} - {canEdit && ( -
- -
- )} -
- - })()} - - {readMore && ( -
- {EntryLink({ - ...props, - entry, - children: {_t("discussion.read-more")} - })} -
- )} + )}
-
+ + ); + })()} - {reply && Comment({ + {readMore && ( +
+ {EntryLink({ ...props, - defText: lsDraft, - submitText: _t('g.reply'), - cancellable: true, - onSubmit: submitReply, - onCancel: toggleReply, - inProgress: inProgress, - autoFocus: true - })} - - {edit && Comment({ - ...props, - defText: entry.body, - submitText: _t('g.update'), - cancellable: true, - onSubmit: _updateReply, - onCancel: toggleEdit, - inProgress: inProgress, - autoFocus: true - })} - - {showSubList && } + entry, + children: {_t('discussion.read-more')}, + })} +
+ )}
- ); - - return normalComponent; -} - +
+ + {reply && + Comment({ + ...props, + defText: lsDraft, + submitText: _t('g.reply'), + cancellable: true, + onSubmit: submitReply, + onCancel: toggleReply, + inProgress: inProgress, + autoFocus: true, + })} + + {edit && + Comment({ + ...props, + defText: entry.body, + submitText: _t('g.update'), + cancellable: true, + onSubmit: _updateReply, + onCancel: toggleEdit, + inProgress: inProgress, + autoFocus: true, + })} + + {showSubList && } +
+ ); + + return normalComponent; +}; interface ListProps { - history: History; - location: Location; - global: Global; - dynamicProps: DynamicProps; - users: User[]; - activeUser: ActiveUser | null; - discussion: DiscussionType; - parent: Entry; - community: Community | null; - ui: UI; - addAccount: (data: Account) => void; - setActiveUser: (username: string | null) => void; - updateActiveUser: (data?: Account) => void; - deleteUser: (username: string) => void; - updateReply: (reply: Entry) => void; - addReply: (reply: Entry) => void; - deleteReply: (reply: Entry) => void; - toggleUIProp: (what: ToggleType) => void; + history: History; + location: Location; + global: Global; + dynamicProps: DynamicProps; + users: User[]; + activeUser: ActiveUser | null; + discussion: DiscussionType; + parent: Entry; + community: Community | null; + ui: UI; + addAccount: (data: Account) => void; + setActiveUser: (username: string | null) => void; + updateActiveUser: (data?: Account) => void; + deleteUser: (username: string) => void; + updateReply: (reply: Entry) => void; + addReply: (reply: Entry) => void; + deleteReply: (reply: Entry) => void; + toggleUIProp: (what: ToggleType) => void; } interface ListState { - isHiddenPermitted: boolean; - mutedData: string[]; - isMounted: boolean; + isHiddenPermitted: boolean; + mutedData: string[]; + isMounted: boolean; } export class List extends Component { - state: ListState = { - isHiddenPermitted: false, - mutedData: [], - isMounted: false - } - - componentWillUnmount(){ - document.getElementsByTagName("html")[0].style.position = 'unset'; - this.setState({isMounted: false}); - } - - componentDidMount(){ - this.setState({isMounted: true}); - document.getElementsByTagName("html")[0].style.position = 'relative'; - this.state.isMounted && this.fetchMutedUsers(); + state: ListState = { + isHiddenPermitted: false, + mutedData: [], + isMounted: false, + }; + + componentWillUnmount() { + document.getElementsByTagName('html')[0].style.position = 'unset'; + this.setState({isMounted: false}); + } + + componentDidMount() { + this.setState({isMounted: true}); + document.getElementsByTagName('html')[0].style.position = 'relative'; + this.state.isMounted && this.fetchMutedUsers(); + } + + shouldComponentUpdate(nextProps: Readonly) { + if ( + this.props.discussion === nextProps.discussion && + this.props.activeUser === nextProps.activeUser + ) { + return false; + } else { + return true; } - - shouldComponentUpdate(nextProps: Readonly) { - if(this.props.discussion === nextProps.discussion && this.props.activeUser === nextProps.activeUser) { - return false - } else { - return true + } + + fetchMutedUsers = () => { + const {activeUser} = this.props; + if (activeUser) { + getFollowing(activeUser.username, '', 'ignore', 100).then(r => { + if (r) { + let filterList = r.map(user => user.following); + this.state.isMounted && this.setState({mutedData: filterList}); } + }); } + }; - fetchMutedUsers = () => { - const { activeUser } = this.props; - if(activeUser){ - getFollowing(activeUser.username, "", "ignore", 100).then(r => { - if (r) { - let filterList = r.map(user=>user.following); - this.state.isMounted && this.setState({ mutedData: filterList }); - } - }); - } - } + render() { + const {discussion, parent, activeUser} = this.props; + const {isHiddenPermitted, mutedData} = this.state; - render() { - const {discussion, parent, activeUser} = this.props; - const { isHiddenPermitted, mutedData } = this.state; - - const {list} = discussion; + const {list} = discussion; - let filtered = list.filter( - (x) => x.parent_author === parent.author && x.parent_permlink === parent.permlink - ); + let filtered = list.filter( + x => + x.parent_author === parent.author && + x.parent_permlink === parent.permlink, + ); - if (filtered.length === 0) { - return null; - } + if (filtered.length === 0) { + return null; + } - let mutedContent = filtered.filter(item => ((activeUser && mutedData.includes(item.author) && item.depth === 1 && item.parent_author === parent.author) )); - let unmutedContent = filtered.filter(md => mutedContent.every(fd => fd.post_id !== md.post_id)) - let data = isHiddenPermitted ? [...unmutedContent, ...mutedContent] : unmutedContent; - if(!activeUser){ - data = filtered - } - return ( -
- {data.map((d) => ( - - ))} - {!isHiddenPermitted && mutedContent.length > 0 && activeUser && activeUser.username && -
-
{_t("discussion.reveal-muted-long-description")}
-
this.setState({isHiddenPermitted:true})} className="pointer p-3"> - {_t("g.show")} -
-
- } -
- ); + let mutedContent = filtered.filter( + item => + activeUser && + mutedData.includes(item.author) && + item.depth === 1 && + item.parent_author === parent.author, + ); + let unmutedContent = filtered.filter(md => + mutedContent.every(fd => fd.post_id !== md.post_id), + ); + let data = isHiddenPermitted + ? [...unmutedContent, ...mutedContent] + : unmutedContent; + if (!activeUser) { + data = filtered; } + return ( +
+ {data.map(d => ( + + ))} + {!isHiddenPermitted && + mutedContent.length > 0 && + activeUser && + activeUser.username && ( +
+
+ {_t('discussion.reveal-muted-long-description')} +
+
this.setState({isHiddenPermitted: true})} + className='pointer p-3' + > + {_t('g.show')} +
+
+ )} +
+ ); + } } - interface Props { - history: History; - location: Location - global: Global; - dynamicProps: DynamicProps; - users: User[]; - activeUser: ActiveUser | null; - parent: Entry; - community: Community | null; - discussion: DiscussionType; - ui: UI; - addAccount: (data: Account) => void; - setActiveUser: (username: string | null) => void; - updateActiveUser: (data?: Account) => void; - deleteUser: (username: string) => void; - fetchDiscussion: (parent_author: string, parent_permlink: string) => void; - sortDiscussion: (order: SortOrder) => void; - resetDiscussion: () => void; - updateReply: (reply: Entry) => void; - addReply: (reply: Entry) => void; - deleteReply: (reply: Entry) => void; - toggleUIProp: (what: ToggleType) => void; + history: History; + location: Location; + global: Global; + dynamicProps: DynamicProps; + users: User[]; + activeUser: ActiveUser | null; + parent: Entry; + community: Community | null; + discussion: DiscussionType; + ui: UI; + addAccount: (data: Account) => void; + setActiveUser: (username: string | null) => void; + updateActiveUser: (data?: Account) => void; + deleteUser: (username: string) => void; + fetchDiscussion: (parent_author: string, parent_permlink: string) => void; + sortDiscussion: (order: SortOrder) => void; + resetDiscussion: () => void; + updateReply: (reply: Entry) => void; + addReply: (reply: Entry) => void; + deleteReply: (reply: Entry) => void; + toggleUIProp: (what: ToggleType) => void; } interface State { - visible: boolean; - isMounted: boolean; + visible: boolean; + isMounted: boolean; } export class Discussion extends Component { - state: State = { - visible: !!this.props.activeUser, - isMounted: false + state: State = { + visible: !!this.props.activeUser, + isMounted: false, + }; + + componentDidMount() { + this.setState({isMounted: true}); + const {activeUser} = this.props; + if (activeUser) { + this.fetch(); } + } - componentDidMount() { - this.setState({isMounted: true}); - const {activeUser} = this.props; - if (activeUser) { - this.fetch(); - } + componentDidUpdate(prevProps: Readonly): void { + const {parent} = this.props; + if (parent.url !== prevProps.parent.url) { + // url changed + this.fetch(); } - componentDidUpdate(prevProps: Readonly): void { - const {parent} = this.props; - if (parent.url !== prevProps.parent.url) { // url changed - this.fetch(); - } - - const {discussion} = this.props; - if (prevProps.discussion.list.length === 0 && discussion.list.length > 0) { - const {location} = this.props; - if (location.hash) { - const permlink = location.hash.replace("#", ""); - const anchorId = `anchor-${permlink}`; - const anchorEl = document.getElementById(anchorId); - if (anchorEl) { - anchorEl.scrollIntoView(); - } - } + const {discussion} = this.props; + if (prevProps.discussion.list.length === 0 && discussion.list.length > 0) { + const {location} = this.props; + if (location.hash) { + const permlink = location.hash.replace('#', ''); + const anchorId = `anchor-${permlink}`; + const anchorEl = document.getElementById(anchorId); + if (anchorEl) { + anchorEl.scrollIntoView(); } + } } - - componentWillUnmount() { - const {resetDiscussion} = this.props; - resetDiscussion(); - this.setState({isMounted: false}); + } + + componentWillUnmount() { + const {resetDiscussion} = this.props; + resetDiscussion(); + this.setState({isMounted: false}); + } + + fetch = () => { + const {parent, fetchDiscussion} = this.props; + const {author, permlink} = parent; + fetchDiscussion(author, permlink); + }; + + orderChanged = ( + e: React.ChangeEvent, + ) => { + const order = e.target.value as SortOrder; + const {sortDiscussion} = this.props; + sortDiscussion(SortOrder[order]); + }; + + show = () => { + this.setState({visible: true}); + this.fetch(); + }; + + render() { + const {parent, discussion, activeUser} = this.props; + const {visible} = this.state; + const {loading, order} = discussion; + const count = parent.children; + + if (loading) { + return ( +
+ +
+ ); } - fetch = () => { - const {parent, fetchDiscussion} = this.props; - const {author, permlink} = parent; - fetchDiscussion(author, permlink); - }; - - orderChanged = (e: React.ChangeEvent) => { - const order = e.target.value as SortOrder; - const {sortDiscussion} = this.props; - sortDiscussion(SortOrder[order]); - }; + const join = ( +
+
{commentSvg}
+
{_t('discussion.join')}
+ {LoginRequired({ + ...this.props, + children: , + })} +
+ ); - show = () => { - this.setState({visible: true}); - this.fetch(); + if (!activeUser && count < 1) { + return
{join}
; } - render() { - const {parent, discussion, activeUser} = this.props; - const {visible} = this.state; - const {loading, order} = discussion; - const count = parent.children; - - if (loading) { - return
; - } - - const join =
-
{commentSvg}
-
{_t("discussion.join")}
- {LoginRequired({ - ...this.props, - children: - })} -
; - - if (!activeUser && count < 1) { - return
{join}
; - } - - if (count < 1) { - return
- } - - const strCount = count > 1 ? _t("discussion.n-replies", {n: count}) : _t("discussion.replies"); - - if (!visible && count >= 1) { - return
-
-
{commentSvg}
-
{strCount}
- -
-
- } - - return ( -
- {!activeUser && <>{join}} -
-
{commentSvg} {strCount}
- -
- {_t("discussion.order")} - - - - - - -
-
- -
- ); + if (count < 1) { + return
; } -} -export default (p: Props) => { - const props: Props = { - history: p.history, - location: p.location, - global: p.global, - dynamicProps: p.dynamicProps, - users: p.users, - activeUser: p.activeUser, - parent: p.parent, - community: p.community, - discussion: p.discussion, - ui: p.ui, - addAccount: p.addAccount, - setActiveUser: p.setActiveUser, - updateActiveUser: p.updateActiveUser, - deleteUser: p.deleteUser, - fetchDiscussion: p.fetchDiscussion, - sortDiscussion: p.sortDiscussion, - resetDiscussion: p.resetDiscussion, - updateReply: p.updateReply, - addReply: p.addReply, - deleteReply: p.deleteReply, - toggleUIProp: p.toggleUIProp, + const strCount = + count > 1 + ? _t('discussion.n-replies', {n: count}) + : _t('discussion.replies'); + + if (!visible && count >= 1) { + return ( +
+
+
{commentSvg}
+
{strCount}
+ +
+
+ ); } - return ; + return ( +
+ {!activeUser && <>{join}} +
+
+ {' '} + {commentSvg} {strCount} +
+ +
+ {_t('discussion.order')} + + + + + + +
+
+ +
+ ); + } } + +export default (p: Props) => { + const props: Props = { + history: p.history, + location: p.location, + global: p.global, + dynamicProps: p.dynamicProps, + users: p.users, + activeUser: p.activeUser, + parent: p.parent, + community: p.community, + discussion: p.discussion, + ui: p.ui, + addAccount: p.addAccount, + setActiveUser: p.setActiveUser, + updateActiveUser: p.updateActiveUser, + deleteUser: p.deleteUser, + fetchDiscussion: p.fetchDiscussion, + sortDiscussion: p.sortDiscussion, + resetDiscussion: p.resetDiscussion, + updateReply: p.updateReply, + addReply: p.addReply, + deleteReply: p.deleteReply, + toggleUIProp: p.toggleUIProp, + }; + + return ; +}; diff --git a/src/common/components/discussion/test1.spec.tsx b/src/common/components/discussion/test1.spec.tsx index 5c8d4be8cde..d114598017e 100644 --- a/src/common/components/discussion/test1.spec.tsx +++ b/src/common/components/discussion/test1.spec.tsx @@ -1,119 +1,121 @@ -import React from "react"; +import React from 'react'; -import Discussion from "./index"; +import Discussion from './index'; -import {Discussion as DiscussionType, SortOrder} from '../../store/discussion/types'; +import { + Discussion as DiscussionType, + SortOrder, +} from '../../store/discussion/types'; -import {create, act} from "react-test-renderer"; +import {create, act} from 'react-test-renderer'; -import {createBrowserHistory, createLocation} from "history"; +import {createBrowserHistory, createLocation} from 'history'; -import {globalInstance, discussionInstace1, dynamicPropsIntance1, activeUserMaker, communityInstance1, UiInstance, allOver} from "../../helper/test-helper"; +import { + globalInstance, + discussionInstace1, + dynamicPropsIntance1, + activeUserMaker, + communityInstance1, + UiInstance, + allOver, +} from '../../helper/test-helper'; -jest.mock("moment", () => () => ({ - fromNow: () => "3 days ago", - format: (f: string, s: string) => "2020-01-01 23:12:00", +jest.mock('moment', () => () => ({ + fromNow: () => '3 days ago', + format: (f: string, s: string) => '2020-01-01 23:12:00', })); const [parent] = discussionInstace1; const [, ...replies] = discussionInstace1; const discussion: DiscussionType = { - list: replies, - loading: false, - error: false, - order: SortOrder.trending -} + list: replies, + loading: false, + error: false, + order: SortOrder.trending, +}; const defProps = { - history: createBrowserHistory(), - location: createLocation({}), - global: globalInstance, - dynamicProps: dynamicPropsIntance1, - users: [], - activeUser: activeUserMaker("foo"), - ui: UiInstance, - parent, - community: null, - discussion, - addAccount: () => { - }, - setActiveUser: () => { - }, - updateActiveUser: () => { - }, - deleteUser: () => { - }, - fetchDiscussion: () => { - }, - sortDiscussion: () => { - }, - resetDiscussion: () => { - }, - updateReply: () => { - }, - addReply: () => { - }, - deleteReply: () => { - }, - toggleUIProp: () => { - } + history: createBrowserHistory(), + location: createLocation({}), + global: globalInstance, + dynamicProps: dynamicPropsIntance1, + users: [], + activeUser: activeUserMaker('foo'), + ui: UiInstance, + parent, + community: null, + discussion, + addAccount: () => {}, + setActiveUser: () => {}, + updateActiveUser: () => {}, + deleteUser: () => {}, + fetchDiscussion: () => {}, + sortDiscussion: () => {}, + resetDiscussion: () => {}, + updateReply: () => {}, + addReply: () => {}, + deleteReply: () => {}, + toggleUIProp: () => {}, }; -it("(1) Full render with active user", async() => { - // render the component - let component = await create(); +it('(1) Full render with active user', async () => { + // render the component + let component = await create(); - // make assertions on component - expect(component.toJSON()).toMatchSnapshot(); + // make assertions on component + expect(component.toJSON()).toMatchSnapshot(); }); -it("(2) Full render with no active user", async() => { - const props = { - ...defProps, - activeUser: null - } - // render the component - let component = await create() +it('(2) Full render with no active user', async () => { + const props = { + ...defProps, + activeUser: null, + }; + // render the component + let component = await create(); - // make assertions on component - expect(component.toJSON()).toMatchSnapshot(); + // make assertions on component + expect(component.toJSON()).toMatchSnapshot(); }); -it("(3) With selected item", async() => { - const props = { - ...defProps, - location: createLocation({hash: "#@forykw/re-esteemapp-202067t12246786z"}), - } - // render the component - let component = await create(); +it('(3) With selected item', async () => { + const props = { + ...defProps, + location: createLocation({hash: '#@forykw/re-esteemapp-202067t12246786z'}), + }; + // render the component + let component = await create(); - // make assertions on component - expect(component.toJSON()).toMatchSnapshot(); + // make assertions on component + expect(component.toJSON()).toMatchSnapshot(); }); +it('(4) Show mute button, muted comment', async () => { + let [reply] = replies; + reply = { + ...reply, + stats: {hide: false, gray: true, total_votes: 180, flag_weight: 0}, + }; -it("(4) Show mute button, muted comment", async() => { - let [reply] = replies; - reply = {...reply, stats: {hide: false, gray: true, total_votes: 180, flag_weight: 0}} - - const discussion: DiscussionType = { - list: [reply, replies[1]], - loading: false, - error: false, - order: SortOrder.trending - } + const discussion: DiscussionType = { + list: [reply, replies[1]], + loading: false, + error: false, + order: SortOrder.trending, + }; - const nProps = { - ...defProps, - discussion, - activeUser: activeUserMaker("hive-148441"), - community: communityInstance1 - } + const nProps = { + ...defProps, + discussion, + activeUser: activeUserMaker('hive-148441'), + community: communityInstance1, + }; - // render the component - const component = await create(); + // render the component + const component = await create(); - // make assertions on component - expect(component.toJSON()).toMatchSnapshot(); + // make assertions on component + expect(component.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/discussion/test2.spec.tsx b/src/common/components/discussion/test2.spec.tsx index adc3b01f0d0..d67b2f443ae 100644 --- a/src/common/components/discussion/test2.spec.tsx +++ b/src/common/components/discussion/test2.spec.tsx @@ -1,78 +1,73 @@ -import React from "react"; +import React from 'react'; -import Discussion from "./index"; +import Discussion from './index'; -import {Discussion as DiscussionType, SortOrder} from '../../store/discussion/types' -import {allOver, UiInstance} from "../../helper/test-helper"; +import { + Discussion as DiscussionType, + SortOrder, +} from '../../store/discussion/types'; +import {allOver, UiInstance} from '../../helper/test-helper'; -import {create, act} from "react-test-renderer"; +import {create, act} from 'react-test-renderer'; -import {createBrowserHistory, createLocation} from "history"; +import {createBrowserHistory, createLocation} from 'history'; -import {globalInstance, discussionInstace1, dynamicPropsIntance1, activeUserMaker} from "../../helper/test-helper"; +import { + globalInstance, + discussionInstace1, + dynamicPropsIntance1, + activeUserMaker, +} from '../../helper/test-helper'; const [parent] = discussionInstace1; const discussion: DiscussionType = { - list: [], - loading: false, - error: false, - order: SortOrder.trending -} + list: [], + loading: false, + error: false, + order: SortOrder.trending, +}; const defProps = { - history: createBrowserHistory(), - location: createLocation({}), - global: globalInstance, - dynamicProps: dynamicPropsIntance1, - users: [], - activeUser: null, - parent: {...parent, children: 0}, - community: null, - discussion, - ui: UiInstance, - addAccount: () => { - }, - setActiveUser: () => { - }, - updateActiveUser: () => { - }, - deleteUser: () => { - }, - fetchDiscussion: () => { - }, - sortDiscussion: () => { - }, - resetDiscussion: () => { - }, - updateReply: () => { - }, - addReply: () => { - }, - deleteReply: () => { - }, - toggleUIProp: () => { - } + history: createBrowserHistory(), + location: createLocation({}), + global: globalInstance, + dynamicProps: dynamicPropsIntance1, + users: [], + activeUser: null, + parent: {...parent, children: 0}, + community: null, + discussion, + ui: UiInstance, + addAccount: () => {}, + setActiveUser: () => {}, + updateActiveUser: () => {}, + deleteUser: () => {}, + fetchDiscussion: () => {}, + sortDiscussion: () => {}, + resetDiscussion: () => {}, + updateReply: () => {}, + addReply: () => {}, + deleteReply: () => {}, + toggleUIProp: () => {}, }; +it('(1) Empty list with no active user', async () => { + // render the component + let component = await create(); -it("(1) Empty list with no active user", async() => { - // render the component - let component = await create(); - - // make assertions on component - expect(component.toJSON()).toMatchSnapshot(); + // make assertions on component + expect(component.toJSON()).toMatchSnapshot(); }); +it('(2) Empty list with active user', async () => { + const props = { + ...defProps, + activeUser: activeUserMaker('foo'), + }; + // render the component + let component = await create(); -it("(2) Empty list with active user", async() => { - const props = { - ...defProps, - activeUser: activeUserMaker("foo") - } - // render the component - let component = await create(); - - // make assertions on component - expect(component.toJSON()).toMatchSnapshot(); + // make assertions on component + expect(component.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/download-trigger/index.spec.tsx b/src/common/components/download-trigger/index.spec.tsx index 885ce1ff643..85460fe3237 100644 --- a/src/common/components/download-trigger/index.spec.tsx +++ b/src/common/components/download-trigger/index.spec.tsx @@ -1,90 +1,84 @@ import React from 'react'; -import renderer from "react-test-renderer"; +import renderer from 'react-test-renderer'; import {DialogContent} from './index'; -import {allOver} from "../../helper/test-helper"; +import {allOver} from '../../helper/test-helper'; -let MOCK: string = ""; +let MOCK: string = ''; -Object.defineProperty( - global.navigator, - 'appVersion', - { - get: () => { - if (MOCK === "MacOS") { - return '5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36'; - } - - if (MOCK === "WindowsOS") { - return "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36"; - } +Object.defineProperty(global.navigator, 'appVersion', { + get: () => { + if (MOCK === 'MacOS') { + return '5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36'; + } - if (MOCK === "LinuxOS") { - return "Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0"; - } + if (MOCK === 'WindowsOS') { + return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36'; + } - if (MOCK === "AndroidOS") { - return "Mozilla/5.0 (Linux; Android 6.0.1; RedMi Note 5 Build/RB3N5C; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/68.0.3440.91 Mobile Safari/537.36"; - } + if (MOCK === 'LinuxOS') { + return 'Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0'; + } - if (MOCK === "iOS") { - return "Mozilla/5.0 (iPhone; CPU iPhone OS 10_0 like Mac OS X) AppleWebKit/602.1.38 (KHTML, like Gecko) Version/10.0 Mobile/14A5297c Safari/602.1" - } + if (MOCK === 'AndroidOS') { + return 'Mozilla/5.0 (Linux; Android 6.0.1; RedMi Note 5 Build/RB3N5C; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/68.0.3440.91 Mobile Safari/537.36'; + } - return "" - } + if (MOCK === 'iOS') { + return 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_0 like Mac OS X) AppleWebKit/602.1.38 (KHTML, like Gecko) Version/10.0 Mobile/14A5297c Safari/602.1'; } -); -jest.mock("../../api/misc", () => ({ - geLatestDesktopTag: () => new Promise((resolve) => { - resolve("3.0.16") - }) + return ''; + }, +}); + +jest.mock('../../api/misc', () => ({ + geLatestDesktopTag: () => + new Promise(resolve => { + resolve('3.0.16'); + }), })); it('(1) Mac.', async () => { - MOCK = "MacOS" - const component = await renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + MOCK = 'MacOS'; + const component = await renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); it('(2) Windows.', async () => { - MOCK = "WindowsOS" - const component = await renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + MOCK = 'WindowsOS'; + const component = await renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); it('(3) Linux.', async () => { - MOCK = "LinuxOS" - const component = await renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + MOCK = 'LinuxOS'; + const component = await renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); it('(4) Android.', async () => { - MOCK = "AndroidOS" - const component = await renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + MOCK = 'AndroidOS'; + const component = await renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); it('(5) iOS.', async () => { - MOCK = "iOS" - const component = await renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + MOCK = 'iOS'; + const component = await renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); it('(6) Other.', async () => { - MOCK = "" - const component = await renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + MOCK = ''; + const component = await renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); - - - diff --git a/src/common/components/download-trigger/index.tsx b/src/common/components/download-trigger/index.tsx index f29b9381823..84b269d6efb 100644 --- a/src/common/components/download-trigger/index.tsx +++ b/src/common/components/download-trigger/index.tsx @@ -2,129 +2,169 @@ import React, {Component, Fragment} from 'react'; import {Modal} from 'react-bootstrap'; -import BaseComponent from "../base"; +import BaseComponent from '../base'; -import {geLatestDesktopTag} from "../../api/misc"; +import {geLatestDesktopTag} from '../../api/misc'; import platform from '../../util/platform'; -import { history } from '../../store'; +import {history} from '../../store'; interface ContentState { - desktopTag: string; + desktopTag: string; } export class DialogContent extends BaseComponent<{}, ContentState> { - - state: ContentState = { - desktopTag: "3.0.15" - } - - componentDidMount() { - geLatestDesktopTag().then(r => { - this.setState({desktopTag: r}); - }) - } - - render() { - const {desktopTag} = this.state; - - const os = platform(window); - - return
-

Download

-
Enjoy Ecency for iPhone, iPad and Android, as well as Windows, Mac or Linux devices
-
- {(os !== 'iOS' && os !== 'AndroidOS' && os === 'WindowsOS') && - Windows - } - {(os !== 'iOS' && os !== 'AndroidOS' && os === 'MacOS') && - Mac - } - {(os !== 'iOS' && os !== 'AndroidOS' && (os === 'UnixOS' || os === 'LinuxOS')) && - Linux - } - {(os === 'AndroidOS' || os !== 'iOS') && - Android - } - {(os === 'iOS' || os !== 'AndroidOS') && - iOS - } -
-
; - } + state: ContentState = { + desktopTag: '3.0.15', + }; + + componentDidMount() { + geLatestDesktopTag().then(r => { + this.setState({desktopTag: r}); + }); + } + + render() { + const {desktopTag} = this.state; + + const os = platform(window); + + return ( +
+

Download

+
+ Enjoy Ecency for iPhone, iPad and Android, as well as Windows, Mac or + Linux devices +
+
+ {os !== 'iOS' && os !== 'AndroidOS' && os === 'WindowsOS' && ( + + Windows + + )} + {os !== 'iOS' && os !== 'AndroidOS' && os === 'MacOS' && ( + + Mac + + )} + {os !== 'iOS' && + os !== 'AndroidOS' && + (os === 'UnixOS' || os === 'LinuxOS') && ( + + Linux + + )} + {(os === 'AndroidOS' || os !== 'iOS') && ( + + Android + + )} + {(os === 'iOS' || os !== 'AndroidOS') && ( + + iOS + + )} +
+
+ ); + } } interface Props { - children: JSX.Element + children: JSX.Element; } interface State { - modal: boolean + modal: boolean; } -export default class DownloadTrigger extends Component { - state: State = { - modal: false, - }; +export default class DownloadTrigger extends Component { + state: State = { + modal: false, + }; - toggle = () => { - const {modal} = this.state; + toggle = () => { + const {modal} = this.state; - this.setState({modal: !modal}); - }; + this.setState({modal: !modal}); + }; - componentDidMount(){ - if(history?.location.hash === "#download"){ - this.toggle() - } + componentDidMount() { + if (history?.location.hash === '#download') { + this.toggle(); } - - componentDidUpdate(prevProps:Props, prevStates: State){ - if(prevStates.modal !== this.state.modal){ - if(!this.state.modal){ - let scrollToTop: any = document.getElementsByClassName("overlay-for-introduction"); - - scrollToTop = scrollToTop.length > 0;; - - if(scrollToTop){ - window.scrollTo({ - top: 0, - behavior: "smooth", - }); - } - } + } + + componentDidUpdate(prevProps: Props, prevStates: State) { + if (prevStates.modal !== this.state.modal) { + if (!this.state.modal) { + let scrollToTop: any = document.getElementsByClassName( + 'overlay-for-introduction', + ); + + scrollToTop = scrollToTop.length > 0; + + if (scrollToTop) { + window.scrollTo({ + top: 0, + behavior: 'smooth', + }); } + } } - - render() { - const {children} = this.props; - const {modal} = this.state; - - const clonedChildren = React.cloneElement(children, { - onClick: this.toggle - }); - - return - {clonedChildren} - - {modal && - { - this.toggle(); - }}> - - - - - } - ; - } + } + + render() { + const {children} = this.props; + const {modal} = this.state; + + const clonedChildren = React.cloneElement(children, { + onClick: this.toggle, + }); + + return ( + + {clonedChildren} + + {modal && ( + { + this.toggle(); + }} + > + + + + + )} + + ); + } } diff --git a/src/common/components/drafts/index.spec.tsx b/src/common/components/drafts/index.spec.tsx index e96ba2e6347..6112bd4504c 100644 --- a/src/common/components/drafts/index.spec.tsx +++ b/src/common/components/drafts/index.spec.tsx @@ -1,93 +1,104 @@ import * as React from 'react'; -import renderer from "react-test-renderer"; +import renderer from 'react-test-renderer'; -import {createBrowserHistory, createLocation} from "history"; +import {createBrowserHistory, createLocation} from 'history'; import {Drafts} from './index'; -import {globalInstance, activeUserInstance, fullAccountInstance, communityInstance1, allOver} from "../../helper/test-helper"; +import { + globalInstance, + activeUserInstance, + fullAccountInstance, + communityInstance1, + allOver, +} from '../../helper/test-helper'; -let TEST_MODE = 0 +let TEST_MODE = 0; -jest.mock("../../api/bridge", () => ({ - getCommunity: () => new Promise((resolve) => { - resolve(communityInstance1); - }) +jest.mock('../../api/bridge', () => ({ + getCommunity: () => + new Promise(resolve => { + resolve(communityInstance1); + }), })); -jest.mock("../../api/private-api", () => ({ - getDrafts: () => - new Promise((resolve) => { - if (TEST_MODE === 0) { - resolve([]); - } +jest.mock('../../api/private-api', () => ({ + getDrafts: () => + new Promise(resolve => { + if (TEST_MODE === 0) { + resolve([]); + } - if (TEST_MODE === 1) { - resolve([{ - "title": "Nam quis enim laoreet", - "body": "Vivamus vel lorem ut metus lacinia pharetra. ", - "created": "Mon Aug 10 2020 17:39:16 GMT+0200 (Central European Summer Time)", - "tags": "hive-184437 ecency", - "_id": "5f316a24baede01c77aa15fc", - "post_type": null, - "timestamp": 1597073956580 - }, { - "title": "Nam vel tincidunt ante, in mattis velit", - "body": "Lorem ipsum dolor sit amet", - "created": "Tue Aug 11 2020 08:59:43 GMT+0200 (Central European Summer Time)", - "tags": "ecency", - "_id": "5f3241dfbaede01c77aa1674", - "post_type": null, - "timestamp": 1597129183659 - }, { - "title": "Aliquam luctus egestas enim", - "body": "Quis rhoncus dui scelerisque sit amet.\n", - "created": "Tue Aug 11 2020 09:57:22 GMT+0200 (Central European Summer Time)", - "tags": "photofeed ecency", - "_id": "5f324f62baede01c77aa168a", - "post_type": null, - "timestamp": 1597132642791 - }]) - } - }), + if (TEST_MODE === 1) { + resolve([ + { + title: 'Nam quis enim laoreet', + body: 'Vivamus vel lorem ut metus lacinia pharetra. ', + created: + 'Mon Aug 10 2020 17:39:16 GMT+0200 (Central European Summer Time)', + tags: 'hive-184437 ecency', + _id: '5f316a24baede01c77aa15fc', + post_type: null, + timestamp: 1597073956580, + }, + { + title: 'Nam vel tincidunt ante, in mattis velit', + body: 'Lorem ipsum dolor sit amet', + created: + 'Tue Aug 11 2020 08:59:43 GMT+0200 (Central European Summer Time)', + tags: 'ecency', + _id: '5f3241dfbaede01c77aa1674', + post_type: null, + timestamp: 1597129183659, + }, + { + title: 'Aliquam luctus egestas enim', + body: 'Quis rhoncus dui scelerisque sit amet.\n', + created: + 'Tue Aug 11 2020 09:57:22 GMT+0200 (Central European Summer Time)', + tags: 'photofeed ecency', + _id: '5f324f62baede01c77aa168a', + post_type: null, + timestamp: 1597132642791, + }, + ]); + } + }), })); -jest.mock("moment", () => () => ({ - fromNow: () => "3 days ago", - format: (f: string, s: string) => "2020-01-01 23:12:00", +jest.mock('moment', () => () => ({ + fromNow: () => '3 days ago', + format: (f: string, s: string) => '2020-01-01 23:12:00', })); const activeUser = {...activeUserInstance, data: fullAccountInstance}; - it('(1) Default render.', async () => { - const props = { - history: createBrowserHistory(), - location: createLocation({}), - global: globalInstance, - activeUser: {...activeUser}, - onHide: () => { - } - }; + const props = { + history: createBrowserHistory(), + location: createLocation({}), + global: globalInstance, + activeUser: {...activeUser}, + onHide: () => {}, + }; - const component = await renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + const component = await renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); it('(2) Test with data.', async () => { - TEST_MODE = 1; + TEST_MODE = 1; - const props = { - history: createBrowserHistory(), - location: createLocation({}), - global: globalInstance, - activeUser: {...activeUser}, - onHide: () => { - } - }; + const props = { + history: createBrowserHistory(), + location: createLocation({}), + global: globalInstance, + activeUser: {...activeUser}, + onHide: () => {}, + }; - const component = renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + const component = renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/drafts/index.tsx b/src/common/components/drafts/index.tsx index 3190571f9c5..fdd211b9fd8 100644 --- a/src/common/components/drafts/index.tsx +++ b/src/common/components/drafts/index.tsx @@ -1,283 +1,357 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import {History, Location} from "history"; +import {History, Location} from 'history'; -import moment from "moment"; +import moment from 'moment'; -import {Form, FormControl, Modal} from "react-bootstrap"; +import {Form, FormControl, Modal} from 'react-bootstrap'; -import {Global} from "../../store/global/types"; -import {ActiveUser} from "../../store/active-user/types"; -import {FullAccount} from "../../store/accounts/types"; +import {Global} from '../../store/global/types'; +import {ActiveUser} from '../../store/active-user/types'; +import {FullAccount} from '../../store/accounts/types'; -import BaseComponent from "../base"; -import UserAvatar from "../user-avatar"; -import LinearProgress from "../linear-progress"; -import PopoverConfirm from "../popover-confirm"; -import Tooltip from "../tooltip"; -import Tag from "../tag"; -import {error} from "../feedback"; +import BaseComponent from '../base'; +import UserAvatar from '../user-avatar'; +import LinearProgress from '../linear-progress'; +import PopoverConfirm from '../popover-confirm'; +import Tooltip from '../tooltip'; +import Tag from '../tag'; +import {error} from '../feedback'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import {getDrafts, Draft, deleteDraft} from "../../api/private-api"; +import {getDrafts, Draft, deleteDraft} from '../../api/private-api'; -import accountReputation from "../../helper/account-reputation"; +import accountReputation from '../../helper/account-reputation'; -import defaults from "../../constants/defaults.json"; +import defaults from '../../constants/defaults.json'; -import {deleteForeverSvg, pencilOutlineSvg} from "../../img/svg"; +import {deleteForeverSvg, pencilOutlineSvg} from '../../img/svg'; -import {catchPostImage, postBodySummary, setProxyBase} from "@ecency/render-helper"; +import { + catchPostImage, + postBodySummary, + setProxyBase, +} from '@ecency/render-helper'; setProxyBase(defaults.imageServer); - interface ItemProps { - history: History; - global: Global; - draft: Draft; - activeUser: ActiveUser; - editFn: (item: Draft) => void; - deleteFn: (item: Draft) => void; + history: History; + global: Global; + draft: Draft; + activeUser: ActiveUser; + editFn: (item: Draft) => void; + deleteFn: (item: Draft) => void; } export class ListItem extends Component { - render() { - const {activeUser, draft, editFn, deleteFn, global} = this.props; - if (!activeUser.data.__loaded) { - return null; - } - const fallbackImage = global.isElectron ? "./img/fallback.png" : require("../../img/fallback.png"); - const noImage = global.isElectron ? "./img/noimage.svg" : require("../../img/noimage.svg"); - - const account = activeUser.data as FullAccount; - - const author = account.name - const reputation = account.reputation; - - const tags = draft.tags ? draft.tags.split(/[ ,]+/) : []; - const tag = tags[0] || ''; - const img = catchPostImage(draft.body, 600, 500, global.canUseWebp ? 'webp' : 'match') || noImage; - const summary = postBodySummary(draft.body, 200); - - const date = moment(new Date(draft.created)); - const dateRelative = date.fromNow(true); - const dateFormatted = date.format("LLLL"); - - return
-
-
- - {Tag({ - ...this.props, - tag, - type: "span", - children: {tag} - })} - {dateRelative} -
+ render() { + const {activeUser, draft, editFn, deleteFn, global} = this.props; + if (!activeUser.data.__loaded) { + return null; + } + const fallbackImage = global.isElectron + ? './img/fallback.png' + : require('../../img/fallback.png'); + const noImage = global.isElectron + ? './img/noimage.svg' + : require('../../img/noimage.svg'); + + const account = activeUser.data as FullAccount; + + const author = account.name; + const reputation = account.reputation; + + const tags = draft.tags ? draft.tags.split(/[ ,]+/) : []; + const tag = tags[0] || ''; + const img = + catchPostImage( + draft.body, + 600, + 500, + global.canUseWebp ? 'webp' : 'match', + ) || noImage; + const summary = postBodySummary(draft.body, 200); + + const date = moment(new Date(draft.created)); + const dateRelative = date.fromNow(true); + const dateFormatted = date.format('LLLL'); + + return ( +
+
+
+ -
-
{ - editFn(draft) - }}> -
- {draft.title} { - const target = e.target as HTMLImageElement; - target.src = fallbackImage; - }} - className={img === noImage ? "no-img" : ""} - /> -
-
- - + {Tag({ + ...this.props, + tag, + type: 'span', + children: {tag}, + })} + + {dateRelative} + +
+
+
+
{ + editFn(draft); + }} + > +
+ {draft.title} { + const target = e.target as HTMLImageElement; + target.src = fallbackImage; + }} + className={img === noImage ? 'no-img' : ''} + /> +
+
+ +
- } +
+ ); + } } interface Props { - global: Global; - history: History; - location: Location; - activeUser: ActiveUser; - onHide: () => void; - onPick?: (url: string) => void; + global: Global; + history: History; + location: Location; + activeUser: ActiveUser; + onHide: () => void; + onPick?: (url: string) => void; } interface State { - loading: boolean, - list: Draft[], - filter: string, - innerRef: any + loading: boolean; + list: Draft[]; + filter: string; + innerRef: any; } export class Drafts extends BaseComponent { - constructor(props:Props){ - super(props); - this.state = { - loading: true, - list: [], - filter: "", - innerRef: React.createRef() - } - } - - componentDidMount() { - this.fetch(); + constructor(props: Props) { + super(props); + this.state = { + loading: true, + list: [], + filter: '', + innerRef: React.createRef(), + }; + } + + componentDidMount() { + this.fetch(); + } + + componentDidUpdate(prevProps: Props, prevState: State) { + if ( + this.state.loading !== prevState.loading && + !this.state.loading && + this.state.list.length > 0 + ) { + this.state!.innerRef!.current && this.state!.innerRef!.current!.focus(); } - - componentDidUpdate(prevProps:Props, prevState: State){ - if(this.state.loading !== prevState.loading && !this.state.loading && this.state.list.length > 0){ - this.state!.innerRef!.current && this.state!.innerRef!.current!.focus() + } + + fetch = () => { + const {activeUser} = this.props; + + this.stateSet({loading: true}); + getDrafts(activeUser?.username!) + .then(items => { + this.stateSet({list: this.sort(items)}); + }) + .catch(() => { + error(_t('g.server-error')); + }) + .finally(() => { + this.stateSet({loading: false}); + }); + }; + + sort = (items: Draft[]) => + items.sort((a, b) => { + return new Date(b.created).getTime() > new Date(a.created).getTime() + ? 1 + : -1; + }); + + delete = (item: Draft) => { + const {activeUser, location, history} = this.props; + + deleteDraft(activeUser?.username!, item._id) + .then(() => { + const {list} = this.state; + const nList = [...list].filter(x => x._id !== item._id); + + this.stateSet({list: this.sort(nList)}); + + // if user editing the draft, redirect to submit page + if (location.pathname === `/draft/${item._id}`) { + history.push('/submit'); } - } - - fetch = () => { - const {activeUser} = this.props; - - this.stateSet({loading: true}); - getDrafts(activeUser?.username!).then(items => { - this.stateSet({list: this.sort(items)}); - }).catch(() => { - error(_t('g.server-error')); - }).finally(() => { - this.stateSet({loading: false}); - }); - } - - sort = (items: Draft[]) => - items.sort((a, b) => { - return new Date(b.created).getTime() > new Date(a.created).getTime() ? 1 : -1; - }); - - delete = (item: Draft) => { - const {activeUser, location, history} = this.props; - - deleteDraft(activeUser?.username!, item._id).then(() => { - const {list} = this.state; - const nList = [...list].filter(x => x._id !== item._id); - - this.stateSet({list: this.sort(nList)}); - - // if user editing the draft, redirect to submit page - if (location.pathname === `/draft/${item._id}`) { - history.push('/submit'); - } - }).catch(() => { - error(_t('g.server-error')); - }) - } - - edit = (item: Draft) => { - const {history, onHide} = this.props; - - history.push(`/draft/${item._id}`); - onHide(); - } - - filterChanged = (e: React.ChangeEvent): void => { - const {value} = e.target; - this.stateSet({filter: value}); - } - - render() { - const {list, filter, loading} = this.state; - - return
- - {(() => { - if (loading) { - return - } - - if (list.length === 0) { - return
- {_t('g.empty-list')} -
- } - - const items = list.filter(x => x.title.toLowerCase().indexOf(filter.toLowerCase()) !== -1); - - return <> -
- -
- - {items.length === 0 && {_t("g.no-matches")}} - - {items.length > 0 && ( -
-
- {items.map(item => ( - - ))} -
-
- )} - ; - })()} -
- } + }) + .catch(() => { + error(_t('g.server-error')); + }); + }; + + edit = (item: Draft) => { + const {history, onHide} = this.props; + + history.push(`/draft/${item._id}`); + onHide(); + }; + + filterChanged = ( + e: React.ChangeEvent, + ): void => { + const {value} = e.target; + this.stateSet({filter: value}); + }; + + render() { + const {list, filter, loading} = this.state; + + return ( +
+ {(() => { + if (loading) { + return ; + } + + if (list.length === 0) { + return
{_t('g.empty-list')}
; + } + + const items = list.filter( + x => x.title.toLowerCase().indexOf(filter.toLowerCase()) !== -1, + ); + + return ( + <> +
+ +
+ + {items.length === 0 && ( + {_t('g.no-matches')} + )} + + {items.length > 0 && ( +
+
+ {items.map(item => ( + + ))} +
+
+ )} + + ); + })()} +
+ ); + } } - -export default class DraftsDialog - extends Component { - hide = () => { - const {onHide} = this.props; - onHide(); - } - - render() { - return ( - - - {_t('drafts.title')} - - - - - - ); - } +export default class DraftsDialog extends Component { + hide = () => { + const {onHide} = this.props; + onHide(); + }; + + render() { + return ( + + + {_t('drafts.title')} + + + + + + ); + } } diff --git a/src/common/components/dropdown/index.spec.tsx b/src/common/components/dropdown/index.spec.tsx index a3ed6d4c76f..5ab8bdd5039 100644 --- a/src/common/components/dropdown/index.spec.tsx +++ b/src/common/components/dropdown/index.spec.tsx @@ -1,50 +1,53 @@ -import React from "react"; +import React from 'react'; -import MyDropDown from "./index"; -import renderer from "react-test-renderer"; -import { createBrowserHistory } from "history"; +import MyDropDown from './index'; +import renderer from 'react-test-renderer'; +import {createBrowserHistory} from 'history'; const props = { history: createBrowserHistory(), - label: "Trending", + label: 'Trending', items: [ { - label: "Trending", - href: "/trending", + label: 'Trending', + href: '/trending', active: true, }, { - label: "Hot", - href: "/hot", + label: 'Hot', + href: '/hot', }, ], - showMenu: () => { - }, - hideMenu: () => { - }, + showMenu: () => {}, + hideMenu: () => {}, }; -const component = renderer.create(); +const component = renderer.create(); -it("(1) Default render", () => { +it('(1) Default render', () => { expect(component.toJSON()).toMatchSnapshot(); }); -it("(2) Show menu", () => { +it('(2) Show menu', () => { const instance = component.root; instance.props?.showMenu(); expect(component.toJSON()).toMatchSnapshot(); }); -it("(3) Hide menu", () => { +it('(3) Hide menu', () => { const instance = component.root; instance.props?.hideMenu(); expect(component.toJSON()).toMatchSnapshot(); }); -it("(4) With custom label and header text", () => { +it('(4) With custom label and header text', () => { const component2 = renderer.create( - open menu} header="My menu" /> + open menu} + header='My menu' + />, ); const instance = component2.root; instance.props?.showMenu(); diff --git a/src/common/components/dropdown/index.tsx b/src/common/components/dropdown/index.tsx index 66bcccbd9a1..71c31b600e3 100644 --- a/src/common/components/dropdown/index.tsx +++ b/src/common/components/dropdown/index.tsx @@ -1,172 +1,190 @@ -import React, {useState, useEffect} from "react"; -import {History} from "history"; +import React, {useState, useEffect} from 'react'; +import {History} from 'history'; -import {menuDownSvg} from "../../img/svg"; -import _c from "../../util/fix-class-names"; +import {menuDownSvg} from '../../img/svg'; +import _c from '../../util/fix-class-names'; export interface MenuItem { - label: string | JSX.Element; - href?: string; - onClick?: () => void; - active?: boolean; - flash?: boolean; - id?: string; - icon?: JSX.Element + label: string | JSX.Element; + href?: string; + onClick?: () => void; + active?: boolean; + flash?: boolean; + id?: string; + icon?: JSX.Element; } interface Props { - history: History | null; - float: "left" | "right"; - alignBottom?: boolean, - header?: string; - preElem?: JSX.Element; - postElem?: JSX.Element; - icon?: JSX.Element; - label: string | JSX.Element; - items: MenuItem[]; - onShow?: () => void; - onHide?: () => void; + history: History | null; + float: 'left' | 'right'; + alignBottom?: boolean; + header?: string; + preElem?: JSX.Element; + postElem?: JSX.Element; + icon?: JSX.Element; + label: string | JSX.Element; + items: MenuItem[]; + onShow?: () => void; + onHide?: () => void; } const MyDropDown = (props: Props) => { + const [menu, setMenu] = useState(false); + const [mounted, setMounted] = useState(false); - const [menu, setMenu] = useState(false); - const [mounted, setMounted] = useState(false); - - useEffect(() => { - setMounted(true); - return () => { - setMounted(false); - } - }, []); - - useEffect(() => { - if (menu) { - if (props?.onShow) { - props.onShow(); - } - } else { - if (props?.onHide) { - props.onHide(); - } - } - }, [menu]); - - let _timer: any = null; - - const mouseClick = () => { - mouseIn(); + useEffect(() => { + setMounted(true); + return () => { + setMounted(false); }; - - const mouseEnter = () => { - mouseIn(); - }; - - const mouseIn = () => { - if (_timer) { - clearTimeout(_timer); - } - if (menu) { - return; - } - showMenu(); + }, []); + + useEffect(() => { + if (menu) { + if (props?.onShow) { + props.onShow(); + } + } else { + if (props?.onHide) { + props.onHide(); + } } + }, [menu]); - const mouseOut = () => { - _timer = setTimeout(() => { - hideMenu(); - }, 300); - }; - - const showMenu = () => { - setMenu(true); - }; - - const hideMenu = () => { - setMenu(false); - }; + let _timer: any = null; - const itemClicked = (i: MenuItem) => { - hideMenu(); + const mouseClick = () => { + mouseIn(); + }; - setTimeout(() => { - if (i?.href) { - props.history && props.history.push(i?.href); - } - - if (i?.onClick) { - i?.onClick(); - } - }, 100); - }; + const mouseEnter = () => { + mouseIn(); + }; - const {label, float, items} = props; - - const child: JSX.Element = - typeof label === "string" ? ( -
- {label &&
{label}
} -
{props?.icon || menuDownSvg}
+ const mouseIn = () => { + if (_timer) { + clearTimeout(_timer); + } + if (menu) { + return; + } + showMenu(); + }; + + const mouseOut = () => { + _timer = setTimeout(() => { + hideMenu(); + }, 300); + }; + + const showMenu = () => { + setMenu(true); + }; + + const hideMenu = () => { + setMenu(false); + }; + + const itemClicked = (i: MenuItem) => { + hideMenu(); + + setTimeout(() => { + if (i?.href) { + props.history && props.history.push(i?.href); + } + + if (i?.onClick) { + i?.onClick(); + } + }, 100); + }; + + const {label, float, items} = props; + + const child: JSX.Element = + typeof label === 'string' ? ( +
+ {label &&
{label}
} +
{props?.icon || menuDownSvg}
+
+ ) : ( + label + ); + + const menuCls = _c( + `custom-dropdown float-${float} ${ + props?.alignBottom ? 'align-bottom' : '' + }`, + ); + + return mounted ? ( +
+ {child} + + {menu && ( +
+
+ {props?.header && ( +
{props?.header}
+ )} + {props?.preElem && ( +
{props?.preElem as JSX.Element}
+ )} +
+ {items.map((i, k) => { + return ( +
{ + itemClicked(i); + }} + > + + {i?.icon ? ( + + {i?.icon as JSX.Element}{' '} + + ) : ( + '' + )} + {i.label as string | JSX.Element} + +
+ ); + })}
- ) : ( - label - ); - - const menuCls = _c(`custom-dropdown float-${float} ${props?.alignBottom ? "align-bottom" : ""}`); - - return mounted ? ( -
- {child} - - {menu && ( -
-
- {props?.header &&
{props?.header}
} - {props?.preElem &&
{props?.preElem as JSX.Element}
} -
- {items.map((i, k) => { - return ( -
{ - itemClicked(i); - }} - > - - {i?.icon ? {i?.icon as JSX.Element}{" "} : ""}{i.label as string|JSX.Element} - -
- ); - })} -
- {props?.postElem &&
{props?.postElem as JSX.Element}
} -
-
+ {props?.postElem && ( +
{props?.postElem as JSX.Element}
)} +
- ) : null; -} + )} +
+ ) : null; +}; export default (p: Props) => { - const props: Props = { - history: p.history, - float: p.float, - alignBottom: p?.alignBottom, - header: p?.header, - preElem: p?.preElem, - postElem: p?.postElem, - icon: p?.icon, - label: p.label, - items: p.items, - onShow: p?.onShow, - onHide: p?.onHide - }; - - return -} + const props: Props = { + history: p.history, + float: p.float, + alignBottom: p?.alignBottom, + header: p?.header, + preElem: p?.preElem, + postElem: p?.postElem, + icon: p?.icon, + label: p.label, + items: p.items, + onShow: p?.onShow, + onHide: p?.onHide, + }; + + return ; +}; diff --git a/src/common/components/edit-history/index.spec.tsx b/src/common/components/edit-history/index.spec.tsx index ae7197c2ac3..83e1cd7d835 100644 --- a/src/common/components/edit-history/index.spec.tsx +++ b/src/common/components/edit-history/index.spec.tsx @@ -1,45 +1,42 @@ -import React from "react"; - -import {EditHistory} from "./index"; - -import TestRenderer from "react-test-renderer"; - -import {entryInstance1, allOver} from "../../helper/test-helper"; - -jest.mock("../../api/private-api", () => ({ - commentHistory: () => - new Promise((resolve) => { - resolve( - { - "meta": {"count": 2}, - "list": [{ - "title": "a test post", - "body": "111XXX Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam rutrum eros eu sapien cursus ullamcorper ac eu justo. Fusce euismod semper tellus, et ornare libero pretium sed. Donec id bibendum orci. Quisque in interdum lectus. \n\nFusce tortor lectus, maximus vitae mollis sit amet, vehicula in lectus. Sed viverra tincidunt pulvinar. Morbi ut odio turpis. Pellentesque feugiat id urna pretium elementum. Fusce mauris nisi, tincidunt non arcu ac, laoreet sollicitudin nisl. Quisque sed mattis felis, ut facilisis quam. Sed dictum eleifend bibendum. Pellentesque mattis turpis mauris, vel interdum mi egestas vel. Phasellus ultricies tristique interdum.\n\n", - "tags": ["ecency"], - "timestamp": "2020-08-22T14:20:57+00:00", - "v": 1 - }, { - "title": "a test post", - "body": "@@ -643,16 +643,24 @@\n istique \n+UPDATED \n interdum\n@@ -660,10 +660,8 @@\n nterdum.\n-%0A%0A\n", - "tags": ["ecency"], - "timestamp": "2020-08-26T11:50:27+00:00", - "v": 2 - }] - } - ); - }), +import React from 'react'; + +import {EditHistory} from './index'; + +import TestRenderer from 'react-test-renderer'; + +import {entryInstance1, allOver} from '../../helper/test-helper'; + +jest.mock('../../api/private-api', () => ({ + commentHistory: () => + new Promise(resolve => { + resolve({ + meta: {count: 2}, + list: [ + { + title: 'a test post', + body: '111XXX Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam rutrum eros eu sapien cursus ullamcorper ac eu justo. Fusce euismod semper tellus, et ornare libero pretium sed. Donec id bibendum orci. Quisque in interdum lectus. \n\nFusce tortor lectus, maximus vitae mollis sit amet, vehicula in lectus. Sed viverra tincidunt pulvinar. Morbi ut odio turpis. Pellentesque feugiat id urna pretium elementum. Fusce mauris nisi, tincidunt non arcu ac, laoreet sollicitudin nisl. Quisque sed mattis felis, ut facilisis quam. Sed dictum eleifend bibendum. Pellentesque mattis turpis mauris, vel interdum mi egestas vel. Phasellus ultricies tristique interdum.\n\n', + tags: ['ecency'], + timestamp: '2020-08-22T14:20:57+00:00', + v: 1, + }, + { + title: 'a test post', + body: '@@ -643,16 +643,24 @@\n istique \n+UPDATED \n interdum\n@@ -660,10 +660,8 @@\n nterdum.\n-%0A%0A\n', + tags: ['ecency'], + timestamp: '2020-08-26T11:50:27+00:00', + v: 2, + }, + ], + }); + }), })); - -it("(1) Render with data", async () => { - const props = { - entry: entryInstance1, - onHide: () => { - } - }; - const renderer = TestRenderer.create(); - await allOver(); - expect(renderer.toJSON()).toMatchSnapshot(); +it('(1) Render with data', async () => { + const props = { + entry: entryInstance1, + onHide: () => {}, + }; + const renderer = TestRenderer.create(); + await allOver(); + expect(renderer.toJSON()).toMatchSnapshot(); }); - - diff --git a/src/common/components/edit-history/index.tsx b/src/common/components/edit-history/index.tsx index 1619cea3dd2..10cdf94bab3 100644 --- a/src/common/components/edit-history/index.tsx +++ b/src/common/components/edit-history/index.tsx @@ -1,207 +1,262 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import moment from "moment"; +import moment from 'moment'; -import {diff_match_patch} from "diff-match-patch"; +import {diff_match_patch} from 'diff-match-patch'; -import {Modal, Form, FormControl} from "react-bootstrap"; +import {Modal, Form, FormControl} from 'react-bootstrap'; -import defaults from "../../constants/defaults.json"; +import defaults from '../../constants/defaults.json'; -import {renderPostBody, setProxyBase} from "@ecency/render-helper"; +import {renderPostBody, setProxyBase} from '@ecency/render-helper'; setProxyBase(defaults.imageServer); -import {Entry} from "../../store/entries/types"; +import {Entry} from '../../store/entries/types'; -import BaseComponent from "../base"; -import LinearProgress from "../linear-progress"; +import BaseComponent from '../base'; +import LinearProgress from '../linear-progress'; -import {error} from "../feedback"; +import {error} from '../feedback'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import _c from "../../util/fix-class-names"; +import _c from '../../util/fix-class-names'; -import {commentHistory, CommentHistoryListItem} from "../../api/private-api"; +import {commentHistory, CommentHistoryListItem} from '../../api/private-api'; -import {historySvg, tagSvg} from "../../img/svg"; +import {historySvg, tagSvg} from '../../img/svg'; const dmp = new diff_match_patch(); const make_diff = (str1: string, str2: string): string => { - const d = dmp.diff_main(str1, str2); - dmp.diff_cleanupSemantic(d); - return dmp.diff_prettyHtml(d).replace(/¶/g, ' '); + const d = dmp.diff_main(str1, str2); + dmp.diff_cleanupSemantic(d); + return dmp.diff_prettyHtml(d).replace(/¶/g, ' '); }; export interface CommentHistoryListItemDiff { - title: string; - titleDiff?: string; - body: string; - bodyDiff?: string; - tags: string; - tagsDiff?: string; - timestamp: string; - v: number; + title: string; + titleDiff?: string; + body: string; + bodyDiff?: string; + tags: string; + tagsDiff?: string; + timestamp: string; + v: number; } interface Props { - entry: Entry; - onHide: () => void; + entry: Entry; + onHide: () => void; } interface State { - history: CommentHistoryListItemDiff[], - selected: number, - showDiff: boolean, - loading: boolean + history: CommentHistoryListItemDiff[]; + selected: number; + showDiff: boolean; + loading: boolean; } export class EditHistory extends BaseComponent { - state: State = { - history: [], - selected: 1, - showDiff: true, - loading: true + state: State = { + history: [], + selected: 1, + showDiff: true, + loading: true, + }; + + componentDidMount() { + this.loadData(); + } + + buildList = (raw: CommentHistoryListItem[]): CommentHistoryListItemDiff[] => { + const t: CommentHistoryListItemDiff[] = []; + + let h = ''; + for (let l = 0; l < raw.length; l += 1) { + if (raw[l].body.startsWith('@@')) { + const p = dmp.patch_fromText(raw[l].body); + h = dmp.patch_apply(p, h)[0]; + raw[l].body = h; + } else { + h = raw[l].body; + } + + t.push({ + v: raw[l].v, + title: raw[l].title, + body: h, + timestamp: raw[l].timestamp, + tags: raw[l].tags.join(', '), + }); } - componentDidMount() { - this.loadData(); - } - - buildList = (raw: CommentHistoryListItem[]): CommentHistoryListItemDiff[] => { - const t: CommentHistoryListItemDiff[] = []; - - let h = ''; - for (let l = 0; l < raw.length; l += 1) { - if (raw[l].body.startsWith('@@')) { - const p = dmp.patch_fromText(raw[l].body); - h = dmp.patch_apply(p, h)[0]; - raw[l].body = h; - } else { - h = raw[l].body; - } - - t.push({ - v: raw[l].v, - title: raw[l].title, - body: h, - timestamp: raw[l].timestamp, - tags: raw[l].tags.join(', ') - }); - } - - for (let l = 0; l < t.length; l += 1) { - const p = l > 0 ? l - 1 : l; - - t[l].titleDiff = make_diff(t[p].title, t[l].title); - t[l].bodyDiff = make_diff(t[p].body, t[l].body); - t[l].tagsDiff = make_diff(t[p].tags, t[l].tags); - } - - return t; - }; + for (let l = 0; l < t.length; l += 1) { + const p = l > 0 ? l - 1 : l; - loadData = () => { - const {entry} = this.props; - - commentHistory(entry.author, entry.permlink) - .then(resp => { - this.stateSet({history: this.buildList(resp.list)}); - }) - .catch(() => { - error(_t('g.server-error')); - }) - .finally(() => { - this.stateSet({loading: false}); - }) - }; + t[l].titleDiff = make_diff(t[p].title, t[l].title); + t[l].bodyDiff = make_diff(t[p].body, t[l].body); + t[l].tagsDiff = make_diff(t[p].tags, t[l].tags); + } - versionClicked = (i: CommentHistoryListItemDiff) => { - this.setState({selected: i.v}); - }; + return t; + }; + + loadData = () => { + const {entry} = this.props; + + commentHistory(entry.author, entry.permlink) + .then(resp => { + this.stateSet({history: this.buildList(resp.list)}); + }) + .catch(() => { + error(_t('g.server-error')); + }) + .finally(() => { + this.stateSet({loading: false}); + }); + }; + + versionClicked = (i: CommentHistoryListItemDiff) => { + this.setState({selected: i.v}); + }; + + versionChanged = ( + e: React.ChangeEvent, + ) => { + this.setState({selected: Number(e.target.value)}); + }; + + diffChanged = ( + e: React.ChangeEvent, + ) => { + this.setState({showDiff: e.target.checked}); + }; + + render() { + const {history, selected, loading, showDiff} = this.state; + + if (loading) { + return ( +
+ +
+ ); + } - versionChanged = (e: React.ChangeEvent) => { - this.setState({selected: Number(e.target.value)}); + const selectedItem = history.find(x => x.v === selected); + if (!selectedItem) { + return null; } - diffChanged = (e: React.ChangeEvent) => { - this.setState({showDiff: e.target.checked}); + const title = { + __html: showDiff ? selectedItem.titleDiff! : selectedItem.title, + }; + const body = { + __html: showDiff + ? selectedItem.bodyDiff! + : renderPostBody(selectedItem.body), + }; + const tags = { + __html: showDiff ? selectedItem.tagsDiff! : selectedItem.tags, }; - render() { - const {history, selected, loading, showDiff} = this.state; - - if (loading) { - return
- } - - const selectedItem = history.find(x => x.v === selected); - if (!selectedItem) { - return null; - } - - const title = {__html: showDiff ? selectedItem.titleDiff! : selectedItem.title}; - const body = {__html: showDiff ? selectedItem.bodyDiff! : renderPostBody(selectedItem.body)}; - const tags = {__html: showDiff ? selectedItem.tagsDiff! : selectedItem.tags}; - - return
-
-
- -
- - {history.map(i => { - return - })} - -
-
-
- + return ( +
+
+
+ +
+ + {history.map(i => { + return ( + + ); + })} + +
+
+
+ +
+ {history.map(i => { + const date = moment(i.timestamp); + return ( +
{ + this.versionClicked(i); + }} + > +
{historySvg}
+
+ {_t('edit-history.version', {n: i.v})}
- {history.map(i => { - const date = moment(i.timestamp); - return
{ - this.versionClicked(i); - }}> -
{historySvg}
-
{_t("edit-history.version", {n: i.v})}
-
{date.format("LLL")}
-
- })} -
-
-

-
{tagSvg}{' '}
-
-
+
{date.format('LLL')}
+

+ ); + })}
- } +
+

+
+ {tagSvg} +
+
+
+

+ ); + } } export default class EditHistoryDialog extends Component { - render() { - const {onHide} = this.props; - return ( - - - {_t("edit-history.title")} - - - - - - ); - } + render() { + const {onHide} = this.props; + return ( + + + {_t('edit-history.title')} + + + + + + ); + } } diff --git a/src/common/components/editor-toolbar/index.tsx b/src/common/components/editor-toolbar/index.tsx index 22c8880e847..6808860e7f7 100644 --- a/src/common/components/editor-toolbar/index.tsx +++ b/src/common/components/editor-toolbar/index.tsx @@ -1,557 +1,601 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import isEqual from "react-fast-compare"; +import isEqual from 'react-fast-compare'; -import {ActiveUser} from "../../store/active-user/types"; -import {User} from "../../store/users/types"; -import {Global} from "../../store/global/types"; +import {ActiveUser} from '../../store/active-user/types'; +import {User} from '../../store/users/types'; +import {Global} from '../../store/global/types'; -import Tooltip from "../tooltip"; -import EmojiPicker from "../emoji-picker"; -import Gallery from "../gallery"; -import Fragments from "../fragments"; -import AddImage from "../add-image"; -import AddImageMobile from "../add-image-mobile"; -import AddLink from "../add-link"; +import Tooltip from '../tooltip'; +import EmojiPicker from '../emoji-picker'; +import Gallery from '../gallery'; +import Fragments from '../fragments'; +import AddImage from '../add-image'; +import AddImageMobile from '../add-image-mobile'; +import AddLink from '../add-link'; -import {uploadImage} from "../../api/misc"; +import {uploadImage} from '../../api/misc'; -import {addImage} from "../../api/private-api"; +import {addImage} from '../../api/private-api'; -import {error} from "../feedback"; +import {error} from '../feedback'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import {insertOrReplace, replace} from "../../util/input-util"; +import {insertOrReplace, replace} from '../../util/input-util'; -import {getAccessToken} from "../../helper/user-token"; +import {getAccessToken} from '../../helper/user-token'; -import _c from "../../util/fix-class-names"; +import _c from '../../util/fix-class-names'; import { - formatBoldSvg, - formatItalicSvg, - formatTitleSvg, - codeTagsSvg, - formatQuoteCloseSvg, - formatListNumberedSvg, - formatListBulletedSvg, - linkSvg, - imageSvg, - gridSvg, - emoticonHappyOutlineSvg, - textShortSvg -} from "../../img/svg"; - + formatBoldSvg, + formatItalicSvg, + formatTitleSvg, + codeTagsSvg, + formatQuoteCloseSvg, + formatListNumberedSvg, + formatListBulletedSvg, + linkSvg, + imageSvg, + gridSvg, + emoticonHappyOutlineSvg, + textShortSvg, +} from '../../img/svg'; interface Props { - global: Global; - users: User[]; - activeUser: ActiveUser | null; - sm?: boolean; - showEmoji?: boolean; + global: Global; + users: User[]; + activeUser: ActiveUser | null; + sm?: boolean; + showEmoji?: boolean; } interface State { - gallery: boolean; - fragments: boolean; - image: boolean; - link: boolean; - mobileImage: boolean; + gallery: boolean; + fragments: boolean; + image: boolean; + link: boolean; + mobileImage: boolean; } export class EditorToolbar extends Component { - state: State = { - gallery: false, - fragments: false, - image: false, - link: false, - mobileImage: false + state: State = { + gallery: false, + fragments: false, + image: false, + link: false, + mobileImage: false, + }; + + holder = React.createRef(); + fileInput = React.createRef(); + + shouldComponentUpdate( + nextProps: Readonly, + nextState: Readonly, + ): boolean { + return ( + !isEqual(this.props.users, nextProps.users) || + !isEqual(this.props.activeUser, nextProps.activeUser) || + !isEqual(this.props.showEmoji, nextProps.showEmoji) || + !isEqual(this.state, nextState) + ); + } + + toggleGallery = () => { + const {gallery} = this.state; + this.setState({gallery: !gallery}); + }; + + toggleFragments = () => { + const {fragments} = this.state; + this.setState({fragments: !fragments}); + }; + + toggleImage = (e?: React.MouseEvent) => { + if (e) { + e.stopPropagation(); } + const {image} = this.state; + this.setState({image: !image}); + }; - holder = React.createRef(); - fileInput = React.createRef(); - - shouldComponentUpdate(nextProps: Readonly, nextState: Readonly): boolean { - return !isEqual(this.props.users, nextProps.users) - || !isEqual(this.props.activeUser, nextProps.activeUser) - || !isEqual(this.props.showEmoji, nextProps.showEmoji) - || !isEqual(this.state, nextState); + toggleMobileImage = (e?: React.MouseEvent) => { + if (e) { + e.stopPropagation(); } + const {mobileImage} = this.state; + this.setState({mobileImage: !mobileImage}); + }; - toggleGallery = () => { - const {gallery} = this.state; - this.setState({gallery: !gallery}); + toggleLink = (e?: React.MouseEvent) => { + if (e) { + e.stopPropagation(); } + const {link} = this.state; - toggleFragments = () => { - const {fragments} = this.state; - this.setState({fragments: !fragments}); - } + this.setState({link: !link}); + }; - toggleImage = (e?: React.MouseEvent) => { - if (e) { - e.stopPropagation(); - } - const {image} = this.state; - this.setState({image: !image}); + componentDidMount() { + const el = this.getTargetEl(); + if (el) { + el.addEventListener('dragover', this.onDragOver); + el.addEventListener('drop', this.drop); + el.addEventListener('paste', this.onPaste); } + } + + componentWillUnmount() { + const el = this.getTargetEl(); + if (el) { + el.removeEventListener('dragover', this.onDragOver); + el.removeEventListener('drop', this.drop); + el.removeEventListener('paste', this.onPaste); + } + } - toggleMobileImage = (e?: React.MouseEvent) => { - if (e) { - e.stopPropagation(); - } - const {mobileImage} = this.state; - this.setState({mobileImage: !mobileImage}); + getTargetEl = (): HTMLInputElement | null => { + const holder = this.holder.current; + if (!holder || !holder.parentElement) { + return null; } - toggleLink = (e?: React.MouseEvent) => { - if (e) { - e.stopPropagation(); - } - const {link} = this.state; + return holder.parentElement.querySelector('.the-editor'); + }; - this.setState({link: !link}); + insertText = (before: string, after: string = '') => { + const el = this.getTargetEl(); + if (el) { + insertOrReplace(el, before, after); } + }; - componentDidMount() { - const el = this.getTargetEl(); - if (el) { - el.addEventListener('dragover', this.onDragOver); - el.addEventListener('drop', this.drop); - el.addEventListener('paste', this.onPaste); - } + replaceText = (find: string, rep: string) => { + const el = this.getTargetEl(); + if (el) { + replace(el, find, rep); } + }; - componentWillUnmount() { - const el = this.getTargetEl(); - if (el) { - el.removeEventListener('dragover', this.onDragOver); - el.removeEventListener('drop', this.drop); - el.removeEventListener('paste', this.onPaste); - } + onDragOver = (e: DragEvent) => { + const {activeUser} = this.props; + if (!activeUser) { + return; } - getTargetEl = (): HTMLInputElement | null => { - const holder = this.holder.current; - if (!holder || !holder.parentElement) { - return null; - } + e.preventDefault(); + e.stopPropagation(); - return holder.parentElement.querySelector('.the-editor'); + if (e.dataTransfer) { + e.dataTransfer.effectAllowed = 'copy'; + e.dataTransfer.dropEffect = 'copy'; } + }; - insertText = (before: string, after: string = "") => { - const el = this.getTargetEl(); - if (el) { - insertOrReplace(el, before, after); - } - }; - - replaceText = (find: string, rep: string) => { - const el = this.getTargetEl(); - if (el) { - replace(el, find, rep); - } + drop = (e: DragEvent) => { + const {activeUser} = this.props; + if (!activeUser) { + return; } - onDragOver = (e: DragEvent) => { - const {activeUser} = this.props; - if (!activeUser) { - return; - } - - e.preventDefault(); - e.stopPropagation(); + e.preventDefault(); + e.stopPropagation(); - if (e.dataTransfer) { - e.dataTransfer.effectAllowed = 'copy'; - e.dataTransfer.dropEffect = 'copy'; - } + if (!e.dataTransfer) { + return; } - drop = (e: DragEvent) => { - const {activeUser} = this.props; - if (!activeUser) { - return; - } - - e.preventDefault(); - e.stopPropagation(); + const files = [...e.dataTransfer.files] + .filter(i => this.checkFile(i.name)) + .filter(i => i); - if (!e.dataTransfer) { - return; - } - - const files = [...e.dataTransfer.files] - .filter(i => this.checkFile(i.name)) - .filter(i => i); - - if (files.length > 0) { - files.forEach(file => this.upload(file)); - } + if (files.length > 0) { + files.forEach(file => this.upload(file)); } + }; - onPaste = (e: ClipboardEvent) => { - if (!e.clipboardData) { - return; - } + onPaste = (e: ClipboardEvent) => { + if (!e.clipboardData) { + return; + } - // when text copied from ms word, it adds screenshot of selected text to clipboard. - // check if data in clipboard is long string and skip upload. - // (i think no one uses more than 50 chars for a image file) - const txtData = e.clipboardData.getData('text/plain'); - if (txtData.length >= 50) { - return; - } + // when text copied from ms word, it adds screenshot of selected text to clipboard. + // check if data in clipboard is long string and skip upload. + // (i think no one uses more than 50 chars for a image file) + const txtData = e.clipboardData.getData('text/plain'); + if (txtData.length >= 50) { + return; + } - const files = [...e.clipboardData.items] - .map(item => - item.type.indexOf('image') !== -1 ? item.getAsFile() : null - ) - .filter(i => i); + const files = [...e.clipboardData.items] + .map(item => + item.type.indexOf('image') !== -1 ? item.getAsFile() : null, + ) + .filter(i => i); - if (files.length > 0) { - e.stopPropagation(); - e.preventDefault(); + if (files.length > 0) { + e.stopPropagation(); + e.preventDefault(); - files.forEach(file => { - if (file) this.upload(file).then(); - }); - } + files.forEach(file => { + if (file) this.upload(file).then(); + }); + } + }; + + bold = () => { + this.insertText('**', '**'); + }; + + italic = () => { + this.insertText('*', '*'); + }; + + header = (w: number) => { + const h = '#'.repeat(w); + this.insertText(`${h} `); + }; + + code = () => { + this.insertText('', ''); + }; + + quote = () => { + this.insertText('>'); + }; + + ol = () => { + this.insertText('1. item1\n2. item2\n3. item3'); + }; + + ul = () => { + this.insertText('* item1\n* item2\n* item3'); + }; + + link = (text: string, url: string) => { + this.insertText(`[${text}`, `](${url})`); + }; + + image = (text: string, url: string) => { + this.insertText(`![${text}`, `](${url})`); + }; + + table = (e: React.MouseEvent) => { + e.stopPropagation(); + const t = + '\n|\tColumn 1\t|\tColumn 2\t|\tColumn 3\t|\n' + + '|\t------------\t|\t------------\t|\t------------\t|\n' + + '|\t Text \t|\t Text \t|\t Text \t|\n'; + this.insertText(t); + }; + + table1 = (e: React.MouseEvent) => { + e.stopPropagation(); + + const t = + '\n|\tColumn 1\t|\n' + '|\t------------\t|\n' + '|\t Text \t|\n'; + this.insertText(t); + }; + + table2 = (e: React.MouseEvent) => { + e.stopPropagation(); + const t = + '\n|\tColumn 1\t|\tColumn 2\t|\n' + + '|\t------------\t|\t------------\t|\n' + + '|\t Text \t|\t Text \t|\n'; + this.insertText(t); + }; + + fileInputChanged = (e: React.ChangeEvent): void => { + let files = [...(e.target.files as FileList)] + .filter(i => this.checkFile(i.name)) + .filter(i => i); + + const { + global: {isElectron}, + } = this.props; + + if (files.length > 0) { + e.stopPropagation(); + e.preventDefault(); } - bold = () => { - this.insertText("**", "**"); - }; - - italic = () => { - this.insertText("*", "*"); - }; - - header = (w: number) => { - const h = "#".repeat(w); - this.insertText(`${h} `); - }; - - code = () => { - this.insertText("", ""); - }; - - quote = () => { - this.insertText(">"); - }; - - ol = () => { - this.insertText("1. item1\n2. item2\n3. item3"); - }; - - ul = () => { - this.insertText("* item1\n* item2\n* item3"); - }; - - link = (text: string, url: string) => { - this.insertText(`[${text}`, `](${url})`); - }; - - image = (text: string, url: string) => { - this.insertText(`![${text}`, `](${url})`); - }; - - table = (e: React.MouseEvent) => { - e.stopPropagation(); - const t = - "\n|\tColumn 1\t|\tColumn 2\t|\tColumn 3\t|\n" + - "|\t------------\t|\t------------\t|\t------------\t|\n" + - "|\t Text \t|\t Text \t|\t Text \t|\n"; - this.insertText(t); - }; - - table1 = (e: React.MouseEvent) => { - e.stopPropagation(); - - const t = "\n|\tColumn 1\t|\n" + "|\t------------\t|\n" + "|\t Text \t|\n"; - this.insertText(t); - }; - - table2 = (e: React.MouseEvent) => { - e.stopPropagation(); - const t = - "\n|\tColumn 1\t|\tColumn 2\t|\n" + - "|\t------------\t|\t------------\t|\n" + - "|\t Text \t|\t Text \t|\n"; - this.insertText(t); - }; - - fileInputChanged = (e: React.ChangeEvent): void => { - let files = [...e.target.files as FileList] - .filter(i => this.checkFile(i.name)) - .filter(i => i); - - const {global: { isElectron } } = this.props; - - if (files.length > 0) { - e.stopPropagation(); - e.preventDefault(); - } + if (files.length > 1 && isElectron) { + let isWindows = process.platform === 'win32'; + if (isWindows) { + files = files.reverse(); + } + } - if(files.length > 1 && isElectron){ - let isWindows = process.platform === "win32"; - if (isWindows) { - files = files.reverse() - } - } + files.forEach(file => this.upload(file)); - files.forEach(file => this.upload(file)); + // reset input + e.target.value = ''; + }; - // reset input - e.target.value = ""; - }; + upload = async (file: File) => { + const {activeUser, global} = this.props; - upload = async (file: File) => { - const {activeUser, global} = this.props; + const username = activeUser?.username!; - const username = activeUser?.username!; + const tempImgTag = `![Uploading ${file.name} #${Math.floor( + Math.random() * 99, + )}]()\n\n`; + this.insertText(tempImgTag); - const tempImgTag = `![Uploading ${file.name} #${Math.floor( - Math.random() * 99 - )}]()\n\n`; - this.insertText(tempImgTag); + let imageUrl: string; + try { + let token = getAccessToken(username); + if (token) { + const resp = await uploadImage(file, token); + imageUrl = resp.url; - let imageUrl: string; - try { - let token = getAccessToken(username); - if(token) { - const resp = await uploadImage(file, token); - imageUrl = resp.url; + if (global.usePrivate && imageUrl.length > 0) { + addImage(username, imageUrl).then(); + } - if (global.usePrivate && imageUrl.length > 0) { - addImage(username, imageUrl).then(); + const imageName = imageUrl.length > 0 && imageUrl.split('/').pop(); + const imgTag = + imageUrl.length > 0 && `![${imageName}](${imageUrl})\n\n`; + + imgTag && this.replaceText(tempImgTag, imgTag); + } else { + error(_t('editor-toolbar.image-error-cache')); + } + } catch (e) { + if (e.response?.status === 413) { + error(_t('editor-toolbar.image-error-size')); + } else { + error(_t('editor-toolbar.image-error')); + } + return; + } + }; + + checkFile = (filename: string) => { + const filenameLow = filename.toLowerCase(); + return ['jpg', 'jpeg', 'gif', 'png'].some(el => filenameLow.endsWith(el)); + }; + + render() { + const {gallery, fragments, image, link, mobileImage} = this.state; + const {global, sm, activeUser, showEmoji = true} = this.props; + + return ( + <> +
+ +
+ {formatBoldSvg} +
+
+ +
+ {formatItalicSvg} +
+
+ +
{ + this.header(1); + }} + > + {formatTitleSvg} +
+ {[...Array(3).keys()].map(i => ( +
) => { + e.stopPropagation(); + this.header(i + 2); + }} + > + {`H${i + 2}`} +
+ ))} +
+
+
+
+ +
+ {codeTagsSvg} +
+
+ +
+ {formatQuoteCloseSvg} +
+
+
+ +
+ {formatListNumberedSvg} +
+
+ +
+ {formatListBulletedSvg} +
+
+
+ +
+ {linkSvg} +
+
+ + {(() => { + if (activeUser && global.isMobile) { + return ( + +
+ {imageSvg} +
+
+ ); } - const imageName = imageUrl.length > 0 && imageUrl.split('/').pop(); - const imgTag = imageUrl.length > 0 && `![${imageName}](${imageUrl})\n\n`; - - imgTag && this.replaceText(tempImgTag, imgTag); - } - else { - error(_t("editor-toolbar.image-error-cache")) - } - } catch (e) { - if (e.response?.status === 413) { - error(_t("editor-toolbar.image-error-size")); - } else { - error(_t("editor-toolbar.image-error")); - } - return; - } - }; - - checkFile = (filename: string) => { - const filenameLow = filename.toLowerCase(); - return ['jpg', 'jpeg', 'gif', 'png'].some(el => filenameLow.endsWith(el)); - }; - - render() { - const {gallery, fragments, image, link, mobileImage} = this.state; - const {global, sm, activeUser, showEmoji = true} = this.props; - - return ( - <> -
- -
- {formatBoldSvg} -
-
- -
- {formatItalicSvg} -
-
- + return ( + +
+ {imageSvg} + + {activeUser && ( +
+
) => { + e.stopPropagation(); + const el = this.fileInput.current; + if (el) el.click(); + }} + > + {_t('editor-toolbar.upload')} +
+ {global.usePrivate && (
{ - this.header(1); - }} + className='sub-tool-menu-item' + onClick={(e: React.MouseEvent) => { + e.stopPropagation(); + this.toggleGallery(); + }} > - {formatTitleSvg} -
- {[...Array(3).keys()].map((i) => ( -
) => { - e.stopPropagation(); - this.header(i + 2); - }} - > - {`H${i + 2}`} -
- ))} -
-
- -
- -
- {codeTagsSvg} + {_t('editor-toolbar.gallery')}
-
- -
- {formatQuoteCloseSvg} -
-
-
- -
- {formatListNumberedSvg} -
-
- -
- {formatListBulletedSvg} -
-
-
- -
- {linkSvg} -
-
- - {(() => { - if (activeUser && global.isMobile) { - return -
- {imageSvg} -
-
- } - - return -
- {imageSvg} - - {activeUser && ( -
-
) => { - e.stopPropagation(); - const el = this.fileInput.current; - if (el) el.click(); - }}> - {_t("editor-toolbar.upload")} -
- {global.usePrivate &&
) => { - e.stopPropagation(); - this.toggleGallery(); - }} - > - {_t("editor-toolbar.gallery")} -
} -
- )} -
-
- })()} - - -
- {gridSvg} -
-
- {_t("editor-toolbar.table-3-col")} -
-
- {_t("editor-toolbar.table-2-col")} -
-
- {_t("editor-toolbar.table-1-col")} -
-
-
-
- -
- {emoticonHappyOutlineSvg} - {showEmoji && { - this.insertText(e, ''); - }}/>} -
-
- {global.usePrivate && -
- {textShortSvg} -
-
} + )} +
+ )}
- + ); + })()} + + +
+ {gridSvg} +
+
+ {_t('editor-toolbar.table-3-col')} +
+
+ {_t('editor-toolbar.table-2-col')} +
+
+ {_t('editor-toolbar.table-1-col')} +
+
+
+
+ +
+ {emoticonHappyOutlineSvg} + {showEmoji && ( + { + this.insertText(e, ''); + }} /> - {(gallery && activeUser) && { - const fileName = url.split("/").pop() || ""; - this.image(fileName, url); - this.toggleGallery(); - }}/>} - {(fragments && activeUser) && { - this.insertText(body); - this.toggleFragments(); - }}/>} - {image && { - this.image(text, link); - this.toggleImage(); - }}/>} - {link && { - this.link(text, link); - this.toggleLink(); - }}/>} - {mobileImage && ( - { - const fileName = url.split("/").pop() || ""; - this.image(fileName, url); - this.toggleMobileImage(); - }} - onGallery={() => { - this.toggleMobileImage(); - this.toggleGallery(); - }} - onUpload={() => { - this.toggleMobileImage(); - const el = this.fileInput.current; - if (el) el.click(); - }} - /> - )} - - ); - } - + )} +
+
+ {global.usePrivate && ( + +
+ {textShortSvg} +
+
+ )} +
+ + {gallery && activeUser && ( + { + const fileName = url.split('/').pop() || ''; + this.image(fileName, url); + this.toggleGallery(); + }} + /> + )} + {fragments && activeUser && ( + { + this.insertText(body); + this.toggleFragments(); + }} + /> + )} + {image && ( + { + this.image(text, link); + this.toggleImage(); + }} + /> + )} + {link && ( + { + this.link(text, link); + this.toggleLink(); + }} + /> + )} + {mobileImage && ( + { + const fileName = url.split('/').pop() || ''; + this.image(fileName, url); + this.toggleMobileImage(); + }} + onGallery={() => { + this.toggleMobileImage(); + this.toggleGallery(); + }} + onUpload={() => { + this.toggleMobileImage(); + const el = this.fileInput.current; + if (el) el.click(); + }} + /> + )} + + ); + } } export default (props: Props) => { - const p: Props = { - global: props.global, - users: props.users, - activeUser: props.activeUser, - sm: props.sm, - showEmoji: props.showEmoji, - } - return -} + const p: Props = { + global: props.global, + users: props.users, + activeUser: props.activeUser, + sm: props.sm, + showEmoji: props.showEmoji, + }; + return ; +}; diff --git a/src/common/components/emoji-picker/index.spec.tsx b/src/common/components/emoji-picker/index.spec.tsx index e04071dfcff..8a6d66ca94a 100644 --- a/src/common/components/emoji-picker/index.spec.tsx +++ b/src/common/components/emoji-picker/index.spec.tsx @@ -1,20 +1,20 @@ -import React from "react"; +import React from 'react'; -import renderer from "react-test-renderer"; +import renderer from 'react-test-renderer'; -import EmojiPicker from "./index"; +import EmojiPicker from './index'; -import emojiData from "../../../../public/emoji.json"; +import emojiData from '../../../../public/emoji.json'; -jest.mock("../../api/misc", () => ({ +jest.mock('../../api/misc', () => ({ getEmojiData: () => - new Promise((resolve) => { + new Promise(resolve => { resolve(emojiData); }), })); -jest.mock("../../util/local-storage", () => ({ - get: () => ["dog2", "wink"], +jest.mock('../../util/local-storage', () => ({ + get: () => ['dog2', 'wink'], })); const detailProps = { @@ -23,18 +23,18 @@ const detailProps = { const component = renderer.create(); -it("(1) Default full render", () => { +it('(1) Default full render', () => { expect(component.toJSON()).toMatchSnapshot(); }); -it("(2) Filter", () => { +it('(2) Filter', () => { const instance: any = component.getInstance(); - instance.setState({ filter: "dog" }); + instance.setState({filter: 'dog'}); expect(component.toJSON()).toMatchSnapshot(); }); -it("(3) Filter - No result", () => { +it('(3) Filter - No result', () => { const instance: any = component.getInstance(); - instance.setState({ filter: "loremipsumdolorsit" }); + instance.setState({filter: 'loremipsumdolorsit'}); expect(component.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/emoji-picker/index.tsx b/src/common/components/emoji-picker/index.tsx index 37c53e92820..6949dde2c6e 100644 --- a/src/common/components/emoji-picker/index.tsx +++ b/src/common/components/emoji-picker/index.tsx @@ -1,192 +1,214 @@ -import React from "react"; +import React from 'react'; -import {FormControl} from "react-bootstrap"; +import {FormControl} from 'react-bootstrap'; -import BaseComponent from "../base"; -import SearchBox from "../search-box"; +import BaseComponent from '../base'; +import SearchBox from '../search-box'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import {getEmojiData} from "../../api/misc"; +import {getEmojiData} from '../../api/misc'; -import * as ls from "../../util/local-storage"; +import * as ls from '../../util/local-storage'; -import {insertOrReplace} from "../../util/input-util"; +import {insertOrReplace} from '../../util/input-util'; interface Emoji { - a: string; - b: string; - j: string[]; + a: string; + b: string; + j: string[]; } interface EmojiCategory { - id: string; - name: string; - emojis: string[]; + id: string; + name: string; + emojis: string[]; } interface EmojiData { - categories: EmojiCategory[]; - emojis: Record; + categories: EmojiCategory[]; + emojis: Record; } interface EmojiCacheItem { - id: string; - name: string; - keywords: string[]; + id: string; + name: string; + keywords: string[]; } interface Props { - fallback?: (e: string) => void + fallback?: (e: string) => void; } interface State { - data: EmojiData | null; - cache: EmojiCacheItem[] | null; - filter: string; + data: EmojiData | null; + cache: EmojiCacheItem[] | null; + filter: string; } export default class EmojiPicker extends BaseComponent { - state: State = { - data: null, - cache: null, - filter: "", - }; - - _target: HTMLInputElement | null = null; - - componentDidMount() { - getEmojiData().then((data) => this.setData(data)); - - this.watchTarget(); // initial - document.querySelectorAll(".accepts-emoji").forEach((i) => { - i.addEventListener("focus", this.watchTarget); - }); + state: State = { + data: null, + cache: null, + filter: '', + }; + + _target: HTMLInputElement | null = null; + + componentDidMount() { + getEmojiData().then(data => this.setData(data)); + + this.watchTarget(); // initial + document.querySelectorAll('.accepts-emoji').forEach(i => { + i.addEventListener('focus', this.watchTarget); + }); + } + + componentWillUnmount() { + super.componentWillUnmount(); + + document.querySelectorAll('.accepts-emoji').forEach(i => { + i.removeEventListener('focus', this.watchTarget); + }); + } + + watchTarget = () => { + if ( + document.activeElement && + document.activeElement.classList.contains('accepts-emoji') + ) { + this._target = document.activeElement as HTMLInputElement; + } + }; + + setData = (data: EmojiData) => { + const cache: EmojiCacheItem[] = Object.keys(data.emojis).map(e => { + const em = data.emojis[e]; + return { + id: e, + name: em.a.toLowerCase(), + keywords: em.j ? em.j : [], + }; + }); + + this.stateSet({data, cache}); + }; + + filterChanged = ( + e: React.ChangeEvent, + ) => { + this.setState({filter: e.target.value}); + }; + + clicked = (id: string, native: string) => { + const recent = ls.get('recent-emoji', []); + if (!recent.includes(id)) { + const newRecent = [...new Set([id, ...recent])].slice(0, 18); + ls.set('recent-emoji', newRecent); + this.forceUpdate(); // Re-render recent list } - componentWillUnmount() { - super.componentWillUnmount(); - - document.querySelectorAll(".accepts-emoji").forEach((i) => { - i.removeEventListener("focus", this.watchTarget); - }); + if (this._target) { + insertOrReplace(this._target, native); + } else { + const {fallback} = this.props; + if (fallback) fallback(native); } + }; - watchTarget = () => { - if (document.activeElement && document.activeElement.classList.contains("accepts-emoji")) { - this._target = document.activeElement as HTMLInputElement; - } - }; - - setData = (data: EmojiData) => { - const cache: EmojiCacheItem[] = Object.keys(data.emojis).map((e) => { - const em = data.emojis[e]; - return { - id: e, - name: em.a.toLowerCase(), - keywords: em.j ? em.j : [], - }; - }); - - this.stateSet({data, cache}); - }; - - filterChanged = (e: React.ChangeEvent) => { - this.setState({filter: e.target.value}); - }; - - clicked = (id: string, native: string) => { - const recent = ls.get("recent-emoji", []); - if (!recent.includes(id)) { - const newRecent = [...new Set([id, ...recent])].slice(0, 18); - ls.set("recent-emoji", newRecent); - this.forceUpdate(); // Re-render recent list - } - - if (this._target) { - insertOrReplace(this._target, native); - } else { - const {fallback} = this.props; - if (fallback) fallback(native); - } - }; - - renderEmoji = (emoji: string) => { - const {data} = this.state; - const em = data!.emojis[emoji]; - if (!em) { - return null; - } - const unicodes = em.b.split("-"); - const codePoints = unicodes.map((u) => Number(`0x${u}`)); - const native = String.fromCodePoint(...codePoints); - - return ( -
{ - this.clicked(emoji, native); - }} - key={emoji} - className="emoji" - title={em.a} - > - {native} -
- ); - }; - - render() { - const {data, cache, filter} = this.state; - if (!data || !cache) { - return null; - } - - const recent: string[] = ls.get("recent-emoji", []); - - return ( -
- - - {(() => { - if (filter) { - const results = cache - .filter( - (i) => i.id.indexOf(filter) !== -1 || i.name.indexOf(filter) !== -1 || i.keywords.includes(filter) - ) - .map((i) => i.id); - - return ( -
-
-
- {results.length === 0 && _t("emoji-picker.filter-no-match")} - {results.length > 0 && results.map((emoji) => this.renderEmoji(emoji))} -
-
-
- ); - } else { - return ( -
- {recent.length > 0 && ( -
-
{_t("emoji-picker.recently-used")}
-
{recent.map((emoji) => this.renderEmoji(emoji))}
-
- )} - - {data.categories.map((cat) => ( -
-
{cat.name}
-
{cat.emojis.map((emoji) => this.renderEmoji(emoji))}
-
- ))} -
- ); - } - })()} -
- ); + renderEmoji = (emoji: string) => { + const {data} = this.state; + const em = data!.emojis[emoji]; + if (!em) { + return null; + } + const unicodes = em.b.split('-'); + const codePoints = unicodes.map(u => Number(`0x${u}`)); + const native = String.fromCodePoint(...codePoints); + + return ( +
{ + this.clicked(emoji, native); + }} + key={emoji} + className='emoji' + title={em.a} + > + {native} +
+ ); + }; + + render() { + const {data, cache, filter} = this.state; + if (!data || !cache) { + return null; } + + const recent: string[] = ls.get('recent-emoji', []); + + return ( +
+ + + {(() => { + if (filter) { + const results = cache + .filter( + i => + i.id.indexOf(filter) !== -1 || + i.name.indexOf(filter) !== -1 || + i.keywords.includes(filter), + ) + .map(i => i.id); + + return ( +
+
+
+ {results.length === 0 && _t('emoji-picker.filter-no-match')} + {results.length > 0 && + results.map(emoji => this.renderEmoji(emoji))} +
+
+
+ ); + } else { + return ( +
+ {recent.length > 0 && ( +
+
+ {_t('emoji-picker.recently-used')} +
+
+ {recent.map(emoji => this.renderEmoji(emoji))} +
+
+ )} + + {data.categories.map(cat => ( +
+
{cat.name}
+
+ {cat.emojis.map(emoji => this.renderEmoji(emoji))} +
+
+ ))} +
+ ); + } + })()} +
+ ); + } } diff --git a/src/common/components/entry-body-extra/index.tsx b/src/common/components/entry-body-extra/index.tsx index e4213f13e0b..b4f2ca14c72 100644 --- a/src/common/components/entry-body-extra/index.tsx +++ b/src/common/components/entry-body-extra/index.tsx @@ -1,47 +1,52 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; import mediumZoom, {Zoom} from 'medium-zoom'; -import {Entry} from "../../store/entries/types"; +import {Entry} from '../../store/entries/types'; -import {injectTwitter} from "../../util/twitter"; +import {injectTwitter} from '../../util/twitter'; interface Props { - entry: Entry + entry: Entry; } class EntryBodyExtra extends Component { - zoom: Zoom | null = null; - - componentDidMount() { - const {entry} = this.props; - - // Tweet renderer - if (/(?:https?:\/\/(?:(?:twitter\.com\/(.*?)\/status\/(\d+)$)))/gim.test(entry.body)) { - injectTwitter(); - } - - // Medium style image zoom - const elements: HTMLElement[] = [...document.querySelectorAll(".entry-body img")] - .filter(x => x.parentNode?.nodeName !== "A"); - this.zoom = mediumZoom(elements); + zoom: Zoom | null = null; + + componentDidMount() { + const {entry} = this.props; + + // Tweet renderer + if ( + /(?:https?:\/\/(?:(?:twitter\.com\/(.*?)\/status\/(\d+)$)))/gim.test( + entry.body, + ) + ) { + injectTwitter(); } - componentWillUnmount() { - if (this.zoom) { - this.zoom.detach(); - } - } + // Medium style image zoom + const elements: HTMLElement[] = [ + ...document.querySelectorAll('.entry-body img'), + ].filter(x => x.parentNode?.nodeName !== 'A'); + this.zoom = mediumZoom(elements); + } - render() { - return null; + componentWillUnmount() { + if (this.zoom) { + this.zoom.detach(); } + } + + render() { + return null; + } } export default (p: Props) => { - const props = { - entry: p.entry - } + const props = { + entry: p.entry, + }; - return -} + return ; +}; diff --git a/src/common/components/entry-delete-btn/index.spec.tsx b/src/common/components/entry-delete-btn/index.spec.tsx index dae86ab8cff..5f3e4270848 100644 --- a/src/common/components/entry-delete-btn/index.spec.tsx +++ b/src/common/components/entry-delete-btn/index.spec.tsx @@ -1,21 +1,20 @@ -import React from "react"; +import React from 'react'; -import EntryDeleteBtn from "./index"; -import TestRenderer from "react-test-renderer"; +import EntryDeleteBtn from './index'; +import TestRenderer from 'react-test-renderer'; -import {entryInstance1} from "../../helper/test-helper"; +import {entryInstance1} from '../../helper/test-helper'; const defProps = { - children: delete, - entry: {...entryInstance1}, - users: [], - activeUser: null, - onSuccess: () => { - } + children: delete, + entry: {...entryInstance1}, + users: [], + activeUser: null, + onSuccess: () => {}, }; -it("(1) Default render", () => { - const props = {...defProps}; - const renderer = TestRenderer.create(); - expect(renderer.toJSON()).toMatchSnapshot(); +it('(1) Default render', () => { + const props = {...defProps}; + const renderer = TestRenderer.create(); + expect(renderer.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/entry-delete-btn/index.tsx b/src/common/components/entry-delete-btn/index.tsx index e1dfe65d3c4..40cebd5ee82 100644 --- a/src/common/components/entry-delete-btn/index.tsx +++ b/src/common/components/entry-delete-btn/index.tsx @@ -1,84 +1,82 @@ -import React from "react"; +import React from 'react'; -import {Entry} from "../../store/entries/types"; -import {ActiveUser} from "../../store/active-user/types"; +import {Entry} from '../../store/entries/types'; +import {ActiveUser} from '../../store/active-user/types'; -import BaseComponent from "../base"; -import PopoverConfirm from "../popover-confirm"; +import BaseComponent from '../base'; +import PopoverConfirm from '../popover-confirm'; -import {error} from "../feedback"; +import {error} from '../feedback'; -import {deleteComment, formatError} from "../../api/operations"; +import {deleteComment, formatError} from '../../api/operations'; -import _c from "../../util/fix-class-names"; +import _c from '../../util/fix-class-names'; interface Props { - children: JSX.Element; - entry: Entry; - activeUser: ActiveUser | null; - onSuccess: () => void; - setDeleteInProgress?: (value: boolean) => void; - isComment?: boolean; + children: JSX.Element; + entry: Entry; + activeUser: ActiveUser | null; + onSuccess: () => void; + setDeleteInProgress?: (value: boolean) => void; + isComment?: boolean; } interface State { - inProgress: boolean; + inProgress: boolean; } export class EntryDeleteBtn extends BaseComponent { - state: State = { - inProgress: false, - }; - - delete = () => { - const {entry, activeUser, onSuccess, setDeleteInProgress} = this.props; - setDeleteInProgress && setDeleteInProgress(true); - - this.stateSet({inProgress: true}); - deleteComment(activeUser?.username!, entry.author, entry.permlink) - .then(() => { - onSuccess(); - this.stateSet({inProgress: false}); - setDeleteInProgress && setDeleteInProgress(false); - }) - .catch((e) => { - error(formatError(e)); - this.stateSet({inProgress: false}); - setDeleteInProgress && setDeleteInProgress(false); - }) - }; - - render() { - const {children} = this.props; - const {inProgress} = this.state; - - const {className} = children.props; - const baseCls = className ? className.replace("in-progress") : ""; - - const clonedChildren = React.cloneElement(children, { - className: _c(`${baseCls} ${inProgress ? "in-progress" : ""}`) - }); - - if (inProgress) { - return clonedChildren - } - - return ( - - {clonedChildren} - - ); + state: State = { + inProgress: false, + }; + + delete = () => { + const {entry, activeUser, onSuccess, setDeleteInProgress} = this.props; + setDeleteInProgress && setDeleteInProgress(true); + + this.stateSet({inProgress: true}); + deleteComment(activeUser?.username!, entry.author, entry.permlink) + .then(() => { + onSuccess(); + this.stateSet({inProgress: false}); + setDeleteInProgress && setDeleteInProgress(false); + }) + .catch(e => { + error(formatError(e)); + this.stateSet({inProgress: false}); + setDeleteInProgress && setDeleteInProgress(false); + }); + }; + + render() { + const {children} = this.props; + const {inProgress} = this.state; + + const {className} = children.props; + const baseCls = className ? className.replace('in-progress') : ''; + + const clonedChildren = React.cloneElement(children, { + className: _c(`${baseCls} ${inProgress ? 'in-progress' : ''}`), + }); + + if (inProgress) { + return clonedChildren; } + + return ( + {clonedChildren} + ); + } } export default (props: Props) => { - const p: Props = { - children: props.children, - entry: props.entry, - activeUser: props.activeUser, - onSuccess: props.onSuccess, - setDeleteInProgress: props.setDeleteInProgress - } - - return -} + const p: Props = { + children: props.children, + entry: props.entry, + activeUser: props.activeUser, + onSuccess: props.onSuccess, + setDeleteInProgress: props.setDeleteInProgress, + }; + + return ; +}; diff --git a/src/common/components/entry-index-menu-dropdown/index.spec.tsx b/src/common/components/entry-index-menu-dropdown/index.spec.tsx index a4bb67231bc..6c34f2e1dbd 100644 --- a/src/common/components/entry-index-menu-dropdown/index.spec.tsx +++ b/src/common/components/entry-index-menu-dropdown/index.spec.tsx @@ -1,34 +1,40 @@ -import React from "react"; +import React from 'react'; -import {StaticRouter} from "react-router-dom"; -import TestRenderer from "react-test-renderer"; -import {createBrowserHistory} from "history"; +import {StaticRouter} from 'react-router-dom'; +import TestRenderer from 'react-test-renderer'; +import {createBrowserHistory} from 'history'; -import {EntryIndexMenuDropdown} from "./index"; +import {EntryIndexMenuDropdown} from './index'; -import {globalInstance} from "../../helper/test-helper"; +import {globalInstance} from '../../helper/test-helper'; const defaultProps = { - history: createBrowserHistory(), - global: globalInstance, - onChangeGlobal: () => {}, - isGlobal: true, - toggleUIProp: () => {}, - isActive: true -} + history: createBrowserHistory(), + global: globalInstance, + onChangeGlobal: () => {}, + isGlobal: true, + toggleUIProp: () => {}, + isActive: true, +}; -it("(1) Renders correctly for global true and active true", () => { - const renderer = TestRenderer.create( - - - ); - expect(renderer.toJSON()).toMatchSnapshot(); +it('(1) Renders correctly for global true and active true', () => { + const renderer = TestRenderer.create( + + + , + ); + expect(renderer.toJSON()).toMatchSnapshot(); }); -it("(2) Renders correctly for global false and active false", () => { - const renderer = TestRenderer.create( - - - ); - expect(renderer.toJSON()).toMatchSnapshot(); -}); \ No newline at end of file +it('(2) Renders correctly for global false and active false', () => { + const renderer = TestRenderer.create( + + + , + ); + expect(renderer.toJSON()).toMatchSnapshot(); +}); diff --git a/src/common/components/entry-index-menu-dropdown/index.tsx b/src/common/components/entry-index-menu-dropdown/index.tsx index 41443f19dab..f97cd170f4f 100644 --- a/src/common/components/entry-index-menu-dropdown/index.tsx +++ b/src/common/components/entry-index-menu-dropdown/index.tsx @@ -1,12 +1,10 @@ -import React from 'react' -import DropDown, { MenuItem } from "../dropdown"; -import { Global } from "../../store/global/types"; -import { History } from "history"; -import { _t } from "../../i18n"; -import { ToggleType } from "../../store/ui/types"; -import { - menuDownSvg, -} from "../../img/svg"; +import React from 'react'; +import DropDown, {MenuItem} from '../dropdown'; +import {Global} from '../../store/global/types'; +import {History} from 'history'; +import {_t} from '../../i18n'; +import {ToggleType} from '../../store/ui/types'; +import {menuDownSvg} from '../../img/svg'; interface Props { global: Global; @@ -18,21 +16,25 @@ interface Props { } export const EntryIndexMenuDropdown = (props: Props) => { - const { global: { filter, tag }, history, onChangeGlobal, isGlobal } = props; + const { + global: {filter, tag}, + history, + onChangeGlobal, + isGlobal, + } = props; let dropDownItems: MenuItem[] = [ { label: {_t('entry-filter.filter-global')}, - active: tag === "", - onClick: () => onTagValueClick('') + active: tag === '', + onClick: () => onTagValueClick(''), }, { label: {_t('entry-filter.filter-community')}, - active: tag === "my", - onClick: () => onTagValueClick('my') - - } - ] + active: tag === 'my', + onClick: () => onTagValueClick('my'), + }, + ]; if (filter === 'created') { dropDownItems = [ @@ -43,7 +45,7 @@ export const EntryIndexMenuDropdown = (props: Props) => { // active: tag === "right_now", // onClick: () => console.log('right_now clicked'), // }, - ] + ]; } const dropDownConfig = { @@ -51,8 +53,11 @@ export const EntryIndexMenuDropdown = (props: Props) => { label: (
- {tag === "" ? _t('entry-filter.filter-global') : tag === 'my' ? _t('entry-filter.filter-community') : tag} - {" "} + {tag === '' + ? _t('entry-filter.filter-global') + : tag === 'my' + ? _t('entry-filter.filter-community') + : tag}{' '} {menuDownSvg}
), @@ -60,18 +65,13 @@ export const EntryIndexMenuDropdown = (props: Props) => { }; const onTagValueClick = (key: string) => { - const { toggleUIProp, isActive } = props; + const {toggleUIProp, isActive} = props; if (key === 'my' && !isActive) { - toggleUIProp('login') + toggleUIProp('login'); } else { - onChangeGlobal(!(key.length > 0)) + onChangeGlobal(!(key.length > 0)); } - } - - return ( - - ); -} - - + }; + return ; +}; diff --git a/src/common/components/entry-index-menu/index.spec.tsx b/src/common/components/entry-index-menu/index.spec.tsx index fd5766db569..a61ef872ccf 100644 --- a/src/common/components/entry-index-menu/index.spec.tsx +++ b/src/common/components/entry-index-menu/index.spec.tsx @@ -1,122 +1,120 @@ -import React from "react"; +import React from 'react'; -import {StaticRouter} from "react-router-dom"; -import TestRenderer from "react-test-renderer"; -import {createBrowserHistory} from "history"; +import {StaticRouter} from 'react-router-dom'; +import TestRenderer from 'react-test-renderer'; +import {createBrowserHistory} from 'history'; -import {EntryIndexMenu} from "./index"; +import {EntryIndexMenu} from './index'; -import {globalInstance, activeUserMaker} from "../../helper/test-helper"; - -import {EntryFilter, AllFilter} from "../../store/global/types"; +import {globalInstance, activeUserMaker} from '../../helper/test-helper'; +import {EntryFilter, AllFilter} from '../../store/global/types'; const defaultProps = { - history: createBrowserHistory(), - global: globalInstance, - activeUser: null, - toggleListStyle: () => { - }, - toggleUIProp: () => { - } -} - -it("(1) No active user. Default filter", () => { - const renderer = TestRenderer.create( - - - ); - expect(renderer.toJSON()).toMatchSnapshot(); + history: createBrowserHistory(), + global: globalInstance, + activeUser: null, + toggleListStyle: () => {}, + toggleUIProp: () => {}, +}; + +it('(1) No active user. Default filter', () => { + const renderer = TestRenderer.create( + + + , + ); + expect(renderer.toJSON()).toMatchSnapshot(); }); -it("(2) No active user. Trending filter", () => { - const props = { - ...defaultProps, - global: { - ...globalInstance, - filter: EntryFilter.trending - } - }; - - const renderer = TestRenderer.create( - - - ); - expect(renderer.toJSON()).toMatchSnapshot(); +it('(2) No active user. Trending filter', () => { + const props = { + ...defaultProps, + global: { + ...globalInstance, + filter: EntryFilter.trending, + }, + }; + + const renderer = TestRenderer.create( + + + , + ); + expect(renderer.toJSON()).toMatchSnapshot(); }); - -it("(3) Active user. Trending filter", () => { - const props = { - ...defaultProps, - global: { - ...globalInstance, - filter: EntryFilter.trending - }, - activeUser: activeUserMaker("foo") - }; - - const renderer = TestRenderer.create( - - - ); - expect(renderer.toJSON()).toMatchSnapshot(); +it('(3) Active user. Trending filter', () => { + const props = { + ...defaultProps, + global: { + ...globalInstance, + filter: EntryFilter.trending, + }, + activeUser: activeUserMaker('foo'), + }; + + const renderer = TestRenderer.create( + + + , + ); + expect(renderer.toJSON()).toMatchSnapshot(); }); - -it("(4) Active user. Friends", () => { - const props = { - ...defaultProps, - global: { - ...globalInstance, - filter: AllFilter.feed, - tag: "@foo" - }, - activeUser: activeUserMaker("foo") - }; - - const renderer = TestRenderer.create( - - - ); - expect(renderer.toJSON()).toMatchSnapshot(); +it('(4) Active user. Friends', () => { + const props = { + ...defaultProps, + global: { + ...globalInstance, + filter: AllFilter.feed, + tag: '@foo', + }, + activeUser: activeUserMaker('foo'), + }; + + const renderer = TestRenderer.create( + + + , + ); + expect(renderer.toJSON()).toMatchSnapshot(); }); -it("(5) Active user. Communities", () => { - const props = { - ...defaultProps, - global: { - ...globalInstance, - filter: AllFilter.trending, - tag: "my" - }, - activeUser: activeUserMaker("foo") - }; - - const renderer = TestRenderer.create( - - - ); - expect(renderer.toJSON()).toMatchSnapshot(); +it('(5) Active user. Communities', () => { + const props = { + ...defaultProps, + global: { + ...globalInstance, + filter: AllFilter.trending, + tag: 'my', + }, + activeUser: activeUserMaker('foo'), + }; + + const renderer = TestRenderer.create( + + + , + ); + expect(renderer.toJSON()).toMatchSnapshot(); }); - -it("(6) No active user. Communities", () => { - const props = { - ...defaultProps, - global: { - ...globalInstance, - filter: AllFilter.trending, - tag: "my" - }, - activeUser: null - }; - - const renderer = TestRenderer.create( - - - ); - expect(renderer.toJSON()).toMatchSnapshot(); +it('(6) No active user. Communities', () => { + const props = { + ...defaultProps, + global: { + ...globalInstance, + filter: AllFilter.trending, + tag: 'my', + }, + activeUser: null, + }; + + const renderer = TestRenderer.create( + + + , + ); + expect(renderer.toJSON()).toMatchSnapshot(); }); - - diff --git a/src/common/components/entry-index-menu/index.tsx b/src/common/components/entry-index-menu/index.tsx index 9ff8540d310..2187b93886f 100644 --- a/src/common/components/entry-index-menu/index.tsx +++ b/src/common/components/entry-index-menu/index.tsx @@ -1,425 +1,634 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import {History} from "history"; +import {History} from 'history'; -import {Link} from "react-router-dom"; +import {Link} from 'react-router-dom'; -import {EntryFilter, Global} from "../../store/global/types"; -import {ActiveUser} from "../../store/active-user/types"; +import {EntryFilter, Global} from '../../store/global/types'; +import {ActiveUser} from '../../store/active-user/types'; -import DropDown, {MenuItem} from "../dropdown"; -import ListStyleToggle from "../list-style-toggle"; +import DropDown, {MenuItem} from '../dropdown'; +import ListStyleToggle from '../list-style-toggle'; -import {_t} from "../../i18n"; -import * as ls from "../../util/local-storage"; -import _c from "../../util/fix-class-names" -import { Form } from "react-bootstrap"; -import { informationVariantSvg } from "../../img/svg"; -import { apiBase } from "../../api/helper"; -import { Introduction } from "../introduction"; -import { EntryIndexMenuDropdown } from "../entry-index-menu-dropdown"; -import {UI, ToggleType} from "../../store/ui/types"; +import {_t} from '../../i18n'; +import * as ls from '../../util/local-storage'; +import _c from '../../util/fix-class-names'; +import {Form} from 'react-bootstrap'; +import {informationVariantSvg} from '../../img/svg'; +import {apiBase} from '../../api/helper'; +import {Introduction} from '../introduction'; +import {EntryIndexMenuDropdown} from '../entry-index-menu-dropdown'; +import {UI, ToggleType} from '../../store/ui/types'; interface Props { - history: History; - global: Global; - activeUser: ActiveUser | null; - toggleListStyle: (view: string | null) => void; - toggleUIProp: (what: ToggleType) => void; + history: History; + global: Global; + activeUser: ActiveUser | null; + toggleListStyle: (view: string | null) => void; + toggleUIProp: (what: ToggleType) => void; } export enum IntroductionType { - FRIENDS = 'FRIENDS', - TRENDING = 'TRENDING', - HOT = 'HOT', - NEW = 'NEW', - NONE = "NONE" + FRIENDS = 'FRIENDS', + TRENDING = 'TRENDING', + HOT = 'HOT', + NEW = 'NEW', + NONE = 'NONE', } interface States { - isGlobal: boolean; - isMounted: boolean; - introduction: IntroductionType; + isGlobal: boolean; + isMounted: boolean; + introduction: IntroductionType; } export const isMyPage = (global: Global, activeUser: ActiveUser | null) => { - const {filter, tag} = global; - return activeUser && activeUser !== null && - ( - (activeUser.username === tag.replace("@", "") && filter === "feed") || - tag === "my" - ); -} - -export const isActiveUser = ( activeUser: ActiveUser | null) => { - return activeUser !== null; -} + const {filter, tag} = global; + return ( + activeUser && + activeUser !== null && + ((activeUser.username === tag.replace('@', '') && filter === 'feed') || + tag === 'my') + ); +}; + +export const isActiveUser = (activeUser: ActiveUser | null) => { + return activeUser !== null; +}; export class EntryIndexMenu extends Component { - constructor(props:Props){ - super(props) - const { activeUser, history: { location: { pathname} }, global : { filter } } = props; - let isGlobal = !pathname.includes('/my'); - if(activeUser && isActiveUser(activeUser) && pathname.includes(activeUser.username)){ - isGlobal = false; - } - let showInitialIntroductionJourney = activeUser && isActiveUser(activeUser) && ls.get(`${activeUser.username}HadTutorial`); - if(activeUser && isActiveUser(activeUser) && (showInitialIntroductionJourney === 'false' || showInitialIntroductionJourney===null)){ - showInitialIntroductionJourney = true; - ls.set(`${activeUser.username}HadTutorial`, 'true'); - } - if(showInitialIntroductionJourney === true){ - showInitialIntroductionJourney = IntroductionType.FRIENDS - } - else { - showInitialIntroductionJourney = IntroductionType.NONE - } - this.state = { isGlobal, introduction: showInitialIntroductionJourney, isMounted: false }; - this.onChangeGlobal = this.onChangeGlobal.bind(this); + constructor(props: Props) { + super(props); + const { + activeUser, + history: { + location: {pathname}, + }, + global: {filter}, + } = props; + let isGlobal = !pathname.includes('/my'); + if ( + activeUser && + isActiveUser(activeUser) && + pathname.includes(activeUser.username) + ) { + isGlobal = false; } - - componentDidMount() { - const { global : { filter } } = this.props; - const { introduction } = this.state; - if(introduction === IntroductionType.NONE){ - if (typeof window !== 'undefined') { - document.getElementById('overlay') && document.getElementById('overlay')!.classList.remove("overlay-for-introduction"); - document.getElementById('feed') && document.getElementById('feed')!.classList.remove("active"); - document.getElementById(filter) && document.getElementById(filter)!.classList.add("active"); - document.getElementsByTagName('ul') && document.getElementsByTagName('ul')[0] && document.getElementsByTagName('ul')[0]!.classList.remove("flash"); - let entryIndexMenuElements = document.getElementsByClassName("entry-index-menu"); - entryIndexMenuElements && entryIndexMenuElements.length > 1 && entryIndexMenuElements[0] && entryIndexMenuElements[0].classList.remove("entry-index-menu"); - } - } - this.setState({isMounted: true}) + let showInitialIntroductionJourney = + activeUser && + isActiveUser(activeUser) && + ls.get(`${activeUser.username}HadTutorial`); + if ( + activeUser && + isActiveUser(activeUser) && + (showInitialIntroductionJourney === 'false' || + showInitialIntroductionJourney === null) + ) { + showInitialIntroductionJourney = true; + ls.set(`${activeUser.username}HadTutorial`, 'true'); } - - onChangeGlobal(value: boolean) { - const { history, global : { tag, filter } } = this.props; - this.setState({ isGlobal: value }); - if (value) { - history.push(`/${filter}`) - } else { - history.push(`/${filter}/my`) - } + if (showInitialIntroductionJourney === true) { + showInitialIntroductionJourney = IntroductionType.FRIENDS; + } else { + showInitialIntroductionJourney = IntroductionType.NONE; } - - componentDidUpdate(prevProps: Props){ - const { history, activeUser, global: { tag, filter } } = this.props; - - if(history.location.pathname.includes('/my') && !isActiveUser(activeUser)){ - history.push(history.location.pathname.replace('/my', '')) - } - // else if (!isActiveUser(activeUser) && (filter === 'feed')) { - // history.push('/trending') - // } - // else if (!isActiveUser(activeUser) && (prevProps.global.filter === 'feed') && (filter === 'trending' || filter === 'hot' || filter === 'created') && (tag.includes('@'))) { - // history.push(`/${filter}`) - // } - else if(!isActiveUser(prevProps.activeUser) !== !isActiveUser(activeUser) && filter !== 'feed'){ - let isGlobalValue = ((tag.length > 0) && (tag === 'my')) ? false : true - this.setState({isGlobal: isGlobalValue}); - } - else if(prevProps.activeUser && activeUser && prevProps.activeUser?.username !== activeUser?.username && filter === 'feed') { - history.push(`/@${activeUser?.username}/${filter}`) - } - - let showInitialIntroductionJourney = activeUser && isActiveUser(activeUser) && ls.get(`${activeUser.username}HadTutorial`); - if(prevProps.activeUser !==activeUser && activeUser && isActiveUser(activeUser) && (showInitialIntroductionJourney==='false' || showInitialIntroductionJourney===null)){ - showInitialIntroductionJourney = true; - ls.set(`${activeUser.username}HadTutorial`, 'true'); - this.setState({introduction: showInitialIntroductionJourney ? IntroductionType.FRIENDS : IntroductionType.NONE}) - } - if(prevProps.activeUser !== activeUser && !activeUser && !isActiveUser(activeUser) && ls.get(`${prevProps.activeUser!.username}HadTutorial`)){ - this.setState({introduction: IntroductionType.NONE}) - } + this.state = { + isGlobal, + introduction: showInitialIntroductionJourney, + isMounted: false, + }; + this.onChangeGlobal = this.onChangeGlobal.bind(this); + } + + componentDidMount() { + const { + global: {filter}, + } = this.props; + const {introduction} = this.state; + if (introduction === IntroductionType.NONE) { + if (typeof window !== 'undefined') { + document.getElementById('overlay') && + document + .getElementById('overlay')! + .classList.remove('overlay-for-introduction'); + document.getElementById('feed') && + document.getElementById('feed')!.classList.remove('active'); + document.getElementById(filter) && + document.getElementById(filter)!.classList.add('active'); + document.getElementsByTagName('ul') && + document.getElementsByTagName('ul')[0] && + document.getElementsByTagName('ul')[0]!.classList.remove('flash'); + let entryIndexMenuElements = + document.getElementsByClassName('entry-index-menu'); + entryIndexMenuElements && + entryIndexMenuElements.length > 1 && + entryIndexMenuElements[0] && + entryIndexMenuElements[0].classList.remove('entry-index-menu'); + } } - - onClosePopup = () => { - this.setState({introduction: IntroductionType.NONE}) + this.setState({isMounted: true}); + } + + onChangeGlobal(value: boolean) { + const { + history, + global: {tag, filter}, + } = this.props; + this.setState({isGlobal: value}); + if (value) { + history.push(`/${filter}`); + } else { + history.push(`/${filter}/my`); } - - getPopupTitle = () => { - let value = ''; - switch(this.state.introduction){ - case IntroductionType.TRENDING: - value = 'filter-trending'; - break; - case IntroductionType.HOT: - value = 'filter-hot'; - break; - case IntroductionType.NEW: - value = 'filter-created'; - break; - case IntroductionType.FRIENDS: - value = 'filter-feed-friends'; - break; - default: - value = value; - - } - return _t(`entry-filter.${value}`) + } + + componentDidUpdate(prevProps: Props) { + const { + history, + activeUser, + global: {tag, filter}, + } = this.props; + + if ( + history.location.pathname.includes('/my') && + !isActiveUser(activeUser) + ) { + history.push(history.location.pathname.replace('/my', '')); } - - onNextWeb = () => { - let value: IntroductionType = this.state.introduction; - switch(value){ - case IntroductionType.TRENDING: - value = IntroductionType.HOT; - break; - case IntroductionType.HOT: - value = IntroductionType.NEW; - break; - case IntroductionType.NEW: - value = IntroductionType.NONE; - break; - default: - value = value; - } - this.setState({ introduction: value }) + // else if (!isActiveUser(activeUser) && (filter === 'feed')) { + // history.push('/trending') + // } + // else if (!isActiveUser(activeUser) && (prevProps.global.filter === 'feed') && (filter === 'trending' || filter === 'hot' || filter === 'created') && (tag.includes('@'))) { + // history.push(`/${filter}`) + // } + else if ( + !isActiveUser(prevProps.activeUser) !== !isActiveUser(activeUser) && + filter !== 'feed' + ) { + let isGlobalValue = tag.length > 0 && tag === 'my' ? false : true; + this.setState({isGlobal: isGlobalValue}); + } else if ( + prevProps.activeUser && + activeUser && + prevProps.activeUser?.username !== activeUser?.username && + filter === 'feed' + ) { + history.push(`/@${activeUser?.username}/${filter}`); } - onNextMobile = () => { - let value : IntroductionType = this.state.introduction; - switch(value){ - case IntroductionType.TRENDING: - value = IntroductionType.HOT; - break; - case IntroductionType.HOT: - value = IntroductionType.NEW; - break; - case IntroductionType.FRIENDS: - value = IntroductionType.TRENDING; - break; - case IntroductionType.NEW: - value = IntroductionType.NONE; - break; - default: - value = value; - } - this.setState({ introduction: value }) + let showInitialIntroductionJourney = + activeUser && + isActiveUser(activeUser) && + ls.get(`${activeUser.username}HadTutorial`); + if ( + prevProps.activeUser !== activeUser && + activeUser && + isActiveUser(activeUser) && + (showInitialIntroductionJourney === 'false' || + showInitialIntroductionJourney === null) + ) { + showInitialIntroductionJourney = true; + ls.set(`${activeUser.username}HadTutorial`, 'true'); + this.setState({ + introduction: showInitialIntroductionJourney + ? IntroductionType.FRIENDS + : IntroductionType.NONE, + }); } - - onPreviousWeb = () => { - const { activeUser } = this.props; - let value: IntroductionType = this.state.introduction; - switch(value){ - case IntroductionType.NEW: - value = IntroductionType.HOT; - break; - case IntroductionType.HOT: - value = IntroductionType.TRENDING; - break; - case IntroductionType.TRENDING: - value = activeUser && isActiveUser(activeUser) ? IntroductionType.FRIENDS: IntroductionType.NONE; - break; - default: - value = value; - } - this.setState({ introduction: value }) + if ( + prevProps.activeUser !== activeUser && + !activeUser && + !isActiveUser(activeUser) && + ls.get(`${prevProps.activeUser!.username}HadTutorial`) + ) { + this.setState({introduction: IntroductionType.NONE}); } - - onPreviousMobile = () => { - const { activeUser } = this.props; - let value : IntroductionType = this.state.introduction; - switch(value){ - case IntroductionType.NEW: - value = IntroductionType.HOT; - break; - case IntroductionType.HOT: - value = IntroductionType.TRENDING; - break; - case IntroductionType.TRENDING: - value = activeUser && isActiveUser(activeUser) ? IntroductionType.FRIENDS: IntroductionType.NONE; - break; - case IntroductionType.FRIENDS: - value = IntroductionType.NONE; - break; - default: - value = value; - } - this.setState({ introduction: value }) + } + + onClosePopup = () => { + this.setState({introduction: IntroductionType.NONE}); + }; + + getPopupTitle = () => { + let value = ''; + switch (this.state.introduction) { + case IntroductionType.TRENDING: + value = 'filter-trending'; + break; + case IntroductionType.HOT: + value = 'filter-hot'; + break; + case IntroductionType.NEW: + value = 'filter-created'; + break; + case IntroductionType.FRIENDS: + value = 'filter-feed-friends'; + break; + default: + value = value; } + return _t(`entry-filter.${value}`); + }; + + onNextWeb = () => { + let value: IntroductionType = this.state.introduction; + switch (value) { + case IntroductionType.TRENDING: + value = IntroductionType.HOT; + break; + case IntroductionType.HOT: + value = IntroductionType.NEW; + break; + case IntroductionType.NEW: + value = IntroductionType.NONE; + break; + default: + value = value; + } + this.setState({introduction: value}); + }; + + onNextMobile = () => { + let value: IntroductionType = this.state.introduction; + switch (value) { + case IntroductionType.TRENDING: + value = IntroductionType.HOT; + break; + case IntroductionType.HOT: + value = IntroductionType.NEW; + break; + case IntroductionType.FRIENDS: + value = IntroductionType.TRENDING; + break; + case IntroductionType.NEW: + value = IntroductionType.NONE; + break; + default: + value = value; + } + this.setState({introduction: value}); + }; + + onPreviousWeb = () => { + const {activeUser} = this.props; + let value: IntroductionType = this.state.introduction; + switch (value) { + case IntroductionType.NEW: + value = IntroductionType.HOT; + break; + case IntroductionType.HOT: + value = IntroductionType.TRENDING; + break; + case IntroductionType.TRENDING: + value = + activeUser && isActiveUser(activeUser) + ? IntroductionType.FRIENDS + : IntroductionType.NONE; + break; + default: + value = value; + } + this.setState({introduction: value}); + }; + + onPreviousMobile = () => { + const {activeUser} = this.props; + let value: IntroductionType = this.state.introduction; + switch (value) { + case IntroductionType.NEW: + value = IntroductionType.HOT; + break; + case IntroductionType.HOT: + value = IntroductionType.TRENDING; + break; + case IntroductionType.TRENDING: + value = + activeUser && isActiveUser(activeUser) + ? IntroductionType.FRIENDS + : IntroductionType.NONE; + break; + case IntroductionType.FRIENDS: + value = IntroductionType.NONE; + break; + default: + value = value; + } + this.setState({introduction: value}); + }; - render() { - const { activeUser, global } = this.props; - const { isGlobal, introduction, isMounted } = this.state; - const { filter, tag } = global; - const isMy = isMyPage(global, activeUser); - const isActive = isActiveUser(activeUser); - const OurVision = apiBase(`/assets/our-vision.${global.canUseWebp?"webp":"png"}`); - - let menuTagValue = tag ? `/${tag}` : '' - - const menuConfig: { - history: History, - label: string, - items: MenuItem[] - } = { - history: this.props.history, - label: isMy && filter === "feed" ? _t("entry-filter.filter-feed-friends") : _t(`entry-filter.filter-${filter}`), - items: [ - ...[EntryFilter.trending, EntryFilter.hot, EntryFilter.created].map((x) => { - return { - label: _t(`entry-filter.filter-${x}`), - href: isActive ? - ((filter === 'feed') && !isGlobal) ? - `/${x}/my` - : ((filter === 'feed') && isGlobal) ? - `/${x}` - : `/${x}${menuTagValue}` - : tag[0] === '@' ? `/${x}` : `/${x}${menuTagValue}`, - active: filter === x || filter === x + '/my', - id: x, - flash: (x === 'trending' && introduction === IntroductionType.TRENDING) || (x === 'hot' && introduction === IntroductionType.HOT) || (x === 'created' && introduction === IntroductionType.NEW) - }; - }), - ], + render() { + const {activeUser, global} = this.props; + const {isGlobal, introduction, isMounted} = this.state; + const {filter, tag} = global; + const isMy = isMyPage(global, activeUser); + const isActive = isActiveUser(activeUser); + const OurVision = apiBase( + `/assets/our-vision.${global.canUseWebp ? 'webp' : 'png'}`, + ); + + let menuTagValue = tag ? `/${tag}` : ''; + + const menuConfig: { + history: History; + label: string; + items: MenuItem[]; + } = { + history: this.props.history, + label: + isMy && filter === 'feed' + ? _t('entry-filter.filter-feed-friends') + : _t(`entry-filter.filter-${filter}`), + items: [ + ...[EntryFilter.trending, EntryFilter.hot, EntryFilter.created].map( + x => { + return { + label: _t(`entry-filter.filter-${x}`), + href: isActive + ? filter === 'feed' && !isGlobal + ? `/${x}/my` + : filter === 'feed' && isGlobal + ? `/${x}` + : `/${x}${menuTagValue}` + : tag[0] === '@' + ? `/${x}` + : `/${x}${menuTagValue}`, + active: filter === x || filter === x + '/my', + id: x, + flash: + (x === 'trending' && + introduction === IntroductionType.TRENDING) || + (x === 'hot' && introduction === IntroductionType.HOT) || + (x === 'created' && introduction === IntroductionType.NEW), + }; + }, + ), + ], + }; + + const mobileMenuConfig = !isActive + ? menuConfig + : { + ...menuConfig, + items: [ + { + label: _t(`entry-filter.filter-feed-friends`), + href: `/@${activeUser?.username}/feed`, + active: filter === 'feed', + id: 'feed', + }, + ...menuConfig.items, + ], }; - const mobileMenuConfig = !isActive ? menuConfig : {...menuConfig, items:[{ - label: _t(`entry-filter.filter-feed-friends`), - href: `/@${activeUser?.username}/feed`, - active: filter === 'feed', - id: 'feed' - }, ...menuConfig.items]} - - const introductionDescription = ( - <> - {_t('entry-filter.filter-global-part1')} - - {_t(`${this.getPopupTitle()}`)} - - {(introduction === IntroductionType.FRIENDS) && _t('entry-filter.filter-global-part4')} - {(introduction === IntroductionType.FRIENDS) && {_t('entry-filter.filter-global-discover')}} - {(isGlobal && introduction !== IntroductionType.FRIENDS) && _t('entry-filter.filter-global-part2')} - {!isGlobal && introduction !== IntroductionType.FRIENDS && _t('entry-filter.filter-global-part3')} - {!isGlobal && introduction !== IntroductionType.FRIENDS && {_t('entry-filter.filter-global-join-communities')}} - ); - const introductionOverlayClass = isMounted && (introduction === IntroductionType.NONE ? "d-none" : "overlay-for-introduction") || 'd-none'; - return isMounted ?
-
-
-
- {isActive && -
-
    -
  • - - {_t("entry-filter.filter-feed-friends")} - -
  • - {isMounted && introduction !== IntroductionType.NONE && introduction === IntroductionType.FRIENDS ? - { - let value = IntroductionType.TRENDING; - this.setState({ introduction: value })} - } - onPrevious={() => { - let value = IntroductionType.NONE; - this.setState({ introduction: value })} - } - onClose={this.onClosePopup} - description={introductionDescription} - /> : null - } -
-
+ const introductionDescription = ( + <> + {_t('entry-filter.filter-global-part1')} + {_t(`${this.getPopupTitle()}`)} + {introduction === IntroductionType.FRIENDS && + _t('entry-filter.filter-global-part4')} + {introduction === IntroductionType.FRIENDS && ( + + {' '} + {_t('entry-filter.filter-global-discover')} + + )} + {isGlobal && + introduction !== IntroductionType.FRIENDS && + _t('entry-filter.filter-global-part2')} + {!isGlobal && + introduction !== IntroductionType.FRIENDS && + _t('entry-filter.filter-global-part3')} + {!isGlobal && introduction !== IntroductionType.FRIENDS && ( + + {' '} + {_t('entry-filter.filter-global-join-communities')} + + )} + + ); + const introductionOverlayClass = + (isMounted && + (introduction === IntroductionType.NONE + ? 'd-none' + : 'overlay-for-introduction')) || + 'd-none'; + return isMounted ? ( +
+
+
+
+ {isActive && ( +
+
    +
  • + + {_t('entry-filter.filter-feed-friends')} + +
  • + {isMounted && + introduction !== IntroductionType.NONE && + introduction === IntroductionType.FRIENDS ? ( + { + let value = IntroductionType.TRENDING; + this.setState({introduction: value}); + }} + onPrevious={() => { + let value = IntroductionType.NONE; + this.setState({introduction: value}); + }} + onClose={this.onClosePopup} + description={introductionDescription} + /> + ) : null} +
+
+ )} +
+
+
+ +
+
+
    + {menuConfig.items.map((i, k) => { + return ( +
  • + + {i.label} + +
  • + ); + })} + {isMounted && + introduction !== IntroductionType.NONE && + introduction !== IntroductionType.FRIENDS && + (introduction === IntroductionType.HOT || + introduction === IntroductionType.TRENDING || + introduction === IntroductionType.NEW) ? ( + - -
    -
    - -
    -
    -
      - {menuConfig.items.map((i, k) => { - return
    • - {i.label} -
    • - })} - {isMounted && introduction !== IntroductionType.NONE && introduction !== IntroductionType.FRIENDS && (introduction === IntroductionType.HOT || introduction === IntroductionType.TRENDING || introduction === IntroductionType.NEW) ? - - : null} -
    -
    -
    - -
    -
    - - {isMounted && introduction !== IntroductionType.NONE ? - : null - } -
    -
    -
      - {mobileMenuConfig.items.map((i, k) => { - return
    • - {i.label} -
    • - })} -
    -
    -
    - - { - filter !== 'feed' ? - ( - <> -
    - - - - - ) - : null - } - -
    -
-
- - this.setState({ introduction: filter === 'feed' ? IntroductionType.FRIENDS : filter === 'trending' ? IntroductionType.TRENDING : filter === 'hot' ? IntroductionType.HOT : filter === 'created' ? IntroductionType.NEW : IntroductionType.NONE }) - } - > - {informationVariantSvg} - - -
-
-
: null} + onNext={this.onNextWeb} + onPrevious={this.onPreviousWeb} + onClose={this.onClosePopup} + description={introductionDescription} + showFinish={introduction === IntroductionType.NEW} + /> + ) : null} + +
+
+ +
+
+ + {isMounted && introduction !== IntroductionType.NONE ? ( + + ) : null} +
+
+
    + {mobileMenuConfig.items.map((i, k) => { + return ( +
  • + + {i.label} + +
  • + ); + })} +
+
+
+ + {filter !== 'feed' ? ( + <> +
+ + + + + ) : null} +
+
+
+ + this.setState({ + introduction: + filter === 'feed' + ? IntroductionType.FRIENDS + : filter === 'trending' + ? IntroductionType.TRENDING + : filter === 'hot' + ? IntroductionType.HOT + : filter === 'created' + ? IntroductionType.NEW + : IntroductionType.NONE, + }) + } + > + {informationVariantSvg} + + +
+
+
+ ) : null; + } } export default (p: Props) => { - const props = { - history: p.history, - global: p.global, - activeUser: p.activeUser, - toggleListStyle: p.toggleListStyle, - toggleUIProp: p.toggleUIProp, - } - - return -} \ No newline at end of file + const props = { + history: p.history, + global: p.global, + activeUser: p.activeUser, + toggleListStyle: p.toggleListStyle, + toggleUIProp: p.toggleUIProp, + }; + + return ; +}; diff --git a/src/common/components/entry-link/index.spec.tsx b/src/common/components/entry-link/index.spec.tsx index 291c72e8310..9f391f66c4c 100644 --- a/src/common/components/entry-link/index.spec.tsx +++ b/src/common/components/entry-link/index.spec.tsx @@ -5,19 +5,16 @@ import EntryLink from './index'; import TestRenderer from 'react-test-renderer'; import {createBrowserHistory} from 'history'; - it('(1) Default Render', () => { - const props = { - history: createBrowserHistory(), - children: click here, - entry: { - category: 'photography', - author: 'foo', - permlink: 'foo-bar-baz' - } - }; - const renderer = TestRenderer.create( - - ); - expect(renderer.toJSON()).toMatchSnapshot(); + const props = { + history: createBrowserHistory(), + children: click here, + entry: { + category: 'photography', + author: 'foo', + permlink: 'foo-bar-baz', + }, + }; + const renderer = TestRenderer.create(); + expect(renderer.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/entry-link/index.tsx b/src/common/components/entry-link/index.tsx index 0f48c40cea6..ebc6dcd8ff8 100644 --- a/src/common/components/entry-link/index.tsx +++ b/src/common/components/entry-link/index.tsx @@ -1,72 +1,79 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import {History} from "history"; +import {History} from 'history'; -import {Entry} from "../../store/entries/types"; +import {Entry} from '../../store/entries/types'; -import {getPost} from "../../api/bridge"; +import {getPost} from '../../api/bridge'; -export const makePath = (category: string, author: string, permlink: string, toReplies: boolean = false) => - `/${category}/@${author}/${permlink}${toReplies ? "#replies" : ""}`; +export const makePath = ( + category: string, + author: string, + permlink: string, + toReplies: boolean = false, +) => `/${category}/@${author}/${permlink}${toReplies ? '#replies' : ''}`; export interface PartialEntry { - category: string; - author: string; - permlink: string; + category: string; + author: string; + permlink: string; } interface Props { - history: History; - children: JSX.Element; - entry: Entry | PartialEntry; - afterClick?: () => void; + history: History; + children: JSX.Element; + entry: Entry | PartialEntry; + afterClick?: () => void; } export class EntryLink extends Component { - clicked = async (e: React.MouseEvent) => { - e.preventDefault(); + clicked = async (e: React.MouseEvent) => { + e.preventDefault(); - const {history, afterClick} = this.props; + const {history, afterClick} = this.props; - if (afterClick) afterClick(); + if (afterClick) afterClick(); - let {entry: _entry} = this.props; + let {entry: _entry} = this.props; - if (!("title" in _entry)) { - // Get full content if the "entry" passed is "PartialEntry" - try { - const resp = await getPost(_entry.author, _entry.permlink); - if (resp) { - _entry = resp - } - } catch (e) { - return; - } + if (!('title' in _entry)) { + // Get full content if the "entry" passed is "PartialEntry" + try { + const resp = await getPost(_entry.author, _entry.permlink); + if (resp) { + _entry = resp; } + } catch (e) { + return; + } + } - const {category, author, permlink} = _entry; + const {category, author, permlink} = _entry; - history.push(makePath(category, author, permlink)); - }; + history.push(makePath(category, author, permlink)); + }; - render() { - const {children, entry} = this.props; + render() { + const {children, entry} = this.props; - const href = makePath(entry.category, entry.author, entry.permlink); + const href = makePath(entry.category, entry.author, entry.permlink); - const props = Object.assign({}, children.props, {href, onClick: this.clicked}); + const props = Object.assign({}, children.props, { + href, + onClick: this.clicked, + }); - return React.createElement("a", props); - } + return React.createElement('a', props); + } } export default (p: Props) => { - const props: Props = { - history: p.history, - children: p.children, - entry: p.entry, - afterClick: p.afterClick - } - - return ; -} + const props: Props = { + history: p.history, + children: p.children, + entry: p.entry, + afterClick: p.afterClick, + }; + + return ; +}; diff --git a/src/common/components/entry-list-item/index.spec.tsx b/src/common/components/entry-list-item/index.spec.tsx index 1eb0d5ef663..0a11030e63d 100644 --- a/src/common/components/entry-list-item/index.spec.tsx +++ b/src/common/components/entry-list-item/index.spec.tsx @@ -1,168 +1,166 @@ -import React from "react"; +import React from 'react'; -import {createBrowserHistory, createLocation} from "history"; +import {createBrowserHistory, createLocation} from 'history'; -import mockDate from "mockdate"; +import mockDate from 'mockdate'; -import {StaticRouter} from "react-router-dom"; +import {StaticRouter} from 'react-router-dom'; -import TestRenderer from "react-test-renderer"; +import TestRenderer from 'react-test-renderer'; -import {globalInstance, dynamicPropsIntance1, entryInstance1, UiInstance, emptyReblogs, activeUserMaker, crossEntryInstance, allOver} from "../../helper/test-helper"; +import { + globalInstance, + dynamicPropsIntance1, + entryInstance1, + UiInstance, + emptyReblogs, + activeUserMaker, + crossEntryInstance, + allOver, +} from '../../helper/test-helper'; -import {ListStyle} from "../../store/global/types"; - -import EntryListItem from "./index"; +import {ListStyle} from '../../store/global/types'; +import EntryListItem from './index'; mockDate.set(1591398131176); const defProps = { - history: createBrowserHistory(), - location: createLocation({}), - global: globalInstance, - dynamicProps: dynamicPropsIntance1, - communities: [], - community: null, - users: [], - activeUser: null, - reblogs: emptyReblogs, - entry: entryInstance1, - ui: UiInstance, - entryPinTracker: {}, - signingKey: "", - asAuthor: "", - promoted: false, - order: 0, - addAccount: () => { - }, - updateEntry: () => { - }, - setActiveUser: () => { - }, - updateActiveUser: () => { - }, - deleteUser: () => { - }, - fetchReblogs: () => { - }, - addReblog: () => { - }, - deleteReblog: () => { - }, - toggleUIProp: () => { - }, - addCommunity: () => { - }, - trackEntryPin: () => { - }, - setSigningKey: () => { - }, - setEntryPin: () => { - }, -} - -it("(1) Default render", async() => { - const renderer = await TestRenderer.create( - - - ); - await allOver(); - expect(renderer.toJSON()).toMatchSnapshot(); + history: createBrowserHistory(), + location: createLocation({}), + global: globalInstance, + dynamicProps: dynamicPropsIntance1, + communities: [], + community: null, + users: [], + activeUser: null, + reblogs: emptyReblogs, + entry: entryInstance1, + ui: UiInstance, + entryPinTracker: {}, + signingKey: '', + asAuthor: '', + promoted: false, + order: 0, + addAccount: () => {}, + updateEntry: () => {}, + setActiveUser: () => {}, + updateActiveUser: () => {}, + deleteUser: () => {}, + fetchReblogs: () => {}, + addReblog: () => {}, + deleteReblog: () => {}, + toggleUIProp: () => {}, + addCommunity: () => {}, + trackEntryPin: () => {}, + setSigningKey: () => {}, + setEntryPin: () => {}, +}; + +it('(1) Default render', async () => { + const renderer = await TestRenderer.create( + + + , + ); + await allOver(); + expect(renderer.toJSON()).toMatchSnapshot(); }); -it("(2) Grid view", async() => { - - const props = { - ...defProps, - global: { - ...globalInstance, - listStyle: ListStyle.grid - } - } - const renderer = await TestRenderer.create( - - - ); - await allOver(); - expect(renderer.toJSON()).toMatchSnapshot(); +it('(2) Grid view', async () => { + const props = { + ...defProps, + global: { + ...globalInstance, + listStyle: ListStyle.grid, + }, + }; + const renderer = await TestRenderer.create( + + + , + ); + await allOver(); + expect(renderer.toJSON()).toMatchSnapshot(); }); - -it("(3) Nsfw", async() => { - const props = { - ...defProps, - entry: { - ...entryInstance1, - json_metadata: { - ...entryInstance1.json_metadata, - tags: [...entryInstance1.json_metadata.tags, "nsfw"] - } - } - } - const renderer = await TestRenderer.create( - - - ); - await allOver(); - expect(renderer.toJSON()).toMatchSnapshot(); +it('(3) Nsfw', async () => { + const props = { + ...defProps, + entry: { + ...entryInstance1, + json_metadata: { + ...entryInstance1.json_metadata, + tags: [...entryInstance1.json_metadata.tags, 'nsfw'], + }, + }, + }; + const renderer = await TestRenderer.create( + + + , + ); + await allOver(); + expect(renderer.toJSON()).toMatchSnapshot(); }); - -it("(4) Nsfw with active user", async() => { - const props = { - ...defProps, - entry: { - ...entryInstance1, - json_metadata: { - ...entryInstance1.json_metadata, - tags: [...entryInstance1.json_metadata.tags, "nsfw"] - } - }, - activeUser: activeUserMaker("foo") - } - const renderer = await TestRenderer.create( - - - ); - await allOver(); - expect(renderer.toJSON()).toMatchSnapshot(); +it('(4) Nsfw with active user', async () => { + const props = { + ...defProps, + entry: { + ...entryInstance1, + json_metadata: { + ...entryInstance1.json_metadata, + tags: [...entryInstance1.json_metadata.tags, 'nsfw'], + }, + }, + activeUser: activeUserMaker('foo'), + }; + const renderer = await TestRenderer.create( + + + , + ); + await allOver(); + expect(renderer.toJSON()).toMatchSnapshot(); }); -it("(5) Nsfw but allowed", async() => { - const props = { - ...defProps, - entry: { - ...entryInstance1, - json_metadata: { - ...entryInstance1.json_metadata, - tags: [...entryInstance1.json_metadata.tags, "nsfw"] - } - }, - global: { - ...globalInstance, - nsfw: true - } - } - const renderer = await TestRenderer.create( - - - ); - await allOver(); - expect(renderer.toJSON()).toMatchSnapshot(); +it('(5) Nsfw but allowed', async () => { + const props = { + ...defProps, + entry: { + ...entryInstance1, + json_metadata: { + ...entryInstance1.json_metadata, + tags: [...entryInstance1.json_metadata.tags, 'nsfw'], + }, + }, + global: { + ...globalInstance, + nsfw: true, + }, + }; + const renderer = await TestRenderer.create( + + + , + ); + await allOver(); + expect(renderer.toJSON()).toMatchSnapshot(); }); -it("(6) Cross post. Bottom menu", async() => { - const props = { - ...defProps, - entry: crossEntryInstance, - order: 2 - } - - const renderer = await TestRenderer.create( - - - ); - await allOver(); - expect(renderer.toJSON()).toMatchSnapshot(); -}) +it('(6) Cross post. Bottom menu', async () => { + const props = { + ...defProps, + entry: crossEntryInstance, + order: 2, + }; + + const renderer = await TestRenderer.create( + + + , + ); + await allOver(); + expect(renderer.toJSON()).toMatchSnapshot(); +}); diff --git a/src/common/components/entry-list-item/index.tsx b/src/common/components/entry-list-item/index.tsx index 1e5866e5a42..4153fd1fb17 100644 --- a/src/common/components/entry-list-item/index.tsx +++ b/src/common/components/entry-list-item/index.tsx @@ -1,437 +1,582 @@ -import React, {Component} from "react"; - -import {History, Location} from "history"; - -import moment from "moment"; - -import isEqual from "react-fast-compare"; - -import {catchPostImage, postBodySummary, setProxyBase} from "@ecency/render-helper"; - -import {Entry, EntryVote} from "../../store/entries/types"; -import {Global} from "../../store/global/types"; -import {Account} from "../../store/accounts/types"; -import {DynamicProps} from "../../store/dynamic-props/types"; -import {Community, Communities} from "../../store/communities/types"; -import {User} from "../../store/users/types"; -import {ActiveUser} from "../../store/active-user/types"; -import {Reblogs} from "../../store/reblogs/types"; -import {UI, ToggleType} from "../../store/ui/types"; -import {EntryPinTracker} from "../../store/entry-pin-tracker/types"; - -import ProfileLink from "../profile-link/index"; -import Tag from "../tag"; -import UserAvatar from "../user-avatar/index"; -import EntryLink from "../entry-link/index"; -import EntryVoteBtn from "../entry-vote-btn/index"; -import EntryReblogBtn from "../entry-reblog-btn/index"; -import EntryPayout from "../entry-payout/index"; -import EntryVotes from "../entry-votes"; -import Tooltip from "../tooltip"; -import EntryMenu from "../entry-menu"; -import parseDate from "../../helper/parse-date"; -import {_t} from "../../i18n"; -import {Tsx} from "../../i18n/helper"; - -import _c from "../../util/fix-class-names"; -import truncate from "../../util/truncate"; - -import {repeatSvg, pinSvg, commentSvg, muteSvg, volumeOffSvg, closeSvg, downArrowSvg, menuDownSvg} from "../../img/svg"; - -import defaults from "../../constants/defaults.json"; -import { ProfilePopover } from "../profile-popover"; +import React, {Component} from 'react'; + +import {History, Location} from 'history'; + +import moment from 'moment'; + +import isEqual from 'react-fast-compare'; + +import { + catchPostImage, + postBodySummary, + setProxyBase, +} from '@ecency/render-helper'; + +import {Entry, EntryVote} from '../../store/entries/types'; +import {Global} from '../../store/global/types'; +import {Account} from '../../store/accounts/types'; +import {DynamicProps} from '../../store/dynamic-props/types'; +import {Community, Communities} from '../../store/communities/types'; +import {User} from '../../store/users/types'; +import {ActiveUser} from '../../store/active-user/types'; +import {Reblogs} from '../../store/reblogs/types'; +import {UI, ToggleType} from '../../store/ui/types'; +import {EntryPinTracker} from '../../store/entry-pin-tracker/types'; + +import ProfileLink from '../profile-link/index'; +import Tag from '../tag'; +import UserAvatar from '../user-avatar/index'; +import EntryLink from '../entry-link/index'; +import EntryVoteBtn from '../entry-vote-btn/index'; +import EntryReblogBtn from '../entry-reblog-btn/index'; +import EntryPayout from '../entry-payout/index'; +import EntryVotes from '../entry-votes'; +import Tooltip from '../tooltip'; +import EntryMenu from '../entry-menu'; +import parseDate from '../../helper/parse-date'; +import {_t} from '../../i18n'; +import {Tsx} from '../../i18n/helper'; + +import _c from '../../util/fix-class-names'; +import truncate from '../../util/truncate'; + +import { + repeatSvg, + pinSvg, + commentSvg, + muteSvg, + volumeOffSvg, + closeSvg, + downArrowSvg, + menuDownSvg, +} from '../../img/svg'; + +import defaults from '../../constants/defaults.json'; +import {ProfilePopover} from '../profile-popover'; setProxyBase(defaults.imageServer); - interface Props { - history: History; - location: Location; - global: Global; - dynamicProps: DynamicProps; - communities: Communities; - community?: Community | null; - users: User[]; - activeUser: ActiveUser | null; - reblogs: Reblogs; - entry: Entry; - ui: UI; - entryPinTracker: EntryPinTracker; - signingKey: string; - asAuthor: string; - promoted: boolean; - order: number; - addAccount: (data: Account) => void; - updateEntry: (entry: Entry) => void; - setActiveUser: (username: string | null) => void; - updateActiveUser: (data?: Account) => void; - deleteUser: (username: string) => void; - fetchReblogs: () => void; - addReblog: (author: string, permlink: string) => void; - deleteReblog: (author: string, permlink: string) => void; - toggleUIProp: (what: ToggleType | "login") => void; - addCommunity: (data: Community) => void; - trackEntryPin: (entry: Entry) => void; - setSigningKey: (key: string) => void; - setEntryPin: (entry: Entry, pin: boolean) => void; - muted?: boolean; + history: History; + location: Location; + global: Global; + dynamicProps: DynamicProps; + communities: Communities; + community?: Community | null; + users: User[]; + activeUser: ActiveUser | null; + reblogs: Reblogs; + entry: Entry; + ui: UI; + entryPinTracker: EntryPinTracker; + signingKey: string; + asAuthor: string; + promoted: boolean; + order: number; + addAccount: (data: Account) => void; + updateEntry: (entry: Entry) => void; + setActiveUser: (username: string | null) => void; + updateActiveUser: (data?: Account) => void; + deleteUser: (username: string) => void; + fetchReblogs: () => void; + addReblog: (author: string, permlink: string) => void; + deleteReblog: (author: string, permlink: string) => void; + toggleUIProp: (what: ToggleType | 'login') => void; + addCommunity: (data: Community) => void; + trackEntryPin: (entry: Entry) => void; + setSigningKey: (key: string) => void; + setEntryPin: (entry: Entry, pin: boolean) => void; + muted?: boolean; } interface State { - showNsfw: boolean; - showMuted: boolean; - showModMuted: boolean; - mounted: boolean; + showNsfw: boolean; + showMuted: boolean; + showModMuted: boolean; + mounted: boolean; } export default class EntryListItem extends Component { - state: State = { - showNsfw: false, - showMuted: false, - showModMuted: false, - mounted: false, + state: State = { + showNsfw: false, + showMuted: false, + showModMuted: false, + mounted: false, + }; + + public static defaultProps = { + asAuthor: '', + promoted: false, + }; + + shouldComponentUpdate( + nextProps: Readonly, + nextState: Readonly, + ): boolean { + return ( + !isEqual(this.props.entry, nextProps.entry) || + !isEqual(this.props.community, nextProps.community) || + !isEqual(this.props.global, nextProps.global) || + !isEqual(this.props.dynamicProps, nextProps.dynamicProps) || + !isEqual(this.props.activeUser, nextProps.activeUser) || + !isEqual(this.props.reblogs, nextProps.reblogs) || + !isEqual(this.props.communities, nextProps.communities) || + !isEqual(this.props.entryPinTracker, nextProps.entryPinTracker) || + !isEqual(this.state, nextState) + ); + } + + afterVote = (votes: EntryVote[], estimated: number) => { + const {entry, updateEntry} = this.props; + + const {payout} = entry; + const newPayout = payout + estimated; + + updateEntry({ + ...entry, + active_votes: votes, + payout: newPayout, + pending_payout_value: String(newPayout), + }); + }; + + toggleNsfw = () => { + this.setState({showNsfw: true}); + }; + + componentDidMount() { + const {entry, muted} = this.props; + if (muted) { + this.setState({showMuted: true}); } - - public static defaultProps = { - asAuthor: "", - promoted: false, - }; - - shouldComponentUpdate(nextProps: Readonly, nextState: Readonly): boolean { - return ( - !isEqual(this.props.entry, nextProps.entry) || - !isEqual(this.props.community, nextProps.community) || - !isEqual(this.props.global, nextProps.global) || - !isEqual(this.props.dynamicProps, nextProps.dynamicProps) || - !isEqual(this.props.activeUser, nextProps.activeUser) || - !isEqual(this.props.reblogs, nextProps.reblogs) || - !isEqual(this.props.communities, nextProps.communities) || - !isEqual(this.props.entryPinTracker, nextProps.entryPinTracker) || - !isEqual(this.state, nextState) - ); + if (entry.stats?.gray) { + this.setState({showModMuted: true}); } - - afterVote = (votes: EntryVote[], estimated: number) => { - const {entry, updateEntry} = this.props; - - const {payout} = entry; - const newPayout = payout + estimated; - - updateEntry({ - ...entry, - active_votes: votes, - payout: newPayout, - pending_payout_value: String(newPayout) - }); - }; - - toggleNsfw = () => { - this.setState({showNsfw: true}); + document.getElementsByTagName('html')[0].style.position = 'relative'; + this.setState({mounted: true}); + } + + componentWillUnmount() { + document.getElementsByTagName('html')[0].style.position = 'unset'; + this.setState({mounted: false}); + } + + componentDidUpdate(prevProps: Props) { + if ( + this.props.activeUser !== prevProps.activeUser && + !this.props.activeUser + ) { + this.setState({showMuted: false}); } - - componentDidMount(){ - const { entry, muted } = this.props; - if (muted) { - this.setState({ showMuted: true }) - } - if (entry.stats?.gray) { - this.setState({ showModMuted: true }) - } - document.getElementsByTagName("html")[0].style.position = 'relative'; - this.setState({ mounted: true }) + if ( + this.props.entry !== prevProps.entry && + this.props.muted !== prevProps.muted + ) { + this.setState({ + showMuted: this.props.muted || false, + showModMuted: this.props.entry.stats?.gray || false, + }); } - - componentWillUnmount(){ - document.getElementsByTagName("html")[0].style.position = 'unset' - this.setState({ mounted: false }) + } + + render() { + const { + entry: theEntry, + community, + asAuthor, + promoted, + global, + activeUser, + history, + order, + } = this.props; + const {mounted} = this.state; + + const fallbackImage = global.isElectron + ? './img/fallback.png' + : require('../../img/fallback.png'); + const noImage = global.isElectron + ? './img/noimage.svg' + : require('../../img/noimage.svg'); + const nsfwImage = global.isElectron + ? './img/nsfw.png' + : require('../../img/nsfw.png'); + const crossPost = !!theEntry.original_entry; + + const entry = theEntry.original_entry || theEntry; + + const imgGrid: string = + (global.canUseWebp + ? catchPostImage(entry, 600, 500, 'webp') + : catchPostImage(entry, 600, 500)) || noImage; + const imgRow: string = + (global.canUseWebp + ? catchPostImage(entry, 260, 200, 'webp') + : catchPostImage(entry, 260, 200)) || noImage; + let svgSizeRow = imgRow === noImage ? 'noImage' : ''; + let svgSizeGrid = imgGrid === noImage ? '172px' : 'auto'; + + const summary: string = postBodySummary(entry, 200); + + const date = moment(parseDate(entry.created)); + const dateRelative = date.fromNow(true); + const dateFormatted = date.format('LLLL'); + + const isChild = !!entry.parent_author; + + const title = entry.title; + + const isVisited = false; + const isPinned = community && !!entry.stats?.is_pinned; + + let reBlogged: string | undefined; + if (asAuthor && asAuthor !== entry.author && !isChild) { + reBlogged = asAuthor; } - componentDidUpdate(prevProps:Props){ - if(this.props.activeUser !== prevProps.activeUser && !this.props.activeUser){ - this.setState({ showMuted: false }) - } - if(this.props.entry !== prevProps.entry && this.props.muted !== prevProps.muted){ - this.setState({ showMuted: this.props.muted || false , showModMuted: this.props.entry.stats?.gray || false}) - } + if (entry.reblogged_by && entry.reblogged_by.length > 0) { + [reBlogged] = entry.reblogged_by; } - render() { - const {entry: theEntry, community, asAuthor, promoted, global, activeUser, history, order} = this.props; - const { mounted } = this.state; - - const fallbackImage = global.isElectron ? "./img/fallback.png" : require("../../img/fallback.png"); - const noImage = global.isElectron ? "./img/noimage.svg" : require("../../img/noimage.svg"); - const nsfwImage = global.isElectron ? "./img/nsfw.png" : require("../../img/nsfw.png"); - const crossPost = !!(theEntry.original_entry); - - const entry = theEntry.original_entry || theEntry; - - const imgGrid: string = (global.canUseWebp ? catchPostImage(entry, 600, 500, 'webp') : catchPostImage(entry, 600, 500)) || noImage; - const imgRow: string = (global.canUseWebp ? catchPostImage(entry, 260, 200, 'webp') : catchPostImage(entry, 260, 200)) || noImage; - let svgSizeRow = imgRow === noImage ? "noImage" : ""; - let svgSizeGrid = imgGrid === noImage ? "172px" : "auto"; - - const summary: string = postBodySummary(entry, 200); - - const date = moment(parseDate(entry.created)); - const dateRelative = date.fromNow(true); - const dateFormatted = date.format("LLLL"); - - const isChild = !!entry.parent_author; - - const title = entry.title; - - const isVisited = false; - const isPinned = community && !!entry.stats?.is_pinned; - - let reBlogged: string | undefined; - if (asAuthor && asAuthor !== entry.author && !isChild) { - reBlogged = asAuthor; - } - - if (entry.reblogged_by && entry.reblogged_by.length > 0) { - [reBlogged] = entry.reblogged_by; - } - - let thumb: JSX.Element | null = null; - if (global.listStyle === 'grid') { - thumb = ( - {title} { - const target = e.target as HTMLImageElement; - target.src = fallbackImage; - }} - /> - ); - } - if (global.listStyle === 'row') { - thumb = ( - - - {title} { - const target = e.target as HTMLImageElement; - target.src = fallbackImage; - }}/> - + let thumb: JSX.Element | null = null; + if (global.listStyle === 'grid') { + thumb = ( + {title} { + const target = e.target as HTMLImageElement; + target.src = fallbackImage; + }} + /> + ); + } + if (global.listStyle === 'row') { + thumb = ( + + + {title} { + const target = e.target as HTMLImageElement; + target.src = fallbackImage; + }} + /> + + ); + } + const nsfw = + entry.json_metadata && + entry.json_metadata.tags && + Array.isArray(entry.json_metadata.tags) && + entry.json_metadata.tags.includes('nsfw'); + + const cls = `entry-list-item ${promoted ? 'promoted-item' : ''} ${ + global.filter + }`; + + return mounted ? ( +
+ {(() => { + if (crossPost) { + return ( +
+ {ProfileLink({ + ...this.props, + username: theEntry.author, + children: ( + {`@${theEntry.author}`} + ), + })}{' '} + {_t('entry-list-item.cross-posted')}{' '} + {EntryLink({ + ...this.props, + entry: theEntry.original_entry!, + children: ( + + {truncate( + `@${theEntry.original_entry!.author}/${ + theEntry.original_entry!.permlink + }`, + 40, + )} + + ), + })}{' '} + {_t('entry-list-item.cross-posted-to')}{' '} + {Tag({ + ...this.props, + tag: + theEntry.community && theEntry.community_title + ? { + name: theEntry.community, + title: theEntry.community_title, + } + : theEntry.category, + type: 'link', + children: ( + + {theEntry.community_title || theEntry.category} + + ), + })} +
); - } - const nsfw = entry.json_metadata && entry.json_metadata.tags && Array.isArray(entry.json_metadata.tags) && entry.json_metadata.tags.includes("nsfw"); - - const cls = `entry-list-item ${promoted ? "promoted-item" : ""} ${global.filter}`; - - return mounted ? ( -
- - {(() => { - if (crossPost) { - - return
- {ProfileLink({ - ...this.props, - username: theEntry.author, - children: {`@${theEntry.author}`} - })} - {" "} - {_t("entry-list-item.cross-posted")} - {" "} - {EntryLink({ - ...this.props, - entry: theEntry.original_entry!, - children: - {truncate(`@${theEntry.original_entry!.author}/${theEntry.original_entry!.permlink}`, 40)} - - })} - {" "} - {_t("entry-list-item.cross-posted-to")} - {" "} - {Tag({ - ...this.props, - tag: theEntry.community && theEntry.community_title ? {name: theEntry.community, title: theEntry.community_title} : theEntry.category, - type: "link", - children: {theEntry.community_title || theEntry.category} - })} -
- } - - return null; - })()} - -
-
-
-
- {ProfileLink({ - ...this.props, - username: entry.author, - children: {UserAvatar({...this.props, username: entry.author, size: "small"})} - })} - - -
- - -
- {Tag({ - ...this.props, - tag: entry.community && entry.community_title ? {name: entry.community, title: entry.community_title} : entry.category, - type: "link", - children: {entry.community_title || entry.category} - })} - {!isVisited && } - {dateRelative} -
-
- {isPinned && ( - - {pinSvg} - - )} - {reBlogged && ( - {repeatSvg} {_t("entry-list-item.reblogged", {n: reBlogged})} - )} - {promoted && ( - <> - - - - )} -
+ } + + return null; + })()} + +
+
+
+
+ {ProfileLink({ + ...this.props, + username: entry.author, + children: ( + + {UserAvatar({ + ...this.props, + username: entry.author, + size: 'small', + })} + + ), + })} + + +
+
+ {Tag({ + ...this.props, + tag: + entry.community && entry.community_title + ? {name: entry.community, title: entry.community_title} + : entry.category, + type: 'link', + children: ( + + {entry.community_title || entry.category} + + ), + })} + {!isVisited && } + + {dateRelative} + +
+
+ {isPinned && ( + + {pinSvg} + + )} + {reBlogged && ( + + {repeatSvg} {_t('entry-list-item.reblogged', {n: reBlogged})} + + )} + {promoted && ( + <> + + -
- {(() => { - if (nsfw && !this.state.showNsfw && !global.nsfw) { - return <> -
- {title}/ -
-
-
NSFW
-
- { - e.preventDefault(); - this.toggleNsfw(); - }}>{_t("nsfw.reveal")} - {" "} {_t("g.or").toLowerCase()} {" "} - - {activeUser && <> - {_t("nsfw.settings-1")} - {" "} - { - e.preventDefault(); - history.push(`/@${activeUser.username}/settings`); - }}>{_t("nsfw.settings-2")}{"."} - } - - {!activeUser && <> - {"."} - } -
-
- - } - if (this.state.showMuted) { - return <> -
- {title}/ -
- - - } - if (this.state.showModMuted) { - return <> -
- {title}/ -
- - - } - - return <> -
- {EntryLink({ - ...this.props, - entry: (crossPost ? theEntry : entry), - children:
- {thumb} -
- })} -
-
- {EntryLink({ - ...this.props, - entry: (crossPost ? theEntry : entry), - children:
{title}
- })} - {EntryLink({ - ...this.props, - entry: (crossPost ? theEntry : entry), - children:
{summary}
- })} -
+ + )} +
+
+
+ {(() => { + if (nsfw && !this.state.showNsfw && !global.nsfw) { + return ( + <> +
+ {title} +
+
+
+ NSFW +
+
+ { + e.preventDefault(); + this.toggleNsfw(); + }} + > + {_t('nsfw.reveal')} + {' '} + {_t('g.or').toLowerCase()}{' '} + {activeUser && ( + <> + {_t('nsfw.settings-1')}{' '} + { + e.preventDefault(); + history.push(`/@${activeUser.username}/settings`); + }} + > + {_t('nsfw.settings-2')} + + {'.'} + + )} + {!activeUser && ( + <> + + + + {'.'} - })()} -
- {EntryVoteBtn({ - ...this.props, - afterVote: this.afterVote - })} - {EntryPayout({ - ...this.props, - entry - })} - {EntryVotes({ - ...this.props, - entry - })} - {EntryLink({ - ...this.props, - entry: (crossPost ? theEntry : entry), - children: - 0 - ? entry.children === 1 - ? _t("entry-list-item.replies") - : _t("entry-list-item.replies-n", {n: entry.children}) - : _t("entry-list-item.no-replies") - }> - - {commentSvg} {entry.children} - - - - })} - {EntryReblogBtn({ - ...this.props - })} - {EntryMenu({ - ...this.props, - alignBottom: order >= 1, - entry, - })} + )} +
+
+ + ); + } + if (this.state.showMuted) { + return ( + <> +
+ {title} +
+ + + ); + } + if (this.state.showModMuted) { + return ( + <> +
+ {title} +
+ + + ); + } + + return ( + <> +
+ {EntryLink({ + ...this.props, + entry: crossPost ? theEntry : entry, + children:
{thumb}
, + })}
-
- ) : null; - } +
+ {EntryLink({ + ...this.props, + entry: crossPost ? theEntry : entry, + children:
{title}
, + })} + {EntryLink({ + ...this.props, + entry: crossPost ? theEntry : entry, + children:
{summary}
, + })} +
+ + ); + })()} +
+ {EntryVoteBtn({ + ...this.props, + afterVote: this.afterVote, + })} + {EntryPayout({ + ...this.props, + entry, + })} + {EntryVotes({ + ...this.props, + entry, + })} + {EntryLink({ + ...this.props, + entry: crossPost ? theEntry : entry, + children: ( + + 0 + ? entry.children === 1 + ? _t('entry-list-item.replies') + : _t('entry-list-item.replies-n', {n: entry.children}) + : _t('entry-list-item.no-replies') + } + > + + {commentSvg} {entry.children} + + + + ), + })} + {EntryReblogBtn({ + ...this.props, + })} + {EntryMenu({ + ...this.props, + alignBottom: order >= 1, + entry, + })} +
+
+
+ ) : null; + } } diff --git a/src/common/components/entry-list-loading-item/index.tsx b/src/common/components/entry-list-loading-item/index.tsx index 4e174e894e7..b89ba60b150 100644 --- a/src/common/components/entry-list-loading-item/index.tsx +++ b/src/common/components/entry-list-loading-item/index.tsx @@ -1,23 +1,23 @@ -import React, { Component } from 'react'; +import React, {Component} from 'react'; export default class EntryListLoadingItem extends Component { - render() { - return [...Array(6).keys()].map(d => ( -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )); - } + render() { + return [...Array(6).keys()].map(d => ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )); + } } diff --git a/src/common/components/entry-list/index.tsx b/src/common/components/entry-list/index.tsx index d4a7de8891c..c63f8bae531 100644 --- a/src/common/components/entry-list/index.tsx +++ b/src/common/components/entry-list/index.tsx @@ -1,208 +1,257 @@ -import React, {Component} from "react"; -import {History, Location} from "history"; -import _ from 'lodash' +import React, {Component} from 'react'; +import {History, Location} from 'history'; +import _ from 'lodash'; -import {Global, ProfileFilter} from "../../store/global/types"; -import {Account} from "../../store/accounts/types"; -import {DynamicProps} from "../../store/dynamic-props/types"; -import {Entry} from "../../store/entries/types"; -import {Community, Communities} from "../../store/communities/types"; -import {User} from "../../store/users/types"; -import {ActiveUser} from "../../store/active-user/types"; -import {Reblogs} from "../../store/reblogs/types"; -import {UI, ToggleType} from "../../store/ui/types"; +import {Global, ProfileFilter} from '../../store/global/types'; +import {Account} from '../../store/accounts/types'; +import {DynamicProps} from '../../store/dynamic-props/types'; +import {Entry} from '../../store/entries/types'; +import {Community, Communities} from '../../store/communities/types'; +import {User} from '../../store/users/types'; +import {ActiveUser} from '../../store/active-user/types'; +import {Reblogs} from '../../store/reblogs/types'; +import {UI, ToggleType} from '../../store/ui/types'; -import EntryListItem from "../entry-list-item/index"; -import {EntryPinTracker} from "../../store/entry-pin-tracker/types"; -import MessageNoData from "../message-no-data"; -import { _t } from "../../i18n"; -import LinearProgress from "../linear-progress"; -import { getFollowing } from "../../api/hive"; +import EntryListItem from '../entry-list-item/index'; +import {EntryPinTracker} from '../../store/entry-pin-tracker/types'; +import MessageNoData from '../message-no-data'; +import {_t} from '../../i18n'; +import LinearProgress from '../linear-progress'; +import {getFollowing} from '../../api/hive'; import isCommunity from '../../helper/is-community'; - interface Props { - history: History; - location: Location; - global: Global; - dynamicProps: DynamicProps; - entries: Entry[]; - promotedEntries: Entry[]; - communities: Communities; - community?: Community | null; - users: User[]; - activeUser: ActiveUser | null; - reblogs: Reblogs; - loading: boolean; - ui: UI; - entryPinTracker: EntryPinTracker; - signingKey: string; - addAccount: (data: Account) => void; - updateEntry: (entry: Entry) => void; - setActiveUser: (username: string | null) => void; - updateActiveUser: (data?: Account) => void; - deleteUser: (username: string) => void; - fetchReblogs: () => void; - addReblog: (author: string, permlink: string) => void; - deleteReblog: (author: string, permlink: string) => void; - toggleUIProp: (what: ToggleType) => void; - addCommunity: (data: Community) => void; - trackEntryPin: (entry: Entry) => void; - setSigningKey: (key: string) => void; - setEntryPin: (entry: Entry, pin: boolean) => void; + history: History; + location: Location; + global: Global; + dynamicProps: DynamicProps; + entries: Entry[]; + promotedEntries: Entry[]; + communities: Communities; + community?: Community | null; + users: User[]; + activeUser: ActiveUser | null; + reblogs: Reblogs; + loading: boolean; + ui: UI; + entryPinTracker: EntryPinTracker; + signingKey: string; + addAccount: (data: Account) => void; + updateEntry: (entry: Entry) => void; + setActiveUser: (username: string | null) => void; + updateActiveUser: (data?: Account) => void; + deleteUser: (username: string) => void; + fetchReblogs: () => void; + addReblog: (author: string, permlink: string) => void; + deleteReblog: (author: string, permlink: string) => void; + toggleUIProp: (what: ToggleType) => void; + addCommunity: (data: Community) => void; + trackEntryPin: (entry: Entry) => void; + setSigningKey: (key: string) => void; + setEntryPin: (entry: Entry, pin: boolean) => void; } interface State { - mutedUsers: string[]; - loadingMutedUsers: boolean; + mutedUsers: string[]; + loadingMutedUsers: boolean; } export class EntryListContent extends Component { - state = { - mutedUsers: [] as string[], - loadingMutedUsers: false, - } + state = { + mutedUsers: [] as string[], + loadingMutedUsers: false, + }; - fetchMutedUsers = () => { - const { activeUser } = this.props; - const { loadingMutedUsers } = this.state; - if(!loadingMutedUsers){ - if(activeUser){ - this.setState({ loadingMutedUsers: true }); - getFollowing(activeUser.username, "", "ignore", 100).then(r => { - if (r) { - let filterList = r.map(user => user.following); - this.setState({mutedUsers: filterList }) - } - }).finally(()=>{ - this.setState({ loadingMutedUsers: false }) - }) - } - } + fetchMutedUsers = () => { + const {activeUser} = this.props; + const {loadingMutedUsers} = this.state; + if (!loadingMutedUsers) { + if (activeUser) { + this.setState({loadingMutedUsers: true}); + getFollowing(activeUser.username, '', 'ignore', 100) + .then(r => { + if (r) { + let filterList = r.map(user => user.following); + this.setState({mutedUsers: filterList}); + } + }) + .finally(() => { + this.setState({loadingMutedUsers: false}); + }); + } } + }; - componentDidUpdate(prevProps:Props){ - if(prevProps.activeUser?.username !== this.props.activeUser?.username){ - this.fetchMutedUsers() - } - if(prevProps.activeUser !== this.props.activeUser && !this.props.activeUser){ - this.setState({mutedUsers:[]}) - } + componentDidUpdate(prevProps: Props) { + if (prevProps.activeUser?.username !== this.props.activeUser?.username) { + this.fetchMutedUsers(); } + if ( + prevProps.activeUser !== this.props.activeUser && + !this.props.activeUser + ) { + this.setState({mutedUsers: []}); + } + } + + componentDidMount() { + this.fetchMutedUsers(); + } - componentDidMount(){ - this.fetchMutedUsers(); + render() { + const {entries, promotedEntries, global, activeUser, loading} = this.props; + const {filter, tag} = global; + const {mutedUsers, loadingMutedUsers} = this.state; + let dataToRender = entries; + + let mutedList: string[] = []; + if ( + mutedUsers && + mutedUsers.length > 0 && + activeUser && + activeUser.username + ) { + mutedList = mutedList.concat(mutedUsers); } + const isMyProfile = + activeUser && + tag.includes('@') && + activeUser.username === tag.replace('@', ''); + return ( + <> + {loadingMutedUsers ? ( + + ) : dataToRender.length > 0 ? ( + <> + {dataToRender.map((e, i) => { + const l = []; - render() { - const {entries, promotedEntries, global, activeUser, loading } = this.props; - const {filter, tag} = global; - const { mutedUsers, loadingMutedUsers } = this.state; - let dataToRender = entries; + if (i % 4 === 0 && i > 0) { + const ix = i / 4 - 1; - let mutedList: string[] = []; - if(mutedUsers && mutedUsers.length > 0 && activeUser && activeUser.username){ - mutedList = mutedList.concat(mutedUsers) - } - const isMyProfile = activeUser && tag.includes('@') && activeUser.username === tag.replace("@",''); - return ( - <> - { - loadingMutedUsers ? : dataToRender.length > 0 ? ( - <> - {dataToRender.map((e, i) => { - const l = []; - - if (i % 4 === 0 && i > 0) { - const ix = i / 4 - 1; - - if (promotedEntries[ix]) { - const p = promotedEntries[ix]; - let isPostMuted = (activeUser && activeUser.username && mutedList.includes(p.author)) || false; - if (!dataToRender.find(x => x.author === p.author && x.permlink === p.permlink)) { - l.push( - - ); - } - } - } - - let isPostMuted = (activeUser && activeUser.username && mutedList.includes(e.author)) || false; - l.push(); - return [...l]; - })} - - ) : !loading && (isMyProfile) ? - : (isCommunity(tag) ? : (tag == 'my' ? : - ) - ) + if (promotedEntries[ix]) { + const p = promotedEntries[ix]; + let isPostMuted = + (activeUser && + activeUser.username && + mutedList.includes(p.author)) || + false; + if ( + !dataToRender.find( + x => x.author === p.author && x.permlink === p.permlink, + ) + ) { + l.push( + , + ); + } } - - ); - } + } + + let isPostMuted = + (activeUser && + activeUser.username && + mutedList.includes(e.author)) || + false; + l.push( + , + ); + return [...l]; + })} + + ) : !loading && isMyProfile ? ( + + ) : isCommunity(tag) ? ( + + ) : tag == 'my' ? ( + + ) : ( + + )} + + ); + } } export default (p: Props) => { - const props: Props = { - history: p.history, - location: p.location, - global: p.global, - dynamicProps: p.dynamicProps, - entries: p.entries, - promotedEntries: p.promotedEntries, - communities: p.communities, - community: p.community, - users: p.users, - activeUser: p.activeUser, - reblogs: p.reblogs, - ui: p.ui, - entryPinTracker: p.entryPinTracker, - signingKey: p.signingKey, - addAccount: p.addAccount, - updateEntry: p.updateEntry, - setActiveUser: p.setActiveUser, - updateActiveUser: p.updateActiveUser, - deleteUser: p.deleteUser, - fetchReblogs: p.fetchReblogs, - addReblog: p.addReblog, - deleteReblog: p.deleteReblog, - toggleUIProp: p.toggleUIProp, - addCommunity: p.addCommunity, - trackEntryPin: p.trackEntryPin, - setSigningKey: p.setSigningKey, - setEntryPin: p.setEntryPin, - loading: p.loading - } + const props: Props = { + history: p.history, + location: p.location, + global: p.global, + dynamicProps: p.dynamicProps, + entries: p.entries, + promotedEntries: p.promotedEntries, + communities: p.communities, + community: p.community, + users: p.users, + activeUser: p.activeUser, + reblogs: p.reblogs, + ui: p.ui, + entryPinTracker: p.entryPinTracker, + signingKey: p.signingKey, + addAccount: p.addAccount, + updateEntry: p.updateEntry, + setActiveUser: p.setActiveUser, + updateActiveUser: p.updateActiveUser, + deleteUser: p.deleteUser, + fetchReblogs: p.fetchReblogs, + addReblog: p.addReblog, + deleteReblog: p.deleteReblog, + toggleUIProp: p.toggleUIProp, + addCommunity: p.addCommunity, + trackEntryPin: p.trackEntryPin, + setSigningKey: p.setSigningKey, + setEntryPin: p.setEntryPin, + loading: p.loading, + }; - return ; -} + return ; +}; diff --git a/src/common/components/entry-menu/index.spec.tsx b/src/common/components/entry-menu/index.spec.tsx index 63822070bb9..ca5a691302e 100644 --- a/src/common/components/entry-menu/index.spec.tsx +++ b/src/common/components/entry-menu/index.spec.tsx @@ -1,51 +1,46 @@ -import React from "react"; +import React from 'react'; -import {createBrowserHistory} from "history"; +import {createBrowserHistory} from 'history'; -import {EntryMenu} from "./index"; +import {EntryMenu} from './index'; -import TestRenderer from "react-test-renderer"; +import TestRenderer from 'react-test-renderer'; -import {entryInstance1, globalInstance, dynamicPropsIntance1} from "../../helper/test-helper"; +import { + entryInstance1, + globalInstance, + dynamicPropsIntance1, +} from '../../helper/test-helper'; const defProps = { - history: createBrowserHistory(), - global: globalInstance, - dynamicProps: dynamicPropsIntance1, - activeUser: null, - entry: entryInstance1, - communities: [], - entryPinTracker: {}, - signingKey: "", - setSigningKey: () => { - }, - updateActiveUser: () => { - }, - updateEntry: () => { - }, - addCommunity: () => { - }, - trackEntryPin: () => { - }, - setEntryPin: () => { - }, - toggleUIProp: () => { - }, + history: createBrowserHistory(), + global: globalInstance, + dynamicProps: dynamicPropsIntance1, + activeUser: null, + entry: entryInstance1, + communities: [], + entryPinTracker: {}, + signingKey: '', + setSigningKey: () => {}, + updateActiveUser: () => {}, + updateEntry: () => {}, + addCommunity: () => {}, + trackEntryPin: () => {}, + setEntryPin: () => {}, + toggleUIProp: () => {}, }; - -it("(1) Default render", () => { - const props = {...defProps}; - const renderer = TestRenderer.create(); - expect(renderer.toJSON()).toMatchSnapshot(); +it('(1) Default render', () => { + const props = {...defProps}; + const renderer = TestRenderer.create(); + expect(renderer.toJSON()).toMatchSnapshot(); }); -it("(2) Separated sharing buttons", () => { - const props = { - ...defProps, - separatedSharing: true - }; - const renderer = TestRenderer.create(); - expect(renderer.toJSON()).toMatchSnapshot(); +it('(2) Separated sharing buttons', () => { + const props = { + ...defProps, + separatedSharing: true, + }; + const renderer = TestRenderer.create(); + expect(renderer.toJSON()).toMatchSnapshot(); }); - diff --git a/src/common/components/entry-menu/index.tsx b/src/common/components/entry-menu/index.tsx index 19aa4d306e6..93a6fdae2d9 100644 --- a/src/common/components/entry-menu/index.tsx +++ b/src/common/components/entry-menu/index.tsx @@ -1,489 +1,606 @@ -import React from "react"; +import React from 'react'; -import {History} from "history"; +import {History} from 'history'; -import BaseComponent from "../base"; +import BaseComponent from '../base'; -import {ActiveUser} from "../../store/active-user/types"; -import {Entry, EntryStat} from "../../store/entries/types"; -import {Communities, Community, ROLES} from "../../store/communities/types"; -import {EntryPinTracker} from "../../store/entry-pin-tracker/types"; -import {Global} from "../../store/global/types"; -import {Account} from "../../store/accounts/types"; -import {DynamicProps} from "../../store/dynamic-props/types"; -import {ToggleType} from "../../store/ui/types"; +import {ActiveUser} from '../../store/active-user/types'; +import {Entry, EntryStat} from '../../store/entries/types'; +import {Communities, Community, ROLES} from '../../store/communities/types'; +import {EntryPinTracker} from '../../store/entry-pin-tracker/types'; +import {Global} from '../../store/global/types'; +import {Account} from '../../store/accounts/types'; +import {DynamicProps} from '../../store/dynamic-props/types'; +import {ToggleType} from '../../store/ui/types'; -import {clone} from "../../store/util"; +import {clone} from '../../store/util'; -import EditHistory from "../edit-history"; -import EntryShare, {shareReddit, shareTwitter, shareFacebook} from "../entry-share"; -import MuteBtn from "../mute-btn"; -import Promote from "../promote"; -import Boost from "../boost"; -import ModalConfirm from "../modal-confirm"; -import {error, success} from "../feedback"; -import DropDown, {MenuItem} from "../dropdown"; -import CrossPost from "../cross-post"; +import EditHistory from '../edit-history'; +import EntryShare, { + shareReddit, + shareTwitter, + shareFacebook, +} from '../entry-share'; +import MuteBtn from '../mute-btn'; +import Promote from '../promote'; +import Boost from '../boost'; +import ModalConfirm from '../modal-confirm'; +import {error, success} from '../feedback'; +import DropDown, {MenuItem} from '../dropdown'; +import CrossPost from '../cross-post'; -import isCommunity from "../../helper/is-community"; +import isCommunity from '../../helper/is-community'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import clipboard from "../../util/clipboard"; +import clipboard from '../../util/clipboard'; -import {deleteComment, formatError, pinPost} from "../../api/operations"; +import {deleteComment, formatError, pinPost} from '../../api/operations'; -import * as bridgeApi from "../../api/bridge"; +import * as bridgeApi from '../../api/bridge'; import { - dotsHorizontal, deleteForeverSvg, - pencilOutlineSvg, pinSvg, historySvg, shareVariantSvg, linkVariantSvg, - volumeOffSvg, redditSvg, twitterSvg, facebookSvg, bullHornSvg, rocketLaunchSvg, - shuffleVariantSvg -} from "../../img/svg"; + dotsHorizontal, + deleteForeverSvg, + pencilOutlineSvg, + pinSvg, + historySvg, + shareVariantSvg, + linkVariantSvg, + volumeOffSvg, + redditSvg, + twitterSvg, + facebookSvg, + bullHornSvg, + rocketLaunchSvg, + shuffleVariantSvg, +} from '../../img/svg'; interface Props { - history: History; - global: Global; - dynamicProps: DynamicProps; - activeUser: ActiveUser | null; - entry: Entry; - extraMenuItems?: any[]; - communities: Communities; - entryPinTracker: EntryPinTracker; - separatedSharing?: boolean; - alignBottom?: boolean, - signingKey: string; - setSigningKey: (key: string) => void; - updateActiveUser: (data?: Account) => void; - updateEntry: (entry: Entry) => void; - addCommunity: (data: Community) => void; - trackEntryPin: (entry: Entry) => void; - setEntryPin: (entry: Entry, pin: boolean) => void; - toggleUIProp: (what: ToggleType) => void; + history: History; + global: Global; + dynamicProps: DynamicProps; + activeUser: ActiveUser | null; + entry: Entry; + extraMenuItems?: any[]; + communities: Communities; + entryPinTracker: EntryPinTracker; + separatedSharing?: boolean; + alignBottom?: boolean; + signingKey: string; + setSigningKey: (key: string) => void; + updateActiveUser: (data?: Account) => void; + updateEntry: (entry: Entry) => void; + addCommunity: (data: Community) => void; + trackEntryPin: (entry: Entry) => void; + setEntryPin: (entry: Entry, pin: boolean) => void; + toggleUIProp: (what: ToggleType) => void; } interface State { - cross: boolean; - share: boolean; - editHistory: boolean; - delete_: boolean; - pin: boolean; - unpin: boolean; - mute: boolean; - promote: boolean; - boost: boolean; + cross: boolean; + share: boolean; + editHistory: boolean; + delete_: boolean; + pin: boolean; + unpin: boolean; + mute: boolean; + promote: boolean; + boost: boolean; } export class EntryMenu extends BaseComponent { - state: State = { - cross: false, - share: false, - editHistory: false, - delete_: false, - pin: false, - unpin: false, - mute: false, - promote: false, - boost: false, - } + state: State = { + cross: false, + share: false, + editHistory: false, + delete_: false, + pin: false, + unpin: false, + mute: false, + promote: false, + boost: false, + }; + + toggleCross = () => { + const {cross} = this.state; + this.stateSet({cross: !cross}); + }; + + toggleShare = () => { + const {share} = this.state; + this.stateSet({share: !share}); + }; + + toggleEditHistory = () => { + const {editHistory} = this.state; + this.stateSet({editHistory: !editHistory}); + }; + + toggleDelete = () => { + const {delete_} = this.state; + this.stateSet({delete_: !delete_}); + }; + + togglePin = () => { + const {pin} = this.state; + this.stateSet({pin: !pin}); + }; + + toggleUnpin = () => { + const {unpin} = this.state; + this.stateSet({unpin: !unpin}); + }; + + toggleMute = () => { + const {mute} = this.state; + this.stateSet({mute: !mute}); + }; + + togglePromote = () => { + const {promote} = this.state; + this.stateSet({promote: !promote}); + }; + + toggleBoost = () => { + const {boost} = this.state; + this.stateSet({boost: !boost}); + }; + + getCommunity = (): Community | null => { + const {communities, entry} = this.props; + + return communities.find(x => x.name === entry.category) || null; + }; + + canPinOrMute = () => { + const {activeUser} = this.props; + + const community = this.getCommunity(); + + return activeUser && community + ? !!community.team.find(m => { + return ( + m[0] === activeUser.username && + [ + ROLES.OWNER.toString(), + ROLES.ADMIN.toString(), + ROLES.MOD.toString(), + ].includes(m[1]) + ); + }) + : false; + }; + + copyAddress = () => { + const {entry} = this.props; + + const u = `https://ecency.com/${entry.category}/@${entry.author}/${entry.permlink}`; + clipboard(u); + success(_t('entry.address-copied')); + }; + + edit = () => { + const {entry, history} = this.props; + + const u = `/@${entry.author}/${entry.permlink}/edit`; + history.push(u); + }; + + delete = () => { + const {history, activeUser, entry} = this.props; + deleteComment(activeUser!.username, entry.author, entry.permlink) + .then(() => { + history.push('/'); + }) + .catch(e => { + error(formatError(e)); + }); + }; + + pin = (pin: boolean) => { + const {entry, activeUser, setEntryPin, updateEntry} = this.props; + + const community = this.getCommunity(); + + pinPost( + activeUser!.username, + community!.name, + entry.author, + entry.permlink, + pin, + ) + .then(() => { + setEntryPin(entry, pin); + + // Update the entry in store + const nStats: EntryStat = {...clone(entry.stats), is_pinned: pin}; + const nEntry: Entry = {...clone(entry), stats: nStats}; + updateEntry(nEntry); + + if (pin) { + success(_t('entry-menu.pin-success')); + } else { + success(_t('entry-menu.unpin-success')); + } + }) + .catch(err => { + error(formatError(err)); + }); + }; - toggleCross = () => { - const {cross} = this.state; - this.stateSet({cross: !cross}); - } + onMenuShow = () => { + const {activeUser} = this.props; - toggleShare = () => { - const {share} = this.state; - this.stateSet({share: !share}); + if (!activeUser) { + return; } - toggleEditHistory = () => { - const {editHistory} = this.state; - this.stateSet({editHistory: !editHistory}); - } + const {trackEntryPin, entry} = this.props; + trackEntryPin(entry); - toggleDelete = () => { - const {delete_} = this.state; - this.stateSet({delete_: !delete_}); + if (this.getCommunity()) { + return; } - togglePin = () => { - const {pin} = this.state; - this.stateSet({pin: !pin}); - } + const {addCommunity} = this.props; - toggleUnpin = () => { - const {unpin} = this.state; - this.stateSet({unpin: !unpin}); + if (isCommunity(entry.category)) { + bridgeApi.getCommunity(entry.category, activeUser.username).then(r => { + if (r) { + addCommunity(r); + } + }); } - - toggleMute = () => { - const {mute} = this.state; - this.stateSet({mute: !mute}); + }; + + toggleLoginModal = () => { + this.props.toggleUIProp('login'); + }; + + render() { + const { + global, + activeUser, + entry, + entryPinTracker, + alignBottom, + separatedSharing, + extraMenuItems, + } = this.props; + + const isComment = !!entry.parent_author; + + const ownEntry = activeUser && activeUser.username === entry.author; + + const editable = ownEntry && !isComment; + const deletable = + ownEntry && + !(entry.children > 0 || entry.net_rshares > 0 || entry.is_paidout); + + let menuItems: MenuItem[] = []; + + if (activeUser && !isComment) { + menuItems = [ + { + label: _t('entry-menu.cross-post'), + onClick: this.toggleCross, + icon: shuffleVariantSvg, + }, + ]; } - togglePromote = () => { - const {promote} = this.state; - this.stateSet({promote: !promote}); + if (!separatedSharing) { + menuItems = [ + ...menuItems, + { + label: _t('entry-menu.share'), + onClick: this.toggleShare, + icon: shareVariantSvg, + }, + ]; } - toggleBoost = () => { - const {boost} = this.state; - this.stateSet({boost: !boost}); + if (global.usePrivate) { + menuItems = [ + ...menuItems, + { + label: _t('entry-menu.edit-history'), + onClick: this.toggleEditHistory, + icon: historySvg, + }, + ]; } - getCommunity = (): Community | null => { - const {communities, entry} = this.props; - - return communities.find((x) => x.name === entry.category) || null + if (editable) { + menuItems = [ + ...menuItems, + ...[ + { + label: _t('g.edit'), + onClick: this.edit, + icon: pencilOutlineSvg, + }, + ], + ]; } - canPinOrMute = () => { - const {activeUser} = this.props; - - const community = this.getCommunity(); - - return activeUser && community ? !!community.team.find(m => { - return m[0] === activeUser.username && - [ROLES.OWNER.toString(), ROLES.ADMIN.toString(), ROLES.MOD.toString()].includes(m[1]) - }) : false; - } - - copyAddress = () => { - const {entry} = this.props; - - const u = `https://ecency.com/${entry.category}/@${entry.author}/${entry.permlink}` - clipboard(u); - success(_t("entry.address-copied")); - }; - - edit = () => { - const {entry, history} = this.props; - - const u = `/@${entry.author}/${entry.permlink}/edit`; - history.push(u); + if (deletable) { + menuItems = [ + ...menuItems, + ...[ + { + label: _t('g.delete'), + onClick: this.toggleDelete, + icon: deleteForeverSvg, + }, + ], + ]; } - delete = () => { - const {history, activeUser, entry} = this.props; - deleteComment(activeUser!.username, entry.author, entry.permlink) - .then(() => { - history.push('/'); - }) - .catch((e) => { - error(formatError(e)); - }) + if (this.canPinOrMute()) { + if (entryPinTracker[`${entry.author}-${entry.permlink}`]) { + menuItems = [ + ...menuItems, + { + label: _t('entry-menu.unpin'), + onClick: this.toggleUnpin, + icon: pinSvg, + }, + ]; + } else { + menuItems = [ + ...menuItems, + { + label: _t('entry-menu.pin'), + onClick: this.togglePin, + icon: pinSvg, + }, + ]; + } + + const isMuted = !!entry.stats?.gray; + menuItems = [ + ...menuItems, + ...[ + { + label: isMuted ? _t('entry-menu.unmute') : _t('entry-menu.mute'), + onClick: this.toggleMute, + icon: volumeOffSvg, + }, + ], + ]; } - pin = (pin: boolean) => { - const {entry, activeUser, setEntryPin, updateEntry} = this.props; - - const community = this.getCommunity(); - - pinPost(activeUser!.username, community!.name, entry.author, entry.permlink, pin) - .then(() => { - setEntryPin(entry, pin); - - // Update the entry in store - const nStats: EntryStat = {...clone(entry.stats), is_pinned: pin} - const nEntry: Entry = {...clone(entry), stats: nStats}; - updateEntry(nEntry); - - if (pin) { - success(_t("entry-menu.pin-success")); - } else { - success(_t("entry-menu.unpin-success")); - } - - }) - .catch(err => { - error(formatError(err)); - }) + if (global.usePrivate) { + menuItems = [ + ...menuItems, + ...[ + { + label: _t('entry-menu.promote'), + onClick: + activeUser !== null ? this.togglePromote : this.toggleLoginModal, + icon: bullHornSvg, + }, + { + label: _t('entry-menu.boost'), + onClick: + activeUser !== null ? this.toggleBoost : this.toggleLoginModal, + icon: rocketLaunchSvg, + }, + ], + ]; } - onMenuShow = () => { - const {activeUser} = this.props; - - if (!activeUser) { - return; - } - - const {trackEntryPin, entry} = this.props; - trackEntryPin(entry); - - if (this.getCommunity()) { - return; - } - - const {addCommunity} = this.props; - - if (isCommunity(entry.category)) { - bridgeApi.getCommunity(entry.category, activeUser.username).then(r => { - if (r) { - addCommunity(r); - } - }) - } + if (global.isElectron) { + menuItems = [ + ...menuItems, + { + label: _t('entry.address-copy'), + onClick: this.copyAddress, + icon: linkVariantSvg, + }, + ]; } - toggleLoginModal = () => { - this.props.toggleUIProp("login") + if (extraMenuItems) { + menuItems = [...menuItems, ...extraMenuItems]; } - render() { - const {global, activeUser, entry, entryPinTracker, alignBottom, separatedSharing, extraMenuItems} = this.props; - - const isComment = !!entry.parent_author; - - const ownEntry = activeUser && activeUser.username === entry.author; - - const editable = ownEntry && !isComment; - const deletable = ownEntry && !(entry.children > 0 || entry.net_rshares > 0 || entry.is_paidout); - - let menuItems: MenuItem[] = []; - - if (activeUser && !isComment) { - menuItems = [ - { - label: _t("entry-menu.cross-post"), - onClick: this.toggleCross, - icon: shuffleVariantSvg - } - ] - } - - - if (!separatedSharing) { - menuItems = [ - ...menuItems, - { - label: _t("entry-menu.share"), - onClick: this.toggleShare, - icon: shareVariantSvg - } - ] - } - - if (global.usePrivate) { - menuItems = [ - ...menuItems, - { - label: _t("entry-menu.edit-history"), - onClick: this.toggleEditHistory, - icon: historySvg - } - ]; + if (menuItems) { + let deleteItems = menuItems.filter(item => item.label === _t('g.delete')); + if (deleteItems.length === 1) { + let items = menuItems.filter(item => item.label !== ''); + menuItems = items; + } + let updatedItems: MenuItem[] = []; + menuItems.forEach(item => { + if ( + item.label === _t('entry-menu.promote') || + item.label === _t('entry-menu.boost') || + item.label === _t('entry-menu.pin') || + item.label === _t('entry-menu.unpin') + ) { + updatedItems.unshift(item); + } else { + updatedItems.push(item); } + }); + menuItems = updatedItems; + } - if (editable) { - menuItems = [...menuItems, - ...[ - { - label: _t("g.edit"), - onClick: this.edit, - icon: pencilOutlineSvg - } - ] - ]; - } - - if (deletable) { - menuItems = [...menuItems, - ...[ - { - label: _t("g.delete"), - onClick: this.toggleDelete, - icon: deleteForeverSvg - } - ] - ]; - } - - if (this.canPinOrMute()) { - if (entryPinTracker[`${entry.author}-${entry.permlink}`]) { - menuItems = [...menuItems, { - label: _t("entry-menu.unpin"), - onClick: this.toggleUnpin, - icon: pinSvg - }]; - } else { - menuItems = [...menuItems, { - label: _t("entry-menu.pin"), - onClick: this.togglePin, - icon: pinSvg - }]; - } - - const isMuted = !!entry.stats?.gray; - menuItems = [ - ...menuItems, - ...[ - { - label: (isMuted ? _t("entry-menu.unmute") : _t("entry-menu.mute")), - onClick: this.toggleMute, - icon: volumeOffSvg - } - ] - ]; - } - - if (global.usePrivate) { - menuItems = [ - ...menuItems, - ...[ - { - label: _t("entry-menu.promote"), - onClick: activeUser !== null ? this.togglePromote : this.toggleLoginModal, - icon: bullHornSvg - }, - { - label: _t("entry-menu.boost"), - onClick: activeUser !== null ? this.toggleBoost : this.toggleLoginModal, - icon: rocketLaunchSvg - } - ] - ]; - } - - if (global.isElectron) { - menuItems = [ - ...menuItems, - { - label: _t("entry.address-copy"), - onClick: this.copyAddress, - icon: linkVariantSvg - } - ] - } - - if(extraMenuItems){ - menuItems = [ - ...menuItems, - ...extraMenuItems - ] - } - - if(menuItems){ - let deleteItems = menuItems.filter(item=>item.label===_t("g.delete")); - if(deleteItems.length === 1){ - let items = menuItems.filter(item=> item.label !== "" ); - menuItems = items; - } - let updatedItems: MenuItem[] = []; - menuItems.forEach(item=> { - if(item.label === _t("entry-menu.promote") || item.label === _t("entry-menu.boost") || item.label === _t("entry-menu.pin") || item.label === _t("entry-menu.unpin")){ - updatedItems.unshift(item) - } - else { - updatedItems.push(item) - } - }); - menuItems = updatedItems; - - } + const menuConfig = { + history: this.props.history, + label: '', + icon: dotsHorizontal, + items: menuItems, + }; - - const menuConfig = { - history: this.props.history, - label: '', - icon: dotsHorizontal, - items: menuItems - }; - - const {cross, share, editHistory, delete_, pin, unpin, mute, promote, boost} = this.state; - const community = this.getCommunity(); - - return
- {separatedSharing && ( -
-
{shareVariantSvg}
-
-
{ - shareReddit(entry); - }}>{redditSvg}
-
{ - shareTwitter(entry); - }}>{twitterSvg}
-
{ - shareFacebook(entry); - }}>{facebookSvg}
-
-
- )} - - - {(activeUser && cross) && { - this.toggleCross(); - - const {history} = this.props; - history.push(`/created/${community}`); - }}/>} - {share && } - {editHistory && } - {delete_ && { - this.delete(); - this.toggleDelete(); - }} onCancel={this.toggleDelete}/>} - {pin && { - this.pin(true); - this.togglePin(); - }} onCancel={this.togglePin}/>} - {unpin && { - this.pin(false); - this.toggleUnpin(); - }} onCancel={this.toggleUnpin}/>} - {(community && activeUser && mute) && MuteBtn({ - community, - entry, - activeUser: activeUser, - onlyDialog: true, - onSuccess: (entry, mute) => { - const {updateEntry} = this.props; - updateEntry(entry); - this.toggleMute(); - - if (pin) { - success(_t("entry-menu.mute-success")); - } else { - success(_t("entry-menu.unmute-success")); - } - }, - onCancel: this.toggleMute - })} - {(activeUser && promote) && ( - - )} - {(activeUser && boost) && ( - - )} -
; - } + const { + cross, + share, + editHistory, + delete_, + pin, + unpin, + mute, + promote, + boost, + } = this.state; + const community = this.getCommunity(); + + return ( +
+ {separatedSharing && ( +
+
+ {shareVariantSvg} +
+
+
{ + shareReddit(entry); + }} + > + {redditSvg} +
+
{ + shareTwitter(entry); + }} + > + {twitterSvg} +
+
{ + shareFacebook(entry); + }} + > + {facebookSvg} +
+
+
+ )} + + + {activeUser && cross && ( + { + this.toggleCross(); + + const {history} = this.props; + history.push(`/created/${community}`); + }} + /> + )} + {share && } + {editHistory && ( + + )} + {delete_ && ( + { + this.delete(); + this.toggleDelete(); + }} + onCancel={this.toggleDelete} + /> + )} + {pin && ( + { + this.pin(true); + this.togglePin(); + }} + onCancel={this.togglePin} + /> + )} + {unpin && ( + { + this.pin(false); + this.toggleUnpin(); + }} + onCancel={this.toggleUnpin} + /> + )} + {community && + activeUser && + mute && + MuteBtn({ + community, + entry, + activeUser: activeUser, + onlyDialog: true, + onSuccess: (entry, mute) => { + const {updateEntry} = this.props; + updateEntry(entry); + this.toggleMute(); + + if (pin) { + success(_t('entry-menu.mute-success')); + } else { + success(_t('entry-menu.unmute-success')); + } + }, + onCancel: this.toggleMute, + })} + {activeUser && promote && ( + + )} + {activeUser && boost && ( + + )} +
+ ); + } } - export default (p: Props) => { - const props: Props = { - history: p.history, - global: p.global, - dynamicProps: p.dynamicProps, - activeUser: p.activeUser, - entry: p.entry, - communities: p.communities, - entryPinTracker: p.entryPinTracker, - separatedSharing: p.separatedSharing, - alignBottom: p.alignBottom, - signingKey: p.signingKey, - extraMenuItems: p.extraMenuItems, - setSigningKey: p.setSigningKey, - updateActiveUser: p.updateActiveUser, - updateEntry: p.updateEntry, - addCommunity: p.addCommunity, - trackEntryPin: p.trackEntryPin, - setEntryPin: p.setEntryPin, - toggleUIProp: p.toggleUIProp - } - - return -} + const props: Props = { + history: p.history, + global: p.global, + dynamicProps: p.dynamicProps, + activeUser: p.activeUser, + entry: p.entry, + communities: p.communities, + entryPinTracker: p.entryPinTracker, + separatedSharing: p.separatedSharing, + alignBottom: p.alignBottom, + signingKey: p.signingKey, + extraMenuItems: p.extraMenuItems, + setSigningKey: p.setSigningKey, + updateActiveUser: p.updateActiveUser, + updateEntry: p.updateEntry, + addCommunity: p.addCommunity, + trackEntryPin: p.trackEntryPin, + setEntryPin: p.setEntryPin, + toggleUIProp: p.toggleUIProp, + }; + + return ; +}; diff --git a/src/common/components/entry-payout/index.spec.tsx b/src/common/components/entry-payout/index.spec.tsx index c3cfcef2d13..c7aa7aa4804 100644 --- a/src/common/components/entry-payout/index.spec.tsx +++ b/src/common/components/entry-payout/index.spec.tsx @@ -1,120 +1,125 @@ -import React from "react"; +import React from 'react'; -import EntryPayout, {EntryPayoutDetail} from "./index"; -import TestRenderer from "react-test-renderer"; +import EntryPayout, {EntryPayoutDetail} from './index'; +import TestRenderer from 'react-test-renderer'; -import {globalInstance, entryInstance1, dynamicPropsIntance1, allOver} from "../../helper/test-helper"; +import { + globalInstance, + entryInstance1, + dynamicPropsIntance1, + allOver, +} from '../../helper/test-helper'; -jest.mock("moment", () => () => ({ - fromNow: () => "in 4 days", - format: (f: string, s: string) => "2020-01-01 23:12:00", +jest.mock('moment', () => () => ({ + fromNow: () => 'in 4 days', + format: (f: string, s: string) => '2020-01-01 23:12:00', })); -it("(1) Default render", async() => { - const props = { - global: {...globalInstance}, - dynamicProps: {...dynamicPropsIntance1}, - entry: { - ...entryInstance1, - ...{ - pending_payout_value: "14.264 HBD", - promoted: "0.000 HBD", - author_payout_value: "0.000 HBD", - curator_payout_value: "0.000 HBD", - payout_at: "2020-06-03T15:15:24", - }, - }, - }; +it('(1) Default render', async () => { + const props = { + global: {...globalInstance}, + dynamicProps: {...dynamicPropsIntance1}, + entry: { + ...entryInstance1, + ...{ + pending_payout_value: '14.264 HBD', + promoted: '0.000 HBD', + author_payout_value: '0.000 HBD', + curator_payout_value: '0.000 HBD', + payout_at: '2020-06-03T15:15:24', + }, + }, + }; - const renderer = await TestRenderer.create(); - await allOver(); - expect(renderer.toJSON()).toMatchSnapshot(); + const renderer = await TestRenderer.create(); + await allOver(); + expect(renderer.toJSON()).toMatchSnapshot(); }); -it("(2) Detail render", async() => { - const props = { - global: {...globalInstance}, - dynamicProps: {...dynamicPropsIntance1}, - entry: { - ...entryInstance1, - ...{ - pending_payout_value: "14.264 HBD", - promoted: "0.000 HBD", - author_payout_value: "0.000 HBD", - curator_payout_value: "0.000 HBD", - payout_at: "2020-06-03T15:15:24", - }, - }, - }; +it('(2) Detail render', async () => { + const props = { + global: {...globalInstance}, + dynamicProps: {...dynamicPropsIntance1}, + entry: { + ...entryInstance1, + ...{ + pending_payout_value: '14.264 HBD', + promoted: '0.000 HBD', + author_payout_value: '0.000 HBD', + curator_payout_value: '0.000 HBD', + payout_at: '2020-06-03T15:15:24', + }, + }, + }; - const renderer = await TestRenderer.create(); - await allOver(); - expect(renderer.toJSON()).toMatchSnapshot(); + const renderer = await TestRenderer.create(); + await allOver(); + expect(renderer.toJSON()).toMatchSnapshot(); }); -it("(3) Detail render with full power", async() => { - const props = { - global: {...globalInstance}, - dynamicProps: {...dynamicPropsIntance1}, - entry: { - ...entryInstance1, - ...{ - percent_hbd: 0, - pending_payout_value: "14.264 HBD", - promoted: "0.000 HBD", - author_payout_value: "0.000 HBD", - curator_payout_value: "0.000 HBD", - payout_at: "2020-06-03T15:15:24", - }, - }, - }; +it('(3) Detail render with full power', async () => { + const props = { + global: {...globalInstance}, + dynamicProps: {...dynamicPropsIntance1}, + entry: { + ...entryInstance1, + ...{ + percent_hbd: 0, + pending_payout_value: '14.264 HBD', + promoted: '0.000 HBD', + author_payout_value: '0.000 HBD', + curator_payout_value: '0.000 HBD', + payout_at: '2020-06-03T15:15:24', + }, + }, + }; - const renderer = await TestRenderer.create(); - await allOver(); - expect(renderer.toJSON()).toMatchSnapshot(); + const renderer = await TestRenderer.create(); + await allOver(); + expect(renderer.toJSON()).toMatchSnapshot(); }); -it("(4) Detail render with max payout", async() => { - const props = { - global: {...globalInstance}, - dynamicProps: {...dynamicPropsIntance1}, - entry: { - ...entryInstance1, - ...{ - percent_hbd: 0, - pending_payout_value: "14.264 HBD", - promoted: "0.000 HBD", - author_payout_value: "0.000 HBD", - curator_payout_value: "0.000 HBD", - payout_at: "2020-06-03T15:15:24", - max_accepted_payout: "10.000 HBD" - }, - }, - }; +it('(4) Detail render with max payout', async () => { + const props = { + global: {...globalInstance}, + dynamicProps: {...dynamicPropsIntance1}, + entry: { + ...entryInstance1, + ...{ + percent_hbd: 0, + pending_payout_value: '14.264 HBD', + promoted: '0.000 HBD', + author_payout_value: '0.000 HBD', + curator_payout_value: '0.000 HBD', + payout_at: '2020-06-03T15:15:24', + max_accepted_payout: '10.000 HBD', + }, + }, + }; - const renderer = await TestRenderer.create(); - await allOver(); - expect(renderer.toJSON()).toMatchSnapshot(); + const renderer = await TestRenderer.create(); + await allOver(); + expect(renderer.toJSON()).toMatchSnapshot(); }); -it("(5) Default with max payout", async() => { - const props = { - global: {...globalInstance}, - dynamicProps: {...dynamicPropsIntance1}, - entry: { - ...entryInstance1, - ...{ - pending_payout_value: "14.264 HBD", - promoted: "0.000 HBD", - author_payout_value: "0.000 HBD", - curator_payout_value: "0.000 HBD", - payout_at: "2020-06-03T15:15:24", - max_accepted_payout: "10.000 HBD" - }, - }, - }; +it('(5) Default with max payout', async () => { + const props = { + global: {...globalInstance}, + dynamicProps: {...dynamicPropsIntance1}, + entry: { + ...entryInstance1, + ...{ + pending_payout_value: '14.264 HBD', + promoted: '0.000 HBD', + author_payout_value: '0.000 HBD', + curator_payout_value: '0.000 HBD', + payout_at: '2020-06-03T15:15:24', + max_accepted_payout: '10.000 HBD', + }, + }, + }; - const renderer = await TestRenderer.create(); - await allOver(); - expect(renderer.toJSON()).toMatchSnapshot(); + const renderer = await TestRenderer.create(); + await allOver(); + expect(renderer.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/entry-payout/index.tsx b/src/common/components/entry-payout/index.tsx index c80d882d35f..68186cd81a2 100644 --- a/src/common/components/entry-payout/index.tsx +++ b/src/common/components/entry-payout/index.tsx @@ -1,173 +1,234 @@ -import React, {Component, Fragment} from "react"; +import React, {Component, Fragment} from 'react'; -import moment from "moment"; +import moment from 'moment'; -import {Popover, OverlayTrigger} from "react-bootstrap"; +import {Popover, OverlayTrigger} from 'react-bootstrap'; -import {Entry} from "../../store/entries/types"; -import {Global} from "../../store/global/types"; -import {DynamicProps} from "../../store/dynamic-props/types"; +import {Entry} from '../../store/entries/types'; +import {Global} from '../../store/global/types'; +import {DynamicProps} from '../../store/dynamic-props/types'; -import FormattedCurrency from "../formatted-currency/index"; +import FormattedCurrency from '../formatted-currency/index'; -import parseAsset from "../../helper/parse-asset"; -import parseDate from "../../helper/parse-date"; +import parseAsset from '../../helper/parse-asset'; +import parseDate from '../../helper/parse-date'; -import formattedNumber from "../../util/formatted-number"; +import formattedNumber from '../../util/formatted-number'; -import {_t} from "../../i18n"; - -import _c from "../../util/fix-class-names"; +import {_t} from '../../i18n'; +import _c from '../../util/fix-class-names'; interface Props { - global: Global; - dynamicProps: DynamicProps; - entry: Entry; + global: Global; + dynamicProps: DynamicProps; + entry: Entry; } export class EntryPayoutDetail extends Component { - render() { - const {entry, dynamicProps} = this.props; - - const {base, quote, hbdPrintRate} = dynamicProps; - - const payoutDate = moment(parseDate(entry.payout_at)); - - const beneficiary = entry.beneficiaries; - const pendingPayout = parseAsset(entry.pending_payout_value).amount; - const promotedPayout = parseAsset(entry.promoted).amount; - const authorPayout = parseAsset(entry.author_payout_value).amount; - const curatorPayout = parseAsset(entry.curator_payout_value).amount; - const maxPayout = parseAsset(entry.max_accepted_payout).amount; - const fullPower = entry.percent_hbd === 0; - - const totalPayout = pendingPayout + authorPayout + curatorPayout; - const payoutLimitHit = totalPayout >= maxPayout; - - const HBD_PRINT_RATE_MAX = 10000; - const percentHiveDollars = (entry.percent_hbd) / 20000; - const pendingPayoutHbd = pendingPayout * (percentHiveDollars); - const pricePerHive = base / quote; - const pendingPayoutHp = (pendingPayout - pendingPayoutHbd) / pricePerHive; - const pendingPayoutPrintedHbd = pendingPayoutHbd * (hbdPrintRate / HBD_PRINT_RATE_MAX); - const pendingPayoutPrintedHive = (pendingPayoutHbd - pendingPayoutPrintedHbd) / pricePerHive; - - let breakdownPayout: string[] = []; - if (pendingPayout > 0) { - if (pendingPayoutPrintedHbd > 0) { - breakdownPayout.push(formattedNumber(pendingPayoutPrintedHbd, {fractionDigits: 3, suffix: 'HBD'})) - } - - if (pendingPayoutPrintedHive > 0) { - breakdownPayout.push(formattedNumber(pendingPayoutPrintedHive, {fractionDigits: 3, suffix: 'HIVE'})) - } - - if (pendingPayoutHp > 0) { - breakdownPayout.push(formattedNumber(pendingPayoutHp, {fractionDigits: 3, suffix: 'HP'})) - } - } - - return ( -
- {fullPower && -

- {_t("entry-payout.reward")} - {_t("entry-payout.full-power")} -

- } - {pendingPayout > 0 && -

- {_t("entry-payout.pending-payout")} - -

- } - {promotedPayout > 0 && -

- {_t("entry-payout.promoted")} - -

- } - {authorPayout > 0 && -

- {_t("entry-payout.author-payout")} - -

- } - {curatorPayout > 0 && -

- {_t("entry-payout.curators-payout")} - -

- } - {beneficiary.length > 0 && ( -

- {_t("entry-payout.beneficiary")} - {beneficiary.map(((x, i) => {x.account}: {(x.weight / 100).toFixed(0)}%
))}
-

- )} - {breakdownPayout.length > 0 && ( -

- {_t("entry-payout.breakdown")} - {breakdownPayout.map(((x, i) => {x}
))}
-

- )} -

- {_t("entry-payout.payout-date")} - {payoutDate.fromNow()} -

- {payoutLimitHit && ( -

- {_t("entry-payout.max-accepted")} - -

- )} -
+ render() { + const {entry, dynamicProps} = this.props; + + const {base, quote, hbdPrintRate} = dynamicProps; + + const payoutDate = moment(parseDate(entry.payout_at)); + + const beneficiary = entry.beneficiaries; + const pendingPayout = parseAsset(entry.pending_payout_value).amount; + const promotedPayout = parseAsset(entry.promoted).amount; + const authorPayout = parseAsset(entry.author_payout_value).amount; + const curatorPayout = parseAsset(entry.curator_payout_value).amount; + const maxPayout = parseAsset(entry.max_accepted_payout).amount; + const fullPower = entry.percent_hbd === 0; + + const totalPayout = pendingPayout + authorPayout + curatorPayout; + const payoutLimitHit = totalPayout >= maxPayout; + + const HBD_PRINT_RATE_MAX = 10000; + const percentHiveDollars = entry.percent_hbd / 20000; + const pendingPayoutHbd = pendingPayout * percentHiveDollars; + const pricePerHive = base / quote; + const pendingPayoutHp = (pendingPayout - pendingPayoutHbd) / pricePerHive; + const pendingPayoutPrintedHbd = + pendingPayoutHbd * (hbdPrintRate / HBD_PRINT_RATE_MAX); + const pendingPayoutPrintedHive = + (pendingPayoutHbd - pendingPayoutPrintedHbd) / pricePerHive; + + let breakdownPayout: string[] = []; + if (pendingPayout > 0) { + if (pendingPayoutPrintedHbd > 0) { + breakdownPayout.push( + formattedNumber(pendingPayoutPrintedHbd, { + fractionDigits: 3, + suffix: 'HBD', + }), ); - } -} - -export class EntryPayout extends Component { - render() { - const {entry} = this.props; - - const isPayoutDeclined = parseAsset(entry.max_accepted_payout).amount === 0; - - const pendingPayout = parseAsset(entry.pending_payout_value).amount; - const authorPayout = parseAsset(entry.author_payout_value).amount; - const curatorPayout = parseAsset(entry.curator_payout_value).amount; - const maxPayout = parseAsset(entry.max_accepted_payout).amount; - - const totalPayout = pendingPayout + authorPayout + curatorPayout; - - const payoutLimitHit = totalPayout >= maxPayout; - const shownPayout = payoutLimitHit && maxPayout > 0 ? maxPayout : totalPayout; - - const popover = ( - - - - - + } + + if (pendingPayoutPrintedHive > 0) { + breakdownPayout.push( + formattedNumber(pendingPayoutPrintedHive, { + fractionDigits: 3, + suffix: 'HIVE', + }), ); + } - return ( - -
- -
-
+ if (pendingPayoutHp > 0) { + breakdownPayout.push( + formattedNumber(pendingPayoutHp, {fractionDigits: 3, suffix: 'HP'}), ); + } } + + return ( +
+ {fullPower && ( +

+ {_t('entry-payout.reward')} + {_t('entry-payout.full-power')} +

+ )} + {pendingPayout > 0 && ( +

+ {_t('entry-payout.pending-payout')} + + + +

+ )} + {promotedPayout > 0 && ( +

+ {_t('entry-payout.promoted')} + + + +

+ )} + {authorPayout > 0 && ( +

+ {_t('entry-payout.author-payout')} + + + +

+ )} + {curatorPayout > 0 && ( +

+ {_t('entry-payout.curators-payout')} + + + +

+ )} + {beneficiary.length > 0 && ( +

+ {_t('entry-payout.beneficiary')} + + {beneficiary.map((x, i) => ( + + {x.account}: {(x.weight / 100).toFixed(0)}%
+
+ ))} +
+

+ )} + {breakdownPayout.length > 0 && ( +

+ {_t('entry-payout.breakdown')} + + {breakdownPayout.map((x, i) => ( + + {x}
+
+ ))} +
+

+ )} +

+ {_t('entry-payout.payout-date')} + {payoutDate.fromNow()} +

+ {payoutLimitHit && ( +

+ {_t('entry-payout.max-accepted')} + + + +

+ )} +
+ ); + } } +export class EntryPayout extends Component { + render() { + const {entry} = this.props; + + const isPayoutDeclined = parseAsset(entry.max_accepted_payout).amount === 0; + + const pendingPayout = parseAsset(entry.pending_payout_value).amount; + const authorPayout = parseAsset(entry.author_payout_value).amount; + const curatorPayout = parseAsset(entry.curator_payout_value).amount; + const maxPayout = parseAsset(entry.max_accepted_payout).amount; + + const totalPayout = pendingPayout + authorPayout + curatorPayout; + + const payoutLimitHit = totalPayout >= maxPayout; + const shownPayout = + payoutLimitHit && maxPayout > 0 ? maxPayout : totalPayout; + + const popover = ( + + + + + + ); + + return ( + +
+ +
+
+ ); + } +} export default (p: Props) => { - const props = { - global: p.global, - dynamicProps: p.dynamicProps, - entry: p.entry - } - - return -} + const props = { + global: p.global, + dynamicProps: p.dynamicProps, + entry: p.entry, + }; + + return ; +}; diff --git a/src/common/components/entry-reblog-btn/index.spec.tsx b/src/common/components/entry-reblog-btn/index.spec.tsx index 3fb573d725e..6bffedccfc2 100644 --- a/src/common/components/entry-reblog-btn/index.spec.tsx +++ b/src/common/components/entry-reblog-btn/index.spec.tsx @@ -1,63 +1,66 @@ -import React from "react"; +import React from 'react'; -import {EntryReblogBtn} from "./index"; +import {EntryReblogBtn} from './index'; -import TestRenderer from "react-test-renderer"; +import TestRenderer from 'react-test-renderer'; -import {entryInstance1, UiInstance, emptyReblogs, activeUserMaker} from "../../helper/test-helper"; +import { + entryInstance1, + UiInstance, + emptyReblogs, + activeUserMaker, +} from '../../helper/test-helper'; const defProps = { - entry: {...entryInstance1}, - users: [], - activeUser: null, - reblogs: emptyReblogs, - ui: UiInstance, - setActiveUser: () => { - }, - updateActiveUser: () => { - }, - deleteUser: () => { - }, - fetchReblogs: () => { - }, - addReblog: () => { - }, - deleteReblog: () => { - }, - toggleUIProp: () => { - - } + entry: {...entryInstance1}, + users: [], + activeUser: null, + reblogs: emptyReblogs, + ui: UiInstance, + setActiveUser: () => {}, + updateActiveUser: () => {}, + deleteUser: () => {}, + fetchReblogs: () => {}, + addReblog: () => {}, + deleteReblog: () => {}, + toggleUIProp: () => {}, }; -it("(1) No active user", () => { - const props = {...defProps}; - const renderer = TestRenderer.create(); - expect(renderer.toJSON()).toMatchSnapshot(); +it('(1) No active user', () => { + const props = {...defProps}; + const renderer = TestRenderer.create(); + expect(renderer.toJSON()).toMatchSnapshot(); }); -it("(2) Active user. Not reblogged", () => { - const props = {...defProps, activeUser: activeUserMaker("user1")}; - const renderer = TestRenderer.create(); - expect(renderer.toJSON()).toMatchSnapshot(); +it('(2) Active user. Not reblogged', () => { + const props = {...defProps, activeUser: activeUserMaker('user1')}; + const renderer = TestRenderer.create(); + expect(renderer.toJSON()).toMatchSnapshot(); }); -it("(3) Active user. Reblogged", () => { - const props = { - ...defProps, - activeUser: activeUserMaker("user1"), - reblogs: { - list: [{account: "user1", author: entryInstance1.author, permlink: entryInstance1.permlink}], - canFetch: false +it('(3) Active user. Reblogged', () => { + const props = { + ...defProps, + activeUser: activeUserMaker('user1'), + reblogs: { + list: [ + { + account: 'user1', + author: entryInstance1.author, + permlink: entryInstance1.permlink, }, - }; - const renderer = TestRenderer.create(); - expect(renderer.toJSON()).toMatchSnapshot(); + ], + canFetch: false, + }, + }; + const renderer = TestRenderer.create(); + expect(renderer.toJSON()).toMatchSnapshot(); }); -it("(4) Reblogging", () => { - const props = {...defProps, activeUser: activeUserMaker("user1")}; - const component = TestRenderer.create(); - const instance: any = component.getInstance(); - instance.stateSet({inProgress: true}); - expect(component.toJSON()).toMatchSnapshot(); +it('(4) Reblogging', () => { + const props = {...defProps, activeUser: activeUserMaker('user1')}; + const component = TestRenderer.create(); + const instance: any = component.getInstance(); + instance.stateSet({inProgress: true}); + expect(component.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/entry-reblog-btn/index.tsx b/src/common/components/entry-reblog-btn/index.tsx index 4408b6a77f7..0088095851a 100644 --- a/src/common/components/entry-reblog-btn/index.tsx +++ b/src/common/components/entry-reblog-btn/index.tsx @@ -1,166 +1,184 @@ -import React from "react"; +import React from 'react'; -import {Entry} from "../../store/entries/types"; -import {Account} from "../../store/accounts/types"; -import {User} from "../../store/users/types"; -import {ActiveUser} from "../../store/active-user/types"; -import {Reblogs} from "../../store/reblogs/types"; -import {UI, ToggleType} from "../../store/ui/types"; +import {Entry} from '../../store/entries/types'; +import {Account} from '../../store/accounts/types'; +import {User} from '../../store/users/types'; +import {ActiveUser} from '../../store/active-user/types'; +import {Reblogs} from '../../store/reblogs/types'; +import {UI, ToggleType} from '../../store/ui/types'; -import BaseComponent from "../base"; -import Tooltip from "../tooltip"; -import LoginRequired from "../login-required"; -import PopoverConfirm from "../popover-confirm"; -import {error, success, info} from "../feedback"; +import BaseComponent from '../base'; +import Tooltip from '../tooltip'; +import LoginRequired from '../login-required'; +import PopoverConfirm from '../popover-confirm'; +import {error, success, info} from '../feedback'; -import {reblog, formatError} from "../../api/operations"; +import {reblog, formatError} from '../../api/operations'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import _c from "../../util/fix-class-names"; +import _c from '../../util/fix-class-names'; -import {repeatSvg} from "../../img/svg"; +import {repeatSvg} from '../../img/svg'; interface Props { - entry: Entry; - users: User[]; - activeUser: ActiveUser | null; - reblogs: Reblogs; - ui: UI; - setActiveUser: (username: string | null) => void; - updateActiveUser: (data?: Account) => void; - deleteUser: (username: string) => void; - fetchReblogs: () => void; - addReblog: (author: string, permlink: string) => void; - deleteReblog: (author: string, permlink: string) => void; - toggleUIProp: (what: ToggleType) => void; + entry: Entry; + users: User[]; + activeUser: ActiveUser | null; + reblogs: Reblogs; + ui: UI; + setActiveUser: (username: string | null) => void; + updateActiveUser: (data?: Account) => void; + deleteUser: (username: string) => void; + fetchReblogs: () => void; + addReblog: (author: string, permlink: string) => void; + deleteReblog: (author: string, permlink: string) => void; + toggleUIProp: (what: ToggleType) => void; } interface State { - inProgress: boolean; + inProgress: boolean; } export class EntryReblogBtn extends BaseComponent { - state: State = { - inProgress: false, - }; - - componentDidMount() { - const {activeUser, reblogs, fetchReblogs} = this.props; - if (activeUser && reblogs.canFetch) { - // since @active-user/LOGIN resets reblogs reducer, wait 500 ms on first load - // to clientStoreTasks (store/helper.ts) finish its job with logging active user in. - // Otherwise condenser_api.get_blog_entries will be called 2 times on page load. - setTimeout(fetchReblogs, 500); - } + state: State = { + inProgress: false, + }; + + componentDidMount() { + const {activeUser, reblogs, fetchReblogs} = this.props; + if (activeUser && reblogs.canFetch) { + // since @active-user/LOGIN resets reblogs reducer, wait 500 ms on first load + // to clientStoreTasks (store/helper.ts) finish its job with logging active user in. + // Otherwise condenser_api.get_blog_entries will be called 2 times on page load. + setTimeout(fetchReblogs, 500); } - - componentDidUpdate(prevProps: Readonly) { - const {activeUser, reblogs, fetchReblogs} = this.props; - if (activeUser && activeUser.username !== prevProps.activeUser?.username && reblogs.canFetch) { - fetchReblogs(); - } + } + + componentDidUpdate(prevProps: Readonly) { + const {activeUser, reblogs, fetchReblogs} = this.props; + if ( + activeUser && + activeUser.username !== prevProps.activeUser?.username && + reblogs.canFetch + ) { + fetchReblogs(); } - - reblog = () => { - const {entry, activeUser, addReblog} = this.props; - - this.stateSet({inProgress: true}); - reblog(activeUser?.username!, entry.author, entry.permlink) - .then(() => { - addReblog(entry.author, entry.permlink); - success(_t("entry-reblog.success")); - }) - .catch((e) => { - error(formatError(e)); - }) - .finally(() => { - this.stateSet({inProgress: false}); - }); - }; - - deleteReblog = () => { - const {entry, activeUser, deleteReblog} = this.props; - - this.stateSet({inProgress: true}); - reblog(activeUser?.username!, entry.author, entry.permlink, true) - .then(() => { - deleteReblog(entry.author, entry.permlink); - info(_t("entry-reblog.delete-success")); - }) - .catch((e) => { - error(formatError(e)); - }) - .finally(() => { - this.stateSet({inProgress: false}); - }); + } + + reblog = () => { + const {entry, activeUser, addReblog} = this.props; + + this.stateSet({inProgress: true}); + reblog(activeUser?.username!, entry.author, entry.permlink) + .then(() => { + addReblog(entry.author, entry.permlink); + success(_t('entry-reblog.success')); + }) + .catch(e => { + error(formatError(e)); + }) + .finally(() => { + this.stateSet({inProgress: false}); + }); + }; + + deleteReblog = () => { + const {entry, activeUser, deleteReblog} = this.props; + + this.stateSet({inProgress: true}); + reblog(activeUser?.username!, entry.author, entry.permlink, true) + .then(() => { + deleteReblog(entry.author, entry.permlink); + info(_t('entry-reblog.delete-success')); + }) + .catch(e => { + error(formatError(e)); + }) + .finally(() => { + this.stateSet({inProgress: false}); + }); + }; + + render() { + const {activeUser, entry, reblogs} = this.props; + const {inProgress} = this.state; + + const reblogged = + activeUser && + reblogs.list.find( + x => x.author === entry.author && x.permlink === entry.permlink, + ) !== undefined; + + const content = ( +
+ + {repeatSvg} + +
+ ); + + if (!activeUser) { + return LoginRequired({ + ...this.props, + children: content, + }); } - render() { - const {activeUser, entry, reblogs} = this.props; - const {inProgress} = this.state; - - const reblogged = - activeUser && - reblogs.list.find((x) => x.author === entry.author && x.permlink === entry.permlink) !== undefined; - - const content = ( - - ); - - if (!activeUser) { - return LoginRequired({ - ...this.props, - children: content - }) - } - - // Delete reblog - if (reblogged) { - return - {content} - - } - - // Reblog - return ( - - {content} - - ); + // Delete reblog + if (reblogged) { + return ( + + {content} + + ); } + + // Reblog + return ( + + {content} + + ); + } } export default (p: Props) => { - const props: Props = { - entry: p.entry, - users: p.users, - activeUser: p.activeUser, - reblogs: p.reblogs, - ui: p.ui, - setActiveUser: p.setActiveUser, - updateActiveUser: p.updateActiveUser, - deleteUser: p.deleteUser, - fetchReblogs: p.fetchReblogs, - addReblog: p.addReblog, - deleteReblog: p.deleteReblog, - toggleUIProp: p.toggleUIProp - } - - return -} + const props: Props = { + entry: p.entry, + users: p.users, + activeUser: p.activeUser, + reblogs: p.reblogs, + ui: p.ui, + setActiveUser: p.setActiveUser, + updateActiveUser: p.updateActiveUser, + deleteUser: p.deleteUser, + fetchReblogs: p.fetchReblogs, + addReblog: p.addReblog, + deleteReblog: p.deleteReblog, + toggleUIProp: p.toggleUIProp, + }; + + return ; +}; diff --git a/src/common/components/entry-share/index.tsx b/src/common/components/entry-share/index.tsx index d2ea7b163dc..5c20267dc7a 100644 --- a/src/common/components/entry-share/index.tsx +++ b/src/common/components/entry-share/index.tsx @@ -1,66 +1,91 @@ -import React from "react"; +import React from 'react'; -import {Modal} from "react-bootstrap"; +import {Modal} from 'react-bootstrap'; -import BaseComponent from "../base"; +import BaseComponent from '../base'; -import {Entry} from "../../store/entries/types"; +import {Entry} from '../../store/entries/types'; -import {redditSvg, twitterSvg, facebookSvg} from "../../img/svg"; +import {redditSvg, twitterSvg, facebookSvg} from '../../img/svg'; -import {makeShareUrlFacebook, makeShareUrlReddit, makeShareUrlTwitter} from "../../helper/url-share"; +import { + makeShareUrlFacebook, + makeShareUrlReddit, + makeShareUrlTwitter, +} from '../../helper/url-share'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; interface Props { - entry: Entry; - onHide: () => void; + entry: Entry; + onHide: () => void; } export const shareReddit = (entry: Entry) => { - const u = makeShareUrlReddit(entry.category, entry.author, entry.permlink, entry.title); - window.open(u, "_blank"); -} + const u = makeShareUrlReddit( + entry.category, + entry.author, + entry.permlink, + entry.title, + ); + window.open(u, '_blank'); +}; export const shareTwitter = (entry: Entry) => { - const u = makeShareUrlTwitter(entry.category, entry.author, entry.permlink, entry.title); - window.open(u, "_blank"); -} + const u = makeShareUrlTwitter( + entry.category, + entry.author, + entry.permlink, + entry.title, + ); + window.open(u, '_blank'); +}; export const shareFacebook = (entry: Entry) => { - const u = makeShareUrlFacebook(entry.category, entry.author, entry.permlink); - window.open(u, "_blank"); -} - + const u = makeShareUrlFacebook(entry.category, entry.author, entry.permlink); + window.open(u, '_blank'); +}; export default class EntryShare extends BaseComponent { - reddit = () => { - shareReddit(this.props.entry) - }; + reddit = () => { + shareReddit(this.props.entry); + }; - twitter = () => { - shareTwitter(this.props.entry); - }; + twitter = () => { + shareTwitter(this.props.entry); + }; - facebook = () => { - shareFacebook(this.props.entry); - }; + facebook = () => { + shareFacebook(this.props.entry); + }; - render() { - const {onHide} = this.props; - return ( - - - {_t("entry-share.title")} - - -
-
{redditSvg}
-
{twitterSvg}
-
{facebookSvg}
-
-
-
- ); - } + render() { + const {onHide} = this.props; + return ( + + + {_t('entry-share.title')} + + +
+
+ {redditSvg} +
+
+ {twitterSvg} +
+
+ {facebookSvg} +
+
+
+
+ ); + } } diff --git a/src/common/components/entry-tip-btn/index.spec.tsx b/src/common/components/entry-tip-btn/index.spec.tsx index 527bfcc07d2..c4f1ee49139 100644 --- a/src/common/components/entry-tip-btn/index.spec.tsx +++ b/src/common/components/entry-tip-btn/index.spec.tsx @@ -1,60 +1,59 @@ -import React from "react"; +import React from 'react'; -import EntryTipBtn, {TippingDialog} from "./index"; +import EntryTipBtn, {TippingDialog} from './index'; -import {globalInstance, dynamicPropsIntance1, UiInstance, entryInstance1, fullAccountInstance} from "../../helper/test-helper"; +import { + globalInstance, + dynamicPropsIntance1, + UiInstance, + entryInstance1, + fullAccountInstance, +} from '../../helper/test-helper'; -import TestRenderer from "react-test-renderer"; +import TestRenderer from 'react-test-renderer'; const defProps = { - global: globalInstance, - dynamicProps: dynamicPropsIntance1, - users: [], - account: { - name: "user1", - }, - ui: UiInstance, - activeUser: { - username: 'foo', - data: { - ...fullAccountInstance, - }, - points: { - points: "200.000", - uPoints: "0.000" - } - }, - entry: entryInstance1, - signingKey: "", - addAccount: () => { - }, - setActiveUser: () => { - }, - updateActiveUser: () => { - }, - deleteUser: () => { - }, - toggleUIProp: () => { - }, - setSigningKey: () => { - }, - fetchPoints: () => {}, - updateWalletValues: () => {} + global: globalInstance, + dynamicProps: dynamicPropsIntance1, + users: [], + account: { + name: 'user1', + }, + ui: UiInstance, + activeUser: { + username: 'foo', + data: { + ...fullAccountInstance, + }, + points: { + points: '200.000', + uPoints: '0.000', + }, + }, + entry: entryInstance1, + signingKey: '', + addAccount: () => {}, + setActiveUser: () => {}, + updateActiveUser: () => {}, + deleteUser: () => {}, + toggleUIProp: () => {}, + setSigningKey: () => {}, + fetchPoints: () => {}, + updateWalletValues: () => {}, }; -it("(1) Default render", async () => { - const renderer = TestRenderer.create(); +it('(1) Default render', async () => { + const renderer = TestRenderer.create(); - expect(renderer.toJSON()).toMatchSnapshot(); + expect(renderer.toJSON()).toMatchSnapshot(); }); -it("(2) Dialog", async () => { - const props = { - ...defProps, - onHide: () => { - } - } +it('(2) Dialog', async () => { + const props = { + ...defProps, + onHide: () => {}, + }; - const renderer = TestRenderer.create(); - expect(renderer.toJSON()).toMatchSnapshot(); + const renderer = TestRenderer.create(); + expect(renderer.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/entry-tip-btn/index.tsx b/src/common/components/entry-tip-btn/index.tsx index 4f3047246d8..826c2c23e9a 100644 --- a/src/common/components/entry-tip-btn/index.tsx +++ b/src/common/components/entry-tip-btn/index.tsx @@ -1,138 +1,149 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import {Modal} from "react-bootstrap"; +import {Modal} from 'react-bootstrap'; -import {Global} from "../../store/global/types"; -import {DynamicProps} from "../../store/dynamic-props/types"; -import {ActiveUser} from "../../store/active-user/types"; -import {Account} from "../../store/accounts/types"; -import {Entry} from "../../store/entries/types"; -import {User} from "../../store/users/types"; -import {ToggleType, UI} from "../../store/ui/types"; -import {Transactions} from "../../store/transactions/types"; +import {Global} from '../../store/global/types'; +import {DynamicProps} from '../../store/dynamic-props/types'; +import {ActiveUser} from '../../store/active-user/types'; +import {Account} from '../../store/accounts/types'; +import {Entry} from '../../store/entries/types'; +import {User} from '../../store/users/types'; +import {ToggleType, UI} from '../../store/ui/types'; +import {Transactions} from '../../store/transactions/types'; -import LoginRequired from "../login-required"; -import {Transfer} from "../transfer"; -import Tooltip from "../tooltip"; +import LoginRequired from '../login-required'; +import {Transfer} from '../transfer'; +import Tooltip from '../tooltip'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import {giftOutlineSvg} from "../../img/svg"; +import {giftOutlineSvg} from '../../img/svg'; interface Props { - global: Global; - dynamicProps: DynamicProps; - users: User[]; - ui: UI; - activeUser: ActiveUser | null; - entry: Entry; - signingKey: string; - account: Account; - fetchPoints: (username: string, type?: number) => void; - updateWalletValues: () => void; - addAccount: (data: Account) => void; - setActiveUser: (username: string | null) => void; - updateActiveUser: (data?: Account) => void; - deleteUser: (username: string) => void; - toggleUIProp: (what: ToggleType) => void; - setSigningKey: (key: string) => void; + global: Global; + dynamicProps: DynamicProps; + users: User[]; + ui: UI; + activeUser: ActiveUser | null; + entry: Entry; + signingKey: string; + account: Account; + fetchPoints: (username: string, type?: number) => void; + updateWalletValues: () => void; + addAccount: (data: Account) => void; + setActiveUser: (username: string | null) => void; + updateActiveUser: (data?: Account) => void; + deleteUser: (username: string) => void; + toggleUIProp: (what: ToggleType) => void; + setSigningKey: (key: string) => void; } interface DialogProps extends Props { - onHide: () => void; + onHide: () => void; } - export class TippingDialog extends Component { - render() { - const {global, entry, activeUser} = this.props; - - if (!activeUser) { - return null; - } - - const to = entry.author; - const memo = `Tip for @${entry.author}/${entry.permlink}` - const transactions: Transactions = { - list: [], - loading: false, - group: "" - } - - return + render() { + const {global, entry, activeUser} = this.props; + + if (!activeUser) { + return null; } + + const to = entry.author; + const memo = `Tip for @${entry.author}/${entry.permlink}`; + const transactions: Transactions = { + list: [], + loading: false, + group: '', + }; + + return ( + + ); + } } interface State { - dialog: boolean; + dialog: boolean; } export class EntryTipBtn extends Component { - state: State = { - dialog: false - } - - toggleDialog = () => { - const {dialog} = this.state; - this.setState({dialog: !dialog}); - }; - - render() { - const {activeUser} = this.props; - const {dialog} = this.state; - - return ( - <> - {LoginRequired({ - ...this.props, - children:
- - {giftOutlineSvg} - -
- })} - - {(dialog && activeUser) && ( - - - - - - - )} - - ) - } + state: State = { + dialog: false, + }; + + toggleDialog = () => { + const {dialog} = this.state; + this.setState({dialog: !dialog}); + }; + + render() { + const {activeUser} = this.props; + const {dialog} = this.state; + + return ( + <> + {LoginRequired({ + ...this.props, + children: ( +
+ + {giftOutlineSvg} + +
+ ), + })} + + {dialog && activeUser && ( + + + + + + + )} + + ); + } } export default (p: Props) => { - const props = { - global: p.global, - dynamicProps: p.dynamicProps, - users: p.users, - ui: p.ui, - account: p.account, - fetchPoints: p.fetchPoints, - updateWalletValues: p.updateWalletValues, - activeUser: p.activeUser, - entry: p.entry, - signingKey: p.signingKey, - addAccount: p.addAccount, - setActiveUser: p.setActiveUser, - updateActiveUser: p.updateActiveUser, - deleteUser: p.deleteUser, - toggleUIProp: p.toggleUIProp, - setSigningKey: p.setSigningKey - } - - return -} + const props = { + global: p.global, + dynamicProps: p.dynamicProps, + users: p.users, + ui: p.ui, + account: p.account, + fetchPoints: p.fetchPoints, + updateWalletValues: p.updateWalletValues, + activeUser: p.activeUser, + entry: p.entry, + signingKey: p.signingKey, + addAccount: p.addAccount, + setActiveUser: p.setActiveUser, + updateActiveUser: p.updateActiveUser, + deleteUser: p.deleteUser, + toggleUIProp: p.toggleUIProp, + setSigningKey: p.setSigningKey, + }; + + return ; +}; diff --git a/src/common/components/entry-vote-btn/index.spec.tsx b/src/common/components/entry-vote-btn/index.spec.tsx index 40fa7909a42..3f1d7bda2e6 100644 --- a/src/common/components/entry-vote-btn/index.spec.tsx +++ b/src/common/components/entry-vote-btn/index.spec.tsx @@ -1,114 +1,109 @@ -import React from "react"; +import React from 'react'; -import EntryVoteBtn, {VoteDialog} from "./index"; +import EntryVoteBtn, {VoteDialog} from './index'; -import renderer from "react-test-renderer"; +import renderer from 'react-test-renderer'; -import {globalInstance, dynamicPropsIntance1, entryInstance1, UiInstance, activeUserMaker, fullAccountInstance} from "../../helper/test-helper"; +import { + globalInstance, + dynamicPropsIntance1, + entryInstance1, + UiInstance, + activeUserMaker, + fullAccountInstance, +} from '../../helper/test-helper'; -import {Account} from "../../store/accounts/types"; +import {Account} from '../../store/accounts/types'; - -jest.mock("../../api/hive", () => ({ - votingPower: () => 5, - getActiveVotes: () => - new Promise((resolve) => { - resolve([{voter: "user1", percent: 10}]); - }), +jest.mock('../../api/hive', () => ({ + votingPower: () => 5, + getActiveVotes: () => + new Promise(resolve => { + resolve([{voter: 'user1', percent: 10}]); + }), })); - describe('(1) Dialog', () => { - - const data: Account = { - ...fullAccountInstance, - name: "user1", - vesting_shares: "0.000000 VESTS", - delegated_vesting_shares: "0.000000 VESTS", - received_vesting_shares: "77883823.534631 VESTS", - }; - - const props = { - activeUser: {...activeUserMaker("user1"), ...{data}}, - dynamicProps: dynamicPropsIntance1, - global: globalInstance, - entry: entryInstance1, - downVoted: false, - upVoted: false, - onClick: () => { - }, - }; - - const component = renderer.create(); - const instance: any = component.getInstance(); - - it("(1) Up vote", () => { - expect(component.toJSON()).toMatchSnapshot(); - }); - - - it("(2) Down vote", () => { - instance.changeMode('down'); - expect(component.toJSON()).toMatchSnapshot(); - }); - + const data: Account = { + ...fullAccountInstance, + name: 'user1', + vesting_shares: '0.000000 VESTS', + delegated_vesting_shares: '0.000000 VESTS', + received_vesting_shares: '77883823.534631 VESTS', + }; + + const props = { + activeUser: {...activeUserMaker('user1'), ...{data}}, + dynamicProps: dynamicPropsIntance1, + global: globalInstance, + entry: entryInstance1, + downVoted: false, + upVoted: false, + onClick: () => {}, + }; + + const component = renderer.create(); + const instance: any = component.getInstance(); + + it('(1) Up vote', () => { + expect(component.toJSON()).toMatchSnapshot(); + }); + + it('(2) Down vote', () => { + instance.changeMode('down'); + expect(component.toJSON()).toMatchSnapshot(); + }); }); describe('(2) Btn - No active user', () => { - const props = { - global: globalInstance, - dynamicProps: dynamicPropsIntance1, - entry: entryInstance1, - users: [], - activeUser: null, - ui: UiInstance, - setActiveUser: () => { - }, - updateActiveUser: () => { - }, - deleteUser: () => { - }, - afterVote: () => { - }, - toggleUIProp: () => { - - } - }; - - const component = renderer.create(); - - it("(1) Render", () => { - expect(component.toJSON()).toMatchSnapshot(); - }); + const props = { + global: globalInstance, + dynamicProps: dynamicPropsIntance1, + entry: entryInstance1, + users: [], + activeUser: null, + ui: UiInstance, + setActiveUser: () => {}, + updateActiveUser: () => {}, + deleteUser: () => {}, + afterVote: () => {}, + toggleUIProp: () => {}, + }; + + const component = renderer.create(); + + it('(1) Render', () => { + expect(component.toJSON()).toMatchSnapshot(); + }); }); describe('(3) Btn - Up voted', () => { - - const props = { - global: globalInstance, - dynamicProps: dynamicPropsIntance1, - entry: entryInstance1, - users: [{username: 'user1', accessToken: 's', refreshToken: 'b', expiresIn: 1, postingKey: null}], - activeUser: activeUserMaker("user1"), - ui: UiInstance, - setActiveUser: () => { - }, - updateActiveUser: () => { - }, - deleteUser: () => { - }, - afterVote: () => { - }, - toggleUIProp: () => { - - } - }; - - const component = renderer.create(); - const instance: any = component.getInstance(); - - it("(1) Render", () => { - expect(component.toJSON()).toMatchSnapshot(); - }); + const props = { + global: globalInstance, + dynamicProps: dynamicPropsIntance1, + entry: entryInstance1, + users: [ + { + username: 'user1', + accessToken: 's', + refreshToken: 'b', + expiresIn: 1, + postingKey: null, + }, + ], + activeUser: activeUserMaker('user1'), + ui: UiInstance, + setActiveUser: () => {}, + updateActiveUser: () => {}, + deleteUser: () => {}, + afterVote: () => {}, + toggleUIProp: () => {}, + }; + + const component = renderer.create(); + const instance: any = component.getInstance(); + + it('(1) Render', () => { + expect(component.toJSON()).toMatchSnapshot(); + }); }); - diff --git a/src/common/components/entry-vote-btn/index.tsx b/src/common/components/entry-vote-btn/index.tsx index 31ca44dc949..2468fe8f49b 100644 --- a/src/common/components/entry-vote-btn/index.tsx +++ b/src/common/components/entry-vote-btn/index.tsx @@ -1,46 +1,54 @@ -import React, { Component } from "react"; +import React, {Component} from 'react'; -import { Form, FormControl } from "react-bootstrap"; +import {Form, FormControl} from 'react-bootstrap'; -import { Global } from "../../store/global/types"; -import { Account } from "../../store/accounts/types"; -import { Entry, EntryVote } from "../../store/entries/types"; -import { User } from "../../store/users/types"; -import { ActiveUser } from "../../store/active-user/types"; -import { DynamicProps } from "../../store/dynamic-props/types"; -import { UI, ToggleType } from "../../store/ui/types"; +import {Global} from '../../store/global/types'; +import {Account} from '../../store/accounts/types'; +import {Entry, EntryVote} from '../../store/entries/types'; +import {User} from '../../store/users/types'; +import {ActiveUser} from '../../store/active-user/types'; +import {DynamicProps} from '../../store/dynamic-props/types'; +import {UI, ToggleType} from '../../store/ui/types'; -import BaseComponent from "../base"; -import FormattedCurrency from "../formatted-currency"; -import LoginRequired from "../login-required"; -import { error } from "../feedback"; +import BaseComponent from '../base'; +import FormattedCurrency from '../formatted-currency'; +import LoginRequired from '../login-required'; +import {error} from '../feedback'; -import { votingPower } from "../../api/hive"; -import { vote, formatError } from "../../api/operations"; +import {votingPower} from '../../api/hive'; +import {vote, formatError} from '../../api/operations'; -import parseAsset from "../../helper/parse-asset"; +import parseAsset from '../../helper/parse-asset'; -import * as ls from "../../util/local-storage"; +import * as ls from '../../util/local-storage'; -import _c from "../../util/fix-class-names"; +import _c from '../../util/fix-class-names'; -import { chevronDownSvgForSlider, chevronUpSvgForSlider, chevronUpSvgForVote } from "../../img/svg"; -import ClickAwayListener from "../clickaway-listener"; -import { _t } from "../../i18n"; +import { + chevronDownSvgForSlider, + chevronUpSvgForSlider, + chevronUpSvgForVote, +} from '../../img/svg'; +import ClickAwayListener from '../clickaway-listener'; +import {_t} from '../../i18n'; -const setVoteValue = (type: "up" | "down" | "downPrevious" | "upPrevious", username: string, value: number) => { +const setVoteValue = ( + type: 'up' | 'down' | 'downPrevious' | 'upPrevious', + username: string, + value: number, +) => { ls.set(`vote-value-${type}-${username}`, value); }; const getVoteValue = ( - type: "up" | "down" | "downPrevious" | "upPrevious", + type: 'up' | 'down' | 'downPrevious' | 'upPrevious', username: string, - def: number + def: number, ): number => { return ls.get(`vote-value-${type}-${username}`, def); }; -type Mode = "up" | "down"; +type Mode = 'up' | 'down'; interface VoteDialogProps { global: Global; @@ -61,33 +69,49 @@ interface VoteDialogState { showWarning: boolean; showRemove: boolean; wrongValueDown: boolean; - initialVoteValues: { up: any; down: any }; + initialVoteValues: {up: any; down: any}; } export class VoteDialog extends Component { state: VoteDialogState = { - upSliderVal: getVoteValue("up", this.props.activeUser?.username! + '-' + this.props.entry.post_id, getVoteValue("upPrevious", this.props.activeUser?.username!, 100)), - downSliderVal: getVoteValue("down", this.props.activeUser?.username! + '-' + this.props.entry.post_id, getVoteValue("downPrevious", this.props.activeUser?.username!, -1)), + upSliderVal: getVoteValue( + 'up', + this.props.activeUser?.username! + '-' + this.props.entry.post_id, + getVoteValue('upPrevious', this.props.activeUser?.username!, 100), + ), + downSliderVal: getVoteValue( + 'down', + this.props.activeUser?.username! + '-' + this.props.entry.post_id, + getVoteValue('downPrevious', this.props.activeUser?.username!, -1), + ), estimated: 0, - mode: this.props.downVoted ? "down" : "up", + mode: this.props.downVoted ? 'down' : 'up', wrongValueUp: false, wrongValueDown: false, showWarning: false, showRemove: false, initialVoteValues: { - up: getVoteValue("up", this.props.activeUser?.username! + '-' + this.props.entry.post_id, getVoteValue("upPrevious", this.props.activeUser?.username!, 100)), - down: getVoteValue("down", this.props.activeUser?.username!+ '-' + this.props.entry.post_id, getVoteValue("downPrevious", this.props.activeUser?.username!, -1)), + up: getVoteValue( + 'up', + this.props.activeUser?.username! + '-' + this.props.entry.post_id, + getVoteValue('upPrevious', this.props.activeUser?.username!, 100), + ), + down: getVoteValue( + 'down', + this.props.activeUser?.username! + '-' + this.props.entry.post_id, + getVoteValue('downPrevious', this.props.activeUser?.username!, -1), + ), }, }; estimate = (percent: number): number => { - const { entry, activeUser, dynamicProps } = this.props; + const {entry, activeUser, dynamicProps} = this.props; if (!activeUser) { return 0; } - const { fundRecentClaims, fundRewardBalance, base, quote } = dynamicProps; - const { data: account } = activeUser; + const {fundRecentClaims, fundRewardBalance, base, quote} = dynamicProps; + const {data: account} = activeUser; if (!account.__loaded) { return 0; @@ -137,114 +161,160 @@ export class VoteDialog extends Component { return voteValue * sign; }; - upSliderChanged = (e: React.ChangeEvent) => { - const { target: { id, value} } = e; + upSliderChanged = ( + e: React.ChangeEvent, + ) => { + const { + target: {id, value}, + } = e; const upSliderVal = Number(value); - const { initialVoteValues } = this.state; - const { upVoted } = this.props; + const {initialVoteValues} = this.state; + const {upVoted} = this.props; this.setState({ upSliderVal, wrongValueUp: upSliderVal === initialVoteValues.up && upVoted, showRemove: upSliderVal === 0 && upVoted, - showWarning: (upSliderVal < initialVoteValues.up || upSliderVal > initialVoteValues.up) && upSliderVal > 0 && upVoted + showWarning: + (upSliderVal < initialVoteValues.up || + upSliderVal > initialVoteValues.up) && + upSliderVal > 0 && + upVoted, }); }; - downSliderChanged = (e: React.ChangeEvent) => { - const { target: { id, value} } = e; + downSliderChanged = ( + e: React.ChangeEvent, + ) => { + const { + target: {id, value}, + } = e; const downSliderVal = Number(value); - const { initialVoteValues } = this.state; - const { upVoted, downVoted } = this.props; + const {initialVoteValues} = this.state; + const {upVoted, downVoted} = this.props; this.setState({ downSliderVal, wrongValueDown: downSliderVal === initialVoteValues.up, showRemove: downSliderVal === 0 && downVoted, - showWarning: (downSliderVal > initialVoteValues.down || downSliderVal < initialVoteValues.down) && downSliderVal < 0 && downVoted + showWarning: + (downSliderVal > initialVoteValues.down || + downSliderVal < initialVoteValues.down) && + downSliderVal < 0 && + downVoted, }); }; changeMode = (m: Mode) => { - this.setState({ mode: m }); + this.setState({mode: m}); }; isVoted = () => { - const { activeUser } = this.props; + const {activeUser} = this.props; if (!activeUser) { - return { upVoted: false, downVoted: false }; + return {upVoted: false, downVoted: false}; } - const { active_votes: votes } = this.props.entry; + const {active_votes: votes} = this.props.entry; const upVoted = votes.some( - (v) => v.voter === activeUser.username && v.rshares > 0 + v => v.voter === activeUser.username && v.rshares > 0, ); const downVoted = votes.some( - (v) => v.voter === activeUser.username && v.rshares < 0 + v => v.voter === activeUser.username && v.rshares < 0, ); - return { upVoted, downVoted }; + return {upVoted, downVoted}; }; upVoteClicked = () => { - const { onClick, activeUser, entry : { post_id } } = this.props; - const { upSliderVal, initialVoteValues } = this.state; - const { upVoted } = this.isVoted(); - + const { + onClick, + activeUser, + entry: {post_id}, + } = this.props; + const {upSliderVal, initialVoteValues} = this.state; + const {upVoted} = this.isVoted(); + if (!upVoted || (upVoted && initialVoteValues.up !== upSliderVal)) { const estimated = Number(this.estimate(upSliderVal).toFixed(3)); onClick(upSliderVal, estimated); - setVoteValue("up", `${activeUser?.username!}-${post_id}`, upSliderVal); - setVoteValue("upPrevious", `${activeUser?.username!}-${post_id}`, upSliderVal); - this.setState({ wrongValueUp: false, wrongValueDown: false }); - } else if(upVoted && initialVoteValues.up === upSliderVal){ - this.setState({ wrongValueUp: true, wrongValueDown: false }); + setVoteValue('up', `${activeUser?.username!}-${post_id}`, upSliderVal); + setVoteValue( + 'upPrevious', + `${activeUser?.username!}-${post_id}`, + upSliderVal, + ); + this.setState({wrongValueUp: false, wrongValueDown: false}); + } else if (upVoted && initialVoteValues.up === upSliderVal) { + this.setState({wrongValueUp: true, wrongValueDown: false}); } }; downVoteClicked = () => { - const { onClick, activeUser, entry : { post_id } } = this.props; - const { downSliderVal, initialVoteValues } = this.state; - const { downVoted } = this.isVoted(); + const { + onClick, + activeUser, + entry: {post_id}, + } = this.props; + const {downSliderVal, initialVoteValues} = this.state; + const {downVoted} = this.isVoted(); if (!downVoted || (downVoted && initialVoteValues.down !== downSliderVal)) { const estimated = Number(this.estimate(downSliderVal).toFixed(3)); onClick(downSliderVal, estimated); - this.setState({ wrongValueDown: false, wrongValueUp: false }); - setVoteValue("down", `${activeUser?.username!}-${post_id}`, downSliderVal); - setVoteValue("downPrevious", `${activeUser?.username!}-${post_id}`, downSliderVal); - } else if(downVoted && initialVoteValues.down === downSliderVal){ - this.setState({ wrongValueDown: true, wrongValueUp: false }); + this.setState({wrongValueDown: false, wrongValueUp: false}); + setVoteValue( + 'down', + `${activeUser?.username!}-${post_id}`, + downSliderVal, + ); + setVoteValue( + 'downPrevious', + `${activeUser?.username!}-${post_id}`, + downSliderVal, + ); + } else if (downVoted && initialVoteValues.down === downSliderVal) { + this.setState({wrongValueDown: true, wrongValueUp: false}); } }; render() { - const { upSliderVal, downSliderVal, mode, wrongValueUp, wrongValueDown, showWarning, showRemove } = this.state; - const { entry: { post_id } } = this.props; + const { + upSliderVal, + downSliderVal, + mode, + wrongValueUp, + wrongValueDown, + showWarning, + showRemove, + } = this.state; + const { + entry: {post_id}, + } = this.props; return ( <> - {mode === "up" && ( + {mode === 'up' && ( <> -
+
- {chevronUpSvgForSlider} + {chevronUpSvgForSlider}
-
+
-
+
{ id={post_id.toString()} />
-
{`${ +
{`${ upSliderVal && upSliderVal.toFixed(1) }%`}
{ - this.changeMode("down"); + this.changeMode('down'); }} > - {chevronDownSvgForSlider} + {chevronDownSvgForSlider}
{wrongValueUp && ( -
+

{_t('entry-list-item.vote-error')}

)} {showWarning && ( -
+

{_t('entry-list-item.vote-warning')}

)} {showRemove && ( -
+

{_t('entry-list-item.vote-remove')}

)} )} - {mode === "down" && ( + {mode === 'down' && ( <> -
+
{ - this.changeMode("up"); + this.changeMode('up'); }} > - {chevronUpSvgForSlider} + + {chevronUpSvgForSlider} +
-
+
-
+
{ value={downSliderVal} onChange={this.downSliderChanged} id={post_id.toString()} - className="reverse-range" + className='reverse-range' />
-
{`${downSliderVal.toFixed(1)}%`}
+
{`${downSliderVal.toFixed(1)}%`}
- {chevronDownSvgForSlider} + {chevronDownSvgForSlider}
- + {wrongValueDown && ( -
-

{_t('entry-list-item.vote-error')}

+
+

{_t('entry-list-item.vote-error')}

)} {showWarning && ( -
+

{_t('entry-list-item.vote-warning')}

)} {showRemove && ( -
+

{_t('entry-list-item.vote-remove')}

)} @@ -374,87 +446,91 @@ export class EntryVoteBtn extends BaseComponent { vote = (percent: number, estimated: number) => { this.toggleDialog(); - const { entry, activeUser, afterVote, updateActiveUser } = this.props; + const {entry, activeUser, afterVote, updateActiveUser} = this.props; const weight = Math.ceil(percent * 100); - this.stateSet({ inProgress: true }); + this.stateSet({inProgress: true}); const username = activeUser?.username!; vote(username, entry.author, entry.permlink, weight) .then(() => { const votes: EntryVote[] = [ - ...entry.active_votes.filter((x) => x.voter !== username), - { rshares: weight, voter: username }, + ...entry.active_votes.filter(x => x.voter !== username), + {rshares: weight, voter: username}, ]; afterVote(votes, estimated); updateActiveUser(); // refresh voting power }) - .catch((e) => { + .catch(e => { error(formatError(e)); }) .finally(() => { - this.stateSet({ inProgress: false }); + this.stateSet({inProgress: false}); }); }; isVoted = () => { - const { activeUser } = this.props; + const {activeUser} = this.props; if (!activeUser) { - return { upVoted: false, downVoted: false }; + return {upVoted: false, downVoted: false}; } - const { active_votes: votes } = this.props.entry; + const {active_votes: votes} = this.props.entry; const upVoted = votes.some( - (v) => v.voter === activeUser.username && v.rshares > 0 + v => v.voter === activeUser.username && v.rshares > 0, ); const downVoted = votes.some( - (v) => v.voter === activeUser.username && v.rshares < 0 + v => v.voter === activeUser.username && v.rshares < 0, ); - - return { upVoted, downVoted }; + + return {upVoted, downVoted}; }; toggleDialog = () => { - const { dialog } = this.state; - this.stateSet({ dialog: !dialog }); + const {dialog} = this.state; + this.stateSet({dialog: !dialog}); }; handleClickAway = () => { - this.stateSet({ dialog: false }); + this.stateSet({dialog: false}); }; render() { - const { activeUser } = this.props; - const { dialog, inProgress } = this.state; - const { upVoted, downVoted } = this.isVoted(); + const {activeUser} = this.props; + const {dialog, inProgress} = this.state; + const {upVoted, downVoted} = this.isVoted(); - let cls = _c(`btn-vote btn-up-vote ${inProgress ? "in-progress" : ""}`); + let cls = _c(`btn-vote btn-up-vote ${inProgress ? 'in-progress' : ''}`); if (upVoted || downVoted) { cls = _c( `btn-vote ${ - upVoted ? "btn-up-vote primary-btn-done" : "btn-down-vote secondary-btn-done" - } ${inProgress ? "in-progress" : ""} voted` + upVoted + ? 'btn-up-vote primary-btn-done' + : 'btn-down-vote secondary-btn-done' + } ${inProgress ? 'in-progress' : ''} voted`, ); } - let tooltipClass = ""; + let tooltipClass = ''; if (dialog) { if (!upVoted || !downVoted) { - cls = cls + " primary-btn secondary-btn"; + cls = cls + ' primary-btn secondary-btn'; } - tooltipClass = "tooltip-vote"; + tooltipClass = 'tooltip-vote'; } const voteBtnClass = `btn-inner ${ tooltipClass.length > 0 - ? upVoted ? "primary-btn-done" : downVoted - ? "secondary-btn-done" - : "primary-btn" - : "" + ? upVoted + ? 'primary-btn-done' + : downVoted + ? 'secondary-btn-done' + : 'primary-btn' + : '' }`; return ( @@ -463,32 +539,39 @@ export class EntryVoteBtn extends BaseComponent { ...this.props, children: (
- {dialog && this.setState({dialog:false})}}> -
-
-
- {chevronUpSvgForVote} - {activeUser && tooltipClass.length > 0 && ( -
- { - e.stopPropagation(); - }} - > - - -
)} + { + dialog && this.setState({dialog: false}); + }} + > +
+
+
+ + {chevronUpSvgForVote} + + {activeUser && tooltipClass.length > 0 && ( +
+ { + e.stopPropagation(); + }} + > + + +
+ )} +
-
- +
), })} diff --git a/src/common/components/entry-votes/index.spec.tsx b/src/common/components/entry-votes/index.spec.tsx index fddde5de9fb..8a22a1b0d2d 100644 --- a/src/common/components/entry-votes/index.spec.tsx +++ b/src/common/components/entry-votes/index.spec.tsx @@ -1,32 +1,36 @@ -import React from "react"; +import React from 'react'; -import EntryVotes, { EntryVotesDetail } from "./index"; -import renderer from "react-test-renderer"; -import { createBrowserHistory } from "history"; +import EntryVotes, {EntryVotesDetail} from './index'; +import renderer from 'react-test-renderer'; +import {createBrowserHistory} from 'history'; -import { globalInstance, entryInstance1, votesInstance1 } from "../../helper/test-helper"; +import { + globalInstance, + entryInstance1, + votesInstance1, +} from '../../helper/test-helper'; -jest.mock("../../constants/defaults.json", () => ({ - imageServer: "https://images.ecency.com", +jest.mock('../../constants/defaults.json', () => ({ + imageServer: 'https://images.ecency.com', })); -jest.mock("moment", () => () => ({ - fromNow: () => "3 days ago", - format: (f: string, s: string) => "2020-01-01 23:12:00", +jest.mock('moment', () => () => ({ + fromNow: () => '3 days ago', + format: (f: string, s: string) => '2020-01-01 23:12:00', })); -jest.mock("../../api/hive", () => ({ +jest.mock('../../api/hive', () => ({ getActiveVotes: () => - new Promise((resolve) => { + new Promise(resolve => { resolve(votesInstance1); }), })); -it("(1) Default render", () => { +it('(1) Default render', () => { const props = { history: createBrowserHistory(), - global: { ...globalInstance }, - entry: { ...entryInstance1 }, + global: {...globalInstance}, + entry: {...entryInstance1}, addAccount: (data: any) => {}, }; @@ -34,11 +38,11 @@ it("(1) Default render", () => { expect(component.toJSON()).toMatchSnapshot(); }); -it("(2) No votes", () => { +it('(2) No votes', () => { const props = { history: createBrowserHistory(), - global: { ...globalInstance }, - entry: { ...entryInstance1, ...{ active_votes: [] } }, + global: {...globalInstance}, + entry: {...entryInstance1, ...{active_votes: []}}, addAccount: (data: any) => {}, }; @@ -48,15 +52,15 @@ it("(2) No votes", () => { const detailProps = { history: createBrowserHistory(), - global: { ...globalInstance }, - entry: { ...entryInstance1 }, + global: {...globalInstance}, + entry: {...entryInstance1}, addAccount: (data: any) => {}, updateInputDisable: (data: any) => {}, - searchText: "" + searchText: '', }; const component = renderer.create(); -it("(3) Render of detail with votes", () => { +it('(3) Render of detail with votes', () => { expect(component.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/entry-votes/index.tsx b/src/common/components/entry-votes/index.tsx index def10a78898..b9f81cbcb6f 100644 --- a/src/common/components/entry-votes/index.tsx +++ b/src/common/components/entry-votes/index.tsx @@ -1,284 +1,356 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import {Form, FormControl} from "react-bootstrap"; +import {Form, FormControl} from 'react-bootstrap'; -import {History} from "history"; +import {History} from 'history'; -import moment from "moment"; +import moment from 'moment'; -import {Modal, Spinner} from "react-bootstrap"; +import {Modal, Spinner} from 'react-bootstrap'; -import {Global} from "../../store/global/types"; -import {Entry} from "../../store/entries/types"; -import {Account} from "../../store/accounts/types"; +import {Global} from '../../store/global/types'; +import {Entry} from '../../store/entries/types'; +import {Account} from '../../store/accounts/types'; -import BaseComponent from "../base"; -import UserAvatar from "../user-avatar/index"; -import FormattedCurrency from "../formatted-currency"; -import ProfileLink from "../profile-link/index"; -import Tooltip from "../tooltip"; -import Pagination from "../pagination"; +import BaseComponent from '../base'; +import UserAvatar from '../user-avatar/index'; +import FormattedCurrency from '../formatted-currency'; +import ProfileLink from '../profile-link/index'; +import Tooltip from '../tooltip'; +import Pagination from '../pagination'; -import {Vote, getActiveVotes} from "../../api/hive"; +import {Vote, getActiveVotes} from '../../api/hive'; -import parseAsset from "../../helper/parse-asset"; -import parseDate from "../../helper/parse-date"; -import accountReputation from "../../helper/account-reputation"; +import parseAsset from '../../helper/parse-asset'; +import parseDate from '../../helper/parse-date'; +import accountReputation from '../../helper/account-reputation'; -import formattedNumber from "../../util/formatted-number"; +import formattedNumber from '../../util/formatted-number'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import {heartSvg} from "../../img/svg"; +import {heartSvg} from '../../img/svg'; export const prepareVotes = (entry: Entry, votes: Vote[]): Vote[] => { - const totalPayout = - parseAsset(entry.pending_payout_value).amount + - parseAsset(entry.author_payout_value).amount + - parseAsset(entry.curator_payout_value).amount; - - const voteRshares = votes.reduce((a, b) => a + parseFloat(b.rshares), 0); - const ratio = totalPayout / voteRshares; - - return votes.map((a) => { - const rew = parseFloat(a.rshares) * ratio; - - return Object.assign({}, a, { - reward: rew, - timestamp: parseDate(a.time).getTime(), - percent: a.percent / 100, - }); - }) + const totalPayout = + parseAsset(entry.pending_payout_value).amount + + parseAsset(entry.author_payout_value).amount + + parseAsset(entry.curator_payout_value).amount; + + const voteRshares = votes.reduce((a, b) => a + parseFloat(b.rshares), 0); + const ratio = totalPayout / voteRshares; + + return votes.map(a => { + const rew = parseFloat(a.rshares) * ratio; + + return Object.assign({}, a, { + reward: rew, + timestamp: parseDate(a.time).getTime(), + percent: a.percent / 100, + }); + }); }; -type SortOption = "reward" | "timestamp" | "voter" | "percent"; +type SortOption = 'reward' | 'timestamp' | 'voter' | 'percent'; interface DetailProps { - history: History; - global: Global; - entry: Entry; - addAccount: (data: Account) => void; - updateInputDisable: (value: boolean) => void; - searchText: string; + history: History; + global: Global; + entry: Entry; + addAccount: (data: Account) => void; + updateInputDisable: (value: boolean) => void; + searchText: string; } interface DetailState { - loading: boolean; - votes: Vote[]; - originalVotes: Vote[]; - page: number; - sort: SortOption + loading: boolean; + votes: Vote[]; + originalVotes: Vote[]; + page: number; + sort: SortOption; } export class EntryVotesDetail extends BaseComponent { - state: DetailState = { - loading: false, - votes: [], - originalVotes: [], + state: DetailState = { + loading: false, + votes: [], + originalVotes: [], + page: 1, + sort: 'reward', + }; + + componentDidMount() { + const {entry, updateInputDisable} = this.props; + + this.stateSet({loading: true}); + getActiveVotes(entry.author, entry.permlink) + .then(r => { + this.setVotes(r); + }) + .finally(() => { + this.stateSet({loading: false}); + updateInputDisable(false); + }); + } + + setVotes = (data: Vote[]) => { + const {entry} = this.props; + this.stateSet({ + votes: prepareVotes(entry, data), + loading: false, + originalVotes: prepareVotes(entry, data), + }); + }; + + sortChanged = ( + e: React.ChangeEvent, + ) => { + this.stateSet({sort: e.target.value as SortOption}); + }; + + componentDidUpdate(prevProps: DetailProps) { + if (prevProps.searchText !== this.props.searchText) { + this.setState({ + votes: this.state.originalVotes.filter(item => + item.voter + .toLocaleLowerCase() + .includes(this.props.searchText.toLocaleLowerCase()), + ), page: 1, - sort: "reward" - }; - - componentDidMount() { - const { entry, updateInputDisable } = this.props; - - this.stateSet({loading: true}); - getActiveVotes(entry.author, entry.permlink) - .then((r) => { - this.setVotes(r); - }) - .finally(() => { - this.stateSet({loading: false}); - updateInputDisable(false) - }); + }); } + } - setVotes = (data: Vote[]) => { - const {entry} = this.props; - this.stateSet({ votes: prepareVotes(entry, data), loading: false, originalVotes: prepareVotes(entry, data) }); - }; + render() { + const {loading, votes, page, sort} = this.state; - sortChanged = (e: React.ChangeEvent) => { - this.stateSet({sort: e.target.value as SortOption}); - }; - - componentDidUpdate(prevProps: DetailProps){ - if(prevProps.searchText !== this.props.searchText){ - this.setState({ votes: this.state.originalVotes.filter(item=>item.voter.toLocaleLowerCase().includes(this.props.searchText.toLocaleLowerCase())), page: 1 }); - } + if (loading) { + return ( +
+ +
+ ); } - render() { - const {loading, votes, page, sort} = this.state; - - if (loading) { - return ( -
- -
- ); - } - - const pageSize = 12; - const start = (page - 1) * pageSize; - const end = start + pageSize; - - const sliced = votes.sort((a, b) => { - const keyA = a[sort]!; - const keyB = b[sort]!; - - if (keyA > keyB) return -1; - if (keyA < keyB) return 1; - return 0; - }).slice(start, end); - - return ( - <> -
-
- {sliced && sliced.length > 0 ? sliced.map(x => { - return
-
- {ProfileLink({ - ...this.props, - username: x.voter, - children: <>{UserAvatar({...this.props, username: x.voter, size: "small"})} - })} - -
- {ProfileLink({ - ...this.props, - username: x.voter, - children: {x.voter} - })} - {accountReputation(x.reputation)} -
-
-
- - - {formattedNumber(x.percent, {fractionDigits: 1, suffix: "%"})} - - - {moment(x.timestamp).fromNow()} - -
-
; - }): _t("communities.no-results")} -
-
- -
-
- {votes.length > pageSize && { - this.stateSet({page}); - }}/>} + const pageSize = 12; + const start = (page - 1) * pageSize; + const end = start + pageSize; + + const sliced = votes + .sort((a, b) => { + const keyA = a[sort]!; + const keyB = b[sort]!; + + if (keyA > keyB) return -1; + if (keyA < keyB) return 1; + return 0; + }) + .slice(start, end); + + return ( + <> +
+
+ {sliced && sliced.length > 0 + ? sliced.map(x => { + return ( +
+
+ {ProfileLink({ + ...this.props, + username: x.voter, + children: ( + <> + {UserAvatar({ + ...this.props, + username: x.voter, + size: 'small', + })} + + ), + })} + +
+ {ProfileLink({ + ...this.props, + username: x.voter, + children: ( + {x.voter} + ), + })} + + {accountReputation(x.reputation)} + +
+
+
+ + + {formattedNumber(x.percent, { + fractionDigits: 1, + suffix: '%', + })} + + + {moment(x.timestamp).fromNow()} + +
-
- {_t("entry-votes.sort")} - - - - - - -
-
- - ); - } + ); + }) + : _t('communities.no-results')} +
+
+ +
+
+ {votes.length > pageSize && ( + { + this.stateSet({page}); + }} + /> + )} +
+
+ {_t('entry-votes.sort')} + + + + + + +
+
+ + ); + } } interface Props { - history: History; - global: Global; - entry: Entry; - addAccount: (data: Account) => void; + history: History; + global: Global; + entry: Entry; + addAccount: (data: Account) => void; } interface State { - visible: boolean; - searchText: string; - searchTextDisabled: boolean; + visible: boolean; + searchText: string; + searchTextDisabled: boolean; } export class EntryVotes extends Component { - state: State = { - visible: false, - searchText: "", - searchTextDisabled: true - }; - - toggle = () => { - const { visible } = this.state; - this.setState({ visible: !visible, searchText: "" }); - }; - - render() { - const { entry } = this.props; - const { visible, searchText, searchTextDisabled } = this.state; - const totalVotes = entry.active_votes.length; - - const title = - totalVotes === 0 - ? _t("entry-votes.title-empty") - : totalVotes === 1 - ? _t("entry-votes.title") - : _t("entry-votes.title-n", {n: totalVotes}); - - const child = ( - <> - {heartSvg} {totalVotes} - - ); - - if (totalVotes === 0) { - return ( -
- - {child} - -
- ); - } - - return ( - <> -
- {child} -
- {visible && ( - - - {title} - - - { - this.setState({ searchText: e.target.value }); - }} - disabled={searchTextDisabled}/> - - - this.setState({searchTextDisabled: value})} /> - - - )} - - ); + state: State = { + visible: false, + searchText: '', + searchTextDisabled: true, + }; + + toggle = () => { + const {visible} = this.state; + this.setState({visible: !visible, searchText: ''}); + }; + + render() { + const {entry} = this.props; + const {visible, searchText, searchTextDisabled} = this.state; + const totalVotes = entry.active_votes.length; + + const title = + totalVotes === 0 + ? _t('entry-votes.title-empty') + : totalVotes === 1 + ? _t('entry-votes.title') + : _t('entry-votes.title-n', {n: totalVotes}); + + const child = ( + <> + {heartSvg} {totalVotes} + + ); + + if (totalVotes === 0) { + return ( +
+ + {child} + +
+ ); } + + return ( + <> +
+ + + {child} + + +
+ {visible && ( + + + {title} + + + { + this.setState({searchText: e.target.value}); + }} + disabled={searchTextDisabled} + /> + + + + this.setState({searchTextDisabled: value}) + } + /> + + + )} + + ); + } } export default (p: Props) => { - const props = { - history: p.history, - global: p.global, - entry: p.entry, - addAccount: p.addAccount - } - - return ; -} + const props = { + history: p.history, + global: p.global, + entry: p.entry, + addAccount: p.addAccount, + }; + + return ; +}; diff --git a/src/common/components/favorite-btn/index.spec.tsx b/src/common/components/favorite-btn/index.spec.tsx index 721c9bfb4bd..8df3114d42e 100644 --- a/src/common/components/favorite-btn/index.spec.tsx +++ b/src/common/components/favorite-btn/index.spec.tsx @@ -1,71 +1,67 @@ -import React from "react"; +import React from 'react'; -import {FavoriteBtn} from "./index"; +import {FavoriteBtn} from './index'; -import TestRenderer from "react-test-renderer"; +import TestRenderer from 'react-test-renderer'; -import {UiInstance, activeUserInstance, allOver} from "../../helper/test-helper"; +import { + UiInstance, + activeUserInstance, + allOver, +} from '../../helper/test-helper'; -let TEST_MODE = 0 +let TEST_MODE = 0; -jest.mock("../../api/private-api", () => ({ - checkFavorite: () => - new Promise((resolve) => { - if (TEST_MODE === 0) { - resolve(false); - } +jest.mock('../../api/private-api', () => ({ + checkFavorite: () => + new Promise(resolve => { + if (TEST_MODE === 0) { + resolve(false); + } - if (TEST_MODE === 1) { - resolve(true); - } - }), + if (TEST_MODE === 1) { + resolve(true); + } + }), })); const defProps = { - targetUsername: "bar", - activeUser: null, - users: [], - ui: UiInstance, - setActiveUser: () => { - }, - updateActiveUser: () => { - }, - deleteUser: () => { - }, - toggleUIProp: () => { - - } + targetUsername: 'bar', + activeUser: null, + users: [], + ui: UiInstance, + setActiveUser: () => {}, + updateActiveUser: () => {}, + deleteUser: () => {}, + toggleUIProp: () => {}, }; -it("(1) No active user", () => { - const props = {...defProps}; - const renderer = TestRenderer.create(); - expect(renderer.toJSON()).toMatchSnapshot(); +it('(1) No active user', () => { + const props = {...defProps}; + const renderer = TestRenderer.create(); + expect(renderer.toJSON()).toMatchSnapshot(); }); +it('(2) Not Favorited', async () => { + const props = { + ...defProps, + activeUser: {...activeUserInstance}, + }; -it("(2) Not Favorited", async () => { - const props = { - ...defProps, - activeUser: {...activeUserInstance} - }; - - const component = TestRenderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + const component = TestRenderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); +it('(3) Favorited', async () => { + TEST_MODE = 1; -it("(3) Favorited", async () => { - - TEST_MODE = 1; - - const props = { - ...defProps, - activeUser: {...activeUserInstance} - }; + const props = { + ...defProps, + activeUser: {...activeUserInstance}, + }; - const component = TestRenderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + const component = TestRenderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/favorite-btn/index.tsx b/src/common/components/favorite-btn/index.tsx index 1b88d814136..31dacf8922c 100644 --- a/src/common/components/favorite-btn/index.tsx +++ b/src/common/components/favorite-btn/index.tsx @@ -1,146 +1,164 @@ -import React from "react"; +import React from 'react'; -import {Button} from "react-bootstrap"; +import {Button} from 'react-bootstrap'; -import {Account} from "../../store/accounts/types"; -import {ActiveUser} from "../../store/active-user/types"; -import {ToggleType, UI} from "../../store/ui/types"; -import {User} from "../../store/users/types"; +import {Account} from '../../store/accounts/types'; +import {ActiveUser} from '../../store/active-user/types'; +import {ToggleType, UI} from '../../store/ui/types'; +import {User} from '../../store/users/types'; -import BaseComponent from "../base"; -import Tooltip from "../tooltip"; -import LoginRequired from "../login-required"; -import {error, success} from "../feedback"; +import BaseComponent from '../base'; +import Tooltip from '../tooltip'; +import LoginRequired from '../login-required'; +import {error, success} from '../feedback'; -import {checkFavorite, addFavorite, deleteFavorite} from "../../api/private-api"; +import { + checkFavorite, + addFavorite, + deleteFavorite, +} from '../../api/private-api'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import {starSvg, starOutlineSvg} from "../../img/svg"; +import {starSvg, starOutlineSvg} from '../../img/svg'; interface Props { - targetUsername: string; - activeUser: ActiveUser | null; - users: User[]; - ui: UI; - setActiveUser: (username: string | null) => void; - updateActiveUser: (data?: Account) => void; - deleteUser: (username: string) => void; - toggleUIProp: (what: ToggleType) => void; + targetUsername: string; + activeUser: ActiveUser | null; + users: User[]; + ui: UI; + setActiveUser: (username: string | null) => void; + updateActiveUser: (data?: Account) => void; + deleteUser: (username: string) => void; + toggleUIProp: (what: ToggleType) => void; } export interface State { - favorited: boolean - inProgress: boolean + favorited: boolean; + inProgress: boolean; } export class FavoriteBtn extends BaseComponent { - state: State = { - favorited: false, - inProgress: false + state: State = { + favorited: false, + inProgress: false, + }; + + componentDidMount() { + this.detect(); + } + + componentDidUpdate(prevProps: Readonly) { + const {activeUser, targetUsername} = this.props; + if ( + // active user changed + activeUser?.username !== prevProps.activeUser?.username || + // or targetUsername changed + targetUsername !== prevProps.targetUsername + ) { + this.detect(); } + } - componentDidMount() { - this.detect(); - } - - componentDidUpdate(prevProps: Readonly) { - const {activeUser, targetUsername} = this.props; - if ( - // active user changed - (activeUser?.username !== prevProps.activeUser?.username) || - // or targetUsername changed - (targetUsername !== prevProps.targetUsername) - ) { - this.detect(); - } - } - - detect = () => { - const {targetUsername, activeUser} = this.props; - if (!activeUser) { - this.stateSet({favorited: false}); - return; - } - - this.stateSet({inProgress: true}); - checkFavorite(activeUser?.username!, targetUsername).then(r => { - this.stateSet({favorited: r}); - }).finally(() => this.stateSet({inProgress: false})); - } - - add = () => { - const {activeUser, targetUsername} = this.props; - this.stateSet({inProgress: true}) - addFavorite(activeUser?.username!, targetUsername) - .then((r) => { - this.detect(); - success(_t('favorite-btn.added')); - }) - .catch(() => error(_t('g.server-error'))) - .finally(() => this.stateSet({inProgress: false})) + detect = () => { + const {targetUsername, activeUser} = this.props; + if (!activeUser) { + this.stateSet({favorited: false}); + return; } - delete = () => { - const {activeUser, targetUsername} = this.props; - const {favorited} = this.state; - - if (!favorited) { - return; - } - - this.stateSet({inProgress: true}); - deleteFavorite(activeUser?.username!, targetUsername) - .then(() => { - this.detect(); - success(_t('favorite-btn.deleted')); - }) - .catch(() => error(_t('g.server-error'))) - .finally(() => this.stateSet({inProgress: false})) + this.stateSet({inProgress: true}); + checkFavorite(activeUser?.username!, targetUsername) + .then(r => { + this.stateSet({favorited: r}); + }) + .finally(() => this.stateSet({inProgress: false})); + }; + + add = () => { + const {activeUser, targetUsername} = this.props; + this.stateSet({inProgress: true}); + addFavorite(activeUser?.username!, targetUsername) + .then(r => { + this.detect(); + success(_t('favorite-btn.added')); + }) + .catch(() => error(_t('g.server-error'))) + .finally(() => this.stateSet({inProgress: false})); + }; + + delete = () => { + const {activeUser, targetUsername} = this.props; + const {favorited} = this.state; + + if (!favorited) { + return; } - render() { - const {activeUser} = this.props; - const {favorited, inProgress} = this.state; - - if (!activeUser) { - return LoginRequired({ - ...this.props, - children: - - - - - }) - } - - if (favorited) { - return ( - - - - ); - } - - return ( + this.stateSet({inProgress: true}); + deleteFavorite(activeUser?.username!, targetUsername) + .then(() => { + this.detect(); + success(_t('favorite-btn.deleted')); + }) + .catch(() => error(_t('g.server-error'))) + .finally(() => this.stateSet({inProgress: false})); + }; + + render() { + const {activeUser} = this.props; + const {favorited, inProgress} = this.state; + + if (!activeUser) { + return LoginRequired({ + ...this.props, + children: ( + - + - ); + + ), + }); } -} -export default (p: Props) => { - const props: Props = { - targetUsername: p.targetUsername, - activeUser: p.activeUser, - users: p.users, - ui: p.ui, - setActiveUser: p.setActiveUser, - updateActiveUser: p.updateActiveUser, - deleteUser: p.deleteUser, - toggleUIProp: p.toggleUIProp + if (favorited) { + return ( + + + + + + ); } - return + return ( + + + + + + ); + } } + +export default (p: Props) => { + const props: Props = { + targetUsername: p.targetUsername, + activeUser: p.activeUser, + users: p.users, + ui: p.ui, + setActiveUser: p.setActiveUser, + updateActiveUser: p.updateActiveUser, + deleteUser: p.deleteUser, + toggleUIProp: p.toggleUIProp, + }; + + return ; +}; diff --git a/src/common/components/feedback/index.tsx b/src/common/components/feedback/index.tsx index 15ff0c7b367..5b2b1f1c51d 100644 --- a/src/common/components/feedback/index.tsx +++ b/src/common/components/feedback/index.tsx @@ -1,122 +1,121 @@ -import React from "react"; +import React from 'react'; -import BaseComponent from "../base"; +import BaseComponent from '../base'; -import random from "../../util/rnd"; +import random from '../../util/rnd'; -import {checkSvg, alertCircleSvg, informationSvg} from "../../img/svg"; +import {checkSvg, alertCircleSvg, informationSvg} from '../../img/svg'; export const error = (message: string) => { - const detail: FeedbackObject = { - id: random(), - type: "error", - message, - }; - const ev = new CustomEvent("feedback", {detail}); - window.dispatchEvent(ev); + const detail: FeedbackObject = { + id: random(), + type: 'error', + message, + }; + const ev = new CustomEvent('feedback', {detail}); + window.dispatchEvent(ev); }; export const success = (message: string) => { - const detail: FeedbackObject = { - id: random(), - type: "success", - message, - }; - const ev = new CustomEvent("feedback", {detail}); - window.dispatchEvent(ev); + const detail: FeedbackObject = { + id: random(), + type: 'success', + message, + }; + const ev = new CustomEvent('feedback', {detail}); + window.dispatchEvent(ev); }; export const info = (message: string) => { - const detail: FeedbackObject = { - id: random(), - type: "info", - message, - }; - const ev = new CustomEvent("feedback", {detail}); - window.dispatchEvent(ev); + const detail: FeedbackObject = { + id: random(), + type: 'info', + message, + }; + const ev = new CustomEvent('feedback', {detail}); + window.dispatchEvent(ev); }; -type FeedbackType = "error" | "success" | "info"; +type FeedbackType = 'error' | 'success' | 'info'; export interface FeedbackObject { - id: string; - type: FeedbackType; - message: string; + id: string; + type: FeedbackType; + message: string; } -interface Props { -} +interface Props {} interface State { - list: FeedbackObject[]; + list: FeedbackObject[]; } export default class Feedback extends BaseComponent { - state: State = { - list: [], - }; + state: State = { + list: [], + }; - componentDidMount() { - window.addEventListener("feedback", this.onFeedback); - } + componentDidMount() { + window.addEventListener('feedback', this.onFeedback); + } + + componentWillUnmount() { + super.componentWillUnmount(); + + window.removeEventListener('feedback', this.onFeedback); + } - componentWillUnmount() { - super.componentWillUnmount(); + onFeedback = (e: Event) => { + const detail: FeedbackObject = (e as CustomEvent).detail; - window.removeEventListener("feedback", this.onFeedback); + const {list} = this.state; + if (list.find(x => x.message === detail.message) !== undefined) { + return; } - onFeedback = (e: Event) => { - const detail: FeedbackObject = (e as CustomEvent).detail; - - const {list} = this.state; - if (list.find((x) => x.message === detail.message) !== undefined) { - return; - } - - const newList = [...list, detail]; - this.stateSet({list: newList}); - - setTimeout(() => { - const {list} = this.state; - const newList = list.filter((x) => x.id !== detail.id); - this.stateSet({list: newList}); - }, 5000); - }; - - render() { - const {list} = this.state; - - if (list.length === 0) { - return null; - } - return ( -
- {list.map((x) => { - switch (x.type) { - case "success": - return ( -
- {checkSvg} {x.message} -
- ); - case "error": - return ( -
- {alertCircleSvg} {x.message} -
- ); - case "info": - return ( -
- {informationSvg} {x.message} -
- ); - default: - return null; - } - })} -
- ); + const newList = [...list, detail]; + this.stateSet({list: newList}); + + setTimeout(() => { + const {list} = this.state; + const newList = list.filter(x => x.id !== detail.id); + this.stateSet({list: newList}); + }, 5000); + }; + + render() { + const {list} = this.state; + + if (list.length === 0) { + return null; } + return ( +
+ {list.map(x => { + switch (x.type) { + case 'success': + return ( +
+ {checkSvg} {x.message} +
+ ); + case 'error': + return ( +
+ {alertCircleSvg} {x.message} +
+ ); + case 'info': + return ( +
+ {informationSvg} {x.message} +
+ ); + default: + return null; + } + })} +
+ ); + } } diff --git a/src/common/components/follow-controls/index.spec.tsx b/src/common/components/follow-controls/index.spec.tsx index 9b2c3c33253..f18fcb5d823 100644 --- a/src/common/components/follow-controls/index.spec.tsx +++ b/src/common/components/follow-controls/index.spec.tsx @@ -1,112 +1,105 @@ import React from 'react'; -import renderer from "react-test-renderer"; +import renderer from 'react-test-renderer'; -import {UiInstance, activeUserMaker, allOver} from "../../helper/test-helper"; +import {UiInstance, activeUserMaker, allOver} from '../../helper/test-helper'; import FollowControls from './index'; let MOCK_MODE: number = 0; -jest.mock("../../api/bridge", () => ({ - getRelationshipBetweenAccounts: () => - new Promise((resolve) => { - - if (MOCK_MODE === 1) { - resolve({ - follows: false, - ignores: false, - is_blacklisted: false, - follows_blacklists: false - }); - } - - if (MOCK_MODE === 2) { - resolve({ - follows: true, - ignores: false, - is_blacklisted: false, - follows_blacklists: false - }); - } - - if (MOCK_MODE === 3) { - resolve({ - follows: false, - ignores: true, - is_blacklisted: false, - follows_blacklists: false - }); - } - }), +jest.mock('../../api/bridge', () => ({ + getRelationshipBetweenAccounts: () => + new Promise(resolve => { + if (MOCK_MODE === 1) { + resolve({ + follows: false, + ignores: false, + is_blacklisted: false, + follows_blacklists: false, + }); + } + + if (MOCK_MODE === 2) { + resolve({ + follows: true, + ignores: false, + is_blacklisted: false, + follows_blacklists: false, + }); + } + + if (MOCK_MODE === 3) { + resolve({ + follows: false, + ignores: true, + is_blacklisted: false, + follows_blacklists: false, + }); + } + }), })); const defProps = { - users: [], - activeUser: null, - targetUsername: 'bar', - ui: UiInstance, - setActiveUser: () => { - }, - updateActiveUser: () => { - }, - deleteUser: () => { - }, - toggleUIProp: () => { - - } + users: [], + activeUser: null, + targetUsername: 'bar', + ui: UiInstance, + setActiveUser: () => {}, + updateActiveUser: () => {}, + deleteUser: () => {}, + toggleUIProp: () => {}, }; describe('FollowControls', () => { - it('(1) Default render. No active user.', () => { - const props = {...defProps}; + it('(1) Default render. No active user.', () => { + const props = {...defProps}; - const component = renderer.create(); - expect(component.toJSON()).toMatchSnapshot(); - }); + const component = renderer.create(); + expect(component.toJSON()).toMatchSnapshot(); + }); - it('(2) Default Render with active account.', async () => { - MOCK_MODE = 1; + it('(2) Default Render with active account.', async () => { + MOCK_MODE = 1; - const activeUser = activeUserMaker("fooo1"); + const activeUser = activeUserMaker('fooo1'); - const props = { - ...defProps, - activeUser - }; + const props = { + ...defProps, + activeUser, + }; - const component = renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); - }); + const component = renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); + }); + it('(2) Following.', async () => { + MOCK_MODE = 2; - it('(2) Following.', async () => { - MOCK_MODE = 2; + const activeUser = activeUserMaker('fooo1'); - const activeUser = activeUserMaker("fooo1"); + const props = { + ...defProps, + activeUser, + }; - const props = { - ...defProps, - activeUser - }; + const component = renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); + }); - const component = renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); - }); + it('(2) Muted.', async () => { + MOCK_MODE = 3; - it('(2) Muted.', async () => { - MOCK_MODE = 3; + const activeUser = activeUserMaker('fooo1'); - const activeUser = activeUserMaker("fooo1"); + const props = { + ...defProps, + activeUser, + }; - const props = { - ...defProps, - activeUser - }; - - const component = renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); - }); + const component = renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); + }); }); diff --git a/src/common/components/follow-controls/index.tsx b/src/common/components/follow-controls/index.tsx index 898031eb361..9d4cdff3350 100644 --- a/src/common/components/follow-controls/index.tsx +++ b/src/common/components/follow-controls/index.tsx @@ -1,218 +1,234 @@ -import React from "react"; +import React from 'react'; -import {Button} from "react-bootstrap"; +import {Button} from 'react-bootstrap'; -import {Account} from "../../store/accounts/types"; -import {User} from "../../store/users/types"; -import {ActiveUser} from "../../store/active-user/types"; -import {UI, ToggleType} from "../../store/ui/types"; +import {Account} from '../../store/accounts/types'; +import {User} from '../../store/users/types'; +import {ActiveUser} from '../../store/active-user/types'; +import {UI, ToggleType} from '../../store/ui/types'; -import BaseComponent from "../base"; -import LoginRequired from "../login-required"; -import {error} from "../feedback"; +import BaseComponent from '../base'; +import LoginRequired from '../login-required'; +import {error} from '../feedback'; -import {getRelationshipBetweenAccounts} from "../../api/bridge"; -import {follow, unFollow, ignore, formatError} from "../../api/operations"; +import {getRelationshipBetweenAccounts} from '../../api/bridge'; +import {follow, unFollow, ignore, formatError} from '../../api/operations'; -import {_t} from "../../i18n"; -import * as ls from "../../util/local-storage"; +import {_t} from '../../i18n'; +import * as ls from '../../util/local-storage'; interface Props { - users: User[]; - activeUser: ActiveUser | null; - ui: UI; - targetUsername: string; - setActiveUser: (username: string | null) => void; - updateActiveUser: (data?: Account) => void; - deleteUser: (username: string) => void; - toggleUIProp: (what: ToggleType) => void; + users: User[]; + activeUser: ActiveUser | null; + ui: UI; + targetUsername: string; + setActiveUser: (username: string | null) => void; + updateActiveUser: (data?: Account) => void; + deleteUser: (username: string) => void; + toggleUIProp: (what: ToggleType) => void; } interface State { - fetching: boolean, - inProgress: boolean, - following: boolean, - muted: boolean + fetching: boolean; + inProgress: boolean; + following: boolean; + muted: boolean; } export default class FollowControls extends BaseComponent { - state: State = { - fetching: false, - inProgress: false, - following: false, - muted: false + state: State = { + fetching: false, + inProgress: false, + following: false, + muted: false, + }; + + componentDidMount() { + this.fetch().then(); + } + + componentDidUpdate(prevProps: Readonly) { + if ( + // active user changed + prevProps.activeUser?.username !== this.props.activeUser?.username || + // or target account username changed + prevProps.targetUsername !== this.props.targetUsername + ) { + this.fetch().then(); } + } - componentDidMount() { - this.fetch().then(); - } - - componentDidUpdate(prevProps: Readonly) { - if ( - // active user changed - (prevProps.activeUser?.username !== this.props.activeUser?.username) || - // or target account username changed - (prevProps.targetUsername !== this.props.targetUsername) - ) { - this.fetch().then() - } - } - - fetch = async () => { - this.stateSet({ - fetching: true, - inProgress: false, - following: false, - muted: false - }) + fetch = async () => { + this.stateSet({ + fetching: true, + inProgress: false, + following: false, + muted: false, + }); - const {activeUser, targetUsername} = this.props; + const {activeUser, targetUsername} = this.props; - if (!activeUser) { - this.stateSet({fetching: false}); - return; - } - - getRelationshipBetweenAccounts(activeUser.username, targetUsername).then(r => { - if (r) { - this.stateSet({following: r.follows, muted: r.ignores}); - } - }).finally(() => { - this.stateSet({fetching: false}); - }) + if (!activeUser) { + this.stateSet({fetching: false}); + return; } - follow = async () => { - const {activeUser, targetUsername} = this.props; - - this.stateSet({inProgress: true}); - try { - await follow(activeUser?.username!, targetUsername); - this.stateSet({following: true, muted: false}); - } catch (err) { - error(formatError(err)); - } finally { - this.stateSet({inProgress: false}); - } - }; - - unFollow = async () => { - const {activeUser, targetUsername} = this.props; - - this.stateSet({inProgress: true}); - try { - await unFollow(activeUser?.username!, targetUsername); - let muted_list = ls.get("muted-list") - if(muted_list){ - muted_list = muted_list.filter((item:string)=>item!==targetUsername); - } - ls.set("muted-list", muted_list); - this.stateSet({following: false, muted: false}); - } catch (err) { - error(formatError(err)); - } finally { - this.stateSet({inProgress: false}); - } - }; - - mute = async () => { - const {activeUser, targetUsername} = this.props; - - this.stateSet({inProgress: true}); - try { - await ignore(activeUser?.username!, targetUsername); - let muted_list = [targetUsername]; - if(ls.get("muted-list")){ - muted_list = ls.get("muted-list").concat(muted_list); - } - ls.set("muted-list",muted_list); - this.stateSet({following: false, muted: true}); - } catch (err) { - error(formatError(err)); - } finally { - this.stateSet({inProgress: false}); - } - }; - - render() { - const {following, muted, fetching, inProgress} = this.state; - const followMsg = _t('follow-controls.follow'); - const unFollowMsg = _t('follow-controls.unFollow'); - const muteMsg = _t('follow-controls.mute'); - const unMuteMsg = _t('follow-controls.unMute'); - - const btnFollow = LoginRequired({ - ...this.props, - children: - }); - - const btnUnfollow = LoginRequired({ - ...this.props, - children: - }); - - const btnMute = LoginRequired({ - ...this.props, - children: - }); - - const btnUnMute = LoginRequired({ - ...this.props, - children: - }); - - if (fetching) { - return ( - <> - - - - ); - } - - if (following) { - return ( - <> - {btnUnfollow} - {btnMute} - - ); + getRelationshipBetweenAccounts(activeUser.username, targetUsername) + .then(r => { + if (r) { + this.stateSet({following: r.follows, muted: r.ignores}); } + }) + .finally(() => { + this.stateSet({fetching: false}); + }); + }; + + follow = async () => { + const {activeUser, targetUsername} = this.props; + + this.stateSet({inProgress: true}); + try { + await follow(activeUser?.username!, targetUsername); + this.stateSet({following: true, muted: false}); + } catch (err) { + error(formatError(err)); + } finally { + this.stateSet({inProgress: false}); + } + }; + + unFollow = async () => { + const {activeUser, targetUsername} = this.props; + + this.stateSet({inProgress: true}); + try { + await unFollow(activeUser?.username!, targetUsername); + let muted_list = ls.get('muted-list'); + if (muted_list) { + muted_list = muted_list.filter( + (item: string) => item !== targetUsername, + ); + } + ls.set('muted-list', muted_list); + this.stateSet({following: false, muted: false}); + } catch (err) { + error(formatError(err)); + } finally { + this.stateSet({inProgress: false}); + } + }; + + mute = async () => { + const {activeUser, targetUsername} = this.props; + + this.stateSet({inProgress: true}); + try { + await ignore(activeUser?.username!, targetUsername); + let muted_list = [targetUsername]; + if (ls.get('muted-list')) { + muted_list = ls.get('muted-list').concat(muted_list); + } + ls.set('muted-list', muted_list); + this.stateSet({following: false, muted: true}); + } catch (err) { + error(formatError(err)); + } finally { + this.stateSet({inProgress: false}); + } + }; + + render() { + const {following, muted, fetching, inProgress} = this.state; + const followMsg = _t('follow-controls.follow'); + const unFollowMsg = _t('follow-controls.unFollow'); + const muteMsg = _t('follow-controls.mute'); + const unMuteMsg = _t('follow-controls.unMute'); + + const btnFollow = LoginRequired({ + ...this.props, + children: ( + + ), + }); + + const btnUnfollow = LoginRequired({ + ...this.props, + children: ( + + ), + }); + + const btnMute = LoginRequired({ + ...this.props, + children: ( + + ), + }); + + const btnUnMute = LoginRequired({ + ...this.props, + children: ( + + ), + }); + + if (fetching) { + return ( + <> + + + + ); + } - if (muted) { - return ( - <> - {btnFollow} - {btnUnMute} - - ); - } + if (following) { + return ( + <> + {btnUnfollow} + {btnMute} + + ); + } - return ( - <> - {btnFollow} - {btnMute} - - ); + if (muted) { + return ( + <> + {btnFollow} + {btnUnMute} + + ); } + + return ( + <> + {btnFollow} + {btnMute} + + ); + } } diff --git a/src/common/components/formatted-currency/index.tsx b/src/common/components/formatted-currency/index.tsx index 4bd38596546..bc4bf92de2f 100644 --- a/src/common/components/formatted-currency/index.tsx +++ b/src/common/components/formatted-currency/index.tsx @@ -1,8 +1,8 @@ -import React, { Component } from "react"; +import React, {Component} from 'react'; -import { Global } from "../../store/global/types"; +import {Global} from '../../store/global/types'; -import formattedNumber from "../../util/formatted-number"; +import formattedNumber from '../../util/formatted-number'; interface Props { global: Global; @@ -16,11 +16,18 @@ export default class FormattedCurrency extends Component { }; render() { - const { global, value, fixAt } = this.props; - const { currencyRate, currencySymbol } = global; + const {global, value, fixAt} = this.props; + const {currencyRate, currencySymbol} = global; const valInCurrency = value * currencyRate; - return <>{formattedNumber(valInCurrency, { fractionDigits: fixAt, prefix: currencySymbol })}; + return ( + <> + {formattedNumber(valInCurrency, { + fractionDigits: fixAt, + prefix: currencySymbol, + })} + + ); } } diff --git a/src/common/components/fragments/index.spec.tsx b/src/common/components/fragments/index.spec.tsx index b975c78f4bf..edbc3c11cb1 100644 --- a/src/common/components/fragments/index.spec.tsx +++ b/src/common/components/fragments/index.spec.tsx @@ -1,110 +1,105 @@ import * as React from 'react'; -import renderer from "react-test-renderer"; +import renderer from 'react-test-renderer'; import {Fragments, AddFragment, EditFragment} from './index'; -import {activeUserInstance, fullAccountInstance, communityInstance1, allOver} from "../../helper/test-helper"; - -let TEST_MODE = 0 - -jest.mock("../../api/private-api", () => ({ - getFragments: () => - new Promise((resolve) => { - if (TEST_MODE === 0) { - resolve([]); - } - - if (TEST_MODE === 1) { - resolve([ - { - id: "id1", - title: "foo", - body: "lorem ipsum dolor sit amet", - created: "2020-10-10T10:00:00", - modified: "2020-10-10T10:00:00" - }, - { - id: "id2", - title: "bar", - body: "lorem ipsum dolor sit amet", - created: "2020-10-10T10:00:00", - modified: "2020-10-10T10:00:00" - }, - { - id: "id3", - title: "baz", - body: "lorem ipsum dolor sit amet", - created: "2020-10-10T10:00:00", - modified: "2020-10-10T10:00:00" - } - ]) - } - }), +import { + activeUserInstance, + fullAccountInstance, + communityInstance1, + allOver, +} from '../../helper/test-helper'; + +let TEST_MODE = 0; + +jest.mock('../../api/private-api', () => ({ + getFragments: () => + new Promise(resolve => { + if (TEST_MODE === 0) { + resolve([]); + } + + if (TEST_MODE === 1) { + resolve([ + { + id: 'id1', + title: 'foo', + body: 'lorem ipsum dolor sit amet', + created: '2020-10-10T10:00:00', + modified: '2020-10-10T10:00:00', + }, + { + id: 'id2', + title: 'bar', + body: 'lorem ipsum dolor sit amet', + created: '2020-10-10T10:00:00', + modified: '2020-10-10T10:00:00', + }, + { + id: 'id3', + title: 'baz', + body: 'lorem ipsum dolor sit amet', + created: '2020-10-10T10:00:00', + modified: '2020-10-10T10:00:00', + }, + ]); + } + }), })); const activeUser = {...activeUserInstance, data: fullAccountInstance}; - it('(1) Default render.', async () => { - const props = { - activeUser: {...activeUser}, - onHide: () => { - } - }; - - const component = renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + const props = { + activeUser: {...activeUser}, + onHide: () => {}, + }; + + const component = renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); it('(2) With data.', async () => { + TEST_MODE = 1; - TEST_MODE = 1; + const props = { + activeUser: {...activeUser}, + onHide: () => {}, + }; - const props = { - activeUser: {...activeUser}, - onHide: () => { - } - }; - - const component = renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + const component = renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); it('(3) Add', async () => { - const props = { - activeUser: {...activeUser}, - onAdd: () => { - }, - onCancel: () => { - - } - }; - - const component = renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + const props = { + activeUser: {...activeUser}, + onAdd: () => {}, + onCancel: () => {}, + }; + + const component = renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); it('(4) Edit', async () => { - const props = { - activeUser: {...activeUser}, - item: { - id: "id3", - title: "baz", - body: "lorem ipsum dolor sit amet", - created: "2020-10-10T10:00:00", - modified: "2020-10-10T10:00:00", - }, - onUpdate: () => { - }, - onCancel: () => { - - } - }; - - const component = renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + const props = { + activeUser: {...activeUser}, + item: { + id: 'id3', + title: 'baz', + body: 'lorem ipsum dolor sit amet', + created: '2020-10-10T10:00:00', + modified: '2020-10-10T10:00:00', + }, + onUpdate: () => {}, + onCancel: () => {}, + }; + + const component = renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/fragments/index.tsx b/src/common/components/fragments/index.tsx index e1410af5263..37b891f3ada 100644 --- a/src/common/components/fragments/index.tsx +++ b/src/common/components/fragments/index.tsx @@ -1,401 +1,529 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import {Button, Form, FormControl, Modal, Spinner} from "react-bootstrap"; +import {Button, Form, FormControl, Modal, Spinner} from 'react-bootstrap'; -import {ActiveUser} from "../../store/active-user/types"; +import {ActiveUser} from '../../store/active-user/types'; -import BaseComponent from "../base"; -import LinearProgress from "../linear-progress"; -import {error} from "../feedback"; +import BaseComponent from '../base'; +import LinearProgress from '../linear-progress'; +import {error} from '../feedback'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import {postBodySummary} from "@ecency/render-helper"; +import {postBodySummary} from '@ecency/render-helper'; -import {getFragments, Fragment, addFragment, deleteFragment, updateFragment} from "../../api/private-api"; +import { + getFragments, + Fragment, + addFragment, + deleteFragment, + updateFragment, +} from '../../api/private-api'; -import PopoverConfirm from "../popover-confirm"; -import { handleInvalid, handleOnInput } from "../../util/input-util"; +import PopoverConfirm from '../popover-confirm'; +import {handleInvalid, handleOnInput} from '../../util/input-util'; // ADD interface AddProps { - activeUser: ActiveUser; - onAdd: () => void; - onCancel: () => void; + activeUser: ActiveUser; + onAdd: () => void; + onCancel: () => void; } interface AddState { - title: string; - body: string; - inProgress: boolean; + title: string; + body: string; + inProgress: boolean; } export class AddFragment extends BaseComponent { - state: AddState = { - title: "", - body: "", - inProgress: false - } - - form = React.createRef(); - - titleChanged = (e: React.ChangeEvent) => { - this.stateSet({title: e.target.value}); - } - - bodyChanged = (e: React.ChangeEvent) => { - this.stateSet({body: e.target.value}); - } - - back = () => { - const {onCancel} = this.props; - onCancel(); - } - - render() { - const {title, body, inProgress} = this.state; - - return
-
{ - e.preventDefault(); - e.stopPropagation(); - - if (!this.form.current?.checkValidity()) { - return; - } - - const {activeUser, onAdd} = this.props; - this.stateSet({inProgress: true}) - addFragment(activeUser.username, title, body).then(() => { - onAdd() - }).catch(() => { - error(_t('g.server-error')); - }).finally(() => { - this.stateSet({inProgress: false}); - }); - }}> - - {_t("fragments.form-title")} - handleInvalid(e, "fragments.", 'validation-title')} - onInput={handleOnInput} - /> - - - {_t("fragments.form-body")} - handleInvalid(e, "fragments.", 'validation-value')} - onInput={handleOnInput} - as="textarea" - style={{height: "300px"}} - value={body} - onChange={this.bodyChanged} - required={true} - type="text" - maxLength={5000} - /> - -
- - -
-
-
- } + state: AddState = { + title: '', + body: '', + inProgress: false, + }; + + form = React.createRef(); + + titleChanged = ( + e: React.ChangeEvent, + ) => { + this.stateSet({title: e.target.value}); + }; + + bodyChanged = ( + e: React.ChangeEvent, + ) => { + this.stateSet({body: e.target.value}); + }; + + back = () => { + const {onCancel} = this.props; + onCancel(); + }; + + render() { + const {title, body, inProgress} = this.state; + + return ( +
+
{ + e.preventDefault(); + e.stopPropagation(); + + if (!this.form.current?.checkValidity()) { + return; + } + + const {activeUser, onAdd} = this.props; + this.stateSet({inProgress: true}); + addFragment(activeUser.username, title, body) + .then(() => { + onAdd(); + }) + .catch(() => { + error(_t('g.server-error')); + }) + .finally(() => { + this.stateSet({inProgress: false}); + }); + }} + > + + {_t('fragments.form-title')} + + handleInvalid(e, 'fragments.', 'validation-title') + } + onInput={handleOnInput} + /> + + + {_t('fragments.form-body')} + + handleInvalid(e, 'fragments.', 'validation-value') + } + onInput={handleOnInput} + as='textarea' + style={{height: '300px'}} + value={body} + onChange={this.bodyChanged} + required={true} + type='text' + maxLength={5000} + /> + +
+ + +
+
+
+ ); + } } // EDIT interface EditProps { - activeUser: ActiveUser; - item: Fragment; - onUpdate: () => void; - onCancel: () => void; + activeUser: ActiveUser; + item: Fragment; + onUpdate: () => void; + onCancel: () => void; } interface EditState { - title: string; - body: string; - inProgress: boolean; + title: string; + body: string; + inProgress: boolean; } export class EditFragment extends BaseComponent { - state: EditState = { - title: this.props.item.title, - body: this.props.item.body, - inProgress: false - } - - form = React.createRef(); - - titleChanged = (e: React.ChangeEvent) => { - this.stateSet({title: e.target.value}); - } - - bodyChanged = (e: React.ChangeEvent) => { - this.stateSet({body: e.target.value}); - } - - back = () => { - const {onCancel} = this.props; - onCancel(); - } - - delete = () => { - const {activeUser, item, onUpdate} = this.props; - this.stateSet({inProgress: true}) - deleteFragment(activeUser.username, item.id).then(() => { - onUpdate(); - }).catch(() => { - error(_t('g.server-error')); - }).finally(() => { - this.stateSet({inProgress: false}); - }); - } - - render() { - const {title, body, inProgress} = this.state; - - return <> -
{ - e.preventDefault(); - e.stopPropagation(); - - if (!this.form.current?.checkValidity()) { - return; - } - - const {activeUser, item, onUpdate} = this.props; - this.stateSet({inProgress: true}) - updateFragment(activeUser.username, item.id, title, body).then(() => { - onUpdate(); - }).catch(() => { - error(_t('g.server-error')); - }).finally(() => { - this.stateSet({inProgress: false}); - }); - }}> - - {_t("fragments.form-title")} - handleInvalid(e, 'fragments.', 'validation-title')} - onInput={handleOnInput} + state: EditState = { + title: this.props.item.title, + body: this.props.item.body, + inProgress: false, + }; + + form = React.createRef(); + + titleChanged = ( + e: React.ChangeEvent, + ) => { + this.stateSet({title: e.target.value}); + }; + + bodyChanged = ( + e: React.ChangeEvent, + ) => { + this.stateSet({body: e.target.value}); + }; + + back = () => { + const {onCancel} = this.props; + onCancel(); + }; + + delete = () => { + const {activeUser, item, onUpdate} = this.props; + this.stateSet({inProgress: true}); + deleteFragment(activeUser.username, item.id) + .then(() => { + onUpdate(); + }) + .catch(() => { + error(_t('g.server-error')); + }) + .finally(() => { + this.stateSet({inProgress: false}); + }); + }; + + render() { + const {title, body, inProgress} = this.state; + + return ( + <> + { + e.preventDefault(); + e.stopPropagation(); + + if (!this.form.current?.checkValidity()) { + return; + } + + const {activeUser, item, onUpdate} = this.props; + this.stateSet({inProgress: true}); + updateFragment(activeUser.username, item.id, title, body) + .then(() => { + onUpdate(); + }) + .catch(() => { + error(_t('g.server-error')); + }) + .finally(() => { + this.stateSet({inProgress: false}); + }); + }} + > + + {_t('fragments.form-title')} + + handleInvalid(e, 'fragments.', 'validation-title') + } + onInput={handleOnInput} + /> + + + {_t('fragments.form-body')} + + handleInvalid(e, 'fragments.', 'validation-body') + } + onInput={handleOnInput} + /> + +
+
+ { + this.delete(); + }} + > + + + +
+ - - -
- -
- - - } + )} + {_t('g.update')} + +
+ + + ); + } } - // LIST interface Props { - activeUser: ActiveUser; - onHide: () => void; - onPick?: (body: string) => void; + activeUser: ActiveUser; + onHide: () => void; + onPick?: (body: string) => void; } interface State { - loading: boolean, - list: Fragment[], - filter: string, - mode: "" | "add" | "edit", - editingItem?: Fragment, - innerRef: any + loading: boolean; + list: Fragment[]; + filter: string; + mode: '' | 'add' | 'edit'; + editingItem?: Fragment; + innerRef: any; } export class Fragments extends BaseComponent { - state: State = { - loading: true, - list: [], - filter: "", - innerRef: React.createRef(), - mode: "" + state: State = { + loading: true, + list: [], + filter: '', + innerRef: React.createRef(), + mode: '', + }; + + componentDidMount() { + this.fetch(); + } + + componentDidUpdate(prevProps: Props, prevState: State) { + if ( + this.state.loading !== prevState.loading && + !this.state.loading && + this.state.list.length > 0 + ) { + this.state!.innerRef!.current && this.state!.innerRef!.current!.focus(); } - - componentDidMount() { - this.fetch(); + } + + fetch = () => { + const {activeUser} = this.props; + + this.stateSet({loading: true}); + getFragments(activeUser?.username!) + .then(items => { + this.stateSet({list: this.sort(items)}); + }) + .catch(() => { + error(_t('g.server-error')); + }) + .finally(() => { + this.stateSet({loading: false}); + }); + }; + + sort = (items: Fragment[]) => + items.sort((a, b) => { + return new Date(b.created).getTime() > new Date(a.created).getTime() + ? 1 + : -1; + }); + + filterChanged = ( + e: React.ChangeEvent, + ): void => { + const {value} = e.target; + this.stateSet({filter: value}); + }; + + render() { + const {list, filter, loading, mode, editingItem, innerRef} = this.state; + + if (mode === 'add') { + return ( + { + this.stateSet({mode: ''}, () => { + this.fetch(); + }); + }} + onCancel={() => { + this.stateSet({mode: ''}); + }} + /> + ); } - componentDidUpdate(prevProps:Props, prevState: State){ - if(this.state.loading !== prevState.loading && !this.state.loading && this.state.list.length > 0){ - this.state!.innerRef!.current && this.state!.innerRef!.current!.focus() - } - } - - fetch = () => { - const {activeUser} = this.props; - - this.stateSet({loading: true}); - getFragments(activeUser?.username!).then(items => { - this.stateSet({list: this.sort(items)}); - }).catch(() => { - error(_t('g.server-error')); - }).finally(() => { - this.stateSet({loading: false}); - }); - } - - sort = (items: Fragment[]) => - items.sort((a, b) => { - return new Date(b.created).getTime() > new Date(a.created).getTime() ? 1 : -1; - }); - - filterChanged = (e: React.ChangeEvent): void => { - const {value} = e.target; - this.stateSet({filter: value}); + if (mode === 'edit' && editingItem) { + return ( + { + this.stateSet({mode: ''}, () => { + this.fetch(); + }); + }} + onCancel={() => { + this.stateSet({mode: ''}); + }} + /> + ); } - render() { - const {list, filter, loading, mode, editingItem, innerRef} = this.state; - - if (mode === "add") { - return { - this.stateSet({mode: ""}, () => { - this.fetch(); - }); - }} - onCancel={() => { - this.stateSet({mode: ""}); - }}/> - } - - if (mode === "edit" && editingItem) { - return { - this.stateSet({mode: ""}, () => { - this.fetch(); - }); - }} - onCancel={() => { - this.stateSet({mode: ""}); - }}/> - } - - return
- - {(() => { - if (loading) { - return - } - - if (list.length === 0) { - return - } - - const items = list.filter(x => x.title.toLowerCase().indexOf(filter.toLowerCase()) !== -1); - - return <> -
- -
-
- - {items.length === 0 && {_t("g.no-matches")}} - - {items.length > 0 && ( -
- {items.map(item => { - const summary = postBodySummary(item.body, 200); - - return
{ - const {onPick} = this.props; - if (onPick) { - onPick(item.body); - return; - } - - this.setState({editingItem: item, mode: "edit"}); - }}> -
{item.title}
-
{summary}
-
- })} -
- )} - ; - })()} -
- } + return ( +
+ {(() => { + if (loading) { + return ; + } + + if (list.length === 0) { + return ( + + ); + } + + const items = list.filter( + x => x.title.toLowerCase().indexOf(filter.toLowerCase()) !== -1, + ); + + return ( + <> +
+ +
+ +
+
+ + {items.length === 0 && ( + {_t('g.no-matches')} + )} + + {items.length > 0 && ( +
+ {items.map(item => { + const summary = postBodySummary(item.body, 200); + + return ( +
{ + const {onPick} = this.props; + if (onPick) { + onPick(item.body); + return; + } + + this.setState({editingItem: item, mode: 'edit'}); + }} + > +
{item.title}
+
{summary}
+
+ ); + })} +
+ )} + + ); + })()} +
+ ); + } } - export default class FragmentsDialog extends Component { - hide = () => { - const {onHide} = this.props; - onHide(); - } - - render() { - return ( - - - {_t('fragments.title')} - - - - - - ); - } + hide = () => { + const {onHide} = this.props; + onHide(); + }; + + render() { + return ( + + + {_t('fragments.title')} + + + + + + ); + } } diff --git a/src/common/components/friends/index.spec.tsx b/src/common/components/friends/index.spec.tsx index 0f13d92f1a0..ee04c99bbd7 100644 --- a/src/common/components/friends/index.spec.tsx +++ b/src/common/components/friends/index.spec.tsx @@ -1,52 +1,52 @@ -import React from "react"; +import React from 'react'; -import { List } from "./index"; +import {List} from './index'; -import renderer from "react-test-renderer"; +import renderer from 'react-test-renderer'; -import { createBrowserHistory } from "history"; +import {createBrowserHistory} from 'history'; -import {globalInstance} from "../../helper/test-helper"; +import {globalInstance} from '../../helper/test-helper'; -jest.mock("../../constants/defaults.json", () => ({ - imageServer: "https://images.ecency.com", +jest.mock('../../constants/defaults.json', () => ({ + imageServer: 'https://images.ecency.com', })); -jest.mock("../../api/hive", () => ({ +jest.mock('../../api/hive', () => ({ getFollowers: () => - new Promise((resolve) => { + new Promise(resolve => { resolve([ { - follower: "foo", - following: "user1", - what: ["blog"], + follower: 'foo', + following: 'user1', + what: ['blog'], }, { - follower: "bar", - following: "user2", - what: ["blog"], + follower: 'bar', + following: 'user2', + what: ['blog'], }, { - follower: "baz", - following: "user3", - what: ["blog"], + follower: 'baz', + following: 'user3', + what: ['blog'], }, ]); }), getAccounts: () => - new Promise((resolve) => { + new Promise(resolve => { resolve([ { - name: "user1", - profile: { name: "User One" }, + name: 'user1', + profile: {name: 'User One'}, }, { - name: "user2", - profile: { name: "User Two" }, + name: 'user2', + profile: {name: 'User Two'}, }, { - name: "user3", - profile: { name: "User Three" }, + name: 'user3', + profile: {name: 'User Three'}, }, ]); }), @@ -55,12 +55,12 @@ jest.mock("../../api/hive", () => ({ const props = { global: globalInstance, history: createBrowserHistory(), - account: { name: "foo" }, + account: {name: 'foo'}, addAccount: () => {}, }; -const component = renderer.create(); +const component = renderer.create(); -it("(1) Render list", () => { +it('(1) Render list', () => { expect(component.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/friends/index.tsx b/src/common/components/friends/index.tsx index e3e67341722..fd945bc6b41 100644 --- a/src/common/components/friends/index.tsx +++ b/src/common/components/friends/index.tsx @@ -1,290 +1,343 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import {History} from "history"; +import {History} from 'history'; -import {Modal, Button, FormControl} from "react-bootstrap"; +import {Modal, Button, FormControl} from 'react-bootstrap'; -import {Global} from "../../store/global/types"; -import {Account} from "../../store/accounts/types"; +import {Global} from '../../store/global/types'; +import {Account} from '../../store/accounts/types'; -import BaseComponent from "../base"; -import ProfileLink from "../profile-link"; -import UserAvatar from "../user-avatar"; -import LinearProgress from "../linear-progress"; +import BaseComponent from '../base'; +import ProfileLink from '../profile-link'; +import UserAvatar from '../user-avatar'; +import LinearProgress from '../linear-progress'; -import {getFollowing, getFollowers, getAccounts} from "../../api/hive"; -import {searchFollowing, searchFollower, FriendSearchResult} from "../../api/search-api"; +import {getFollowing, getFollowers, getAccounts} from '../../api/hive'; +import { + searchFollowing, + searchFollower, + FriendSearchResult, +} from '../../api/search-api'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import accountReputation from "../../helper/account-reputation"; -import formattedNumber from "../../util/formatted-number"; +import accountReputation from '../../helper/account-reputation'; +import formattedNumber from '../../util/formatted-number'; interface Friend { - name: string; - reputation: string | number; + name: string; + reputation: string | number; } interface ListProps { - global: Global; - history: History; - account: Account; - mode: "follower" | "following"; - addAccount: (data: Account) => void; + global: Global; + history: History; + account: Account; + mode: 'follower' | 'following'; + addAccount: (data: Account) => void; } interface ListState { - loading: boolean; - data: Friend[]; - results: Friend[]; - hasMore: boolean; - search: string; + loading: boolean; + data: Friend[]; + results: Friend[]; + hasMore: boolean; + search: string; } const loadLimit = 30; export class List extends BaseComponent { - state: ListState = { - loading: false, - data: [], - results: [], - hasMore: false, - search: "", - }; + state: ListState = { + loading: false, + data: [], + results: [], + hasMore: false, + search: '', + }; + + _timer: any = null; + + componentDidMount() { + this.fetchFirst().then(); + } + + fKey = () => { + const {mode} = this.props; + + return mode === 'following' ? 'following' : 'follower'; + }; + + fetch = async (start = '', limit = loadLimit): Promise => { + const {account, mode} = this.props; + + const loadFn = mode === 'following' ? getFollowing : getFollowers; + + return loadFn(account.name, start, 'blog', limit) + .then(resp => { + const accountNames = resp.map(e => e[this.fKey()]); + return getAccounts(accountNames).then(resp2 => resp2); + }) + .then(accounts => + accounts.map(a => ({ + name: a.name, + reputation: a.reputation!, + })), + ); + }; + + fetchFirst = async () => { + this.stateSet({loading: true, data: [], hasMore: false}); + let data: Friend[]; + + try { + data = await this.fetch(); + } catch (e) { + data = []; + } + + this.stateSet({ + data, + hasMore: data.length >= loadLimit, + loading: false, + }); + }; + + fetchMore = async () => { + const {data} = this.state; + const lastItem = [...data].pop()!; + const startUserName = lastItem.name; + + this.stateSet({loading: true}); + + let moreData: Friend[]; + + try { + moreData = await this.fetch(startUserName); + } catch (e) { + moreData = []; + } - _timer: any = null; + const newData = [ + ...data, + ...moreData.filter(a => !data.find(b => b.name === a.name)), + ]; - componentDidMount() { - this.fetchFirst().then(); + this.stateSet({ + data: newData, + hasMore: moreData.length >= loadLimit, + loading: false, + }); + }; + + search = async () => { + const {search} = this.state; + + if (search.length < 3) { + this.stateSet({ + results: [], + loading: false, + }); + return; } - fKey = () => { - const {mode} = this.props; - - return mode === "following" ? "following" : "follower"; - }; - - fetch = async (start = "", limit = loadLimit): Promise => { - const {account, mode} = this.props; - - const loadFn = (mode === "following" ? getFollowing : getFollowers); - - return loadFn(account.name, start, "blog", limit) - .then((resp) => { - const accountNames = resp.map((e) => e[this.fKey()]); - return getAccounts(accountNames).then((resp2) => resp2); - }) - .then((accounts) => - accounts.map((a) => ({ - name: a.name, - reputation: a.reputation! - })) - ); - }; - - fetchFirst = async () => { - this.stateSet({loading: true, data: [], hasMore: false}); - let data: Friend[]; - - try { - data = await this.fetch(); - } catch (e) { - data = []; - } - - this.stateSet({ - data, - hasMore: data.length >= loadLimit, - loading: false, - }); - }; - - fetchMore = async () => { - const {data} = this.state; - const lastItem = [...data].pop()!; - const startUserName = lastItem.name; - - this.stateSet({loading: true}); - - let moreData: Friend[]; - - try { - moreData = await this.fetch(startUserName); - } catch (e) { - moreData = []; - } - - const newData = [...data, ...moreData.filter((a) => !data.find((b) => b.name === a.name))]; - - this.stateSet({ - data: newData, - hasMore: moreData.length >= loadLimit, - loading: false, - }); - }; - - search = async () => { - const {search} = this.state; - - if (search.length < 3) { - this.stateSet({ - results: [], - loading: false, - }); - return; - } - - this.stateSet({loading: true}); - - const {account, mode} = this.props; - - let results: FriendSearchResult[]; - - try { - if (mode === "following") { - results = await searchFollowing(account.name, search); - } else { - results = await searchFollower(account.name, search); - } - } catch (e) { - results = []; - } - - this.stateSet({ - results: results, - loading: false, - }); - }; - - searchChanged = (e: React.ChangeEvent) => { - clearTimeout(this._timer); - - this.stateSet({search: e.target.value.trim(), loading: true}, () => { - this._timer = setTimeout(() => { - this.search().then(); - }, 500); - }); - }; - - searchKeyDown = (e: React.KeyboardEvent) => { - if (e.keyCode === 13) { - this.search(); - } - }; - - renderList = (loading: boolean, list: Friend[]) => { - return <> - {!loading && list.length === 0 &&
{_t("g.empty-list")}
} - - {list.map((item) => ( -
-
- {ProfileLink({ - ...this.props, - username: item.name, - children: <>{UserAvatar({...this.props, username: item.name, size: "small"})} - })} -
- {ProfileLink({ - ...this.props, - username: item.name, - children: {item.name} - })} - {(item?.reputation !== undefined) && {accountReputation(item.reputation)}} -
-
-
- ))} - + this.stateSet({loading: true}); + + const {account, mode} = this.props; + + let results: FriendSearchResult[]; + + try { + if (mode === 'following') { + results = await searchFollowing(account.name, search); + } else { + results = await searchFollower(account.name, search); + } + } catch (e) { + results = []; } - render() { - const {loading, data, results, hasMore, search} = this.state; - - const inSearch = search.length >= 3; - - return ( - <> -
- {loading && ( -
- -
- )} - -
-
- -
- -
- {inSearch && this.renderList(loading, results)} - {!inSearch && this.renderList(loading, data)} -
-
- - {(!inSearch && data.length > 1) && ( -
- -
- )} -
- - ); + this.stateSet({ + results: results, + loading: false, + }); + }; + + searchChanged = ( + e: React.ChangeEvent, + ) => { + clearTimeout(this._timer); + + this.stateSet({search: e.target.value.trim(), loading: true}, () => { + this._timer = setTimeout(() => { + this.search().then(); + }, 500); + }); + }; + + searchKeyDown = (e: React.KeyboardEvent) => { + if (e.keyCode === 13) { + this.search(); } + }; + + renderList = (loading: boolean, list: Friend[]) => { + return ( + <> + {!loading && list.length === 0 && ( +
{_t('g.empty-list')}
+ )} + + {list.map(item => ( +
+
+ {ProfileLink({ + ...this.props, + username: item.name, + children: ( + <> + {UserAvatar({ + ...this.props, + username: item.name, + size: 'small', + })} + + ), + })} +
+ {ProfileLink({ + ...this.props, + username: item.name, + children: ( + {item.name} + ), + })} + {item?.reputation !== undefined && ( + + {accountReputation(item.reputation)} + + )} +
+
+
+ ))} + + ); + }; + + render() { + const {loading, data, results, hasMore, search} = this.state; + + const inSearch = search.length >= 3; + + return ( + <> +
+ {loading && ( +
+ +
+ )} + +
+
+ +
+ +
+ {inSearch && this.renderList(loading, results)} + {!inSearch && this.renderList(loading, data)} +
+
+ + {!inSearch && data.length > 1 && ( +
+ +
+ )} +
+ + ); + } } interface Props { - global: Global; - history: History; - account: Account; - addAccount: (data: Account) => void; - onHide: () => void; + global: Global; + history: History; + account: Account; + addAccount: (data: Account) => void; + onHide: () => void; } export class Followers extends Component { - render() { - const {account, onHide} = this.props; - const title = (account.__loaded && account.follow_stats) ? _t("friends.followers", {n: formattedNumber(account.follow_stats.follower_count, {fractionDigits: 0})}) : ""; - - return ( - <> - - - {title} - - - - - - - ); - } + render() { + const {account, onHide} = this.props; + const title = + account.__loaded && account.follow_stats + ? _t('friends.followers', { + n: formattedNumber(account.follow_stats.follower_count, { + fractionDigits: 0, + }), + }) + : ''; + + return ( + <> + + + {title} + + + + + + + ); + } } export class Following extends Component { - render() { - const {account, onHide} = this.props; - const title = (account.__loaded && account.follow_stats) ? _t("friends.following", {n: formattedNumber(account.follow_stats.following_count, {fractionDigits: 0})}) : ""; - - return ( - <> - - - {title} - - - - - - - ); - } + render() { + const {account, onHide} = this.props; + const title = + account.__loaded && account.follow_stats + ? _t('friends.following', { + n: formattedNumber(account.follow_stats.following_count, { + fractionDigits: 0, + }), + }) + : ''; + + return ( + <> + + + {title} + + + + + + + ); + } } diff --git a/src/common/components/full-height/index.ts b/src/common/components/full-height/index.ts index a90bc2bbd44..5db5907a711 100644 --- a/src/common/components/full-height/index.ts +++ b/src/common/components/full-height/index.ts @@ -1,17 +1,17 @@ -import { Component } from "react"; +import {Component} from 'react'; export default class FullHeight extends Component { componentDidMount() { - const root = document.getElementById("root"); + const root = document.getElementById('root'); if (root) { - root.classList.add("full-height"); + root.classList.add('full-height'); } } componentWillUnmount() { - const root = document.getElementById("root"); + const root = document.getElementById('root'); if (root) { - root.classList.remove("full-height"); + root.classList.remove('full-height'); } } diff --git a/src/common/components/gallery/index.spec.tsx b/src/common/components/gallery/index.spec.tsx index 2c130852d36..a277fd0bca6 100644 --- a/src/common/components/gallery/index.spec.tsx +++ b/src/common/components/gallery/index.spec.tsx @@ -1,73 +1,82 @@ import * as React from 'react'; -import renderer from "react-test-renderer"; +import renderer from 'react-test-renderer'; import {Gallery} from './index'; -import {activeUserInstance, globalInstance, allOver} from "../../helper/test-helper"; +import { + activeUserInstance, + globalInstance, + allOver, +} from '../../helper/test-helper'; +let TEST_MODE = 0; -let TEST_MODE = 0 +jest.mock('../../api/private-api', () => ({ + getImages: () => + new Promise(resolve => { + if (TEST_MODE === 0) { + resolve([]); + } -jest.mock("../../api/private-api", () => ({ - getImages: () => - new Promise((resolve) => { - if (TEST_MODE === 0) { - resolve([]); - } - - if (TEST_MODE === 1) { - resolve([{ - "created": "Sat Aug 08 2020 12:50:55 GMT+0200 (Central European Summer Time)", - "url": "https://images.ecency.com/DQmSoXUteHvx1evzu27Xn5xbf6Mrn29L9Swn2yH2h4keuSQ/test-3.jpg", - "_id": "5f2e838fbaede01c77aa13a1", - "timestamp": 1596883855848 - }, { - "created": "Sat Aug 08 2020 12:57:47 GMT+0200 (Central European Summer Time)", - "url": "https://images.ecency.com/DQmYFWxjApdbdFVYdG6RMGh1vHQdAsAek38ePF3UsRbUYJv/test-10.jpg", - "_id": "5f2e852bbaede01c77aa13a2", - "timestamp": 1596884267539 - }, { - "created": "Sat Aug 08 2020 12:57:53 GMT+0200 (Central European Summer Time)", - "url": "https://images.ecency.com/DQmRJdj2PZmKHz3zZ31CUC6fpHuQaxsQCvpEp15rX3RpTFG/test-9.jpg", - "_id": "5f2e8531baede01c77aa13a3", - "timestamp": 1596884273695 - }, { - "created": "Sat Aug 08 2020 12:58:15 GMT+0200 (Central European Summer Time)", - "url": "https://images.ecency.com/DQmce1GReq9pwLiEHLsf2hKhAmouZnNSgnH95udH4g2VzAH/test-8.jpg", - "_id": "5f2e8547baede01c77aa13a4", - "timestamp": 1596884295409 - }]) - } - }), + if (TEST_MODE === 1) { + resolve([ + { + created: + 'Sat Aug 08 2020 12:50:55 GMT+0200 (Central European Summer Time)', + url: 'https://images.ecency.com/DQmSoXUteHvx1evzu27Xn5xbf6Mrn29L9Swn2yH2h4keuSQ/test-3.jpg', + _id: '5f2e838fbaede01c77aa13a1', + timestamp: 1596883855848, + }, + { + created: + 'Sat Aug 08 2020 12:57:47 GMT+0200 (Central European Summer Time)', + url: 'https://images.ecency.com/DQmYFWxjApdbdFVYdG6RMGh1vHQdAsAek38ePF3UsRbUYJv/test-10.jpg', + _id: '5f2e852bbaede01c77aa13a2', + timestamp: 1596884267539, + }, + { + created: + 'Sat Aug 08 2020 12:57:53 GMT+0200 (Central European Summer Time)', + url: 'https://images.ecency.com/DQmRJdj2PZmKHz3zZ31CUC6fpHuQaxsQCvpEp15rX3RpTFG/test-9.jpg', + _id: '5f2e8531baede01c77aa13a3', + timestamp: 1596884273695, + }, + { + created: + 'Sat Aug 08 2020 12:58:15 GMT+0200 (Central European Summer Time)', + url: 'https://images.ecency.com/DQmce1GReq9pwLiEHLsf2hKhAmouZnNSgnH95udH4g2VzAH/test-8.jpg', + _id: '5f2e8547baede01c77aa13a4', + timestamp: 1596884295409, + }, + ]); + } + }), })); - it('(1) Default render.', async () => { - const props = { - global: globalInstance, - users: [], - activeUser: {...activeUserInstance}, - onHide: () => { - } - }; + const props = { + global: globalInstance, + users: [], + activeUser: {...activeUserInstance}, + onHide: () => {}, + }; - const component = await renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + const component = await renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); it('(2) Test with data.', async () => { - TEST_MODE = 1; + TEST_MODE = 1; - const props = { - global: globalInstance, - activeUser: {...activeUserInstance}, - onPick: () => {}, - onHide: () => { - } - }; + const props = { + global: globalInstance, + activeUser: {...activeUserInstance}, + onPick: () => {}, + onHide: () => {}, + }; - const component = renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + const component = renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/gallery/index.tsx b/src/common/components/gallery/index.tsx index 29dd41b7d70..1c5c473b680 100644 --- a/src/common/components/gallery/index.tsx +++ b/src/common/components/gallery/index.tsx @@ -1,152 +1,177 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import {Modal} from "react-bootstrap"; +import {Modal} from 'react-bootstrap'; -import {ActiveUser} from "../../store/active-user/types"; +import {ActiveUser} from '../../store/active-user/types'; -import {proxifyImageSrc, setProxyBase} from "@ecency/render-helper"; +import {proxifyImageSrc, setProxyBase} from '@ecency/render-helper'; -import BaseComponent from "../base"; -import LinearProgress from "../linear-progress"; -import PopoverConfirm from "../popover-confirm"; -import Tooltip from "../tooltip"; +import BaseComponent from '../base'; +import LinearProgress from '../linear-progress'; +import PopoverConfirm from '../popover-confirm'; +import Tooltip from '../tooltip'; -import {getImages, deleteImage, UserImage} from "../../api/private-api"; +import {getImages, deleteImage, UserImage} from '../../api/private-api'; -import {success, error} from "../feedback"; +import {success, error} from '../feedback'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import {deleteForeverSvg} from "../../img/svg"; +import {deleteForeverSvg} from '../../img/svg'; -import clipboard from "../../util/clipboard"; +import clipboard from '../../util/clipboard'; -import defaults from "../../constants/defaults.json"; -import {Global} from "../../store/global/types"; +import defaults from '../../constants/defaults.json'; +import {Global} from '../../store/global/types'; setProxyBase(defaults.imageServer); interface Props { - global: Global; - activeUser: ActiveUser | null; - onHide: () => void; - onPick?: (url: string) => void; + global: Global; + activeUser: ActiveUser | null; + onHide: () => void; + onPick?: (url: string) => void; } interface State { - loading: boolean, - items: UserImage[] + loading: boolean; + items: UserImage[]; } export class Gallery extends BaseComponent { - state: State = { - loading: true, - items: [] + state: State = { + loading: true, + items: [], + }; + + componentDidMount() { + this.fetch(); + } + + fetch = () => { + const {activeUser} = this.props; + + this.stateSet({loading: true}); + getImages(activeUser?.username!) + .then(items => { + this.stateSet({items: this.sort(items), loading: false}); + }) + .catch(() => { + this.stateSet({loading: false}); + error(_t('g.server-error')); + }); + }; + + sort = (items: UserImage[]) => + items.sort((a, b) => { + return new Date(b.created).getTime() > new Date(a.created).getTime() + ? 1 + : -1; + }); + + itemClicked = (item: UserImage) => { + const {onPick} = this.props; + if (onPick) { + onPick(item.url); + return; } - componentDidMount() { - this.fetch(); - } - - fetch = () => { - const {activeUser} = this.props; - - this.stateSet({loading: true}); - getImages(activeUser?.username!).then(items => { - this.stateSet({items: this.sort(items), loading: false}); - }).catch(() => { - this.stateSet({loading: false}); - error(_t('g.server-error')); - }) - } - - sort = (items: UserImage[]) => - items.sort((a, b) => { - return new Date(b.created).getTime() > new Date(a.created).getTime() ? 1 : -1; - }); - - itemClicked = (item: UserImage) => { - const {onPick} = this.props; - if (onPick) { - onPick(item.url); - return; - } - - clipboard(item.url); - success(_t('gallery.copied')); - } - - delete = (item: UserImage) => { - const {activeUser} = this.props; - - deleteImage(activeUser?.username!, item._id).then(() => { - const {items} = this.state; - const nItems = [...items].filter(x => x._id !== item._id); - this.stateSet({items: this.sort(nItems)}); - }).catch(() => { - error(_t('g.server-error')); - }) - } - - render() { - const {global} = this.props; - const {items, loading} = this.state; - - return
- {loading && } - {items.length > 0 && ( -
-
- {items.map(item => { - const src = proxifyImageSrc(item.url, 600, 500, global.canUseWebp ? 'webp' : 'match'); - return
-
{ - this.itemClicked(item); - }}/> -
- { - this.delete(item); - }}> - - {deleteForeverSvg} - - -
-
- })} + clipboard(item.url); + success(_t('gallery.copied')); + }; + + delete = (item: UserImage) => { + const {activeUser} = this.props; + + deleteImage(activeUser?.username!, item._id) + .then(() => { + const {items} = this.state; + const nItems = [...items].filter(x => x._id !== item._id); + this.stateSet({items: this.sort(nItems)}); + }) + .catch(() => { + error(_t('g.server-error')); + }); + }; + + render() { + const {global} = this.props; + const {items, loading} = this.state; + + return ( +
+ {loading && } + {items.length > 0 && ( +
+
+ {items.map(item => { + const src = proxifyImageSrc( + item.url, + 600, + 500, + global.canUseWebp ? 'webp' : 'match', + ); + return ( +
+
{ + this.itemClicked(item); + }} + /> +
+ { + this.delete(item); + }} + > + + + {deleteForeverSvg} + + +
-
- )} - {(!loading && items.length === 0) && ( -
- {_t('g.empty-list')} -
- )} -
- } +
+ ); + })} +
+
+ )} + {!loading && items.length === 0 && ( +
{_t('g.empty-list')}
+ )} +
+ ); + } } - export default class GalleryDialog extends Component { - hide = () => { - const {onHide} = this.props; - onHide(); - } - - render() { - return ( - - - {_t('gallery.title')} - - - - - - ); - } + hide = () => { + const {onHide} = this.props; + onHide(); + }; + + render() { + return ( + + + {_t('gallery.title')} + + + + + + ); + } } diff --git a/src/common/components/hive-barter/index.spec.tsx b/src/common/components/hive-barter/index.spec.tsx index fc98224cf6e..1e8df2c6aaf 100644 --- a/src/common/components/hive-barter/index.spec.tsx +++ b/src/common/components/hive-barter/index.spec.tsx @@ -1,43 +1,51 @@ -import React from "react"; +import React from 'react'; -import {HiveBarter} from "./index"; +import {HiveBarter} from './index'; -import TestRenderer from "react-test-renderer"; +import TestRenderer from 'react-test-renderer'; -import {activeUserInstance, allOver, globalInstance} from "../../helper/test-helper"; +import { + activeUserInstance, + allOver, + globalInstance, +} from '../../helper/test-helper'; -it("(1) Buying render", async () => { - const props = { - available: "dummy value", - username: "dummy value", - peakValue: 6.9, - basePeakValue: 9.0, - loading: false, - activeUser: activeUserInstance, - global: globalInstance, - onClickPeakValue: (value:any) => {}, - onTransactionSuccess: () => {} - }; +it('(1) Buying render', async () => { + const props = { + available: 'dummy value', + username: 'dummy value', + peakValue: 6.9, + basePeakValue: 9.0, + loading: false, + activeUser: activeUserInstance, + global: globalInstance, + onClickPeakValue: (value: any) => {}, + onTransactionSuccess: () => {}, + }; - const renderer = await TestRenderer.create(); - await allOver(); - expect(renderer.toJSON()).toMatchSnapshot(); + const renderer = await TestRenderer.create( + , + ); + await allOver(); + expect(renderer.toJSON()).toMatchSnapshot(); }); -it("(1) Selling render", async () => { - const props = { - available: "dummy value", - username: "dummy value", - peakValue: 6.9, - basePeakValue: 9.0, - loading: false, - activeUser: activeUserInstance, - global: globalInstance, - onClickPeakValue: (value:any) => {}, - onTransactionSuccess: () => {} - }; +it('(1) Selling render', async () => { + const props = { + available: 'dummy value', + username: 'dummy value', + peakValue: 6.9, + basePeakValue: 9.0, + loading: false, + activeUser: activeUserInstance, + global: globalInstance, + onClickPeakValue: (value: any) => {}, + onTransactionSuccess: () => {}, + }; - const renderer = await TestRenderer.create(); - await allOver(); - expect(renderer.toJSON()).toMatchSnapshot(); -}); \ No newline at end of file + const renderer = await TestRenderer.create( + , + ); + await allOver(); + expect(renderer.toJSON()).toMatchSnapshot(); +}); diff --git a/src/common/components/hive-barter/index.tsx b/src/common/components/hive-barter/index.tsx index 52ae537a4d0..84d99f28430 100644 --- a/src/common/components/hive-barter/index.tsx +++ b/src/common/components/hive-barter/index.tsx @@ -1,15 +1,13 @@ -import React from "react"; -import { useEffect } from "react"; -import { useState } from "react"; -import { Button, Form, InputGroup } from "react-bootstrap"; -import { _t } from "../../i18n"; -import { ActiveUser } from "../../store/active-user/types"; -import { Global } from "../../store/global/types"; -import BuySellHiveDialog, { - TransactionType, -} from "../buy-sell-hive"; -import { error } from "../feedback"; -import { Skeleton } from "../skeleton"; +import React from 'react'; +import {useEffect} from 'react'; +import {useState} from 'react'; +import {Button, Form, InputGroup} from 'react-bootstrap'; +import {_t} from '../../i18n'; +import {ActiveUser} from '../../store/active-user/types'; +import {Global} from '../../store/global/types'; +import BuySellHiveDialog, {TransactionType} from '../buy-sell-hive'; +import {error} from '../feedback'; +import {Skeleton} from '../skeleton'; interface Props { type: 1 | 2; @@ -50,12 +48,12 @@ export const HiveBarter = ({ }, [peakValue]); const fixDecimals = (value: string, decimals: number): string => { - let splittedValue = value.split("."); + let splittedValue = value.split('.'); let valueAfterPoints = splittedValue[1]; if (valueAfterPoints && valueAfterPoints.length > decimals) { valueAfterPoints = valueAfterPoints.substring(0, decimals); - error(_t("market.decimal-error",{decimals})); - return `${splittedValue[0] + "." + valueAfterPoints}`; + error(_t('market.decimal-error', {decimals})); + return `${splittedValue[0] + '.' + valueAfterPoints}`; } return value; }; @@ -63,26 +61,25 @@ export const HiveBarter = ({ const disabled = !(totalValue > 0); return loading ? ( - + ) : ( -
-
-

- {type === 1 ? _t("market.buy") : _t("market.sell")}{" "} - HIVE +
+
+

+ {type === 1 ? _t('market.buy') : _t('market.sell')} HIVE

- -
{_t("market.available")}:
+ +
{_t('market.available')}:
{available}
- -
- {type === 1 ? _t("market.lowest-ask") : _t("market.highest-bid")}: + +
+ {type === 1 ? _t('market.lowest-ask') : _t('market.highest-bid')}:
onClickPeakValue(basePeakValue.toFixed(3))} - className="pointer" + className='pointer' > {basePeakValue.toFixed(3)}
@@ -91,85 +88,79 @@ export const HiveBarter = ({

{ + onSubmit={e => { e.preventDefault(); setTransaction( - type === 1 ? TransactionType.Buy : TransactionType.Sell + type === 1 ? TransactionType.Buy : TransactionType.Sell, ); }} > - {_t("market.price")} + {_t('market.price')} { - setPrice(value.includes(".") ? fixDecimals(value, 6) : value); + placeholder='0.0' + onChange={({target: {value}}) => { + setPrice(value.includes('.') ? fixDecimals(value, 6) : value); let refinedAmount = amount ? parseFloat(amount) : 0; let total = parseFloat( - `${(parseFloat(value) * refinedAmount) as any}` + `${(parseFloat(value) * refinedAmount) as any}`, ).toFixed(3); setTotal(total); }} /> - - HBD/HIVE - + HBD/HIVE - {_t("market.amount")} + {_t('market.amount')} { - setAmount(value.includes(".") ? fixDecimals(value, 3) : value); + onChange={({target: {value}}) => { + setAmount(value.includes('.') ? fixDecimals(value, 3) : value); let refinedAmount = value ? parseFloat(value) : 0; let total = parseFloat( - `${(parseFloat(price) * refinedAmount) as any}` + `${(parseFloat(price) * refinedAmount) as any}`, ).toFixed(3); setTotal(total); }} /> - - HIVE - + HIVE - - {_t("market.total")} + + {_t('market.total')} { + onChange={({target: {value}}) => { setTotal( isNaN(value as any) ? 0 - : value.includes(".") + : value.includes('.') ? fixDecimals(value, 3) - : value + : value, ); setAmount( isNaN(`${parseFloat(value) / parseFloat(price)}` as any) ? 0 : parseFloat( - `${parseFloat(value) / parseFloat(price)}` - ).toFixed(3) + `${parseFloat(value) / parseFloat(price)}`, + ).toFixed(3), ); }} /> - - HBD($) - + HBD($) - {transaction !== TransactionType.None && ( @@ -188,5 +179,5 @@ export const HiveBarter = ({ /> )}
- ) + ); }; diff --git a/src/common/components/image-upload-button/index.spec.tsx b/src/common/components/image-upload-button/index.spec.tsx index bb7544ae023..2e75f71d33b 100644 --- a/src/common/components/image-upload-button/index.spec.tsx +++ b/src/common/components/image-upload-button/index.spec.tsx @@ -1,20 +1,18 @@ -import React from "react"; +import React from 'react'; -import renderer from "react-test-renderer"; +import renderer from 'react-test-renderer'; -import UploadButton from "./index"; +import UploadButton from './index'; -import {activeUserMaker} from "../../helper/test-helper"; +import {activeUserMaker} from '../../helper/test-helper'; -it("(1) Default render", () => { - const props = { - activeUser: activeUserMaker("foo"), - onBegin: () => { - }, - onEnd: () => { - }, - }; +it('(1) Default render', () => { + const props = { + activeUser: activeUserMaker('foo'), + onBegin: () => {}, + onEnd: () => {}, + }; - const component = renderer.create(); - expect(component.toJSON()).toMatchSnapshot(); + const component = renderer.create(); + expect(component.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/image-upload-button/index.tsx b/src/common/components/image-upload-button/index.tsx index 2a1891e60ce..5c5205a5813 100644 --- a/src/common/components/image-upload-button/index.tsx +++ b/src/common/components/image-upload-button/index.tsx @@ -1,92 +1,99 @@ -import React from "react"; -import {Button, Spinner} from "react-bootstrap"; +import React from 'react'; +import {Button, Spinner} from 'react-bootstrap'; -import {ActiveUser} from "../../store/active-user/types"; +import {ActiveUser} from '../../store/active-user/types'; -import BaseComponent from "../base"; -import {error, success} from "../feedback"; +import BaseComponent from '../base'; +import {error, success} from '../feedback'; -import {uploadImage} from "../../api/misc"; -import {getAccessToken} from "../../helper/user-token"; +import {uploadImage} from '../../api/misc'; +import {getAccessToken} from '../../helper/user-token'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import {uploadSvg} from "../../img/svg"; +import {uploadSvg} from '../../img/svg'; interface UploadButtonProps { - activeUser: ActiveUser; - onBegin: () => void; - onEnd: (url: string) => void; + activeUser: ActiveUser; + onBegin: () => void; + onEnd: (url: string) => void; } interface UploadButtonState { - inProgress: boolean; + inProgress: boolean; } -export default class UploadButton extends BaseComponent { - input = React.createRef(); +export default class UploadButton extends BaseComponent< + UploadButtonProps, + UploadButtonState +> { + input = React.createRef(); - state: UploadButtonState = { - inProgress: false - } + state: UploadButtonState = { + inProgress: false, + }; - upload = () => { - if (this.input.current) this.input.current.click(); - }; + upload = () => { + if (this.input.current) this.input.current.click(); + }; - handleFileInput = (e: React.ChangeEvent) => { - const files = [...e.target.files]; + handleFileInput = (e: React.ChangeEvent) => { + const files = [...e.target.files]; - if (files.length === 0) { - return; - } + if (files.length === 0) { + return; + } - const [file] = files; + const [file] = files; - const {onBegin, onEnd, activeUser} = this.props; + const {onBegin, onEnd, activeUser} = this.props; - onBegin(); + onBegin(); - this.stateSet({inProgress: true}); - let token = getAccessToken(activeUser.username); + this.stateSet({inProgress: true}); + let token = getAccessToken(activeUser.username); - if(token){ - uploadImage(file, token).then(r => { - onEnd(r.url); - success(_t('image-upload-button.uploaded')); - }).catch(() => { - error(_t('g.server-error')); - }).finally(() => { - this.stateSet({inProgress: false}); + if (token) { + uploadImage(file, token) + .then(r => { + onEnd(r.url); + success(_t('image-upload-button.uploaded')); + }) + .catch(() => { + error(_t('g.server-error')); + }) + .finally(() => { + this.stateSet({inProgress: false}); }); + } else { + error(_t('editor-toolbar.image-error-cache')); } - else { - error(_t("editor-toolbar.image-error-cache")) - } - }; - - render() { - const {inProgress} = this.state; - const spinner = ; - - return ( - <> - - - ); - } + }; + + render() { + const {inProgress} = this.state; + const spinner = ; + + return ( + <> + + + ); + } } diff --git a/src/common/components/image-upload/index.tsx b/src/common/components/image-upload/index.tsx index a599496c62c..bdc8010bdcb 100644 --- a/src/common/components/image-upload/index.tsx +++ b/src/common/components/image-upload/index.tsx @@ -1,86 +1,119 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import {Form, FormControl, InputGroup, Modal, Button, Spinner} from "react-bootstrap"; +import { + Form, + FormControl, + InputGroup, + Modal, + Button, + Spinner, +} from 'react-bootstrap'; -import {ActiveUser} from "../../store/active-user/types"; +import {ActiveUser} from '../../store/active-user/types'; -import BaseComponent from "../base"; -import UploadButton from "../image-upload-button"; +import BaseComponent from '../base'; +import UploadButton from '../image-upload-button'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; interface Props { - activeUser: ActiveUser; - title: string; - defImage: string; - inProgress: boolean; - onDone: (url: string) => void; - onHide: () => void; + activeUser: ActiveUser; + title: string; + defImage: string; + inProgress: boolean; + onDone: (url: string) => void; + onHide: () => void; } interface State { - image: string, - uploading: boolean, + image: string; + uploading: boolean; } export class ImageUpload extends BaseComponent { - state: State = { - image: this.props.defImage, - uploading: false, - } + state: State = { + image: this.props.defImage, + uploading: false, + }; - imageChanged = (e: React.ChangeEvent): void => { - const {value: image} = e.target; + imageChanged = ( + e: React.ChangeEvent, + ): void => { + const {value: image} = e.target; - this.stateSet({image}); - } + this.stateSet({image}); + }; - done = () => { - const {onDone} = this.props; - const {image} = this.state; - onDone(image); - } + done = () => { + const {onDone} = this.props; + const {image} = this.state; + onDone(image); + }; - render() { - const {title, inProgress} = this.props; - const { - image, - uploading - } = this.state; + render() { + const {title, inProgress} = this.props; + const {image, uploading} = this.state; - const spinner = ; + const spinner = ( + + ); - return
- - {title} - - - - { - this.stateSet({uploading: true}); - }} - onEnd={(url) => { - this.stateSet({image: url, uploading: false}); - }}/> - - - - -
- } + return ( +
+ + {title} + + + + { + this.stateSet({uploading: true}); + }} + onEnd={url => { + this.stateSet({image: url, uploading: false}); + }} + /> + + + + +
+ ); + } } export default class ImageUploadDialog extends Component { - render() { - const {onHide} = this.props; - return ( - - - - - - - ); - } + render() { + const {onHide} = this.props; + return ( + + + + + + + ); + } } diff --git a/src/common/components/intro/index.spec.tsx b/src/common/components/intro/index.spec.tsx index 79c7a472ccb..3a8089a7eb2 100644 --- a/src/common/components/intro/index.spec.tsx +++ b/src/common/components/intro/index.spec.tsx @@ -1,22 +1,21 @@ -import React from "react"; +import React from 'react'; -import {StaticRouter} from "react-router-dom"; -import TestRenderer from "react-test-renderer"; +import {StaticRouter} from 'react-router-dom'; +import TestRenderer from 'react-test-renderer'; -import Intro from "./index"; +import Intro from './index'; -import {globalInstance} from "../../helper/test-helper"; +import {globalInstance} from '../../helper/test-helper'; -it("(1) Render", () => { - const props = { - global: globalInstance, - hideIntro: () => { - } - }; - const renderer = TestRenderer.create( - - - ); - expect(renderer.toJSON()).toMatchSnapshot(); +it('(1) Render', () => { + const props = { + global: globalInstance, + hideIntro: () => {}, + }; + const renderer = TestRenderer.create( + + + , + ); + expect(renderer.toJSON()).toMatchSnapshot(); }); - diff --git a/src/common/components/intro/index.tsx b/src/common/components/intro/index.tsx index 108f2d86140..f5375341371 100644 --- a/src/common/components/intro/index.tsx +++ b/src/common/components/intro/index.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import {Link} from "react-router-dom"; +import {Link} from 'react-router-dom'; import {Global} from '../../store/global/types'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; import {closeSvg} from '../../img/svg'; @@ -12,31 +12,39 @@ import {closeSvg} from '../../img/svg'; // const friendsWebp = require('../../img/welcome_community.webp'); interface Props { - global: Global - hideIntro: () => any + global: Global; + hideIntro: () => any; } const Intro = (props: Props) => { - const hideIntro = () => props.hideIntro(); - - if (!props.global.intro) { - return null; - } - - return
-
{closeSvg}
-
-

{_t("intro.title")}

-

-
{_t("intro.sub-title")}
-
{_t("intro.c2a")}
-

-
-
-
-
- {/* Friends */} -
; + const hideIntro = () => props.hideIntro(); + + if (!props.global.intro) { + return null; + } + + return ( +
+
+ {closeSvg} +
+
+

{_t('intro.title')}

+

+
{_t('intro.sub-title')}
+
+ + {_t('intro.c2a')} + +
+

+
+
+
+
+ {/* Friends */} +
+ ); }; export default Intro; diff --git a/src/common/components/introduction/index.spec.tsx b/src/common/components/introduction/index.spec.tsx index 23baadc8381..c8284f902c5 100644 --- a/src/common/components/introduction/index.spec.tsx +++ b/src/common/components/introduction/index.spec.tsx @@ -1,31 +1,37 @@ -import React from "react"; -import { Introduction } from "./index"; -import renderer from "react-test-renderer"; +import React from 'react'; +import {Introduction} from './index'; +import renderer from 'react-test-renderer'; describe('Introduction component', () => { - const props = { - title: 'Test Title', - description: "Test decription", - media: 'https://propakistani.pk/wp-content/uploads/2019/12/Social-Media-Insight-top.png', - onClose: () => {} - }; + const props = { + title: 'Test Title', + description: 'Test decription', + media: + 'https://propakistani.pk/wp-content/uploads/2019/12/Social-Media-Insight-top.png', + onClose: () => {}, + }; - const component = renderer.create(); - const instance: any = component.getInstance(); + const component = renderer.create(); + const instance: any = component.getInstance(); - it("(1) renders successfully with minimal props", () => { - expect(component.toJSON()).toMatchSnapshot(); - }); + it('(1) renders successfully with minimal props', () => { + expect(component.toJSON()).toMatchSnapshot(); + }); - it("(2) has title, media and description", () => { - expect(component.root.findByProps({id: "title"}).children).toContain(props.title); - expect(component.root.findByProps({id: "media"}).props.src).toContain(props.media); - expect(component.root.findByProps({id: "description"}).children).toContain(props.description); - }); - - it("(3) closes on clicking close button", () => { - component.root.findByProps({ id: "close-btn" }).props.onClick(); - expect(component.root.findByProps({ id: "close-btn" })).toBeFalsy; - }); + it('(2) has title, media and description', () => { + expect(component.root.findByProps({id: 'title'}).children).toContain( + props.title, + ); + expect(component.root.findByProps({id: 'media'}).props.src).toContain( + props.media, + ); + expect(component.root.findByProps({id: 'description'}).children).toContain( + props.description, + ); + }); + it('(3) closes on clicking close button', () => { + component.root.findByProps({id: 'close-btn'}).props.onClick(); + expect(component.root.findByProps({id: 'close-btn'})).toBeFalsy; + }); }); diff --git a/src/common/components/introduction/index.tsx b/src/common/components/introduction/index.tsx index a27ecf3d183..e374228ab06 100644 --- a/src/common/components/introduction/index.tsx +++ b/src/common/components/introduction/index.tsx @@ -1,53 +1,102 @@ import React from 'react'; -import { useRef } from 'react'; -import { useEffect } from 'react'; -import { Col, Container, Row, Button } from "react-bootstrap"; -import { _t } from '../../i18n'; +import {useRef} from 'react'; +import {useEffect} from 'react'; +import {Col, Container, Row, Button} from 'react-bootstrap'; +import {_t} from '../../i18n'; export interface Props { - title: string; - description: React.ReactNode; - media: string; - onClose: () => void; - onNext?: () => void; - onPrevious?: () => void; - placement?: string - showFinish?: boolean; + title: string; + description: React.ReactNode; + media: string; + onClose: () => void; + onNext?: () => void; + onPrevious?: () => void; + placement?: string; + showFinish?: boolean; } -export const Introduction = ({ title, description, media, onClose, onPrevious, onNext, placement, showFinish }: Props) => { +export const Introduction = ({ + title, + description, + media, + onClose, + onPrevious, + onNext, + placement, + showFinish, +}: Props) => { + const prevButton = useRef(null); - const prevButton = useRef(null); + useEffect(() => { + let body = document.getElementsByTagName('body')[0]; + body.classList.add('overflow-hidden'); + return () => { + body.classList.remove('overflow-hidden'); + }; + }, []); - useEffect(() => { - let body = document.getElementsByTagName('body')[0]; - body.classList.add("overflow-hidden"); - return () => { - body.classList.remove("overflow-hidden"); - } - },[]); - - return <> -
- - - - - - - -

{title}

-

{description}

-
- {onPrevious && } - {onNext && } -
- -
-
-
- - -} + return ( + <> +
+ + + + + + + +

+ {title} +

+

+ {description} +

+
+ {onPrevious && ( + + )} + {onNext && ( + + )} +
+ +
+
+
+ + ); +}; diff --git a/src/common/components/key-or-hot-dialog/index.tsx b/src/common/components/key-or-hot-dialog/index.tsx index 5730ee5dc8a..3649e925bfc 100644 --- a/src/common/components/key-or-hot-dialog/index.tsx +++ b/src/common/components/key-or-hot-dialog/index.tsx @@ -1,99 +1,109 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import {Modal} from "react-bootstrap"; +import {Modal} from 'react-bootstrap'; -import {PrivateKey} from "@hiveio/dhive"; +import {PrivateKey} from '@hiveio/dhive'; -import {Global} from "../../store/global/types"; -import {ActiveUser} from "../../store/active-user/types"; +import {Global} from '../../store/global/types'; +import {ActiveUser} from '../../store/active-user/types'; -import KeyOrHot from "../key-or-hot"; +import KeyOrHot from '../key-or-hot'; interface Props { - global: Global; - activeUser: ActiveUser; - signingKey: string; - setSigningKey: (key: string) => void; - children: JSX.Element; - onKey: (key: PrivateKey) => void; - onHot?: () => void; - onKc?: () => void; - onToggle?: () => void; + global: Global; + activeUser: ActiveUser; + signingKey: string; + setSigningKey: (key: string) => void; + children: JSX.Element; + onKey: (key: PrivateKey) => void; + onHot?: () => void; + onKc?: () => void; + onToggle?: () => void; } interface State { - keyDialog: boolean; + keyDialog: boolean; } export class KeyOrHotDialog extends Component { - state: State = { - keyDialog: false - } + state: State = { + keyDialog: false, + }; - toggleKeyDialog = () => { - const {keyDialog} = this.state; - this.setState({keyDialog: !keyDialog}); + toggleKeyDialog = () => { + const {keyDialog} = this.state; + this.setState({keyDialog: !keyDialog}); - const {onToggle} = this.props; - if (onToggle) onToggle(); - } + const {onToggle} = this.props; + if (onToggle) onToggle(); + }; - render() { - const {children, onKey, onHot, onKc} = this.props; - const {keyDialog} = this.state; + render() { + const {children, onKey, onHot, onKc} = this.props; + const {keyDialog} = this.state; - const newChildren = React.cloneElement(children, { - onClick: (e: React.MouseEvent) => { - e.preventDefault(); - this.toggleKeyDialog(); - }, - }); + const newChildren = React.cloneElement(children, { + onClick: (e: React.MouseEvent) => { + e.preventDefault(); + this.toggleKeyDialog(); + }, + }); - return <> - {newChildren} + return ( + <> + {newChildren} - {keyDialog && ( - - - {KeyOrHot({ - ...this.props, - inProgress: false, - onKey: (key) => { - this.toggleKeyDialog(); - onKey(key); - }, - onHot: () => { - this.toggleKeyDialog(); - if (onHot) { - onHot(); - } - }, - onKc: () => { - this.toggleKeyDialog(); - if (onKc) { - onKc(); - } - } - })} - - - )} - - } + {keyDialog && ( + + + {KeyOrHot({ + ...this.props, + inProgress: false, + onKey: key => { + this.toggleKeyDialog(); + onKey(key); + }, + onHot: () => { + this.toggleKeyDialog(); + if (onHot) { + onHot(); + } + }, + onKc: () => { + this.toggleKeyDialog(); + if (onKc) { + onKc(); + } + }, + })} + + + )} + + ); + } } export default (p: Props) => { - const props = { - global: p.global, - activeUser: p.activeUser, - signingKey: p.signingKey, - setSigningKey: p.setSigningKey, - children: p.children, - onKey: p.onKey, - onHot: p.onHot, - onKc: p.onKc, - onToggle: p.onToggle - } + const props = { + global: p.global, + activeUser: p.activeUser, + signingKey: p.signingKey, + setSigningKey: p.setSigningKey, + children: p.children, + onKey: p.onKey, + onHot: p.onHot, + onKc: p.onKc, + onToggle: p.onToggle, + }; - return -} + return ; +}; diff --git a/src/common/components/key-or-hot/index.spec.tsx b/src/common/components/key-or-hot/index.spec.tsx index d6bd03509a1..57508393d20 100644 --- a/src/common/components/key-or-hot/index.spec.tsx +++ b/src/common/components/key-or-hot/index.spec.tsx @@ -1,25 +1,22 @@ -import React from "react"; +import React from 'react'; -import renderer from "react-test-renderer"; +import renderer from 'react-test-renderer'; -import {KeyOrHot} from "./index"; +import {KeyOrHot} from './index'; -import {activeUserMaker, globalInstance} from "../../helper/test-helper"; +import {activeUserMaker, globalInstance} from '../../helper/test-helper'; const defProps = { - global: globalInstance, - activeUser: activeUserMaker("foo"), - signingKey: 'aprivatesigningkey', - setSigningKey: () => { - }, - inProgress: false, - onKey: () => { - }, - onHot: () => { - } + global: globalInstance, + activeUser: activeUserMaker('foo'), + signingKey: 'aprivatesigningkey', + setSigningKey: () => {}, + inProgress: false, + onKey: () => {}, + onHot: () => {}, }; -it("(1) Default render", () => { - const component = renderer.create(); - expect(component.toJSON()).toMatchSnapshot(); +it('(1) Default render', () => { + const component = renderer.create(); + expect(component.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/key-or-hot/index.tsx b/src/common/components/key-or-hot/index.tsx index c33883c1c17..b52fe73ddd7 100644 --- a/src/common/components/key-or-hot/index.tsx +++ b/src/common/components/key-or-hot/index.tsx @@ -1,144 +1,156 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import {Button, Form, FormControl, InputGroup} from "react-bootstrap"; +import {Button, Form, FormControl, InputGroup} from 'react-bootstrap'; -import {cryptoUtils, PrivateKey} from "@hiveio/dhive"; +import {cryptoUtils, PrivateKey} from '@hiveio/dhive'; -import {ActiveUser} from "../../store/active-user/types"; -import {Global} from "../../store/global/types"; +import {ActiveUser} from '../../store/active-user/types'; +import {Global} from '../../store/global/types'; -import OrDivider from "../or-divider"; -import {error} from "../feedback"; +import OrDivider from '../or-divider'; +import {error} from '../feedback'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import {keySvg} from "../../img/svg"; +import {keySvg} from '../../img/svg'; interface Props { - global: Global; - activeUser: ActiveUser; - signingKey: string; - setSigningKey: (key: string) => void; - inProgress: boolean; - onKey: (key: PrivateKey) => void; - onHot?: () => void; - onKc?: () => void; + global: Global; + activeUser: ActiveUser; + signingKey: string; + setSigningKey: (key: string) => void; + inProgress: boolean; + onKey: (key: PrivateKey) => void; + onHot?: () => void; + onKc?: () => void; } interface State { - key: string; + key: string; } export class KeyOrHot extends Component { - state: State = { - key: this.props.signingKey || "" + state: State = { + key: this.props.signingKey || '', + }; + + keyChanged = ( + e: React.ChangeEvent, + ): void => { + const {value: key} = e.target; + this.setState({key}); + }; + + keyEntered = () => { + const {activeUser} = this.props; + const {key} = this.state; + + let pKey: PrivateKey; + + if (cryptoUtils.isWif(key)) { + // wif + try { + pKey = PrivateKey.fromString(key); + } catch (e) { + error('Invalid active private key!'); + return; + } + } else { + // master key + pKey = PrivateKey.fromLogin(activeUser.username, key, 'active'); } - keyChanged = (e: React.ChangeEvent): void => { - const {value: key} = e.target; - this.setState({key}); - } - - keyEntered = () => { - const {activeUser} = this.props; - const {key} = this.state; - - let pKey: PrivateKey; - - if (cryptoUtils.isWif(key)) { - // wif - try { - pKey = PrivateKey.fromString(key); - } catch (e) { - error('Invalid active private key!'); - return; - } - } else { - // master key - pKey = PrivateKey.fromLogin(activeUser.username, key, 'active'); - } - - const {onKey, setSigningKey} = this.props; - - setSigningKey(key); + const {onKey, setSigningKey} = this.props; - onKey(pKey); - } + setSigningKey(key); - hotClicked = () => { - const {onHot} = this.props; - if (onHot) { - onHot(); - } - } + onKey(pKey); + }; - kcClicked = () => { - const {onKc} = this.props; - if (onKc) { - onKc(); - } + hotClicked = () => { + const {onHot} = this.props; + if (onHot) { + onHot(); } + }; - render() { - const {inProgress, global} = this.props; - const {key} = this.state; - const hsLogo = global.isElectron ? "./img/hive-signer.svg" : require("../../img/hive-signer.svg"); - const keyChainLogo = global.isElectron ? "./img/keychain.png" : require("../../img/keychain.png"); - - return ( - <> -
-
{ - e.preventDefault(); - }}> - - - {keySvg} - - - - - - -
- -
- -
- - {global.hasKeyChain && ( -
- -
- )} -
- - ); + kcClicked = () => { + const {onKc} = this.props; + if (onKc) { + onKc(); } + }; + + render() { + const {inProgress, global} = this.props; + const {key} = this.state; + const hsLogo = global.isElectron + ? './img/hive-signer.svg' + : require('../../img/hive-signer.svg'); + const keyChainLogo = global.isElectron + ? './img/keychain.png' + : require('../../img/keychain.png'); + + return ( + <> +
+
{ + e.preventDefault(); + }} + > + + + {keySvg} + + + + + + +
+ +
+ +
+ + {global.hasKeyChain && ( +
+ +
+ )} +
+ + ); + } } - export default (p: Props) => { - const props = { - global: p.global, - activeUser: p.activeUser, - signingKey: p.signingKey, - setSigningKey: p.setSigningKey, - inProgress: p.inProgress, - onKey: p.onKey, - onHot: p.onHot, - onKc: p.onKc - } - - return ; -} + const props = { + global: p.global, + activeUser: p.activeUser, + signingKey: p.signingKey, + setSigningKey: p.setSigningKey, + inProgress: p.inProgress, + onKey: p.onKey, + onHot: p.onHot, + onKc: p.onKc, + }; + + return ; +}; diff --git a/src/common/components/landing-page/index.tsx b/src/common/components/landing-page/index.tsx index 5fa94900052..68d06cec242 100644 --- a/src/common/components/landing-page/index.tsx +++ b/src/common/components/landing-page/index.tsx @@ -1,40 +1,75 @@ -import React, { FormEvent, useState } from "react"; +import React, {FormEvent, useState} from 'react'; import htmlParse from 'html-react-parser'; -import { subscribeEmail } from "../../api/private-api"; -import { _t } from "../../i18n"; -import { scrollDown } from "../../img/svg"; -import { error, success } from "../feedback"; -import LinearProgress from "../linear-progress"; -import Link from "../alink"; +import {subscribeEmail} from '../../api/private-api'; +import {_t} from '../../i18n'; +import {scrollDown} from '../../img/svg'; +import {error, success} from '../feedback'; +import LinearProgress from '../linear-progress'; +import Link from '../alink'; -import { apiBase } from "../../api/helper"; -import { handleInvalid, handleOnInput } from "../../util/input-util"; -import isElectron from "../../util/is-electron"; +import {apiBase} from '../../api/helper'; +import {handleInvalid, handleOnInput} from '../../util/input-util'; +import isElectron from '../../util/is-electron'; const LandingPage = (props: any) => { - const {global} = props; - const [email, setEmail] = useState(""); + const [email, setEmail] = useState(''); const [loading, setLoading] = useState(false); - const EarnMoney = apiBase(`/assets/illustration-earn-money.${global.canUseWebp?"webp":"png"}`); - const WhaleCatchsFish = apiBase(`/assets/illustration-true-ownership.${global.canUseWebp?"webp":"png"}`); - const Decentralization = apiBase(`/assets/illustration-decentralization.${global.canUseWebp?"webp":"png"}`); - const MechanicFish = apiBase(`/assets/illustration-open-source.${global.canUseWebp?"webp":"png"}`); - const FishOne = apiBase(`/assets/fish-1.${global.canUseWebp?"webp":"png"}`); - const FishTwo = apiBase(`/assets/fish-2.${global.canUseWebp?"webp":"png"}`); - const FishThree = apiBase(`/assets/fish-3.${global.canUseWebp?"webp":"png"}`); - const FishFour = apiBase(`/assets/fish-4.${global.canUseWebp?"webp":"png"}`); - const FishFive = apiBase(`/assets/fish-5.${global.canUseWebp?"webp":"png"}`); - const JuniorFish = apiBase(`/assets/fish-junior.${global.canUseWebp?"webp":"png"}`); - const SeniorFish = apiBase(`/assets/fish-senior.${global.canUseWebp?"webp":"png"}`); - const DownloadAndroid = apiBase(`/assets/icon-android.${global.canUseWebp?"webp":"png"}`); - const OurHistory = apiBase(`/assets/our-history.${global.canUseWebp?"webp":"png"}`); - const OurTeam = apiBase(`/assets/our-team.${global.canUseWebp?"webp":"png"}`); - const OurVision = apiBase(`/assets/our-vision.${global.canUseWebp?"webp":"png"}`); - const FooterMainFish = apiBase(`/assets/footer-main-fish.${global.canUseWebp?"webp":"png"}`); - const LeftFishes = apiBase(`/assets/left-fishes.${global.canUseWebp?"webp":"png"}`); + const EarnMoney = apiBase( + `/assets/illustration-earn-money.${global.canUseWebp ? 'webp' : 'png'}`, + ); + const WhaleCatchsFish = apiBase( + `/assets/illustration-true-ownership.${global.canUseWebp ? 'webp' : 'png'}`, + ); + const Decentralization = apiBase( + `/assets/illustration-decentralization.${ + global.canUseWebp ? 'webp' : 'png' + }`, + ); + const MechanicFish = apiBase( + `/assets/illustration-open-source.${global.canUseWebp ? 'webp' : 'png'}`, + ); + const FishOne = apiBase( + `/assets/fish-1.${global.canUseWebp ? 'webp' : 'png'}`, + ); + const FishTwo = apiBase( + `/assets/fish-2.${global.canUseWebp ? 'webp' : 'png'}`, + ); + const FishThree = apiBase( + `/assets/fish-3.${global.canUseWebp ? 'webp' : 'png'}`, + ); + const FishFour = apiBase( + `/assets/fish-4.${global.canUseWebp ? 'webp' : 'png'}`, + ); + const FishFive = apiBase( + `/assets/fish-5.${global.canUseWebp ? 'webp' : 'png'}`, + ); + const JuniorFish = apiBase( + `/assets/fish-junior.${global.canUseWebp ? 'webp' : 'png'}`, + ); + const SeniorFish = apiBase( + `/assets/fish-senior.${global.canUseWebp ? 'webp' : 'png'}`, + ); + const DownloadAndroid = apiBase( + `/assets/icon-android.${global.canUseWebp ? 'webp' : 'png'}`, + ); + const OurHistory = apiBase( + `/assets/our-history.${global.canUseWebp ? 'webp' : 'png'}`, + ); + const OurTeam = apiBase( + `/assets/our-team.${global.canUseWebp ? 'webp' : 'png'}`, + ); + const OurVision = apiBase( + `/assets/our-vision.${global.canUseWebp ? 'webp' : 'png'}`, + ); + const FooterMainFish = apiBase( + `/assets/footer-main-fish.${global.canUseWebp ? 'webp' : 'png'}`, + ); + const LeftFishes = apiBase( + `/assets/left-fishes.${global.canUseWebp ? 'webp' : 'png'}`, + ); const DownloadAndroidWhite = apiBase(`/assets/icon-android-white.svg`); const DownloadIPhone = apiBase(`/assets/icon-apple.svg`); const DownloadIPhoneWhite = apiBase(`/assets/icon-apple-white.svg`); @@ -44,263 +79,436 @@ const LandingPage = (props: any) => { const FooterTwitter = apiBase(`/assets/footer-twitter.svg`); const FooterTelegram = apiBase(`/assets/footer-telegram.svg`); const FooterDiscord = apiBase(`/assets/footer-discord.svg`); - const PhoneDarkPc = apiBase(`/assets/phone-download-tiny.${global.canUseWebp?"webp":"png"}`); + const PhoneDarkPc = apiBase( + `/assets/phone-download-tiny.${global.canUseWebp ? 'webp' : 'png'}`, + ); - const BubbleLeftTop = apiBase(`/assets/bubble-left-top.${global.canUseWebp?"webp":"png"}`); - const BubbleLeftBottom = apiBase(`/assets/bubble-left-bottom.${global.canUseWebp?"webp":"png"}`); - const BubbleRightTop = apiBase(`/assets/bubble-right-top.${global.canUseWebp?"webp":"png"}`); - const BubbleLRightBottom = apiBase(`/assets/bubble-right-bottom.${global.canUseWebp?"webp":"png"}`); - const BubbleLCenter = apiBase(`/assets/bubble-center.${global.canUseWebp?"webp":"png"}`); - const DownloadDarkFishes = apiBase(`/assets/download-dark-fishes.${global.canUseWebp?"webp":"png"}`); + const BubbleLeftTop = apiBase( + `/assets/bubble-left-top.${global.canUseWebp ? 'webp' : 'png'}`, + ); + const BubbleLeftBottom = apiBase( + `/assets/bubble-left-bottom.${global.canUseWebp ? 'webp' : 'png'}`, + ); + const BubbleRightTop = apiBase( + `/assets/bubble-right-top.${global.canUseWebp ? 'webp' : 'png'}`, + ); + const BubbleLRightBottom = apiBase( + `/assets/bubble-right-bottom.${global.canUseWebp ? 'webp' : 'png'}`, + ); + const BubbleLCenter = apiBase( + `/assets/bubble-center.${global.canUseWebp ? 'webp' : 'png'}`, + ); + const DownloadDarkFishes = apiBase( + `/assets/download-dark-fishes.${global.canUseWebp ? 'webp' : 'png'}`, + ); - const FounderImg = apiBase(`/assets/good-karma.${global.canUseWebp?"webp":"jpeg"}`); - const DevopsImg = apiBase(`/assets/talhasch.${global.canUseWebp?"webp":"jpeg"}`); - const DesignGuru = apiBase(`/assets/dunsky.${global.canUseWebp?"webp":"jpeg"}`);; + const FounderImg = apiBase( + `/assets/good-karma.${global.canUseWebp ? 'webp' : 'jpeg'}`, + ); + const DevopsImg = apiBase( + `/assets/talhasch.${global.canUseWebp ? 'webp' : 'jpeg'}`, + ); + const DesignGuru = apiBase( + `/assets/dunsky.${global.canUseWebp ? 'webp' : 'jpeg'}`, + ); - const LogoCircle = global.isElectron ? "./img/logo-circle.svg" : require("../../img/logo-circle.svg"); + const LogoCircle = global.isElectron + ? './img/logo-circle.svg' + : require('../../img/logo-circle.svg'); const handleSubsccribe = async (e: FormEvent) => { e.preventDefault(); setLoading(true); try { const response = await subscribeEmail(email); - if(200 == response?.status) { - success(_t("landing-page.success-message-subscribe")) - } + if (200 == response?.status) { + success(_t('landing-page.success-message-subscribe')); + } } catch (err) { error(_t('landing-page.error-occured')); } - setEmail(""); - setLoading(false) + setEmail(''); + setLoading(false); }; - + return ( -
-
-
-
-
-
-

{_t("landing-page.welcome-text")}

-
-

{_t("landing-page.what-is-ecency")}

+
+
+
+
+
+
+

{_t('landing-page.welcome-text')}

+
+

{_t('landing-page.what-is-ecency')}

- + {scrollDown}
-
-
-
- earn-money -
-

{_t("landing-page.earn-money")}

-

- {_t("landing-page.earn-money-block-chain-based")} +

+
+
+ earn-money +
+

{_t('landing-page.earn-money')}

+

+ {_t('landing-page.earn-money-block-chain-based')} - - {_t("landing-page.join-us")} + + {_t('landing-page.join-us')} - {_t("landing-page.various-digital-tokens")} + {_t('landing-page.various-digital-tokens')}

- - {_t("landing-page.read-more")} + + {_t('landing-page.read-more')}
-
-
-
-

{_t("landing-page.true-ownership")}

-

{_t("landing-page.true-ownership-desc")}

+
+
+
+

{_t('landing-page.true-ownership')}

+

+ {_t('landing-page.true-ownership-desc')} +

-
+
whale
-
-
-
-
+
+
+
+
decentralization
-
-

{_t("landing-page.decentralization")}

+
+

{_t('landing-page.decentralization')}

- isElectron() && window.open('https://hive.io', '_blank', 'top=500,left=200,frame=false,nodeIntegration=no')}> - {_t("landing-page.hive-blockchain")} + + isElectron() && + window.open( + 'https://hive.io', + '_blank', + 'top=500,left=200,frame=false,nodeIntegration=no', + ) + } + > + {_t('landing-page.hive-blockchain')} - {" "} - {_t("landing-page.decentralization-desc")} + {' '} + {_t('landing-page.decentralization-desc')}

-
-
-
-

{_t("landing-page.open-source")}

-

- {_t("landing-page.open-source-desc")} -

- - {_t("landing-page.feel-free-join")} +
+
+
+

{_t('landing-page.open-source')}

+

{_t('landing-page.open-source-desc')}

+ + {_t('landing-page.feel-free-join')}
-
+
mechanic
-
-
-
-
- earn-money - earn-money - earn-money +
+
+
+
+ earn-money + earn-money + earn-money
-
- earn-money - earn-money +
+ earn-money + earn-money
  • 96M

    -

    {_t("landing-page.posts")}

    +

    {_t('landing-page.posts')}

  • 600K

    -

    {_t("landing-page.unique-visitors")}

    +

    {_t('landing-page.unique-visitors')}

  • 38M

    -

    {_t("landing-page.points-distrubuted")}

    +

    {_t('landing-page.points-distrubuted')}

  • 75K

    -

    {_t("landing-page.new-users")}

    +

    {_t('landing-page.new-users')}

-
-
+
+
- dark phone image - dark phone image - light phone image - light phone image + dark phone image + dark phone image + light phone image + light phone image - bubble - bubble - bubble - bubble - bubble + bubble + bubble + bubble + bubble + bubble - fishes - fish - fish - fish + fishes + fish + fish + fish -
-

{_t("landing-page.download-our-application")}

-

{_t("landing-page.download-our-application-desc-1")}

-

{_t("landing-page.download-our-application-desc-2")}

- isElectron() && window.open('https://desktop.ecency.com/', '_blank', 'top=500,left=200,frame=false,nodeIntegration=no')} +
+

{_t('landing-page.download-our-application')}

+

+ {_t('landing-page.download-our-application-desc-1')} +

+

{_t('landing-page.download-our-application-desc-2')}

+ + isElectron() && + window.open( + 'https://desktop.ecency.com/', + '_blank', + 'top=500,left=200,frame=false,nodeIntegration=no', + ) + } > Download for Windows - {_t("landing-page.download-for-windows")} + {_t('landing-page.download-for-windows')} - isElectron() && window.open('https://ios.ecency.com/', '_blank', 'top=500,left=200,frame=false,nodeIntegration=no')}> + + isElectron() && + window.open( + 'https://ios.ecency.com/', + '_blank', + 'top=500,left=200,frame=false,nodeIntegration=no', + ) + } + > Download for IOS - {_t("landing-page.download-for-ios")} + {_t('landing-page.download-for-ios')} - isElectron() && window.open('https://android.ecency.com/', '_blank', 'top=500,left=200,frame=false,nodeIntegration=no')}> + + isElectron() && + window.open( + 'https://android.ecency.com/', + '_blank', + 'top=500,left=200,frame=false,nodeIntegration=no', + ) + } + > Download for Android - {_t("landing-page.download-for-android")} + {_t('landing-page.download-for-android')}
-
-
-
-
-

{_t("landing-page.our-history")}

+
+
+
+
+

{_t('landing-page.our-history')}

{htmlParse(_t('landing-page.our-history-p-one'))}

-

{_t("landing-page.our-history-p-two")}

+

{_t('landing-page.our-history-p-two')}

- Our History + Our History
-
-
- Our Vision +
+
+ Our Vision -
-

{_t("landing-page.our-vision")}

+
+

{_t('landing-page.our-vision')}

{htmlParse(_t('landing-page.our-vision-p-one'))}

{htmlParse(_t('landing-page.our-vision-p-two'))}

@@ -308,141 +516,205 @@ const LandingPage = (props: any) => {
-
-
-
-
-

{_t("landing-page.our-team")}

+
+
+
+
+

{_t('landing-page.our-team')}

  • - Founder -
    - @good-karma -

    {_t("landing-page.founder")}

    + Founder +
    + @good-karma +

    {_t('landing-page.founder')}

  • - Devops -
    - @talhasch -

    {_t("landing-page.devops-guru")}

    + Devops +
    + @talhasch +

    {_t('landing-page.devops-guru')}

  • - Designer -
    - @dunsky -

    {_t("landing-page.design-guru")}

    + Designer +
    + @dunsky +

    {_t('landing-page.design-guru')}

  • -
  • - {_t("landing-page.community-contributors")} - {_t("landing-page.blockchain-witnesses")} +
  • + + {_t('landing-page.community-contributors')} + + + {_t('landing-page.blockchain-witnesses')} +
-
- Our Team +
+ Our Team Senior Fish Junior Fish
-
- - Big fish -
-
-
-
    +
    + + Big fish +
    +
    +
    +
    • - {_t("landing-page.about")} + {_t('landing-page.about')}
    • - {_t("landing-page.faq")} + {_t('landing-page.faq')}
    • - {_t("landing-page.terms-of-service")} + + {_t('landing-page.terms-of-service')} +
    • - {_t("landing-page.privacy-policy")} + + {_t('landing-page.privacy-policy')} +
    -
      +
      • - {_t("landing-page.discover")} + {_t('landing-page.discover')}
      • -

        props.toggleUIProp("login")}>{_t("landing-page.sign-in")}

        +

        props.toggleUIProp('login')}> + {_t('landing-page.sign-in')} +

      • - {_t("landing-page.communities")} + + {_t('landing-page.communities')} +
      • - {_t("landing-page.help")} + {_t('landing-page.help')}
    - -
    -

    {_t("landing-page.subscribe-us")}

    + +
    +

    {_t('landing-page.subscribe-us')}

    setEmail(e.target.value)} + onChange={e => setEmail(e.target.value)} required={true} - onInvalid={(e: any) => handleInvalid(e, "landing-page.", 'validation-email')} + onInvalid={(e: any) => + handleInvalid(e, 'landing-page.', 'validation-email') + } onInput={handleOnInput} /> - +
    -

    {_t("landing-page.subscribe-paragraph")}

    - +

    {_t('landing-page.subscribe-paragraph')}

    -
    -
      +
      +
      • - - youtube + + youtube
      • - isElectron() && window.open('https://twitter.com/ecency_official', '_blank', 'top=500,left=200,frame=false,nodeIntegration=no')}> - twitter + + isElectron() && + window.open( + 'https://twitter.com/ecency_official', + '_blank', + 'top=500,left=200,frame=false,nodeIntegration=no', + ) + } + > + twitter
      • - isElectron() && window.open('https://t.me/ecency', '_blank', 'top=500,left=200,frame=false,nodeIntegration=no')}> - telegram + + isElectron() && + window.open( + 'https://t.me/ecency', + '_blank', + 'top=500,left=200,frame=false,nodeIntegration=no', + ) + } + > + telegram
      • - isElectron() && window.open('https://discord.me/ecency', '_blank', 'top=500,left=200,frame=false,nodeIntegration=no')}> - discord + + isElectron() && + window.open( + 'https://discord.me/ecency', + '_blank', + 'top=500,left=200,frame=false,nodeIntegration=no', + ) + } + > + discord
      - -
    -
    - - ecency logo +
    +
    +
    + + ecency logo -

    {_t("landing-page.copy-rights")}

    +

    {_t('landing-page.copy-rights')}

    @@ -452,4 +724,3 @@ const LandingPage = (props: any) => { }; export default LandingPage; - diff --git a/src/common/components/leaderboard/index.spec.tsx b/src/common/components/leaderboard/index.spec.tsx index aa3135b1c5f..01a44d593f5 100644 --- a/src/common/components/leaderboard/index.spec.tsx +++ b/src/common/components/leaderboard/index.spec.tsx @@ -1,34 +1,31 @@ import React from 'react'; -import renderer from "react-test-renderer"; -import {createBrowserHistory} from "history"; +import renderer from 'react-test-renderer'; +import {createBrowserHistory} from 'history'; -import {globalInstance, allOver} from "../../helper/test-helper"; +import {globalInstance, allOver} from '../../helper/test-helper'; import {LeaderBoard} from './index'; -jest.mock("../../api/private-api", () => ({ - getLeaderboard: (duration: string) => - new Promise((resolve) => { - resolve([ - {"_id": "foo", "count": 42, "points": "121.325"}, - {"_id": "bar", "count": 40, "points": "60.040"}, - {"_id": "baz", "count": 26, "points": "44.707"}, - {"_id": "zoo", "count": 22, "points": "55.040"} - ]); - }), +jest.mock('../../api/private-api', () => ({ + getLeaderboard: (duration: string) => + new Promise(resolve => { + resolve([ + {_id: 'foo', count: 42, points: '121.325'}, + {_id: 'bar', count: 40, points: '60.040'}, + {_id: 'baz', count: 26, points: '44.707'}, + {_id: 'zoo', count: 22, points: '55.040'}, + ]); + }), })); - it('(1) Render with data.', async () => { + const props = { + global: globalInstance, + history: createBrowserHistory(), + addAccount: () => {}, + }; - const props = { - global: globalInstance, - history: createBrowserHistory(), - addAccount: () => { - } - }; - - const component = await renderer.create(); - await allOver(); - expect(component.toJSON()).toMatchSnapshot(); + const component = await renderer.create(); + await allOver(); + expect(component.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/leaderboard/index.tsx b/src/common/components/leaderboard/index.tsx index 24ea5394ca1..df8a18fa547 100644 --- a/src/common/components/leaderboard/index.tsx +++ b/src/common/components/leaderboard/index.tsx @@ -1,158 +1,164 @@ -import React from "react"; -import {History} from "history"; +import React from 'react'; +import {History} from 'history'; -import {Global} from "../../store/global/types"; -import {Account} from "../../store/accounts/types"; +import {Global} from '../../store/global/types'; +import {Account} from '../../store/accounts/types'; -import BaseComponent from "../base"; -import UserAvatar from "../user-avatar"; -import ProfileLink from "../profile-link" +import BaseComponent from '../base'; +import UserAvatar from '../user-avatar'; +import ProfileLink from '../profile-link'; -import {getLeaderboard, LeaderBoardDuration, LeaderBoardItem} from "../../api/private-api"; +import { + getLeaderboard, + LeaderBoardDuration, + LeaderBoardItem, +} from '../../api/private-api'; -import { informationVariantSvg } from "../../img/svg"; -import DropDown from "../dropdown"; -import { OverlayTrigger, Tooltip } from "react-bootstrap"; -import LinearProgress from "../linear-progress"; +import {informationVariantSvg} from '../../img/svg'; +import DropDown from '../dropdown'; +import {OverlayTrigger, Tooltip} from 'react-bootstrap'; +import LinearProgress from '../linear-progress'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import _c from "../../util/fix-class-names" +import _c from '../../util/fix-class-names'; interface Props { - global: Global; - history: History; - addAccount: (data: Account) => void; + global: Global; + history: History; + addAccount: (data: Account) => void; } interface State { - data: LeaderBoardItem[], - period: LeaderBoardDuration, - loading: boolean + data: LeaderBoardItem[]; + period: LeaderBoardDuration; + loading: boolean; } export class LeaderBoard extends BaseComponent { - - state: State = { - data: [], - period: "day", - loading: true - } - - componentDidMount() { - this.fetch(); - } - - fetch = () => { - const {period} = this.state; - this.stateSet({loading: true, data: []}); - - getLeaderboard(period).then(data => { - this.stateSet({data}); - this.stateSet({loading: false}); - }); - } - - render() { - - const {data, period, loading} = this.state; - - const menuItems = [ - ...["day", "week", "month"].map((f => { - return { - label: _t(`leaderboard.period-${f}`), - onClick: () => { - this.stateSet({period: f as LeaderBoardDuration}); - this.fetch(); - } - } - })) - ] - - const dropDownConfig = { - history: this.props.history, - label: '', - items: menuItems + state: State = { + data: [], + period: 'day', + loading: true, + }; + + componentDidMount() { + this.fetch(); + } + + fetch = () => { + const {period} = this.state; + this.stateSet({loading: true, data: []}); + + getLeaderboard(period).then(data => { + this.stateSet({data}); + this.stateSet({loading: false}); + }); + }; + + render() { + const {data, period, loading} = this.state; + + const menuItems = [ + ...['day', 'week', 'month'].map(f => { + return { + label: _t(`leaderboard.period-${f}`), + onClick: () => { + this.stateSet({period: f as LeaderBoardDuration}); + this.fetch(); + }, }; + }), + ]; - return ( -
    -
    -
    - {_t('leaderboard.title-stars')} -
    -
    - {_t(`leaderboard.title-${period}`)} -
    -
    - {loading && } - {data.length > 0 && ( -
    -
    - - - {_t('leaderboard.header-score-tip')} - - } - > -
    - {informationVariantSvg} - - {_t('leaderboard.header-score')} - -
    -
    - - {_t('leaderboard.header-reward')} - -
    - - {data.map((r, i) => { - - return
    -
    {i + 1}
    -
    - {ProfileLink({ - ...this.props, - username: r._id, - children: {UserAvatar({...this.props, size: "medium", username: r._id})} - })} -
    -
    - {ProfileLink({ - ...this.props, - username: r._id, - children: {r._id} - })} -
    -
    - {r.count} -
    -
    - {r.points !== '0.000' && `${r.points} POINTS`} -
    -
    ; - })} -
    - )} + const dropDownConfig = { + history: this.props.history, + label: '', + items: menuItems, + }; + return ( +
    +
    +
    + {_t('leaderboard.title-stars')}{' '} + +
    +
    {_t(`leaderboard.title-${period}`)}
    +
    + {loading && } + {data.length > 0 && ( +
    +
    + + + {_t('leaderboard.header-score-tip')} + + } + > +
    + + {informationVariantSvg} + + + {_t('leaderboard.header-score')} + +
    +
    + {_t('leaderboard.header-reward')}
    - ); - } -} + {data.map((r, i) => { + return ( +
    +
    {i + 1}
    +
    + {ProfileLink({ + ...this.props, + username: r._id, + children: ( + + {UserAvatar({ + ...this.props, + size: 'medium', + username: r._id, + })} + + ), + })} +
    +
    + {ProfileLink({ + ...this.props, + username: r._id, + children: {r._id}, + })} +
    +
    {r.count}
    +
    + {r.points !== '0.000' && `${r.points} POINTS`} +
    +
    + ); + })} +
    + )} +
    + ); + } +} export default (p: Props) => { - const props: Props = { - global: p.global, - history: p.history, - addAccount: p.addAccount - }; - - return -} + const props: Props = { + global: p.global, + history: p.history, + addAccount: p.addAccount, + }; + + return ; +}; diff --git a/src/common/components/linear-progress/index.tsx b/src/common/components/linear-progress/index.tsx index 7a5afb53f42..f9e8c314447 100644 --- a/src/common/components/linear-progress/index.tsx +++ b/src/common/components/linear-progress/index.tsx @@ -1,12 +1,12 @@ -import React, { Component } from 'react'; +import React, {Component} from 'react'; export default class LinearProgress extends Component { - render() { - return ( -
    -
    -
    -
    - ); - } + render() { + return ( +
    +
    +
    +
    + ); + } } diff --git a/src/common/components/list-style-toggle/index.spec.tsx b/src/common/components/list-style-toggle/index.spec.tsx index 2af79538134..6867698f546 100644 --- a/src/common/components/list-style-toggle/index.spec.tsx +++ b/src/common/components/list-style-toggle/index.spec.tsx @@ -1,14 +1,14 @@ -import React from "react"; +import React from 'react'; -import ListStyleToggle from "./index"; +import ListStyleToggle from './index'; -import { ListStyle } from "../../store/global/types"; +import {ListStyle} from '../../store/global/types'; -import TestRenderer from "react-test-renderer"; +import TestRenderer from 'react-test-renderer'; -import { globalInstance } from "../../helper/test-helper"; +import {globalInstance} from '../../helper/test-helper'; -it("(1) Default", () => { +it('(1) Default', () => { const props = { global: globalInstance, toggleListStyle: () => {}, @@ -17,9 +17,9 @@ it("(1) Default", () => { expect(renderer.toJSON()).toMatchSnapshot(); }); -it("(2) Toggled", () => { +it('(2) Toggled', () => { const props = { - global: { ...globalInstance, listStyle: ListStyle.grid }, + global: {...globalInstance, listStyle: ListStyle.grid}, toggleListStyle: () => {}, }; const renderer = TestRenderer.create(); diff --git a/src/common/components/list-style-toggle/index.tsx b/src/common/components/list-style-toggle/index.tsx index a39d4cf2baf..0356093629e 100644 --- a/src/common/components/list-style-toggle/index.tsx +++ b/src/common/components/list-style-toggle/index.tsx @@ -1,75 +1,79 @@ -import React, { Component } from "react"; +import React, {Component} from 'react'; -import isEqual from "react-fast-compare"; +import isEqual from 'react-fast-compare'; // import { Dropdown, DropdownButton } from "react-bootstrap"; -import DropDown, {MenuItem} from "../dropdown"; +import DropDown, {MenuItem} from '../dropdown'; -import { Global } from "../../store/global/types"; +import {Global} from '../../store/global/types'; -import { _t } from "../../i18n"; +import {_t} from '../../i18n'; -import _c from "../../util/fix-class-names"; +import _c from '../../util/fix-class-names'; -import { - viewModuleSvg, - gridView, - listView, - menuDownSvg, -} from "../../img/svg"; +import {viewModuleSvg, gridView, listView, menuDownSvg} from '../../img/svg'; interface Props { - global: Global; - toggleListStyle: (view: string | null) => void; + global: Global; + toggleListStyle: (view: string | null) => void; } export default class ListStyleToggle extends Component { - shouldComponentUpdate(nextProps: Readonly): boolean { - return !isEqual( - this.props.global.listStyle, - nextProps.global.listStyle - ); - } + shouldComponentUpdate(nextProps: Readonly): boolean { + return !isEqual(this.props.global.listStyle, nextProps.global.listStyle); + } - changeStyle = (view: string) => { - const { toggleListStyle } = this.props; + changeStyle = (view: string) => { + const {toggleListStyle} = this.props; - toggleListStyle(view); - }; + toggleListStyle(view); + }; - render() { - const { global } = this.props; - const { listStyle } = global; + render() { + const {global} = this.props; + const {listStyle} = global; - const dropDownItems: MenuItem[] = [ - { - label: {gridView} {_t("layouts.grid")}, - active: global.listStyle === "grid", - onClick: () => {this.changeStyle("grid")}, - }, - { - label: {listView} {_t("layouts.classic")}, - active: global.listStyle === "row", - onClick: () => {this.changeStyle("row")}, - }, - ]; + const dropDownItems: MenuItem[] = [ + { + label: ( + + {gridView} {_t('layouts.grid')} + + ), + active: global.listStyle === 'grid', + onClick: () => { + this.changeStyle('grid'); + }, + }, + { + label: ( + + {listView} {_t('layouts.classic')} + + ), + active: global.listStyle === 'row', + onClick: () => { + this.changeStyle('row'); + }, + }, + ]; - const dropDownConfig = { - history: null, - label: ( - - {viewModuleSvg}{" "} - {menuDownSvg} - - ), - items: dropDownItems, - preElem: undefined, - }; + const dropDownConfig = { + history: null, + label: ( + + {viewModuleSvg}{' '} + {menuDownSvg} + + ), + items: dropDownItems, + preElem: undefined, + }; - return ( -
    - -
    - ); - } + return ( +
    + +
    + ); + } } diff --git a/src/common/components/login-required/index.tsx b/src/common/components/login-required/index.tsx index 3f0d8f6003e..fb0b31ff0a7 100644 --- a/src/common/components/login-required/index.tsx +++ b/src/common/components/login-required/index.tsx @@ -1,52 +1,51 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import {User} from "../../store/users/types"; -import {Account} from "../../store/accounts/types"; -import {ActiveUser} from "../../store/active-user/types"; -import {UI, ToggleType} from "../../store/ui/types"; +import {User} from '../../store/users/types'; +import {Account} from '../../store/accounts/types'; +import {ActiveUser} from '../../store/active-user/types'; +import {UI, ToggleType} from '../../store/ui/types'; interface Props { - users: User[]; - activeUser: ActiveUser | null; - ui: UI; - children: JSX.Element; - setActiveUser: (username: string | null) => void; - updateActiveUser: (data?: Account) => void; - deleteUser: (username: string) => void; - toggleUIProp: (what: ToggleType) => void; + users: User[]; + activeUser: ActiveUser | null; + ui: UI; + children: JSX.Element; + setActiveUser: (username: string | null) => void; + updateActiveUser: (data?: Account) => void; + deleteUser: (username: string) => void; + toggleUIProp: (what: ToggleType) => void; } export class LoginRequired extends Component { + toggle = () => { + const {toggleUIProp} = this.props; + toggleUIProp('login'); + }; - toggle = () => { - const {toggleUIProp} = this.props; - toggleUIProp('login'); - }; + render() { + const {children, activeUser} = this.props; - render() { - const {children, activeUser} = this.props; - - if (activeUser) { - return children; - } - - return React.cloneElement(children, { - onClick: this.toggle, - }); + if (activeUser) { + return children; } + + return React.cloneElement(children, { + onClick: this.toggle, + }); + } } export default (p: Props) => { - const props: Props = { - users: p.users, - activeUser: p.activeUser, - ui: p.ui, - children: p.children, - setActiveUser: p.setActiveUser, - updateActiveUser: p.updateActiveUser, - deleteUser: p.deleteUser, - toggleUIProp: p.toggleUIProp, - } - - return -} + const props: Props = { + users: p.users, + activeUser: p.activeUser, + ui: p.ui, + children: p.children, + setActiveUser: p.setActiveUser, + updateActiveUser: p.updateActiveUser, + deleteUser: p.deleteUser, + toggleUIProp: p.toggleUIProp, + }; + + return ; +}; diff --git a/src/common/components/login/index.spec.tsx b/src/common/components/login/index.spec.tsx index 003b753d077..cb0be34779d 100644 --- a/src/common/components/login/index.spec.tsx +++ b/src/common/components/login/index.spec.tsx @@ -1,90 +1,85 @@ -import React from "react"; +import React from 'react'; -import renderer from "react-test-renderer"; +import renderer from 'react-test-renderer'; -import {createBrowserHistory} from "history"; +import {createBrowserHistory} from 'history'; -import {Login} from "./index"; - -import {globalInstance, activeUserMaker} from "../../helper/test-helper"; +import {Login} from './index'; +import {globalInstance, activeUserMaker} from '../../helper/test-helper'; const defProps = { - history: createBrowserHistory(), - global: globalInstance, - users: [], - activeUser: null, - setActiveUser: () => { - }, - deleteUser: () => { - }, - toggleUIProp: () => { - }, - doLogin: async () => { - } + history: createBrowserHistory(), + global: globalInstance, + users: [], + activeUser: null, + setActiveUser: () => {}, + deleteUser: () => {}, + toggleUIProp: () => {}, + doLogin: async () => {}, }; -it("(1) Default render", () => { - const component = renderer.create(); - expect(component.toJSON()).toMatchSnapshot(); +it('(1) Default render', () => { + const component = renderer.create(); + expect(component.toJSON()).toMatchSnapshot(); }); -it("(2) With users", () => { - const users = [ - { - username: "user1", - accessToken: "aa", - refreshToken: "bb", - expiresIn: 2, - postingKey: null - }, - { - username: "user2", - accessToken: "aa", - refreshToken: "bb", - expiresIn: 2, - postingKey: null - }, - ]; +it('(2) With users', () => { + const users = [ + { + username: 'user1', + accessToken: 'aa', + refreshToken: 'bb', + expiresIn: 2, + postingKey: null, + }, + { + username: 'user2', + accessToken: 'aa', + refreshToken: 'bb', + expiresIn: 2, + postingKey: null, + }, + ]; - const props = {...defProps, users}; + const props = {...defProps, users}; - const component = renderer.create(); - expect(component.toJSON()).toMatchSnapshot(); + const component = renderer.create(); + expect(component.toJSON()).toMatchSnapshot(); }); -it("(3) With users and active user", () => { - const users = [ - { - username: "user1", - accessToken: "aa", - refreshToken: "bb", - expiresIn: 2, - postingKey: null - }, - { - username: "user2", - accessToken: "aa", - refreshToken: "bb", - expiresIn: 2, - postingKey: null - }, - ]; +it('(3) With users and active user', () => { + const users = [ + { + username: 'user1', + accessToken: 'aa', + refreshToken: 'bb', + expiresIn: 2, + postingKey: null, + }, + { + username: 'user2', + accessToken: 'aa', + refreshToken: 'bb', + expiresIn: 2, + postingKey: null, + }, + ]; - const activeUser = activeUserMaker("user2"); + const activeUser = activeUserMaker('user2'); - const props = {...defProps, users, activeUser}; + const props = {...defProps, users, activeUser}; - const component = renderer.create(); - expect(component.toJSON()).toMatchSnapshot(); + const component = renderer.create(); + expect(component.toJSON()).toMatchSnapshot(); }); -it("(4) Show keychain option", () => { - const props = { - ...defProps, - global: {...globalInstance, hasKeyChain: true} - }; +it('(4) Show keychain option', () => { + const props = { + ...defProps, + global: {...globalInstance, hasKeyChain: true}, + }; - const component = renderer.create(); - expect(component.toJSON()).toMatchSnapshot(); + const component = renderer.create(); + expect(component.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/login/index.tsx b/src/common/components/login/index.tsx index 83777942427..a415fec4fc1 100644 --- a/src/common/components/login/index.tsx +++ b/src/common/components/login/index.tsx @@ -1,638 +1,800 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import {Modal, Form, Button, FormControl, Spinner} from "react-bootstrap"; +import {Modal, Form, Button, FormControl, Spinner} from 'react-bootstrap'; -import isEqual from "react-fast-compare"; +import isEqual from 'react-fast-compare'; -import {PrivateKey, PublicKey, cryptoUtils} from "@hiveio/dhive"; +import {PrivateKey, PublicKey, cryptoUtils} from '@hiveio/dhive'; -import {History, Location} from "history"; -import * as ls from "../../util/local-storage"; +import {History, Location} from 'history'; +import * as ls from '../../util/local-storage'; -import {AppWindow} from "../../../client/window"; +import {AppWindow} from '../../../client/window'; -import {Global} from "../../store/global/types"; -import {UI} from "../../store/ui/types"; -import {User} from "../../store/users/types"; -import {Account} from "../../store/accounts/types"; -import {ActiveUser} from "../../store/active-user/types"; -import {ToggleType} from "../../store/ui/types"; +import {Global} from '../../store/global/types'; +import {UI} from '../../store/ui/types'; +import {User} from '../../store/users/types'; +import {Account} from '../../store/accounts/types'; +import {ActiveUser} from '../../store/active-user/types'; +import {ToggleType} from '../../store/ui/types'; -import BaseComponent from "../base"; -import UserAvatar from "../user-avatar"; -import Tooltip from "../tooltip"; -import PopoverConfirm from "../popover-confirm"; -import OrDivider from "../or-divider"; -import {error} from "../feedback"; +import BaseComponent from '../base'; +import UserAvatar from '../user-avatar'; +import Tooltip from '../tooltip'; +import PopoverConfirm from '../popover-confirm'; +import OrDivider from '../or-divider'; +import {error} from '../feedback'; -import {getAuthUrl, makeHsCode} from "../../helper/hive-signer"; -import {hsLogin} from "../../../desktop/app/helper/hive-signer"; +import {getAuthUrl, makeHsCode} from '../../helper/hive-signer'; +import {hsLogin} from '../../../desktop/app/helper/hive-signer'; -import {getAccount} from "../../api/hive"; -import {usrActivity} from "../../api/private-api"; -import {hsTokenRenew} from "../../api/auth-api"; -import {formatError, grantPostingPermission, revokePostingPermission} from "../../api/operations"; +import {getAccount} from '../../api/hive'; +import {usrActivity} from '../../api/private-api'; +import {hsTokenRenew} from '../../api/auth-api'; +import { + formatError, + grantPostingPermission, + revokePostingPermission, +} from '../../api/operations'; -import {getRefreshToken} from "../../helper/user-token"; +import {getRefreshToken} from '../../helper/user-token'; -import {addAccountAuthority, removeAccountAuthority, signBuffer} from "../../helper/keychain"; +import { + addAccountAuthority, + removeAccountAuthority, + signBuffer, +} from '../../helper/keychain'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import _c from "../../util/fix-class-names"; +import _c from '../../util/fix-class-names'; -import {deleteForeverSvg} from "../../img/svg"; +import {deleteForeverSvg} from '../../img/svg'; declare var window: AppWindow; interface LoginKcProps { - toggleUIProp: (what: ToggleType) => void; - doLogin: (hsCode: string, postingKey: null | undefined | string, account: Account) => Promise; - global: Global; + toggleUIProp: (what: ToggleType) => void; + doLogin: ( + hsCode: string, + postingKey: null | undefined | string, + account: Account, + ) => Promise; + global: Global; } interface LoginKcState { - username: string; - inProgress: boolean; + username: string; + inProgress: boolean; } export class LoginKc extends BaseComponent { - state: LoginKcState = { - username: "", - inProgress: false + state: LoginKcState = { + username: '', + inProgress: false, + }; + + usernameChanged = ( + e: React.ChangeEvent, + ): void => { + const {value: username} = e.target; + this.stateSet({username: username.trim().toLowerCase()}); + }; + + inputKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + this.login().then(); } + }; - usernameChanged = (e: React.ChangeEvent): void => { - const {value: username} = e.target; - this.stateSet({username: username.trim().toLowerCase()}); - } - - inputKeyDown = (e: React.KeyboardEvent) => { - if (e.key === 'Enter') { - this.login().then(); - } - } + hide = () => { + const {toggleUIProp} = this.props; + toggleUIProp('login'); + }; - hide = () => { - const {toggleUIProp} = this.props; - toggleUIProp('login'); + login = async () => { + const {username} = this.state; + if (!username) { + return; } - login = async () => { - const {username} = this.state; - if (!username) { - return; - } - - let account: Account; - - this.stateSet({inProgress: true}); - - try { - account = await getAccount(username); - } catch (err) { - error(_t('login.error-user-fetch')); - return; - } finally { - this.stateSet({inProgress: false}); - } - - if (!(account && account.name === username)) { - error(_t('login.error-user-not-found')); - return; - } - - const hasPostingPerm = account?.posting!.account_auths.filter(x => x[0] === "ecency.app").length > 0; - - if (!hasPostingPerm) { - const weight = account.posting!.weight_threshold; - - this.stateSet({inProgress: true}); - try { - await addAccountAuthority(username, "ecency.app", "Posting", weight) - } catch (err) { - error(_t('login.error-permission')); - return; - } finally { - this.stateSet({inProgress: false}); - } - } - - this.stateSet({inProgress: true}); - - const signer = (message: string): Promise => signBuffer(username, message, "Active").then(r => r.result); - - let code: string; - try { - code = await makeHsCode(username, signer); - } catch (err) { - error(formatError(err)); - this.stateSet({inProgress: false}); - return; - } + let account: Account; - const {doLogin} = this.props; + this.stateSet({inProgress: true}); - doLogin(code, null, account) - .then(() => { - this.hide(); - }) - .catch(() => { - error(_t('g.server-error')); - }) - .finally(() => { - this.stateSet({inProgress: false}); - }); + try { + account = await getAccount(username); + } catch (err) { + error(_t('login.error-user-fetch')); + return; + } finally { + this.stateSet({inProgress: false}); } - back = () => { - const {toggleUIProp} = this.props; - toggleUIProp("loginKc"); + if (!(account && account.name === username)) { + error(_t('login.error-user-not-found')); + return; } - render() { - const {username, inProgress} = this.state; - const {global} = this.props; - - const keyChainLogo = global.isElectron ? "./img/keychain.png" : require("../../img/keychain.png"); + const hasPostingPerm = + account?.posting!.account_auths.filter(x => x[0] === 'ecency.app') + .length > 0; + + if (!hasPostingPerm) { + const weight = account.posting!.weight_threshold; + + this.stateSet({inProgress: true}); + try { + await addAccountAuthority(username, 'ecency.app', 'Posting', weight); + } catch (err) { + error(_t('login.error-permission')); + return; + } finally { + this.stateSet({inProgress: false}); + } + } - const spinner = ; + this.stateSet({inProgress: true}); - return <> -
    - Logo -

    {_t('login.with-keychain')}

    -
    + const signer = (message: string): Promise => + signBuffer(username, message, 'Active').then(r => r.result); -
    { - e.preventDefault(); - }}> - - - - - -
    - + let code: string; + try { + code = await makeHsCode(username, signer); + } catch (err) { + error(formatError(err)); + this.stateSet({inProgress: false}); + return; } + + const {doLogin} = this.props; + + doLogin(code, null, account) + .then(() => { + this.hide(); + }) + .catch(() => { + error(_t('g.server-error')); + }) + .finally(() => { + this.stateSet({inProgress: false}); + }); + }; + + back = () => { + const {toggleUIProp} = this.props; + toggleUIProp('loginKc'); + }; + + render() { + const {username, inProgress} = this.state; + const {global} = this.props; + + const keyChainLogo = global.isElectron + ? './img/keychain.png' + : require('../../img/keychain.png'); + + const spinner = ( + + ); + + return ( + <> +
    + Logo +

    {_t('login.with-keychain')}

    +
    + +
    { + e.preventDefault(); + }} + > + + + + + +
    + + ); + } } interface UserItemProps { - global: Global; - user: User; - activeUser: ActiveUser | null; - disabled: boolean; - onSelect: (user: User) => void; - onDelete: (user: User) => void; - containerRef?: React.RefObject; + global: Global; + user: User; + activeUser: ActiveUser | null; + disabled: boolean; + onSelect: (user: User) => void; + onDelete: (user: User) => void; + containerRef?: React.RefObject; } export class UserItem extends Component { - render() { - const {user, activeUser, disabled, containerRef} = this.props; - - return ( -
    { - const {onSelect} = this.props; - onSelect(user); - }} - > - {UserAvatar({...this.props, username: user.username, size: "medium"})} - @{user.username} - {activeUser && activeUser.username === user.username &&
    } -
    - { - const {onDelete} = this.props; - onDelete(user); - }} - placement="left" - trigger="click" - containerRef={containerRef} - > -
    { - e.stopPropagation(); - }} - > - - {deleteForeverSvg} - -
    -
    -
    - ); - } + render() { + const {user, activeUser, disabled, containerRef} = this.props; + + return ( +
    { + const {onSelect} = this.props; + onSelect(user); + }} + > + {UserAvatar({...this.props, username: user.username, size: 'medium'})} + @{user.username} + {activeUser && activeUser.username === user.username && ( +
    + )} +
    + { + const {onDelete} = this.props; + onDelete(user); + }} + placement='left' + trigger='click' + containerRef={containerRef} + > +
    { + e.stopPropagation(); + }} + > + + {deleteForeverSvg} + +
    +
    +
    + ); + } } interface LoginProps { - history: History; - global: Global; - users: User[]; - activeUser: ActiveUser | null; - setActiveUser: (username: string | null) => void; - deleteUser: (username: string) => void; - toggleUIProp: (what: ToggleType) => void; - doLogin: (hsCode: string, postingKey: null | undefined | string, account: Account) => Promise; - userListRef?: any; + history: History; + global: Global; + users: User[]; + activeUser: ActiveUser | null; + setActiveUser: (username: string | null) => void; + deleteUser: (username: string) => void; + toggleUIProp: (what: ToggleType) => void; + doLogin: ( + hsCode: string, + postingKey: null | undefined | string, + account: Account, + ) => Promise; + userListRef?: any; } interface State { - username: string; - key: string; - inProgress: boolean; + username: string; + key: string; + inProgress: boolean; } export class Login extends BaseComponent { - state: State = { - username: '', - key: '', - inProgress: false + state: State = { + username: '', + key: '', + inProgress: false, + }; + + shouldComponentUpdate( + nextProps: Readonly, + nextState: Readonly, + ): boolean { + return ( + !isEqual(this.props.users, nextProps.users) || + !isEqual(this.props.activeUser, nextProps.activeUser) || + !isEqual(this.state, nextState) + ); + } + + hide = () => { + const {toggleUIProp} = this.props; + toggleUIProp('login'); + }; + + userSelect = (user: User) => { + const {doLogin} = this.props; + + this.stateSet({inProgress: true}); + + getAccount(user.username) + .then(account => { + let token = getRefreshToken(user.username); + if (!token) { + error(`${_t('login.error-user-not-found-cache')}`); + } + return token + ? doLogin(token, user.postingKey, account) + : this.userDelete(user); + }) + .then(() => { + this.hide(); + let shouldShowTutorialJourney = ls.get(`${user.username}HadTutorial`); + + if ( + !shouldShowTutorialJourney && + shouldShowTutorialJourney && + shouldShowTutorialJourney !== 'true' + ) { + ls.set(`${user.username}HadTutorial`, 'false'); + } + }) + .catch(() => { + error(_t('g.server-error')); + }) + .finally(() => { + this.stateSet({inProgress: false}); + }); + }; + + userDelete = (user: User) => { + const {activeUser, deleteUser, setActiveUser} = this.props; + deleteUser(user.username); + + // logout if active user + if (activeUser && user.username === activeUser.username) { + setActiveUser(null); } - - shouldComponentUpdate(nextProps: Readonly, nextState: Readonly): boolean { - return !isEqual(this.props.users, nextProps.users) - || !isEqual(this.props.activeUser, nextProps.activeUser) - || !isEqual(this.state, nextState); + }; + + usernameChanged = ( + e: React.ChangeEvent, + ): void => { + const {value: username} = e.target; + this.stateSet({username: username.trim().toLowerCase()}); + }; + + keyChanged = ( + e: React.ChangeEvent, + ): void => { + const {value: key} = e.target; + this.stateSet({key: key.trim()}); + }; + + inputKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + this.login().then(); } - - hide = () => { - const {toggleUIProp} = this.props; - toggleUIProp('login'); + }; + + hsLogin = () => { + const {global, history} = this.props; + if (global.isElectron) { + hsLogin() + .then(r => { + this.hide(); + history.push(`/auth?code=${r.code}`); + }) + .catch(e => { + error(e); + }); + return; } - userSelect = (user: User) => { - const {doLogin} = this.props; - - this.stateSet({inProgress: true}); - - getAccount(user.username) - .then((account) => { - let token = getRefreshToken(user.username); - if(!token){ - error(`${_t("login.error-user-not-found-cache")}`) - } - return token ? doLogin(token, user.postingKey, account) : this.userDelete(user) - }) - .then(() => { - this.hide(); - let shouldShowTutorialJourney = ls.get(`${user.username}HadTutorial`); - - if(!shouldShowTutorialJourney && (shouldShowTutorialJourney && shouldShowTutorialJourney!=='true')){ - ls.set(`${user.username}HadTutorial`, 'false'); - } - }) - .catch(() => { - error(_t('g.server-error')); - }) - .finally(() => { - this.stateSet({inProgress: false}); - }); - } + window.location.href = getAuthUrl(); + }; - userDelete = (user: User) => { - const {activeUser, deleteUser, setActiveUser} = this.props; - deleteUser(user.username); + kcLogin = () => { + const {toggleUIProp} = this.props; + toggleUIProp('loginKc'); + }; - // logout if active user - if (activeUser && user.username === activeUser.username) { - setActiveUser(null); - } - } + login = async () => { + const {username, key} = this.state; - usernameChanged = (e: React.ChangeEvent): void => { - const {value: username} = e.target; - this.stateSet({username: username.trim().toLowerCase()}); + if (username === '' || key === '') { + error(_t('login.error-fields-required')); + return; } - keyChanged = (e: React.ChangeEvent): void => { - const {value: key} = e.target; - this.stateSet({key: key.trim()}); - } + // Warn if the code is a public key + try { + PublicKey.fromString(key); + error(_t('login.error-public-key')); + return; + } catch (e) {} - inputKeyDown = (e: React.KeyboardEvent) => { - if (e.key === 'Enter') { - this.login().then(); - } - } + let account: Account; - hsLogin = () => { - const {global, history} = this.props; - if (global.isElectron) { - hsLogin().then(r => { - this.hide(); - history.push(`/auth?code=${r.code}`); - }).catch((e) => { - error(e); - }); - return; - } + this.stateSet({inProgress: true}); - window.location.href = getAuthUrl(); + try { + account = await getAccount(username); + } catch (err) { + error(_t('login.error-user-fetch')); + return; + } finally { + this.stateSet({inProgress: false}); } - kcLogin = () => { - const {toggleUIProp} = this.props; - toggleUIProp("loginKc"); + if (!(account && account.name === username)) { + error(_t('login.error-user-not-found')); + return; } - login = async () => { - const {username, key} = this.state; - - if (username === '' || key === '') { - error(_t('login.error-fields-required')); - return; - } - - // Warn if the code is a public key - try { - PublicKey.fromString(key); - error(_t('login.error-public-key')); - return; - } catch (e) { - } - - let account: Account; - + // Posting public key of the account + const postingPublic = account?.posting!.key_auths.map(x => x[0]); + + const isPlainPassword = !cryptoUtils.isWif(key); + + let thePrivateKey: PrivateKey; + + // Whether using posting private key to login + let withPostingKey = false; + + if ( + !isPlainPassword && + postingPublic.includes( + PrivateKey.fromString(key).createPublic().toString(), + ) + ) { + // Login with posting private key + withPostingKey = true; + thePrivateKey = PrivateKey.fromString(key); + } else { + // Login with master or active private key + // Get active private key from user entered code + if (isPlainPassword) { + thePrivateKey = PrivateKey.fromLogin(account.name, key, 'active'); + } else { + thePrivateKey = PrivateKey.fromString(key); + } + + // Generate public key from the private key + const activePublicInput = thePrivateKey.createPublic().toString(); + + // Active public key of the account + const activePublic = account?.active!.key_auths.map(x => x[0]); + + // Compare keys + if (!activePublic.includes(activePublicInput)) { + error(_t('login.error-authenticate')); // enter master or active key + return; + } + + const hasPostingPerm = + account?.posting!.account_auths.filter(x => x[0] === 'ecency.app') + .length > 0; + + if (!hasPostingPerm) { this.stateSet({inProgress: true}); - try { - account = await getAccount(username); + await grantPostingPermission(thePrivateKey, account, 'ecency.app'); } catch (err) { - error(_t('login.error-user-fetch')); - return; + error(_t('login.error-permission')); + return; } finally { - this.stateSet({inProgress: false}); + this.stateSet({inProgress: false}); } + } + } - if (!(account && account.name === username)) { - error(_t('login.error-user-not-found')); - return; + // Prepare hivesigner code + const signer = (message: string): Promise => { + const hash = cryptoUtils.sha256(message); + return new Promise(resolve => + resolve(thePrivateKey.sign(hash).toString()), + ); + }; + const code = await makeHsCode(account.name, signer); + + this.stateSet({inProgress: true}); + + const {doLogin} = this.props; + + doLogin(code, withPostingKey ? key : null, account) + .then(() => { + if ( + !ls.get(`${username}HadTutorial`) || + (ls.get(`${username}HadTutorial`) && + ls.get(`${username}HadTutorial`) !== 'true') + ) { + ls.set(`${username}HadTutorial`, 'false'); } - // Posting public key of the account - const postingPublic = account?.posting!.key_auths.map(x => x[0]); - - const isPlainPassword = !cryptoUtils.isWif(key); - - let thePrivateKey: PrivateKey; - - // Whether using posting private key to login - let withPostingKey = false; - - if (!isPlainPassword && postingPublic.includes(PrivateKey.fromString(key).createPublic().toString())) { - // Login with posting private key - withPostingKey = true; - thePrivateKey = PrivateKey.fromString(key); - } else { - // Login with master or active private key - // Get active private key from user entered code - if (isPlainPassword) { - thePrivateKey = PrivateKey.fromLogin(account.name, key, 'active'); - } else { - thePrivateKey = PrivateKey.fromString(key); - } - - // Generate public key from the private key - const activePublicInput = thePrivateKey.createPublic().toString(); - - // Active public key of the account - const activePublic = account?.active!.key_auths.map(x => x[0]); - - // Compare keys - if (!activePublic.includes(activePublicInput)) { - error(_t('login.error-authenticate')); // enter master or active key - return; - } - - const hasPostingPerm = account?.posting!.account_auths.filter(x => x[0] === "ecency.app").length > 0; - - if (!hasPostingPerm) { - this.stateSet({inProgress: true}); - try { - await grantPostingPermission(thePrivateKey, account, "ecency.app") - } catch (err) { - error(_t('login.error-permission')); - return; - } finally { - this.stateSet({inProgress: false}); - } - } - } + let shouldShowTutorialJourney = ls.get(`${username}HadTutorial`); - // Prepare hivesigner code - const signer = (message: string): Promise => { - const hash = cryptoUtils.sha256(message); - return new Promise((resolve) => resolve(thePrivateKey.sign(hash).toString())); + if ( + !shouldShowTutorialJourney && + shouldShowTutorialJourney && + shouldShowTutorialJourney === 'false' + ) { + ls.set(`${username}HadTutorial`, 'false'); } - const code = await makeHsCode(account.name, signer); - - this.stateSet({inProgress: true}); - - const {doLogin} = this.props; - - doLogin(code, (withPostingKey ? key : null), account) - .then(() => { - if(!ls.get(`${username}HadTutorial`) || ls.get(`${username}HadTutorial`) && ls.get(`${username}HadTutorial`)!=='true'){ - ls.set(`${username}HadTutorial`, 'false'); - } - - let shouldShowTutorialJourney = ls.get(`${username}HadTutorial`); - - if(!shouldShowTutorialJourney && (shouldShowTutorialJourney && shouldShowTutorialJourney==='false')){ - ls.set(`${username}HadTutorial`, 'false'); - } + this.hide(); + }) + .catch(() => { + error(_t('g.server-error')); + }) + .finally(() => { + this.stateSet({inProgress: false}); + }); + }; + + render() { + const {username, key, inProgress} = this.state; + const {users, activeUser, global, userListRef} = this.props; + const logo = global.isElectron + ? './img/logo-circle.svg' + : require('../../img/logo-circle.svg'); + const hsLogo = global.isElectron + ? './img/hive-signer.svg' + : require('../../img/hive-signer.svg'); + const keyChainLogo = global.isElectron + ? './img/keychain.png' + : require('../../img/keychain.png'); + + const spinner = ( + + ); + + return ( + <> + {users.length === 0 && ( +
    + Logo +

    {_t('login.title')}

    +
    + )} + + {users.length > 0 && ( + <> +
    +
    {_t('g.login-as')}
    +
    + {users.map(u => { + return ( + + ); + })} +
    +
    + + + )} + +
    { + e.preventDefault(); + }} + > +

    {_t('login.with-user-pass')}

    + + + + + + +

    + {_t('login.login-info-1')}{' '} + { + e.preventDefault(); + this.hide(); + const {history} = this.props; + history.push('/faq#how-to-signin'); + setTimeout(() => { + const el = document.getElementById('how-to-signin'); + if (el) el.scrollIntoView(); + }, 300); + }} + href='#' + > + {_t('login.login-info-2')} + +

    + +
    + + + {global.hasKeyChain && ( + + )} + {activeUser === null && ( +

    + {_t('login.sign-up-text-1')} +   + { + e.preventDefault(); this.hide(); - }) - .catch(() => { - error(_t('g.server-error')); - }) - .finally(() => { - this.stateSet({inProgress: false}); - }); - } - render() { - const {username, key, inProgress} = this.state; - const {users, activeUser, global, userListRef} = this.props; - const logo = global.isElectron ? "./img/logo-circle.svg" : require('../../img/logo-circle.svg'); - const hsLogo = global.isElectron ? "./img/hive-signer.svg" : require("../../img/hive-signer.svg"); - const keyChainLogo = global.isElectron ? "./img/keychain.png" : require("../../img/keychain.png"); - - const spinner = ; - - return ( - <> - {users.length === 0 && ( -

    - )} - - {users.length > 0 && ( - <> -
    -
    {_t("g.login-as")}
    -
    - {users.map((u) => { - return ( - - ); - })} -
    -
    - - - )} - -
    { - e.preventDefault(); - }}> -

    {_t('login.with-user-pass')}

    - - - - - - -

    {_t('login.login-info-1')} { - e.preventDefault(); - this.hide(); - const {history} = this.props; - history.push("/faq#how-to-signin"); - setTimeout(() => { - const el = document.getElementById("how-to-signin"); - if (el) el.scrollIntoView(); - }, 300) - })} href="#">{_t('login.login-info-2')}

    - -
    - - - {global.hasKeyChain && ( - - )} - {activeUser === null && ( -

    - {_t("login.sign-up-text-1")} -   - { - e.preventDefault(); - this.hide(); - - const {history} = this.props; - history.push("/signup"); - - }}>{_t("login.sign-up-text-2")} -

    - )} - - ); - } + const {history} = this.props; + history.push('/signup'); + }} + > + {_t('login.sign-up-text-2')} + +

    + )} + + ); + } } interface Props { - history: History; - location: Location; - global: Global; - ui: UI; - users: User[]; - activeUser: ActiveUser | null; - addUser: (user: User) => void; - setActiveUser: (username: string | null) => void; - updateActiveUser: (data?: Account) => void; - deleteUser: (username: string) => void; - toggleUIProp: (what: ToggleType) => void; + history: History; + location: Location; + global: Global; + ui: UI; + users: User[]; + activeUser: ActiveUser | null; + addUser: (user: User) => void; + setActiveUser: (username: string | null) => void; + updateActiveUser: (data?: Account) => void; + deleteUser: (username: string) => void; + toggleUIProp: (what: ToggleType) => void; } export default class LoginDialog extends Component { + userListRef = React.createRef(); - userListRef = React.createRef(); - - hide = () => { - const {toggleUIProp} = this.props; - toggleUIProp("login"); - } - - componentWillUnmount() { - const {toggleUIProp, ui} = this.props; - if (ui.loginKc) { - toggleUIProp("loginKc"); - } - } - - doLogin = async (hsCode: string, postingKey: null | undefined | string, account: Account) => { - const {global, setActiveUser, updateActiveUser, addUser} = this.props; - - // get access token from code - return hsTokenRenew(hsCode).then(x => { - const user: User = { - username: x.username, - accessToken: x.access_token, - refreshToken: x.refresh_token, - expiresIn: x.expires_in, - postingKey - }; - - // add / update user data - addUser(user); - - // activate user - setActiveUser(user.username); - - // add account data of the user to the reducer - updateActiveUser(account); - - if (global.usePrivate) { - // login activity - usrActivity(user.username, 20); - } - - // redirection based on path name - const {location, history} = this.props; - if (location.pathname.startsWith("/signup")) { - const u = `/@${x.username}/feed`; - history.push(u); - } - }); - } + hide = () => { + const {toggleUIProp} = this.props; + toggleUIProp('login'); + }; - render() { - const {ui} = this.props; - - return ( - - - - {!ui.loginKc && } - {ui.loginKc && } - - - ); + componentWillUnmount() { + const {toggleUIProp, ui} = this.props; + if (ui.loginKc) { + toggleUIProp('loginKc'); } + } + + doLogin = async ( + hsCode: string, + postingKey: null | undefined | string, + account: Account, + ) => { + const {global, setActiveUser, updateActiveUser, addUser} = this.props; + + // get access token from code + return hsTokenRenew(hsCode).then(x => { + const user: User = { + username: x.username, + accessToken: x.access_token, + refreshToken: x.refresh_token, + expiresIn: x.expires_in, + postingKey, + }; + + // add / update user data + addUser(user); + + // activate user + setActiveUser(user.username); + + // add account data of the user to the reducer + updateActiveUser(account); + + if (global.usePrivate) { + // login activity + usrActivity(user.username, 20); + } + + // redirection based on path name + const {location, history} = this.props; + if (location.pathname.startsWith('/signup')) { + const u = `/@${x.username}/feed`; + history.push(u); + } + }); + }; + + render() { + const {ui} = this.props; + + return ( + + + + {!ui.loginKc && ( + + )} + {ui.loginKc && } + + + ); + } } diff --git a/src/common/components/market-chart/index.spec.tsx b/src/common/components/market-chart/index.spec.tsx index a0dfac4e92e..4ea6d781109 100644 --- a/src/common/components/market-chart/index.spec.tsx +++ b/src/common/components/market-chart/index.spec.tsx @@ -1,20 +1,20 @@ -import React from "react"; +import React from 'react'; -import MarketChart from "./index"; +import MarketChart from './index'; -import TestRenderer from "react-test-renderer"; +import TestRenderer from 'react-test-renderer'; -import {allOver} from "../../helper/test-helper"; +import {allOver} from '../../helper/test-helper'; -it("(1) Default render", async () => { - const props = { - loading: false, - bids: [], - asks: [], - theme: 'day' - }; +it('(1) Default render', async () => { + const props = { + loading: false, + bids: [], + asks: [], + theme: 'day', + }; - const renderer = await TestRenderer.create(); - await allOver(); - expect(renderer.toJSON()).toMatchSnapshot(); -}); \ No newline at end of file + const renderer = await TestRenderer.create(); + await allOver(); + expect(renderer.toJSON()).toMatchSnapshot(); +}); diff --git a/src/common/components/market-chart/index.tsx b/src/common/components/market-chart/index.tsx index aa968fd9ba5..72c4e38be2d 100644 --- a/src/common/components/market-chart/index.tsx +++ b/src/common/components/market-chart/index.tsx @@ -1,182 +1,193 @@ -import React, { useEffect } from 'react'; -import { _t } from '../../i18n'; -import { Theme } from '../../store/global/types'; +import React, {useEffect} from 'react'; +import {_t} from '../../i18n'; +import {Theme} from '../../store/global/types'; const ReactHighcharts = require('react-highcharts/dist/ReactHighstock'); const power = 100; const precision = 1000; -const MarketChart = ({ bids, asks, theme }:any) => { - +const MarketChart = ({bids, asks, theme}: any) => { if (!bids.length && !asks.length) { - return null; - } - - useEffect(()=>{ - - if (theme) { - // I know I'm adding this logic out of nowhere here but if I - // touch the exisiting logic that's there for mobile, I know I'll break - // many pages that will be hard to detect. It works fine here. - // We can refactor it later in toggleTheme() - - let body: any = document.getElementsByTagName("body"); - if (!body) return; - body = body[0]; - body.classList.remove(`theme-night`); - body.classList.remove(`theme-day`); - body.classList.add(`theme-${theme}`); + return null; } - },[theme]) + useEffect(() => { + if (theme) { + // I know I'm adding this logic out of nowhere here but if I + // touch the exisiting logic that's there for mobile, I know I'll break + // many pages that will be hard to detect. It works fine here. + // We can refactor it later in toggleTheme() + + let body: any = document.getElementsByTagName('body'); + if (!body) return; + body = body[0]; + body.classList.remove(`theme-night`); + body.classList.remove(`theme-day`); + body.classList.add(`theme-${theme}`); + } + }, [theme]); const depth_chart_config = generateDepthChart(bids, asks, theme); - return
    + return ( +
    -
    -} +
    + ); +}; export default MarketChart; -function generateBidAsk(bidsArray:any, asksArray:any) { - // Input raw orders (from TOP of order book DOWN), output grouped depth - function aggregateOrders(orders:any) { - if (typeof orders == 'undefined') { - return []; - } - - let ttl = 0; - return orders - .map((o:any) => { - ttl += o.hbd; - return [o.real_price * power, ttl]; - }) - .sort((a:any, b:any) => { - // Sort here to make sure arrays are in the right direction for HighCharts - return a[0] - b[0]; - }); +function generateBidAsk(bidsArray: any, asksArray: any) { + // Input raw orders (from TOP of order book DOWN), output grouped depth + function aggregateOrders(orders: any) { + if (typeof orders == 'undefined') { + return []; } - let bids = aggregateOrders(bidsArray); - // Insert a 0 entry to make sure the chart is centered properly - bids.unshift([0, bids[0][1]]); + let ttl = 0; + return orders + .map((o: any) => { + ttl += o.hbd; + return [o.real_price * power, ttl]; + }) + .sort((a: any, b: any) => { + // Sort here to make sure arrays are in the right direction for HighCharts + return a[0] - b[0]; + }); + } + + let bids = aggregateOrders(bidsArray); + // Insert a 0 entry to make sure the chart is centered properly + bids.unshift([0, bids[0][1]]); - let asks = aggregateOrders(asksArray); - // Insert a final entry to make sure the chart is centered properly - asks.push([asks[asks.length - 1][0] * 4, asks[asks.length - 1][1]]); + let asks = aggregateOrders(asksArray); + // Insert a final entry to make sure the chart is centered properly + asks.push([asks[asks.length - 1][0] * 4, asks[asks.length - 1][1]]); - return { bids, asks }; + return {bids, asks}; } -function getMinMax(bids:any, asks:any) { - const highestBid = bids.length ? bids[bids.length - 1][0] : 0; - const lowestAsk = asks.length ? asks[0][0] : 1; +function getMinMax(bids: any, asks: any) { + const highestBid = bids.length ? bids[bids.length - 1][0] : 0; + const lowestAsk = asks.length ? asks[0][0] : 1; - const middle = (highestBid + lowestAsk) / 2; + const middle = (highestBid + lowestAsk) / 2; - return { - min: Math.max(middle * 0.65, bids[0][0]), - max: Math.min(middle * 1.35, asks[asks.length - 1][0]), - }; + return { + min: Math.max(middle * 0.65, bids[0][0]), + max: Math.min(middle * 1.35, asks[asks.length - 1][0]), + }; } -function generateDepthChart(bidsArray:any, asksArray:any, theme: string) { - const { bids, asks } = generateBidAsk(bidsArray, asksArray); - let series = []; - - const { min, max } = getMinMax(bids, asks); - if (bids[0]) { - series.push({ - step: 'right', - name: _t('market.bid'), - color: 'rgba(0,150,0,1.0)', - dataGrouping: { - enabled: false - }, - fillColor: 'rgba(0,150,0,0.2)', - data: bids, - }); - } - if (asks[0]) { - series.push({ - step: 'left', - name: _t('market.ask'), - color: 'rgba(150,0,0,1.0)', - fillColor: 'rgba(150,0,0,0.2)', - data: asks, - }); - } - - let depth_chart_config = { - title: { text: null }, - subtitle: { text: null }, - chart: { type: 'area', zoomType: 'x', backgroundColor: theme === Theme.night ? "#202834" : "white"}, - xAxis: { - min: min, - max: max, - labels: { - formatter: (values:any) => { - return values.value / power; - }, - }, - ordinal: false, - lineColor: '#000000', - title: { - text: null, - }, - }, - yAxis: { - title: { text: null }, - lineWidth: 2, - labels: { - align: 'left', - formatter: (values:any) => { - const value = values.value / precision; - let label = '$' + - (value > 1e6 - ? (value / 1e6).toFixed(3) + 'M' - : value > 10000 - ? (value / 1e3).toFixed(0) + 'k' - : value); - return label - }, - }, - gridLineWidth: 1, - }, - legend: { enabled: false }, - credits: { - enabled: false, - }, - rangeSelector: { - enabled: false, - }, - navigator: { - enabled: false, - }, - scrollbar: { - enabled: false, +function generateDepthChart(bidsArray: any, asksArray: any, theme: string) { + const {bids, asks} = generateBidAsk(bidsArray, asksArray); + let series = []; + + const {min, max} = getMinMax(bids, asks); + if (bids[0]) { + series.push({ + step: 'right', + name: _t('market.bid'), + color: 'rgba(0,150,0,1.0)', + dataGrouping: { + enabled: false, + }, + fillColor: 'rgba(0,150,0,0.2)', + data: bids, + }); + } + if (asks[0]) { + series.push({ + step: 'left', + name: _t('market.ask'), + color: 'rgba(150,0,0,1.0)', + fillColor: 'rgba(150,0,0,0.2)', + data: asks, + }); + } + + let depth_chart_config = { + title: {text: null}, + subtitle: {text: null}, + chart: { + type: 'area', + zoomType: 'x', + backgroundColor: theme === Theme.night ? '#202834' : 'white', + }, + xAxis: { + min: min, + max: max, + labels: { + formatter: (values: any) => { + return values.value / power; }, - dataGrouping: { - enabled: false, + }, + ordinal: false, + lineColor: '#000000', + title: { + text: null, + }, + }, + yAxis: { + title: {text: null}, + lineWidth: 2, + labels: { + align: 'left', + formatter: (values: any) => { + const value = values.value / precision; + let label = + '$' + + (value > 1e6 + ? (value / 1e6).toFixed(3) + 'M' + : value > 10000 + ? (value / 1e3).toFixed(0) + 'k' + : value); + return label; }, - tooltip: { - shared: false, - backgroundColor: 'rgba(0, 0, 0, 0.3)', - formatter: ({chart:{hoverPoint:{options:{x,y}}, hoverSeries:{name}}}:any) => { - return ( - `${_t('market.price')}: ${(x / power).toFixed(6)} ${ - '$/HIVE' - }
    \u25CF${ - name - }: ${(y / 1000).toFixed(3)} HBD ($) ` +'' - ); - }, - style: { - color: '#FFFFFF', - }, + }, + gridLineWidth: 1, + }, + legend: {enabled: false}, + credits: { + enabled: false, + }, + rangeSelector: { + enabled: false, + }, + navigator: { + enabled: false, + }, + scrollbar: { + enabled: false, + }, + dataGrouping: { + enabled: false, + }, + tooltip: { + shared: false, + backgroundColor: 'rgba(0, 0, 0, 0.3)', + formatter: ({ + chart: { + hoverPoint: { + options: {x, y}, + }, + hoverSeries: {name}, }, - plotOptions: { series: { animation: true } }, - series, - }; - - return depth_chart_config; + }: any) => { + return ( + `${_t('market.price')}: ${(x / power).toFixed( + 6, + )} ${'$/HIVE'}
    \u25CF${name}: ${( + y / 1000 + ).toFixed(3)} HBD ($) ` + '' + ); + }, + style: { + color: '#FFFFFF', + }, + }, + plotOptions: {series: {animation: true}}, + series, + }; + + return depth_chart_config; } diff --git a/src/common/components/market-data/index.tsx b/src/common/components/market-data/index.tsx index 52183d0fa09..400c3d13aca 100644 --- a/src/common/components/market-data/index.tsx +++ b/src/common/components/market-data/index.tsx @@ -1,89 +1,148 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import moment from "moment"; +import moment from 'moment'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import {Tsx} from "../../i18n/helper"; -import { appleSvg, desktopSvg, eyeBoldSvg, eyeSvg, googleSvg } from "../../img/svg"; -import { Link } from "react-router-dom"; -import DownloadTrigger from "../download-trigger"; -import SSRSuspense from "../ssr-suspense"; -import { Global } from "../../store/global/types"; -const Market = React.lazy(()=> import ("./market")); +import {Tsx} from '../../i18n/helper'; +import { + appleSvg, + desktopSvg, + eyeBoldSvg, + eyeSvg, + googleSvg, +} from '../../img/svg'; +import {Link} from 'react-router-dom'; +import DownloadTrigger from '../download-trigger'; +import SSRSuspense from '../ssr-suspense'; +import {Global} from '../../store/global/types'; +const Market = React.lazy(() => import('./market')); interface MarketDataProps { - global: Global + global: Global; } interface MarketDataState { - visible: boolean + visible: boolean; } -export default class MarketData extends Component { - constructor(props:MarketDataProps){ - super(props); - this.state = { - visible: false - } - } +export default class MarketData extends Component< + MarketDataProps, + MarketDataState +> { + constructor(props: MarketDataProps) { + super(props); + this.state = { + visible: false, + }; + } - render() { - const fromTs = moment().subtract(2, "days").format("X"); - const toTs = moment().format("X"); - const {visible} = this.state; - const {global:{theme}} = this.props; + render() { + const fromTs = moment().subtract(2, 'days').format('X'); + const toTs = moment().format('X'); + const {visible} = this.state; + const { + global: {theme}, + } = this.props; - return
    -
    - {_t("market-data.title")} -
    this.setState({visible: !visible})}> - {visible ? eyeSvg : eyeBoldSvg} -
    -
    - {visible && -
    - } + return ( +
    +
    + + {_t('market-data.title')} +
    this.setState({visible: !visible})} + > + {visible ? eyeSvg : eyeBoldSvg}
    - {visible ? - - - - -
    - -
    - {_t("g.downloads")} - - {appleSvg} - {googleSvg} - {desktopSvg} - -
    -
    - -
    - - {_t("entry-index.faq")} - - - {_t("entry-index.tos")} - - - {_t("entry-index.pp")} - -
    -
    -
    :
    -
    - FAQ
    -
    - Terms of service
    -
    - Privacy Policy
    -
    - Market
    -
    } +
    + {visible && ( + +
    + + )}
    - } + {visible ? ( + + + + + +
    + +
    + {_t('g.downloads')} + + {appleSvg} + {googleSvg} + {desktopSvg} + +
    +
    + +
    + + {_t('entry-index.faq')} + + + {_t('entry-index.tos')} + + + {_t('entry-index.pp')} + +
    +
    +
    + ) : ( +
    +
    + FAQ +
    +
    + Terms of service +
    +
    + Privacy Policy +
    +
    + Market +
    +
    + )} +
    + ); + } } diff --git a/src/common/components/market-data/market.tsx b/src/common/components/market-data/market.tsx index 0454cd0b251..f1a18cb3d0b 100644 --- a/src/common/components/market-data/market.tsx +++ b/src/common/components/market-data/market.tsx @@ -1,220 +1,232 @@ -import { isEqual } from 'lodash'; +import {isEqual} from 'lodash'; import moment from 'moment'; import React from 'react'; import ReactHighcharts from 'react-highcharts'; -import { getMarketData } from '../../api/misc'; +import {getMarketData} from '../../api/misc'; import BaseComponent from '../base'; -import numeral from "numeral"; -import { _t } from '../../i18n'; -import { Theme } from '../../store/global/types'; +import numeral from 'numeral'; +import {_t} from '../../i18n'; +import {Theme} from '../../store/global/types'; interface Price { - time: number; - price: number; + time: number; + price: number; } interface Props { - label: string; - coin: string; - vsCurrency: string; - fromTs: string; - toTs: string; - formatter: string; - theme: Theme; + label: string; + coin: string; + vsCurrency: string; + fromTs: string; + toTs: string; + formatter: string; + theme: Theme; } interface State { - prices: Price[]; + prices: Price[]; } class Market extends BaseComponent { - state: State = { - prices: [], - } - - node = React.createRef(); + state: State = { + prices: [], + }; + + node = React.createRef(); + + componentDidMount() { + const {coin, vsCurrency, fromTs, toTs} = this.props; + this._mounted = true; + + getMarketData(coin, vsCurrency, fromTs, toTs) + .then(r => { + if (r && r.prices && this._mounted) { + const prices: Price[] = r.prices.map((x: any) => ({ + time: x[0], + price: x[1], + })); + this.stateSet({prices}, () => { + this.attachEvents(); + }); + } + }) + .catch(err => console.log('market_data_error', err)); + } - componentDidMount() { - const {coin, vsCurrency, fromTs, toTs} = this.props; - this._mounted = true; + componentWillUnmount() { + this._mounted = false; - getMarketData(coin, vsCurrency, fromTs, toTs).then((r) => { - if (r && r.prices && this._mounted) { - const prices: Price[] = r.prices.map((x: any) => ({time: x[0], price: x[1]})); - this.stateSet({prices}, () => { - this.attachEvents(); - }); - } - }).catch(err=> console.log('market_data_error', err)); - } + this.detachEvents(); + } - componentWillUnmount() { - this._mounted = false; + shouldComponentUpdate( + nextProps: Readonly, + nextState: Readonly, + ): boolean { + return !isEqual(this.state, nextState); + } - this.detachEvents(); - } + attachEvents = () => { + const node = this.node.current; + if (!node) return; - shouldComponentUpdate(nextProps: Readonly, nextState: Readonly): boolean { - return !isEqual(this.state, nextState); - } + const graph = node.querySelector('.graph')!; - attachEvents = () => { - const node = this.node.current; - if (!node) return; + node.querySelectorAll('.ct-point').forEach(el => { + const left = el.getAttribute('x1'); - const graph = node.querySelector(".graph")!; + const graphBar = document.createElement('span'); + graphBar.setAttribute('class', 'graph-bar'); + graphBar.style.left = `${left}px`; - node.querySelectorAll('.ct-point').forEach(el => { - const left = el.getAttribute("x1"); + graphBar.addEventListener('mouseover', this.pointMouseMove); + graphBar.addEventListener('mouseout', this.pointMouseOut); - const graphBar = document.createElement("span"); - graphBar.setAttribute("class", "graph-bar"); - graphBar.style.left = `${left}px`; + graph.appendChild(graphBar); + }); + }; - graphBar.addEventListener('mouseover', this.pointMouseMove); - graphBar.addEventListener('mouseout', this.pointMouseOut); + detachEvents = () => { + const node = this.node.current; + if (!node) return; - graph.appendChild(graphBar); - }); - } + node.querySelectorAll('.graph-bar').forEach(el => { + el.removeEventListener('mouseover', this.pointMouseMove); + el.removeEventListener('mouseout', this.pointMouseOut); + }); + }; - detachEvents = () => { + pointMouseMove = (e: Event) => { + const node = this.node.current; + if (!node) return; - const node = this.node.current; - if (!node) return; + const {formatter} = this.props; - node.querySelectorAll('.graph-bar').forEach(el => { - el.removeEventListener('mouseover', this.pointMouseMove); - el.removeEventListener('mouseout', this.pointMouseOut); - }); - } + const circle = e.currentTarget; + const circles = node.querySelectorAll('.graph-bar'); + const index = Array.prototype.indexOf.call(circles, circle); + const item = this.state.prices[index]; - pointMouseMove = (e: Event) => { - const node = this.node.current; - if (!node) return; + const strPrice = numeral(item.price).format(formatter); + const strDate = moment(item.time).format('YYYY-MM-DD HH:mm:ss'); + const html = `${strPrice} ${strDate}`; - const {formatter} = this.props; + const tooltip = node.querySelector('.tooltip') as HTMLElement; + tooltip.style.visibility = 'visible'; + tooltip!.innerHTML = html; + }; - const circle = e.currentTarget; - const circles = node.querySelectorAll('.graph-bar'); - const index = Array.prototype.indexOf.call(circles, circle); - const item = this.state.prices[index]; + pointMouseOut = () => { + const node = this.node.current; + if (!node) return; - const strPrice = numeral(item.price).format(formatter); - const strDate = moment(item.time).format("YYYY-MM-DD HH:mm:ss"); - const html = `${strPrice} ${strDate}`; + const tooltip = node.querySelector('.tooltip') as HTMLElement; + tooltip.style.visibility = 'hidden'; + tooltip!.innerHTML = ''; + }; - const tooltip = node.querySelector(".tooltip") as HTMLElement; - tooltip.style.visibility = "visible"; - tooltip!.innerHTML = html; + componentDidUpdate(prevProps: Props) { + if (prevProps.theme !== this.props.theme) { + this.forceUpdate(); } - - pointMouseOut = () => { - const node = this.node.current; - if (!node) return; - - const tooltip = node.querySelector(".tooltip") as HTMLElement; - tooltip.style.visibility = "hidden"; - tooltip!.innerHTML = ""; + } + + render() { + const {label, formatter, theme} = this.props; + const {prices} = this.state; + let strPrice = '...'; + if (prices.length) { + const price = prices[prices.length - 1].price; + strPrice = numeral(price).format(formatter); } - componentDidUpdate(prevProps:Props){ - if(prevProps.theme !== this.props.theme){ - this.forceUpdate() - } - } - - render() { - const {label, formatter, theme} = this.props; - const {prices} = this.state; - let strPrice = "..."; - if (prices.length) { - const price = prices[prices.length - 1].price; - strPrice = numeral(price).format(formatter); - } - - const config:any = { - title: { - text: null - }, - credits: { enabled: false }, - legend:{ - enabled:false - }, - chart:{ - height: 140, - zoomType: 'x', - backgroundColor: theme === Theme.night ? "#161d26" : "" - }, - plotOptions:{ - area:{ - fillColor:theme === Theme.night ? "#2e3d51" : "#f3f7fb", - lineColor:'#81acef', - lineWidth:3 - }, - }, - tooltip:{ - valueDecimals:2, - useHTML:true, - shadow:false, - formatter: (({chart}:any) => { - let date = moment(chart.hoverPoint.options.x).calendar(); - let rate = chart.hoverPoint.options.y; - return `
    ${_t("g.when")}: ${date}
    ${_t("g.price")}:${rate.toFixed(3)}
    ` - }) as any, - enabled: true - }, - xAxis: { - lineWidth: 0, - minorGridLineWidth: 0, - lineColor: 'transparent', - labels: { - enabled: false - }, - title: { - text:null - }, - minorTickLength: 0, - tickLength: 0, - gridLineWidth: 0 - }, - yAxis: { - lineWidth: 0, - minorGridLineWidth: 0, - lineColor: 'transparent', - title: { - text: null - }, - labels: { - enabled: false, - }, - minorTickLength: 0, - tickLength: 0, - gridLineWidth: 0, - }, - series: [ - { - name: ' ', - data: prices.map(item=>[item.time, item.price]), - type: 'line', - enableMouseTracking: true - }, - ], - }; - - return
    -
    - -
    -
    -
    - {label}{" "}{strPrice} -
    -
    -
    -
    ; - } + const config: any = { + title: { + text: null, + }, + credits: {enabled: false}, + legend: { + enabled: false, + }, + chart: { + height: 140, + zoomType: 'x', + backgroundColor: theme === Theme.night ? '#161d26' : '', + }, + plotOptions: { + area: { + fillColor: theme === Theme.night ? '#2e3d51' : '#f3f7fb', + lineColor: '#81acef', + lineWidth: 3, + }, + }, + tooltip: { + valueDecimals: 2, + useHTML: true, + shadow: false, + formatter: (({chart}: any) => { + let date = moment(chart.hoverPoint.options.x).calendar(); + let rate = chart.hoverPoint.options.y; + return `
    ${_t('g.when')}: ${date}
    ${_t( + 'g.price', + )}:${rate.toFixed(3)}
    `; + }) as any, + enabled: true, + }, + xAxis: { + lineWidth: 0, + minorGridLineWidth: 0, + lineColor: 'transparent', + labels: { + enabled: false, + }, + title: { + text: null, + }, + minorTickLength: 0, + tickLength: 0, + gridLineWidth: 0, + }, + yAxis: { + lineWidth: 0, + minorGridLineWidth: 0, + lineColor: 'transparent', + title: { + text: null, + }, + labels: { + enabled: false, + }, + minorTickLength: 0, + tickLength: 0, + gridLineWidth: 0, + }, + series: [ + { + name: ' ', + data: prices.map(item => [item.time, item.price]), + type: 'line', + enableMouseTracking: true, + }, + ], + }; + + return ( +
    +
    + +
    +
    +
    + {label}{' '} + {strPrice} +
    +
    +
    +
    + ); + } } -export default Market; \ No newline at end of file +export default Market; diff --git a/src/common/components/md-handler/index.ts b/src/common/components/md-handler/index.ts index 4e44bcee732..1019b621a8f 100644 --- a/src/common/components/md-handler/index.ts +++ b/src/common/components/md-handler/index.ts @@ -1,80 +1,80 @@ -import {Component} from "react"; -import {History} from "history"; +import {Component} from 'react'; +import {History} from 'history'; -import {Global} from "../../store/global/types"; +import {Global} from '../../store/global/types'; interface Props { - history: History; - global: Global; + history: History; + global: Global; } export default class MdHandler extends Component { - componentDidMount() { - document.addEventListener("click", this.clicked); - } - - componentWillUnmount() { - document.removeEventListener("click", this.clicked); - } + componentDidMount() { + document.addEventListener('click', this.clicked); + } - clicked = (e: MouseEvent): void => { - const {global} = this.props; + componentWillUnmount() { + document.removeEventListener('click', this.clicked); + } - let el = e.target as HTMLElement; + clicked = (e: MouseEvent): void => { + const {global} = this.props; - // A element can be wrapped with inline element. Look parent elements. - while (el.tagName !== "A") { - if (!el.parentNode) { - break; - } - el = el.parentNode as HTMLElement; - } + let el = e.target as HTMLElement; - if (!el || el.tagName !== "A") { - return; - } + // A element can be wrapped with inline element. Look parent elements. + while (el.tagName !== 'A') { + if (!el.parentNode) { + break; + } + el = el.parentNode as HTMLElement; + } - if ( - el.classList.contains("markdown-author-link") || - el.classList.contains("markdown-post-link") || - el.classList.contains("markdown-community-link") || - el.classList.contains("markdown-tag-link") - ) { - e.preventDefault(); - const href = el.getAttribute("href"); - if (!href) { - return; - } - const {history} = this.props; - history.push(href); - } + if (!el || el.tagName !== 'A') { + return; + } - if (el.classList.contains("markdown-video-link")) { - const embedSrc = el.getAttribute("data-embed-src"); - if (embedSrc) { - el.innerHTML = ``; - return; - } - const videoHref = el.getAttribute("data-video-href"); - if (videoHref) { - window.open(videoHref); - e.preventDefault(); - return; - } - } - // TODO: check if moving markdown-img-link from into didn't break this - if (global.isElectron && el.classList.contains("markdown-img-link")) { - e.preventDefault(); - const href = el.getAttribute("href"); - if (!href) { - return; - } + if ( + el.classList.contains('markdown-author-link') || + el.classList.contains('markdown-post-link') || + el.classList.contains('markdown-community-link') || + el.classList.contains('markdown-tag-link') + ) { + e.preventDefault(); + const href = el.getAttribute('href'); + if (!href) { + return; + } + const {history} = this.props; + history.push(href); + } - window.open(href); - } - }; + if (el.classList.contains('markdown-video-link')) { + const embedSrc = el.getAttribute('data-embed-src'); + if (embedSrc) { + el.innerHTML = ``; + return; + } + const videoHref = el.getAttribute('data-video-href'); + if (videoHref) { + window.open(videoHref); + e.preventDefault(); + return; + } + } + // TODO: check if moving markdown-img-link from into didn't break this + if (global.isElectron && el.classList.contains('markdown-img-link')) { + e.preventDefault(); + const href = el.getAttribute('href'); + if (!href) { + return; + } - render() { - return null; + window.open(href); } + }; + + render() { + return null; + } } diff --git a/src/common/components/message-no-data/index.spec.tsx b/src/common/components/message-no-data/index.spec.tsx index b18cae3b320..1d552fa5a39 100644 --- a/src/common/components/message-no-data/index.spec.tsx +++ b/src/common/components/message-no-data/index.spec.tsx @@ -1,20 +1,18 @@ -import React from "react"; -import TestRenderer from "react-test-renderer"; -import { globalInstance } from '../../helper/test-helper'; -import MessageNoData from "./index"; +import React from 'react'; +import TestRenderer from 'react-test-renderer'; +import {globalInstance} from '../../helper/test-helper'; +import MessageNoData from './index'; const defProps = { - buttonTo: "", - buttonText: "", - title: "", - description: "", - global: globalInstance + buttonTo: '', + buttonText: '', + title: '', + description: '', + global: globalInstance, }; -it("Renders a message", () => { - const props = {...defProps}; - const renderer = TestRenderer.create( - ); - expect(renderer.toJSON()).toMatchSnapshot(); +it('Renders a message', () => { + const props = {...defProps}; + const renderer = TestRenderer.create(); + expect(renderer.toJSON()).toMatchSnapshot(); }); - diff --git a/src/common/components/message-no-data/index.tsx b/src/common/components/message-no-data/index.tsx index 9fb98fa1c9c..9de876da40a 100644 --- a/src/common/components/message-no-data/index.tsx +++ b/src/common/components/message-no-data/index.tsx @@ -1,39 +1,54 @@ import React from 'react'; -import { Button } from 'react-bootstrap'; -import { Link } from 'react-router-dom'; +import {Button} from 'react-bootstrap'; +import {Link} from 'react-router-dom'; interface Props { - buttonTo: string; - buttonText: string; - title: string; - description: string; - img?: any; - global: any; + buttonTo: string; + buttonText: string; + title: string; + description: string; + img?: any; + global: any; } -const MessageNoData = ({buttonText, buttonTo, title, description, img, global}:Props) => { - const writer = global.isElectron ? "./img/writer.png" : require("../../img/writer.png"); - return
    -
    - -
    -
    -

    {title}

    -

    {description}

    - {buttonText && } -
    -
    -} +const MessageNoData = ({ + buttonText, + buttonTo, + title, + description, + img, + global, +}: Props) => { + const writer = global.isElectron + ? './img/writer.png' + : require('../../img/writer.png'); + return ( +
    +
    + +
    +
    +

    {title}

    +

    {description}

    + {buttonText && ( + + + + )} +
    +
    + ); +}; export default (p: Props) => { - const props: Props = { - buttonTo: p.buttonTo, - buttonText: p.buttonText, - title: p.title, - description: p.description, - img: p.img, - global: p.global - } + const props: Props = { + buttonTo: p.buttonTo, + buttonText: p.buttonText, + title: p.title, + description: p.description, + img: p.img, + global: p.global, + }; - return ; -} + return ; +}; diff --git a/src/common/components/meta/index.tsx b/src/common/components/meta/index.tsx index 1208a255d92..c62e3ea75dd 100644 --- a/src/common/components/meta/index.tsx +++ b/src/common/components/meta/index.tsx @@ -1,8 +1,8 @@ -import React, { Component } from "react"; +import React, {Component} from 'react'; -import { Helmet } from "react-helmet"; +import {Helmet} from 'react-helmet'; -import defaults from "../../constants/defaults.json"; +import defaults from '../../constants/defaults.json'; interface Props { title?: string; @@ -19,14 +19,24 @@ interface Props { const capitalize = (s: string) => { return s.charAt(0).toUpperCase() + s.slice(1); -} +}; const title_ = (s: string): string => `${s} | ${capitalize(defaults.name)}`; export default class Meta extends Component { render() { - const { title, description, url, canonical, tag, keywords, published, modified, rss } = this.props; - let { image } = this.props; + const { + title, + description, + url, + canonical, + tag, + keywords, + published, + modified, + rss, + } = this.props; + let {image} = this.props; if (!image) { image = `${defaults.base}/og.jpg`; @@ -35,92 +45,100 @@ export default class Meta extends Component { return ( <> - - - - - + + + + + {title && ( {title} - - + + )} {!title && ( {defaults.title} - - + + )} {description && ( - - - + + + )} {!description && ( - - - + + + )} {url && ( - - + + )} {canonical && ( - + )} {tag && ( - + )} {keywords && ( - + )} {published && ( - - + + )} {modified && ( - + )} {rss && ( - + )} - - + + ); diff --git a/src/common/components/modal-confirm/index.tsx b/src/common/components/modal-confirm/index.tsx index 5bda11c1c0f..16980a7753f 100644 --- a/src/common/components/modal-confirm/index.tsx +++ b/src/common/components/modal-confirm/index.tsx @@ -1,44 +1,52 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import {Modal, Button} from "react-bootstrap"; +import {Modal, Button} from 'react-bootstrap'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; interface Props { - titleText?: string; - okText?: string; - okVariant?: "primary" | "danger"; - cancelText?: string; - onConfirm?: () => void; - onCancel?: () => void; + titleText?: string; + okText?: string; + okVariant?: 'primary' | 'danger'; + cancelText?: string; + onConfirm?: () => void; + onCancel?: () => void; } export default class ModalConfirm extends Component { - confirm = () => { - const {onConfirm} = this.props; - if (onConfirm) { - onConfirm(); - } - }; + confirm = () => { + const {onConfirm} = this.props; + if (onConfirm) { + onConfirm(); + } + }; - cancel = () => { - const {onCancel} = this.props; - if (onCancel) { - onCancel(); - } - }; + cancel = () => { + const {onCancel} = this.props; + if (onCancel) { + onCancel(); + } + }; - render() { - const {titleText, okText, okVariant, cancelText} = this.props; + render() { + const {titleText, okText, okVariant, cancelText} = this.props; - return - - {titleText || _t("confirm.title")} - - - - - - ; - } + return ( + + + {titleText || _t('confirm.title')} + + + + + + + ); + } } diff --git a/src/common/components/mute-btn/index.spec.tsx b/src/common/components/mute-btn/index.spec.tsx index 82007574015..535e67a640f 100644 --- a/src/common/components/mute-btn/index.spec.tsx +++ b/src/common/components/mute-btn/index.spec.tsx @@ -1,63 +1,63 @@ -import React from "react"; +import React from 'react'; -import {MuteBtn, DialogBody} from "./index"; -import TestRenderer from "react-test-renderer"; +import {MuteBtn, DialogBody} from './index'; +import TestRenderer from 'react-test-renderer'; -import {entryInstance1, communityInstance1, activeUserMaker} from "../../helper/test-helper"; -import {Entry, EntryStat} from "../../store/entries/types"; +import { + entryInstance1, + communityInstance1, + activeUserMaker, +} from '../../helper/test-helper'; +import {Entry, EntryStat} from '../../store/entries/types'; const defProps = { - entry: {...entryInstance1}, - community: {...communityInstance1}, - activeUser: activeUserMaker("foo"), - onSuccess: () => { - } + entry: {...entryInstance1}, + community: {...communityInstance1}, + activeUser: activeUserMaker('foo'), + onSuccess: () => {}, }; it("(1) Should show 'mute' label", () => { - const props = {...defProps}; - const renderer = TestRenderer.create(); - expect(renderer.toJSON()).toMatchSnapshot(); + const props = {...defProps}; + const renderer = TestRenderer.create(); + expect(renderer.toJSON()).toMatchSnapshot(); }); - it("(2) Should show 'unmute' label", () => { - const stats = {...entryInstance1.stats!, gray: true}; - const entry = {...entryInstance1, stats}; + const stats = {...entryInstance1.stats!, gray: true}; + const entry = {...entryInstance1, stats}; - const props = {...defProps, entry: entry}; - const renderer = TestRenderer.create(); - expect(renderer.toJSON()).toMatchSnapshot(); + const props = {...defProps, entry: entry}; + const renderer = TestRenderer.create(); + expect(renderer.toJSON()).toMatchSnapshot(); }); it("(3) Dialog body for 'mute'", () => { - const props = { - entry: {...entryInstance1}, - inProgress: false, - onSubmit: () => { - } - } - const renderer = TestRenderer.create(); - expect(renderer.toJSON()).toMatchSnapshot(); + const props = { + entry: {...entryInstance1}, + inProgress: false, + onSubmit: () => {}, + }; + const renderer = TestRenderer.create(); + expect(renderer.toJSON()).toMatchSnapshot(); }); it("(4) Dialog body for 'unmute'", () => { - const stats: EntryStat = { - hide: false, - gray: true, - total_votes: 10, - flag_weight: 1.0, - } - - const props = { - entry: { - ...entryInstance1, - stats - }, - inProgress: false, - onSubmit: () => { - } - } - const renderer = TestRenderer.create(); - expect(renderer.toJSON()).toMatchSnapshot(); + const stats: EntryStat = { + hide: false, + gray: true, + total_votes: 10, + flag_weight: 1.0, + }; + + const props = { + entry: { + ...entryInstance1, + stats, + }, + inProgress: false, + onSubmit: () => {}, + }; + const renderer = TestRenderer.create(); + expect(renderer.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/mute-btn/index.tsx b/src/common/components/mute-btn/index.tsx index 173da875386..d7f3f9ddafa 100644 --- a/src/common/components/mute-btn/index.tsx +++ b/src/common/components/mute-btn/index.tsx @@ -1,187 +1,236 @@ -import React from "react"; +import React from 'react'; -import {Button, Form, FormControl, InputGroup, Modal} from "react-bootstrap"; +import {Button, Form, FormControl, InputGroup, Modal} from 'react-bootstrap'; -import isEqual from "react-fast-compare"; +import isEqual from 'react-fast-compare'; -import {Entry, EntryStat} from "../../store/entries/types"; -import {Community} from "../../store/communities/types"; -import {ActiveUser} from "../../store/active-user/types"; -import {clone} from "../../store/util"; +import {Entry, EntryStat} from '../../store/entries/types'; +import {Community} from '../../store/communities/types'; +import {ActiveUser} from '../../store/active-user/types'; +import {clone} from '../../store/util'; -import BaseComponent from "../base"; +import BaseComponent from '../base'; -import {formatError, mutePost} from "../../api/operations"; -import {error} from "../feedback"; +import {formatError, mutePost} from '../../api/operations'; +import {error} from '../feedback'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; -import _c from "../../util/fix-class-names"; +import _c from '../../util/fix-class-names'; interface DialogProps { - entry: Entry; - inProgress: boolean; - onSubmit: (notes: string) => void; + entry: Entry; + inProgress: boolean; + onSubmit: (notes: string) => void; } interface DialogState { - value: string; + value: string; } export class DialogBody extends React.Component { - state: DialogState = { - value: '' - } - - valueChanged = (e: React.ChangeEvent): void => { - const {value} = e.target; - this.setState({value}); - } - - render() { - const {entry, inProgress} = this.props; - const {value} = this.state; - - const isMuted = !!entry.stats?.gray; - - return
    - -
    @{entry.author}/{entry.permlink}
    - - - - - {!isMuted && _t('mute-btn.note-placeholder-mute')} - {isMuted && "unmute" && _t('mute-btn.note-placeholder-unmute')} - -
    -
    - -
    + state: DialogState = { + value: '', + }; + + valueChanged = ( + e: React.ChangeEvent, + ): void => { + const {value} = e.target; + this.setState({value}); + }; + + render() { + const {entry, inProgress} = this.props; + const {value} = this.state; + + const isMuted = !!entry.stats?.gray; + + return ( +
    + +
    + @{entry.author}/{entry.permlink} +
    + + + + + {!isMuted && _t('mute-btn.note-placeholder-mute')} + {isMuted && 'unmute' && _t('mute-btn.note-placeholder-unmute')} + +
    +
    +
    - } +
    + ); + } } interface Props { - entry: Entry; - community: Community; - activeUser: ActiveUser; - onlyDialog?: boolean; - onSuccess: (entry: Entry, mute: boolean) => void; - onCancel?: () => void; + entry: Entry; + community: Community; + activeUser: ActiveUser; + onlyDialog?: boolean; + onSuccess: (entry: Entry, mute: boolean) => void; + onCancel?: () => void; } interface State { - dialog: boolean; - inProgress: boolean; + dialog: boolean; + inProgress: boolean; } export class MuteBtn extends BaseComponent { - state: State = { - dialog: false, - inProgress: false - } - - shouldComponentUpdate(nextProps: Readonly, nextState: Readonly): boolean { - return !isEqual(this.state, nextState) || - !isEqual(this.props.entry, nextProps.entry) || - !isEqual(this.props.community, nextProps.community) || - !isEqual(this.props.activeUser?.username, nextProps.activeUser?.username) - } - - toggleDialog = () => { - const {dialog} = this.state; - this.stateSet({dialog: !dialog}); - } - - mute = (mute: boolean, notes: string) => { - const {entry, community, activeUser, onSuccess} = this.props; - - this.stateSet({inProgress: true}); - mutePost(activeUser.username, community.name, entry.author, entry.permlink, notes, mute) - .then(() => { - const nStats: EntryStat = {...clone(entry.stats), gray: mute} - const nEntry: Entry = {...clone(entry), stats: nStats}; - onSuccess(nEntry, mute); - }) - .catch(err => error(formatError(err))) - .finally(() => this.stateSet({inProgress: false})); + state: State = { + dialog: false, + inProgress: false, + }; + + shouldComponentUpdate( + nextProps: Readonly, + nextState: Readonly, + ): boolean { + return ( + !isEqual(this.state, nextState) || + !isEqual(this.props.entry, nextProps.entry) || + !isEqual(this.props.community, nextProps.community) || + !isEqual(this.props.activeUser?.username, nextProps.activeUser?.username) + ); + } + + toggleDialog = () => { + const {dialog} = this.state; + this.stateSet({dialog: !dialog}); + }; + + mute = (mute: boolean, notes: string) => { + const {entry, community, activeUser, onSuccess} = this.props; + + this.stateSet({inProgress: true}); + mutePost( + activeUser.username, + community.name, + entry.author, + entry.permlink, + notes, + mute, + ) + .then(() => { + const nStats: EntryStat = {...clone(entry.stats), gray: mute}; + const nEntry: Entry = {...clone(entry), stats: nStats}; + onSuccess(nEntry, mute); + }) + .catch(err => error(formatError(err))) + .finally(() => this.stateSet({inProgress: false})); + }; + + render() { + const {entry, onlyDialog} = this.props; + const {inProgress, dialog} = this.state; + const isMuted = !!entry.stats?.gray; + + const cls = _c(`mute-btn ${inProgress ? 'in-progress' : ''}`); + + const modal = + dialog || onlyDialog ? ( + { + const {onCancel} = this.props; + if (onCancel) { + onCancel(); + } + this.toggleDialog(); + }} + keyboard={false} + className='mute-dialog modal-thin-header' + size='lg' + > + + + { + this.toggleDialog(); + this.mute(!isMuted, value); + }} + /> + + + ) : null; + + if (onlyDialog) { + return modal; } - render() { - const {entry, onlyDialog} = this.props; - const {inProgress, dialog} = this.state; - const isMuted = !!entry.stats?.gray; - - const cls = _c(`mute-btn ${inProgress ? "in-progress" : ""}`); - - const modal = (dialog || onlyDialog) ? - { - const {onCancel} = this.props; - if (onCancel) { - onCancel(); - } - this.toggleDialog(); - }} keyboard={false} className="mute-dialog modal-thin-header" size="lg"> - - - { - this.toggleDialog(); - this.mute(!isMuted, value); - }}/> - - : null; - - if (onlyDialog) { - return modal; - } - - if (isMuted) { - return <> -
    { - e.preventDefault(); - this.toggleDialog(); - }}>{_t("mute-btn.unmute")} - {modal} - - } - - return <> - { - e.preventDefault(); - this.toggleDialog(); - }}>{_t("mute-btn.mute")} - {modal} + if (isMuted) { + return ( + <> + { + e.preventDefault(); + this.toggleDialog(); + }} + > + {_t('mute-btn.unmute')} + + {modal} + ); } + + return ( + <> + { + e.preventDefault(); + this.toggleDialog(); + }} + > + {_t('mute-btn.mute')} + + {modal} + + ); + } } export default (p: Props) => { - const props = { - entry: p.entry, - community: p.community, - activeUser: p.activeUser, - onlyDialog: p.onlyDialog, - onSuccess: p.onSuccess, - onCancel: p.onCancel - } - - return ; -} + const props = { + entry: p.entry, + community: p.community, + activeUser: p.activeUser, + onlyDialog: p.onlyDialog, + onSuccess: p.onSuccess, + onCancel: p.onCancel, + }; + + return ; +}; diff --git a/src/common/components/navbar/index.spec.tsx b/src/common/components/navbar/index.spec.tsx index 3b53e8ffe0e..b88b0e4029d 100644 --- a/src/common/components/navbar/index.spec.tsx +++ b/src/common/components/navbar/index.spec.tsx @@ -1,134 +1,123 @@ -import React from "react"; -import {StaticRouter} from "react-router-dom"; -import TestRenderer from "react-test-renderer"; -import {createBrowserHistory, createLocation} from "history"; - -import NavBar from "./index"; - -import {Theme} from "../../store/global/types"; - -import {globalInstance, TrendingTagsInstance, UiInstance, notificationsInstance1, activeUserInstance, dynamicPropsIntance1} from "../../helper/test-helper"; +import React from 'react'; +import {StaticRouter} from 'react-router-dom'; +import TestRenderer from 'react-test-renderer'; +import {createBrowserHistory, createLocation} from 'history'; + +import NavBar from './index'; + +import {Theme} from '../../store/global/types'; + +import { + globalInstance, + TrendingTagsInstance, + UiInstance, + notificationsInstance1, + activeUserInstance, + dynamicPropsIntance1, +} from '../../helper/test-helper'; import './matchMedia'; const defProps = { - history: createBrowserHistory(), - location: createLocation({}), - global: globalInstance, - dynamicProps: dynamicPropsIntance1, - trendingTags: TrendingTagsInstance, - users: [], - activeUser: null, - ui: UiInstance, - notifications: notificationsInstance1, - step: 2, - fetchTrendingTags: () => { - }, - toggleTheme: () => { - }, - addUser: () => { - }, - setActiveUser: () => { - }, - updateActiveUser: () => { - }, - addAccount: () => { - }, - deleteUser: () => { - }, - fetchNotifications: () => { - }, - fetchUnreadNotificationCount: () => { - }, - setNotificationsFilter: () => { - }, - markNotifications: () => { - }, - toggleUIProp: () => { - }, - muteNotifications: () => { - }, - unMuteNotifications: () => { - }, - setLang: () => { - }, - setStepOne: () => { - }, + history: createBrowserHistory(), + location: createLocation({}), + global: globalInstance, + dynamicProps: dynamicPropsIntance1, + trendingTags: TrendingTagsInstance, + users: [], + activeUser: null, + ui: UiInstance, + notifications: notificationsInstance1, + step: 2, + fetchTrendingTags: () => {}, + toggleTheme: () => {}, + addUser: () => {}, + setActiveUser: () => {}, + updateActiveUser: () => {}, + addAccount: () => {}, + deleteUser: () => {}, + fetchNotifications: () => {}, + fetchUnreadNotificationCount: () => {}, + setNotificationsFilter: () => {}, + markNotifications: () => {}, + toggleUIProp: () => {}, + muteNotifications: () => {}, + unMuteNotifications: () => {}, + setLang: () => {}, + setStepOne: () => {}, }; //const itif = (condition) => condition ? it : it.skip; -it("(1) Default render", () => { - const component = ; +it('(1) Default render', () => { + const component = ; - const renderer = TestRenderer.create( - - {component} - - ); + const renderer = TestRenderer.create( + + {component} + , + ); - expect(renderer.toJSON()).toMatchSnapshot(); + expect(renderer.toJSON()).toMatchSnapshot(); }); - -it("(2) Night Theme", () => { - const props = { - ...defProps, - ...{ - global: { - ...globalInstance, - theme: Theme.night - } - } - } - const component = ; - - const renderer = TestRenderer.create( - - {component} - - ); - - expect(renderer.toJSON()).toMatchSnapshot(); +it('(2) Night Theme', () => { + const props = { + ...defProps, + ...{ + global: { + ...globalInstance, + theme: Theme.night, + }, + }, + }; + const component = ; + + const renderer = TestRenderer.create( + + {component} + , + ); + + expect(renderer.toJSON()).toMatchSnapshot(); }); +it('(3) With active user', () => { + const props = { + ...defProps, + ...{ + activeUser: {...activeUserInstance}, + }, + }; + const component = ; -it("(3) With active user", () => { - const props = { - ...defProps, - ...{ - activeUser: {...activeUserInstance} - } - } - const component = ; - - const renderer = TestRenderer.create( - - {component} - - ); + const renderer = TestRenderer.create( + + {component} + , + ); - expect(renderer.toJSON()).toMatchSnapshot(); + expect(renderer.toJSON()).toMatchSnapshot(); }); -it("(4) With active user && usePrivate = false", () => { - const props = { - ...defProps, - ...{ - activeUser: {...activeUserInstance} - }, - global: { - ...globalInstance, - usePrivate: false - } - } - - const component = ; - - const renderer = TestRenderer.create( - - {component} - - ); - - expect(renderer.toJSON()).toMatchSnapshot(); +it('(4) With active user && usePrivate = false', () => { + const props = { + ...defProps, + ...{ + activeUser: {...activeUserInstance}, + }, + global: { + ...globalInstance, + usePrivate: false, + }, + }; + + const component = ; + + const renderer = TestRenderer.create( + + {component} + , + ); + + expect(renderer.toJSON()).toMatchSnapshot(); }); diff --git a/src/common/components/navbar/index.tsx b/src/common/components/navbar/index.tsx index 5ae1b48ea5e..fe46569a33d 100644 --- a/src/common/components/navbar/index.tsx +++ b/src/common/components/navbar/index.tsx @@ -1,506 +1,739 @@ -import React, {Component} from "react"; - -import {History, Location} from "history"; - -import {Button} from "react-bootstrap"; - -import {Link} from "react-router-dom"; - -import isEqual from "react-fast-compare"; - -import queryString from "query-string"; - -import {Global, Theme} from "../../store/global/types"; -import {TrendingTags} from "../../store/trending-tags/types"; -import {Account, FullAccount} from "../../store/accounts/types"; -import {User} from "../../store/users/types"; -import {ActiveUser} from "../../store/active-user/types"; -import {UI, ToggleType} from "../../store/ui/types"; -import {NotificationFilter, Notifications} from "../../store/notifications/types"; -import {DynamicProps} from "../../store/dynamic-props/types"; -import NotificationHandler from "../notification-handler"; -import SwitchLang from "../switch-lang"; - -import ToolTip from "../tooltip"; -import Search from "../search"; -import Login from "../login"; -import UserNav from "../user-nav"; -import UserNotifications from "../notifications"; -import Gallery from "../gallery"; -import Drafts from "../drafts"; -import Bookmarks from "../bookmarks"; -import Schedules from "../schedules"; -import Fragments from "../fragments"; - -import {_t} from "../../i18n"; - -import _c from "../../util/fix-class-names"; - -import {brightnessSvg, pencilOutlineSvg, menuSvg, closeSvg, magnifySvg, accountOutlineSvg, powerDownSvg, chevronDownSvgForSlider, moonSvg, globeSvg, bellSvg, walletTravelSvg, walletSvg, notificationSvg, pencilOutlinedSvg, userOutlineSvg, downArrowSvg, chevronUpSvg, upArrowSvg, keySvg, sunSvg, gifCardSvg} from "../../img/svg"; -import userAvatar from "../user-avatar"; -import { downVotingPower, votingPower } from "../../api/hive"; +import React, {Component} from 'react'; + +import {History, Location} from 'history'; + +import {Button} from 'react-bootstrap'; + +import {Link} from 'react-router-dom'; + +import isEqual from 'react-fast-compare'; + +import queryString from 'query-string'; + +import {Global, Theme} from '../../store/global/types'; +import {TrendingTags} from '../../store/trending-tags/types'; +import {Account, FullAccount} from '../../store/accounts/types'; +import {User} from '../../store/users/types'; +import {ActiveUser} from '../../store/active-user/types'; +import {UI, ToggleType} from '../../store/ui/types'; +import { + NotificationFilter, + Notifications, +} from '../../store/notifications/types'; +import {DynamicProps} from '../../store/dynamic-props/types'; +import NotificationHandler from '../notification-handler'; +import SwitchLang from '../switch-lang'; + +import ToolTip from '../tooltip'; +import Search from '../search'; +import Login from '../login'; +import UserNav from '../user-nav'; +import UserNotifications from '../notifications'; +import Gallery from '../gallery'; +import Drafts from '../drafts'; +import Bookmarks from '../bookmarks'; +import Schedules from '../schedules'; +import Fragments from '../fragments'; + +import {_t} from '../../i18n'; + +import _c from '../../util/fix-class-names'; + +import { + brightnessSvg, + pencilOutlineSvg, + menuSvg, + closeSvg, + magnifySvg, + accountOutlineSvg, + powerDownSvg, + chevronDownSvgForSlider, + moonSvg, + globeSvg, + bellSvg, + walletTravelSvg, + walletSvg, + notificationSvg, + pencilOutlinedSvg, + userOutlineSvg, + downArrowSvg, + chevronUpSvg, + upArrowSvg, + keySvg, + sunSvg, + gifCardSvg, +} from '../../img/svg'; +import userAvatar from '../user-avatar'; +import {downVotingPower, votingPower} from '../../api/hive'; //const logo = require('../../img/logo-circle.svg'); -const communityPattern = "^hive-[0-9]{6}$"; +const communityPattern = '^hive-[0-9]{6}$'; interface Props { - history: History; - location: Location; - global: Global; - dynamicProps: DynamicProps; - trendingTags: TrendingTags; - users: User[]; - activeUser: ActiveUser | null; - ui: UI; - notifications: Notifications; - step?: number; - match?: any; - fetchTrendingTags: () => void; - toggleTheme: (theme_key?: string) => void; - addUser: (user: User) => void; - setActiveUser: (username: string | null) => void; - updateActiveUser: (data?: Account) => void; - addAccount: (data: Account) => void; - deleteUser: (username: string) => void; - fetchNotifications: (since: string | null) => void; - fetchUnreadNotificationCount: () => void; - setNotificationsFilter: (filter: NotificationFilter | null) => void; - markNotifications: (id: string | null) => void; - toggleUIProp: (what: ToggleType) => void; - muteNotifications: () => void; - unMuteNotifications: () => void; - setLang: (lang: string) => void; - setStepOne?:() => void; - setStepTwo?:() => void; + history: History; + location: Location; + global: Global; + dynamicProps: DynamicProps; + trendingTags: TrendingTags; + users: User[]; + activeUser: ActiveUser | null; + ui: UI; + notifications: Notifications; + step?: number; + match?: any; + fetchTrendingTags: () => void; + toggleTheme: (theme_key?: string) => void; + addUser: (user: User) => void; + setActiveUser: (username: string | null) => void; + updateActiveUser: (data?: Account) => void; + addAccount: (data: Account) => void; + deleteUser: (username: string) => void; + fetchNotifications: (since: string | null) => void; + fetchUnreadNotificationCount: () => void; + setNotificationsFilter: (filter: NotificationFilter | null) => void; + markNotifications: (id: string | null) => void; + toggleUIProp: (what: ToggleType) => void; + muteNotifications: () => void; + unMuteNotifications: () => void; + setLang: (lang: string) => void; + setStepOne?: () => void; + setStepTwo?: () => void; } interface State { - smVisible: boolean, - floating: boolean, - showMobileSearch: boolean, - showProfileMenu: boolean, - gallery: boolean, - drafts: boolean, - bookmarks: boolean, - schedules: boolean, - fragments: boolean, - notifications: boolean + smVisible: boolean; + floating: boolean; + showMobileSearch: boolean; + showProfileMenu: boolean; + gallery: boolean; + drafts: boolean; + bookmarks: boolean; + schedules: boolean; + fragments: boolean; + notifications: boolean; } export class NavBar extends Component { - state: State = { - smVisible: false, - floating: false, - showProfileMenu: false, - showMobileSearch: false, - gallery: false, - drafts: false, - bookmarks: false, - schedules: false, - fragments: false, - notifications: false, + state: State = { + smVisible: false, + floating: false, + showProfileMenu: false, + showMobileSearch: false, + gallery: false, + drafts: false, + bookmarks: false, + schedules: false, + fragments: false, + notifications: false, + }; + + timer: any = null; + nav = React.createRef(); + + componentDidMount() { + // referral check / redirect + const {location, history} = this.props; + const qs = queryString.parse(location.search); + if (!location.pathname.startsWith('/signup') && qs.referral) { + history.push(`/signup?referral=${qs.referral}`); } - timer: any = null; - nav = React.createRef(); - - componentDidMount() { - // referral check / redirect - const {location, history} = this.props; - const qs = queryString.parse(location.search); - if (!location.pathname.startsWith("/signup") && qs.referral) { - history.push(`/signup?referral=${qs.referral}`) - } - - window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', this.handleAutoDetectTheme); // listen to dark theme - // this.handleSetTheme(); // detect default set theme on load page + window + .matchMedia('(prefers-color-scheme: dark)') + .addEventListener('change', this.handleAutoDetectTheme); // listen to dark theme + // this.handleSetTheme(); // detect default set theme on load page + } + + componentWillUnmount() { + document + .getElementsByTagName('body')[0] + .classList.remove('overflow-hidden'); + + window + .matchMedia('(prefers-color-scheme: dark)') + .removeEventListener('change', this.handleAutoDetectTheme); + } + + shouldComponentUpdate( + nextProps: Readonly, + nextState: Readonly, + ): boolean { + return ( + !isEqual(this.props.global, nextProps.global) || + !isEqual(this.props.trendingTags, nextProps.trendingTags) || + !isEqual(this.props.users, nextProps.users) || + !isEqual(this.props.activeUser, nextProps.activeUser) || + !isEqual(this.props.ui, nextProps.ui) || + !isEqual(this.props.notifications, nextProps.notifications) || + !isEqual(this.props.location, nextProps.location) || + !isEqual(this.props.step, nextProps.step) || + !isEqual(this.state, nextState) + ); + } + + componentDidUpdate(prevProps: Props, prevStates: State) { + if (prevStates.smVisible !== this.state.smVisible) { + if (this.state.smVisible) { + document + .getElementsByTagName('body')[0] + .classList.add('overflow-hidden'); + } + if (!this.state.smVisible) { + document + .getElementsByTagName('body')[0] + .classList.remove('overflow-hidden'); + } } - componentWillUnmount() { - document.getElementsByTagName('body')[0].classList.remove("overflow-hidden"); - - window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', this.handleAutoDetectTheme) + if ( + prevProps.location.pathname !== this.props.location.pathname || + prevProps.activeUser !== this.props.activeUser + ) { + if (this.props.location.pathname === '/' && !this.props.activeUser) { + this.props.setStepOne!(); + } else { + this.props.setStepTwo && this.props.setStepTwo(); + } } - - shouldComponentUpdate(nextProps: Readonly, nextState: Readonly): boolean { - return !isEqual(this.props.global, nextProps.global) - || !isEqual(this.props.trendingTags, nextProps.trendingTags) - || !isEqual(this.props.users, nextProps.users) - || !isEqual(this.props.activeUser, nextProps.activeUser) - || !isEqual(this.props.ui, nextProps.ui) - || !isEqual(this.props.notifications, nextProps.notifications) - || !isEqual(this.props.location, nextProps.location) - || !isEqual(this.props.step, nextProps.step) - || !isEqual(this.state, nextState) - } - - componentDidUpdate(prevProps: Props, prevStates: State) { - if(prevStates.smVisible !== this.state.smVisible){ - if(this.state.smVisible) { - document.getElementsByTagName('body')[0].classList.add("overflow-hidden") - } - if(!this.state.smVisible) { - document.getElementsByTagName('body')[0].classList.remove("overflow-hidden") - } - } - - - if(prevProps.location.pathname !== this.props.location.pathname || prevProps.activeUser !== this.props.activeUser){ - if(this.props.location.pathname === "/" && !this.props.activeUser){ - this.props.setStepOne!(); - } - else { - this.props.setStepTwo && this.props.setStepTwo(); - } - } + } + + changeTheme = () => { + this.props.toggleTheme(); + }; + + toggleSmVisible = () => { + const {smVisible} = this.state; + this.setState({smVisible: !smVisible}); + if (!smVisible) { + let rootElement = document.getElementById('root'); + rootElement && rootElement.scrollIntoView(); } - - changeTheme = () => { - this.props.toggleTheme(); - }; - - toggleSmVisible = () => { - const {smVisible} = this.state; - this.setState({smVisible: !smVisible}); - if(!smVisible){ - let rootElement = document.getElementById("root"); - rootElement && rootElement.scrollIntoView() - } - } - - handleIconClick = () => { - if( "/" !== this.props?.location?.pathname - || this.props?.location?.pathname?.startsWith("/hot") - || this.props?.location?.pathname?.startsWith("/created") - || this.props?.location?.pathname?.startsWith("/trending") - ) { - this.props.history.push("/"); - } - if(this.props.setStepOne) { - return this.props.setStepOne() - } + }; + + handleIconClick = () => { + if ( + '/' !== this.props?.location?.pathname || + this.props?.location?.pathname?.startsWith('/hot') || + this.props?.location?.pathname?.startsWith('/created') || + this.props?.location?.pathname?.startsWith('/trending') + ) { + this.props.history.push('/'); } - - // handleSetTheme = () => { - // const _default_theme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? Theme.night : Theme.day - // this.props.toggleTheme(_default_theme); - // } - - handleAutoDetectTheme = (e: any = null) => { - const _default_theme = e && e.matches ? Theme.night : Theme.day - this.props.toggleTheme(_default_theme); + if (this.props.setStepOne) { + return this.props.setStepOne(); } - - render() { - const {global, activeUser, ui, step, toggleUIProp, setActiveUser, match } = this.props; - const logo = global.isElectron ? "./img/logo-circle.svg" : require('../../img/logo-circle.svg'); - const themeText = global.theme == Theme.day ? _t("navbar.night-theme") : _t("navbar.day-theme"); - const re = new RegExp(communityPattern); - const tagValue = global.tag ? `/${global.tag}` : '' - const logoHref = activeUser ? - (match && (re.test(match.params.name)) || ((global.tag === `@${activeUser.username}`) && (global.filter !== 'feed'))) ? - '/hot' : - global.filter === 'feed' ? `${tagValue}/${global.filter}` : `/${global.filter}${tagValue}` - : '/'; - const {smVisible, floating, showMobileSearch, showProfileMenu, drafts, bookmarks, fragments, gallery, schedules } = this.state; - - const transparentVerify = this.props?.location?.pathname?.startsWith("/hot") - || this.props?.location?.pathname?.startsWith("/created") - || this.props?.location?.pathname?.startsWith("/trending") - - const textMenu =
    - - {_t("navbar.discover")} - - - {_t("navbar.communities")} - + }; + + // handleSetTheme = () => { + // const _default_theme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? Theme.night : Theme.day + // this.props.toggleTheme(_default_theme); + // } + + handleAutoDetectTheme = (e: any = null) => { + const _default_theme = e && e.matches ? Theme.night : Theme.day; + this.props.toggleTheme(_default_theme); + }; + + render() { + const {global, activeUser, ui, step, toggleUIProp, setActiveUser, match} = + this.props; + const logo = global.isElectron + ? './img/logo-circle.svg' + : require('../../img/logo-circle.svg'); + const themeText = + global.theme == Theme.day + ? _t('navbar.night-theme') + : _t('navbar.day-theme'); + const re = new RegExp(communityPattern); + const tagValue = global.tag ? `/${global.tag}` : ''; + const logoHref = activeUser + ? (match && re.test(match.params.name)) || + (global.tag === `@${activeUser.username}` && global.filter !== 'feed') + ? '/hot' + : global.filter === 'feed' + ? `${tagValue}/${global.filter}` + : `/${global.filter}${tagValue}` + : '/'; + const { + smVisible, + floating, + showMobileSearch, + showProfileMenu, + drafts, + bookmarks, + fragments, + gallery, + schedules, + } = this.state; + + const transparentVerify = + this.props?.location?.pathname?.startsWith('/hot') || + this.props?.location?.pathname?.startsWith('/created') || + this.props?.location?.pathname?.startsWith('/trending'); + + const textMenu = ( +
    + + {_t('navbar.discover')} + + + {_t('navbar.communities')} + +
    + ); + + return ( +
    + {floating && smVisible &&
    } +
    + {smVisible ? closeSvg : menuSvg}
    - return ( -
    - {floating && smVisible && (
    )} -
    {smVisible ? closeSvg : menuSvg}
    - -
    -
    - { - activeUser !== null ? ( - - Logo - - ) : - ( - Logo - ) - } -
    +
    +
    + {activeUser !== null ? ( + + Logo + + ) : ( + Logo + )} +
    + + {textMenu} +
    - {textMenu} + {!smVisible && ( +
    +
    +
    + {activeUser !== null ? ( + + Logo + + ) : ( + Logo + )} +
    + {textMenu} +
    + {(step !== 1 || transparentVerify) && ( +
    {Search({...this.props})}
    + )} +
    + {SwitchLang({...this.props})} + {(step !== 1 || transparentVerify) && ( + +
    + {brightnessSvg}
    - - {!smVisible && ( -
    -
    -
    - { - activeUser !== null ? ( - - Logo - - ) : - ( - Logo - ) - } -
    - {textMenu} -
    - { - (step !== 1 || transparentVerify) && -
    - {Search({...this.props})} -
    - } -
    - {SwitchLang({...this.props})} - { - (step !== 1 || transparentVerify) && - -
    - {brightnessSvg} -
    -
    - } - { - (step !== 1 || transparentVerify) && ( - - - {pencilOutlineSvg} - - - )} -
    -
    - {!activeUser && ( -
    -
    - - - {_t("g.signup")} -
    -
    - - - {pencilOutlineSvg} - - -
    -
    - )} -
    - {activeUser && ( -
    - -
    - - - {pencilOutlineSvg} - - -
    -
    - )} + + )} + {(step !== 1 || transparentVerify) && ( + + + {pencilOutlineSvg} + + + )} +
    +
    + {!activeUser && ( +
    +
    + + + + {_t('g.signup')} + +
    +
    + + + {pencilOutlineSvg} + + +
    +
    + )} +
    + {activeUser && ( +
    + +
    + + + {pencilOutlineSvg} + + +
    +
    + )} +
    +
    + )} +
    +
    +
    + {activeUser && ( + +
    + {userAvatar({ + ...this.props, + username: activeUser.username, + size: 'large', + })} +
    + @{activeUser.username} +
    + {_t('user-nav.vote-power')} {upArrowSvg}{' '} + {(activeUser.data as FullAccount).active && + votingPower(activeUser.data as FullAccount).toFixed( + 0, + )} + % {downArrowSvg}{' '} + {(activeUser.data as FullAccount).active && + downVotingPower( + activeUser.data as FullAccount, + ).toFixed(0)} + % +
    +
    + + )} +
    + !showMobileSearch && this.setState({showMobileSearch: true}) + } + > +
    + {showMobileSearch ? ( + <> + {Search({...this.props, containerClassName: 'w-100'})} +
    this.setState({showMobileSearch: false})} + className='navbar-icon text-secondary ml-2' + > + {closeSvg} +
    + + ) : ( + <> +
    {magnifySvg}
    +
    {_t('g.search')}
    + + )}
    +
    + + {!activeUser && ( + <> +
    toggleUIProp('login')} + > +
    {userOutlineSvg}
    +
    {_t('g.login')}
    +
    + + !showMobileSearch && this.setState({smVisible: false}) + } + > +
    +
    {keySvg}
    +
    {_t('g.signup')}
    +
    + + + )} + + this.setState({smVisible: false})} + > +
    +
    {pencilOutlinedSvg}
    +
    {_t('g.submit')}
    +
    + + +
    + {activeUser && ( +
    + this.setState({showProfileMenu: !showProfileMenu}) + } + > +
    {userOutlineSvg}
    +
    + {_t('user-nav.profile-menu')} +
    +
    + {showProfileMenu ? upArrowSvg : downArrowSvg} +
    +
    )} -
    - -
    -
    - {activeUser && - -
    - {userAvatar({...this.props, username: activeUser.username, size:"large"})} -
    - @{activeUser.username} -
    {_t("user-nav.vote-power")} {upArrowSvg} {(activeUser.data as FullAccount).active && votingPower(activeUser.data as FullAccount).toFixed(0)}% {downArrowSvg} {(activeUser.data as FullAccount).active && downVotingPower(activeUser.data as FullAccount).toFixed(0)}%
    -
    -
    - - } -
    !showMobileSearch && this.setState({ showMobileSearch: true })}> -
    - - {showMobileSearch ? - <> - {Search({...this.props, containerClassName:'w-100'})} -
    this.setState({showMobileSearch:false})} - className="navbar-icon text-secondary ml-2" - > - {closeSvg} -
    - : - <> -
    {magnifySvg}
    -
    {_t("g.search")}
    - - } -
    -
    - - {!activeUser && - <> -
    toggleUIProp("login")}> -
    {userOutlineSvg}
    -
    {_t("g.login")}
    -
    - !showMobileSearch && this.setState({ smVisible: false })}> -
    -
    {keySvg}
    -
    {_t("g.signup")}
    -
    - - - } - - this.setState({ smVisible: false} )}> -
    -
    {pencilOutlinedSvg}
    -
    {_t("g.submit")}
    -
    - - -
    - {activeUser &&
    this.setState({showProfileMenu: !showProfileMenu})}> -
    {userOutlineSvg}
    -
    {_t("user-nav.profile-menu")}
    -
    {showProfileMenu ? upArrowSvg : downArrowSvg}
    -
    } - - {activeUser && showProfileMenu ? -
    -
    - -
    this.setState({drafts: !drafts})}> -
    {_t("user-nav.drafts")}
    -
    - -
    this.setState({gallery: !gallery})}> -
    {_t("user-nav.gallery")}
    -
    - -
    this.setState({bookmarks: !bookmarks})}> -
    {_t("user-nav.bookmarks")}
    -
    - -
    this.setState({schedules: !schedules})}> -
    {_t("user-nav.schedules")}
    -
    - -
    this.setState({fragments: !fragments})}> -
    {_t("user-nav.fragments")}
    -
    - -
    - this.setState({ smVisible: false} )}> -
    {_t("user-nav.settings")}
    - -
    - -
    toggleUIProp('login')}> -
    {_t("g.login-as")}
    -
    - -
    setActiveUser(null)}> -
    {_t("user-nav.logout")}
    -
    -
    -
    : null} - -
    - - {activeUser && - <> -
    toggleUIProp('notifications')}> -
    {notificationSvg}
    -
    {_t("user-nav.notifications")}
    -
    - this.setState({ smVisible: false} )}> -
    -
    {gifCardSvg}
    -
    {_t("user-nav.points")}
    -
    - - this.setState({ smVisible: false} )}> -
    -
    {walletSvg}
    -
    {_t("user-nav.wallet")}
    -
    - - } - -
    -
    {globeSvg}
    -
    {SwitchLang({...this.props, label: _t("community-settings.lang")})}
    -
    - -
    -
    {global.theme == Theme.day ? moonSvg : sunSvg}
    -
    {_t("user-nav.switch-to")} {global.theme == Theme.day ? _t("user-nav.dark") : _t("user-nav.light")}
    -
    + {activeUser && showProfileMenu ? ( +
    +
    +
    this.setState({drafts: !drafts})} + > +
    {_t('user-nav.drafts')}
    +
    + +
    this.setState({gallery: !gallery})} + > +
    + {_t('user-nav.gallery')} +
    +
    + +
    this.setState({bookmarks: !bookmarks})} + > +
    + {_t('user-nav.bookmarks')} +
    +
    + +
    this.setState({schedules: !schedules})} + > +
    + {_t('user-nav.schedules')}
    +
    + +
    this.setState({fragments: !fragments})} + > +
    + {_t('user-nav.fragments')} +
    +
    + +
    + this.setState({smVisible: false})} + > +
    + {_t('user-nav.settings')} +
    + +
    + +
    toggleUIProp('login')} + > +
    {_t('g.login-as')}
    +
    + +
    setActiveUser(null)} + > +
    {_t('user-nav.logout')}
    +
    +
    +
    + ) : null} +
    + + {activeUser && ( + <> +
    toggleUIProp('notifications')} + > +
    + {notificationSvg} +
    +
    + {_t('user-nav.notifications')}
    - {ui.login && } - {global.usePrivate && } - {gallery && this.setState({gallery:!gallery})} />} - {ui.notifications && activeUser && } - {drafts && activeUser && this.setState({drafts:!drafts})} activeUser={activeUser as ActiveUser} />} - {bookmarks && activeUser && this.setState({bookmarks:!bookmarks})} activeUser={activeUser as ActiveUser} />} - {schedules && activeUser && this.setState({schedules:!schedules})} activeUser={activeUser as ActiveUser} />} - {fragments && activeUser && this.setState({fragments:!fragments})} activeUser={activeUser as ActiveUser} />} +
    + this.setState({smVisible: false})} + > +
    +
    {gifCardSvg}
    +
    + {_t('user-nav.points')} +
    +
    + + this.setState({smVisible: false})} + > +
    +
    {walletSvg}
    +
    + {_t('user-nav.wallet')}{' '} +
    +
    +
    + + + )} + +
    +
    {globeSvg}
    +
    + {SwitchLang({ + ...this.props, + label: _t('community-settings.lang'), + })} +
    +
    + +
    +
    + {global.theme == Theme.day ? moonSvg : sunSvg}
    +
    + {_t('user-nav.switch-to')}{' '} + {global.theme == Theme.day + ? _t('user-nav.dark') + : _t('user-nav.light')} +
    +
    - ); - } +
    + {ui.login && } + {global.usePrivate && } + {gallery && ( + this.setState({gallery: !gallery})} + /> + )} + {ui.notifications && activeUser && ( + + )} + {drafts && activeUser && ( + this.setState({drafts: !drafts})} + activeUser={activeUser as ActiveUser} + /> + )} + {bookmarks && activeUser && ( + this.setState({bookmarks: !bookmarks})} + activeUser={activeUser as ActiveUser} + /> + )} + {schedules && activeUser && ( + this.setState({schedules: !schedules})} + activeUser={activeUser as ActiveUser} + /> + )} + {fragments && activeUser && ( + this.setState({fragments: !fragments})} + activeUser={activeUser as ActiveUser} + /> + )} +
    +
    + ); + } } export default (p: Props) => { - const props: Props = { - history: p.history, - location: p.location, - global: p.global, - dynamicProps: p.dynamicProps, - trendingTags: p.trendingTags, - users: p.users, - activeUser: p.activeUser, - ui: p.ui, - notifications: p.notifications, - step: p.step, - fetchTrendingTags: p.fetchTrendingTags, - toggleTheme: p.toggleTheme, - addUser: p.addUser, - setActiveUser: p.setActiveUser, - updateActiveUser: p.updateActiveUser, - addAccount: p.addAccount, - deleteUser: p.deleteUser, - fetchNotifications: p.fetchNotifications, - fetchUnreadNotificationCount: p.fetchUnreadNotificationCount, - setNotificationsFilter: p.setNotificationsFilter, - markNotifications: p.markNotifications, - toggleUIProp: p.toggleUIProp, - muteNotifications: p.muteNotifications, - unMuteNotifications: p.unMuteNotifications, - setLang: p.setLang, - setStepOne: p.setStepOne, - setStepTwo: p.setStepTwo, - match: p.match, - } - - return ; -} + const props: Props = { + history: p.history, + location: p.location, + global: p.global, + dynamicProps: p.dynamicProps, + trendingTags: p.trendingTags, + users: p.users, + activeUser: p.activeUser, + ui: p.ui, + notifications: p.notifications, + step: p.step, + fetchTrendingTags: p.fetchTrendingTags, + toggleTheme: p.toggleTheme, + addUser: p.addUser, + setActiveUser: p.setActiveUser, + updateActiveUser: p.updateActiveUser, + addAccount: p.addAccount, + deleteUser: p.deleteUser, + fetchNotifications: p.fetchNotifications, + fetchUnreadNotificationCount: p.fetchUnreadNotificationCount, + setNotificationsFilter: p.setNotificationsFilter, + markNotifications: p.markNotifications, + toggleUIProp: p.toggleUIProp, + muteNotifications: p.muteNotifications, + unMuteNotifications: p.unMuteNotifications, + setLang: p.setLang, + setStepOne: p.setStepOne, + setStepTwo: p.setStepTwo, + match: p.match, + }; + + return ; +}; diff --git a/src/common/components/navbar/matchMedia.tsx b/src/common/components/navbar/matchMedia.tsx index f23e281642a..3a6eff91450 100644 --- a/src/common/components/navbar/matchMedia.tsx +++ b/src/common/components/navbar/matchMedia.tsx @@ -1,13 +1,13 @@ Object.defineProperty(window, 'matchMedia', { writable: true, - value: jest.fn().mockImplementation((query) => ({ - matches: false, - media: query, - onchange: null, - addListener: jest.fn(), // Deprecated - removeListener: jest.fn(), // Deprecated - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - dispatchEvent: jest.fn(), + value: jest.fn().mockImplementation(query => ({ + matches: false, + media: query, + onchange: null, + addListener: jest.fn(), // Deprecated + removeListener: jest.fn(), // Deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), })), }); diff --git a/src/common/components/notification-handler/index.tsx b/src/common/components/notification-handler/index.tsx index 2b15f5d2f94..18cb03fa3f4 100644 --- a/src/common/components/notification-handler/index.tsx +++ b/src/common/components/notification-handler/index.tsx @@ -1,177 +1,194 @@ -import React, {Component} from "react"; +import React, {Component} from 'react'; -import {AppWindow} from "../../../client/window"; +import {AppWindow} from '../../../client/window'; -import {Global} from "../../store/global/types"; -import {ActiveUser} from "../../store/active-user/types"; -import {ToggleType, UI} from "../../store/ui/types"; -import {Notifications, WsNotification} from "../../store/notifications/types"; +import {Global} from '../../store/global/types'; +import {ActiveUser} from '../../store/active-user/types'; +import {ToggleType, UI} from '../../store/ui/types'; +import {Notifications, WsNotification} from '../../store/notifications/types'; -import defaults from "../../constants/defaults.json" +import defaults from '../../constants/defaults.json'; -import {_t} from "../../i18n"; +import {_t} from '../../i18n'; declare var window: AppWindow; export const notificationBody = (data: WsNotification): string => { - const {source} = data; - - switch (data.type) { - case 'vote': - return _t('notification.voted', {source}); - case 'mention': - return data.extra.is_post === 1 - ? _t('notification.mention-post', {source}) - : _t('notification.mention-comment', {source}); - case 'follow': - return _t('notification.followed', {source}); - case 'reply': - return _t('notification.replied', {source}); - case 'reblog': - return _t('notification.reblogged', {source}); - case 'transfer': - return _t('notification.transfer', {source, amount: data.extra.amount}); - default: - return ''; - } + const {source} = data; + + switch (data.type) { + case 'vote': + return _t('notification.voted', {source}); + case 'mention': + return data.extra.is_post === 1 + ? _t('notification.mention-post', {source}) + : _t('notification.mention-comment', {source}); + case 'follow': + return _t('notification.followed', {source}); + case 'reply': + return _t('notification.replied', {source}); + case 'reblog': + return _t('notification.reblogged', {source}); + case 'transfer': + return _t('notification.transfer', {source, amount: data.extra.amount}); + default: + return ''; + } }; - interface Props { - global: Global; - activeUser: ActiveUser | null; - ui: UI; - notifications: Notifications; - fetchNotifications: (since: string | null) => void; - fetchUnreadNotificationCount: () => void; - toggleUIProp: (what: ToggleType) => void; + global: Global; + activeUser: ActiveUser | null; + ui: UI; + notifications: Notifications; + fetchNotifications: (since: string | null) => void; + fetchUnreadNotificationCount: () => void; + toggleUIProp: (what: ToggleType) => void; } export default class NotificationHandler extends Component { - componentDidMount() { - this.nwsConnect(); + componentDidMount() { + this.nwsConnect(); - const {activeUser, notifications, fetchUnreadNotificationCount} = this.props; - if (activeUser && notifications.unreadFetchFlag) { - fetchUnreadNotificationCount(); - } + const {activeUser, notifications, fetchUnreadNotificationCount} = + this.props; + if (activeUser && notifications.unreadFetchFlag) { + fetchUnreadNotificationCount(); } - - componentDidUpdate(prevProps: Readonly, prevState: Readonly<{}>, snapshot?: any) { - const {activeUser, fetchUnreadNotificationCount} = this.props; - if(!prevProps.activeUser && activeUser && activeUser.username){ - this.nwsDisconnect(); - this.nwsConnect(); - fetchUnreadNotificationCount(); - } - - if (activeUser?.username !== prevProps.activeUser?.username) { - this.nwsDisconnect(); - this.nwsConnect(); - - if (activeUser) { - fetchUnreadNotificationCount(); - } - } + } + + componentDidUpdate( + prevProps: Readonly, + prevState: Readonly<{}>, + snapshot?: any, + ) { + const {activeUser, fetchUnreadNotificationCount} = this.props; + if (!prevProps.activeUser && activeUser && activeUser.username) { + this.nwsDisconnect(); + this.nwsConnect(); + fetchUnreadNotificationCount(); } - nwsConnect = () => { - const {activeUser} = this.props; - if (!activeUser) { - this.nwsDisconnect(); - return; - } - - if (window.nws !== undefined) { - return; - } + if (activeUser?.username !== prevProps.activeUser?.username) { + this.nwsDisconnect(); + this.nwsConnect(); - if ('Notification' in window) { - Notification.requestPermission(); - } - - window.nws = new WebSocket(`${defaults.nwsServer}/ws?user=${activeUser.username}`); + if (activeUser) { + fetchUnreadNotificationCount(); + } + } + } - window.nws.onopen = () => { - console.log("nws connected"); - } + nwsConnect = () => { + const {activeUser} = this.props; + if (!activeUser) { + this.nwsDisconnect(); + return; + } - window.nws.onmessage = (evt: MessageEvent) => { - const {global} = this.props; - const logo = global.isElectron ? "./img/logo-circle.svg" : require('../../img/logo-circle.svg'); + if (window.nws !== undefined) { + return; + } + if ('Notification' in window) { + Notification.requestPermission(); + } - const data = JSON.parse(evt.data); - const msg = notificationBody(data); + window.nws = new WebSocket( + `${defaults.nwsServer}/ws?user=${activeUser.username}`, + ); - if (msg) { - const {fetchUnreadNotificationCount, fetchNotifications} = this.props; + window.nws.onopen = () => { + console.log('nws connected'); + }; - fetchUnreadNotificationCount(); - fetchNotifications(null); + window.nws.onmessage = (evt: MessageEvent) => { + const {global} = this.props; + const logo = global.isElectron + ? './img/logo-circle.svg' + : require('../../img/logo-circle.svg'); - if (!global.notifications) { - return; - } + const data = JSON.parse(evt.data); + const msg = notificationBody(data); - this.playSound(); + if (msg) { + const {fetchUnreadNotificationCount, fetchNotifications} = this.props; - new Notification(_t('notification.popup-title'), { - body: msg, - icon: logo - }).onclick = () => { - const {ui, toggleUIProp} = this.props; - if (!ui.notifications) { - toggleUIProp('notifications'); - } - }; - } + fetchUnreadNotificationCount(); + fetchNotifications(null); + if (!global.notifications) { + return; } - window.nws.onclose = (evt: CloseEvent) => { - console.log('nws disconnected'); + this.playSound(); - window.nws = undefined; - - if (!evt.wasClean) { - // Disconnected due connection error - console.log('nws trying to reconnect'); - - setTimeout(() => { - this.nwsConnect(); - }, 2000); - } + new Notification(_t('notification.popup-title'), { + body: msg, + icon: logo, + }).onclick = () => { + const {ui, toggleUIProp} = this.props; + if (!ui.notifications) { + toggleUIProp('notifications'); + } }; + } + }; + + window.nws.onclose = (evt: CloseEvent) => { + console.log('nws disconnected'); + + window.nws = undefined; + + if (!evt.wasClean) { + // Disconnected due connection error + console.log('nws trying to reconnect'); + + setTimeout(() => { + this.nwsConnect(); + }, 2000); + } + }; + }; + + playSound = () => { + if ('Notification' in window) { + const req = Notification.requestPermission(); + if (!req) { + // safari may return undefined instead of promise + return; + } + req.then(r => { + if (r !== 'granted') return; + const el: HTMLAudioElement = document.getElementById( + 'notification-audio', + )! as HTMLAudioElement; + el.muted = false; + el.play().then(); + }); } + }; - playSound = () => { - if ('Notification' in window) { - const req = Notification.requestPermission(); - if (!req) { - // safari may return undefined instead of promise - return; - } - req.then((r) => { - if (r !== 'granted') return; - const el: HTMLAudioElement = document.getElementById('notification-audio')! as HTMLAudioElement; - el.muted = false; - el.play().then(); - }) - } - } - - nwsDisconnect = () => { - if (window.nws !== undefined) { - window.nws.close(); - window.nws = undefined; - } - } - - render() { - - const notificationSound = this.props.global.isElectron ? "./img/notification.mp3" : require("../../img/notification.mp3"); - - return