Bluesky app fork with some witchin' additions 💫 witchsky.app
bluesky fork client

MAKE CUSTOM POST PHRASE FEATURE WORK!!!

And changed some defaults (deer settings fixes)

xan.lol d5bdcf84 e31d3ba4

verified
+202 -114
+9 -9
README.md
··· 16 17 - Cooler name (and kawaii logo) 18 - Color scheme options and hue slider (defaults to Witchsky orange) 19 - - Posts are called Skeets (may let you choose in the future) 20 - Choose between sharing witchsky.app or bsky.app links 21 - Embed player works with [stream.place](https://stream.place/) links! 22 - - Open skeets in PDSls and original pages of bridged posts 23 - - You can redraft skeets 24 - Better defaults (alt text required 😉 autoplay off 🫨) 25 - More unique repost icons 26 - Can download videos ··· 33 These are all available as options in a sub-page of the app's settings. 34 35 - Toggle go.bsky.app link proxying for analytics 36 - - Toggle to see skeets in quotes through blocks and detachments 37 - Toggle for buttons to show original fedi posts and in PDSls 38 - Toggle to trust your own preferred verifiers (and to operate as one yourself) 39 - Toggle to change Constellation instance for custom features ··· 42 #### Tweaks 43 44 - Toggle to turn non-bsky.social handles into clickable links 45 - - Toggle to combine reskeets in horizontal carousels 46 - Toggle the following feed fallback to the discover feed 47 - Toggle displaying images in higher quality 48 - Toggle to only show a single tab if only one feed is pinned 49 - - Toggle to prevent others from getting notified when you interact with their reskeets 50 - Toggle similar account recommendations 51 - Toggle to make all user avatars square (like labelers) 52 - Toggle for more square-ish UI (still slightly rounded) ··· 58 You can completely disable the visiblity of all metrics individually, including the number of: 59 60 - likes 61 - - reskeets 62 - quotes 63 - saves 64 - replies ··· 68 69 ## Upcoming or wishful features 70 71 - - Better OpenGraph support for sharing profiles & skeets (including videos & fixing quotes) 72 - Selecting a custom AppView 73 - Seeing past blocks in threads (the nuclear block in reply chains) 74 - Configure the location used to determine regional labelers ··· 115 116 > Witchsky is a community fork, and we'd love to merge your PR! 117 118 - As a rule of thumb, the best features for Witchsky are those that have a disproportionately positive impact on the user experience compared to the maintenance overhead. Unlike some open source projects, since Witchsky is a soft fork, any features (patches) we add on top of upstream social-app need to be maintained. For example, a change to the way skeets are composed may be very invasive, touching lots of code across the codebase. If upstream refactors this component, we will need to rewrite this feature to be compatible or drop it from the client. 119 120 For this reason, only features that require changing only a small amount of code from upstream should be considered. 121
··· 16 17 - Cooler name (and kawaii logo) 18 - Color scheme options and hue slider (defaults to Witchsky orange) 19 + - You can change 20 - Choose between sharing witchsky.app or bsky.app links 21 - Embed player works with [stream.place](https://stream.place/) links! 22 + - Open posts in PDSls and original pages of bridged posts 23 + - You can redraft posts 24 - Better defaults (alt text required 😉 autoplay off 🫨) 25 - More unique repost icons 26 - Can download videos ··· 33 These are all available as options in a sub-page of the app's settings. 34 35 - Toggle go.bsky.app link proxying for analytics 36 + - Toggle to see posts in quotes through blocks and detachments 37 - Toggle for buttons to show original fedi posts and in PDSls 38 - Toggle to trust your own preferred verifiers (and to operate as one yourself) 39 - Toggle to change Constellation instance for custom features ··· 42 #### Tweaks 43 44 - Toggle to turn non-bsky.social handles into clickable links 45 + - Toggle to combine reposts in horizontal carousels 46 - Toggle the following feed fallback to the discover feed 47 - Toggle displaying images in higher quality 48 - Toggle to only show a single tab if only one feed is pinned 49 + - Toggle to prevent others from getting notified when you interact with their reposts 50 - Toggle similar account recommendations 51 - Toggle to make all user avatars square (like labelers) 52 - Toggle for more square-ish UI (still slightly rounded) ··· 58 You can completely disable the visiblity of all metrics individually, including the number of: 59 60 - likes 61 + - reposts 62 - quotes 63 - saves 64 - replies ··· 68 69 ## Upcoming or wishful features 70 71 + - Better OpenGraph support for sharing profiles & posts (including videos & fixing quotes) 72 - Selecting a custom AppView 73 - Seeing past blocks in threads (the nuclear block in reply chains) 74 - Configure the location used to determine regional labelers ··· 115 116 > Witchsky is a community fork, and we'd love to merge your PR! 117 118 + As a rule of thumb, the best features for Witchsky are those that have a disproportionately positive impact on the user experience compared to the maintenance overhead. Unlike some open source projects, since Witchsky is a soft fork, any features (patches) we add on top of upstream social-app need to be maintained. For example, a change to the way posts are composed may be very invasive, touching lots of code across the codebase. If upstream refactors this component, we will need to rewrite this feature to be compatible or drop it from the client. 119 120 For this reason, only features that require changing only a small amount of code from upstream should be considered. 121
+3 -3
src/locale/i18n.ts
··· 13 14 import {sanitizeAppLanguageSetting} from '#/locale/helpers' 15 import {AppLanguage} from '#/locale/languages' 16 - import {applySkeetReplacements} from '#/locale/linguiHook' 17 import {messages as messagesAn} from '#/locale/locales/an/messages' 18 import {messages as messagesAst} from '#/locale/locales/ast/messages' 19 import {messages as messagesCa} from '#/locale/locales/ca/messages' ··· 126 break 127 } 128 case AppLanguage.en_GB: { 129 - const transformedMsgs = applySkeetReplacements(messagesEn_GB, locale) 130 i18n.loadAndActivate({locale, messages: transformedMsgs}) 131 await Promise.all([ 132 import('@formatjs/intl-pluralrules/locale-data/en'), ··· 424 break 425 } 426 default: { 427 - const transformedMsgs = applySkeetReplacements(messagesEn, locale) 428 i18n.loadAndActivate({locale, messages: transformedMsgs}) 429 break 430 }
··· 13 14 import {sanitizeAppLanguageSetting} from '#/locale/helpers' 15 import {AppLanguage} from '#/locale/languages' 16 + import {applyPostReplacements} from '#/locale/linguiHook' 17 import {messages as messagesAn} from '#/locale/locales/an/messages' 18 import {messages as messagesAst} from '#/locale/locales/ast/messages' 19 import {messages as messagesCa} from '#/locale/locales/ca/messages' ··· 126 break 127 } 128 case AppLanguage.en_GB: { 129 + const transformedMsgs = applyPostReplacements(messagesEn_GB, locale) 130 i18n.loadAndActivate({locale, messages: transformedMsgs}) 131 await Promise.all([ 132 import('@formatjs/intl-pluralrules/locale-data/en'), ··· 424 break 425 } 426 default: { 427 + const transformedMsgs = applyPostReplacements(messagesEn, locale) 428 i18n.loadAndActivate({locale, messages: transformedMsgs}) 429 break 430 }
+4 -4
src/locale/i18n.web.ts
··· 3 4 import {sanitizeAppLanguageSetting} from '#/locale/helpers' 5 import {AppLanguage} from '#/locale/languages' 6 - import {applySkeetReplacements} from '#/locale/linguiHook' 7 import {useLanguagePrefs} from '#/state/preferences' 8 9 /** ··· 43 } 44 case AppLanguage.en: { 45 mod = await import(`./locales/en/messages`) 46 - const transformedEnMessages = applySkeetReplacements(mod.messages, locale) 47 i18n.load(locale, transformedEnMessages) 48 i18n.activate(locale) 49 break 50 } 51 case AppLanguage.en_GB: { 52 mod = await import(`./locales/en-GB/messages`) 53 - const transformedEnGbMessages = applySkeetReplacements( 54 mod.messages, 55 locale, 56 ) ··· 188 } 189 default: { 190 mod = await import(`./locales/en/messages`) 191 - const transformedDefaultMessages = applySkeetReplacements( 192 mod.messages, 193 locale, 194 )
··· 3 4 import {sanitizeAppLanguageSetting} from '#/locale/helpers' 5 import {AppLanguage} from '#/locale/languages' 6 + import {applyPostReplacements} from '#/locale/linguiHook' 7 import {useLanguagePrefs} from '#/state/preferences' 8 9 /** ··· 43 } 44 case AppLanguage.en: { 45 mod = await import(`./locales/en/messages`) 46 + const transformedEnMessages = applyPostReplacements(mod.messages, locale) 47 i18n.load(locale, transformedEnMessages) 48 i18n.activate(locale) 49 break 50 } 51 case AppLanguage.en_GB: { 52 mod = await import(`./locales/en-GB/messages`) 53 + const transformedEnGbMessages = applyPostReplacements( 54 mod.messages, 55 locale, 56 ) ··· 188 } 189 default: { 190 mod = await import(`./locales/en/messages`) 191 + const transformedDefaultMessages = applyPostReplacements( 192 mod.messages, 193 locale, 194 )
+14 -5
src/locale/linguiHook.ts
··· 4 5 // Helper to apply the replacement to a single string 6 function replaceInString(text: string): string { 7 - const {string: replacement, enabled} = persisted.get('postReplacement') 8 if (!enabled) return text 9 - let repl = replacement?.length ? replacement.toLowerCase() : 'skeet' 10 return text 11 - .replaceAll('Post', repl[0].toUpperCase() + repl.slice(1)) 12 - .replaceAll('post', repl) 13 } 14 15 // Recursive helper to traverse and replace strings in nested structures ··· 40 * @returns The messages object with replacements applied if the locale is English, 41 * otherwise the original messages object. 42 */ 43 - export function applySkeetReplacements( 44 messages: Messages, 45 locale: string, 46 ): Messages {
··· 4 5 // Helper to apply the replacement to a single string 6 function replaceInString(text: string): string { 7 + const {postName, postsName, enabled} = persisted.get('postReplacement') 8 if (!enabled) return text 9 + 10 + const singular = postName?.length ? postName : 'skeet' 11 + const plural = postsName?.length ? postsName : 'skeets' 12 + 13 + // Capitalize first letter for proper noun replacements 14 + const singularCapitalized = singular[0].toUpperCase() + singular.slice(1) 15 + const pluralCapitalized = plural[0].toUpperCase() + plural.slice(1) 16 + 17 return text 18 + .replaceAll('Posts', pluralCapitalized) 19 + .replaceAll('posts', plural) 20 + .replaceAll('Post', singularCapitalized) 21 + .replaceAll('post', singular) 22 } 23 24 // Recursive helper to traverse and replace strings in nested structures ··· 49 * @returns The messages object with replacements applied if the locale is English, 50 * otherwise the original messages object. 51 */ 52 + export function applyPostReplacements( 53 messages: Messages, 54 locale: string, 55 ): Messages {
+152 -76
src/screens/Settings/DeerSettings.tsx
··· 7 8 import {usePalette} from '#/lib/hooks/usePalette' 9 import {type CommonNavigatorParams} from '#/lib/routes/types' 10 import * as persisted from '#/state/persisted' 11 import {useGoLinksEnabled, useSetGoLinksEnabled} from '#/state/preferences' 12 import { ··· 123 import {Admonition} from '#/components/Admonition' 124 import {Button, ButtonText} from '#/components/Button' 125 import * as Dialog from '#/components/Dialog' 126 - import * as TextField from '#/components/forms/TextField' 127 import * as Toggle from '#/components/forms/Toggle' 128 import {Atom_Stroke2_Corner0_Rounded as DeerIcon} from '#/components/icons/Atom' 129 import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink' ··· 295 ) 296 } 297 298 function TrustedVerifiersDialog({ 299 control, 300 }: { ··· 431 432 const setLibreTranslateInstanceControl = Dialog.useDialogControl() 433 434 - const postReplacement = usePostReplacement() 435 - const setPostReplacement = useSetPostReplacement() 436 437 return ( 438 <Layout.Screen> ··· 584 585 <SettingsList.Divider /> 586 587 - <SettingsList.Group contentContainerStyle={[a.gap_sm]}> 588 <SettingsList.ItemIcon icon={PencilIcon} /> 589 <SettingsList.ItemText> 590 - <Trans> 591 - Call posts{' '} 592 - {postReplacement.string.length 593 - ? postReplacement.string.toLowerCase() 594 - : 'skeet'} 595 - s 596 - </Trans> 597 </SettingsList.ItemText> 598 - <Toggle.Item 599 - name="call_posts_skeets" 600 - label={_( 601 - msg`Changes post to another word of your choosing. Requires a refresh to update.`, 602 - )} 603 - value={postReplacement.enabled} 604 - onChange={value => 605 - setPostReplacement({ 606 - enabled: value, 607 - string: postReplacement.string, 608 - }) 609 - } 610 - style={[a.w_full]}> 611 - <Toggle.LabelText style={[a.flex_1]}> 612 - <Trans> 613 - Changes post to another word of your choosing. Requires a 614 - refresh to update. 615 - </Trans> 616 - </Toggle.LabelText> 617 - <Toggle.Platform /> 618 - </Toggle.Item> 619 620 - {postReplacement.enabled && ( 621 - <SettingsList.Item> 622 - <TextField.Root> 623 - <TextField.Input 624 - label={_(msg`Custom post name`)} 625 - value={postReplacement.string} 626 - onChangeText={(value: string) => 627 - setPostReplacement( 628 - (curr: {enabled: boolean; string: string}) => ({ 629 - ...curr, 630 - string: value, 631 - }), 632 - ) 633 - } 634 - /> 635 - </TextField.Root> 636 - </SettingsList.Item> 637 - )} 638 - </SettingsList.Group> 639 640 <SettingsList.Group contentContainerStyle={[a.gap_sm]}> 641 <SettingsList.ItemIcon icon={PaintRollerIcon} /> ··· 653 </Toggle.LabelText> 654 <Toggle.Platform /> 655 </Toggle.Item> 656 - <Toggle.Item 657 - name="no_discover_fallback" 658 - label={_(msg`Do not fall back to discover feed`)} 659 - value={noDiscoverFallback} 660 - onChange={value => setNoDiscoverFallback(value)} 661 - style={[a.w_full]}> 662 - <Toggle.LabelText style={[a.flex_1]}> 663 - <Trans>Do not fall back to discover feed</Trans> 664 - </Toggle.LabelText> 665 - <Toggle.Platform /> 666 - </Toggle.Item> 667 <Toggle.Item 668 name="show_link_in_handle" 669 label={_( ··· 676 <Trans> 677 On non-bsky.social handles, show a link to that URL 678 </Trans> 679 - </Toggle.LabelText> 680 - <Toggle.Platform /> 681 - </Toggle.Item> 682 - 683 - <Toggle.Item 684 - name="repost_carousel" 685 - label={_(msg`Combine reposts into a horizontal carousel`)} 686 - value={repostCarouselEnabled} 687 - onChange={value => setRepostCarouselEnabled(value)} 688 - style={[a.w_full]}> 689 - <Toggle.LabelText style={[a.flex_1]}> 690 - <Trans>Combine reposts into a horizontal carousel</Trans> 691 </Toggle.LabelText> 692 <Toggle.Platform /> 693 </Toggle.Item> ··· 908 909 <Toggle.Item 910 name="disable_reposts_metrics" 911 - label={_(msg`Disable Reposts Metrics`)} 912 value={disableRepostsMetrics} 913 onChange={value => setDisableRepostsMetrics(value)} 914 style={[a.w_full]}> 915 <Toggle.LabelText style={[a.flex_1]}> 916 - <Trans>Disable Reposts Metrics</Trans> 917 </Toggle.LabelText> 918 <Toggle.Platform /> 919 </Toggle.Item> ··· 1053 <LibreTranslateInstanceDialog 1054 control={setLibreTranslateInstanceControl} 1055 /> 1056 </Layout.Screen> 1057 ) 1058 }
··· 7 8 import {usePalette} from '#/lib/hooks/usePalette' 9 import {type CommonNavigatorParams} from '#/lib/routes/types' 10 + import {dynamicActivate} from '#/locale/i18n' 11 + import {dynamicActivate as dynamicActivateWeb} from '#/locale/i18n.web' 12 + import {type AppLanguage} from '#/locale/languages' 13 import * as persisted from '#/state/persisted' 14 import {useGoLinksEnabled, useSetGoLinksEnabled} from '#/state/preferences' 15 import { ··· 126 import {Admonition} from '#/components/Admonition' 127 import {Button, ButtonText} from '#/components/Button' 128 import * as Dialog from '#/components/Dialog' 129 import * as Toggle from '#/components/forms/Toggle' 130 import {Atom_Stroke2_Corner0_Rounded as DeerIcon} from '#/components/icons/Atom' 131 import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink' ··· 297 ) 298 } 299 300 + function PostReplacementDialog({ 301 + control, 302 + }: { 303 + control: Dialog.DialogControlProps 304 + }) { 305 + const pal = usePalette('default') 306 + const {_, i18n} = useLingui() 307 + 308 + const postReplacement = usePostReplacement() 309 + const setPostReplacement = useSetPostReplacement() 310 + 311 + const [singular, setSingular] = useState(postReplacement.postName) 312 + const [plural, setPlural] = useState(postReplacement.postsName) 313 + const [pluralManuallyEdited, setPluralManuallyEdited] = useState(false) 314 + 315 + const submit = async () => { 316 + setPostReplacement({ 317 + enabled: singular.trim().toLowerCase() !== 'post', 318 + postName: singular, 319 + postsName: plural, 320 + }) 321 + 322 + // Force reload the i18n messages to apply the replacement immediately 323 + const locale = i18n.locale 324 + await (IS_WEB 325 + ? dynamicActivateWeb(locale as AppLanguage) 326 + : dynamicActivate(locale as AppLanguage)) 327 + 328 + control.close() 329 + } 330 + 331 + const handleSingularChange = (value: string) => { 332 + setSingular(value) 333 + if (!pluralManuallyEdited) { 334 + setPlural(value + 's') 335 + } 336 + } 337 + 338 + const handlePluralChange = (value: string) => { 339 + setPlural(value) 340 + setPluralManuallyEdited(true) 341 + } 342 + 343 + const handlePresetSelect = (singularForm: string, pluralForm: string) => { 344 + setSingular(singularForm) 345 + setPlural(pluralForm) 346 + setPluralManuallyEdited(false) 347 + } 348 + 349 + const shouldDisable = () => { 350 + return !singular.trim() || !plural.trim() 351 + } 352 + 353 + return ( 354 + <Dialog.Outer 355 + control={control} 356 + nativeOptions={{preventExpansion: true}} 357 + onClose={() => { 358 + setSingular(postReplacement.postName) 359 + setPlural(postReplacement.postsName) 360 + setPluralManuallyEdited(false) 361 + }}> 362 + <Dialog.Handle /> 363 + <Dialog.ScrollableInner label={_(msg`Custom post phrase`)}> 364 + <View style={[a.gap_sm, a.pb_lg]}> 365 + <Text style={[a.text_2xl, a.font_bold]}> 366 + <Trans>Custom post phrase</Trans> 367 + </Text> 368 + </View> 369 + 370 + <View style={a.gap_lg}> 371 + <Dialog.Input 372 + label="Singular form" 373 + autoFocus 374 + style={[styles.textInput, pal.border, pal.text]} 375 + onChangeText={handleSingularChange} 376 + placeholder="skeet" 377 + placeholderTextColor={pal.colors.textLight} 378 + accessibilityHint={_(msg`Input the singular form (e.g., "skeet")`)} 379 + value={singular} 380 + /> 381 + 382 + <View style={[a.flex_row, a.flex_wrap, a.mb_xs]}> 383 + {[ 384 + {singular: 'post', plural: 'posts'}, 385 + {singular: 'skeet', plural: 'skeets'}, 386 + {singular: 'note', plural: 'notes'}, 387 + {singular: 'woot', plural: 'woots'}, 388 + {singular: 'toot', plural: 'toots'}, 389 + {singular: 'silly', plural: 'sillies'}, 390 + ].map(preset => ( 391 + <Button 392 + key={preset.singular} 393 + variant="ghost" 394 + color="primary" 395 + label={preset.singular} 396 + style={[a.px_sm, a.py_xs, a.rounded_sm, a.gap_sm]} 397 + onPress={() => 398 + handlePresetSelect(preset.singular, preset.plural) 399 + }> 400 + <ButtonText>{preset.singular}</ButtonText> 401 + </Button> 402 + ))} 403 + </View> 404 + 405 + <Dialog.Input 406 + label="Plural form" 407 + style={[styles.textInput, pal.border, pal.text]} 408 + onChangeText={handlePluralChange} 409 + placeholder="skeets" 410 + placeholderTextColor={pal.colors.textLight} 411 + accessibilityHint={_(msg`Input the plural form (e.g., "skeets")`)} 412 + value={plural} 413 + /> 414 + 415 + <View style={IS_WEB && [a.flex_row, a.justify_end]}> 416 + <Button 417 + label={_(msg`Save`)} 418 + size="large" 419 + onPress={submit} 420 + variant="solid" 421 + color="primary" 422 + disabled={shouldDisable()}> 423 + <ButtonText> 424 + <Trans>Save</Trans> 425 + </ButtonText> 426 + </Button> 427 + </View> 428 + </View> 429 + 430 + <Dialog.Close /> 431 + </Dialog.ScrollableInner> 432 + </Dialog.Outer> 433 + ) 434 + } 435 + 436 function TrustedVerifiersDialog({ 437 control, 438 }: { ··· 569 570 const setLibreTranslateInstanceControl = Dialog.useDialogControl() 571 572 + const setPostReplacementDialogControl = Dialog.useDialogControl() 573 574 return ( 575 <Layout.Screen> ··· 721 722 <SettingsList.Divider /> 723 724 + <SettingsList.Item> 725 <SettingsList.ItemIcon icon={PencilIcon} /> 726 <SettingsList.ItemText> 727 + <Trans>{`Custom post phrase`}</Trans> 728 </SettingsList.ItemText> 729 + <SettingsList.BadgeButton 730 + label={_(msg`Change`)} 731 + onPress={() => setPostReplacementDialogControl.open()} 732 + /> 733 + </SettingsList.Item> 734 735 + <SettingsList.Divider /> 736 737 <SettingsList.Group contentContainerStyle={[a.gap_sm]}> 738 <SettingsList.ItemIcon icon={PaintRollerIcon} /> ··· 750 </Toggle.LabelText> 751 <Toggle.Platform /> 752 </Toggle.Item> 753 + 754 <Toggle.Item 755 name="show_link_in_handle" 756 label={_( ··· 763 <Trans> 764 On non-bsky.social handles, show a link to that URL 765 </Trans> 766 </Toggle.LabelText> 767 <Toggle.Platform /> 768 </Toggle.Item> ··· 983 984 <Toggle.Item 985 name="disable_reposts_metrics" 986 + label={_(msg`Disable reposts metrics`)} 987 value={disableRepostsMetrics} 988 onChange={value => setDisableRepostsMetrics(value)} 989 style={[a.w_full]}> 990 <Toggle.LabelText style={[a.flex_1]}> 991 + <Trans>Disable reposts metrics</Trans> 992 </Toggle.LabelText> 993 <Toggle.Platform /> 994 </Toggle.Item> ··· 1128 <LibreTranslateInstanceDialog 1129 control={setLibreTranslateInstanceControl} 1130 /> 1131 + <PostReplacementDialog control={setPostReplacementDialogControl} /> 1132 </Layout.Screen> 1133 ) 1134 }
+6 -4
src/state/persisted/schema.ts
··· 172 173 postReplacement: z.object({ 174 enabled: z.boolean().optional(), 175 - string: z.string().optional(), 176 }), 177 178 showExternalShareButtons: z.boolean().optional(), ··· 218 ]), 219 }, 220 requireAltTextEnabled: true, 221 - largeAltBadgeEnabled: true, 222 externalEmbeds: {}, 223 mutedThreads: [], 224 invites: { ··· 242 // deer 243 goLinksEnabled: true, 244 constellationEnabled: true, 245 - directFetchRecords: false, 246 noAppLabelers: false, 247 noDiscoverFallback: false, 248 repostCarouselEnabled: false, ··· 299 300 postReplacement: { 301 enabled: false, 302 - string: 'skeet', 303 }, 304 } 305
··· 172 173 postReplacement: z.object({ 174 enabled: z.boolean().optional(), 175 + postName: z.string().optional(), 176 + postsName: z.string().optional(), 177 }), 178 179 showExternalShareButtons: z.boolean().optional(), ··· 219 ]), 220 }, 221 requireAltTextEnabled: true, 222 + largeAltBadgeEnabled: false, 223 externalEmbeds: {}, 224 mutedThreads: [], 225 invites: { ··· 243 // deer 244 goLinksEnabled: true, 245 constellationEnabled: true, 246 + directFetchRecords: true, 247 noAppLabelers: false, 248 noDiscoverFallback: false, 249 repostCarouselEnabled: false, ··· 300 301 postReplacement: { 302 enabled: false, 303 + postName: 'skeet', 304 + postsName: 'skeets', 305 }, 306 } 307
+14 -13
src/state/preferences/post-name-replacement.tsx
··· 4 5 interface PostReplacementState { 6 enabled: boolean 7 - string: string 8 } 9 10 type StateContext = PostReplacementState ··· 31 return { 32 enabled: 33 persistedState?.enabled ?? persisted.defaults.postReplacement.enabled!, 34 - string: 35 - persistedState?.string ?? persisted.defaults.postReplacement.string!, 36 } 37 }) 38 ··· 53 54 React.useEffect(() => { 55 return persisted.onUpdate('postReplacement', next => { 56 - setState({string: next.string ?? 'skeet', enabled: next.enabled ?? true}) 57 - /* 58 - if (nextVal) { 59 - _setState({ 60 - enabled: 61 - nextVal.enabled ?? persisted.defaults.postReplacement.enabled!, 62 - string: nextVal.string ?? persisted.defaults.postReplacement.string!, 63 - }) 64 - }*/ 65 }) 66 - }, []) 67 68 return ( 69 <stateContext.Provider value={state}>
··· 4 5 interface PostReplacementState { 6 enabled: boolean 7 + postName: string 8 + postsName: string 9 } 10 11 type StateContext = PostReplacementState ··· 32 return { 33 enabled: 34 persistedState?.enabled ?? persisted.defaults.postReplacement.enabled!, 35 + postName: 36 + persistedState?.postName ?? 37 + persisted.defaults.postReplacement.postName!, 38 + postsName: 39 + persistedState?.postsName ?? 40 + persisted.defaults.postReplacement.postsName!, 41 } 42 }) 43 ··· 58 59 React.useEffect(() => { 60 return persisted.onUpdate('postReplacement', next => { 61 + setState({ 62 + postName: next.postName ?? 'skeet', 63 + postsName: next.postsName ?? 'skeets', 64 + enabled: next.enabled ?? true, 65 + }) 66 }) 67 + }, [setState]) 68 69 return ( 70 <stateContext.Provider value={state}>