From 5574122f2663efbd37f193131184a4bc8caa2610 Mon Sep 17 00:00:00 2001 From: Mashbean Date: Thu, 23 Jul 2026 14:30:15 +0800 Subject: [PATCH 1/2] feat(fediverse): add social interaction center --- lang/default.json | 126 +++- lang/en.json | 126 +++- lang/zh-Hans.json | 126 +++- lang/zh-Hant.json | 126 +++- src/common/enums/route.ts | 2 + src/common/utils/types/index.ts | 119 ++++ src/components/Context/Viewer/index.tsx | 3 + .../Layout/GlobalNav/MeMenu/index.tsx | 8 + .../GlobalNav/UnreadIcon/Notification.tsx | 28 +- .../DrawerContent/MeMenu/index.tsx | 9 + src/pages/me/fediverse.tsx | 10 + .../FediverseInteractions/index.tsx | 300 +++++++++ .../FediverseInteractions/styles.module.css | 120 ++++ src/views/ArticleDetail/index.tsx | 3 + src/views/Me/Fediverse/gql.ts | 89 +++ src/views/Me/Fediverse/index.tsx | 627 ++++++++++++++++++ src/views/Me/Fediverse/styles.module.css | 247 +++++++ src/views/Me/Notifications/index.tsx | 146 +++- src/views/Me/Notifications/styles.module.css | 40 ++ .../Me/Settings/Misc/FederationSetting.tsx | 61 +- 20 files changed, 2255 insertions(+), 61 deletions(-) create mode 100644 src/pages/me/fediverse.tsx create mode 100644 src/views/ArticleDetail/FediverseInteractions/index.tsx create mode 100644 src/views/ArticleDetail/FediverseInteractions/styles.module.css create mode 100644 src/views/Me/Fediverse/gql.ts create mode 100644 src/views/Me/Fediverse/index.tsx create mode 100644 src/views/Me/Fediverse/styles.module.css create mode 100644 src/views/Me/Notifications/styles.module.css diff --git a/lang/default.json b/lang/default.json index a85bf22aa6..a834ecbf42 100644 --- a/lang/default.json +++ b/lang/default.json @@ -84,6 +84,9 @@ "+vVZ/G": { "defaultMessage": "Connect" }, + "+wqFkx": { + "defaultMessage": "聯邦宇宙互動" + }, "/0OJlF": { "defaultMessage": "No discussion yet", "description": "src/views/CampaignDetail/Discussion" @@ -188,6 +191,9 @@ "defaultMessage": "Create collection", "description": "src/views/User/Collections/UserCollections.tsx" }, + "0XnomJ": { + "defaultMessage": "找不到這個聯邦帳號" + }, "0bTlm4": { "defaultMessage": "Are you sure you want to delete this moment?" }, @@ -353,6 +359,9 @@ "defaultMessage": "Edit", "description": "src/views/ArticleDetail/MetaInfo/index.tsx" }, + "2cnZc/": { + "defaultMessage": "開啟後,新發佈的公開作品會自動送到 Fediverse。其他站台可能保留副本;關閉後將停止同步,並嘗試刪除先前內容。" + }, "2ef/Xl": { "defaultMessage": "Block", "description": "src/views/Me/Settings/Blocked/ToggleBlockButton.tsx" @@ -456,6 +465,9 @@ "defaultMessage": "色情廣告", "description": "src/components/Comment/DropdownActions/CommunityWatchRemoveComment.tsx" }, + "4GmeZE": { + "defaultMessage": "來自其他 Fediverse 站台的互動" + }, "4KuZ0o": { "defaultMessage": "Asset not found", "description": "ASSET_NOT_FOUND" @@ -516,6 +528,9 @@ "defaultMessage": "liked your deleted moment", "description": "src/components/Notice/MomentNotice/MomentLiked.tsx" }, + "5UgS3k": { + "defaultMessage": "封鎖" + }, "5UglrB": { "defaultMessage": "Successfully delivered" }, @@ -535,6 +550,9 @@ "5jlQTx": { "defaultMessage": "Manage Invitation" }, + "5kX6Qk": { + "defaultMessage": "檢舉" + }, "5mu8HJ": { "defaultMessage": "Moment deleted" }, @@ -606,6 +624,9 @@ "6Sj2lN": { "defaultMessage": "Claim" }, + "6UaJbf": { + "defaultMessage": "送出" + }, "6Vznnt": { "defaultMessage": "left a comment in your circle", "description": "src/components/Notice/CircleNotice/CircleNewDiscussionComments.tsx" @@ -978,6 +999,9 @@ "defaultMessage": "Provide support funds to", "description": "src/components/Forms/PaymentForm/PaymentInfo/index.tsx" }, + "ByvxDL": { + "defaultMessage": "追蹤中的作者" + }, "C/4nS6": { "defaultMessage": "Connect and claim" }, @@ -1348,6 +1372,9 @@ "defaultMessage": "Your schedule article has been published", "description": "src/components/Notice/ArticleNotice/ScheduledArticlePublishedNotice.tsx" }, + "HFBocb": { + "defaultMessage": "追蹤遠端作者後,最新內容會出現在這裡" + }, "HFVDeB": { "defaultMessage": "Related Tags", "description": "src/views/TagDetail/RelatedTags/index.tsx" @@ -1429,6 +1456,9 @@ "defaultMessage": "Unlink", "description": "src/components/Editor" }, + "IK6Mk7": { + "defaultMessage": "查看原文" + }, "IKPYe9": { "defaultMessage": "View", "description": "src/components/Dialogs/CollectionSelectDialog/index.tsx" @@ -1459,6 +1489,9 @@ "IjNYll": { "defaultMessage": "Let me think about it" }, + "InCosw": { + "defaultMessage": "搜尋" + }, "IpMWC4": { "defaultMessage": "You’ve posted several times in a short period. Please take a break." }, @@ -1714,6 +1747,9 @@ "defaultMessage": "Supports", "description": "src/views/Me/Transactions/index.tsx" }, + "NDG8qY": { + "defaultMessage": "喜歡" + }, "NDV5Mq": { "defaultMessage": "Options" }, @@ -1759,6 +1795,9 @@ "NmhF45": { "defaultMessage": "Adding tags helps readers find your articles. Add or create new tags." }, + "Nw8VNu": { + "defaultMessage": "聯邦宇宙互動中心" + }, "O0MQs/": { "defaultMessage": "Please add email first", "description": "src/views/Me/Settings/Settings/Password/index.tsx" @@ -1863,6 +1902,9 @@ "PXAur5": { "defaultMessage": "Withdraw" }, + "PbBsp5": { + "defaultMessage": "目前還沒有聯邦宇宙回覆" + }, "PesLat": { "defaultMessage": "Publishing..." }, @@ -1899,9 +1941,15 @@ "Q8Qw5B": { "defaultMessage": "Description" }, + "Q8g2+W": { + "defaultMessage": "全部已讀" + }, "QHRze5": { "defaultMessage": "Moments" }, + "QJd23B": { + "defaultMessage": "Fediverse 帳號" + }, "QKJWqd": { "defaultMessage": "Bookmark", "description": "src/components/Buttons/TagBookmark/Bookmark.tsx" @@ -1953,6 +2001,9 @@ "defaultMessage": "Incorrect verification code", "description": "CODE_INVALID" }, + "R6sMIX": { + "defaultMessage": "Fediverse" + }, "R7yHDl": { "defaultMessage": "Circle Revenue", "description": "src/components/Transaction/index.tsx" @@ -2068,12 +2119,12 @@ "Sj+TN8": { "defaultMessage": "Announcement" }, - "2cnZc/": { - "defaultMessage": "開啟後,新發佈的公開作品會自動送到 Fediverse。其他站台可能保留副本;關閉後將停止同步,並嘗試刪除先前內容。" - }, "SuRTsQ": { "defaultMessage": "Register for ISCN" }, + "SyxunU": { + "defaultMessage": "聯邦宇宙操作已送出" + }, "Szd1tH": { "defaultMessage": "Failed to log out, please try again." }, @@ -2227,6 +2278,9 @@ "defaultMessage": "Ended", "description": "src/views/Campaigns/Feeds/Tabs/index.tsx" }, + "VWeoZC": { + "defaultMessage": "目前沒有通知" + }, "VZsE96": { "defaultMessage": "Collection Name" }, @@ -2269,6 +2323,9 @@ "defaultMessage": "Last 3 months", "description": "src/views/Me/Analytics/SelectPeriod/index.tsx" }, + "W1j+F0": { + "defaultMessage": "請簡述檢舉原因" + }, "W3hNBA": { "defaultMessage": "Free Write in 7 days Grand Badge" }, @@ -2346,6 +2403,9 @@ "XTRqqT": { "defaultMessage": "Add to event" }, + "XTkPen": { + "defaultMessage": "來自其他 Fediverse 站台的回應" + }, "XTyKFR": { "defaultMessage": "Adding articles to a collection helps readers find your articles." }, @@ -2455,9 +2515,6 @@ "defaultMessage": "Archived for violation.", "description": "src/views/ArticleDetail/StickyTopBanner/index.tsx" }, - "cY80V3": { - "defaultMessage": "公開作品會自動送出,遠端站台可能保留副本" - }, "ZAs170": { "defaultMessage": "Profile", "description": "src/views/Circle/Settings/index.tsx" @@ -2586,6 +2643,9 @@ "b6x6lm": { "defaultMessage": "Enter a clear and concise title" }, + "b8/cFV": { + "defaultMessage": "聯邦宇宙" + }, "b8LMpq": { "defaultMessage": "View all {count} comments", "description": "src/views/CampaignDetail/Discussion" @@ -2616,6 +2676,9 @@ "beLe/F": { "defaultMessage": "Broadcast" }, + "bmR7T4": { + "defaultMessage": "轉發" + }, "bv9UzQ": { "defaultMessage": "See all quotes" }, @@ -2663,6 +2726,9 @@ "defaultMessage": "Currency", "description": "src/views/Me/Settings/Misc/Currency/index.tsx" }, + "cY80V3": { + "defaultMessage": "公開作品會自動送出,遠端站台可能保留副本" + }, "cYvIpp": { "defaultMessage": "Already subscribed to this circle.", "description": "DUPLICATE_CIRCLE_SUBSCRIPTION" @@ -2947,6 +3013,9 @@ "defaultMessage": "The payment amount is invalid, please re-enter.", "description": "PAYMENT_AMOUNT_INVALID" }, + "glsBoL": { + "defaultMessage": "聯邦通知" + }, "gsZ/aj": { "defaultMessage": "Comments allowed, cannot be closed once published", "description": "src/components/Editor/Sidebar/CanComment/index.tsx" @@ -3033,6 +3102,9 @@ "defaultMessage": "Number of readers", "description": "src/components/ArticleDigest/Published/FooterActions/index.tsx" }, + "hyDCe8": { + "defaultMessage": "位追蹤者" + }, "i4OBEw": { "defaultMessage": "\"{name}\" is invalid." }, @@ -3066,6 +3138,9 @@ "ieGrWo": { "defaultMessage": "Follow" }, + "ih+UCV": { + "defaultMessage": "回覆內容" + }, "imXsmo": { "defaultMessage": "back to latest", "description": "src/views/ArticleDetail/StickyTopBanner/index.tsx" @@ -3106,6 +3181,9 @@ "defaultMessage": "replied you", "description": "src/components/Notice/CommentCommentNotice/CommentNewReplyNotice.tsx" }, + "jWwn1K": { + "defaultMessage": "尋找遠端作者" + }, "jiB0Z2": { "defaultMessage": "Unable to bind wallet", "description": "src/components/Forms/PaymentForm/BindWallet/index.tsx" @@ -3159,6 +3237,9 @@ "kHMa3H": { "defaultMessage": "The new password and confirmation password do not match." }, + "kOStiL": { + "defaultMessage": "輸入完整帳號,例如 @alice@example.social" + }, "kS3vTS": { "defaultMessage": "Liker ID", "description": "src/views/Me/Settings/Misc/LikerID.tsx" @@ -3170,6 +3251,9 @@ "kc79d3": { "defaultMessage": "Topics" }, + "kggbxQ": { + "defaultMessage": "追蹤" + }, "kkZioy": { "defaultMessage": "Remove", "description": "src/components/Dialogs/RemoveArticleCollectionDialog/index.tsx" @@ -3188,6 +3272,9 @@ "defaultMessage": "Please enter new transaction password", "description": "src/components/Forms/PaymentForm/ResetPassword/Confirm.tsx" }, + "kw8J39": { + "defaultMessage": "回覆到聯邦宇宙" + }, "l/f7bu": { "defaultMessage": "Want to know more? Check the", "description": "src/views/Me/Analytics/EmptyAnalytics/index.tsx" @@ -3203,6 +3290,9 @@ "defaultMessage": "Preview", "description": "src/components/Editor/MoreSettings/SupportSettingDialog/Content.tsx" }, + "lDTamI": { + "defaultMessage": "回覆" + }, "lFi0TA": { "defaultMessage": "Add to Collection" }, @@ -3321,6 +3411,9 @@ "me1nR+": { "defaultMessage": "Copy Address" }, + "miEMcG": { + "defaultMessage": "追蹤中" + }, "mij/rJ": { "defaultMessage": "Delete draft" }, @@ -3614,6 +3707,9 @@ "rfz/fN": { "defaultMessage": "Recommended size: 1600px x 900px" }, + "rjEc9z": { + "defaultMessage": "取消追蹤" + }, "rqS2aA": { "defaultMessage": "Sign in to that account to unlink it then try again", "description": "src/components/AuthMethodFeed/AuthWalletFeed.tsx" @@ -3625,9 +3721,15 @@ "ruZqtT": { "defaultMessage": "Must be between {MIN_USER_DISPLAY_NAME_LENGTH}-{MAX_USER_DISPLAY_NAME_LENGTH} characters long." }, + "rudmDY": { + "defaultMessage": "追蹤遠端作者、閱讀聯邦動態,並管理回覆與通知" + }, "s2s6tx": { "defaultMessage": "The article has been successfully published and will be synced to IPFS soon." }, + "sAB+vZ": { + "defaultMessage": "等待核准" + }, "sE2Ui3": { "defaultMessage": "Account Frozen" }, @@ -3669,10 +3771,16 @@ "syEQFE": { "defaultMessage": "Publish" }, + "t0kvjC": { + "defaultMessage": "查看全部" + }, "t1wXVG": { "defaultMessage": "Say something? Someone might listen.", "description": "MOMENT_FORM" }, + "t1yrE4": { + "defaultMessage": "最多追蹤 {maxFollowing} 個帳號,動態保留 {days} 天,上限 {items} 筆" + }, "t2EpN/": { "defaultMessage": "Request on {network} network will be confirmed and synced to Matters in a bit" }, @@ -4097,6 +4205,9 @@ "zE51j6": { "defaultMessage": "Failed to publish, please try again." }, + "zK82DU": { + "defaultMessage": "追蹤動態" + }, "zKOr2x": { "defaultMessage": "Conversion Rate of Followers", "description": "src/views/Circle/Analytics/FollowerAnalytics/index.tsx" @@ -4114,6 +4225,9 @@ "zWXgmM": { "defaultMessage": "Return and continue" }, + "zWonDB": { + "defaultMessage": "尚未追蹤任何遠端作者" + }, "zaY8Cu": { "defaultMessage": "Enter transaction password", "description": "src/components/Forms/PaymentForm/PayTo/Confirm/index.tsx" diff --git a/lang/en.json b/lang/en.json index a2a6e551ee..4e61989c92 100644 --- a/lang/en.json +++ b/lang/en.json @@ -84,6 +84,9 @@ "+vVZ/G": { "defaultMessage": "Connect" }, + "+wqFkx": { + "defaultMessage": "聯邦宇宙互動" + }, "/0OJlF": { "defaultMessage": "No discussion yet", "description": "src/views/CampaignDetail/Discussion" @@ -188,6 +191,9 @@ "defaultMessage": "Create collection", "description": "src/views/User/Collections/UserCollections.tsx" }, + "0XnomJ": { + "defaultMessage": "找不到這個聯邦帳號" + }, "0bTlm4": { "defaultMessage": "Are you sure you want to delete this moment?" }, @@ -353,6 +359,9 @@ "defaultMessage": "Edit", "description": "src/views/ArticleDetail/MetaInfo/index.tsx" }, + "2cnZc/": { + "defaultMessage": "When enabled, newly published public works are automatically sent to the Fediverse. Other servers may retain copies; disabling stops future sync and requests deletion of prior content." + }, "2ef/Xl": { "defaultMessage": "Block", "description": "src/views/Me/Settings/Blocked/ToggleBlockButton.tsx" @@ -456,6 +465,9 @@ "defaultMessage": "色情廣告", "description": "src/components/Comment/DropdownActions/CommunityWatchRemoveComment.tsx" }, + "4GmeZE": { + "defaultMessage": "來自其他 Fediverse 站台的互動" + }, "4KuZ0o": { "defaultMessage": "Asset not found", "description": "ASSET_NOT_FOUND" @@ -516,6 +528,9 @@ "defaultMessage": "liked your deleted moment", "description": "src/components/Notice/MomentNotice/MomentLiked.tsx" }, + "5UgS3k": { + "defaultMessage": "封鎖" + }, "5UglrB": { "defaultMessage": "Successfully delivered" }, @@ -535,6 +550,9 @@ "5jlQTx": { "defaultMessage": "Manage Invitation" }, + "5kX6Qk": { + "defaultMessage": "檢舉" + }, "5mu8HJ": { "defaultMessage": "Moment deleted" }, @@ -606,6 +624,9 @@ "6Sj2lN": { "defaultMessage": "Claim" }, + "6UaJbf": { + "defaultMessage": "送出" + }, "6Vznnt": { "defaultMessage": "left a comment in your circle", "description": "src/components/Notice/CircleNotice/CircleNewDiscussionComments.tsx" @@ -978,6 +999,9 @@ "defaultMessage": "Provide support funds to", "description": "src/components/Forms/PaymentForm/PaymentInfo/index.tsx" }, + "ByvxDL": { + "defaultMessage": "追蹤中的作者" + }, "C/4nS6": { "defaultMessage": "Connect and claim" }, @@ -1348,6 +1372,9 @@ "defaultMessage": "Your schedule article has been published", "description": "src/components/Notice/ArticleNotice/ScheduledArticlePublishedNotice.tsx" }, + "HFBocb": { + "defaultMessage": "追蹤遠端作者後,最新內容會出現在這裡" + }, "HFVDeB": { "defaultMessage": "Related Tags", "description": "src/views/TagDetail/RelatedTags/index.tsx" @@ -1429,6 +1456,9 @@ "defaultMessage": "Unlink", "description": "src/components/Editor" }, + "IK6Mk7": { + "defaultMessage": "查看原文" + }, "IKPYe9": { "defaultMessage": "View", "description": "src/components/Dialogs/CollectionSelectDialog/index.tsx" @@ -1459,6 +1489,9 @@ "IjNYll": { "defaultMessage": "Let me think about it" }, + "InCosw": { + "defaultMessage": "搜尋" + }, "IpMWC4": { "defaultMessage": "You’ve posted several times in a short period. Please take a break." }, @@ -1714,6 +1747,9 @@ "defaultMessage": "Supports", "description": "src/views/Me/Transactions/index.tsx" }, + "NDG8qY": { + "defaultMessage": "喜歡" + }, "NDV5Mq": { "defaultMessage": "Options" }, @@ -1759,6 +1795,9 @@ "NmhF45": { "defaultMessage": "Adding tags helps readers find your articles. Add or create new tags." }, + "Nw8VNu": { + "defaultMessage": "聯邦宇宙互動中心" + }, "O0MQs/": { "defaultMessage": "Please add email first", "description": "src/views/Me/Settings/Settings/Password/index.tsx" @@ -1863,6 +1902,9 @@ "PXAur5": { "defaultMessage": "Withdraw" }, + "PbBsp5": { + "defaultMessage": "目前還沒有聯邦宇宙回覆" + }, "PesLat": { "defaultMessage": "Publishing..." }, @@ -1899,9 +1941,15 @@ "Q8Qw5B": { "defaultMessage": "Description" }, + "Q8g2+W": { + "defaultMessage": "全部已讀" + }, "QHRze5": { "defaultMessage": "Moments" }, + "QJd23B": { + "defaultMessage": "Fediverse 帳號" + }, "QKJWqd": { "defaultMessage": "Bookmark", "description": "src/components/Buttons/TagBookmark/Bookmark.tsx" @@ -1953,6 +2001,9 @@ "defaultMessage": "Incorrect verification code", "description": "CODE_INVALID" }, + "R6sMIX": { + "defaultMessage": "Fediverse" + }, "R7yHDl": { "defaultMessage": "Circle Revenue", "description": "src/components/Transaction/index.tsx" @@ -2068,12 +2119,12 @@ "Sj+TN8": { "defaultMessage": "Announcement" }, - "2cnZc/": { - "defaultMessage": "When enabled, newly published public works are automatically sent to the Fediverse. Other servers may retain copies; disabling stops future sync and requests deletion of prior content." - }, "SuRTsQ": { "defaultMessage": "Register for ISCN" }, + "SyxunU": { + "defaultMessage": "聯邦宇宙操作已送出" + }, "Szd1tH": { "defaultMessage": "Failed to log out, please try again." }, @@ -2227,6 +2278,9 @@ "defaultMessage": "Ended", "description": "src/views/Campaigns/Feeds/Tabs/index.tsx" }, + "VWeoZC": { + "defaultMessage": "目前沒有通知" + }, "VZsE96": { "defaultMessage": "Collection Name" }, @@ -2269,6 +2323,9 @@ "defaultMessage": "Last 3 months", "description": "src/views/Me/Analytics/SelectPeriod/index.tsx" }, + "W1j+F0": { + "defaultMessage": "請簡述檢舉原因" + }, "W3hNBA": { "defaultMessage": "Free Write in 7 days Grand Badge" }, @@ -2346,6 +2403,9 @@ "XTRqqT": { "defaultMessage": "Add to event" }, + "XTkPen": { + "defaultMessage": "來自其他 Fediverse 站台的回應" + }, "XTyKFR": { "defaultMessage": "Adding articles to a collection helps readers find your articles." }, @@ -2455,9 +2515,6 @@ "defaultMessage": "Archived for violation.", "description": "src/views/ArticleDetail/StickyTopBanner/index.tsx" }, - "cY80V3": { - "defaultMessage": "Public works are sent automatically and remote servers may retain copies" - }, "ZAs170": { "defaultMessage": "Profile", "description": "src/views/Circle/Settings/index.tsx" @@ -2586,6 +2643,9 @@ "b6x6lm": { "defaultMessage": "Enter a clear and concise title" }, + "b8/cFV": { + "defaultMessage": "聯邦宇宙" + }, "b8LMpq": { "defaultMessage": "View all {count} comments", "description": "src/views/CampaignDetail/Discussion" @@ -2616,6 +2676,9 @@ "beLe/F": { "defaultMessage": "Broadcast" }, + "bmR7T4": { + "defaultMessage": "轉發" + }, "bv9UzQ": { "defaultMessage": "See all quotes" }, @@ -2663,6 +2726,9 @@ "defaultMessage": "Currency", "description": "src/views/Me/Settings/Misc/Currency/index.tsx" }, + "cY80V3": { + "defaultMessage": "Public works are sent automatically and remote servers may retain copies" + }, "cYvIpp": { "defaultMessage": "Already subscribed to this circle.", "description": "DUPLICATE_CIRCLE_SUBSCRIPTION" @@ -2947,6 +3013,9 @@ "defaultMessage": "The payment amount is invalid, please re-enter.", "description": "PAYMENT_AMOUNT_INVALID" }, + "glsBoL": { + "defaultMessage": "聯邦通知" + }, "gsZ/aj": { "defaultMessage": "Comments allowed, cannot be closed once published", "description": "src/components/Editor/Sidebar/CanComment/index.tsx" @@ -3033,6 +3102,9 @@ "defaultMessage": "Number of readers", "description": "src/components/ArticleDigest/Published/FooterActions/index.tsx" }, + "hyDCe8": { + "defaultMessage": "位追蹤者" + }, "i4OBEw": { "defaultMessage": "\"{name}\" is invalid." }, @@ -3066,6 +3138,9 @@ "ieGrWo": { "defaultMessage": "Follow" }, + "ih+UCV": { + "defaultMessage": "回覆內容" + }, "imXsmo": { "defaultMessage": "back to latest", "description": "src/views/ArticleDetail/StickyTopBanner/index.tsx" @@ -3106,6 +3181,9 @@ "defaultMessage": "replied you", "description": "src/components/Notice/CommentCommentNotice/CommentNewReplyNotice.tsx" }, + "jWwn1K": { + "defaultMessage": "尋找遠端作者" + }, "jiB0Z2": { "defaultMessage": "Unable to bind wallet", "description": "src/components/Forms/PaymentForm/BindWallet/index.tsx" @@ -3159,6 +3237,9 @@ "kHMa3H": { "defaultMessage": "The new password and confirmation password do not match." }, + "kOStiL": { + "defaultMessage": "輸入完整帳號,例如 @alice@example.social" + }, "kS3vTS": { "defaultMessage": "Liker ID", "description": "src/views/Me/Settings/Misc/LikerID.tsx" @@ -3170,6 +3251,9 @@ "kc79d3": { "defaultMessage": "Topics" }, + "kggbxQ": { + "defaultMessage": "追蹤" + }, "kkZioy": { "defaultMessage": "Remove", "description": "src/components/Dialogs/RemoveArticleCollectionDialog/index.tsx" @@ -3188,6 +3272,9 @@ "defaultMessage": "Please enter new transaction password", "description": "src/components/Forms/PaymentForm/ResetPassword/Confirm.tsx" }, + "kw8J39": { + "defaultMessage": "回覆到聯邦宇宙" + }, "l/f7bu": { "defaultMessage": "Want to know more? Check the", "description": "src/views/Me/Analytics/EmptyAnalytics/index.tsx" @@ -3203,6 +3290,9 @@ "defaultMessage": "Preview", "description": "src/components/Editor/MoreSettings/SupportSettingDialog/Content.tsx" }, + "lDTamI": { + "defaultMessage": "回覆" + }, "lFi0TA": { "defaultMessage": "Add to Collection" }, @@ -3321,6 +3411,9 @@ "me1nR+": { "defaultMessage": "Copy Address" }, + "miEMcG": { + "defaultMessage": "追蹤中" + }, "mij/rJ": { "defaultMessage": "Delete draft" }, @@ -3614,6 +3707,9 @@ "rfz/fN": { "defaultMessage": "Recommended size: 1600px x 900px" }, + "rjEc9z": { + "defaultMessage": "取消追蹤" + }, "rqS2aA": { "defaultMessage": "Sign in to that account to unlink it then try again", "description": "src/components/AuthMethodFeed/AuthWalletFeed.tsx" @@ -3625,9 +3721,15 @@ "ruZqtT": { "defaultMessage": "Must be between {MIN_USER_DISPLAY_NAME_LENGTH}-{MAX_USER_DISPLAY_NAME_LENGTH} characters long." }, + "rudmDY": { + "defaultMessage": "追蹤遠端作者、閱讀聯邦動態,並管理回覆與通知" + }, "s2s6tx": { "defaultMessage": "The article has been successfully published and will be synced to IPFS soon." }, + "sAB+vZ": { + "defaultMessage": "等待核准" + }, "sE2Ui3": { "defaultMessage": "Account Frozen" }, @@ -3669,10 +3771,16 @@ "syEQFE": { "defaultMessage": "Publish" }, + "t0kvjC": { + "defaultMessage": "查看全部" + }, "t1wXVG": { "defaultMessage": "Say something? Someone might listen.", "description": "MOMENT_FORM" }, + "t1yrE4": { + "defaultMessage": "最多追蹤 {maxFollowing} 個帳號,動態保留 {days} 天,上限 {items} 筆" + }, "t2EpN/": { "defaultMessage": "Request on {network} network will be confirmed and synced to Matters in a bit" }, @@ -4097,6 +4205,9 @@ "zE51j6": { "defaultMessage": "Failed to publish, please try again." }, + "zK82DU": { + "defaultMessage": "追蹤動態" + }, "zKOr2x": { "defaultMessage": "Conversion Rate of Followers", "description": "src/views/Circle/Analytics/FollowerAnalytics/index.tsx" @@ -4114,6 +4225,9 @@ "zWXgmM": { "defaultMessage": "Return and continue" }, + "zWonDB": { + "defaultMessage": "尚未追蹤任何遠端作者" + }, "zaY8Cu": { "defaultMessage": "Enter transaction password", "description": "src/components/Forms/PaymentForm/PayTo/Confirm/index.tsx" diff --git a/lang/zh-Hans.json b/lang/zh-Hans.json index 5d031750f9..f45436678c 100644 --- a/lang/zh-Hans.json +++ b/lang/zh-Hans.json @@ -84,6 +84,9 @@ "+vVZ/G": { "defaultMessage": "绑定" }, + "+wqFkx": { + "defaultMessage": "聯邦宇宙互動" + }, "/0OJlF": { "defaultMessage": "还没有人留言", "description": "src/views/CampaignDetail/Discussion" @@ -188,6 +191,9 @@ "defaultMessage": "新建选集", "description": "src/views/User/Collections/UserCollections.tsx" }, + "0XnomJ": { + "defaultMessage": "找不到這個聯邦帳號" + }, "0bTlm4": { "defaultMessage": "确认要删除该动态吗?" }, @@ -353,6 +359,9 @@ "defaultMessage": "修改", "description": "src/views/ArticleDetail/MetaInfo/index.tsx" }, + "2cnZc/": { + "defaultMessage": "开启后,新发布的公开作品会自动发送到 Fediverse。其他站点可能保留副本;关闭后将停止同步,并尝试删除先前内容。" + }, "2ef/Xl": { "defaultMessage": "屏蔽", "description": "src/views/Me/Settings/Blocked/ToggleBlockButton.tsx" @@ -456,6 +465,9 @@ "defaultMessage": "色情廣告", "description": "src/components/Comment/DropdownActions/CommunityWatchRemoveComment.tsx" }, + "4GmeZE": { + "defaultMessage": "來自其他 Fediverse 站台的互動" + }, "4KuZ0o": { "defaultMessage": "资源不存在", "description": "ASSET_NOT_FOUND" @@ -515,6 +527,9 @@ "defaultMessage": "喜欢你已删除的动态", "description": "src/components/Notice/MomentNotice/MomentLiked.tsx" }, + "5UgS3k": { + "defaultMessage": "封鎖" + }, "5UglrB": { "defaultMessage": "送出成功" }, @@ -534,6 +549,9 @@ "5jlQTx": { "defaultMessage": "邀请管理" }, + "5kX6Qk": { + "defaultMessage": "檢舉" + }, "5mu8HJ": { "defaultMessage": "动态已删除" }, @@ -605,6 +623,9 @@ "6Sj2lN": { "defaultMessage": "提领支持" }, + "6UaJbf": { + "defaultMessage": "送出" + }, "6Vznnt": { "defaultMessage": "在你的围炉中留言", "description": "src/components/Notice/CircleNotice/CircleNewDiscussionComments.tsx" @@ -977,6 +998,9 @@ "defaultMessage": "递出支持资金给", "description": "src/components/Forms/PaymentForm/PaymentInfo/index.tsx" }, + "ByvxDL": { + "defaultMessage": "追蹤中的作者" + }, "C/4nS6": { "defaultMessage": "绑定并提领" }, @@ -1347,6 +1371,9 @@ "defaultMessage": "你的定时发布作品已发布成功", "description": "src/components/Notice/ArticleNotice/ScheduledArticlePublishedNotice.tsx" }, + "HFBocb": { + "defaultMessage": "追蹤遠端作者後,最新內容會出現在這裡" + }, "HFVDeB": { "defaultMessage": "相关标签", "description": "src/views/TagDetail/RelatedTags/index.tsx" @@ -1428,6 +1455,9 @@ "defaultMessage": "取消链接", "description": "src/components/Editor" }, + "IK6Mk7": { + "defaultMessage": "查看原文" + }, "IKPYe9": { "defaultMessage": "查看", "description": "src/components/Dialogs/CollectionSelectDialog/index.tsx" @@ -1458,6 +1488,9 @@ "IjNYll": { "defaultMessage": "等等再说" }, + "InCosw": { + "defaultMessage": "搜尋" + }, "IpMWC4": { "defaultMessage": "短时间内已发布多条动态,请先休息一会吧~" }, @@ -1713,6 +1746,9 @@ "defaultMessage": "支持", "description": "src/views/Me/Transactions/index.tsx" }, + "NDG8qY": { + "defaultMessage": "喜歡" + }, "NDV5Mq": { "defaultMessage": "选项" }, @@ -1758,6 +1794,9 @@ "NmhF45": { "defaultMessage": "通过添加标签帮助读者更好地找到你的作品。如果没有合适的标签,你可以创建新的。" }, + "Nw8VNu": { + "defaultMessage": "聯邦宇宙互動中心" + }, "O0MQs/": { "defaultMessage": "请先绑定邮箱", "description": "src/views/Me/Settings/Settings/Password/index.tsx" @@ -1862,6 +1901,9 @@ "PXAur5": { "defaultMessage": "提现" }, + "PbBsp5": { + "defaultMessage": "目前還沒有聯邦宇宙回覆" + }, "PesLat": { "defaultMessage": "正在发布,马上就好" }, @@ -1898,9 +1940,15 @@ "Q8Qw5B": { "defaultMessage": "描述" }, + "Q8g2+W": { + "defaultMessage": "全部已讀" + }, "QHRze5": { "defaultMessage": "闲聊" }, + "QJd23B": { + "defaultMessage": "Fediverse 帳號" + }, "QKJWqd": { "defaultMessage": "收藏", "description": "src/components/Buttons/TagBookmark/Bookmark.tsx" @@ -1952,6 +2000,9 @@ "defaultMessage": "验证码错误", "description": "CODE_INVALID" }, + "R6sMIX": { + "defaultMessage": "Fediverse" + }, "R7yHDl": { "defaultMessage": "围炉营收", "description": "src/components/Transaction/index.tsx" @@ -2067,12 +2118,12 @@ "Sj+TN8": { "defaultMessage": "公告" }, - "2cnZc/": { - "defaultMessage": "开启后,新发布的公开作品会自动发送到 Fediverse。其他站点可能保留副本;关闭后将停止同步,并尝试删除先前内容。" - }, "SuRTsQ": { "defaultMessage": "注册 ISCN" }, + "SyxunU": { + "defaultMessage": "聯邦宇宙操作已送出" + }, "Szd1tH": { "defaultMessage": "登出失败,再来一次" }, @@ -2226,6 +2277,9 @@ "defaultMessage": "过往活动", "description": "src/views/Campaigns/Feeds/Tabs/index.tsx" }, + "VWeoZC": { + "defaultMessage": "目前沒有通知" + }, "VZsE96": { "defaultMessage": "选集名称" }, @@ -2268,6 +2322,9 @@ "defaultMessage": "最近 3 个月", "description": "src/views/Me/Analytics/SelectPeriod/index.tsx" }, + "W1j+F0": { + "defaultMessage": "請簡述檢舉原因" + }, "W3hNBA": { "defaultMessage": "七日书大满贯" }, @@ -2345,6 +2402,9 @@ "XTRqqT": { "defaultMessage": "投稿活动" }, + "XTkPen": { + "defaultMessage": "來自其他 Fediverse 站台的回應" + }, "XTyKFR": { "defaultMessage": "精挑细选作品,帮助读者更容易发现优质内容" }, @@ -2454,9 +2514,6 @@ "defaultMessage": "因违反用户协定而被封存,", "description": "src/views/ArticleDetail/StickyTopBanner/index.tsx" }, - "cY80V3": { - "defaultMessage": "公开作品会自动发送,远程站点可能保留副本" - }, "ZAs170": { "defaultMessage": "基本资料", "description": "src/views/Circle/Settings/index.tsx" @@ -2585,6 +2642,9 @@ "b6x6lm": { "defaultMessage": "输入简单明了的标题" }, + "b8/cFV": { + "defaultMessage": "聯邦宇宙" + }, "b8LMpq": { "defaultMessage": "查看全部 {count} 则留言", "description": "src/views/CampaignDetail/Discussion" @@ -2615,6 +2675,9 @@ "beLe/F": { "defaultMessage": "广播" }, + "bmR7T4": { + "defaultMessage": "轉發" + }, "bv9UzQ": { "defaultMessage": "查看全部金句" }, @@ -2662,6 +2725,9 @@ "defaultMessage": "汇率币别", "description": "src/views/Me/Settings/Misc/Currency/index.tsx" }, + "cY80V3": { + "defaultMessage": "公开作品会自动发送,远程站点可能保留副本" + }, "cYvIpp": { "defaultMessage": "无需重复订阅此围炉", "description": "DUPLICATE_CIRCLE_SUBSCRIPTION" @@ -2946,6 +3012,9 @@ "defaultMessage": "支付金额无效,请重新输入", "description": "PAYMENT_AMOUNT_INVALID" }, + "glsBoL": { + "defaultMessage": "聯邦通知" + }, "gsZ/aj": { "defaultMessage": "允许评论后,一经发布后无法关闭", "description": "src/components/Editor/Sidebar/CanComment/index.tsx" @@ -3032,6 +3101,9 @@ "defaultMessage": "读者数量", "description": "src/components/ArticleDigest/Published/FooterActions/index.tsx" }, + "hyDCe8": { + "defaultMessage": "位追蹤者" + }, "i4OBEw": { "defaultMessage": "不能使用 “{name}”" }, @@ -3065,6 +3137,9 @@ "ieGrWo": { "defaultMessage": "关注" }, + "ih+UCV": { + "defaultMessage": "回覆內容" + }, "imXsmo": { "defaultMessage": "回到作品页", "description": "src/views/ArticleDetail/StickyTopBanner/index.tsx" @@ -3105,6 +3180,9 @@ "defaultMessage": "回复了你", "description": "src/components/Notice/CommentCommentNotice/CommentNewReplyNotice.tsx" }, + "jWwn1K": { + "defaultMessage": "尋找遠端作者" + }, "jiB0Z2": { "defaultMessage": "无法绑定钱包", "description": "src/components/Forms/PaymentForm/BindWallet/index.tsx" @@ -3158,6 +3236,9 @@ "kHMa3H": { "defaultMessage": "密码不一致" }, + "kOStiL": { + "defaultMessage": "輸入完整帳號,例如 @alice@example.social" + }, "kS3vTS": { "defaultMessage": "Liker ID", "description": "src/views/Me/Settings/Misc/LikerID.tsx" @@ -3169,6 +3250,9 @@ "kc79d3": { "defaultMessage": "热门标签" }, + "kggbxQ": { + "defaultMessage": "追蹤" + }, "kkZioy": { "defaultMessage": "确认移出", "description": "src/components/Dialogs/RemoveArticleCollectionDialog/index.tsx" @@ -3187,6 +3271,9 @@ "defaultMessage": "请输入新交易密码", "description": "src/components/Forms/PaymentForm/ResetPassword/Confirm.tsx" }, + "kw8J39": { + "defaultMessage": "回覆到聯邦宇宙" + }, "l/f7bu": { "defaultMessage": "想了解更多?詳見 ", "description": "src/views/Me/Analytics/EmptyAnalytics/index.tsx" @@ -3202,6 +3289,9 @@ "defaultMessage": "预览", "description": "src/components/Editor/MoreSettings/SupportSettingDialog/Content.tsx" }, + "lDTamI": { + "defaultMessage": "回覆" + }, "lFi0TA": { "defaultMessage": "加入选集" }, @@ -3319,6 +3409,9 @@ "me1nR+": { "defaultMessage": "复制地址" }, + "miEMcG": { + "defaultMessage": "追蹤中" + }, "mij/rJ": { "defaultMessage": "删除草稿" }, @@ -3612,6 +3705,9 @@ "rfz/fN": { "defaultMessage": "建議尺寸 1600 x 900 像素" }, + "rjEc9z": { + "defaultMessage": "取消追蹤" + }, "rqS2aA": { "defaultMessage": "请登录该帐户解绑钱包后再试", "description": "src/components/AuthMethodFeed/AuthWalletFeed.tsx" @@ -3623,9 +3719,15 @@ "ruZqtT": { "defaultMessage": "仅供输入 {MIN_USER_DISPLAY_NAME_LENGTH}-{MAX_USER_DISPLAY_NAME_LENGTH} 个字符" }, + "rudmDY": { + "defaultMessage": "追蹤遠端作者、閱讀聯邦動態,並管理回覆與通知" + }, "s2s6tx": { "defaultMessage": "作品已成功发布,稍后将同步到 IPFS" }, + "sAB+vZ": { + "defaultMessage": "等待核准" + }, "sE2Ui3": { "defaultMessage": "账号已冻结" }, @@ -3667,10 +3769,16 @@ "syEQFE": { "defaultMessage": "发布" }, + "t0kvjC": { + "defaultMessage": "查看全部" + }, "t1wXVG": { "defaultMessage": "说说看?也许有人听", "description": "MOMENT_FORM" }, + "t1yrE4": { + "defaultMessage": "最多追蹤 {maxFollowing} 個帳號,動態保留 {days} 天,上限 {items} 筆" + }, "t2EpN/": { "defaultMessage": "持续与 {network} 网络同步,稍后更新至 Matters" }, @@ -4095,6 +4203,9 @@ "zE51j6": { "defaultMessage": "发布失败" }, + "zK82DU": { + "defaultMessage": "追蹤動態" + }, "zKOr2x": { "defaultMessage": "已关注用户占总观看人数比例", "description": "src/views/Circle/Analytics/FollowerAnalytics/index.tsx" @@ -4112,6 +4223,9 @@ "zWXgmM": { "defaultMessage": "请回到原页面继续操作" }, + "zWonDB": { + "defaultMessage": "尚未追蹤任何遠端作者" + }, "zaY8Cu": { "defaultMessage": "输入交易密码", "description": "src/components/Forms/PaymentForm/PayTo/Confirm/index.tsx" diff --git a/lang/zh-Hant.json b/lang/zh-Hant.json index bed238c281..c09bd21940 100644 --- a/lang/zh-Hant.json +++ b/lang/zh-Hant.json @@ -84,6 +84,9 @@ "+vVZ/G": { "defaultMessage": "綁定" }, + "+wqFkx": { + "defaultMessage": "聯邦宇宙互動" + }, "/0OJlF": { "defaultMessage": "還沒有人留言", "description": "src/views/CampaignDetail/Discussion" @@ -188,6 +191,9 @@ "defaultMessage": "新建選集", "description": "src/views/User/Collections/UserCollections.tsx" }, + "0XnomJ": { + "defaultMessage": "找不到這個聯邦帳號" + }, "0bTlm4": { "defaultMessage": "確認要刪除該動態嗎?" }, @@ -353,6 +359,9 @@ "defaultMessage": "修改", "description": "src/views/ArticleDetail/MetaInfo/index.tsx" }, + "2cnZc/": { + "defaultMessage": "開啟後,新發佈的公開作品會自動送到 Fediverse。其他站台可能保留副本;關閉後將停止同步,並嘗試刪除先前內容。" + }, "2ef/Xl": { "defaultMessage": "封鎖", "description": "src/views/Me/Settings/Blocked/ToggleBlockButton.tsx" @@ -456,6 +465,9 @@ "defaultMessage": "色情廣告", "description": "src/components/Comment/DropdownActions/CommunityWatchRemoveComment.tsx" }, + "4GmeZE": { + "defaultMessage": "來自其他 Fediverse 站台的互動" + }, "4KuZ0o": { "defaultMessage": "資源不存在", "description": "ASSET_NOT_FOUND" @@ -515,6 +527,9 @@ "defaultMessage": "喜歡你已刪除的動態", "description": "src/components/Notice/MomentNotice/MomentLiked.tsx" }, + "5UgS3k": { + "defaultMessage": "封鎖" + }, "5UglrB": { "defaultMessage": "送出成功" }, @@ -534,6 +549,9 @@ "5jlQTx": { "defaultMessage": "邀請管理" }, + "5kX6Qk": { + "defaultMessage": "檢舉" + }, "5mu8HJ": { "defaultMessage": "動態已刪除" }, @@ -605,6 +623,9 @@ "6Sj2lN": { "defaultMessage": "提領支持" }, + "6UaJbf": { + "defaultMessage": "送出" + }, "6Vznnt": { "defaultMessage": "在你的圍爐中留言", "description": "src/components/Notice/CircleNotice/CircleNewDiscussionComments.tsx" @@ -977,6 +998,9 @@ "defaultMessage": "遞出支持資金給", "description": "src/components/Forms/PaymentForm/PaymentInfo/index.tsx" }, + "ByvxDL": { + "defaultMessage": "追蹤中的作者" + }, "C/4nS6": { "defaultMessage": "綁定並提領" }, @@ -1347,6 +1371,9 @@ "defaultMessage": "你的排程作品已發布成功", "description": "src/components/Notice/ArticleNotice/ScheduledArticlePublishedNotice.tsx" }, + "HFBocb": { + "defaultMessage": "追蹤遠端作者後,最新內容會出現在這裡" + }, "HFVDeB": { "defaultMessage": "相關標籤", "description": "src/views/TagDetail/RelatedTags/index.tsx" @@ -1428,6 +1455,9 @@ "defaultMessage": "取消連結", "description": "src/components/Editor" }, + "IK6Mk7": { + "defaultMessage": "查看原文" + }, "IKPYe9": { "defaultMessage": "查看", "description": "src/components/Dialogs/CollectionSelectDialog/index.tsx" @@ -1458,6 +1488,9 @@ "IjNYll": { "defaultMessage": "等等再說" }, + "InCosw": { + "defaultMessage": "搜尋" + }, "IpMWC4": { "defaultMessage": "短時間內已發布多條動態,請先休息一會吧~" }, @@ -1713,6 +1746,9 @@ "defaultMessage": "支持", "description": "src/views/Me/Transactions/index.tsx" }, + "NDG8qY": { + "defaultMessage": "喜歡" + }, "NDV5Mq": { "defaultMessage": "選項" }, @@ -1758,6 +1794,9 @@ "NmhF45": { "defaultMessage": "通過添加標籤幫助讀者更好地找到你的作品。如果沒有合適的標籤,你可以創建新的。" }, + "Nw8VNu": { + "defaultMessage": "聯邦宇宙互動中心" + }, "O0MQs/": { "defaultMessage": "請先綁定郵箱", "description": "src/views/Me/Settings/Settings/Password/index.tsx" @@ -1862,6 +1901,9 @@ "PXAur5": { "defaultMessage": "提現" }, + "PbBsp5": { + "defaultMessage": "目前還沒有聯邦宇宙回覆" + }, "PesLat": { "defaultMessage": "正在發布,馬上就好" }, @@ -1898,9 +1940,15 @@ "Q8Qw5B": { "defaultMessage": "描述" }, + "Q8g2+W": { + "defaultMessage": "全部已讀" + }, "QHRze5": { "defaultMessage": "閒聊" }, + "QJd23B": { + "defaultMessage": "Fediverse 帳號" + }, "QKJWqd": { "defaultMessage": "收藏", "description": "src/components/Buttons/TagBookmark/Bookmark.tsx" @@ -1952,6 +2000,9 @@ "defaultMessage": "驗證碼錯誤", "description": "CODE_INVALID" }, + "R6sMIX": { + "defaultMessage": "Fediverse" + }, "R7yHDl": { "defaultMessage": "圍爐營收", "description": "src/components/Transaction/index.tsx" @@ -2067,12 +2118,12 @@ "Sj+TN8": { "defaultMessage": "公告" }, - "2cnZc/": { - "defaultMessage": "開啟後,新發佈的公開作品會自動送到 Fediverse。其他站台可能保留副本;關閉後將停止同步,並嘗試刪除先前內容。" - }, "SuRTsQ": { "defaultMessage": "註冊 ISCN" }, + "SyxunU": { + "defaultMessage": "聯邦宇宙操作已送出" + }, "Szd1tH": { "defaultMessage": "登出失敗,請重試" }, @@ -2226,6 +2277,9 @@ "defaultMessage": "過往活動", "description": "src/views/Campaigns/Feeds/Tabs/index.tsx" }, + "VWeoZC": { + "defaultMessage": "目前沒有通知" + }, "VZsE96": { "defaultMessage": "選集名稱" }, @@ -2268,6 +2322,9 @@ "defaultMessage": "最近 3 個月", "description": "src/views/Me/Analytics/SelectPeriod/index.tsx" }, + "W1j+F0": { + "defaultMessage": "請簡述檢舉原因" + }, "W3hNBA": { "defaultMessage": "七日書大滿貫" }, @@ -2345,6 +2402,9 @@ "XTRqqT": { "defaultMessage": "投稿活動" }, + "XTkPen": { + "defaultMessage": "來自其他 Fediverse 站台的回應" + }, "XTyKFR": { "defaultMessage": "精挑細選作品,幫助讀者更容易發現優質內容" }, @@ -2454,9 +2514,6 @@ "defaultMessage": "因違反用戶協定而被封存,", "description": "src/views/ArticleDetail/StickyTopBanner/index.tsx" }, - "cY80V3": { - "defaultMessage": "公開作品會自動送出,遠端站台可能保留副本" - }, "ZAs170": { "defaultMessage": "基本資料", "description": "src/views/Circle/Settings/index.tsx" @@ -2585,6 +2642,9 @@ "b6x6lm": { "defaultMessage": "輸入簡單明瞭的標題" }, + "b8/cFV": { + "defaultMessage": "聯邦宇宙" + }, "b8LMpq": { "defaultMessage": "查看全部 {count} 則留言", "description": "src/views/CampaignDetail/Discussion" @@ -2615,6 +2675,9 @@ "beLe/F": { "defaultMessage": "廣播" }, + "bmR7T4": { + "defaultMessage": "轉發" + }, "bv9UzQ": { "defaultMessage": "查看全部金句" }, @@ -2662,6 +2725,9 @@ "defaultMessage": "匯率幣別", "description": "src/views/Me/Settings/Misc/Currency/index.tsx" }, + "cY80V3": { + "defaultMessage": "公開作品會自動送出,遠端站台可能保留副本" + }, "cYvIpp": { "defaultMessage": "無需重複訂閱此圍爐", "description": "DUPLICATE_CIRCLE_SUBSCRIPTION" @@ -2946,6 +3012,9 @@ "defaultMessage": "支付金額無效,請重新輸入", "description": "PAYMENT_AMOUNT_INVALID" }, + "glsBoL": { + "defaultMessage": "聯邦通知" + }, "gsZ/aj": { "defaultMessage": "允許評論後,一經發布後無法關閉", "description": "src/components/Editor/Sidebar/CanComment/index.tsx" @@ -3032,6 +3101,9 @@ "defaultMessage": "讀者數量", "description": "src/components/ArticleDigest/Published/FooterActions/index.tsx" }, + "hyDCe8": { + "defaultMessage": "位追蹤者" + }, "i4OBEw": { "defaultMessage": "不能使用「{name}」" }, @@ -3065,6 +3137,9 @@ "ieGrWo": { "defaultMessage": "追蹤" }, + "ih+UCV": { + "defaultMessage": "回覆內容" + }, "imXsmo": { "defaultMessage": "回到作品頁", "description": "src/views/ArticleDetail/StickyTopBanner/index.tsx" @@ -3105,6 +3180,9 @@ "defaultMessage": "回覆了你", "description": "src/components/Notice/CommentCommentNotice/CommentNewReplyNotice.tsx" }, + "jWwn1K": { + "defaultMessage": "尋找遠端作者" + }, "jiB0Z2": { "defaultMessage": "無法綁定錢包", "description": "src/components/Forms/PaymentForm/BindWallet/index.tsx" @@ -3158,6 +3236,9 @@ "kHMa3H": { "defaultMessage": "密碼不一致" }, + "kOStiL": { + "defaultMessage": "輸入完整帳號,例如 @alice@example.social" + }, "kS3vTS": { "defaultMessage": "Liker ID", "description": "src/views/Me/Settings/Misc/LikerID.tsx" @@ -3169,6 +3250,9 @@ "kc79d3": { "defaultMessage": "熱門標籤" }, + "kggbxQ": { + "defaultMessage": "追蹤" + }, "kkZioy": { "defaultMessage": "確認移出", "description": "src/components/Dialogs/RemoveArticleCollectionDialog/index.tsx" @@ -3187,6 +3271,9 @@ "defaultMessage": "請輸入新交易密碼", "description": "src/components/Forms/PaymentForm/ResetPassword/Confirm.tsx" }, + "kw8J39": { + "defaultMessage": "回覆到聯邦宇宙" + }, "l/f7bu": { "defaultMessage": "想了解更多?詳見 ", "description": "src/views/Me/Analytics/EmptyAnalytics/index.tsx" @@ -3202,6 +3289,9 @@ "defaultMessage": "預覽", "description": "src/components/Editor/MoreSettings/SupportSettingDialog/Content.tsx" }, + "lDTamI": { + "defaultMessage": "回覆" + }, "lFi0TA": { "defaultMessage": "加入選集" }, @@ -3319,6 +3409,9 @@ "me1nR+": { "defaultMessage": "複製地址" }, + "miEMcG": { + "defaultMessage": "追蹤中" + }, "mij/rJ": { "defaultMessage": "刪除草稿" }, @@ -3612,6 +3705,9 @@ "rfz/fN": { "defaultMessage": "建議尺寸 1600 x 900 像素" }, + "rjEc9z": { + "defaultMessage": "取消追蹤" + }, "rqS2aA": { "defaultMessage": "請登入該帳戶解綁錢包後再試", "description": "src/components/AuthMethodFeed/AuthWalletFeed.tsx" @@ -3623,9 +3719,15 @@ "ruZqtT": { "defaultMessage": "僅供輸入 {MIN_USER_DISPLAY_NAME_LENGTH}-{MAX_USER_DISPLAY_NAME_LENGTH} 個字元" }, + "rudmDY": { + "defaultMessage": "追蹤遠端作者、閱讀聯邦動態,並管理回覆與通知" + }, "s2s6tx": { "defaultMessage": "已成功發布作品,稍後同步到 IPFS" }, + "sAB+vZ": { + "defaultMessage": "等待核准" + }, "sE2Ui3": { "defaultMessage": "帳號已凍結" }, @@ -3667,10 +3769,16 @@ "syEQFE": { "defaultMessage": "發布" }, + "t0kvjC": { + "defaultMessage": "查看全部" + }, "t1wXVG": { "defaultMessage": "說說看?也許有人聽", "description": "MOMENT_FORM" }, + "t1yrE4": { + "defaultMessage": "最多追蹤 {maxFollowing} 個帳號,動態保留 {days} 天,上限 {items} 筆" + }, "t2EpN/": { "defaultMessage": "持續與 {network} 網絡同步,稍後更新至 Matters" }, @@ -4095,6 +4203,9 @@ "zE51j6": { "defaultMessage": "發布失敗" }, + "zK82DU": { + "defaultMessage": "追蹤動態" + }, "zKOr2x": { "defaultMessage": "已追蹤用戶佔總觀看人數比例", "description": "src/views/Circle/Analytics/FollowerAnalytics/index.tsx" @@ -4112,6 +4223,9 @@ "zWXgmM": { "defaultMessage": "請回到原頁面繼續操作" }, + "zWonDB": { + "defaultMessage": "尚未追蹤任何遠端作者" + }, "zaY8Cu": { "defaultMessage": "輸入交易密碼", "description": "src/components/Forms/PaymentForm/PayTo/Confirm/index.tsx" diff --git a/src/common/enums/route.ts b/src/common/enums/route.ts index 1a549d1041..f778080a6e 100644 --- a/src/common/enums/route.ts +++ b/src/common/enums/route.ts @@ -53,6 +53,7 @@ type ROUTE_KEY = | 'ME_HISTORY_LIKES_SENT' | 'ME_HISTORY_LIKES_RECEIVED' | 'ME_NOTIFICATIONS' + | 'ME_FEDIVERSE' | 'ME_ANALYTICS' | 'ME_WALLET' | 'ME_WALLET_TRANSACTIONS' @@ -106,6 +107,7 @@ export const PROTECTED_ROUTES: { { key: 'ME_HISTORY_LIKES_SENT', pathname: '/me/history/likes/sent' }, { key: 'ME_HISTORY_LIKES_RECEIVED', pathname: '/me/history/likes/received' }, { key: 'ME_NOTIFICATIONS', pathname: '/me/notifications' }, + { key: 'ME_FEDIVERSE', pathname: '/me/fediverse' }, { key: 'ME_WALLET', pathname: '/me/wallet' }, { key: 'ME_WALLET_TRANSACTIONS', pathname: '/me/wallet/transactions' }, { key: 'ME_ANALYTICS', pathname: '/me/analytics' }, diff --git a/src/common/utils/types/index.ts b/src/common/utils/types/index.ts index d8382f8c65..714869fc9b 100644 --- a/src/common/utils/types/index.ts +++ b/src/common/utils/types/index.ts @@ -105,6 +105,125 @@ export default gql` ): ArticleFederationSetting! } + extend type Query { + viewerFediverse: FediverseProfile! + fediverseArticle(input: FediverseArticleInput!): FediverseArticle! + fediverseRemoteActor( + input: FediverseRemoteActorInput! + ): FediverseRemoteActor! + } + + extend type Mutation { + actFediverse(input: FediverseActionInput!): FediverseActionResult! + refreshFediverseProfile: Boolean! + } + + type FediverseProfile { + actorId: String! + handle: String! + account: String! + displayName: String! + summary: String! + profileUrl: String! + avatarUrl: String + headerUrl: String + followersCount: Int! + followingCount: Int! + pendingFollowingCount: Int! + unreadNotificationsCount: Int! + maxFollowing: Int! + retentionDays: Int! + timelineMaxItems: Int! + following: [FediverseRemoteActor!]! + notifications: [FediverseNotification!]! + timeline: [FediversePost!]! + } + + type FediverseRemoteActor { + actorId: String! + account: String + preferredUsername: String + name: String + summary: String! + url: String! + avatarUrl: String + status: String + } + + type FediverseNotification { + id: ID! + category: String! + contentId: String + objectId: String + remoteActorIds: [String!]! + headline: String + preview: String + eventCount: Int! + unreadCount: Int! + publishedAt: DateTime + } + + type FediversePost { + objectId: String! + content: String! + summary: String! + url: String + inReplyTo: String + publishedAt: DateTime + remoteActor: FediverseRemoteActor! + } + + type FediverseArticle { + contentId: String + repliesCount: Int! + likesCount: Int! + announcesCount: Int! + notificationsCount: Int! + unreadNotificationsCount: Int! + replies: [FediversePost!]! + } + + type FediverseActionResult { + status: String! + mapping: String + activityId: String + remoteActorId: String + } + + enum FediverseAction { + follow + unfollow + reply + like + unlike + announce + unannounce + block + unblock + report + mark_read + } + + input FediverseArticleInput { + id: ID! + } + + input FediverseRemoteActorInput { + account: String + actorId: String + } + + input FediverseActionInput { + action: FediverseAction! + account: String + remoteActorId: String + objectId: String + content: String + activityId: String + notificationIds: [ID!] + reason: String + } + enum CommunityWatchRemoveCommentReason { porn_ad spam_ad diff --git a/src/components/Context/Viewer/index.tsx b/src/components/Context/Viewer/index.tsx index 529b5264e5..f2d7224311 100644 --- a/src/components/Context/Viewer/index.tsx +++ b/src/components/Context/Viewer/index.tsx @@ -60,6 +60,9 @@ const ViewerFragments = { followers(input: { first: 0 }) { totalCount } + federationSetting { + state + } } `, private: gql` diff --git a/src/components/Layout/GlobalNav/MeMenu/index.tsx b/src/components/Layout/GlobalNav/MeMenu/index.tsx index 7b5795625e..a45ef76f0b 100644 --- a/src/components/Layout/GlobalNav/MeMenu/index.tsx +++ b/src/components/Layout/GlobalNav/MeMenu/index.tsx @@ -14,6 +14,7 @@ import IconProfile from '@/public/static/icons/24px/profile.svg' import IconSave from '@/public/static/icons/24px/save.svg' import IconSettings from '@/public/static/icons/24px/settings.svg' import IconWallet from '@/public/static/icons/24px/wallet.svg' +import IconWorld from '@/public/static/icons/24px/world.svg' import { COOKIE_TOKEN_NAME, COOKIE_USER_GROUP, @@ -140,6 +141,13 @@ const MeMenu: React.FC = () => { is="link" /> + } + icon={} + href={PATHS.ME_FEDIVERSE} + is="link" + /> + = ({ active, iconSize = 24, @@ -28,15 +37,30 @@ const NotificationUnreadIcon: React.FC = ({ skip: !viewer.isAuthed || typeof window === 'undefined', } ) + const federationEnabled = viewer.federationSetting?.state === 'enabled' + const { data: federationData, startPolling: startFederationPolling } = + useQuery<{ + viewerFediverse: { unreadNotificationsCount: number } + }>(FEDIVERSE_UNREAD_COUNT, { + errorPolicy: 'ignore', + fetchPolicy: 'network-only', + skip: + !viewer.isAuthed || !federationEnabled || typeof window === 'undefined', + }) // FIXME: https://github.com/apollographql/apollo-client/issues/3775 useEffect(() => { if (viewer.isAuthed) { startPolling(1000 * 20) // 20s + if (federationEnabled) { + startFederationPolling(1000 * 20) + } } - }, []) + }, [federationEnabled]) - const unread = (data?.viewer?.status?.unreadNoticeCount || 0) >= 1 + const unread = + (data?.viewer?.status?.unreadNoticeCount || 0) >= 1 || + (federationData?.viewerFediverse.unreadNotificationsCount || 0) >= 1 const iconClasses = classNames({ [styles.unreadIcon]: true, [styles.unread]: unread, diff --git a/src/components/Layout/Header/MeButton/SideDrawerNav/DrawerContent/MeMenu/index.tsx b/src/components/Layout/Header/MeButton/SideDrawerNav/DrawerContent/MeMenu/index.tsx index d15474f60a..08b61caf4f 100644 --- a/src/components/Layout/Header/MeButton/SideDrawerNav/DrawerContent/MeMenu/index.tsx +++ b/src/components/Layout/Header/MeButton/SideDrawerNav/DrawerContent/MeMenu/index.tsx @@ -14,6 +14,7 @@ import IconProfile from '@/public/static/icons/24px/profile.svg' import IconSave from '@/public/static/icons/24px/save.svg' import IconSettings from '@/public/static/icons/24px/settings.svg' import IconWallet from '@/public/static/icons/24px/wallet.svg' +import IconWorld from '@/public/static/icons/24px/world.svg' import { COOKIE_TOKEN_NAME, COOKIE_USER_GROUP, @@ -109,6 +110,14 @@ const Top: React.FC = () => { href={PATHS.ME_ANALYTICS} is="link" /> + + } + icon={} + href={PATHS.ME_FEDIVERSE} + is="link" + /> ) } diff --git a/src/pages/me/fediverse.tsx b/src/pages/me/fediverse.tsx new file mode 100644 index 0000000000..91d0bcba6c --- /dev/null +++ b/src/pages/me/fediverse.tsx @@ -0,0 +1,10 @@ +import { Protected } from '~/components' +import Fediverse from '~/views/Me/Fediverse' + +const ProtectedFediverse = () => ( + + + +) + +export default ProtectedFediverse diff --git a/src/views/ArticleDetail/FediverseInteractions/index.tsx b/src/views/ArticleDetail/FediverseInteractions/index.tsx new file mode 100644 index 0000000000..cec768dbba --- /dev/null +++ b/src/views/ArticleDetail/FediverseInteractions/index.tsx @@ -0,0 +1,300 @@ +import { useMutation, useQuery } from '@apollo/client' +import gql from 'graphql-tag' +import { useState } from 'react' +import { FormattedMessage, useIntl } from 'react-intl' + +import { Button, toast } from '~/components' + +import styles from './styles.module.css' + +const ARTICLE_FEDIVERSE = gql` + query ArticleFediverse($input: FediverseArticleInput!) { + fediverseArticle(input: $input) { + contentId + repliesCount + likesCount + announcesCount + notificationsCount + unreadNotificationsCount + replies { + objectId + content + summary + url + publishedAt + remoteActor { + actorId + account + preferredUsername + name + avatarUrl + } + } + } + } +` + +const ACT_ARTICLE_FEDIVERSE = gql` + mutation ActArticleFediverse($input: FediverseActionInput!) { + actFediverse(input: $input) { + status + activityId + } + } +` + +type ArticleFediverseData = { + fediverseArticle: { + repliesCount: number + likesCount: number + announcesCount: number + notificationsCount: number + unreadNotificationsCount: number + replies: Array<{ + objectId: string + content: string + summary: string + url?: string | null + remoteActor: { + actorId: string + account?: string | null + preferredUsername?: string | null + name?: string | null + avatarUrl?: string | null + } + }> + } +} + +const plainText = (html: string) => + html + .replace(//gi, '\n') + .replace(/<[^>]*>/g, '') + .replace(/ /g, ' ') + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .trim() + +const FediverseInteractions = ({ + articleId, + enabled, +}: { + articleId: string + enabled: boolean +}) => { + const intl = useIntl() + const [replyDrafts, setReplyDrafts] = useState>({}) + const [busy, setBusy] = useState(null) + const { data, loading, error, refetch } = useQuery( + ARTICLE_FEDIVERSE, + { + variables: { input: { id: articleId } }, + skip: !enabled, + fetchPolicy: 'network-only', + } + ) + const [act] = useMutation(ACT_ARTICLE_FEDIVERSE) + + if (!enabled || loading || error || !data?.fediverseArticle) { + return null + } + + const social = data.fediverseArticle + + const runAction = async ({ + key, + input, + }: { + key: string + input: Record + }) => { + setBusy(key) + try { + await act({ variables: { input } }) + await refetch() + toast.success({ + message: intl.formatMessage({ + defaultMessage: '聯邦宇宙操作已送出', + id: 'SyxunU', + }), + }) + return true + } catch { + toast.error({ + message: intl.formatMessage({ + defaultMessage: '操作失敗,請稍後再試', + id: 'vXgChH', + }), + }) + return false + } finally { + setBusy(null) + } + } + + return ( +
+
+
+

+ +

+

+ +

+
+ {social.unreadNotificationsCount > 0 && ( + + {social.unreadNotificationsCount} new + + )} +
+
+ + {social.repliesCount} + + + + {social.likesCount} + + + + {social.announcesCount} + + +
+ {social.replies.length === 0 ? ( +

+ +

+ ) : ( + social.replies.map((reply) => { + const actor = reply.remoteActor + const name = + actor.name || + actor.preferredUsername || + actor.account || + actor.actorId + const draft = replyDrafts[reply.objectId] || '' + + return ( +
+
+ + {!actor.avatarUrl && name.slice(0, 1).toUpperCase()} + +
+ {name} + {actor.account || actor.actorId} +
+
+

{plainText(reply.content || reply.summary)}

+
+ + +
+
{ + event.preventDefault() + if (!draft.trim()) { + return + } + const sent = await runAction({ + key: `reply:${reply.objectId}`, + input: { + action: 'reply', + objectId: reply.objectId, + remoteActorId: actor.actorId, + content: draft.trim(), + }, + }) + if (sent) { + setReplyDrafts((current) => ({ + ...current, + [reply.objectId]: '', + })) + } + }} + > + + setReplyDrafts((current) => ({ + ...current, + [reply.objectId]: event.target.value, + })) + } + placeholder={intl.formatMessage({ + defaultMessage: '回覆到聯邦宇宙', + id: 'kw8J39', + })} + value={draft} + /> + +
+
+ ) + }) + )} +
+ ) +} + +export default FediverseInteractions diff --git a/src/views/ArticleDetail/FediverseInteractions/styles.module.css b/src/views/ArticleDetail/FediverseInteractions/styles.module.css new file mode 100644 index 0000000000..ea066b7a90 --- /dev/null +++ b/src/views/ArticleDetail/FediverseInteractions/styles.module.css @@ -0,0 +1,120 @@ +.container { + padding: var(--sp20); + margin-top: var(--sp24); + border: 1px solid var(--color-grey-lighter); + border-radius: 0.75rem; + + & > header { + display: flex; + align-items: flex-start; + justify-content: space-between; + + & h2 { + font-size: var(--font-size-18); + } + + & p { + margin-top: var(--sp4); + font-size: var(--font-size-14); + color: var(--color-grey-darker); + } + } +} + +.badge { + padding: var(--sp4) var(--sp8); + font-size: var(--font-size-12); + color: var(--color-white); + background: var(--color-red); + border-radius: 5rem; +} + +.metrics { + display: flex; + gap: var(--sp24); + padding: var(--sp16) 0; + margin-top: var(--sp16); + border-top: 1px solid var(--color-grey-lighter); + border-bottom: 1px solid var(--color-grey-lighter); + + & span { + display: flex; + gap: var(--sp4); + color: var(--color-grey-darker); + } + + & strong { + color: var(--color-black); + } +} + +.empty { + padding-top: var(--sp16); + color: var(--color-grey-darker); +} + +.reply { + padding: var(--sp20) 0; + border-bottom: 1px solid var(--color-grey-lighter); + + &:last-child { + padding-bottom: 0; + border-bottom: 0; + } + + & > p { + margin: var(--sp12) 0; + white-space: pre-wrap; + } +} + +.author { + display: flex; + gap: var(--sp12); + align-items: center; + + & > div { + display: flex; + flex-direction: column; + min-width: 0; + } + + & small { + overflow: hidden; + text-overflow: ellipsis; + color: var(--color-grey-darker); + white-space: nowrap; + } +} + +.avatar { + @mixin flex-center-all; + + flex: 0 0 auto; + width: 2.5rem; + height: 2.5rem; + color: var(--color-white); + background: var(--color-green); + background-position: center; + background-size: cover; + border-radius: 50%; +} + +.actions { + display: flex; + gap: var(--sp16); +} + +.replyForm { + display: flex; + gap: var(--sp8); + margin-top: var(--sp12); + + & input { + flex: 1; + min-width: 0; + padding: var(--sp8) var(--sp12); + border: 1px solid var(--color-grey-light); + border-radius: 0.5rem; + } +} diff --git a/src/views/ArticleDetail/index.tsx b/src/views/ArticleDetail/index.tsx index c8f0daf379..289fc620cc 100644 --- a/src/views/ArticleDetail/index.tsx +++ b/src/views/ArticleDetail/index.tsx @@ -57,6 +57,7 @@ import { CommentsDialog } from './Comments/CommentsDialog' import { Placeholder as CommentsPlaceholder } from './Comments/Placeholder' import Content from './Content' import CustomizedSummary from './CustomizedSummary' +import FediverseInteractions from './FediverseInteractions' import { ARTICLE_AVAILABLE_TRANSLATIONS, ARTICLE_DETAIL_PRIVATE, @@ -379,6 +380,8 @@ const BaseArticleDetail = ({ + + {features.payment && ( + html + .replace(//gi, '\n') + .replace(/<[^>]*>/g, '') + .replace(/ /g, ' ') + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .trim() + +const actorLabel = (actor: RemoteActor) => + actor.name || actor.preferredUsername || actor.account || actor.actorId + +const ActorAvatar = ({ actor }: { actor: RemoteActor }) => ( + + {!actor.avatarUrl && actorLabel(actor).slice(0, 1).toUpperCase()} + +) + +const Fediverse = () => { + const intl = useIntl() + const [search, setSearch] = useState('') + const [replyDrafts, setReplyDrafts] = useState>({}) + const [busyKey, setBusyKey] = useState(null) + const { data, loading, error, refetch } = useQuery( + VIEWER_FEDIVERSE, + { fetchPolicy: 'network-only' } + ) + const [resolveActor, actorResult] = useLazyQuery( + FEDIVERSE_REMOTE_ACTOR, + { fetchPolicy: 'network-only' } + ) + const [act] = useMutation(ACT_FEDIVERSE) + const [refreshProfile] = useMutation(REFRESH_FEDIVERSE_PROFILE) + + useEffect(() => { + refreshProfile() + .then(() => refetch()) + .catch(() => undefined) + }, []) + + const runAction = async (input: ActionInput, key: string) => { + setBusyKey(key) + try { + await act({ variables: { input } }) + await refetch() + toast.success({ + message: intl.formatMessage({ + defaultMessage: '聯邦宇宙操作已送出', + id: 'SyxunU', + }), + }) + return true + } catch { + toast.error({ + message: intl.formatMessage({ + defaultMessage: '操作失敗,請稍後再試', + id: 'vXgChH', + }), + }) + return false + } finally { + setBusyKey(null) + } + } + + const searchActor = async (event: React.FormEvent) => { + event.preventDefault() + const account = search.trim() + if (!account) { + return + } + await resolveActor({ variables: { input: { account } } }) + } + + const markNotificationsRead = async () => { + const ids = + data?.viewerFediverse.notifications + .filter((notification) => notification.unreadCount > 0) + .map((notification) => notification.id) ?? [] + if (ids.length === 0) { + return + } + await runAction( + { action: 'mark_read', notificationIds: ids }, + 'notifications' + ) + } + + const reportPost = async (post: FediversePost) => { + const reason = window.prompt( + intl.formatMessage({ + defaultMessage: '請簡述檢舉原因', + id: 'W1j+F0', + }) + ) + if (!reason?.trim()) { + return + } + await runAction( + { + action: 'report', + remoteActorId: post.remoteActor.actorId, + objectId: post.objectId, + reason: reason.trim(), + }, + `report:${post.objectId}` + ) + } + + const Header = () => ( + <> + + + + } + /> + + + ) + + if (loading) { + return ( + +
+ + + ) + } + + if (error || !data?.viewerFediverse) { + return ( + +
+ {error ? : null} + + ) + } + + const profile = data.viewerFediverse + const foundActor = actorResult.data?.fediverseRemoteActor + + return ( + +
+ +
+
+
+ + {!profile.avatarUrl && + profile.displayName.slice(0, 1).toUpperCase()} + +
+

{profile.displayName}

+ + @{profile.account} + + {profile.summary &&

{plainText(profile.summary)}

} +
+
+
+ + {profile.followersCount} + + + + {profile.followingCount} + + + + {profile.pendingFollowingCount} + + +
+
+ +
+

+ +

+

+ +

+
+ setSearch(event.target.value)} + placeholder="@alice@example.social" + value={search} + /> + +
+ {actorResult.error && ( +

+ +

+ )} + {foundActor && ( +
+ +
+ {actorLabel(foundActor)} + {foundActor.account || foundActor.actorId} +
+ +
+ )} +
+ +
+

+ +

+ {profile.following.length === 0 ? ( +

+ +

+ ) : ( + profile.following.map((actor) => ( +
+ +
+ {actorLabel(actor)} + {actor.account || actor.actorId} +
+ +
+ )) + )} +
+ +
+
+

+ + {profile.unreadNotificationsCount > 0 && ( + + {profile.unreadNotificationsCount} + + )} +

+ {profile.unreadNotificationsCount > 0 && ( + + )} +
+ {profile.notifications.length === 0 ? ( +

+ +

+ ) : ( + profile.notifications.map((notification) => ( +
+
+ + {notification.headline || notification.category} + + {notification.preview && ( +

{plainText(notification.preview)}

+ )} +
+ + {notification.eventCount} + {notification.unreadCount > 0 && ' · new'} + +
+ )) + )} +
+ +
+

+ +

+ {profile.timeline.length === 0 ? ( +

+ +

+ ) : ( + profile.timeline.map((post) => ( +
+
+ +
+ {actorLabel(post.remoteActor)} + + {post.remoteActor.account || post.remoteActor.actorId} + +
+
+

+ {plainText(post.content || post.summary)} +

+ {post.url && ( + + + + )} +
+ + + + +
+
{ + event.preventDefault() + const content = replyDrafts[post.objectId]?.trim() + if (!content) { + return + } + const sent = await runAction( + { + action: 'reply', + objectId: post.objectId, + remoteActorId: post.remoteActor.actorId, + content, + }, + `reply:${post.objectId}` + ) + if (sent) { + setReplyDrafts((current) => ({ + ...current, + [post.objectId]: '', + })) + } + }} + > + + setReplyDrafts((current) => ({ + ...current, + [post.objectId]: event.target.value, + })) + } + placeholder={intl.formatMessage({ + defaultMessage: '回覆到聯邦宇宙', + id: 'kw8J39', + })} + value={replyDrafts[post.objectId] || ''} + /> + +
+
+ )) + )} +

+ +

+
+
+ + ) +} + +export default Fediverse diff --git a/src/views/Me/Fediverse/styles.module.css b/src/views/Me/Fediverse/styles.module.css new file mode 100644 index 0000000000..bc58ba3389 --- /dev/null +++ b/src/views/Me/Fediverse/styles.module.css @@ -0,0 +1,247 @@ +.profile, +.section { + margin-bottom: var(--sp24); + overflow: hidden; + background: var(--color-white); + border: 1px solid var(--color-grey-lighter); + border-radius: 0.75rem; +} + +.headerImage { + height: 8rem; + background-color: var(--color-green-lighter); + background-position: center; + background-size: cover; +} + +.profileBody { + display: flex; + gap: var(--sp16); + align-items: flex-start; + padding: 0 var(--sp20) var(--sp20); +} + +.profileAvatar { + @mixin flex-center-all; + + flex: 0 0 auto; + width: 5rem; + height: 5rem; + margin-top: -2.5rem; + overflow: hidden; + font-size: 2rem; + color: var(--color-white); + background-color: var(--color-green); + background-position: center; + background-size: cover; + border: 4px solid var(--color-white); + border-radius: 50%; +} + +.profileText { + min-width: 0; + padding-top: var(--sp12); + + & h2 { + font-size: var(--font-size-18); + } + + & a, + & span { + color: var(--color-grey-darker); + overflow-wrap: anywhere; + } + + & p { + margin-top: var(--sp8); + white-space: pre-wrap; + } +} + +.metrics { + display: flex; + gap: var(--sp24); + padding: var(--sp16) var(--sp20); + border-top: 1px solid var(--color-grey-lighter); + + & span { + display: flex; + gap: var(--sp4); + font-size: var(--font-size-14); + color: var(--color-grey-darker); + } + + & strong { + color: var(--color-black); + } +} + +.section { + padding: var(--sp20); + + & > h2, + & .sectionHeader h2 { + font-size: var(--font-size-18); + } +} + +.sectionHeader { + display: flex; + align-items: center; + justify-content: space-between; +} + +.hint, +.empty, +.retention { + margin-top: var(--sp8); + font-size: var(--font-size-14); + color: var(--color-grey-darker); +} + +.search, +.reply { + display: flex; + gap: var(--sp8); + margin-top: var(--sp12); + + & input { + flex: 1; + min-width: 0; + padding: var(--sp10) var(--sp12); + background: var(--color-white); + border: 1px solid var(--color-grey-light); + border-radius: 0.5rem; + } +} + +.error { + margin-top: var(--sp8); + color: var(--color-red); +} + +.actorRow { + display: flex; + gap: var(--sp12); + align-items: center; + padding: var(--sp16) 0; + border-bottom: 1px solid var(--color-grey-lighter); + + &:last-child { + padding-bottom: 0; + border-bottom: 0; + } +} + +.avatar { + @mixin flex-center-all; + + flex: 0 0 auto; + width: 2.75rem; + height: 2.75rem; + color: var(--color-white); + background-color: var(--color-green); + background-position: center; + background-size: cover; + border-radius: 50%; +} + +.actorText { + display: flex; + flex: 1; + flex-direction: column; + min-width: 0; + + & strong, + & span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + & span { + font-size: var(--font-size-12); + color: var(--color-grey-darker); + } +} + +.badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 1.25rem; + height: 1.25rem; + padding: 0 var(--sp4); + margin-left: var(--sp8); + font-size: var(--font-size-12); + color: var(--color-white); + background: var(--color-red); + border-radius: 5rem; +} + +.notification { + display: flex; + gap: var(--sp16); + justify-content: space-between; + padding: var(--sp16) 0; + border-bottom: 1px solid var(--color-grey-lighter); + + & p { + margin-top: var(--sp4); + color: var(--color-grey-darker); + } + + & > span { + color: var(--color-green); + white-space: nowrap; + } +} + +.post { + padding: var(--sp20) 0; + border-bottom: 1px solid var(--color-grey-lighter); + + & header { + display: flex; + gap: var(--sp12); + align-items: center; + } +} + +.postContent { + margin: var(--sp12) 0; + white-space: pre-wrap; +} + +.postLink { + font-size: var(--font-size-14); + color: var(--color-green); +} + +.actions { + display: flex; + flex-wrap: wrap; + gap: var(--sp16); + margin-top: var(--sp12); +} + +.retention { + padding-top: var(--sp16); + text-align: center; +} + +@media (--sm-down) { + .metrics { + gap: var(--sp12); + justify-content: space-between; + + & span { + flex-direction: column; + gap: 0; + } + } + + .section { + padding: var(--sp16); + } +} diff --git a/src/views/Me/Notifications/index.tsx b/src/views/Me/Notifications/index.tsx index a032cf4a6f..f94537d7ad 100644 --- a/src/views/Me/Notifications/index.tsx +++ b/src/views/Me/Notifications/index.tsx @@ -3,8 +3,10 @@ import gql from 'graphql-tag' import { useContext, useEffect } from 'react' import { FormattedMessage, useIntl } from 'react-intl' +import { PATHS } from '~/common/enums' import { mergeConnections, shouldRenderNode } from '~/common/utils' import { + Button, EmptyNotice, Head, InfiniteScroll, @@ -25,6 +27,8 @@ import { MeNotificationsQuery, } from '~/gql/graphql' +import styles from './styles.module.css' + const renderableTypes = new Set([ 'ArticleArticleNotice', 'CircleNotice', @@ -77,8 +81,34 @@ const MARK_ALL_NOTICES_AS_READ = gql` } ` +const FEDIVERSE_NOTIFICATIONS = gql` + query FediverseNotificationsDigest { + viewerFediverse { + unreadNotificationsCount + notifications { + id + category + headline + preview + eventCount + unreadCount + publishedAt + } + } + } +` + +const MARK_FEDIVERSE_NOTIFICATIONS_READ = gql` + mutation MarkFediverseNotificationsRead($input: FediverseActionInput!) { + actFediverse(input: $input) { + status + } + } +` + const BaseNotifications = () => { const viewer = useContext(ViewerContext) + const federationEnabled = viewer.federationSetting?.state === 'enabled' const [markAllNoticesAsRead] = useMutation( MARK_ALL_NOTICES_AS_READ, @@ -102,11 +132,48 @@ const BaseNotifications = () => { >(ME_NOTIFICATIONS, { fetchPolicy: 'network-only', }) + const { data: federationData } = useQuery<{ + viewerFediverse: { + unreadNotificationsCount: number + notifications: Array<{ + id: string + category: string + headline?: string | null + preview?: string | null + eventCount: number + unreadCount: number + }> + } + }>(FEDIVERSE_NOTIFICATIONS, { + errorPolicy: 'ignore', + fetchPolicy: 'network-only', + skip: !federationEnabled, + }) + const [markFediverseNotificationsRead] = useMutation( + MARK_FEDIVERSE_NOTIFICATIONS_READ + ) useEffect(() => { markAllNoticesAsRead() }, []) + useEffect(() => { + const ids = + federationData?.viewerFediverse.notifications + .filter((notification) => notification.unreadCount > 0) + .map((notification) => notification.id) ?? [] + if (ids.length > 0) { + markFediverseNotificationsRead({ + variables: { + input: { + action: 'mark_read', + notificationIds: ids, + }, + }, + }) + } + }, [federationData?.viewerFediverse.notifications]) + const connectionPath = 'viewer.notices' const { edges, pageInfo } = data?.viewer?.notices || {} @@ -114,12 +181,21 @@ const BaseNotifications = () => { return } - if (!edges || edges.length <= 0 || !pageInfo) { + const federationNotifications = + federationData?.viewerFediverse.notifications ?? [] + + if ( + (!edges || edges.length <= 0 || !pageInfo) && + federationNotifications.length === 0 + ) { return } - const loadMore = () => - fetchMore({ + const loadMore = () => { + if (!pageInfo) { + return Promise.resolve() + } + return fetchMore({ variables: { first: 20, after: pageInfo.endCursor }, updateQuery: (previousResult, { fetchMoreResult }) => mergeConnections({ @@ -128,20 +204,60 @@ const BaseNotifications = () => { path: connectionPath, }), }) + } return ( - - - {edges.map( - ({ node }) => - shouldRenderNode(node, renderableTypes) && ( - - - - ) - )} - - + <> + {federationNotifications.length > 0 && ( +
+
+
+

+ +

+

+ +

+
+ +
+ {federationNotifications.slice(0, 5).map((notification) => ( +
+
+ + {notification.headline || notification.category} + + {notification.preview &&

{notification.preview}

} +
+ {notification.eventCount} +
+ ))} +
+ )} + {edges && pageInfo && ( + + + {edges.map( + ({ node }) => + shouldRenderNode(node, renderableTypes) && ( + + + + ) + )} + + + )} + ) } diff --git a/src/views/Me/Notifications/styles.module.css b/src/views/Me/Notifications/styles.module.css new file mode 100644 index 0000000000..cfcb1c1a76 --- /dev/null +++ b/src/views/Me/Notifications/styles.module.css @@ -0,0 +1,40 @@ +.fediverse { + padding: var(--sp16); + margin-bottom: var(--sp24); + border: 1px solid var(--color-grey-lighter); + border-radius: 0.75rem; + + & > header { + display: flex; + align-items: flex-start; + justify-content: space-between; + margin-bottom: var(--sp8); + + & h2 { + font-size: var(--font-size-18); + } + + & p { + margin-top: var(--sp4); + font-size: var(--font-size-14); + color: var(--color-grey-darker); + } + } +} + +.fediverseItem { + display: flex; + gap: var(--sp16); + justify-content: space-between; + padding: var(--sp12) 0; + border-top: 1px solid var(--color-grey-lighter); + + & p { + margin-top: var(--sp4); + color: var(--color-grey-darker); + } + + & > span { + color: var(--color-green); + } +} diff --git a/src/views/Me/Settings/Misc/FederationSetting.tsx b/src/views/Me/Settings/Misc/FederationSetting.tsx index 493f1a1599..86cce981df 100644 --- a/src/views/Me/Settings/Misc/FederationSetting.tsx +++ b/src/views/Me/Settings/Misc/FederationSetting.tsx @@ -3,6 +3,7 @@ import gql from 'graphql-tag' import { useEffect, useState } from 'react' import { FormattedMessage, useIntl } from 'react-intl' +import { PATHS } from '~/common/enums' import { Switch, TableView, toast, useMutation } from '~/components' import { FederationAuthorSettingState, @@ -93,28 +94,48 @@ const FederationSetting = () => { } return ( - - } - subtitle={ - - } - right={ - + <> + + } + subtitle={ + + } + right={ + + } + checked={enabled} + loading={loading || saving} + onChange={updateSetting} + /> + } + /> + {enabled && ( + } - checked={enabled} - loading={loading || saving} - onChange={updateSetting} + subtitle={ + + } + href={PATHS.ME_FEDIVERSE} /> - } - /> + )} + ) } From 142576b5918fafe8f61bfc3f00098642a0d30b28 Mon Sep 17 00:00:00 2001 From: Mashbean Date: Thu, 23 Jul 2026 16:09:37 +0800 Subject: [PATCH 2/2] Make Fediverse interactions reversible --- lang/default.json | 6 + lang/en.json | 6 + lang/zh-Hans.json | 6 + lang/zh-Hant.json | 6 + src/common/utils/types/index.ts | 172 +----------------- .../GlobalNav/UnreadIcon/Notification.tsx | 44 +++-- src/views/Me/Fediverse/gql.ts | 4 + src/views/Me/Fediverse/index.tsx | 25 ++- 8 files changed, 85 insertions(+), 184 deletions(-) diff --git a/lang/default.json b/lang/default.json index a834ecbf42..1867ad5434 100644 --- a/lang/default.json +++ b/lang/default.json @@ -159,6 +159,9 @@ "/wbyoV": { "defaultMessage": "The article has been removed from the event" }, + "0/FjWm": { + "defaultMessage": "取消喜歡" + }, "0/gRer": { "defaultMessage": "I am migrating to Matters, and I invite you to come along" }, @@ -2995,6 +2998,9 @@ "defaultMessage": "Leave a comment?", "description": "src/views/ArticleDetail/Toolbar/FixedToolbar/index.tsx" }, + "gLotOL": { + "defaultMessage": "取消轉發" + }, "gN4jLB": { "defaultMessage": "Your work {articleTitle} has been republished to decentralized network", "description": "src/components/Notice/ArticleNotice/RevisedArticlePublishedNotice.tsx" diff --git a/lang/en.json b/lang/en.json index 4e61989c92..77bba0ab84 100644 --- a/lang/en.json +++ b/lang/en.json @@ -159,6 +159,9 @@ "/wbyoV": { "defaultMessage": "The article has been removed from the event" }, + "0/FjWm": { + "defaultMessage": "Unlike" + }, "0/gRer": { "defaultMessage": "I am migrating to Matters, and I invite you to come along" }, @@ -2995,6 +2998,9 @@ "defaultMessage": "Leave a comment?", "description": "src/views/ArticleDetail/Toolbar/FixedToolbar/index.tsx" }, + "gLotOL": { + "defaultMessage": "Undo repost" + }, "gN4jLB": { "defaultMessage": "Your work {articleTitle} has been republished to decentralized network", "description": "src/components/Notice/ArticleNotice/RevisedArticlePublishedNotice.tsx" diff --git a/lang/zh-Hans.json b/lang/zh-Hans.json index f45436678c..ff3506c665 100644 --- a/lang/zh-Hans.json +++ b/lang/zh-Hans.json @@ -159,6 +159,9 @@ "/wbyoV": { "defaultMessage": "作品已从活动移除" }, + "0/FjWm": { + "defaultMessage": "取消喜欢" + }, "0/gRer": { "defaultMessage": "我正在搬家到 Matters,邀请你一起来" }, @@ -2994,6 +2997,9 @@ "defaultMessage": "留个友善的评论?", "description": "src/views/ArticleDetail/Toolbar/FixedToolbar/index.tsx" }, + "gLotOL": { + "defaultMessage": "取消转发" + }, "gN4jLB": { "defaultMessage": "你对 {articleTitle} 的修改已发布到 IPFS", "description": "src/components/Notice/ArticleNotice/RevisedArticlePublishedNotice.tsx" diff --git a/lang/zh-Hant.json b/lang/zh-Hant.json index c09bd21940..02156d98b2 100644 --- a/lang/zh-Hant.json +++ b/lang/zh-Hant.json @@ -159,6 +159,9 @@ "/wbyoV": { "defaultMessage": "作品已從活動移除" }, + "0/FjWm": { + "defaultMessage": "取消喜歡" + }, "0/gRer": { "defaultMessage": "我正在搬家到 Matters,邀請你一起來" }, @@ -2994,6 +2997,9 @@ "defaultMessage": "留個友善的評論?", "description": "src/views/ArticleDetail/Toolbar/FixedToolbar/index.tsx" }, + "gLotOL": { + "defaultMessage": "取消轉發" + }, "gN4jLB": { "defaultMessage": "你對 {articleTitle} 的修改已發佈到 IPFS", "description": "src/components/Notice/ArticleNotice/RevisedArticlePublishedNotice.tsx" diff --git a/src/common/utils/types/index.ts b/src/common/utils/types/index.ts index 714869fc9b..24fcf5244a 100644 --- a/src/common/utils/types/index.ts +++ b/src/common/utils/types/index.ts @@ -55,173 +55,17 @@ export default gql` community_watch } - enum FederationAuthorSettingState { - enabled - disabled - } - - enum FederationArticleSettingState { - inherit - enabled - disabled - } - - type UserFederationSetting { - userId: ID! - state: FederationAuthorSettingState! - updatedBy: ID - } - - type ArticleFederationSetting { - articleId: ID! - state: FederationArticleSettingState! - updatedBy: ID - } - - extend type User { - federationSetting: UserFederationSetting - } - - extend type Article { - federationSetting: ArticleFederationSetting - } - - input SetViewerFederationSettingInput { - state: FederationAuthorSettingState! - } - - input SetArticleFederationSettingInput { - id: ID! - state: FederationArticleSettingState! - } - - extend type Mutation { - setViewerFederationSetting( - input: SetViewerFederationSettingInput! - ): UserFederationSetting! - - setArticleFederationSetting( - input: SetArticleFederationSettingInput! - ): ArticleFederationSetting! - } - + # Temporary schema extension for the Fediverse interaction hardening release. + # Remove these fields after the matching matters-server schema is deployed. extend type Query { - viewerFediverse: FediverseProfile! - fediverseArticle(input: FediverseArticleInput!): FediverseArticle! - fediverseRemoteActor( - input: FediverseRemoteActorInput! - ): FediverseRemoteActor! - } - - extend type Mutation { - actFediverse(input: FediverseActionInput!): FediverseActionResult! - refreshFediverseProfile: Boolean! - } - - type FediverseProfile { - actorId: String! - handle: String! - account: String! - displayName: String! - summary: String! - profileUrl: String! - avatarUrl: String - headerUrl: String - followersCount: Int! - followingCount: Int! - pendingFollowingCount: Int! - unreadNotificationsCount: Int! - maxFollowing: Int! - retentionDays: Int! - timelineMaxItems: Int! - following: [FediverseRemoteActor!]! - notifications: [FediverseNotification!]! - timeline: [FediversePost!]! - } - - type FediverseRemoteActor { - actorId: String! - account: String - preferredUsername: String - name: String - summary: String! - url: String! - avatarUrl: String - status: String - } - - type FediverseNotification { - id: ID! - category: String! - contentId: String - objectId: String - remoteActorIds: [String!]! - headline: String - preview: String - eventCount: Int! - unreadCount: Int! - publishedAt: DateTime - } - - type FediversePost { - objectId: String! - content: String! - summary: String! - url: String - inReplyTo: String - publishedAt: DateTime - remoteActor: FediverseRemoteActor! - } - - type FediverseArticle { - contentId: String - repliesCount: Int! - likesCount: Int! - announcesCount: Int! - notificationsCount: Int! - unreadNotificationsCount: Int! - replies: [FediversePost!]! - } - - type FediverseActionResult { - status: String! - mapping: String - activityId: String - remoteActorId: String - } - - enum FediverseAction { - follow - unfollow - reply - like - unlike - announce - unannounce - block - unblock - report - mark_read - } - - input FediverseArticleInput { - id: ID! - } - - input FediverseRemoteActorInput { - account: String - actorId: String + viewerFediverseUnreadCount: Int! } - input FediverseActionInput { - action: FediverseAction! - account: String - remoteActorId: String - objectId: String - content: String - activityId: String - notificationIds: [ID!] - reason: String + extend type FediversePost { + liked: Boolean! + announced: Boolean! + likeActivityId: String + announceActivityId: String } enum CommunityWatchRemoveCommentReason { diff --git a/src/components/Layout/GlobalNav/UnreadIcon/Notification.tsx b/src/components/Layout/GlobalNav/UnreadIcon/Notification.tsx index 566e621625..14b45dfabc 100644 --- a/src/components/Layout/GlobalNav/UnreadIcon/Notification.tsx +++ b/src/components/Layout/GlobalNav/UnreadIcon/Notification.tsx @@ -18,9 +18,7 @@ interface UnreadIconProps { const FEDIVERSE_UNREAD_COUNT = gql` query FediverseUnreadCount { - viewerFediverse { - unreadNotificationsCount - } + viewerFediverseUnreadCount } ` @@ -29,7 +27,7 @@ const NotificationUnreadIcon: React.FC = ({ iconSize = 24, }) => { const viewer = useContext(ViewerContext) - const { data, startPolling } = useQuery( + const { data, startPolling, stopPolling } = useQuery( UNREAD_NOTICE_COUNT, { errorPolicy: 'ignore', @@ -38,29 +36,43 @@ const NotificationUnreadIcon: React.FC = ({ } ) const federationEnabled = viewer.federationSetting?.state === 'enabled' - const { data: federationData, startPolling: startFederationPolling } = - useQuery<{ - viewerFediverse: { unreadNotificationsCount: number } - }>(FEDIVERSE_UNREAD_COUNT, { - errorPolicy: 'ignore', - fetchPolicy: 'network-only', - skip: - !viewer.isAuthed || !federationEnabled || typeof window === 'undefined', - }) + const { + data: federationData, + startPolling: startFederationPolling, + stopPolling: stopFederationPolling, + } = useQuery<{ + viewerFediverseUnreadCount: number + }>(FEDIVERSE_UNREAD_COUNT, { + errorPolicy: 'ignore', + fetchPolicy: 'network-only', + skip: + !viewer.isAuthed || !federationEnabled || typeof window === 'undefined', + }) // FIXME: https://github.com/apollographql/apollo-client/issues/3775 useEffect(() => { if (viewer.isAuthed) { startPolling(1000 * 20) // 20s if (federationEnabled) { - startFederationPolling(1000 * 20) + startFederationPolling(1000 * 60) // 60s } } - }, [federationEnabled]) + return () => { + stopPolling() + stopFederationPolling() + } + }, [ + federationEnabled, + startFederationPolling, + startPolling, + stopFederationPolling, + stopPolling, + viewer.isAuthed, + ]) const unread = (data?.viewer?.status?.unreadNoticeCount || 0) >= 1 || - (federationData?.viewerFediverse.unreadNotificationsCount || 0) >= 1 + (federationData?.viewerFediverseUnreadCount || 0) >= 1 const iconClasses = classNames({ [styles.unreadIcon]: true, [styles.unread]: unread, diff --git a/src/views/Me/Fediverse/gql.ts b/src/views/Me/Fediverse/gql.ts index dffa57e1c7..ebe127083b 100644 --- a/src/views/Me/Fediverse/gql.ts +++ b/src/views/Me/Fediverse/gql.ts @@ -53,6 +53,10 @@ export const VIEWER_FEDIVERSE = gql` url inReplyTo publishedAt + liked + announced + likeActivityId + announceActivityId remoteActor { ...FediverseRemoteActorFields } diff --git a/src/views/Me/Fediverse/index.tsx b/src/views/Me/Fediverse/index.tsx index ba80077a2a..557d5d2506 100644 --- a/src/views/Me/Fediverse/index.tsx +++ b/src/views/Me/Fediverse/index.tsx @@ -37,6 +37,10 @@ type FediversePost = { url?: string | null inReplyTo?: string | null publishedAt?: string | null + liked: boolean + announced: boolean + likeActivityId?: string | null + announceActivityId?: string | null remoteActor: RemoteActor } @@ -82,7 +86,9 @@ type FediverseAction = | 'unfollow' | 'reply' | 'like' + | 'unlike' | 'announce' + | 'unannounce' | 'block' | 'report' | 'mark_read' @@ -93,6 +99,7 @@ type ActionInput = { remoteActorId?: string objectId?: string content?: string + activityId?: string notificationIds?: string[] reason?: string } @@ -497,32 +504,42 @@ const Fediverse = () => { onClick={() => runAction( { - action: 'like', + action: post.liked ? 'unlike' : 'like', objectId: post.objectId, remoteActorId: post.remoteActor.actorId, + activityId: post.likeActivityId || undefined, }, `like:${post.objectId}` ) } textColor="green" > - + {post.liked ? ( + + ) : ( + + )}