feat: mobile workspaces (#1093)

This commit is contained in:
Vardan Hakobyan
2022-06-21 15:42:43 +04:00
committed by GitHub
parent 1f903f17d1
commit 7d60dfee73
71 changed files with 599 additions and 317 deletions

14
packages/mobile/.eslintrc Normal file
View File

@@ -0,0 +1,14 @@
{
"env": {
"node": true,
"commonjs": true
},
"extends": ["@react-native-community", "plugin:react-hooks/recommended", "../../.eslintrc.json"],
"rules": {
"no-console": "warn",
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-var-requires": "off",
"eqeqeq": ["off"]
},
"ignorePatterns": ["metro.config.js"]
}

View File

@@ -1,28 +0,0 @@
module.exports = {
root: true,
extends: ['@react-native-community', 'prettier', '../../node_modules/@standardnotes/config/src/.eslintrc'],
parser: '@typescript-eslint/parser',
parserOptions: {
tsconfigRootDir: __dirname,
},
plugins: ['@typescript-eslint', 'prettier'],
ignorePatterns: ['.eslintrc.js', 'metro.config.js'],
overrides: [
{
files: ['*.ts', '*.tsx'],
rules: {
'@typescript-eslint/no-shadow': ['error'],
'no-shadow': 'off',
'no-undef': 'off',
'@typescript-eslint/no-explicit-any': 'warn',
'no-invalid-this': 'warn',
'no-console': 'warn',
eqeqeq: ['warn', 'smart'],
'no-void': 'off',
},
},
],
rules: {
'prettier/prettier': 'warn',
},
}

View File

@@ -1,11 +0,0 @@
module.exports = {
arrowParens: 'avoid',
bracketSpacing: true,
jsxSingleQuote: false,
quoteProps: 'as-needed',
semi: false,
singleQuote: true,
tabWidth: 2,
printWidth: 120,
trailingComma: 'all',
}

View File

@@ -1,4 +1,3 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const faker = require('faker') const faker = require('faker')
import { by, device, element, expect, waitFor } from 'detox' import { by, device, element, expect, waitFor } from 'detox'
@@ -13,14 +12,10 @@ export const expectToBeVisible = async (testedElement: Detox.IndexableNativeElem
const checkAfterReinstall = async () => { const checkAfterReinstall = async () => {
if (device.getPlatform() === 'ios') { if (device.getPlatform() === 'ios') {
const alertElement = element( const alertElement = element(by.label('Delete Local Data').and(by.type('_UIAlertControllerActionView')))
by.label('Delete Local Data').and(by.type('_UIAlertControllerActionView'))
)
const alertVisible = await expectToBeVisible(alertElement) const alertVisible = await expectToBeVisible(alertElement)
if (alertVisible) { if (alertVisible) {
await element( await element(by.label('Delete Local Data').and(by.type('_UIAlertControllerActionView'))).tap()
by.label('Delete Local Data').and(by.type('_UIAlertControllerActionView'))
).tap()
} }
} }
} }

View File

@@ -1,4 +1,3 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const helpers = require('../../Helpers') const helpers = require('../../Helpers')
import { by, device, element, expect, waitFor } from 'detox' import { by, device, element, expect, waitFor } from 'detox'
import { openSettingsScreen } from '../../Helpers' import { openSettingsScreen } from '../../Helpers'
@@ -45,9 +44,7 @@ fdescribe('Account section', () => {
await element(by.id('passwordField')).typeText(helpers.randomCredentials.password) await element(by.id('passwordField')).typeText(helpers.randomCredentials.password)
await element(by.id('otherOptionsButton')).tap() await element(by.id('otherOptionsButton')).tap()
await element(by.id('syncServerField')).clearText() await element(by.id('syncServerField')).clearText()
await element(by.id('syncServerField')).typeText( await element(by.id('syncServerField')).typeText(helpers.randomCredentials.syncServerUrl + '\n')
helpers.randomCredentials.syncServerUrl + '\n'
)
// wait for buttons to be visible after closing keyboard on smaller devices // wait for buttons to be visible after closing keyboard on smaller devices
await waitFor(element(by.id('registerButton'))) await waitFor(element(by.id('registerButton')))
.toBeVisible() .toBeVisible()
@@ -90,9 +87,7 @@ fdescribe('Account section', () => {
await element(by.id('passwordField')).typeText(helpers.randomCredentials.password) await element(by.id('passwordField')).typeText(helpers.randomCredentials.password)
await element(by.id('otherOptionsButton')).tap() await element(by.id('otherOptionsButton')).tap()
await element(by.id('syncServerField')).clearText() await element(by.id('syncServerField')).clearText()
await element(by.id('syncServerField')).typeText( await element(by.id('syncServerField')).typeText(helpers.randomCredentials.syncServerUrl + '\n')
helpers.randomCredentials.syncServerUrl + '\n'
)
// wait for button to be visible after keyboard close // wait for button to be visible after keyboard close
await waitFor(element(by.id('signInButton'))) await waitFor(element(by.id('signInButton')))
.toBeVisible() .toBeVisible()

View File

@@ -1,4 +1,3 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const helpers = require('../../Helpers') const helpers = require('../../Helpers')
import { by, device, element, expect } from 'detox' import { by, device, element, expect } from 'detox'

View File

@@ -21,6 +21,18 @@ if (__DEV__ === false) {
} }
/* eslint-enable no-console */ /* eslint-enable no-console */
const originalWarn = console.warn
console.warn = function filterWarnings(msg) {
const supressedWarnings = [
"[react-native-gesture-handler] Seems like you're using an old API with gesture components",
]
if (!supressedWarnings.some(entry => msg.includes(entry))) {
originalWarn.apply(console, arguments)
}
}
enableAndroidFontFix() enableAndroidFontFix()
AppRegistry.registerComponent(appName, () => App) AppRegistry.registerComponent(appName, () => App)

View File

@@ -11,9 +11,10 @@
"ios-dev": "react-native run-ios --scheme StandardNotesDev", "ios-dev": "react-native run-ios --scheme StandardNotesDev",
"ios-prod": "react-native run-ios --scheme StandardNotes", "ios-prod": "react-native run-ios --scheme StandardNotes",
"clear-cache": "watchman watch-del-all && rm -rf $TMPDIR/react-native-packager-cache-* && rm -rf $TMPDIR/metro-bundler-cache-*", "clear-cache": "watchman watch-del-all && rm -rf $TMPDIR/react-native-packager-cache-* && rm -rf $TMPDIR/metro-bundler-cache-*",
"lint": "yarn lint:eslint && yarn lint:prettier", "lint": "yarn eslint . --ext .ts,.tsx",
"lint:eslint": "yarn eslint . --ext .ts,.tsx --fix --quiet", "lint:fix": "yarn lint --fix",
"lint:prettier": "prettier ./src --write", "format": "prettier ./src",
"format:fix": "yarn format --write",
"build": "yarn android:bundle && yarn install:pods", "build": "yarn android:bundle && yarn install:pods",
"tsc": "tsc --noEmit", "tsc": "tsc --noEmit",
"start": "react-native start", "start": "react-native start",

View File

@@ -4,6 +4,7 @@ import { MobileApplication } from '@Lib/Application'
import { ApplicationGroup } from '@Lib/ApplicationGroup' import { ApplicationGroup } from '@Lib/ApplicationGroup'
import { navigationRef } from '@Lib/NavigationService' import { navigationRef } from '@Lib/NavigationService'
import { DefaultTheme, NavigationContainer } from '@react-navigation/native' import { DefaultTheme, NavigationContainer } from '@react-navigation/native'
import { ApplicationGroupContext } from '@Root/ApplicationGroupContext'
import { MobileThemeVariables } from '@Root/Style/Themes/styled-components' import { MobileThemeVariables } from '@Root/Style/Themes/styled-components'
import { ApplicationGroupEvent, DeinitMode, DeinitSource } from '@standardnotes/snjs' import { ApplicationGroupEvent, DeinitMode, DeinitSource } from '@standardnotes/snjs'
import { ThemeService, ThemeServiceContext } from '@Style/ThemeService' import { ThemeService, ThemeServiceContext } from '@Style/ThemeService'
@@ -68,7 +69,7 @@ const AppComponent: React.FC<{
setThemeServiceRef(themeServiceInstance) setThemeServiceRef(themeServiceInstance)
await application.prepareForLaunch({ await application.prepareForLaunch({
receiveChallenge: async challenge => { receiveChallenge: async (challenge) => {
application.promptForChallenge(challenge) application.promptForChallenge(challenge)
}, },
}) })
@@ -135,7 +136,7 @@ export const App = (props: { env: TEnvironment }) => {
const [appGroup, setAppGroup] = useState<ApplicationGroup>(() => createNewAppGroup()) const [appGroup, setAppGroup] = useState<ApplicationGroup>(() => createNewAppGroup())
useEffect(() => { useEffect(() => {
const removeAppChangeObserver = appGroup.addEventObserver(event => { const removeAppChangeObserver = appGroup.addEventObserver((event) => {
if (event === ApplicationGroupEvent.PrimaryApplicationSet) { if (event === ApplicationGroupEvent.PrimaryApplicationSet) {
const mobileApplication = appGroup.primaryApplication as MobileApplication const mobileApplication = appGroup.primaryApplication as MobileApplication
setApplication(mobileApplication) setApplication(mobileApplication)
@@ -145,15 +146,17 @@ export const App = (props: { env: TEnvironment }) => {
} }
}) })
return removeAppChangeObserver return removeAppChangeObserver
}, [appGroup, appGroup.primaryApplication, setAppGroup, createNewAppGroup]) }, [appGroup, appGroup.primaryApplication, createNewAppGroup])
if (!application) { if (!application) {
return null return null
} }
return ( return (
<ApplicationContext.Provider value={application}> <ApplicationGroupContext.Provider value={appGroup}>
<AppComponent env={props.env} key={application.Uuid} application={application} /> <ApplicationContext.Provider value={application}>
</ApplicationContext.Provider> <AppComponent env={props.env} key={application.Uuid} application={application} />
</ApplicationContext.Provider>
</ApplicationGroupContext.Provider>
) )
} }

View File

@@ -63,7 +63,7 @@ export const AppStackComponent = (props: ModalStackNavigationProp<'AppStack'>) =
const noteDrawerRef = useRef<DrawerLayout>(null) const noteDrawerRef = useRef<DrawerLayout>(null)
useEffect(() => { useEffect(() => {
const removeObserver = application?.getAppState().addStateChangeObserver(event => { const removeObserver = application?.getAppState().addStateChangeObserver((event) => {
if (event === AppStateType.EditorClosed) { if (event === AppStateType.EditorClosed) {
noteDrawerRef.current?.closeDrawer() noteDrawerRef.current?.closeDrawer()
if (!isInTabletMode && props.navigation.canGoBack()) { if (!isInTabletMode && props.navigation.canGoBack()) {
@@ -76,7 +76,7 @@ export const AppStackComponent = (props: ModalStackNavigationProp<'AppStack'>) =
}, [application, props.navigation, isInTabletMode]) }, [application, props.navigation, isInTabletMode])
useEffect(() => { useEffect(() => {
const removeObserver = application?.getStatusManager().addHeaderStatusObserver(messages => { const removeObserver = application?.getStatusManager().addHeaderStatusObserver((messages) => {
setNotesStatus(messages[SCREEN_NOTES]) setNotesStatus(messages[SCREEN_NOTES])
setComposeStatus(messages[SCREEN_COMPOSE]) setComposeStatus(messages[SCREEN_COMPOSE])
}) })
@@ -161,7 +161,7 @@ export const AppStackComponent = (props: ModalStackNavigationProp<'AppStack'>) =
const screenStatus = isInTabletMode ? composeStatus || notesStatus : notesStatus const screenStatus = isInTabletMode ? composeStatus || notesStatus : notesStatus
const title = route.params?.title ?? (children || '') const title = route.params?.title ?? (children || '')
const subtitle = [screenStatus?.status, route.params?.subTitle].filter(x => !!x).join(' • ') const subtitle = [screenStatus?.status, route.params?.subTitle].filter((x) => !!x).join(' • ')
return <HeaderTitleView title={title} subtitle={subtitle} subtitleColor={screenStatus?.color} /> return <HeaderTitleView title={title} subtitle={subtitle} subtitleColor={screenStatus?.color} />
}, },

View File

@@ -0,0 +1,4 @@
import { ApplicationGroup } from '@Lib/ApplicationGroup'
import React from 'react'
export const ApplicationGroupContext = React.createContext<ApplicationGroup | undefined>(undefined)

View File

@@ -55,7 +55,7 @@ const ButtonLabel = styled.Text<{ primary?: boolean }>`
color: ${({ theme, primary }) => { color: ${({ theme, primary }) => {
return primary ? theme.stylekitInfoContrastColor : theme.stylekitForegroundColor return primary ? theme.stylekitInfoContrastColor : theme.stylekitForegroundColor
}}; }};
font-size: ${props => props.theme.mainTextFontSize}px; font-size: ${(props) => props.theme.mainTextFontSize}px;
` `
export const Button: React.FC<Props> = ({ onPress, label, primary, fullWidth, last }: Props) => { export const Button: React.FC<Props> = ({ onPress, label, primary, fullWidth, last }: Props) => {

View File

@@ -17,7 +17,7 @@ type Props = {
} }
type ContainerProps = Pick<Props, 'maxHeight'> & TableCellProps type ContainerProps = Pick<Props, 'maxHeight'> & TableCellProps
const Container = styled(SectionedTableCellTouchableHighlight).attrs(props => ({ const Container = styled(SectionedTableCellTouchableHighlight).attrs((props) => ({
underlayColor: props.theme.stylekitBorderColor, underlayColor: props.theme.stylekitBorderColor,
}))<ContainerProps>` }))<ContainerProps>`
padding-top: ${12}px; padding-top: ${12}px;
@@ -32,9 +32,9 @@ const ButtonContainer = styled.View``
type ButtonLabelProps = Pick<Props, 'leftAligned' | 'bold' | 'disabled' | 'important'> type ButtonLabelProps = Pick<Props, 'leftAligned' | 'bold' | 'disabled' | 'important'>
const ButtonLabel = styled.Text<ButtonLabelProps>` const ButtonLabel = styled.Text<ButtonLabelProps>`
text-align: ${props => (props.leftAligned ? 'left' : 'center')}; text-align: ${(props) => (props.leftAligned ? 'left' : 'center')};
text-align-vertical: center; text-align-vertical: center;
color: ${props => { color: ${(props) => {
let color = Platform.OS === 'android' ? props.theme.stylekitForegroundColor : props.theme.stylekitInfoColor let color = Platform.OS === 'android' ? props.theme.stylekitForegroundColor : props.theme.stylekitInfoColor
if (props.disabled) { if (props.disabled) {
color = 'gray' color = 'gray'
@@ -43,7 +43,7 @@ const ButtonLabel = styled.Text<ButtonLabelProps>`
} }
return color return color
}}; }};
font-size: ${props => props.theme.mainTextFontSize}px; font-size: ${(props) => props.theme.mainTextFontSize}px;
${({ bold }) => ${({ bold }) =>
bold && bold &&
css` css`
@@ -56,7 +56,7 @@ const ButtonLabel = styled.Text<ButtonLabelProps>`
`} `}
` `
export const ButtonCell: React.FC<Props> = props => ( export const ButtonCell: React.FC<Props> = (props) => (
<Container <Container
first={props.first} first={props.first}
last={props.last} last={props.last}

View File

@@ -7,10 +7,10 @@ type Props = {
} }
export const Circle = styled.View<Props>` export const Circle = styled.View<Props>`
width: ${props => props.size ?? 12}px; width: ${(props) => props.size ?? 12}px;
height: ${props => props.size ?? 12}px; height: ${(props) => props.size ?? 12}px;
border-radius: ${props => (props.size ?? 12) / 2}px; border-radius: ${(props) => (props.size ?? 12) / 2}px;
background-color: ${props => props.backgroundColor}; background-color: ${(props) => props.backgroundColor};
border-color: ${props => props.borderColor}; border-color: ${(props) => props.borderColor};
border-width: 1px; border-width: 1px;
` `

View File

@@ -9,14 +9,14 @@ type Props = {
} }
const Container = styled.View` const Container = styled.View`
/* background-color: ${props => props.theme.stylekitContrastBackgroundColor}; */ /* background-color: ${(props) => props.theme.stylekitContrastBackgroundColor}; */
flex: 1; flex: 1;
justify-content: center; justify-content: center;
${Platform.OS === 'android' && 'align-items: flex-start; min-width: 100px;'} ${Platform.OS === 'android' && 'align-items: flex-start; min-width: 100px;'}
` `
const Title = styled.Text` const Title = styled.Text`
color: ${props => props.theme.stylekitForegroundColor}; color: ${(props) => props.theme.stylekitForegroundColor};
font-weight: bold; font-weight: bold;
font-size: 18px; font-size: 18px;
text-align: center; text-align: center;
@@ -25,13 +25,13 @@ const SubTitle = styled.Text.attrs(() => ({
adjustsFontSizeToFit: true, adjustsFontSizeToFit: true,
numberOfLines: 1, numberOfLines: 1,
}))<{ color?: string }>` }))<{ color?: string }>`
color: ${props => props.color ?? props.theme.stylekitForegroundColor}; color: ${(props) => props.color ?? props.theme.stylekitForegroundColor};
opacity: ${props => (props.color ? 1 : 0.6)}; opacity: ${(props) => (props.color ? 1 : 0.6)};
font-size: ${Platform.OS === 'android' ? 13 : 12}px; font-size: ${Platform.OS === 'android' ? 13 : 12}px;
${Platform.OS === 'ios' && 'text-align: center'} ${Platform.OS === 'ios' && 'text-align: center'}
` `
export const HeaderTitleView: React.FC<Props> = props => ( export const HeaderTitleView: React.FC<Props> = (props) => (
<Container> <Container>
<Title>{props.title}</Title> <Title>{props.title}</Title>
{props.subtitle && props.subtitle.length > 0 ? ( {props.subtitle && props.subtitle.length > 0 ? (

View File

@@ -17,19 +17,19 @@ const Container = styled.View<Pick<Props, 'backgroundColor'>>`
/* flex-grow: 0; */ /* flex-grow: 0; */
justify-content: space-between; justify-content: space-between;
flex-direction: row; flex-direction: row;
padding-right: ${props => props.theme.paddingLeft}px; padding-right: ${(props) => props.theme.paddingLeft}px;
padding-bottom: 10px; padding-bottom: 10px;
padding-top: 10px; padding-top: 10px;
background-color: ${props => props.backgroundColor ?? props.theme.stylekitBackgroundColor}; background-color: ${(props) => props.backgroundColor ?? props.theme.stylekitBackgroundColor};
` `
const TitleContainer = styled.View`` const TitleContainer = styled.View``
const Title = styled.Text<Pick<Props, 'tinted'>>` const Title = styled.Text<Pick<Props, 'tinted'>>`
background-color: ${props => props.theme.stylekitBackgroundColor}; background-color: ${(props) => props.theme.stylekitBackgroundColor};
font-size: ${props => { font-size: ${(props) => {
return Platform.OS === 'android' ? props.theme.mainTextFontSize - 2 : props.theme.mainTextFontSize - 4 return Platform.OS === 'android' ? props.theme.mainTextFontSize - 2 : props.theme.mainTextFontSize - 4
}}px; }}px;
padding-left: ${props => props.theme.paddingLeft}px; padding-left: ${(props) => props.theme.paddingLeft}px;
color: ${props => { color: ${(props) => {
if (props.tinted) { if (props.tinted) {
return props.theme.stylekitInfoColor return props.theme.stylekitInfoColor
} }
@@ -39,11 +39,11 @@ const Title = styled.Text<Pick<Props, 'tinted'>>`
font-weight: ${Platform.OS === 'android' ? 'bold' : 'normal'}; font-weight: ${Platform.OS === 'android' ? 'bold' : 'normal'};
` `
const SubTitle = styled.Text` const SubTitle = styled.Text`
background-color: ${props => props.theme.stylekitBackgroundColor}; background-color: ${(props) => props.theme.stylekitBackgroundColor};
font-size: ${props => props.theme.mainTextFontSize - 5}px; font-size: ${(props) => props.theme.mainTextFontSize - 5}px;
margin-top: 4px; margin-top: 4px;
padding-left: ${props => props.theme.paddingLeft}px; padding-left: ${(props) => props.theme.paddingLeft}px;
color: ${props => props.theme.stylekitNeutralColor}; color: ${(props) => props.theme.stylekitNeutralColor};
` `
const ButtonContainer = styled.TouchableOpacity` const ButtonContainer = styled.TouchableOpacity`
flex: 1; flex: 1;
@@ -51,10 +51,10 @@ const ButtonContainer = styled.TouchableOpacity`
justify-content: center; justify-content: center;
` `
const Button = styled.Text` const Button = styled.Text`
color: ${props => props.theme.stylekitInfoColor}; color: ${(props) => props.theme.stylekitInfoColor};
` `
export const SectionHeader: React.FC<Props> = props => ( export const SectionHeader: React.FC<Props> = (props) => (
<Container> <Container>
<TitleContainer> <TitleContainer>
{!!props.title && ( {!!props.title && (

View File

@@ -22,7 +22,7 @@ type Props = {
last?: boolean last?: boolean
} }
const TouchableContainer = styled(SectionedTableCellTouchableHighlight).attrs(props => ({ const TouchableContainer = styled(SectionedTableCellTouchableHighlight).attrs((props) => ({
underlayColor: props.theme.stylekitBorderColor, underlayColor: props.theme.stylekitBorderColor,
}))` }))`
flex-direction: column; flex-direction: column;
@@ -33,7 +33,7 @@ const TouchableContainer = styled(SectionedTableCellTouchableHighlight).attrs(pr
` `
const ContentContainer = styled.View<Pick<Props, 'leftAlignIcon'>>` const ContentContainer = styled.View<Pick<Props, 'leftAlignIcon'>>`
flex: 1; flex: 1;
justify-content: ${props => { justify-content: ${(props) => {
return props.leftAlignIcon ? 'flex-start' : 'space-between' return props.leftAlignIcon ? 'flex-start' : 'space-between'
}}; }};
flex-direction: row; flex-direction: row;
@@ -46,7 +46,7 @@ const IconContainer = styled.View`
type LabelProps = Pick<Props, 'bold' | 'tinted' | 'dimmed' | 'selected' | 'color'> type LabelProps = Pick<Props, 'bold' | 'tinted' | 'dimmed' | 'selected' | 'color'>
const Label = styled.Text<LabelProps>` const Label = styled.Text<LabelProps>`
min-width: 80%; min-width: 80%;
color: ${props => { color: ${(props) => {
let color = props.theme.stylekitForegroundColor let color = props.theme.stylekitForegroundColor
if (props.tinted) { if (props.tinted) {
color = props.theme.stylekitInfoColor color = props.theme.stylekitInfoColor
@@ -59,7 +59,7 @@ const Label = styled.Text<LabelProps>`
} }
return color return color
}}; }};
font-size: ${props => props.theme.mainTextFontSize}px; font-size: ${(props) => props.theme.mainTextFontSize}px;
${({ bold, selected }) => ${({ bold, selected }) =>
((selected && selected() === true) || bold) && ((selected && selected() === true) || bold) &&
css` css`
@@ -67,7 +67,7 @@ const Label = styled.Text<LabelProps>`
`}; `};
` `
export const SectionedAccessoryTableCell: React.FC<Props> = props => { export const SectionedAccessoryTableCell: React.FC<Props> = (props) => {
const themeContext = useContext(ThemeContext) const themeContext = useContext(ThemeContext)
const onPress = () => { const onPress = () => {
if (props.disabled) { if (props.disabled) {

View File

@@ -15,11 +15,11 @@ type Props = {
type ContainerProps = Omit<Props, 'title' | 'onPress' | 'options'> type ContainerProps = Omit<Props, 'title' | 'onPress' | 'options'>
export const Container = styled.View<ContainerProps>` export const Container = styled.View<ContainerProps>`
border-bottom-color: ${props => props.theme.stylekitBorderColor}; border-bottom-color: ${(props) => props.theme.stylekitBorderColor};
border-bottom-width: 1px; border-bottom-width: 1px;
padding-left: ${props => props.theme.paddingLeft}px; padding-left: ${(props) => props.theme.paddingLeft}px;
padding-right: ${props => props.theme.paddingLeft}px; padding-right: ${(props) => props.theme.paddingLeft}px;
background-color: ${props => props.theme.stylekitBackgroundColor}; background-color: ${(props) => props.theme.stylekitBackgroundColor};
${({ first, theme }) => ${({ first, theme }) =>
first && first &&
css` css`
@@ -38,9 +38,9 @@ export const Container = styled.View<ContainerProps>`
` `
const Title = styled.Text<{ leftAligned?: boolean }>` const Title = styled.Text<{ leftAligned?: boolean }>`
font-size: ${props => props.theme.mainTextFontSize}px; font-size: ${(props) => props.theme.mainTextFontSize}px;
color: ${props => props.theme.stylekitForegroundColor}; color: ${(props) => props.theme.stylekitForegroundColor};
text-align: ${props => (props.leftAligned ? 'left' : 'center')}; text-align: ${(props) => (props.leftAligned ? 'left' : 'center')};
width: 42%; width: 42%;
min-width: 0px; min-width: 0px;
` `
@@ -50,13 +50,13 @@ const OptionsContainer = styled.View`
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background-color: ${props => props.theme.stylekitBackgroundColor}; background-color: ${(props) => props.theme.stylekitBackgroundColor};
` `
const ButtonTouchable = styled.TouchableHighlight.attrs(props => ({ const ButtonTouchable = styled.TouchableHighlight.attrs((props) => ({
underlayColor: props.theme.stylekitBorderColor, underlayColor: props.theme.stylekitBorderColor,
}))` }))`
border-left-color: ${props => props.theme.stylekitBorderColor}; border-left-color: ${(props) => props.theme.stylekitBorderColor};
border-left-width: 1px; border-left-width: 1px;
flex-grow: 1; flex-grow: 1;
padding: 10px; padding: 10px;
@@ -64,7 +64,7 @@ const ButtonTouchable = styled.TouchableHighlight.attrs(props => ({
` `
const ButtonTitle = styled.Text<{ selected: boolean }>` const ButtonTitle = styled.Text<{ selected: boolean }>`
color: ${props => { color: ${(props) => {
return props.selected ? props.theme.stylekitInfoColor : props.theme.stylekitNeutralColor return props.selected ? props.theme.stylekitInfoColor : props.theme.stylekitNeutralColor
}}; }};
font-size: 16px; font-size: 16px;
@@ -72,11 +72,11 @@ const ButtonTitle = styled.Text<{ selected: boolean }>`
width: 100%; width: 100%;
` `
export const SectionedOptionsTableCell: React.FC<Props> = props => ( export const SectionedOptionsTableCell: React.FC<Props> = (props) => (
<Container first={props.first}> <Container first={props.first}>
<Title leftAligned={props.leftAligned}>{props.title}</Title> <Title leftAligned={props.leftAligned}>{props.title}</Title>
<OptionsContainer> <OptionsContainer>
{props.options.map(option => { {props.options.map((option) => {
return ( return (
<ButtonTouchable key={option.title} onPress={() => props.onPress(option)}> <ButtonTouchable key={option.title} onPress={() => props.onPress(option)}>
<ButtonTitle selected={option.selected}>{option.title}</ButtonTitle> <ButtonTitle selected={option.selected}>{option.title}</ButtonTitle>

View File

@@ -9,12 +9,12 @@ export type Props = {
} }
export const SectionedTableCell = styled.View<Props>` export const SectionedTableCell = styled.View<Props>`
border-bottom-color: ${props => props.theme.stylekitBorderColor}; border-bottom-color: ${(props) => props.theme.stylekitBorderColor};
border-bottom-width: 1px; border-bottom-width: 1px;
padding-left: ${props => props.theme.paddingLeft}px; padding-left: ${(props) => props.theme.paddingLeft}px;
padding-right: ${props => props.theme.paddingLeft}px; padding-right: ${(props) => props.theme.paddingLeft}px;
padding-bottom: ${props => (props.textInputCell ? 0 : 12)}px; padding-bottom: ${(props) => (props.textInputCell ? 0 : 12)}px;
background-color: ${props => props.theme.stylekitBackgroundColor}; background-color: ${(props) => props.theme.stylekitBackgroundColor};
${({ first, theme }) => ${({ first, theme }) =>
first && first &&
css` css`
@@ -34,12 +34,12 @@ export const SectionedTableCell = styled.View<Props>`
` `
export const SectionedTableCellTouchableHighlight = styled.TouchableHighlight<Props>` export const SectionedTableCellTouchableHighlight = styled.TouchableHighlight<Props>`
border-bottom-color: ${props => props.theme.stylekitBorderColor}; border-bottom-color: ${(props) => props.theme.stylekitBorderColor};
border-bottom-width: 1px; border-bottom-width: 1px;
padding-left: ${props => props.theme.paddingLeft}px; padding-left: ${(props) => props.theme.paddingLeft}px;
padding-right: ${props => props.theme.paddingLeft}px; padding-right: ${(props) => props.theme.paddingLeft}px;
padding-bottom: ${props => (props.textInputCell ? 0 : 12)}px; padding-bottom: ${(props) => (props.textInputCell ? 0 : 12)}px;
background-color: ${props => props.theme.stylekitBackgroundColor}; background-color: ${(props) => props.theme.stylekitBackgroundColor};
${({ first, theme }) => ${({ first, theme }) =>
first && first &&
css` css`

View File

@@ -3,5 +3,5 @@ import styled from 'styled-components/native'
export const TableSection = styled.View` export const TableSection = styled.View`
margin-top: 10px; margin-top: 10px;
margin-bottom: 10px; margin-bottom: 10px;
background-color: ${props => props.theme.stylekitBackgroundColor}; background-color: ${(props) => props.theme.stylekitBackgroundColor};
` `

View File

@@ -12,7 +12,7 @@ export const ToastWrapper: FC = () => {
const { updateProgressBar, progressBarWidth } = useProgressBar() const { updateProgressBar, progressBarWidth } = useProgressBar()
const toastStyles: ToastConfig = { const toastStyles: ToastConfig = {
info: props => { info: (props) => {
const percentComplete = props.props?.percentComplete || 0 const percentComplete = props.props?.percentComplete || 0
updateProgressBar(percentComplete) updateProgressBar(percentComplete)
@@ -35,13 +35,13 @@ export const ToastWrapper: FC = () => {
</View> </View>
) )
}, },
success: props => { success: (props) => {
const percentComplete = props.props?.percentComplete || 0 const percentComplete = props.props?.percentComplete || 0
updateProgressBar(percentComplete) updateProgressBar(percentComplete)
return <SuccessToast {...props} style={styles(props.props).success} /> return <SuccessToast {...props} style={styles(props.props).success} />
}, },
error: props => { error: (props) => {
const percentComplete = props.props?.percentComplete || 0 const percentComplete = props.props?.percentComplete || 0
updateProgressBar(percentComplete) updateProgressBar(percentComplete)

View File

@@ -560,7 +560,7 @@ export const useFiles = ({ note }: Props) => {
return return
} }
if (shouldAttachToNote(currentTab)) { if (shouldAttachToNote(currentTab)) {
uploadedFiles.forEach(file => attachFileToNote(file, false)) uploadedFiles.forEach((file) => attachFileToNote(file, false))
} }
}, },
}, },
@@ -587,7 +587,7 @@ export const useFiles = ({ note }: Props) => {
}, },
}, },
] ]
const osSpecificOptions = Platform.OS === 'android' ? options.filter(option => option.key !== 'library') : options const osSpecificOptions = Platform.OS === 'android' ? options.filter((option) => option.key !== 'library') : options
showActionSheet({ showActionSheet({
title: 'Choose action', title: 'Choose action',
options: osSpecificOptions, options: osSpecificOptions,
@@ -783,7 +783,7 @@ export const useFiles = ({ note }: Props) => {
}, },
] ]
const osDependentActions = const osDependentActions =
Platform.OS === 'ios' ? actions.filter(action => action.text !== 'Download') : [...actions] Platform.OS === 'ios' ? actions.filter((action) => action.text !== 'Download') : [...actions]
showActionSheet({ showActionSheet({
title: file.name, title: file.name,
options: osDependentActions, options: osDependentActions,

View File

@@ -0,0 +1,8 @@
import { ApplicationGroup } from '@Lib/ApplicationGroup'
import { ApplicationGroupContext } from '@Root/ApplicationGroupContext'
import { useContext } from 'react'
export const useSafeApplicationGroupContext = () => {
const applicationGroupContext = useContext(ApplicationGroupContext) as ApplicationGroup
return applicationGroupContext
}

View File

@@ -10,7 +10,7 @@ export class MobileAlertService extends AlertService {
return goBack return goBack
} }
alert(text: string, title: string, closeButtonText?: string) { alert(text: string, title: string, closeButtonText?: string) {
return new Promise<void>(resolve => { return new Promise<void>((resolve) => {
// On iOS, confirm should go first. On Android, cancel should go first. // On iOS, confirm should go first. On Android, cancel should go first.
const buttons = [ const buttons = [
{ {

View File

@@ -155,7 +155,7 @@ export class ApplicationState extends ApplicationService {
const savedTag = const savedTag =
(this.application.items.findItem(savedTagUuid) as SNTag) || (this.application.items.findItem(savedTagUuid) as SNTag) ||
this.application.items.getSmartViews().find(tag => tag.uuid === savedTagUuid) this.application.items.getSmartViews().find((tag) => tag.uuid === savedTagUuid)
if (savedTag) { if (savedTag) {
this.setSelectedTag(savedTag, false) this.setSelectedTag(savedTag, false)
this.selectedTagRestored = true this.selectedTagRestored = true
@@ -288,7 +288,7 @@ export class ApplicationState extends ApplicationService {
if (note && note.conflictOf) { if (note && note.conflictOf) {
void InteractionManager.runAfterInteractions(() => { void InteractionManager.runAfterInteractions(() => {
void this.application?.mutator.changeAndSaveItem(note, mutator => { void this.application?.mutator.changeAndSaveItem(note, (mutator) => {
mutator.conflictOf = undefined mutator.conflictOf = undefined
}) })
}) })
@@ -328,7 +328,7 @@ export class ApplicationState extends ApplicationService {
} }
} }
private keyboardDidShow: KeyboardEventListener = e => { private keyboardDidShow: KeyboardEventListener = (e) => {
this.keyboardHeight = e.endCoordinates.height this.keyboardHeight = e.endCoordinates.height
this.notifyEventObservers(AppStateEventType.KeyboardChangeEvent) this.notifyEventObservers(AppStateEventType.KeyboardChangeEvent)
} }
@@ -353,7 +353,7 @@ export class ApplicationState extends ApplicationService {
[ContentType.Note, ContentType.Tag], [ContentType.Note, ContentType.Tag],
async ({ changed, inserted, removed, source }) => { async ({ changed, inserted, removed, source }) => {
if (source === PayloadEmitSource.PreSyncSave || source === PayloadEmitSource.RemoteRetrieved) { if (source === PayloadEmitSource.PreSyncSave || source === PayloadEmitSource.RemoteRetrieved) {
const removedNotes = removed.filter(i => i.content_type === ContentType.Note) const removedNotes = removed.filter((i) => i.content_type === ContentType.Note)
for (const removedNote of removedNotes) { for (const removedNote of removedNotes) {
const editor = this.editorForNote(removedNote.uuid) const editor = this.editorForNote(removedNote.uuid)
if (editor) { if (editor) {
@@ -361,7 +361,7 @@ export class ApplicationState extends ApplicationService {
} }
} }
const notes = [...changed, ...inserted].filter(candidate => candidate.content_type === ContentType.Note) const notes = [...changed, ...inserted].filter((candidate) => candidate.content_type === ContentType.Note)
const isBrowswingTrashedNotes = const isBrowswingTrashedNotes =
this.selectedTag instanceof SmartView && this.selectedTag.uuid === SystemViewId.TrashedNotes this.selectedTag instanceof SmartView && this.selectedTag.uuid === SystemViewId.TrashedNotes
@@ -384,7 +384,7 @@ export class ApplicationState extends ApplicationService {
} }
if (this.selectedTag) { if (this.selectedTag) {
const matchingTag = [...changed, ...inserted].find(candidate => candidate.uuid === this.selectedTag.uuid) const matchingTag = [...changed, ...inserted].find((candidate) => candidate.uuid === this.selectedTag.uuid)
if (matchingTag) { if (matchingTag) {
this.selectedTag = matchingTag as SNTag this.selectedTag = matchingTag as SNTag
} }
@@ -397,7 +397,7 @@ export class ApplicationState extends ApplicationService {
* Registers for MobileApplication events * Registers for MobileApplication events
*/ */
private handleApplicationEvents() { private handleApplicationEvents() {
this.removeAppEventObserver = this.application.addEventObserver(async eventName => { this.removeAppEventObserver = this.application.addEventObserver(async (eventName) => {
switch (eventName) { switch (eventName) {
case ApplicationEvent.LocalDataIncrementalLoad: case ApplicationEvent.LocalDataIncrementalLoad:
case ApplicationEvent.LocalDataLoaded: { case ApplicationEvent.LocalDataLoaded: {
@@ -441,7 +441,7 @@ export class ApplicationState extends ApplicationService {
* @returns tags that are referencing note * @returns tags that are referencing note
*/ */
public getNoteTags(note: SNNote) { public getNoteTags(note: SNNote) {
return this.application.items.itemsReferencingItem(note).filter(ref => { return this.application.items.itemsReferencingItem(note).filter((ref) => {
return ref.content_type === ContentType.Tag return ref.content_type === ContentType.Tag
}) as SNTag[] }) as SNTag[]
} }
@@ -453,7 +453,7 @@ export class ApplicationState extends ApplicationService {
if (tag instanceof SmartView) { if (tag instanceof SmartView) {
return this.application.items.notesMatchingSmartView(tag) return this.application.items.notesMatchingSmartView(tag)
} else { } else {
return this.application.items.referencesForItem(tag).filter(ref => { return this.application.items.referencesForItem(tag).filter((ref) => {
return ref.content_type === ContentType.Note return ref.content_type === ContentType.Note
}) as SNNote[] }) as SNNote[]
} }

View File

@@ -61,13 +61,13 @@ export class BackupsService extends ApplicationService {
} }
private async exportIOS(filename: string, data: string) { private async exportIOS(filename: string, data: string) {
return new Promise<boolean>(resolve => { return new Promise<boolean>((resolve) => {
void (this.application! as MobileApplication).getAppState().performActionWithoutStateChangeImpact(async () => { void (this.application! as MobileApplication).getAppState().performActionWithoutStateChangeImpact(async () => {
Share.share({ Share.share({
title: filename, title: filename,
message: data, message: data,
}) })
.then(result => { .then((result) => {
resolve(result.action !== Share.dismissedAction) resolve(result.action !== Share.dismissedAction)
}) })
.catch(() => { .catch(() => {
@@ -98,7 +98,7 @@ export class BackupsService extends ApplicationService {
// success // success
return true return true
}) })
.catch(error => { .catch((error) => {
console.error('Error opening file', error) console.error('Error opening file', error)
return false return false
}) })
@@ -119,7 +119,7 @@ export class BackupsService extends ApplicationService {
} }
private async exportViaEmailAndroid(data: string, filename: string) { private async exportViaEmailAndroid(data: string, filename: string) {
return new Promise<boolean>(resolve => { return new Promise<boolean>((resolve) => {
const fileType = '.json' // Android creates a tmp file and expects dot with extension const fileType = '.json' // Android creates a tmp file and expects dot with extension
let resolved = false let resolved = false

View File

@@ -18,7 +18,7 @@ import { Base64 } from 'js-base64'
import RNFS, { DocumentDirectoryPath } from 'react-native-fs' import RNFS, { DocumentDirectoryPath } from 'react-native-fs'
import StaticServer from 'react-native-static-server' import StaticServer from 'react-native-static-server'
import { unzip } from 'react-native-zip-archive' import { unzip } from 'react-native-zip-archive'
import { componentsCdn, version, name } from '../../package.json' import { componentsCdn, name, version } from '../../package.json'
import { MobileThemeContent } from '../Style/MobileTheme' import { MobileThemeContent } from '../Style/MobileTheme'
import { IsDev } from './Utils' import { IsDev } from './Utils'
@@ -356,7 +356,7 @@ export class ComponentManager extends SNComponentManager {
} }
export async function associateComponentWithNote(application: SNApplication, component: SNComponent, note: SNNote) { export async function associateComponentWithNote(application: SNApplication, component: SNComponent, note: SNNote) {
return application.mutator.changeItem<ComponentMutator>(component, mutator => { return application.mutator.changeItem<ComponentMutator>(component, (mutator) => {
mutator.removeDisassociatedItemId(note.uuid) mutator.removeDisassociatedItemId(note.uuid)
mutator.associateWithItem(note.uuid) mutator.associateWithItem(note.uuid)
}) })

View File

@@ -1,5 +1,5 @@
import SNReactNative from '@standardnotes/react-native-utils' import SNReactNative from '@standardnotes/react-native-utils'
import { ApplicationService, ButtonType, isNullOrUndefined, StorageValueModes } from '@standardnotes/snjs' import { ApplicationService, ButtonType, StorageValueModes } from '@standardnotes/snjs'
import { MobileDeviceInterface } from './Interface' import { MobileDeviceInterface } from './Interface'
const FIRST_RUN_KEY = 'first_run' const FIRST_RUN_KEY = 'first_run'
@@ -14,7 +14,7 @@ export class InstallationService extends ApplicationService {
} }
async markApplicationAsRan() { async markApplicationAsRan() {
return this.application?.setValue(FIRST_RUN_KEY, false, StorageValueModes.Nonwrapped) return this.application.deviceInterface.setRawStorageValue(FIRST_RUN_KEY, 'false')
} }
/** /**
@@ -22,24 +22,20 @@ export class InstallationService extends ApplicationService {
* AsyncStorage failures, we want to confirm with the user before deleting anything. * AsyncStorage failures, we want to confirm with the user before deleting anything.
*/ */
async needsWipe() { async needsWipe() {
const hasNormalKeys = this.application?.hasAccount() || this.application?.hasPasscode() const hasAccountOrPasscode = this.application.hasAccount() || this.application?.hasPasscode()
const deviceInterface = this.application?.deviceInterface as MobileDeviceInterface const deviceInterface = this.application.deviceInterface as MobileDeviceInterface
const keychainKey = await deviceInterface?.getRawKeychainValue() const keychainKey = await deviceInterface.getNamespacedKeychainValue(this.application.identifier)
const hasKeychainValue = !(
isNullOrUndefined(keychainKey) ||
(typeof keychainKey === 'object' && Object.keys(keychainKey).length === 0)
)
const firstRunKey = await this.application?.getValue(FIRST_RUN_KEY, StorageValueModes.Nonwrapped) const hasKeychainValue = keychainKey != undefined
let firstRunKeyMissing = isNullOrUndefined(firstRunKey)
/* const firstRunKey = await this.application.deviceInterface.getRawStorageValue(FIRST_RUN_KEY)
* Because of migration failure first run key might not be in non wrapped storage let firstRunKeyMissing = firstRunKey == undefined
*/
if (firstRunKeyMissing) { if (firstRunKeyMissing) {
const fallbackFirstRunValue = await this.application?.deviceInterface?.getRawStorageValue(FIRST_RUN_KEY) const fallbackFirstRunValue = await this.application.getValue(FIRST_RUN_KEY, StorageValueModes.Nonwrapped)
firstRunKeyMissing = isNullOrUndefined(fallbackFirstRunValue) firstRunKeyMissing = fallbackFirstRunValue == undefined
} }
return !hasNormalKeys && hasKeychainValue && firstRunKeyMissing
return !hasAccountOrPasscode && hasKeychainValue && firstRunKeyMissing
} }
/** /**

View File

@@ -1,4 +1,5 @@
import AsyncStorage from '@react-native-community/async-storage' import AsyncStorage from '@react-native-community/async-storage'
import SNReactNative from '@standardnotes/react-native-utils'
import { import {
ApplicationIdentifier, ApplicationIdentifier,
DeviceInterface, DeviceInterface,
@@ -32,7 +33,7 @@ const showLoadFailForItemIds = (failedItemIds: string[]) => {
let text = let text =
'The following items could not be loaded. This may happen if you are in low-memory conditions, or if the note is very large in size. We recommend breaking up large notes into smaller chunks using the desktop or web app.\n\nItems:\n' 'The following items could not be loaded. This may happen if you are in low-memory conditions, or if the note is very large in size. We recommend breaking up large notes into smaller chunks using the desktop or web app.\n\nItems:\n'
let index = 0 let index = 0
text += failedItemIds.map(id => { text += failedItemIds.map((id) => {
let result = id let result = id
if (index !== failedItemIds.length - 1) { if (index !== failedItemIds.length - 1) {
result += '\n' result += '\n'
@@ -79,8 +80,8 @@ export class MobileDeviceInterface implements DeviceInterface {
private async getAllDatabaseKeys(identifier: ApplicationIdentifier) { private async getAllDatabaseKeys(identifier: ApplicationIdentifier) {
const keys = await AsyncStorage.getAllKeys() const keys = await AsyncStorage.getAllKeys()
const filtered = keys.filter(key => { const filtered = keys.filter((key) => {
return key.includes(this.getDatabaseKeyPrefix(identifier)) return key.startsWith(this.getDatabaseKeyPrefix(identifier))
}) })
return filtered return filtered
} }
@@ -205,7 +206,7 @@ export class MobileDeviceInterface implements DeviceInterface {
return return
} }
await Promise.all( await Promise.all(
payloads.map(item => { payloads.map((item) => {
return AsyncStorage.setItem(this.keyForPayloadId(item.uuid, identifier), JSON.stringify(item)) return AsyncStorage.setItem(this.keyForPayloadId(item.uuid, identifier), JSON.stringify(item))
}), }),
) )
@@ -294,7 +295,7 @@ export class MobileDeviceInterface implements DeviceInterface {
} }
Linking.canOpenURL(url) Linking.canOpenURL(url)
.then(supported => { .then((supported) => {
if (!supported) { if (!supported) {
showAlert() showAlert()
return return
@@ -306,15 +307,16 @@ export class MobileDeviceInterface implements DeviceInterface {
} }
async clearAllDataFromDevice(_workspaceIdentifiers: string[]): Promise<{ killsApplication: boolean }> { async clearAllDataFromDevice(_workspaceIdentifiers: string[]): Promise<{ killsApplication: boolean }> {
await this.clearRawKeychainValue()
await this.removeAllRawStorageValues() await this.removeAllRawStorageValues()
await this.clearRawKeychainValue()
return { killsApplication: false } return { killsApplication: false }
} }
// eslint-disable-next-line @typescript-eslint/no-empty-function performSoftReset() {
performSoftReset() {} SNReactNative.exitApp()
}
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
performHardReset() {} performHardReset() {}

View File

@@ -30,7 +30,7 @@ export const useSignedIn = (signedInCallback?: () => void, signedOutCallback?: (
} }
} }
void getSignedIn() void getSignedIn()
const removeSignedInObserver = application.addEventObserver(async event => { const removeSignedInObserver = application.addEventObserver(async (event) => {
if (event === ApplicationEvent.Launched) { if (event === ApplicationEvent.Launched) {
void getSignedIn() void getSignedIn()
} }
@@ -74,7 +74,7 @@ export const useOutOfSync = () => {
}, [application]) }, [application])
React.useEffect(() => { React.useEffect(() => {
const removeSignedInObserver = application.addEventObserver(async event => { const removeSignedInObserver = application.addEventObserver(async (event) => {
if (event === ApplicationEvent.EnteredOutOfSync) { if (event === ApplicationEvent.EnteredOutOfSync) {
setOutOfSync(true) setOutOfSync(true)
} else if (event === ApplicationEvent.ExitedOutOfSync) { } else if (event === ApplicationEvent.ExitedOutOfSync) {
@@ -103,7 +103,7 @@ export const useIsLocked = () => {
useEffect(() => { useEffect(() => {
let isMounted = true let isMounted = true
const removeSignedInObserver = application?.getAppState().addLockStateChangeObserver(event => { const removeSignedInObserver = application?.getAppState().addLockStateChangeObserver((event) => {
if (isMounted) { if (isMounted) {
if (event === LockStateType.Locked) { if (event === LockStateType.Locked) {
setIsLocked(true) setIsLocked(true)
@@ -131,7 +131,7 @@ export const useHasEditor = () => {
const [hasEditor, setHasEditor] = React.useState<boolean>(false) const [hasEditor, setHasEditor] = React.useState<boolean>(false)
useEffect(() => { useEffect(() => {
const removeEditorObserver = application?.editorGroup.addActiveControllerChangeObserver(newEditor => { const removeEditorObserver = application?.editorGroup.addActiveControllerChangeObserver((newEditor) => {
setHasEditor(Boolean(newEditor)) setHasEditor(Boolean(newEditor))
}) })
return removeEditorObserver return removeEditorObserver
@@ -207,7 +207,7 @@ export const useSyncStatus = () => {
}, [application, completedInitialSync, setStatus]) }, [application, completedInitialSync, setStatus])
useEffect(() => { useEffect(() => {
const unsubscribeAppEvents = application?.addEventObserver(async eventName => { const unsubscribeAppEvents = application?.addEventObserver(async (eventName) => {
if (eventName === ApplicationEvent.LocalDataIncrementalLoad) { if (eventName === ApplicationEvent.LocalDataIncrementalLoad) {
updateLocalDataStatus() updateLocalDataStatus()
} else if (eventName === ApplicationEvent.SyncStatusChanged || eventName === ApplicationEvent.FailedSync) { } else if (eventName === ApplicationEvent.SyncStatusChanged || eventName === ApplicationEvent.FailedSync) {
@@ -340,7 +340,7 @@ export const useProtectionSessionExpiry = () => {
const [protectionsDisabledUntil, setProtectionsDisabledUntil] = React.useState(getProtectionsDisabledUntil()) const [protectionsDisabledUntil, setProtectionsDisabledUntil] = React.useState(getProtectionsDisabledUntil())
useEffect(() => { useEffect(() => {
const removeProtectionLengthSubscriber = application?.addEventObserver(async event => { const removeProtectionLengthSubscriber = application?.addEventObserver(async (event) => {
if ([ApplicationEvent.UnprotectedSessionBegan, ApplicationEvent.UnprotectedSessionExpired].includes(event)) { if ([ApplicationEvent.UnprotectedSessionBegan, ApplicationEvent.UnprotectedSessionExpired].includes(event)) {
setProtectionsDisabledUntil(getProtectionsDisabledUntil()) setProtectionsDisabledUntil(getProtectionsDisabledUntil())
} }
@@ -389,7 +389,7 @@ export const useChangeNote = (note: SNNote | undefined, editor: NoteViewControll
if (await canChangeNote()) { if (await canChangeNote()) {
await application?.mutator.changeAndSaveItem( await application?.mutator.changeAndSaveItem(
note!, note!,
mutator => { (mutator) => {
const noteMutator = mutator as NoteMutator const noteMutator = mutator as NoteMutator
mutate(noteMutator) mutate(noteMutator)
}, },

View File

@@ -14,13 +14,15 @@ import {
SCREEN_INPUT_MODAL_FILE_NAME, SCREEN_INPUT_MODAL_FILE_NAME,
SCREEN_INPUT_MODAL_PASSCODE, SCREEN_INPUT_MODAL_PASSCODE,
SCREEN_INPUT_MODAL_TAG, SCREEN_INPUT_MODAL_TAG,
SCREEN_INPUT_MODAL_WORKSPACE_NAME,
SCREEN_MANAGE_SESSIONS, SCREEN_MANAGE_SESSIONS,
SCREEN_SETTINGS, SCREEN_SETTINGS,
SCREEN_UPLOADED_FILES_LIST, SCREEN_UPLOADED_FILES_LIST,
} from '@Root/Screens/screens' } from '@Root/Screens/screens'
import { Settings } from '@Root/Screens/Settings/Settings' import { Settings } from '@Root/Screens/Settings/Settings'
import { UploadedFilesList } from '@Root/Screens/UploadedFilesList/UploadedFilesList' import { UploadedFilesList } from '@Root/Screens/UploadedFilesList/UploadedFilesList'
import { Challenge, DeinitMode, DeinitSource, FileItem, SNNote } from '@standardnotes/snjs' import { WorkspaceInputModal } from '@Screens/InputModal/WorkspaceInputModal'
import { ApplicationDescriptor, Challenge, DeinitMode, DeinitSource, FileItem, SNNote } from '@standardnotes/snjs'
import { ICON_CHECKMARK, ICON_CLOSE } from '@Style/Icons' import { ICON_CHECKMARK, ICON_CLOSE } from '@Style/Icons'
import { ThemeService } from '@Style/ThemeService' import { ThemeService } from '@Style/ThemeService'
import React, { memo, useContext } from 'react' import React, { memo, useContext } from 'react'
@@ -48,6 +50,10 @@ export type ModalStackNavigatorParamList = {
[SCREEN_UPLOADED_FILES_LIST]: HeaderTitleParams & { [SCREEN_UPLOADED_FILES_LIST]: HeaderTitleParams & {
note: SNNote note: SNNote
} }
[SCREEN_INPUT_MODAL_WORKSPACE_NAME]: HeaderTitleParams & {
descriptor: ApplicationDescriptor
renameWorkspace: (descriptor: ApplicationDescriptor, workspaceName: string) => Promise<void>
}
[SCREEN_INPUT_MODAL_PASSCODE]: undefined [SCREEN_INPUT_MODAL_PASSCODE]: undefined
[SCREEN_AUTHENTICATE]: { [SCREEN_AUTHENTICATE]: {
challenge: Challenge challenge: Challenge
@@ -275,6 +281,28 @@ export const MainStackComponent = ({ env }: { env: TEnvironment }) => {
})} })}
component={BlockingModal} component={BlockingModal}
/> />
<MainStack.Screen
name={SCREEN_INPUT_MODAL_WORKSPACE_NAME}
options={({ route }) => ({
title: 'Workspace',
gestureEnabled: false,
headerTitle: ({ children }) => {
return <HeaderTitleView title={route.params?.title ?? (children || '')} />
},
headerLeft: ({ disabled, onPress }) => (
<HeaderButtons HeaderButtonComponent={IoniconsHeaderButton}>
<Item
testID="headerButton"
disabled={disabled}
title={Platform.OS === 'ios' ? 'Cancel' : ''}
iconName={Platform.OS === 'ios' ? undefined : ThemeService.nameForIcon(ICON_CLOSE)}
onPress={onPress}
/>
</HeaderButtons>
),
})}
component={WorkspaceInputModal}
/>
</MainStack.Navigator> </MainStack.Navigator>
) )
} }

View File

@@ -71,7 +71,7 @@ export const Authenticate = ({
const [passcodeKeyboardType, setPasscodeKeyboardType] = useState<PasscodeKeyboardType | undefined>( const [passcodeKeyboardType, setPasscodeKeyboardType] = useState<PasscodeKeyboardType | undefined>(
PasscodeKeyboardType.Default, PasscodeKeyboardType.Default,
) )
const [singleValidation] = useState(() => !(challenge.prompts.filter(prompt => prompt.validates).length > 0)) const [singleValidation] = useState(() => !(challenge.prompts.filter((prompt) => prompt.validates).length > 0))
const [showSwitchKeyboard, setShowSwitchKeyboard] = useState<boolean>(false) const [showSwitchKeyboard, setShowSwitchKeyboard] = useState<boolean>(false)
const [{ challengeValues, challengeValueStates }, dispatch] = useReducer( const [{ challengeValues, challengeValueStates }, dispatch] = useReducer(
@@ -217,7 +217,7 @@ export const Authenticate = ({
onValueChange(newChallengeValue) onValueChange(newChallengeValue)
return validateChallengeValue(newChallengeValue) return validateChallengeValue(newChallengeValue)
}) })
.catch(error => { .catch((error) => {
FingerprintScanner.release() FingerprintScanner.release()
if (error.name === 'DeviceLocked') { if (error.name === 'DeviceLocked') {
onValueLocked(challengeValue) onValueLocked(challengeValue)
@@ -250,7 +250,7 @@ export const Authenticate = ({
onValueChange(newChallengeValue) onValueChange(newChallengeValue)
return validateChallengeValue(newChallengeValue) return validateChallengeValue(newChallengeValue)
}) })
.catch(error_1 => { .catch((error_1) => {
onValueChange({ ...challengeValue, value: false }) onValueChange({ ...challengeValue, value: false })
FingerprintScanner.release() FingerprintScanner.release()
if (error_1.name !== 'SystemCancel') { if (error_1.name !== 'SystemCancel') {
@@ -340,7 +340,7 @@ export const Authenticate = ({
) )
useEffect(() => { useEffect(() => {
const remove = application?.getAppState().addStateChangeObserver(state => { const remove = application?.getAppState().addStateChangeObserver((state) => {
if (state === AppStateType.ResumingFromBackground) { if (state === AppStateType.ResumingFromBackground) {
if (!isAuthenticating.current) { if (!isAuthenticating.current) {
beginAuthenticatingForNextChallengeReason() beginAuthenticatingForNextChallengeReason()
@@ -440,7 +440,7 @@ export const Authenticate = ({
Keyboard.dismiss() Keyboard.dismiss()
const biometricChallengeValue = Object.values(challengeValues).find( const biometricChallengeValue = Object.values(challengeValues).find(
value => value.prompt.validation === ChallengeValidation.Biometric, (value) => value.prompt.validation === ChallengeValidation.Biometric,
) )
const state = challengeValueStates[biometricChallengeValue?.prompt.id as number] const state = challengeValueStates[biometricChallengeValue?.prompt.id as number]
if (state === AuthenticationValueStateType.Locked || state === AuthenticationValueStateType.Success) { if (state === AuthenticationValueStateType.Locked || state === AuthenticationValueStateType.Success) {
@@ -506,8 +506,8 @@ export const Authenticate = ({
const readyToSubmit = useMemo( const readyToSubmit = useMemo(
() => () =>
Object.values(challengeValues) Object.values(challengeValues)
.map(challengeValue => challengeValue.value) .map((challengeValue) => challengeValue.value)
.filter(value => !value).length === 0, .filter((value) => !value).length === 0,
[challengeValues], [challengeValues],
) )
@@ -556,7 +556,7 @@ export const Authenticate = ({
key={Platform.OS === 'android' ? keyboardType : undefined} key={Platform.OS === 'android' ? keyboardType : undefined}
ref={Array.of(firstInputRef, secondInputRef, thirdInputRef, fourthInputRef)[index] as any} ref={Array.of(firstInputRef, secondInputRef, thirdInputRef, fourthInputRef)[index] as any}
placeholder={challengeValue.prompt.placeholder} placeholder={challengeValue.prompt.placeholder}
onChangeText={text => { onChangeText={(text) => {
onValueChange({ ...challengeValue, value: text }) onValueChange({ ...challengeValue, value: text })
}} }}
value={(challengeValue.value || '') as string} value={(challengeValue.value || '') as string}
@@ -621,7 +621,7 @@ export const Authenticate = ({
} }
const isPending = useMemo( const isPending = useMemo(
() => Object.values(challengeValueStates).findIndex(state => state === AuthenticationValueStateType.Pending) >= 0, () => Object.values(challengeValueStates).findIndex((state) => state === AuthenticationValueStateType.Pending) >= 0,
[challengeValueStates], [challengeValueStates],
) )
@@ -644,7 +644,7 @@ export const Authenticate = ({
return ( return (
<HeaderHeightContext.Consumer> <HeaderHeightContext.Consumer>
{headerHeight => ( {(headerHeight) => (
<StyledKeyboardAvoidingView <StyledKeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : undefined} behavior={Platform.OS === 'ios' ? 'padding' : undefined}
keyboardVerticalOffset={headerHeight} keyboardVerticalOffset={headerHeight}

View File

@@ -220,7 +220,7 @@ export const ComponentView = ({
onLoadStart() onLoadStart()
} }
const onShouldStartLoadWithRequest: OnShouldStartLoadWithRequest = request => { const onShouldStartLoadWithRequest: OnShouldStartLoadWithRequest = (request) => {
log('Setting last iframe URL to', request.url) log('Setting last iframe URL to', request.url)
/** The first request can typically be 'about:blank', which we want to ignore */ /** The first request can typically be 'about:blank', which we want to ignore */
if (!didLoadRootUrl.current) { if (!didLoadRootUrl.current) {

View File

@@ -88,7 +88,7 @@ export class Compose extends React.Component<PropsWhenNavigating | PropsWhenRend
this.context = context this.context = context
const noteUuid = 'noteUuid' in props ? props.noteUuid : props.route.params.noteUuid const noteUuid = 'noteUuid' in props ? props.noteUuid : props.route.params.noteUuid
const editor = this.context.editorGroup.itemControllers.find(c => c.item.uuid === noteUuid) as NoteViewController const editor = this.context.editorGroup.itemControllers.find((c) => c.item.uuid === noteUuid) as NoteViewController
if (!editor) { if (!editor) {
throw 'Unable to to find note controller' throw 'Unable to to find note controller'
} }
@@ -149,7 +149,7 @@ export class Compose extends React.Component<PropsWhenNavigating | PropsWhenRend
void this.reloadComponentEditorState() void this.reloadComponentEditorState()
}) })
this.removeAppEventObserver = this.context?.addEventObserver(async eventName => { this.removeAppEventObserver = this.context?.addEventObserver(async (eventName) => {
if (eventName === ApplicationEvent.CompletedFullSync) { if (eventName === ApplicationEvent.CompletedFullSync) {
/** if we're still dirty, don't change status, a sync is likely upcoming. */ /** if we're still dirty, don't change status, a sync is likely upcoming. */
if (!this.note.dirty && this.state.saveError) { if (!this.note.dirty && this.state.saveError) {
@@ -169,7 +169,7 @@ export class Compose extends React.Component<PropsWhenNavigating | PropsWhenRend
} }
}) })
this.removeStateEventObserver = this.context?.getAppState().addStateEventObserver(state => { this.removeStateEventObserver = this.context?.getAppState().addStateEventObserver((state) => {
if (state === AppStateEventType.DrawerOpen) { if (state === AppStateEventType.DrawerOpen) {
this.dismissKeyboard() this.dismissKeyboard()
/** /**
@@ -352,7 +352,7 @@ export class Compose extends React.Component<PropsWhenNavigating | PropsWhenRend
await this.context.mutator.changeItem( await this.context.mutator.changeItem(
this.note, this.note,
mutator => { (mutator) => {
const noteMutator = mutator as NoteMutator const noteMutator = mutator as NoteMutator
if (newTitle != null) { if (newTitle != null) {
@@ -483,7 +483,7 @@ export class Compose extends React.Component<PropsWhenNavigating | PropsWhenRend
return ( return (
<Container> <Container>
<ThemeContext.Consumer> <ThemeContext.Consumer>
{theme => ( {(theme) => (
<> <>
{this.noteLocked && ( {this.noteLocked && (
<LockedContainer> <LockedContainer>
@@ -507,7 +507,7 @@ export class Compose extends React.Component<PropsWhenNavigating | PropsWhenRend
</LockedContainer> </LockedContainer>
)} )}
<ThemeServiceContext.Consumer> <ThemeServiceContext.Consumer>
{themeService => ( {(themeService) => (
<> <>
<NoteTitleInput <NoteTitleInput
testID="noteTitleField" testID="noteTitleField"

View File

@@ -11,7 +11,7 @@ import { Container, Input } from './InputModal.styled'
type Props = ModalStackNavigationProp<typeof SCREEN_INPUT_MODAL_FILE_NAME> type Props = ModalStackNavigationProp<typeof SCREEN_INPUT_MODAL_FILE_NAME>
export const FileInputModal: FC<Props> = props => { export const FileInputModal: FC<Props> = (props) => {
const { file, renameFile } = props.route.params const { file, renameFile } = props.route.params
const themeService = useContext(ThemeServiceContext) const themeService = useContext(ThemeServiceContext)
const application = useSafeApplicationContext() const application = useSafeApplicationContext()

View File

@@ -41,7 +41,7 @@ export const TagInputModal = (props: Props) => {
const onSubmit = useCallback(async () => { const onSubmit = useCallback(async () => {
if (props.route.params.tagUuid) { if (props.route.params.tagUuid) {
const tag = application?.items.findItem(props.route.params.tagUuid) as SNTag const tag = application?.items.findItem(props.route.params.tagUuid) as SNTag
await application?.mutator.changeItem(tag, mutator => { await application?.mutator.changeItem(tag, (mutator) => {
const tagMutator = mutator as TagMutator const tagMutator = mutator as TagMutator
tagMutator.title = text tagMutator.title = text
if (props.route.params.noteUuid) { if (props.route.params.noteUuid) {
@@ -54,7 +54,7 @@ export const TagInputModal = (props: Props) => {
} else { } else {
const tag = await application!.mutator.findOrCreateTag(text) const tag = await application!.mutator.findOrCreateTag(text)
if (props.route.params.noteUuid) { if (props.route.params.noteUuid) {
await application?.mutator.changeItem(tag, mutator => { await application?.mutator.changeItem(tag, (mutator) => {
const tagMutator = mutator as TagMutator const tagMutator = mutator as TagMutator
const note = application.items.findItem(props.route.params.noteUuid!) const note = application.items.findItem(props.route.params.noteUuid!)
if (note) { if (note) {

View File

@@ -0,0 +1,61 @@
import { ButtonCell } from '@Root/Components/ButtonCell'
import { SectionedTableCell } from '@Root/Components/SectionedTableCell'
import { TableSection } from '@Root/Components/TableSection'
import { useSafeApplicationContext } from '@Root/Hooks/useSafeApplicationContext'
import { ModalStackNavigationProp } from '@Root/ModalStack'
import { SCREEN_INPUT_MODAL_WORKSPACE_NAME } from '@Root/Screens/screens'
import { ThemeServiceContext } from '@Style/ThemeService'
import React, { FC, useContext, useEffect, useRef, useState } from 'react'
import { TextInput } from 'react-native'
import { Container, Input } from './InputModal.styled'
type Props = ModalStackNavigationProp<typeof SCREEN_INPUT_MODAL_WORKSPACE_NAME>
export const WorkspaceInputModal: FC<Props> = (props) => {
const { descriptor, renameWorkspace } = props.route.params
const themeService = useContext(ThemeServiceContext)
const application = useSafeApplicationContext()
const workspaceNameInputRef = useRef<TextInput>(null)
const [workspaceName, setWorkspaceName] = useState(descriptor.label)
const onSubmit = async () => {
const trimmedWorkspaceName = workspaceName.trim()
if (trimmedWorkspaceName === '') {
setWorkspaceName(descriptor.label)
await application?.alertService.alert('Workspace name cannot be empty')
workspaceNameInputRef.current?.focus()
return
}
await renameWorkspace(descriptor, trimmedWorkspaceName)
void application.sync.sync()
props.navigation.goBack()
}
useEffect(() => {
workspaceNameInputRef.current?.focus()
}, [])
return (
<Container>
<TableSection>
<SectionedTableCell textInputCell first={true}>
<Input
ref={workspaceNameInputRef as any}
placeholder={'Workspace name'}
onChangeText={setWorkspaceName}
value={workspaceName}
autoCorrect={false}
autoCapitalize={'none'}
keyboardAppearance={themeService?.keyboardColorForActiveTheme()}
underlineColorAndroid={'transparent'}
onSubmitEditing={onSubmit}
/>
</SectionedTableCell>
<ButtonCell maxHeight={45} disabled={workspaceName.length === 0} title={'Save'} bold onPress={onSubmit} />
</TableSection>
</Container>
)
}

View File

@@ -64,7 +64,7 @@ const useSessions = (): [
setErrorMessage('An unknown error occurred while revoking the session.') setErrorMessage('An unknown error occurred while revoking the session.')
} }
} else { } else {
setSessions(sessions.filter(session => session.uuid !== uuid)) setSessions(sessions.filter((session) => session.uuid !== uuid))
} }
} }
@@ -136,7 +136,7 @@ export const ManageSessions: React.FC = () => {
return ( return (
<FlatList<RemoteSession> <FlatList<RemoteSession>
keyExtractor={item => item.uuid} keyExtractor={(item) => item.uuid}
contentContainerStyle={{ paddingBottom: insets.bottom }} contentContainerStyle={{ paddingBottom: insets.bottom }}
initialNumToRender={7} initialNumToRender={7}
windowSize={7} windowSize={7}

View File

@@ -11,7 +11,7 @@ type Props = {
currentSession: boolean currentSession: boolean
} }
const Container = styled(SectionedTableCellTouchableHighlight).attrs(props => ({ const Container = styled(SectionedTableCellTouchableHighlight).attrs((props) => ({
underlayColor: props.theme.stylekitBorderColor, underlayColor: props.theme.stylekitBorderColor,
}))<TableCellProps>` }))<TableCellProps>`
padding-top: ${12}px; padding-top: ${12}px;
@@ -21,7 +21,7 @@ const ButtonContainer = styled.View``
type ButtonLabelProps = Pick<Props, 'disabled'> type ButtonLabelProps = Pick<Props, 'disabled'>
const ButtonLabel = styled.Text<ButtonLabelProps>` const ButtonLabel = styled.Text<ButtonLabelProps>`
color: ${props => { color: ${(props) => {
let color = props.theme.stylekitForegroundColor let color = props.theme.stylekitForegroundColor
if (props.disabled) { if (props.disabled) {
color = 'gray' color = 'gray'
@@ -29,7 +29,7 @@ const ButtonLabel = styled.Text<ButtonLabelProps>`
return color return color
}}; }};
font-weight: bold; font-weight: bold;
font-size: ${props => props.theme.mainTextFontSize}px; font-size: ${(props) => props.theme.mainTextFontSize}px;
${({ disabled }) => ${({ disabled }) =>
disabled && disabled &&
css` css`
@@ -46,7 +46,7 @@ export const SubTitleText = styled.Text<{ current: boolean }>`
line-height: 21px; line-height: 21px;
` `
export const SessionCell: React.FC<Props> = props => ( export const SessionCell: React.FC<Props> = (props) => (
<Container testID={props.testID} disabled={props.disabled} onPress={props.onPress}> <Container testID={props.testID} disabled={props.disabled} onPress={props.onPress}>
<ButtonContainer> <ButtonContainer>
<ButtonLabel disabled={props.disabled}>{props.title}</ButtonLabel> <ButtonLabel disabled={props.disabled}>{props.title}</ButtonLabel>

View File

@@ -61,9 +61,9 @@ export const NoteHistory = (props: Props) => {
fontStyle={{ fontStyle={{
color: theme.stylekitForegroundColor, color: theme.stylekitForegroundColor,
}} }}
values={routes.map(route => route.title)} values={routes.map((route) => route.title)}
selectedIndex={tabBarProps.navigationState.index} selectedIndex={tabBarProps.navigationState.index}
onChange={event => { onChange={(event) => {
setIndex(event.nativeEvent.selectedSegmentIndex) setIndex(event.nativeEvent.selectedSegmentIndex)
}} }}
/> />

View File

@@ -12,7 +12,7 @@ type Props = {
subTitle?: string subTitle?: string
} }
const Container = styled(SectionedTableCellTouchableHighlight).attrs(props => ({ const Container = styled(SectionedTableCellTouchableHighlight).attrs((props) => ({
underlayColor: props.theme.stylekitBorderColor, underlayColor: props.theme.stylekitBorderColor,
}))<TableCellProps>` }))<TableCellProps>`
padding-top: ${12}px; padding-top: ${12}px;
@@ -22,7 +22,7 @@ const ButtonContainer = styled.View``
type ButtonLabelProps = Pick<Props, 'disabled'> type ButtonLabelProps = Pick<Props, 'disabled'>
const ButtonLabel = styled.Text<ButtonLabelProps>` const ButtonLabel = styled.Text<ButtonLabelProps>`
color: ${props => { color: ${(props) => {
let color = props.theme.stylekitForegroundColor let color = props.theme.stylekitForegroundColor
if (props.disabled) { if (props.disabled) {
color = 'gray' color = 'gray'
@@ -30,7 +30,7 @@ const ButtonLabel = styled.Text<ButtonLabelProps>`
return color return color
}}; }};
font-weight: bold; font-weight: bold;
font-size: ${props => props.theme.mainTextFontSize}px; font-size: ${(props) => props.theme.mainTextFontSize}px;
${({ disabled }) => ${({ disabled }) =>
disabled && disabled &&
css` css`
@@ -45,7 +45,7 @@ export const SubTitleText = styled.Text`
line-height: 21px; line-height: 21px;
` `
export const NoteHistoryCell: React.FC<Props> = props => ( export const NoteHistoryCell: React.FC<Props> = (props) => (
<Container <Container
first={props.first} first={props.first}
last={props.last} last={props.last}

View File

@@ -43,7 +43,7 @@ export const NoteHistoryPreview = ({
} else { } else {
await application?.mutator.changeAndSaveItem( await application?.mutator.changeAndSaveItem(
originalNote, originalNote,
mutator => { (mutator) => {
mutator.setCustomContent(revision.payload.content) mutator.setCustomContent(revision.payload.content)
}, },
true, true,

View File

@@ -70,7 +70,7 @@ export const RemoteHistory: React.FC<Props> = ({ note, onPress }) => {
return ( return (
<FlatList<RevisionListEntry> <FlatList<RevisionListEntry>
keyExtractor={item => item.uuid} keyExtractor={(item) => item.uuid}
contentContainerStyle={{ paddingBottom: insets.bottom }} contentContainerStyle={{ paddingBottom: insets.bottom }}
initialNumToRender={10} initialNumToRender={10}
windowSize={10} windowSize={10}

View File

@@ -42,7 +42,7 @@ export const SessionHistory: React.FC<Props> = ({ note, onPress }) => {
return ( return (
<FlatList<NoteHistoryEntry> <FlatList<NoteHistoryEntry>
keyExtractor={item => item.previewTitle()} keyExtractor={(item) => item.previewTitle()}
contentContainerStyle={{ paddingBottom: insets.bottom }} contentContainerStyle={{ paddingBottom: insets.bottom }}
initialNumToRender={10} initialNumToRender={10}
windowSize={10} windowSize={10}

View File

@@ -8,7 +8,7 @@ export const Container = styled.View<{ selected: boolean; distance: number }>`
flex-direction: row; flex-direction: row;
align-items: flex-start; align-items: flex-start;
justify-content: space-between; justify-content: space-between;
padding: ${props => props.distance}px 0 0 ${props => props.distance}px; padding: ${(props) => props.distance}px 0 0 ${(props) => props.distance}px;
background-color: ${({ theme, selected }) => { background-color: ${({ theme, selected }) => {
return selected ? theme.stylekitInfoColor : theme.stylekitBackgroundColor return selected ? theme.stylekitInfoColor : theme.stylekitBackgroundColor
}}; }};
@@ -16,10 +16,10 @@ export const Container = styled.View<{ selected: boolean; distance: number }>`
export const NoteDataContainer = styled.View<{ distance: number }>` export const NoteDataContainer = styled.View<{ distance: number }>`
border-bottom-color: ${({ theme }) => hexToRGBA(theme.stylekitBorderColor, 0.75)}; border-bottom-color: ${({ theme }) => hexToRGBA(theme.stylekitBorderColor, 0.75)};
border-bottom-width: 1px; border-bottom-width: 1px;
padding-bottom: ${props => props.distance}px; padding-bottom: ${(props) => props.distance}px;
flex-grow: 1; flex-grow: 1;
flex-shrink: 1; flex-shrink: 1;
padding-right: ${props => props.distance}px; padding-right: ${(props) => props.distance}px;
` `
export const DeletedText = styled.Text` export const DeletedText = styled.Text`
color: ${({ theme }) => theme.stylekitInfoColor}; color: ${({ theme }) => theme.stylekitInfoColor};
@@ -54,7 +54,7 @@ export const TagText = styled.Text<{ selected: boolean }>`
color: ${({ theme, selected }) => { color: ${({ theme, selected }) => {
return selected ? theme.stylekitInfoContrastColor : theme.stylekitForegroundColor return selected ? theme.stylekitInfoContrastColor : theme.stylekitForegroundColor
}}; }};
opacity: ${props => (props.selected ? 0.8 : 0.5)}; opacity: ${(props) => (props.selected ? 0.8 : 0.5)};
` `
export const DetailsText = styled(TagText)` export const DetailsText = styled(TagText)`
margin-right: 0; margin-right: 0;

View File

@@ -62,7 +62,7 @@ export const NoteCell = ({
await application?.mutator.deleteItem(note) await application?.mutator.deleteItem(note)
}, },
() => { () => {
void changeNote(mutator => { void changeNote((mutator) => {
mutator.trashed = true mutator.trashed = true
}, false) }, false)
}, },
@@ -105,7 +105,7 @@ export const NoteCell = ({
text: note.pinned ? 'Unpin' : 'Pin', text: note.pinned ? 'Unpin' : 'Pin',
key: 'pin', key: 'pin',
callback: () => callback: () =>
changeNote(mutator => { changeNote((mutator) => {
mutator.pinned = !note.pinned mutator.pinned = !note.pinned
}, false), }, false),
}) })
@@ -123,7 +123,7 @@ export const NoteCell = ({
return return
} }
void changeNote(mutator => { void changeNote((mutator) => {
mutator.archived = !note.archived mutator.archived = !note.archived
}, false) }, false)
}, },
@@ -133,7 +133,7 @@ export const NoteCell = ({
text: note.locked ? 'Enable editing' : 'Prevent editing', text: note.locked ? 'Enable editing' : 'Prevent editing',
key: 'lock', key: 'lock',
callback: () => callback: () =>
changeNote(mutator => { changeNote((mutator) => {
mutator.locked = !note.locked mutator.locked = !note.locked
}, false), }, false),
}) })
@@ -157,7 +157,7 @@ export const NoteCell = ({
text: 'Restore', text: 'Restore',
key: 'restore-note', key: 'restore-note',
callback: () => { callback: () => {
void changeNote(mutator => { void changeNote((mutator) => {
mutator.trashed = false mutator.trashed = false
}, false) }, false)
}, },

View File

@@ -44,7 +44,7 @@ export const NoteCellFlags = ({ note, highlight }: { note: SNNote; highlight: bo
return flags.length > 0 ? ( return flags.length > 0 ? (
<FlagsContainer> <FlagsContainer>
{flags.map(flag => ( {flags.map((flag) => (
<FlagContainer key={flag.text.concat(flag.color)} color={flag.color} selected={highlight}> <FlagContainer key={flag.text.concat(flag.color)} color={flag.color} selected={highlight}>
<FlagLabel selected={highlight}>{flag.text}</FlagLabel> <FlagLabel selected={highlight}>{flag.text}</FlagLabel>
</FlagContainer> </FlagContainer>

View File

@@ -15,7 +15,7 @@ export const styles = StyleSheet.create({
}) })
export const Container = styled.View` export const Container = styled.View`
background-color: ${props => props.theme.stylekitBackgroundColor}; background-color: ${(props) => props.theme.stylekitBackgroundColor};
flex: 1; flex: 1;
` `
@@ -32,8 +32,8 @@ interface LoadingTextProps {
export const LoadingText = styled.Text<LoadingTextProps>` export const LoadingText = styled.Text<LoadingTextProps>`
position: absolute; position: absolute;
opacity: 0.5; opacity: 0.5;
color: ${props => props.theme.stylekitForegroundColor}; color: ${(props) => props.theme.stylekitForegroundColor};
text-align: ${props => props.textAlign ?? 'left'}; text-align: ${(props) => props.textAlign ?? 'left'};
` `
export const HeaderContainer = styled.View` export const HeaderContainer = styled.View`
@@ -43,7 +43,7 @@ export const HeaderContainer = styled.View`
` `
export const SearchBarContainer = styled.View` export const SearchBarContainer = styled.View`
background-color: ${props => props.theme.stylekitBackgroundColor}; background-color: ${(props) => props.theme.stylekitBackgroundColor};
z-index: 2; z-index: 2;
` `

View File

@@ -82,7 +82,7 @@ export const NoteList = (props: Props) => {
}) })
useEffect(() => { useEffect(() => {
const unsubscribeStateEventObserver = application?.getAppState().addStateEventObserver(state => { const unsubscribeStateEventObserver = application?.getAppState().addStateEventObserver((state) => {
if (state === AppStateEventType.DrawerOpen) { if (state === AppStateEventType.DrawerOpen) {
dismissKeyboard() dismissKeyboard()
} }
@@ -99,7 +99,7 @@ export const NoteList = (props: Props) => {
}, [noteListScrolled, props.notes]) }, [noteListScrolled, props.notes])
useEffect(() => { useEffect(() => {
const unsubscribeTagChangedEventObserver = application?.getAppState().addStateChangeObserver(event => { const unsubscribeTagChangedEventObserver = application?.getAppState().addStateChangeObserver((event) => {
if (event === AppStateType.TagChanged) { if (event === AppStateType.TagChanged) {
scrollListToTop() scrollListToTop()
} }
@@ -223,7 +223,7 @@ export const NoteList = (props: Props) => {
<FlatList <FlatList
ref={noteListRef} ref={noteListRef}
style={styles.list} style={styles.list}
keyExtractor={item => item?.uuid} keyExtractor={(item) => item?.uuid}
contentContainerStyle={[{ paddingBottom: insets.bottom }, props.notes.length > 0 ? {} : { height: '100%' }]} contentContainerStyle={[{ paddingBottom: insets.bottom }, props.notes.length > 0 ? {} : { height: '100%' }]}
initialNumToRender={6} initialNumToRender={6}
windowSize={6} windowSize={6}

View File

@@ -83,7 +83,7 @@ export const Notes = React.memo(
title = selectedTag.title title = selectedTag.title
if (selectedTag instanceof SNTag && selectedTag.parentId) { if (selectedTag instanceof SNTag && selectedTag.parentId) {
const parents = application.items.getTagParentChain(selectedTag) const parents = application.items.getTagParentChain(selectedTag)
const hierarchy = parents.map(tag => tag.title).join(' ⫽ ') const hierarchy = parents.map((tag) => tag.title).join(' ⫽ ')
subTitle = hierarchy.length > 0 ? `in ${hierarchy}` : undefined subTitle = hierarchy.length > 0 ? `in ${hierarchy}` : undefined
} }
} }
@@ -163,7 +163,7 @@ export const Notes = React.memo(
useEffect(() => { useEffect(() => {
let mounted = true let mounted = true
const removeEditorObserver = application.editorGroup.addActiveControllerChangeObserver(activeEditor => { const removeEditorObserver = application.editorGroup.addActiveControllerChangeObserver((activeEditor) => {
if (mounted) { if (mounted) {
setSelectedNoteId(activeEditor?.item?.uuid) setSelectedNoteId(activeEditor?.item?.uuid)
} }
@@ -305,7 +305,7 @@ export const Notes = React.memo(
toggleIncludeTrashed, toggleIncludeTrashed,
]) ])
const getFirstSelectableNote = useCallback((newNotes: SNNote[]) => newNotes.find(note => !note.protected), []) const getFirstSelectableNote = useCallback((newNotes: SNNote[]) => newNotes.find((note) => !note.protected), [])
const selectFirstNote = useCallback( const selectFirstNote = useCallback(
(newNotes: SNNote[]) => { (newNotes: SNNote[]) => {
@@ -475,7 +475,7 @@ export const Notes = React.memo(
) )
useEffect(() => { useEffect(() => {
const removeAppStateChangeHandler = application.getAppState().addStateChangeObserver(state => { const removeAppStateChangeHandler = application.getAppState().addStateChangeObserver((state) => {
if (state === AppStateType.TagChanged) { if (state === AppStateType.TagChanged) {
reloadNotesDisplayOptions() reloadNotesDisplayOptions()
reloadNotes(true, true) reloadNotes(true, true)

View File

@@ -11,17 +11,17 @@ const Container = styled.View`
padding: ${PADDING}px; padding: ${PADDING}px;
border-width: 1px; border-width: 1px;
border-radius: 4px; border-radius: 4px;
border-color: ${props => props.theme.stylekitBorderColor}; border-color: ${(props) => props.theme.stylekitBorderColor};
` `
const CenterContainer = styled.View` const CenterContainer = styled.View`
justify-content: center; justify-content: center;
` `
const UserIcon = styled(Icon)` const UserIcon = styled(Icon)`
font-size: 24px; font-size: 24px;
color: ${props => props.theme.stylekitInfoColor}; color: ${(props) => props.theme.stylekitInfoColor};
` `
const ForwardIcon = styled(UserIcon)` const ForwardIcon = styled(UserIcon)`
color: ${props => props.theme.stylekitNeutralColor}; color: ${(props) => props.theme.stylekitNeutralColor};
` `
const TextContainer = styled.View` const TextContainer = styled.View`
flex: 1; flex: 1;
@@ -30,12 +30,12 @@ const TextContainer = styled.View`
const BoldText = styled.Text` const BoldText = styled.Text`
font-size: 15px; font-size: 15px;
font-weight: 600; font-weight: 600;
color: ${props => props.theme.stylekitForegroundColor}; color: ${(props) => props.theme.stylekitForegroundColor};
` `
const SubText = styled.Text` const SubText = styled.Text`
margin-top: 2px; margin-top: 2px;
font-size: 11px; font-size: 11px;
color: ${props => props.theme.stylekitNeutralColor}; color: ${(props) => props.theme.stylekitNeutralColor};
` `
export { Touchable, Container, CenterContainer, UserIcon, ForwardIcon, TextContainer, BoldText, SubText } export { Touchable, Container, CenterContainer, UserIcon, ForwardIcon, TextContainer, BoldText, SubText }

View File

@@ -26,7 +26,7 @@ export const Root = () => {
const [keyboardHeight, setKeyboardHeight] = useState<number | undefined>(undefined) const [keyboardHeight, setKeyboardHeight] = useState<number | undefined>(undefined)
useEffect(() => { useEffect(() => {
const removeStateObserver = application?.getAppState().addStateChangeObserver(state => { const removeStateObserver = application?.getAppState().addStateChangeObserver((state) => {
if (state === AppStateType.GainingFocus) { if (state === AppStateType.GainingFocus) {
void application.sync.sync() void application.sync.sync()
} }
@@ -50,7 +50,7 @@ export const Root = () => {
} }
} }
}) })
const removeNoteObserver = application?.editorGroup.addActiveControllerChangeObserver(activeController => { const removeNoteObserver = application?.editorGroup.addActiveControllerChangeObserver((activeController) => {
if (activeController instanceof NoteViewController) { if (activeController instanceof NoteViewController) {
setActiveNoteView(activeController) setActiveNoteView(activeController)
} else { } else {
@@ -100,7 +100,7 @@ export const Root = () => {
} }
const toggleNoteList = () => { const toggleNoteList = () => {
setNoteListCollapsed(value => !value) setNoteListCollapsed((value) => !value)
} }
const collapseIconBottomPosition = (keyboardHeight ?? 0) > (height ?? 0) / 2 ? (keyboardHeight ?? 0) + 40 : '50%' const collapseIconBottomPosition = (keyboardHeight ?? 0) > (height ?? 0) / 2 ? (keyboardHeight ?? 0) + 40 : '50%'

View File

@@ -1,16 +1,16 @@
import { useSignedIn } from '@Lib/SnjsHelperHooks' import { useSignedIn } from '@Lib/SnjsHelperHooks'
import { useNavigation } from '@react-navigation/native' import { useNavigation } from '@react-navigation/native'
import { ApplicationContext } from '@Root/ApplicationContext'
import { ButtonCell } from '@Root/Components/ButtonCell' import { ButtonCell } from '@Root/Components/ButtonCell'
import { SectionedAccessoryTableCell } from '@Root/Components/SectionedAccessoryTableCell' import { SectionedAccessoryTableCell } from '@Root/Components/SectionedAccessoryTableCell'
import { SectionedOptionsTableCell } from '@Root/Components/SectionedOptionsTableCell' import { SectionedOptionsTableCell } from '@Root/Components/SectionedOptionsTableCell'
import { SectionHeader } from '@Root/Components/SectionHeader' import { SectionHeader } from '@Root/Components/SectionHeader'
import { TableSection } from '@Root/Components/TableSection' import { TableSection } from '@Root/Components/TableSection'
import { useSafeApplicationContext } from '@Root/Hooks/useSafeApplicationContext'
import { ModalStackNavigationProp } from '@Root/ModalStack' import { ModalStackNavigationProp } from '@Root/ModalStack'
import { SCREEN_MANAGE_SESSIONS, SCREEN_SETTINGS } from '@Root/Screens/screens' import { SCREEN_MANAGE_SESSIONS, SCREEN_SETTINGS } from '@Root/Screens/screens'
import { ButtonType, PrefKey } from '@standardnotes/snjs' import { ButtonType, PrefKey } from '@standardnotes/snjs'
import moment from 'moment' import moment from 'moment'
import React, { useCallback, useContext, useMemo, useState } from 'react' import React, { useCallback, useMemo, useState } from 'react'
import { Platform } from 'react-native' import { Platform } from 'react-native'
import DocumentPicker from 'react-native-document-picker' import DocumentPicker from 'react-native-document-picker'
import RNFS from 'react-native-fs' import RNFS from 'react-native-fs'
@@ -22,7 +22,8 @@ type Props = {
export const OptionsSection = ({ title, encryptionAvailable }: Props) => { export const OptionsSection = ({ title, encryptionAvailable }: Props) => {
// Context // Context
const application = useContext(ApplicationContext) const application = useSafeApplicationContext()
const [signedIn] = useSignedIn() const [signedIn] = useSignedIn()
const navigation = useNavigation<ModalStackNavigationProp<typeof SCREEN_SETTINGS>['navigation']>() const navigation = useNavigation<ModalStackNavigationProp<typeof SCREEN_SETTINGS>['navigation']>()
@@ -57,7 +58,7 @@ export const OptionsSection = ({ title, encryptionAvailable }: Props) => {
const email = useMemo(() => { const email = useMemo(() => {
if (signedIn) { if (signedIn) {
const user = application?.getUser() const user = application.getUser()
return user?.email return user?.email
} }
return return
@@ -76,25 +77,25 @@ export const OptionsSection = ({ title, encryptionAvailable }: Props) => {
const destroyLocalData = async () => { const destroyLocalData = async () => {
if ( if (
await application?.alertService?.confirm( await application.alertService.confirm(
'Signing out will remove all data from this device, including notes and tags. Make sure your data is synced before proceeding.', 'Signing out will remove all data from this device, including notes and tags. Make sure your data is synced before proceeding.',
'Sign Out?', 'Sign Out?',
'Sign Out', 'Sign Out',
ButtonType.Danger, ButtonType.Danger,
) )
) { ) {
await application!.user.signOut() await application.user.signOut()
} }
} }
const exportData = useCallback( const exportData = useCallback(
async (encrypted: boolean) => { async (encrypted: boolean) => {
setExporting(true) setExporting(true)
const result = await application?.getBackupsService().export(encrypted) const result = await application.getBackupsService().export(encrypted)
if (result) { if (result) {
const exportDate = new Date() const exportDate = new Date()
setLastExportDate(exportDate) setLastExportDate(exportDate)
void application?.getLocalPreferences().setUserPrefValue(PrefKey.MobileLastExportDate, exportDate) void application.getLocalPreferences().setUserPrefValue(PrefKey.MobileLastExportDate, exportDate)
} }
setExporting(false) setExporting(false)
}, },
@@ -103,25 +104,25 @@ export const OptionsSection = ({ title, encryptionAvailable }: Props) => {
const readImportFile = async (fileUri: string): Promise<any> => { const readImportFile = async (fileUri: string): Promise<any> => {
return RNFS.readFile(fileUri) return RNFS.readFile(fileUri)
.then(result => JSON.parse(result)) .then((result) => JSON.parse(result))
.catch(() => { .catch(() => {
void application!.alertService!.alert('Unable to open file. Ensure it is a proper JSON file and try again.') void application.alertService.alert('Unable to open file. Ensure it is a proper JSON file and try again.')
}) })
} }
const performImport = async (data: any) => { const performImport = async (data: any) => {
const result = await application!.mutator.importData(data) const result = await application.mutator.importData(data)
if (!result) { if (!result) {
return return
} else if ('error' in result) { } else if ('error' in result) {
void application!.alertService!.alert(result.error.text) void application.alertService.alert(result.error.text)
} else if (result.errorCount) { } else if (result.errorCount) {
void application!.alertService!.alert( void application.alertService.alert(
`Import complete. ${result.errorCount} items were not imported because ` + `Import complete. ${result.errorCount} items were not imported because ` +
'there was an error decrypting them. Make sure the password is correct and try again.', 'there was an error decrypting them. Make sure the password is correct and try again.',
) )
} else { } else {
void application!.alertService!.alert('Your data has been successfully imported.') void application.alertService.alert('Your data has been successfully imported.')
} }
} }
@@ -139,10 +140,10 @@ export const OptionsSection = ({ title, encryptionAvailable }: Props) => {
setImporting(true) setImporting(true)
if (data.version || data.auth_params || data.keyParams) { if (data.version || data.auth_params || data.keyParams) {
const version = data.version || data.keyParams?.version || data.auth_params?.version const version = data.version || data.keyParams?.version || data.auth_params?.version
if (application!.protocolService.supportedVersions().includes(version)) { if (application.protocolService.supportedVersions().includes(version)) {
await performImport(data) await performImport(data)
} else { } else {
void application!.alertService.alert( void application.alertService.alert(
'This backup file was created using an unsupported version of the application ' + 'This backup file was created using an unsupported version of the application ' +
'and cannot be imported here. Please update your application and try again.', 'and cannot be imported here. Please update your application and try again.',
) )
@@ -159,7 +160,7 @@ export const OptionsSection = ({ title, encryptionAvailable }: Props) => {
async (option: { key: string }) => { async (option: { key: string }) => {
const encrypted = option.key === 'encrypted' const encrypted = option.key === 'encrypted'
if (encrypted && !encryptionAvailable) { if (encrypted && !encryptionAvailable) {
void application?.alertService!.alert( void application.alertService.alert(
'You must be signed in, or have a local passcode set, to generate an encrypted export file.', 'You must be signed in, or have a local passcode set, to generate an encrypted export file.',
'Not Available', 'Not Available',
'OK', 'OK',
@@ -176,12 +177,12 @@ export const OptionsSection = ({ title, encryptionAvailable }: Props) => {
}, [navigation]) }, [navigation])
const showDataBackupAlert = useCallback(() => { const showDataBackupAlert = useCallback(() => {
void application?.alertService.alert( void application.alertService.alert(
'Because you are using the app offline without a sync account, it is your responsibility to keep your data safe and backed up. It is recommended you export a backup of your data at least once a week, or, to sign up for a sync account so that your data is backed up automatically.', 'Because you are using the app offline without a sync account, it is your responsibility to keep your data safe and backed up. It is recommended you export a backup of your data at least once a week, or, to sign up for a sync account so that your data is backed up automatically.',
'No Backups Created', 'No Backups Created',
'OK', 'OK',
) )
}, [application?.alertService]) }, [application.alertService])
return ( return (
<TableSection> <TableSection>
@@ -192,7 +193,7 @@ export const OptionsSection = ({ title, encryptionAvailable }: Props) => {
<ButtonCell <ButtonCell
testID="manageSessionsButton" testID="manageSessionsButton"
leftAligned={true} leftAligned={true}
first={true} first={false}
title={'Manage Sessions'} title={'Manage Sessions'}
onPress={openManageSessions} onPress={openManageSessions}
/> />

View File

@@ -36,7 +36,7 @@ export const PreferencesSection = () => {
const toggleReverseSort = () => { const toggleReverseSort = () => {
void application.getLocalPreferences().setUserPrefValue(PrefKey.MobileSortNotesReverse, !sortReverse) void application.getLocalPreferences().setUserPrefValue(PrefKey.MobileSortNotesReverse, !sortReverse)
setSortReverse(value => !value) setSortReverse((value) => !value)
} }
const changeSortOption = (key: CollectionSortProperty) => { const changeSortOption = (key: CollectionSortProperty) => {
@@ -45,15 +45,15 @@ export const PreferencesSection = () => {
} }
const toggleNotesPreviewHidden = () => { const toggleNotesPreviewHidden = () => {
void application.getLocalPreferences().setUserPrefValue(PrefKey.MobileNotesHideNotePreview, !hidePreviews) void application.getLocalPreferences().setUserPrefValue(PrefKey.MobileNotesHideNotePreview, !hidePreviews)
setHidePreviews(value => !value) setHidePreviews((value) => !value)
} }
const toggleNotesDateHidden = () => { const toggleNotesDateHidden = () => {
void application.getLocalPreferences().setUserPrefValue(PrefKey.MobileNotesHideDate, !hideDates) void application.getLocalPreferences().setUserPrefValue(PrefKey.MobileNotesHideDate, !hideDates)
setHideDates(value => !value) setHideDates((value) => !value)
} }
const toggleNotesEditorIconHidden = () => { const toggleNotesEditorIconHidden = () => {
void application.getLocalPreferences().setUserPrefValue(PrefKey.MobileNotesHideEditorIcon, !hideEditorIcon) void application.getLocalPreferences().setUserPrefValue(PrefKey.MobileNotesHideEditorIcon, !hideEditorIcon)
setHideEditorIcon(value => !value) setHideEditorIcon((value) => !value)
} }
return ( return (

View File

@@ -0,0 +1,199 @@
import { SectionedAccessoryTableCell } from '@Components/SectionedAccessoryTableCell'
import { SectionHeader } from '@Components/SectionHeader'
import { useNavigation } from '@react-navigation/native'
import { TableSection } from '@Root/Components/TableSection'
import { useSafeApplicationContext } from '@Root/Hooks/useSafeApplicationContext'
import { useSafeApplicationGroupContext } from '@Root/Hooks/useSafeApplicationGroupContext'
import { ModalStackNavigationProp } from '@Root/ModalStack'
import { SCREEN_INPUT_MODAL_WORKSPACE_NAME, SCREEN_SETTINGS } from '@Screens/screens'
import { ApplicationDescriptor, ApplicationGroupEvent, ButtonType } from '@standardnotes/snjs'
import { CustomActionSheetOption, useCustomActionSheet } from '@Style/CustomActionSheet'
import React, { useCallback, useEffect, useState } from 'react'
export const WorkspacesSection = () => {
const application = useSafeApplicationContext()
const appGroup = useSafeApplicationGroupContext()
const navigation = useNavigation<ModalStackNavigationProp<typeof SCREEN_SETTINGS>['navigation']>()
const [applicationDescriptors, setApplicationDescriptors] = useState<ApplicationDescriptor[]>([])
enum WorkspaceAction {
AddAnother = 'Add another workspace',
Activate = 'Activate',
Rename = 'Rename',
Remove = 'Remove',
SignOutAll = 'Sign out all workspaces',
}
useEffect(() => {
let descriptors = appGroup.getDescriptors()
setApplicationDescriptors(descriptors)
const removeAppGroupObserver = appGroup.addEventObserver((event) => {
if (event === ApplicationGroupEvent.DescriptorsDataChanged) {
descriptors = appGroup.getDescriptors()
setApplicationDescriptors(descriptors)
}
})
return () => {
removeAppGroupObserver()
}
}, [appGroup])
const { showActionSheet } = useCustomActionSheet()
const getWorkspaceActionConfirmation = useCallback(
async (action: WorkspaceAction): Promise<boolean> => {
const { Info, Danger } = ButtonType
const { AddAnother, Activate, Remove, SignOutAll } = WorkspaceAction
let message = ''
let buttonText = ''
let buttonType = Info
switch (action) {
case Activate:
message = 'Your workspace will be ready for you when you come back.'
buttonText = 'Quit App'
break
case AddAnother:
message = 'Your new workspace will be ready for you when you come back.'
buttonText = 'Quit App'
break
case SignOutAll:
message =
'Are you sure you want to sign out of all workspaces on this device? This action will restart the application.'
buttonText = 'Sign Out All'
break
case Remove:
message =
'This action will remove this workspace and its related data from this device. Your synced data will not be affected.'
buttonText = 'Delete Workspace'
buttonType = Danger
break
default:
break
}
return application.alertService.confirm(message, undefined, buttonText, buttonType)
},
[WorkspaceAction, application.alertService],
)
const renameWorkspace = useCallback(
async (descriptor: ApplicationDescriptor, newName: string) => {
appGroup.renameDescriptor(descriptor, newName)
},
[appGroup],
)
const signOutWorkspace = useCallback(async () => {
const confirmed = await getWorkspaceActionConfirmation(WorkspaceAction.Remove)
if (!confirmed) {
return
}
try {
await application.user.signOut()
} catch (error) {
console.error(error)
}
}, [WorkspaceAction.Remove, application.user, getWorkspaceActionConfirmation])
const openWorkspace = useCallback(
async (descriptor: ApplicationDescriptor) => {
const confirmed = await getWorkspaceActionConfirmation(WorkspaceAction.Activate)
if (!confirmed) {
return
}
await appGroup.unloadCurrentAndActivateDescriptor(descriptor)
},
[WorkspaceAction.Activate, appGroup, getWorkspaceActionConfirmation],
)
const getSingleWorkspaceItemOptions = useCallback(
(descriptor: ApplicationDescriptor) => {
const { Activate, Rename, Remove } = WorkspaceAction
const worskspaceItemOptions: CustomActionSheetOption[] = []
if (descriptor.primary) {
worskspaceItemOptions.push(
{
text: Rename,
callback: () => {
navigation.navigate(SCREEN_INPUT_MODAL_WORKSPACE_NAME, {
descriptor,
renameWorkspace,
})
},
},
{
text: Remove,
destructive: true,
callback: signOutWorkspace,
},
)
} else {
worskspaceItemOptions.push({
text: Activate,
callback: () => openWorkspace(descriptor),
})
}
return worskspaceItemOptions
},
[WorkspaceAction, navigation, openWorkspace, renameWorkspace, signOutWorkspace],
)
const addAnotherWorkspace = useCallback(async () => {
const confirmed = await getWorkspaceActionConfirmation(WorkspaceAction.AddAnother)
if (!confirmed) {
return
}
await appGroup.unloadCurrentAndCreateNewDescriptor()
}, [WorkspaceAction.AddAnother, appGroup, applicationDescriptors, getWorkspaceActionConfirmation])
const signOutAllWorkspaces = useCallback(async () => {
try {
const confirmed = await getWorkspaceActionConfirmation(WorkspaceAction.SignOutAll)
if (!confirmed) {
return
}
await appGroup.signOutAllWorkspaces()
} catch (error) {
console.error(error)
}
}, [WorkspaceAction.SignOutAll, appGroup, getWorkspaceActionConfirmation])
return (
<TableSection>
<SectionHeader title={'Workspaces'} />
{applicationDescriptors.map((descriptor, index) => {
return (
<SectionedAccessoryTableCell
onPress={() => {
const singleItemOptions = getSingleWorkspaceItemOptions(descriptor)
showActionSheet({
title: '',
options: singleItemOptions,
})
}}
key={descriptor.identifier}
text={descriptor.label}
first={index === 0}
selected={() => descriptor.primary}
/>
)
})}
<SectionedAccessoryTableCell onPress={addAnotherWorkspace} text={WorkspaceAction.AddAnother} key={'add-new'} />
<SectionedAccessoryTableCell
onPress={signOutAllWorkspaces}
text={WorkspaceAction.SignOutAll}
key={'sign-out-all'}
/>
</TableSection>
)
}

View File

@@ -3,6 +3,7 @@ import { useSafeApplicationContext } from '@Root/Hooks/useSafeApplicationContext
import { ModalStackNavigationProp } from '@Root/ModalStack' import { ModalStackNavigationProp } from '@Root/ModalStack'
import { SCREEN_SETTINGS } from '@Root/Screens/screens' import { SCREEN_SETTINGS } from '@Root/Screens/screens'
import { FilesSection } from '@Screens/Settings/Sections/FilesSection' import { FilesSection } from '@Screens/Settings/Sections/FilesSection'
import { WorkspacesSection } from '@Screens/Settings/Sections/WorkspacesSection'
import { FeatureIdentifier } from '@standardnotes/features' import { FeatureIdentifier } from '@standardnotes/features'
import { ApplicationEvent, FeatureStatus } from '@standardnotes/snjs' import { ApplicationEvent, FeatureStatus } from '@standardnotes/snjs'
import React, { useCallback, useEffect, useState } from 'react' import React, { useCallback, useEffect, useState } from 'react'
@@ -30,7 +31,7 @@ export const Settings = (props: Props) => {
}, [application]) }, [application])
useEffect(() => { useEffect(() => {
const removeApplicationEventSubscriber = application.addEventObserver(async event => { const removeApplicationEventSubscriber = application.addEventObserver(async (event) => {
if (event === ApplicationEvent.KeyStatusChanged) { if (event === ApplicationEvent.KeyStatusChanged) {
setHasPasscode(Boolean(application.hasPasscode())) setHasPasscode(Boolean(application.hasPasscode()))
updateProtectionsAvailable() updateProtectionsAvailable()
@@ -54,6 +55,7 @@ export const Settings = (props: Props) => {
<Container keyboardShouldPersistTaps={'always'} keyboardDismissMode={'interactive'}> <Container keyboardShouldPersistTaps={'always'} keyboardDismissMode={'interactive'}>
<AuthSection title="Account" signedIn={signedIn} /> <AuthSection title="Account" signedIn={signedIn} />
<OptionsSection encryptionAvailable={!!encryptionAvailable} title="Options" /> <OptionsSection encryptionAvailable={!!encryptionAvailable} title="Options" />
<WorkspacesSection />
<PreferencesSection /> <PreferencesSection />
{application.hasAccount() && isEntitledToFiles && <FilesSection />} {application.hasAccount() && isEntitledToFiles && <FilesSection />}
<SecuritySection <SecuritySection

View File

@@ -54,7 +54,7 @@ export const Files: FC<Props> = ({ note }) => {
return ( return (
<FilesContainer> <FilesContainer>
{attachedFiles.sort(filesService.sortByName).map(file => { {attachedFiles.sort(filesService.sortByName).map((file) => {
const iconType = application.iconsController.getIconForFileType(file.mimeType) const iconType = application.iconsController.getIconForFileType(file.mimeType)
return ( return (

View File

@@ -102,7 +102,7 @@ export const Listed: FC<TProps> = ({ note }) => {
showActionSheet({ showActionSheet({
title: item.display_name, title: item.display_name,
options: item.actions.map(action => ({ options: item.actions.map((action) => ({
text: (action as Action).label, text: (action as Action).label,
callback: async () => { callback: async () => {
setIsActionInProgress(true) setIsActionInProgress(true)

View File

@@ -36,7 +36,7 @@ export const MainSideMenu = React.memo(({ drawerRef }: Props) => {
const styles = useStyles(theme) const styles = useStyles(theme)
useEffect(() => { useEffect(() => {
const removeTagChangeObserver = application!.getAppState().addStateChangeObserver(state => { const removeTagChangeObserver = application!.getAppState().addStateChangeObserver((state) => {
if (state === AppStateType.TagChanged) { if (state === AppStateType.TagChanged) {
setSelectedTag(application!.getAppState().getSelectedTag()) setSelectedTag(application!.getAppState().getSelectedTag())
} }
@@ -160,7 +160,7 @@ export const MainSideMenu = React.memo(({ drawerRef }: Props) => {
const themeOptions = useMemo(() => { const themeOptions = useMemo(() => {
const options: SideMenuOption[] = themeService! const options: SideMenuOption[] = themeService!
.systemThemes() .systemThemes()
.map(systemTheme => ({ .map((systemTheme) => ({
text: systemTheme?.name, text: systemTheme?.name,
key: systemTheme?.uuid, key: systemTheme?.uuid,
iconDesc: iconDescriptorForTheme(systemTheme), iconDesc: iconDescriptorForTheme(systemTheme),
@@ -172,7 +172,7 @@ export const MainSideMenu = React.memo(({ drawerRef }: Props) => {
.concat( .concat(
themes themes
.sort((a, b) => a.name.localeCompare(b.name)) .sort((a, b) => a.name.localeCompare(b.name))
.map(mapTheme => ({ .map((mapTheme) => ({
text: mapTheme.name, text: mapTheme.name,
key: mapTheme.uuid, key: mapTheme.uuid,
iconDesc: iconDescriptorForTheme(mapTheme), iconDesc: iconDescriptorForTheme(mapTheme),
@@ -207,7 +207,7 @@ export const MainSideMenu = React.memo(({ drawerRef }: Props) => {
const onTagSelect = useCallback( const onTagSelect = useCallback(
async (tag: SNTag | SmartView) => { async (tag: SNTag | SmartView) => {
if (tag.conflictOf) { if (tag.conflictOf) {
void application!.mutator.changeAndSaveItem(tag, mutator => { void application!.mutator.changeAndSaveItem(tag, (mutator) => {
mutator.conflictOf = undefined mutator.conflictOf = undefined
}) })
} }
@@ -251,7 +251,7 @@ export const MainSideMenu = React.memo(({ drawerRef }: Props) => {
<SideMenuHero testID="settingsButton" onPress={openSettings} onOutOfSyncPress={outOfSyncPressed} /> <SideMenuHero testID="settingsButton" onPress={openSettings} onOutOfSyncPress={outOfSyncPressed} />
<FlatList <FlatList
style={styles.sections} style={styles.sections}
data={['themes-section', 'views-section', 'tags-section'].map(key => ({ data={['themes-section', 'views-section', 'tags-section'].map((key) => ({
key, key,
themeOptions, themeOptions,
onTagSelect, onTagSelect,

View File

@@ -122,7 +122,7 @@ export const NoteSideMenu = React.memo((props: Props) => {
} }
}, },
() => { () => {
void changeNote(mutator => { void changeNote((mutator) => {
mutator.trashed = true mutator.trashed = true
}, false) }, false)
props.drawerRef?.closeDrawer() props.drawerRef?.closeDrawer()
@@ -225,7 +225,7 @@ export const NoteSideMenu = React.memo((props: Props) => {
const disassociateComponentWithCurrentNote = useCallback( const disassociateComponentWithCurrentNote = useCallback(
async (component: SNComponent) => { async (component: SNComponent) => {
if (note) { if (note) {
return application.mutator.changeItem(component, m => { return application.mutator.changeItem(component, (m) => {
const mutator = m as ComponentMutator const mutator = m as ComponentMutator
mutator.removeAssociatedItemId(note.uuid) mutator.removeAssociatedItemId(note.uuid)
mutator.disassociateWithItem(note.uuid) mutator.disassociateWithItem(note.uuid)
@@ -256,7 +256,7 @@ export const NoteSideMenu = React.memo((props: Props) => {
if (!note?.prefersPlainEditor) { if (!note?.prefersPlainEditor) {
await application.mutator.changeItem( await application.mutator.changeItem(
note, note,
mutator => { (mutator) => {
const noteMutator = mutator as NoteMutator const noteMutator = mutator as NoteMutator
noteMutator.prefersPlainEditor = true noteMutator.prefersPlainEditor = true
}, },
@@ -275,7 +275,7 @@ export const NoteSideMenu = React.memo((props: Props) => {
if (prefersPlain) { if (prefersPlain) {
await application.mutator.changeItem( await application.mutator.changeItem(
note, note,
mutator => { (mutator) => {
const noteMutator = mutator as NoteMutator const noteMutator = mutator as NoteMutator
noteMutator.prefersPlainEditor = false noteMutator.prefersPlainEditor = false
}, },
@@ -294,7 +294,7 @@ export const NoteSideMenu = React.memo((props: Props) => {
async (component?: SNComponent) => { async (component?: SNComponent) => {
const currentDefault = application.componentManager const currentDefault = application.componentManager
.componentsForArea(ComponentArea.Editor) .componentsForArea(ComponentArea.Editor)
.filter(e => e.isMobileDefault)[0] .filter((e) => e.isMobileDefault)[0]
let isDefault = false let isDefault = false
if (!component) { if (!component) {
@@ -314,14 +314,14 @@ export const NoteSideMenu = React.memo((props: Props) => {
const setAsDefault = () => { const setAsDefault = () => {
if (currentDefault) { if (currentDefault) {
void application.mutator.changeItem(currentDefault, m => { void application.mutator.changeItem(currentDefault, (m) => {
const mutator = m as ComponentMutator const mutator = m as ComponentMutator
mutator.isMobileDefault = false mutator.isMobileDefault = false
}) })
} }
if (component) { if (component) {
void application.mutator.changeAndSaveItem(component, m => { void application.mutator.changeAndSaveItem(component, (m) => {
const mutator = m as ComponentMutator const mutator = m as ComponentMutator
mutator.isMobileDefault = true mutator.isMobileDefault = true
}) })
@@ -329,7 +329,7 @@ export const NoteSideMenu = React.memo((props: Props) => {
} }
const removeAsDefault = () => { const removeAsDefault = () => {
void application.mutator.changeItem(currentDefault, m => { void application.mutator.changeItem(currentDefault, (m) => {
const mutator = m as ComponentMutator const mutator = m as ComponentMutator
mutator.isMobileDefault = false mutator.isMobileDefault = false
}) })
@@ -376,7 +376,7 @@ export const NoteSideMenu = React.memo((props: Props) => {
}, },
}, },
] ]
components.map(component => { components.map((component) => {
options.push({ options.push({
text: FindNativeFeature(component.identifier)?.name || component.name, text: FindNativeFeature(component.identifier)?.name || component.name,
subtext: component.isMobileDefault ? 'Mobile Default' : undefined, subtext: component.isMobileDefault ? 'Mobile Default' : undefined,
@@ -435,7 +435,7 @@ export const NoteSideMenu = React.memo((props: Props) => {
const pinOption = note.pinned ? 'Unpin' : 'Pin' const pinOption = note.pinned ? 'Unpin' : 'Pin'
const pinEvent = () => const pinEvent = () =>
changeNote(mutator => { changeNote((mutator) => {
mutator.pinned = !note.pinned mutator.pinned = !note.pinned
}, false) }, false)
@@ -447,7 +447,7 @@ export const NoteSideMenu = React.memo((props: Props) => {
) )
return return
} }
void changeNote(mutator => { void changeNote((mutator) => {
mutator.archived = !note.archived mutator.archived = !note.archived
}, false) }, false)
leaveEditor() leaveEditor()
@@ -455,7 +455,7 @@ export const NoteSideMenu = React.memo((props: Props) => {
const lockOption = note.locked ? 'Enable editing' : 'Prevent editing' const lockOption = note.locked ? 'Enable editing' : 'Prevent editing'
const lockEvent = () => const lockEvent = () =>
changeNote(mutator => { changeNote((mutator) => {
mutator.locked = !note.locked mutator.locked = !note.locked
}, false) }, false)
@@ -506,7 +506,7 @@ export const NoteSideMenu = React.memo((props: Props) => {
}) })
} }
let options: SideMenuOption[] = rawOptions.map(rawOption => ({ let options: SideMenuOption[] = rawOptions.map((rawOption) => ({
text: rawOption.text, text: rawOption.text,
key: rawOption.icon, key: rawOption.icon,
iconDesc: { iconDesc: {
@@ -523,7 +523,7 @@ export const NoteSideMenu = React.memo((props: Props) => {
text: 'Restore', text: 'Restore',
key: 'restore-note', key: 'restore-note',
onSelect: () => { onSelect: () => {
void changeNote(mutator => { void changeNote((mutator) => {
mutator.trashed = false mutator.trashed = false
}, false) }, false)
}, },
@@ -574,11 +574,11 @@ export const NoteSideMenu = React.memo((props: Props) => {
const onTagSelect = useCallback( const onTagSelect = useCallback(
async (tag: SNTag | SmartView, addTagHierachy: boolean) => { async (tag: SNTag | SmartView, addTagHierachy: boolean) => {
const isSelected = selectedTags.findIndex(selectedTag => selectedTag.uuid === tag.uuid) > -1 const isSelected = selectedTags.findIndex((selectedTag) => selectedTag.uuid === tag.uuid) > -1
if (note) { if (note) {
if (isSelected) { if (isSelected) {
await application.mutator.changeItem(tag, mutator => { await application.mutator.changeItem(tag, (mutator) => {
mutator.removeItemAsRelationship(note) mutator.removeItemAsRelationship(note)
}) })
} else { } else {
@@ -607,7 +607,7 @@ export const NoteSideMenu = React.memo((props: Props) => {
<SafeAreaContainer edges={['top', 'bottom', 'right']}> <SafeAreaContainer edges={['top', 'bottom', 'right']}>
<FlatList <FlatList
style={styles.sections} style={styles.sections}
data={Object.values(MenuSections).map(key => ({ data={Object.values(MenuSections).map((key) => ({
key, key,
noteOptions, noteOptions,
editorComponents: editors, editorComponents: editors,
@@ -650,7 +650,7 @@ export const NoteSideMenu = React.memo((props: Props) => {
<TagSelectionList <TagSelectionList
hasBottomPadding={Platform.OS === 'android'} hasBottomPadding={Platform.OS === 'android'}
contentType={ContentType.Tag} contentType={ContentType.Tag}
onTagSelect={tag => item.onTagSelect(tag, shouldAddTagHierarchy)} onTagSelect={(tag) => item.onTagSelect(tag, shouldAddTagHierarchy)}
selectedTags={item.selectedTags} selectedTags={item.selectedTags}
emptyPlaceholder={'Create a new tag using the tag button in the bottom right corner.'} emptyPlaceholder={'Create a new tag using the tag button in the bottom right corner.'}
/> />

View File

@@ -2,7 +2,7 @@ import { Platform } from 'react-native'
import styled, { css } from 'styled-components/native' import styled, { css } from 'styled-components/native'
export const Touchable = styled.TouchableOpacity<{ isSubtext: boolean }>` export const Touchable = styled.TouchableOpacity<{ isSubtext: boolean }>`
min-height: ${props => (props.isSubtext ? 52 : 42)}px; min-height: ${(props) => (props.isSubtext ? 52 : 42)}px;
` `
export const CellContent = styled.View<{ export const CellContent = styled.View<{
iconSide: 'right' | 'left' | null iconSide: 'right' | 'left' | null
@@ -32,7 +32,7 @@ export const TextContainer = styled.View<{
isSubtext: boolean isSubtext: boolean
selected?: boolean selected?: boolean
}>` }>`
min-height: ${props => (props.isSubtext ? 38 : 24)}px; min-height: ${(props) => (props.isSubtext ? 38 : 24)}px;
margin-left: 6px; margin-left: 6px;
flex-shrink: 1; flex-shrink: 1;
${({ selected, theme }) => ${({ selected, theme }) =>

View File

@@ -46,7 +46,7 @@ const renderIcon = (desc: SideMenuOption['iconDesc'], color: string) => {
return <RegularText>*</RegularText> return <RegularText>*</RegularText>
} }
export const SideMenuCell: React.FC<SideMenuOption> = props => { export const SideMenuCell: React.FC<SideMenuOption> = (props) => {
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
const colorForTextClass = (textClass: SideMenuOption['textClass']) => { const colorForTextClass = (textClass: SideMenuOption['textClass']) => {
if (!textClass) { if (!textClass) {

View File

@@ -13,7 +13,7 @@ type Props = {
testID: ViewProps['testID'] testID: ViewProps['testID']
} }
export const SideMenuHero: React.FC<Props> = props => { export const SideMenuHero: React.FC<Props> = (props) => {
// Context // Context
const application = useContext(ApplicationContext) const application = useContext(ApplicationContext)
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
@@ -26,7 +26,7 @@ export const SideMenuHero: React.FC<Props> = props => {
useEffect(() => { useEffect(() => {
const observedContentTypes = [ContentType.Note, ContentType.Tag] const observedContentTypes = [ContentType.Note, ContentType.Tag]
const removeStreamItems = application?.streamItems(observedContentTypes, _items => { const removeStreamItems = application?.streamItems(observedContentTypes, (_items) => {
const notesAndTagsCount = application?.items.getItems(observedContentTypes).length ?? 0 const notesAndTagsCount = application?.items.getItems(observedContentTypes).length ?? 0
if (notesAndTagsCount !== itemsCount) { if (notesAndTagsCount !== itemsCount) {

View File

@@ -4,7 +4,7 @@ export const Root = styled.View`
padding-bottom: 6px; padding-bottom: 6px;
` `
export const Header = styled.TouchableOpacity<{ collapsed: boolean }>` export const Header = styled.TouchableOpacity<{ collapsed: boolean }>`
height: ${props => (props.collapsed ? 50 : 22)}px; height: ${(props) => (props.collapsed ? 50 : 22)}px;
` `
export const Title = styled.Text` export const Title = styled.Text`
color: ${({ theme }) => theme.stylekitInfoColor}; color: ${({ theme }) => theme.stylekitInfoColor};

View File

@@ -39,7 +39,7 @@ type Props = {
options?: SideMenuOption[] options?: SideMenuOption[]
} }
export const SideMenuSection: React.FC<Props> = React.memo(props => { export const SideMenuSection: React.FC<Props> = React.memo((props) => {
const [collapsed, setCollapsed] = useState(Boolean(props.collapsed)) const [collapsed, setCollapsed] = useState(Boolean(props.collapsed))
const options = useMemo(() => { const options = useMemo(() => {
return props.options || [] return props.options || []
@@ -58,7 +58,7 @@ export const SideMenuSection: React.FC<Props> = React.memo(props => {
{!collapsed && ( {!collapsed && (
<> <>
{options.map(option => { {options.map((option) => {
return ( return (
<SideMenuCell <SideMenuCell
text={option.text} text={option.text}

View File

@@ -100,7 +100,7 @@ export const TagSelectionList = React.memo(
let children: SNTag[] = [] let children: SNTag[] = []
if (showFolders && item instanceof SNTag) { if (showFolders && item instanceof SNTag) {
const rawChildren = application.items.getTagChildren(item).map(tag => tag.uuid) const rawChildren = application.items.getTagChildren(item).map((tag) => tag.uuid)
children = (tags as SNTag[]).filter((tag: SNTag) => rawChildren.includes(tag.uuid)) children = (tags as SNTag[]).filter((tag: SNTag) => rawChildren.includes(tag.uuid))
} }
@@ -130,7 +130,7 @@ export const TagSelectionList = React.memo(
windowSize={10} windowSize={10}
maxToRenderPerBatch={10} maxToRenderPerBatch={10}
data={children} data={children}
keyExtractor={childTag => childTag.uuid} keyExtractor={(childTag) => childTag.uuid}
renderItem={renderItem} renderItem={renderItem}
/> />
)} )}
@@ -147,7 +147,7 @@ export const TagSelectionList = React.memo(
windowSize={10} windowSize={10}
maxToRenderPerBatch={10} maxToRenderPerBatch={10}
data={renderedTags as SNTag[]} data={renderedTags as SNTag[]}
keyExtractor={item => item.uuid} keyExtractor={(item) => item.uuid}
renderItem={renderItem} renderItem={renderItem}
/> />
{tags.length === 0 && <EmptyPlaceholder>{emptyPlaceholder}</EmptyPlaceholder>} {tags.length === 0 && <EmptyPlaceholder>{emptyPlaceholder}</EmptyPlaceholder>}

View File

@@ -29,7 +29,7 @@ export enum Tabs {
type Props = ModalStackNavigationProp<typeof SCREEN_UPLOADED_FILES_LIST> type Props = ModalStackNavigationProp<typeof SCREEN_UPLOADED_FILES_LIST>
export const UploadedFilesList: FC<Props> = props => { export const UploadedFilesList: FC<Props> = (props) => {
const { AttachedFiles, AllFiles } = Tabs const { AttachedFiles, AllFiles } = Tabs
const { note } = props.route.params const { note } = props.route.params
@@ -54,7 +54,7 @@ export const UploadedFilesList: FC<Props> = props => {
const filteredList = useMemo(() => { const filteredList = useMemo(() => {
return searchString return searchString
? filesList.filter(file => file.name.toLowerCase().includes(searchString.toLowerCase())) ? filesList.filter((file) => file.name.toLowerCase().includes(searchString.toLowerCase()))
: filesList : filesList
}, [filesList, searchString]) }, [filesList, searchString])
@@ -128,7 +128,7 @@ export const UploadedFilesList: FC<Props> = props => {
ref={filesListRef} ref={filesListRef}
data={filteredList} data={filteredList}
renderItem={renderItem} renderItem={renderItem}
keyExtractor={item => item.uuid} keyExtractor={(item) => item.uuid}
onScroll={onScroll} onScroll={onScroll}
/> />
) : ( ) : (

View File

@@ -8,6 +8,7 @@ export const SCREEN_INPUT_MODAL_FILE_NAME = 'InputModalFileName'
export const SCREEN_NOTE_HISTORY = 'NoteSessionHistory' as const export const SCREEN_NOTE_HISTORY = 'NoteSessionHistory' as const
export const SCREEN_NOTE_HISTORY_PREVIEW = 'NoteSessionHistoryPreview' as const export const SCREEN_NOTE_HISTORY_PREVIEW = 'NoteSessionHistoryPreview' as const
export const SCREEN_UPLOADED_FILES_LIST = 'UploadedFilesList' as const export const SCREEN_UPLOADED_FILES_LIST = 'UploadedFilesList' as const
export const SCREEN_INPUT_MODAL_WORKSPACE_NAME = 'InputModalWorkspaceName' as const
export const SCREEN_SETTINGS = 'Settings' export const SCREEN_SETTINGS = 'Settings'
export const SCREEN_MANAGE_SESSIONS = 'ManageSessions' as const export const SCREEN_MANAGE_SESSIONS = 'ManageSessions' as const

View File

@@ -3,7 +3,7 @@ const PREFIX_STANDARD_NOTES = '--sn-stylekit'
const PREFIX_STANDARD_NOTES_BURN = '--sn-' const PREFIX_STANDARD_NOTES_BURN = '--sn-'
function camelCaseToDashed(camel: string) { function camelCaseToDashed(camel: string) {
return camel.replace(/[A-Z]/g, m => '-' + m.toLowerCase()) return camel.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase())
} }
export function objectToCss(object: any) { export function objectToCss(object: any) {

View File

@@ -49,12 +49,12 @@ export const useCustomActionSheet = () => {
}, },
] ]
const tempOptions = options.concat(cancelOption) const tempOptions = options.concat(cancelOption)
const destructiveIndex = tempOptions.findIndex(item => item.destructive) const destructiveIndex = tempOptions.findIndex((item) => item.destructive)
const cancelIndex = tempOptions.length - 1 const cancelIndex = tempOptions.length - 1
showActionSheetWithOptions( showActionSheetWithOptions(
{ {
options: tempOptions.map(option => option.text), options: tempOptions.map((option) => option.text),
destructiveButtonIndex: destructiveIndex, destructiveButtonIndex: destructiveIndex,
cancelButtonIndex: cancelIndex, cancelButtonIndex: cancelIndex,
title, title,
@@ -72,7 +72,7 @@ export const useCustomActionSheet = () => {
}, },
anchor: anchor ? findNodeHandle(anchor) ?? undefined : undefined, anchor: anchor ? findNodeHandle(anchor) ?? undefined : undefined,
}, },
buttonIndex => { (buttonIndex) => {
const option = tempOptions[buttonIndex!] const option = tempOptions[buttonIndex!]
option.callback && option.callback(option) option.callback && option.callback(option)
}, },

View File

@@ -78,7 +78,7 @@ export class ThemeService {
} }
private registerObservers() { private registerObservers() {
this.unsubsribeAppEventObserver = this.application?.addEventObserver(async event => { this.unsubsribeAppEventObserver = this.application?.addEventObserver(async (event) => {
/** /**
* If there are any migrations we need to set default theme to start UI * If there are any migrations we need to set default theme to start UI
*/ */
@@ -259,7 +259,7 @@ export class ThemeService {
private async resolveInitialThemeForMode() { private async resolveInitialThemeForMode() {
try { try {
const savedThemeId = await this.getThemeForMode(this.getColorScheme()) const savedThemeId = await this.getThemeForMode(this.getColorScheme())
const matchingThemeId = Object.keys(this.themes).find(themeId => themeId === savedThemeId) const matchingThemeId = Object.keys(this.themes).find((themeId) => themeId === savedThemeId)
if (matchingThemeId) { if (matchingThemeId) {
this.setActiveTheme(matchingThemeId) this.setActiveTheme(matchingThemeId)
void this.application?.mobileComponentManager.preloadThirdPartyThemeIndexPath() void this.application?.mobileComponentManager.preloadThirdPartyThemeIndexPath()
@@ -278,7 +278,7 @@ export class ThemeService {
systemThemes() { systemThemes() {
return Object.values(this.themes) return Object.values(this.themes)
.filter(theme => theme.mobileContent.isSystemTheme) .filter((theme) => theme.mobileContent.isSystemTheme)
.sort((a, b) => { .sort((a, b) => {
if (a.name < b.name) { if (a.name < b.name) {
return -1 return -1
@@ -292,7 +292,7 @@ export class ThemeService {
nonSystemThemes() { nonSystemThemes() {
return Object.values(this.themes) return Object.values(this.themes)
.filter(theme => !theme.mobileContent.isSystemTheme) .filter((theme) => !theme.mobileContent.isSystemTheme)
.sort((a, b) => { .sort((a, b) => {
if (a.name < b.name) { if (a.name < b.name) {
return -1 return -1
@@ -465,7 +465,7 @@ export class ThemeService {
private async loadCachedThemes() { private async loadCachedThemes() {
const rawValue = (await this.application!.getValue(CACHED_THEMES_KEY, StorageValueModes.Nonwrapped)) || [] const rawValue = (await this.application!.getValue(CACHED_THEMES_KEY, StorageValueModes.Nonwrapped)) || []
const themes = (rawValue as DecryptedTransferPayload<ComponentContent>[]).map(rawPayload => { const themes = (rawValue as DecryptedTransferPayload<ComponentContent>[]).map((rawPayload) => {
const payload = new DecryptedPayload<ComponentContent>(rawPayload) const payload = new DecryptedPayload<ComponentContent>(rawPayload)
return new MobileTheme(payload) return new MobileTheme(payload)
@@ -480,7 +480,7 @@ export class ThemeService {
const themes = this.nonSystemThemes() const themes = this.nonSystemThemes()
return this.application!.setValue( return this.application!.setValue(
CACHED_THEMES_KEY, CACHED_THEMES_KEY,
themes.map(t => t.payloadRepresentation()), themes.map((t) => t.payloadRepresentation()),
StorageValueModes.Nonwrapped, StorageValueModes.Nonwrapped,
) )
} }