Bluesky app fork with some witchin' additions 💫

feat: buttons to open external mastodon post + open in pdsls

source:
https://github.com/m-doescode/antlers-social/commit/f45a34d4932bdc25ec6eb014e61d1fdfb1acf226

authored by maelstrom.dev and committed by xan.lol 28550f09 0cb096c0

verified
+197 -25
+42
src/components/PostControls/ShareMenu/ShareMenuItems.tsx
··· 5 5 import {useLingui} from '@lingui/react' 6 6 import {useNavigation} from '@react-navigation/native' 7 7 8 + import {useOpenLink} from '#/lib/hooks/useOpenLink' 8 9 import {makeProfileLink} from '#/lib/routes/links' 9 10 import {type NavigationProp} from '#/lib/routes/types' 10 11 import {shareText, shareUrl} from '#/lib/sharing' ··· 13 14 import {isIOS} from '#/platform/detection' 14 15 import {useAgeAssurance} from '#/state/ageAssurance/useAgeAssurance' 15 16 import {useProfileShadow} from '#/state/cache/profile-shadow' 17 + import {useShowExternalShareButtons} from '#/state/preferences/external-share-buttons' 16 18 import {useSession} from '#/state/session' 17 19 import * as Toast from '#/view/com/util/Toast' 18 20 import {atoms as a} from '#/alf' ··· 23 25 import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink' 24 26 import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard' 25 27 import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlaneIcon} from '#/components/icons/PaperPlane' 28 + import {SquareArrowTopRight_Stroke2_Corner0_Rounded as ExternalIcon} from '#/components/icons/SquareArrowTopRight' 26 29 import * as Menu from '#/components/Menu' 27 30 import {useDevMode} from '#/storage/hooks/dev-mode' 28 31 import {RecentChats} from './RecentChats' ··· 38 41 const sendViaChatControl = useDialogControl() 39 42 const [devModeEnabled] = useDevMode() 40 43 const {isAgeRestricted} = useAgeAssurance() 44 + const openLink = useOpenLink() 41 45 42 46 const postUri = post.uri 43 47 const postAuthor = useProfileShadow(post.author) ··· 108 112 shareText(postAuthor.did) 109 113 } 110 114 115 + const showExternalShareButtons = useShowExternalShareButtons() 116 + const isBridgyPost = !!post.record.bridgyOriginalUrl 117 + const bridgyOriginalUrl = post.record.bridgyOriginalUrl as string | undefined 118 + 119 + const onOpenOriginalPost = () => { 120 + bridgyOriginalUrl && openLink(bridgyOriginalUrl, true) 121 + } 122 + 123 + const onOpenPostInPdsls = () => { 124 + openLink(`https://pdsls.dev/${post.uri}`, true) 125 + } 126 + 111 127 return ( 112 128 <> 113 129 <Menu.Outer> ··· 127 143 <Trans>Send via direct message</Trans> 128 144 </Menu.ItemText> 129 145 <Menu.ItemIcon icon={PaperPlaneIcon} position="right" /> 146 + </Menu.Item> 147 + </Menu.Group> 148 + )} 149 + 150 + {showExternalShareButtons && ( 151 + <Menu.Group> 152 + {isBridgyPost && ( 153 + <Menu.Item 154 + testID="postDropdownOpenOriginalPost" 155 + label={_(msg`Open original post`)} 156 + onPress={onOpenOriginalPost}> 157 + <Menu.ItemText> 158 + <Trans>Open original post</Trans> 159 + </Menu.ItemText> 160 + <Menu.ItemIcon icon={ExternalIcon} position="right" /> 161 + </Menu.Item> 162 + )} 163 + 164 + <Menu.Item 165 + testID="postDropdownOpenInPdsls" 166 + label={_(msg`Open post in pdsls`)} 167 + onPress={onOpenPostInPdsls}> 168 + <Menu.ItemText> 169 + <Trans>Open post in pdsls</Trans> 170 + </Menu.ItemText> 171 + <Menu.ItemIcon icon={ExternalIcon} position="right" /> 130 172 </Menu.Item> 131 173 </Menu.Group> 132 174 )}
+40
src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx
··· 4 4 import {useLingui} from '@lingui/react' 5 5 import {useNavigation} from '@react-navigation/native' 6 6 7 + import {useOpenLink} from '#/lib/hooks/useOpenLink' 7 8 import {makeProfileLink} from '#/lib/routes/links' 8 9 import {type NavigationProp} from '#/lib/routes/types' 9 10 import {shareText, shareUrl} from '#/lib/sharing' ··· 12 13 import {isWeb} from '#/platform/detection' 13 14 import {useAgeAssurance} from '#/state/ageAssurance/useAgeAssurance' 14 15 import {useProfileShadow} from '#/state/cache/profile-shadow' 16 + import {useShowExternalShareButtons} from '#/state/preferences/external-share-buttons' 15 17 import {useSession} from '#/state/session' 16 18 import {useBreakpoints} from '#/alf' 17 19 import {useDialogControl} from '#/components/Dialog' ··· 21 23 import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard' 22 24 import {CodeBrackets_Stroke2_Corner0_Rounded as CodeBracketsIcon} from '#/components/icons/CodeBrackets' 23 25 import {PaperPlane_Stroke2_Corner0_Rounded as Send} from '#/components/icons/PaperPlane' 26 + import {SquareArrowTopRight_Stroke2_Corner0_Rounded as ExternalIcon} from '#/components/icons/SquareArrowTopRight' 24 27 import * as Menu from '#/components/Menu' 25 28 import {useDevMode} from '#/storage/hooks/dev-mode' 26 29 import {type ShareMenuItemsProps} from './ShareMenuItems.types' ··· 39 42 const sendViaChatControl = useDialogControl() 40 43 const [devModeEnabled] = useDevMode() 41 44 const {isAgeRestricted} = useAgeAssurance() 45 + const openLink = useOpenLink() 42 46 43 47 const postUri = post.uri 44 48 const postCid = post.cid ··· 87 91 shareText(postAuthor.did) 88 92 } 89 93 94 + const showExternalShareButtons = useShowExternalShareButtons() 95 + const isBridgyPost = !!record.bridgyOriginalUrl 96 + const bridgyOriginalUrl = record.bridgyOriginalUrl as string | undefined 97 + 98 + const onOpenOriginalPost = () => { 99 + bridgyOriginalUrl && openLink(bridgyOriginalUrl, true) 100 + } 101 + 102 + const onOpenPostInPdsls = () => { 103 + openLink(`https://pdsls.dev/${post.uri}`, true) 104 + } 105 + 90 106 const copyLinkItem = ( 91 107 <Menu.Group> 92 108 <Menu.Item ··· 114 130 <> 115 131 <Menu.Outer> 116 132 {copyLinkItem} 133 + 134 + {showExternalShareButtons && isBridgyPost && ( 135 + <Menu.Item 136 + testID="postDropdownOpenOriginalPost" 137 + label={_(msg`Open original post`)} 138 + onPress={onOpenOriginalPost}> 139 + <Menu.ItemText> 140 + <Trans>Open original post</Trans> 141 + </Menu.ItemText> 142 + <Menu.ItemIcon icon={ExternalIcon} position="right" /> 143 + </Menu.Item> 144 + )} 145 + 146 + {showExternalShareButtons && ( 147 + <Menu.Item 148 + testID="postDropdownOpenInPdsls" 149 + label={_(msg`Open post in pdsls`)} 150 + onPress={onOpenPostInPdsls}> 151 + <Menu.ItemText> 152 + <Trans>Open post in pdsls</Trans> 153 + </Menu.ItemText> 154 + <Menu.ItemIcon icon={ExternalIcon} position="right" /> 155 + </Menu.Item> 156 + )} 117 157 118 158 {hasSession && !isAgeRestricted && ( 119 159 <Menu.Item
+30
src/screens/Settings/DeerSettings.tsx
··· 87 87 useShowLinkInHandle, 88 88 } from '#/state/preferences/show-link-in-handle.tsx' 89 89 import {useProfilesQuery} from '#/state/queries/profile' 90 + import { 91 + useSetShowExternalShareButtons, 92 + useShowExternalShareButtons, 93 + } from '#/state/preferences/external-share-buttons' 90 94 import * as SettingsList from '#/screens/Settings/components/SettingsList' 91 95 import {atoms as a, useBreakpoints} from '#/alf' 92 96 import {Admonition} from '#/components/Admonition' ··· 94 98 import * as Dialog from '#/components/Dialog' 95 99 import * as Toggle from '#/components/forms/Toggle' 96 100 import {Atom_Stroke2_Corner0_Rounded as DeerIcon} from '#/components/icons/Atom' 101 + import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink' 97 102 import {Eye_Stroke2_Corner0_Rounded as VisibilityIcon} from '#/components/icons/Eye' 98 103 import {Lab_Stroke2_Corner0_Rounded as BeakerIcon} from '#/components/icons/Lab' 99 104 import {PaintRoller_Stroke2_Corner2_Rounded as PaintRollerIcon} from '#/components/icons/PaintRoller' ··· 243 248 const directFetchRecords = useDirectFetchRecords() 244 249 const setDirectFetchRecords = useSetDirectFetchRecords() 245 250 251 + const showExternalShareButtons = useShowExternalShareButtons() 252 + const setShowExternalShareButtons = useSetShowExternalShareButtons() 253 + 246 254 const noAppLabelers = useNoAppLabelers() 247 255 const setNoAppLabelers = useSetNoAppLabelers() 248 256 ··· 348 356 <Trans> 349 357 Fetch records directly from PDS to see contents of blocked and 350 358 detatched quotes 359 + </Trans> 360 + </Toggle.LabelText> 361 + <Toggle.Platform /> 362 + </Toggle.Item> 363 + </SettingsList.Group> 364 + 365 + <SettingsList.Group contentContainerStyle={[a.gap_sm]}> 366 + <SettingsList.ItemIcon icon={ChainLinkIcon} /> 367 + <SettingsList.ItemText> 368 + <Trans>Bridging and Fediverse</Trans> 369 + </SettingsList.ItemText> 370 + <Toggle.Item 371 + name="external_share_buttons" 372 + label={_( 373 + msg`Show "Open original post" and "Open post in pdsls" buttons`, 374 + )} 375 + value={showExternalShareButtons} 376 + onChange={value => setShowExternalShareButtons(value)} 377 + style={[a.w_full]}> 378 + <Toggle.LabelText style={[a.flex_1]}> 379 + <Trans> 380 + Show "Open original post" and "Open post in pdsls" buttons 351 381 </Trans> 352 382 </Toggle.LabelText> 353 383 <Toggle.Platform />
+3
src/state/persisted/schema.ts
··· 149 149 .optional(), 150 150 highQualityImages: z.boolean().optional(), 151 151 152 + showExternalShareButtons: z.boolean().optional(), 153 + 152 154 /** @deprecated */ 153 155 mutedThreads: z.array(z.string()), 154 156 trendingDisabled: z.boolean().optional(), ··· 240 242 ], 241 243 }, 242 244 highQualityImages: false, 245 + showExternalShareButtons: false, 243 246 } 244 247 245 248 export function tryParse(rawData: string): Schema | undefined {
+54
src/state/preferences/external-share-buttons.tsx
··· 1 + import React from 'react' 2 + 3 + import * as persisted from '#/state/persisted' 4 + 5 + type StateContext = persisted.Schema['showExternalShareButtons'] 6 + type SetContext = (v: persisted.Schema['showExternalShareButtons']) => void 7 + 8 + const stateContext = React.createContext<StateContext>( 9 + persisted.defaults.showExternalShareButtons, 10 + ) 11 + const setContext = React.createContext<SetContext>( 12 + (_: persisted.Schema['showExternalShareButtons']) => {}, 13 + ) 14 + 15 + export function Provider({children}: React.PropsWithChildren<{}>) { 16 + const [state, setState] = React.useState( 17 + persisted.get('showExternalShareButtons'), 18 + ) 19 + 20 + const setStateWrapped = React.useCallback( 21 + ( 22 + showExternalShareButtons: persisted.Schema['showExternalShareButtons'], 23 + ) => { 24 + setState(showExternalShareButtons) 25 + persisted.write('showExternalShareButtons', showExternalShareButtons) 26 + }, 27 + [setState], 28 + ) 29 + 30 + React.useEffect(() => { 31 + return persisted.onUpdate( 32 + 'showExternalShareButtons', 33 + nextShowExternalShareButtons => { 34 + setState(nextShowExternalShareButtons) 35 + }, 36 + ) 37 + }, [setStateWrapped]) 38 + 39 + return ( 40 + <stateContext.Provider value={state}> 41 + <setContext.Provider value={setStateWrapped}> 42 + {children} 43 + </setContext.Provider> 44 + </stateContext.Provider> 45 + ) 46 + } 47 + 48 + export function useShowExternalShareButtons() { 49 + return React.useContext(stateContext) 50 + } 51 + 52 + export function useSetShowExternalShareButtons() { 53 + return React.useContext(setContext) 54 + }
+28 -25
src/state/preferences/index.tsx
··· 14 14 import {Provider as DisableSavesMetricsProvider} from './disable-saves-metrics' 15 15 import {Provider as DisableViaRepostNotificationProvider} from './disable-via-repost-notification' 16 16 import {Provider as ExternalEmbedsProvider} from './external-embeds-prefs' 17 + import {Provider as ExternalShareButtonsProvider} from './external-share-buttons' 17 18 import {Provider as GoLinksProvider} from './go-links-enabled' 18 19 import {Provider as HiddenPostsProvider} from './hidden-posts' 19 20 import {Provider as HideFeedsPromoTabProvider} from './hide-feeds-promo-tab' ··· 55 56 return ( 56 57 <LanguagesProvider> 57 58 <AltTextRequiredProvider> 58 - <GoLinksProvider> 59 + <ExternalShareButtonsProvider> 60 + <GoLinksProvider> 59 61 <NoAppLabelersProvider> 60 - <DirectFetchRecordsProvider> 61 - <ConstellationProvider> 62 + <DirectFetchRecordsProvider> 63 + <ConstellationProvider> 62 64 <ConstellationInstanceProvider> 63 65 <DeerVerificationProvider> 64 66 <NoDiscoverProvider> 65 67 <ShowLinkInHandleProvider> 66 - <LargeAltBadgeProvider> 67 - <ExternalEmbedsProvider> 68 - <HiddenPostsProvider> 68 + <LargeAltBadgeProvider> 69 + <ExternalEmbedsProvider> 70 + <HiddenPostsProvider> 69 71 <HighQualityImagesProvider> 70 - <InAppBrowserProvider> 71 - <DisableHapticsProvider> 72 - <AutoplayProvider> 73 - <UsedStarterPacksProvider> 74 - <SubtitlesProvider> 75 - <TrendingSettingsProvider> 76 - <RepostCarouselProvider> 72 + <InAppBrowserProvider> 73 + <DisableHapticsProvider> 74 + <AutoplayProvider> 75 + <UsedStarterPacksProvider> 76 + <SubtitlesProvider> 77 + <TrendingSettingsProvider> 78 + <RepostCarouselProvider> 77 79 <KawaiiProvider> 78 80 <HideFeedsPromoTabProvider> 79 81 <DisableViaRepostNotificationProvider> ··· 94 96 </HideFeedsPromoTabProvider> 95 97 </KawaiiProvider> 96 98 </RepostCarouselProvider> 97 - </TrendingSettingsProvider> 98 - </SubtitlesProvider> 99 - </UsedStarterPacksProvider> 100 - </AutoplayProvider> 101 - </DisableHapticsProvider> 102 - </InAppBrowserProvider> 99 + </TrendingSettingsProvider> 100 + </SubtitlesProvider> 101 + </UsedStarterPacksProvider> 102 + </AutoplayProvider> 103 + </DisableHapticsProvider> 104 + </InAppBrowserProvider> 103 105 </HighQualityImagesProvider> 104 - </HiddenPostsProvider> 105 - </ExternalEmbedsProvider> 106 - </LargeAltBadgeProvider> 106 + </HiddenPostsProvider> 107 + </ExternalEmbedsProvider> 108 + </LargeAltBadgeProvider> 107 109 </ShowLinkInHandleProvider> 108 110 </NoDiscoverProvider> 109 111 </DeerVerificationProvider> 110 112 </ConstellationInstanceProvider> 111 - </ConstellationProvider> 112 - </DirectFetchRecordsProvider> 113 + </ConstellationProvider> 114 + </DirectFetchRecordsProvider> 113 115 </NoAppLabelersProvider> 114 - </GoLinksProvider> 116 + </GoLinksProvider> 117 + </ExternalShareButtonsProvider> 115 118 </AltTextRequiredProvider> 116 119 </LanguagesProvider> 117 120 )