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

Add keyboard shortcuts: new, escape, and hard break (#552)

* Add keyboard shortcuts: new, escape, and hard break

* Add preferences modal

* Remove code accidentally re-added due to rebase

* Fix incorrect copy and lint

* Put stuff back so diffs are clearer

* Re-add invite codes to settings

* Address comments

* Tune the copy

---------

Co-authored-by: Paul Frazee <pfrazee@gmail.com>

authored by

Ollie H
Paul Frazee
and committed by
GitHub
95f8360d af905947

+78 -26
+2
package.json
··· 49 "@sentry/react-native": "4.13.0", 50 "@tiptap/core": "^2.0.0-beta.220", 51 "@tiptap/extension-document": "^2.0.0-beta.220", 52 "@tiptap/extension-history": "^2.0.3", 53 "@tiptap/extension-link": "^2.0.0-beta.220", 54 "@tiptap/extension-mention": "^2.0.0-beta.220", ··· 58 "@tiptap/pm": "^2.0.0-beta.220", 59 "@tiptap/react": "^2.0.0-beta.220", 60 "@tiptap/suggestion": "^2.0.0-beta.220", 61 "@zxing/text-encoding": "^0.9.0", 62 "await-lock": "^2.2.2", 63 "base64-js": "^1.5.1",
··· 49 "@sentry/react-native": "4.13.0", 50 "@tiptap/core": "^2.0.0-beta.220", 51 "@tiptap/extension-document": "^2.0.0-beta.220", 52 + "@tiptap/extension-hard-break": "^2.0.3", 53 "@tiptap/extension-history": "^2.0.3", 54 "@tiptap/extension-link": "^2.0.0-beta.220", 55 "@tiptap/extension-mention": "^2.0.0-beta.220", ··· 59 "@tiptap/pm": "^2.0.0-beta.220", 60 "@tiptap/react": "^2.0.0-beta.220", 61 "@tiptap/suggestion": "^2.0.0-beta.220", 62 + "@types/node": "^18.16.2", 63 "@zxing/text-encoding": "^0.9.0", 64 "await-lock": "^2.2.2", 65 "base64-js": "^1.5.1",
+2 -1
src/state/models/ui/shell.ts
··· 11 title: string 12 message: string | (() => JSX.Element) 13 onPressConfirm: () => void | Promise<void> 14 } 15 16 export interface EditProfileModal { ··· 86 87 export type Modal = 88 // Account 89 | ChangeHandleModal 90 | DeleteAccountModal 91 | EditProfileModal 92 - | AddAppPasswordModal 93 94 // Curation 95 | ContentFilteringSettingsModal
··· 11 title: string 12 message: string | (() => JSX.Element) 13 onPressConfirm: () => void | Promise<void> 14 + onPressCancel?: () => void | Promise<void> 15 } 16 17 export interface EditProfileModal { ··· 87 88 export type Modal = 89 // Account 90 + | AddAppPasswordModal 91 | ChangeHandleModal 92 | DeleteAccountModal 93 | EditProfileModal 94 95 // Curation 96 | ContentFilteringSettingsModal
+24
src/view/com/composer/Composer.tsx
··· 88 autocompleteView.setup() 89 }, [autocompleteView]) 90 91 const onPressAddLinkCard = useCallback( 92 (uri: string) => { 93 setExtLink({uri, isLoading: true})
··· 88 autocompleteView.setup() 89 }, [autocompleteView]) 90 91 + const onEscape = useCallback( 92 + (e: KeyboardEvent) => { 93 + if (e.key === 'Escape') { 94 + store.shell.openModal({ 95 + name: 'confirm', 96 + title: 'Cancel draft', 97 + onPressConfirm: onClose, 98 + onPressCancel: () => { 99 + store.shell.closeModal() 100 + }, 101 + message: "Are you sure you'd like to cancel this draft?", 102 + }) 103 + } 104 + }, 105 + [store.shell, onClose], 106 + ) 107 + 108 + useEffect(() => { 109 + if (isDesktopWeb) { 110 + window.addEventListener('keydown', onEscape) 111 + return () => window.removeEventListener('keydown', onEscape) 112 + } 113 + }, [onEscape]) 114 + 115 const onPressAddLinkCard = useCallback( 116 (uri: string) => { 117 setExtLink({uri, isLoading: true})
+2
src/view/com/composer/text-input/TextInput.web.tsx
··· 4 import {useEditor, EditorContent, JSONContent} from '@tiptap/react' 5 import {Document} from '@tiptap/extension-document' 6 import History from '@tiptap/extension-history' 7 import {Link} from '@tiptap/extension-link' 8 import {Mention} from '@tiptap/extension-mention' 9 import {Paragraph} from '@tiptap/extension-paragraph' ··· 72 }), 73 Text, 74 History, 75 ], 76 editorProps: { 77 attributes: {
··· 4 import {useEditor, EditorContent, JSONContent} from '@tiptap/react' 5 import {Document} from '@tiptap/extension-document' 6 import History from '@tiptap/extension-history' 7 + import Hardbreak from '@tiptap/extension-hard-break' 8 import {Link} from '@tiptap/extension-link' 9 import {Mention} from '@tiptap/extension-mention' 10 import {Paragraph} from '@tiptap/extension-paragraph' ··· 73 }), 74 Text, 75 History, 76 + Hardbreak, 77 ], 78 editorProps: { 79 attributes: {
+2 -2
src/view/com/modals/AltImageRead.tsx
··· 34 testID="altTextImageSaveBtn" 35 onPress={onPress} 36 accessibilityRole="button" 37 - accessibilityLabel="Save" 38 - accessibilityHint="Save alt text"> 39 <LinearGradient 40 colors={[gradients.blueLight.start, gradients.blueLight.end]} 41 start={{x: 0, y: 0}}
··· 34 testID="altTextImageSaveBtn" 35 onPress={onPress} 36 accessibilityRole="button" 37 + accessibilityLabel="Done" 38 + accessibilityHint="Closes alt text modal"> 39 <LinearGradient 40 colors={[gradients.blueLight.start, gradients.blueLight.end]} 41 start={{x: 0, y: 0}}
+24 -3
src/view/com/modals/Confirm.tsx
··· 19 title, 20 message, 21 onPressConfirm, 22 }: { 23 title: string 24 message: string | (() => JSX.Element) 25 onPressConfirm: () => void | Promise<void> 26 }) { 27 const pal = usePalette('default') 28 const store = useStores() ··· 69 style={[styles.btn]} 70 accessibilityRole="button" 71 accessibilityLabel="Confirm" 72 - // TODO: This needs to be updated so that modal roles are clear; 73 - // Currently there is only one usage for the confirm modal: post deletion 74 - accessibilityHint="Confirms a potentially destructive action"> 75 <Text style={[s.white, s.bold, s.f18]}>Confirm</Text> 76 </TouchableOpacity> 77 )} 78 </View> 79 ) 80 } ··· 103 marginTop: 22, 104 marginHorizontal: 44, 105 backgroundColor: colors.blue3, 106 }, 107 })
··· 19 title, 20 message, 21 onPressConfirm, 22 + onPressCancel, 23 }: { 24 title: string 25 message: string | (() => JSX.Element) 26 onPressConfirm: () => void | Promise<void> 27 + onPressCancel?: () => void | Promise<void> 28 }) { 29 const pal = usePalette('default') 30 const store = useStores() ··· 71 style={[styles.btn]} 72 accessibilityRole="button" 73 accessibilityLabel="Confirm" 74 + accessibilityHint=""> 75 <Text style={[s.white, s.bold, s.f18]}>Confirm</Text> 76 </TouchableOpacity> 77 )} 78 + {onPressCancel === undefined ? null : ( 79 + <TouchableOpacity 80 + testID="cancelBtn" 81 + onPress={onPressCancel} 82 + style={[styles.btnCancel, s.mt10]} 83 + accessibilityRole="button" 84 + accessibilityLabel="Cancel" 85 + accessibilityHint=""> 86 + <Text type="button-lg" style={pal.textLight}> 87 + Cancel 88 + </Text> 89 + </TouchableOpacity> 90 + )} 91 </View> 92 ) 93 } ··· 116 marginTop: 22, 117 marginHorizontal: 44, 118 backgroundColor: colors.blue3, 119 + }, 120 + btnCancel: { 121 + flexDirection: 'row', 122 + alignItems: 'center', 123 + justifyContent: 'center', 124 + borderRadius: 32, 125 + padding: 14, 126 + marginHorizontal: 20, 127 }, 128 })
+6 -4
src/view/com/util/forms/ToggleButton.tsx
··· 142 ]} 143 /> 144 </View> 145 - <Text type="button" style={[labelStyle, styles.label]}> 146 - {label} 147 - </Text> 148 </View> 149 </Button> 150 ) ··· 154 outer: { 155 flexDirection: 'row', 156 alignItems: 'center', 157 }, 158 circle: { 159 width: 42, ··· 161 borderRadius: 15, 162 padding: 4, 163 borderWidth: 1, 164 - marginRight: 10, 165 }, 166 circleFill: { 167 width: 16,
··· 142 ]} 143 /> 144 </View> 145 + {label === '' ? null : ( 146 + <Text type="button" style={[labelStyle, styles.label]}> 147 + {label} 148 + </Text> 149 + )} 150 </View> 151 </Button> 152 ) ··· 156 outer: { 157 flexDirection: 'row', 158 alignItems: 'center', 159 + gap: 10, 160 }, 161 circle: { 162 width: 42, ··· 164 borderRadius: 15, 165 padding: 4, 166 borderWidth: 1, 167 }, 168 circleFill: { 169 width: 16,
+6 -16
src/view/screens/Settings.tsx
··· 34 import {AccountData} from 'state/models/session' 35 import {useAnalytics} from 'lib/analytics' 36 import {NavigationProp} from 'lib/routes/types' 37 - import {pluralize} from 'lib/strings/helpers' 38 import {isDesktopWeb} from 'platform/detection' 39 40 type Props = NativeStackScreenProps<CommonNavigatorParams, 'Settings'> 41 export const SettingsScreen = withAuthRequired( ··· 54 light: {color: colors.blue3}, 55 dark: {color: colors.blue2}, 56 }) 57 const dangerBg = useCustomPalette<ViewStyle>({ 58 light: {backgroundColor: colors.red1}, 59 dark: {backgroundColor: colors.red7}, ··· 140 }, [store]) 141 142 return ( 143 - <View style={s.hContentRegion} testID="settingsScreen"> 144 <ViewHeader title="Settings" /> 145 <ScrollView 146 - style={s.hContentRegion} 147 contentContainerStyle={!isDesktopWeb && pal.viewLight} 148 scrollIndicatorInsets={{right: 1}}> 149 - <View style={styles.spacer20} /> 150 <View style={[s.flexRow, styles.heading]}> 151 <Text type="xl-bold" style={pal.text}> 152 Signed in as ··· 161 <Link 162 href={`/profile/${store.me.handle}`} 163 title="Your profile" 164 - noFeedback 165 - accessibilityLabel={`Signed in as ${store.me.handle}`} 166 - accessibilityHint="Double tap to sign out"> 167 <View style={[pal.view, styles.linkCard]}> 168 <View style={styles.avi}> 169 <UserAvatar size={40} avatar={store.me.avatar} /> ··· 231 Add account 232 </Text> 233 </TouchableOpacity> 234 - 235 <View style={styles.spacer20} /> 236 - 237 <Text type="xl-bold" style={[pal.text, styles.heading]}> 238 Invite a friend 239 </Text> ··· 301 Blocked accounts 302 </Text> 303 </Link> 304 - 305 <View style={styles.spacer20} /> 306 - 307 <Text type="xl-bold" style={[pal.text, styles.heading]}> 308 Advanced 309 </Text> ··· 338 Change my handle 339 </Text> 340 </TouchableOpacity> 341 - 342 <View style={styles.spacer20} /> 343 - 344 <Text type="xl-bold" style={[pal.text, styles.heading]}> 345 Danger zone 346 </Text> ··· 355 <FontAwesomeIcon 356 icon={['far', 'trash-can']} 357 style={dangerText as FontAwesomeIconStyle} 358 - size={21} 359 /> 360 </View> 361 <Text type="lg" style={dangerText}> 362 Delete my account 363 </Text> 364 </TouchableOpacity> 365 - 366 <View style={styles.spacer20} /> 367 - 368 <Text type="xl-bold" style={[pal.text, styles.heading]}> 369 Developer tools 370 </Text>
··· 34 import {AccountData} from 'state/models/session' 35 import {useAnalytics} from 'lib/analytics' 36 import {NavigationProp} from 'lib/routes/types' 37 import {isDesktopWeb} from 'platform/detection' 38 + import {pluralize} from 'lib/strings/helpers' 39 40 type Props = NativeStackScreenProps<CommonNavigatorParams, 'Settings'> 41 export const SettingsScreen = withAuthRequired( ··· 54 light: {color: colors.blue3}, 55 dark: {color: colors.blue2}, 56 }) 57 + 58 const dangerBg = useCustomPalette<ViewStyle>({ 59 light: {backgroundColor: colors.red1}, 60 dark: {backgroundColor: colors.red7}, ··· 141 }, [store]) 142 143 return ( 144 + <View style={[s.hContentRegion]} testID="settingsScreen"> 145 <ViewHeader title="Settings" /> 146 <ScrollView 147 + style={[s.hContentRegion]} 148 contentContainerStyle={!isDesktopWeb && pal.viewLight} 149 scrollIndicatorInsets={{right: 1}}> 150 <View style={[s.flexRow, styles.heading]}> 151 <Text type="xl-bold" style={pal.text}> 152 Signed in as ··· 161 <Link 162 href={`/profile/${store.me.handle}`} 163 title="Your profile" 164 + noFeedback> 165 <View style={[pal.view, styles.linkCard]}> 166 <View style={styles.avi}> 167 <UserAvatar size={40} avatar={store.me.avatar} /> ··· 229 Add account 230 </Text> 231 </TouchableOpacity> 232 <View style={styles.spacer20} /> 233 <Text type="xl-bold" style={[pal.text, styles.heading]}> 234 Invite a friend 235 </Text> ··· 297 Blocked accounts 298 </Text> 299 </Link> 300 <View style={styles.spacer20} /> 301 <Text type="xl-bold" style={[pal.text, styles.heading]}> 302 Advanced 303 </Text> ··· 332 Change my handle 333 </Text> 334 </TouchableOpacity> 335 <View style={styles.spacer20} /> 336 <Text type="xl-bold" style={[pal.text, styles.heading]}> 337 Danger zone 338 </Text> ··· 347 <FontAwesomeIcon 348 icon={['far', 'trash-can']} 349 style={dangerText as FontAwesomeIconStyle} 350 + size={18} 351 /> 352 </View> 353 <Text type="lg" style={dangerText}> 354 Delete my account 355 </Text> 356 </TouchableOpacity> 357 <View style={styles.spacer20} /> 358 <Text type="xl-bold" style={[pal.text, styles.heading]}> 359 Developer tools 360 </Text>
+10
yarn.lock
··· 4433 dependencies: 4434 tippy.js "^6.3.7" 4435 4436 "@tiptap/extension-history@^2.0.3": 4437 version "2.0.3" 4438 resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.0.3.tgz#8936c15aa46f2ddeada1c3d9abe2888d58d08c30" ··· 4819 version "18.16.3" 4820 resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.3.tgz#6bda7819aae6ea0b386ebc5b24bdf602f1b42b01" 4821 integrity sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q== 4822 4823 "@types/object.omit@^3.0.0": 4824 version "3.0.0"
··· 4433 dependencies: 4434 tippy.js "^6.3.7" 4435 4436 + "@tiptap/extension-hard-break@^2.0.3": 4437 + version "2.0.3" 4438 + resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-2.0.3.tgz#aa7805d825e5244bdccc508da18c781e231b2859" 4439 + integrity sha512-RCln6ARn16jvKTjhkcAD5KzYXYS0xRMc0/LrHeV8TKdCd4Yd0YYHe0PU4F9gAgAfPQn7Dgt4uTVJLN11ICl8sQ== 4440 + 4441 "@tiptap/extension-history@^2.0.3": 4442 version "2.0.3" 4443 resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.0.3.tgz#8936c15aa46f2ddeada1c3d9abe2888d58d08c30" ··· 4824 version "18.16.3" 4825 resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.3.tgz#6bda7819aae6ea0b386ebc5b24bdf602f1b42b01" 4826 integrity sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q== 4827 + 4828 + "@types/node@^18.16.2": 4829 + version "18.16.2" 4830 + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.2.tgz#2f610ea71034b3971c312192377f8a7178eb57f1" 4831 + integrity sha512-GQW/JL/5Fz/0I8RpeBG9lKp0+aNcXEaVL71c0D2Q0QHDTFvlYKT7an0onCUXj85anv7b4/WesqdfchLc0jtsCg== 4832 4833 "@types/object.omit@^3.0.0": 4834 version "3.0.0"