mirror of
https://github.com/standardnotes/mobile.git
synced 2026-02-05 13:31:34 -05:00
feature: note session history
This commit is contained in:
@@ -243,6 +243,8 @@ PODS:
|
||||
- React
|
||||
- react-native-safe-area-context (3.1.6):
|
||||
- React
|
||||
- react-native-segmented-control (2.1.1):
|
||||
- React
|
||||
- react-native-sodium (0.4.0):
|
||||
- React
|
||||
- react-native-version-info (1.0.1):
|
||||
@@ -370,6 +372,7 @@ DEPENDENCIES:
|
||||
- react-native-fingerprint-scanner (from `../node_modules/react-native-fingerprint-scanner`)
|
||||
- react-native-mail (from `../node_modules/react-native-mail`)
|
||||
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
|
||||
- "react-native-segmented-control (from `../node_modules/@react-native-community/segmented-control`)"
|
||||
- react-native-sodium (from `../node_modules/react-native-sodium`)
|
||||
- react-native-version-info (from `../node_modules/react-native-version-info`)
|
||||
- react-native-webview (from `../node_modules/react-native-webview`)
|
||||
@@ -457,6 +460,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native-mail"
|
||||
react-native-safe-area-context:
|
||||
:path: "../node_modules/react-native-safe-area-context"
|
||||
react-native-segmented-control:
|
||||
:path: "../node_modules/@react-native-community/segmented-control"
|
||||
react-native-sodium:
|
||||
:path: "../node_modules/react-native-sodium"
|
||||
react-native-version-info:
|
||||
@@ -547,6 +552,7 @@ SPEC CHECKSUMS:
|
||||
react-native-fingerprint-scanner: f0d8190ceaf0b9e1893e3379d78724375b8f6ea7
|
||||
react-native-mail: a864fb211feaa5845c6c478a3266de725afdce89
|
||||
react-native-safe-area-context: 4fb3cdeb4a405ec19f18aca80ef2144381ffc761
|
||||
react-native-segmented-control: 05d93c1c7576b53189720012a3e8a3c23608d849
|
||||
react-native-sodium: ef43e28fdf8d866e68ed06890c32f8d86a570cc7
|
||||
react-native-version-info: f95938832bdb2ce94c3d045c2e44fabcc88f796a
|
||||
react-native-webview: 0e49a2e85f42b20ea5579928a8b254a07fa901dc
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"@expo/react-native-action-sheet": "^3.8.0",
|
||||
"@react-native-community/async-storage": "1.12.0",
|
||||
"@react-native-community/masked-view": "^0.1.10",
|
||||
"@react-native-community/segmented-control": "^2.1.1",
|
||||
"@react-navigation/native": "^5.7.3",
|
||||
"@react-navigation/stack": "^5.9.0",
|
||||
"base64-arraybuffer": "^0.2.0",
|
||||
@@ -47,6 +48,7 @@
|
||||
"react-native-search-box": "standardnotes/react-native-search-box#f61a2b5",
|
||||
"react-native-sodium": "standardnotes/react-native-sodium#f68a35b",
|
||||
"react-native-store-review": "^0.1.5",
|
||||
"react-native-tab-view": "^2.15.1",
|
||||
"react-native-vector-icons": "^7.0.0",
|
||||
"react-native-version-info": "^1.0.1",
|
||||
"react-native-webview": "^10.8.2",
|
||||
|
||||
23
src/App.tsx
23
src/App.tsx
@@ -25,6 +25,7 @@ import { AuthenticatePrivileges } from '@Screens/Authenticate/AuthenticatePrivil
|
||||
import { Compose } from '@Screens/Compose/Compose';
|
||||
import { PasscodeInputModal } from '@Screens/InputModal/PasscodeInputModal';
|
||||
import { TagInputModal } from '@Screens/InputModal/TagInputModal';
|
||||
import { NoteHistory } from '@Screens/NoteHistory/NoteHistory';
|
||||
import { Root } from '@Screens/Root';
|
||||
import { ManagePrivileges } from '@Screens/Settings/ManagePrivileges';
|
||||
import { Settings } from '@Screens/Settings/Settings';
|
||||
@@ -33,7 +34,7 @@ import { NoteSideMenu } from '@Screens/SideMenu/NoteSideMenu';
|
||||
import { ICON_CHECKMARK, ICON_CLOSE, ICON_MENU } from '@Style/icons';
|
||||
import { StyleKit, StyleKitContext } from '@Style/StyleKit';
|
||||
import { StyleKitTheme } from '@Style/Themes/styled-components';
|
||||
import { getDefaultDrawerWidth } from '@Style/Util/getDefaultDraerWidth';
|
||||
import { getDefaultDrawerWidth } from '@Style/Util/getDefaultDrawerWidth';
|
||||
import React, {
|
||||
useCallback,
|
||||
useContext,
|
||||
@@ -63,6 +64,7 @@ import {
|
||||
SCREEN_INPUT_MODAL_TAG,
|
||||
SCREEN_MANAGE_PRIVILEGES,
|
||||
SCREEN_NOTES,
|
||||
SCREEN_NOTE_HISTORY,
|
||||
SCREEN_SETTINGS,
|
||||
} from './screens/screens';
|
||||
|
||||
@@ -75,6 +77,9 @@ type HeaderTitleParams = {
|
||||
type AppStackNavigatorParamList = {
|
||||
[SCREEN_NOTES]: HeaderTitleParams;
|
||||
[SCREEN_COMPOSE]: HeaderTitleParams | undefined;
|
||||
[SCREEN_NOTE_HISTORY]:
|
||||
| (HeaderTitleParams & { noteUuid: string })
|
||||
| (undefined & { noteUuid: string });
|
||||
};
|
||||
|
||||
type ModalStackNavigatorParamList = {
|
||||
@@ -294,6 +299,22 @@ const AppStackComponent = (props: ModalStackNavigationProp<'AppStack'>) => {
|
||||
})}
|
||||
component={Compose}
|
||||
/>
|
||||
<AppStack.Screen
|
||||
name={SCREEN_NOTE_HISTORY}
|
||||
options={({ route }) => ({
|
||||
title: 'Note history',
|
||||
headerTitle: ({ children }) => {
|
||||
return (
|
||||
<HeaderTitleView
|
||||
title={route.params?.title ?? (children || '')}
|
||||
subtitle={route.params?.subTitle}
|
||||
subtitleColor={route.params?.subTitleColor}
|
||||
/>
|
||||
);
|
||||
},
|
||||
})}
|
||||
component={NoteHistory}
|
||||
/>
|
||||
</AppStack.Navigator>
|
||||
</DrawerLayout>
|
||||
</DrawerLayout>
|
||||
|
||||
17
src/screens/NoteHistory/NoteHistory.styled.ts
Normal file
17
src/screens/NoteHistory/NoteHistory.styled.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import styled from 'styled-components/native';
|
||||
|
||||
export const Container = styled.View``;
|
||||
|
||||
export const DateText = styled.Text`
|
||||
font-size: 15px;
|
||||
margin-top: 4px;
|
||||
opacity: 0.8;
|
||||
line-height: 21px;
|
||||
`;
|
||||
|
||||
export const IosTabBarContainer = styled.View`
|
||||
padding-top: 10px;
|
||||
padding-bottom: 5px;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
`;
|
||||
145
src/screens/NoteHistory/NoteHistory.tsx
Normal file
145
src/screens/NoteHistory/NoteHistory.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
import SegmentedControl from '@react-native-community/segmented-control';
|
||||
import { AppStackNavigationProp } from '@Root/App';
|
||||
import { ApplicationContext } from '@Root/ApplicationContext';
|
||||
import { SCREEN_NOTE_HISTORY } from '@Screens/screens';
|
||||
import { StyleKitContext } from '@Style/StyleKit';
|
||||
import React, { useCallback, useContext, useState } from 'react';
|
||||
import { Dimensions, Platform } from 'react-native';
|
||||
import {
|
||||
NavigationState,
|
||||
Route,
|
||||
SceneRendererProps,
|
||||
TabBar,
|
||||
TabView,
|
||||
} from 'react-native-tab-view';
|
||||
import { ButtonType, ContentType, PayloadSource, SNNote } from 'snjs';
|
||||
import { PayloadContent } from 'snjs/dist/@types/protocol/payloads/generator';
|
||||
import { ThemeContext } from 'styled-components/native';
|
||||
import { IosTabBarContainer } from './NoteHistory.styled';
|
||||
import { RemoteHistory } from './RemoteHistory';
|
||||
import { SessionHistory } from './SessionHistory';
|
||||
|
||||
const initialLayout = { width: Dimensions.get('window').width };
|
||||
|
||||
type Props = AppStackNavigationProp<typeof SCREEN_NOTE_HISTORY>;
|
||||
export const NoteHistory = (props: Props) => {
|
||||
// Context
|
||||
const application = useContext(ApplicationContext);
|
||||
const theme = useContext(ThemeContext);
|
||||
const styleKit = useContext(StyleKitContext);
|
||||
|
||||
// State
|
||||
const [note] = useState<SNNote>(
|
||||
() => application?.findItem(props.route.params.noteUuid) as SNNote
|
||||
);
|
||||
const [routes] = React.useState([
|
||||
{ key: 'session', title: 'Session' },
|
||||
{ key: 'remote', title: 'Remote' },
|
||||
]);
|
||||
const [index, setIndex] = useState(0);
|
||||
|
||||
const restore = useCallback(
|
||||
async (asCopy: boolean, revisionUuid: string, content: PayloadContent) => {
|
||||
const run = async () => {
|
||||
if (asCopy) {
|
||||
const contentCopy = Object.assign({}, content);
|
||||
if (contentCopy.title) {
|
||||
contentCopy.title += ' (copy)';
|
||||
}
|
||||
await application?.createManagedItem(
|
||||
ContentType.Note,
|
||||
contentCopy,
|
||||
true
|
||||
);
|
||||
props.navigation.popToTop();
|
||||
} else {
|
||||
await application?.changeAndSaveItem(
|
||||
revisionUuid,
|
||||
mutator => {
|
||||
mutator.setContent(content);
|
||||
},
|
||||
true,
|
||||
PayloadSource.RemoteActionRetrieved
|
||||
);
|
||||
props.navigation.goBack();
|
||||
}
|
||||
};
|
||||
|
||||
if (!asCopy) {
|
||||
const confirmed = await application?.alertService?.confirm(
|
||||
"Are you sure you want to replace the current note's contents with what you see in this preview?",
|
||||
'Restore note',
|
||||
'Restore',
|
||||
ButtonType.Info
|
||||
);
|
||||
if (confirmed) {
|
||||
run();
|
||||
}
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
},
|
||||
[application, props.navigation]
|
||||
);
|
||||
|
||||
const renderScene = ({
|
||||
route,
|
||||
}: {
|
||||
route: { key: string; title: string };
|
||||
}) => {
|
||||
switch (route.key) {
|
||||
case 'session':
|
||||
return <SessionHistory restoreNote={restore} note={note} />;
|
||||
case 'remote':
|
||||
return <RemoteHistory restoreNote={restore} note={note} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const renderTabBar = (
|
||||
tabBarProps: SceneRendererProps & {
|
||||
navigationState: NavigationState<Route>;
|
||||
}
|
||||
) => {
|
||||
return Platform.OS === 'ios' &&
|
||||
parseInt(Platform.Version as string, 10) >= 13 ? (
|
||||
<IosTabBarContainer>
|
||||
<SegmentedControl
|
||||
backgroundColor={theme.stylekitContrastBackgroundColor}
|
||||
appearance={styleKit?.keyboardColorForActiveTheme()}
|
||||
fontStyle={{
|
||||
color: theme.stylekitForegroundColor,
|
||||
}}
|
||||
values={routes.map(route => route.title)}
|
||||
selectedIndex={tabBarProps.navigationState.index}
|
||||
onChange={event => {
|
||||
setIndex(event.nativeEvent.selectedSegmentIndex);
|
||||
}}
|
||||
/>
|
||||
</IosTabBarContainer>
|
||||
) : (
|
||||
<TabBar
|
||||
{...tabBarProps}
|
||||
indicatorStyle={{ backgroundColor: theme.stylekitInfoColor }}
|
||||
inactiveColor={theme.stylekitBorderColor}
|
||||
activeColor={theme.stylekitInfoColor}
|
||||
style={{
|
||||
backgroundColor: theme.stylekitBackgroundColor,
|
||||
shadowColor: theme.stylekitShadowColor,
|
||||
}}
|
||||
labelStyle={{ color: theme.stylekitInfoColor }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<TabView
|
||||
renderTabBar={renderTabBar}
|
||||
navigationState={{ index, routes }}
|
||||
renderScene={renderScene}
|
||||
onIndexChange={setIndex}
|
||||
initialLayout={initialLayout}
|
||||
/>
|
||||
);
|
||||
};
|
||||
64
src/screens/NoteHistory/NoteHistoryCell.tsx
Normal file
64
src/screens/NoteHistory/NoteHistoryCell.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import {
|
||||
Props as TableCellProps,
|
||||
SectionedTableCellTouchableHighlight,
|
||||
} from '@Components/SectionedTableCell';
|
||||
import React from 'react';
|
||||
import styled, { css } from 'styled-components/native';
|
||||
|
||||
type Props = {
|
||||
testID?: string;
|
||||
disabled?: boolean;
|
||||
onPress: () => void;
|
||||
first?: boolean;
|
||||
last?: boolean;
|
||||
title: string;
|
||||
subTitle?: string;
|
||||
};
|
||||
|
||||
const Container = styled(SectionedTableCellTouchableHighlight).attrs(props => ({
|
||||
underlayColor: props.theme.stylekitBorderColor,
|
||||
}))<TableCellProps>`
|
||||
padding-top: ${12}px;
|
||||
justify-content: center;
|
||||
`;
|
||||
const ButtonContainer = styled.View``;
|
||||
|
||||
type ButtonLabelProps = Pick<Props, 'disabled'>;
|
||||
const ButtonLabel = styled.Text<ButtonLabelProps>`
|
||||
color: ${props => {
|
||||
let color = props.theme.stylekitForegroundColor;
|
||||
if (props.disabled) {
|
||||
color = 'gray';
|
||||
}
|
||||
return color;
|
||||
}};
|
||||
font-weight: bold;
|
||||
font-size: ${props => props.theme.mainTextFontSize}px;
|
||||
${({ disabled }) =>
|
||||
disabled &&
|
||||
css`
|
||||
opacity: 0.6;
|
||||
`}
|
||||
`;
|
||||
export const SubTitleText = styled.Text`
|
||||
font-size: 14px;
|
||||
margin-top: 4px;
|
||||
color: ${({ theme }) => theme.stylekitForegroundColor};
|
||||
opacity: 0.8;
|
||||
line-height: 21px;
|
||||
`;
|
||||
|
||||
export const NoteHistoryCell: React.FC<Props> = props => (
|
||||
<Container
|
||||
first={props.first}
|
||||
last={props.last}
|
||||
testID={props.testID}
|
||||
disabled={props.disabled}
|
||||
onPress={props.onPress}
|
||||
>
|
||||
<ButtonContainer>
|
||||
<ButtonLabel disabled={props.disabled}>{props.title}</ButtonLabel>
|
||||
{props.subTitle && <SubTitleText>{props.subTitle}</SubTitleText>}
|
||||
</ButtonContainer>
|
||||
</Container>
|
||||
);
|
||||
136
src/screens/NoteHistory/RemoteHistory.tsx
Normal file
136
src/screens/NoteHistory/RemoteHistory.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import { ApplicationContext } from '@Root/ApplicationContext';
|
||||
import { LoadingContainer, LoadingText } from '@Screens/Notes/NoteList.styled';
|
||||
import { useCustomActionSheet } from '@Style/useCustomActionSheet';
|
||||
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { FlatList, ListRenderItem } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { SNNote } from 'snjs';
|
||||
import { PayloadContent } from 'snjs/dist/@types/protocol/payloads/generator';
|
||||
import {
|
||||
RemoteHistoryList,
|
||||
RemoteHistoryListEntry,
|
||||
} from 'snjs/dist/@types/services/history/history_manager';
|
||||
import { NoteHistoryCell } from './NoteHistoryCell';
|
||||
|
||||
type Props = {
|
||||
note: SNNote;
|
||||
restoreNote: (
|
||||
asCopy: boolean,
|
||||
uuid: string,
|
||||
content: PayloadContent
|
||||
) => Promise<void>;
|
||||
};
|
||||
export const RemoteHistory: React.FC<Props> = ({ note, restoreNote }) => {
|
||||
// Context
|
||||
const application = useContext(ApplicationContext);
|
||||
const { showActionSheet } = useCustomActionSheet();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
// State
|
||||
const [remoteHistoryList, setRemoteHistoryList] = useState<
|
||||
RemoteHistoryList
|
||||
>();
|
||||
const [fetchingRemoteHistory, setFetchingRemoteHistory] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
|
||||
const fetchRemoteHistoryList = async () => {
|
||||
if (note) {
|
||||
setFetchingRemoteHistory(true);
|
||||
const newRemoteHistory = await application?.historyManager?.remoteHistoryForItem(
|
||||
note
|
||||
);
|
||||
if (isMounted) {
|
||||
setFetchingRemoteHistory(false);
|
||||
setRemoteHistoryList(newRemoteHistory);
|
||||
}
|
||||
}
|
||||
};
|
||||
fetchRemoteHistoryList();
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
};
|
||||
}, [application?.historyManager, note]);
|
||||
|
||||
const onPress = useCallback(
|
||||
async (item: RemoteHistoryListEntry) => {
|
||||
const fetchAndRestoreRevision = async (asCopy: boolean) => {
|
||||
const remoteRevision = await application?.historyManager!.fetchRemoteRevision(
|
||||
note.uuid,
|
||||
item
|
||||
);
|
||||
if (remoteRevision) {
|
||||
restoreNote(
|
||||
asCopy,
|
||||
remoteRevision.payload.uuid,
|
||||
remoteRevision.payload.safeContent
|
||||
);
|
||||
} else {
|
||||
application?.alertService!.alert(
|
||||
'The remote revision could not be loaded. Please try again later.',
|
||||
'Error'
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
showActionSheet(item.updated_at.toLocaleString(), [
|
||||
{
|
||||
text: 'Restore',
|
||||
callback: () => fetchAndRestoreRevision(false),
|
||||
},
|
||||
{
|
||||
text: 'Restore as copy',
|
||||
callback: async () => fetchAndRestoreRevision(true),
|
||||
},
|
||||
]);
|
||||
},
|
||||
[
|
||||
application?.alertService,
|
||||
application?.historyManager,
|
||||
note.uuid,
|
||||
restoreNote,
|
||||
showActionSheet,
|
||||
]
|
||||
);
|
||||
|
||||
const RenderItem:
|
||||
| ListRenderItem<RemoteHistoryListEntry>
|
||||
| null
|
||||
| undefined = ({ item }) => {
|
||||
return (
|
||||
<NoteHistoryCell
|
||||
onPress={() => onPress(item)}
|
||||
title={new Date(item.updated_at).toLocaleString()}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
if (
|
||||
fetchingRemoteHistory ||
|
||||
(remoteHistoryList && remoteHistoryList.length === 0)
|
||||
) {
|
||||
const placeholderText = fetchingRemoteHistory
|
||||
? 'Loading entries...'
|
||||
: 'No entries.';
|
||||
return (
|
||||
<LoadingContainer>
|
||||
<LoadingText>{placeholderText}</LoadingText>
|
||||
</LoadingContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FlatList<RemoteHistoryListEntry>
|
||||
keyExtractor={item => item.uuid}
|
||||
contentContainerStyle={{ paddingBottom: insets.bottom }}
|
||||
initialNumToRender={10}
|
||||
windowSize={10}
|
||||
keyboardShouldPersistTaps={'never'}
|
||||
data={remoteHistoryList}
|
||||
renderItem={RenderItem}
|
||||
/>
|
||||
);
|
||||
};
|
||||
77
src/screens/NoteHistory/SessionHistory.tsx
Normal file
77
src/screens/NoteHistory/SessionHistory.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import { ApplicationContext } from '@Root/ApplicationContext';
|
||||
import { useCustomActionSheet } from '@Style/useCustomActionSheet';
|
||||
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { FlatList, ListRenderItem } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { ItemSessionHistory, SNNote } from 'snjs';
|
||||
import { PayloadContent } from 'snjs/dist/@types/protocol/payloads/generator';
|
||||
import { NoteHistoryEntry } from 'snjs/dist/@types/services/history/entries/note_history_entry';
|
||||
import { NoteHistoryCell } from './NoteHistoryCell';
|
||||
|
||||
type Props = {
|
||||
note: SNNote;
|
||||
restoreNote: (
|
||||
asCopy: boolean,
|
||||
uuid: string,
|
||||
content: PayloadContent
|
||||
) => Promise<void>;
|
||||
};
|
||||
export const SessionHistory: React.FC<Props> = ({ note, restoreNote }) => {
|
||||
// Context
|
||||
const application = useContext(ApplicationContext);
|
||||
const { showActionSheet } = useCustomActionSheet();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
// State
|
||||
const [sessionHistory, setSessionHistory] = useState<ItemSessionHistory>();
|
||||
|
||||
useEffect(() => {
|
||||
if (note) {
|
||||
setSessionHistory(
|
||||
application?.historyManager?.sessionHistoryForItem(note)
|
||||
);
|
||||
}
|
||||
}, [application?.historyManager, note]);
|
||||
|
||||
const onPress = useCallback(
|
||||
(item: NoteHistoryEntry) => {
|
||||
showActionSheet(item.previewTitle(), [
|
||||
{
|
||||
text: 'Restore',
|
||||
callback: () =>
|
||||
restoreNote(false, item.payload.uuid, item.payload.safeContent),
|
||||
},
|
||||
{
|
||||
text: 'Restore as copy',
|
||||
callback: async () =>
|
||||
restoreNote(true, item.payload.uuid, item.payload.safeContent),
|
||||
},
|
||||
]);
|
||||
},
|
||||
[restoreNote, showActionSheet]
|
||||
);
|
||||
|
||||
const RenderItem: ListRenderItem<NoteHistoryEntry> | null | undefined = ({
|
||||
item,
|
||||
}) => {
|
||||
return (
|
||||
<NoteHistoryCell
|
||||
onPress={() => onPress(item)}
|
||||
title={item.previewTitle()}
|
||||
subTitle={item.previewSubTitle()}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<FlatList<NoteHistoryEntry>
|
||||
keyExtractor={item => item.previewTitle()}
|
||||
contentContainerStyle={{ paddingBottom: insets.bottom }}
|
||||
initialNumToRender={10}
|
||||
windowSize={10}
|
||||
keyboardShouldPersistTaps={'never'}
|
||||
data={sessionHistory?.entries as NoteHistoryEntry[]}
|
||||
renderItem={RenderItem}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -240,7 +240,7 @@ export const NoteCell = ({
|
||||
</NoteText>
|
||||
)}
|
||||
|
||||
{!hideDates && (
|
||||
{!note.errorDecrypting && !hideDates && (
|
||||
<DateText numberOfLines={1} selected={highlight}>
|
||||
{sortType === CollectionSort.UpdatedAt
|
||||
? 'Modified ' + note.updatedAtString
|
||||
|
||||
@@ -3,11 +3,16 @@ import { useDeleteNoteWithPrivileges } from '@Lib/snjsHooks';
|
||||
import { useFocusEffect, useNavigation } from '@react-navigation/native';
|
||||
import { AppStackNavigationProp } from '@Root/App';
|
||||
import { ApplicationContext } from '@Root/ApplicationContext';
|
||||
import { SCREEN_COMPOSE, SCREEN_INPUT_MODAL_TAG } from '@Screens/screens';
|
||||
import {
|
||||
SCREEN_COMPOSE,
|
||||
SCREEN_INPUT_MODAL_TAG,
|
||||
SCREEN_NOTE_HISTORY,
|
||||
} from '@Screens/screens';
|
||||
import {
|
||||
ICON_ARCHIVE,
|
||||
ICON_BOOKMARK,
|
||||
ICON_FINGER_PRINT,
|
||||
ICON_HISTORY,
|
||||
ICON_LOCK,
|
||||
ICON_MEDICAL,
|
||||
ICON_PRICE_TAG,
|
||||
@@ -455,6 +460,11 @@ export const NoteSideMenu = (props: Props) => {
|
||||
mutator.protected = !note.protected;
|
||||
});
|
||||
|
||||
const openSessionHistory = () => {
|
||||
props.drawerRef?.closeDrawer();
|
||||
navigation.push(SCREEN_NOTE_HISTORY, { noteUuid: note.uuid });
|
||||
};
|
||||
|
||||
const shareNote = () => {
|
||||
if (note) {
|
||||
application?.getAppState().performActionWithoutStateChangeImpact(() => {
|
||||
@@ -471,6 +481,11 @@ export const NoteSideMenu = (props: Props) => {
|
||||
{ text: archiveOption, onSelect: archiveEvent, icon: ICON_ARCHIVE },
|
||||
{ text: lockOption, onSelect: lockEvent, icon: ICON_LOCK },
|
||||
{ text: protectOption, onSelect: protectEvent, icon: ICON_FINGER_PRINT },
|
||||
{
|
||||
text: 'Show History',
|
||||
onSelect: openSessionHistory,
|
||||
icon: ICON_HISTORY,
|
||||
},
|
||||
{ text: 'Share', onSelect: shareNote, icon: ICON_SHARE },
|
||||
];
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ export const SCREEN_NOTES = 'Notes' as 'Notes';
|
||||
export const SCREEN_COMPOSE = 'Compose' as 'Compose';
|
||||
export const SCREEN_INPUT_MODAL_PASSCODE = 'InputModalPasscode' as 'InputModalPasscode';
|
||||
export const SCREEN_INPUT_MODAL_TAG = 'InputModalTag' as 'InputModalTag';
|
||||
export const SCREEN_NOTE_HISTORY = 'NoteSessionHistory' as 'NoteSessionHistory';
|
||||
|
||||
export const SCREEN_SETTINGS = 'Settings';
|
||||
export const SCREEN_MANAGE_PRIVILEGES = 'ManagePrivileges' as 'ManagePrivileges';
|
||||
|
||||
@@ -15,3 +15,4 @@ export const ICON_SHARE = 'share';
|
||||
export const ICON_TRASH = 'trash';
|
||||
export const ICON_USER = 'person-circle';
|
||||
export const ICON_FORWARD = 'arrow-forward';
|
||||
export const ICON_HISTORY = 'time';
|
||||
|
||||
10
yarn.lock
10
yarn.lock
@@ -1319,6 +1319,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@react-native-community/masked-view/-/masked-view-0.1.10.tgz#5dda643e19e587793bc2034dd9bf7398ad43d401"
|
||||
integrity sha512-rk4sWFsmtOw8oyx8SD3KSvawwaK7gRBSEIy2TAwURyGt+3TizssXP1r8nx3zY+R7v2vYYHXZ+k2/GULAT/bcaQ==
|
||||
|
||||
"@react-native-community/segmented-control@^2.1.1":
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@react-native-community/segmented-control/-/segmented-control-2.1.1.tgz#e3a0934b79e7bd64b84215366989bb26b850ca06"
|
||||
integrity sha512-vSrg+DIqX0zGeOb1o6oFLoWFFW8l1UEX/f7/9dXXzWHChF3rIqEpNHC4ONmsLJqWePN4E/n+k+q29z+GbqrqsQ==
|
||||
|
||||
"@react-navigation/core@^5.12.3":
|
||||
version "5.12.3"
|
||||
resolved "https://registry.yarnpkg.com/@react-navigation/core/-/core-5.12.3.tgz#0484fcca290569a0dc10b70b99f00edd3f1fd93c"
|
||||
@@ -6674,6 +6679,11 @@ react-native-store-review@^0.1.5:
|
||||
resolved "https://registry.yarnpkg.com/react-native-store-review/-/react-native-store-review-0.1.5.tgz#9df69786a137580748e368641698d2104519e4cf"
|
||||
integrity sha512-vVx7NYaQva3bGU5MdqXn4yEB+o+GPdmjqAuj7PnkepfeCS6Bi3sqniiKoXmKOKDgRTfIobBZjUkHzWeHli1+3A==
|
||||
|
||||
react-native-tab-view@^2.15.1:
|
||||
version "2.15.1"
|
||||
resolved "https://registry.yarnpkg.com/react-native-tab-view/-/react-native-tab-view-2.15.1.tgz#cf4df4ffdec504263a2e06a6becd8831b9a19ad9"
|
||||
integrity sha512-cDYl1pNWspbEHBjHrHVpIC40h4g8VhZe0CIG0CUKcouX98vHVfAojxhgWuRosq9qMhITHpLKM+7BmBLnhCJ0nw==
|
||||
|
||||
react-native-vector-icons@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native-vector-icons/-/react-native-vector-icons-7.0.0.tgz#5b92ed363c867645daad48c559e1f99efcfbb813"
|
||||
|
||||
Reference in New Issue
Block a user