mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-09-03 04:31:46 +02:00
Merge pull request #73 from streetwriters/feat-version-history
Feat version history
This commit is contained in:
@@ -79,6 +79,7 @@
|
||||
|
||||
.htmldiff_div {
|
||||
padding: 12px !important;
|
||||
padding-top: 0px !important;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
min-height: 150px;
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
|
||||
.htmldiff_div {
|
||||
padding: 12px !important;
|
||||
padding-top: 0px !important;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
min-height: 150px;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {
|
||||
Dimensions,
|
||||
@@ -29,6 +28,7 @@ import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent,
|
||||
openVault,
|
||||
presentSheet,
|
||||
ToastEvent
|
||||
} from '../../services/EventManager';
|
||||
import Navigation from '../../services/Navigation';
|
||||
@@ -58,6 +58,7 @@ import {SIZE} from '../../utils/SizeUtils';
|
||||
import {sleep, timeConverter} from '../../utils/TimeUtils';
|
||||
import {Button} from '../Button';
|
||||
import {presentDialog} from '../Dialog/functions';
|
||||
import NoteHistory from '../NoteHistory';
|
||||
import {PressableButton} from '../PressableButton';
|
||||
import Heading from '../Typography/Heading';
|
||||
import Paragraph from '../Typography/Paragraph';
|
||||
@@ -657,6 +658,20 @@ export const ActionSheetComponent = ({
|
||||
positiveType: 'errorShade'
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'History',
|
||||
title: 'History',
|
||||
icon: 'history',
|
||||
func: async () => {
|
||||
close();
|
||||
await sleep(300);
|
||||
presentSheet({
|
||||
noProgress: true,
|
||||
noIcon: true,
|
||||
component: ref => <NoteHistory ref={ref} note={note} />
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -42,7 +42,10 @@ const GeneralSheet = ({context}) => {
|
||||
actionSheetRef.current?.setModalVisible(true);
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
const close = ctx => {
|
||||
if ((ctx && !context) || (ctx && ctx !== context)) {
|
||||
return;
|
||||
}
|
||||
actionSheetRef.current?.setModalVisible(false);
|
||||
};
|
||||
|
||||
|
||||
97
apps/mobile/src/components/NoteHistory/index.js
Normal file
97
apps/mobile/src/components/NoteHistory/index.js
Normal file
@@ -0,0 +1,97 @@
|
||||
import React, {useCallback, useEffect, useState} from 'react';
|
||||
import {View} from 'react-native';
|
||||
import {FlatList} from 'react-native-gesture-handler';
|
||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import {useTracked} from '../../provider';
|
||||
import {presentSheet} from '../../services/EventManager';
|
||||
import {db} from '../../utils/database';
|
||||
import {timeConverter} from '../../utils/TimeUtils';
|
||||
import DialogHeader from '../Dialog/dialog-header';
|
||||
import GeneralSheet from '../GeneralSheet';
|
||||
import {PressableButton} from '../PressableButton';
|
||||
import Seperator from '../Seperator';
|
||||
import Paragraph from '../Typography/Paragraph';
|
||||
import NotePreview from './preview';
|
||||
|
||||
export default function NoteHistory({note, ref}) {
|
||||
const [history, setHistory] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [state] = useTracked();
|
||||
const {colors} = state;
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
console.log(note.id);
|
||||
console.log(await db.noteHistory.get(note.id));
|
||||
|
||||
setHistory([...(await db.noteHistory.get(note.id))].reverse());
|
||||
setLoading(false);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
async function preview(item) {
|
||||
let content = await db.noteHistory.content(item.sessionContentId);
|
||||
|
||||
presentSheet({
|
||||
component: <NotePreview session={item} content={content} />,
|
||||
context: 'note_history',
|
||||
noProgress: true,
|
||||
noIcon: true
|
||||
});
|
||||
}
|
||||
|
||||
const renderItem = useCallback(
|
||||
({item, index}) => (
|
||||
<PressableButton
|
||||
type="grayBg"
|
||||
onPress={() => preview(item)}
|
||||
customStyle={{
|
||||
justifyContent: 'center',
|
||||
alignItems: 'flex-start',
|
||||
paddingHorizontal: 12,
|
||||
height: 45,
|
||||
marginBottom: 10
|
||||
}}>
|
||||
<Paragraph>{timeConverter(item.dateEdited)}</Paragraph>
|
||||
</PressableButton>
|
||||
),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<View>
|
||||
<GeneralSheet context="note_history" />
|
||||
<DialogHeader
|
||||
title="Note history"
|
||||
paragraph="Revert back to an older version of this note"
|
||||
padding={12}
|
||||
/>
|
||||
|
||||
<Seperator />
|
||||
|
||||
<FlatList
|
||||
onMomentumScrollEnd={() => {
|
||||
ref?.current?.handleChildScrollEnd();
|
||||
}}
|
||||
style={{
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
keyExtractor={item => item.id}
|
||||
data={history}
|
||||
ListEmptyComponent={
|
||||
<View
|
||||
style={{
|
||||
width: '100%',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: 200
|
||||
}}>
|
||||
<Icon name="history" size={60} color={colors.icon} />
|
||||
<Paragraph color={colors.icon}>No note history found.</Paragraph>
|
||||
</View>
|
||||
}
|
||||
renderItem={renderItem}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
119
apps/mobile/src/components/NoteHistory/preview.js
Normal file
119
apps/mobile/src/components/NoteHistory/preview.js
Normal file
@@ -0,0 +1,119 @@
|
||||
import React, {useRef} from 'react';
|
||||
import {View} from 'react-native';
|
||||
import WebView from 'react-native-webview';
|
||||
import {useTracked} from '../../provider';
|
||||
import {eSendEvent, ToastEvent} from '../../services/EventManager';
|
||||
import Navigation from '../../services/Navigation';
|
||||
import {dHeight} from '../../utils';
|
||||
import {db} from '../../utils/database';
|
||||
import {eCloseProgressDialog} from '../../utils/Events';
|
||||
import {normalize} from '../../utils/SizeUtils';
|
||||
import {timeConverter} from '../../utils/TimeUtils';
|
||||
import {sourceUri} from '../../views/Editor/Functions';
|
||||
import {Button} from '../Button';
|
||||
import DialogHeader from '../Dialog/dialog-header';
|
||||
|
||||
export default function NotePreview({session, content}) {
|
||||
const [state] = useTracked();
|
||||
const {colors} = state;
|
||||
const webviewRef = useRef();
|
||||
|
||||
const onLoad = async () => {
|
||||
console.log(content);
|
||||
let preview = await db.content.insertPlaceholders(
|
||||
content,
|
||||
'placeholder.svg'
|
||||
);
|
||||
console.log(preview, 'preview');
|
||||
postMessage('htmldiff', preview?.data);
|
||||
let theme = {...colors};
|
||||
theme.factor = normalize(1);
|
||||
postMessage('theme', JSON.stringify(theme));
|
||||
};
|
||||
|
||||
function postMessage(type, value = null) {
|
||||
let message = {
|
||||
type: type,
|
||||
value
|
||||
};
|
||||
webviewRef.current?.postMessage(JSON.stringify(message));
|
||||
}
|
||||
|
||||
const _onShouldStartLoadWithRequest = request => {
|
||||
if (request.url.includes('http')) {
|
||||
openLinkInBrowser(request.url, colors)
|
||||
.catch(e =>
|
||||
ToastEvent.show({
|
||||
title: 'Failed to open link',
|
||||
message: e.message,
|
||||
type: 'success',
|
||||
context: 'local'
|
||||
})
|
||||
)
|
||||
.then(r => {
|
||||
console.log('closed');
|
||||
});
|
||||
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
async function restore() {
|
||||
await db.noteHistory.restore(session.id);
|
||||
eSendEvent(eCloseProgressDialog, 'note_history');
|
||||
eSendEvent(eCloseProgressDialog);
|
||||
Navigation.setRoutesToUpdate([
|
||||
Navigation.routeNames.NotesPage,
|
||||
Navigation.routeNames.Favorites,
|
||||
Navigation.routeNames.Notes
|
||||
]);
|
||||
ToastEvent.show({
|
||||
heading: 'Note restored successfully',
|
||||
type: 'success'
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
height: 600,
|
||||
width: '100%'
|
||||
}}>
|
||||
<DialogHeader padding={12} title={timeConverter(session.dateEdited)} />
|
||||
<WebView
|
||||
ref={webviewRef}
|
||||
onShouldStartLoadWithRequest={_onShouldStartLoadWithRequest}
|
||||
onLoad={onLoad}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: 'transparent'
|
||||
}}
|
||||
cacheMode="LOAD_DEFAULT"
|
||||
domStorageEnabled={true}
|
||||
scrollEnabled={true}
|
||||
bounces={false}
|
||||
allowFileAccess={true}
|
||||
scalesPageToFit={true}
|
||||
allowingReadAccessToURL={Platform.OS === 'android' ? true : null}
|
||||
allowFileAccessFromFileURLs={true}
|
||||
allowUniversalAccessFromFileURLs={true}
|
||||
originWhitelist={['*']}
|
||||
javaScriptEnabled={true}
|
||||
cacheEnabled={true}
|
||||
source={{
|
||||
uri: sourceUri + 'plaineditor.html'
|
||||
}}
|
||||
/>
|
||||
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 12
|
||||
}}>
|
||||
<Button onPress={restore} title="Restore" type="accent" width="100%" />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -67,6 +67,7 @@ const showActionSheet = (item, isTrash) => {
|
||||
'Vault',
|
||||
'Delete',
|
||||
'RemoveTopic',
|
||||
"History",
|
||||
...android
|
||||
]
|
||||
);
|
||||
|
||||
@@ -39,7 +39,6 @@ export const SectionHeader = ({item, index, type, color, screen}) => {
|
||||
: groupBy.slice(0, 1).toUpperCase() + groupBy.slice(1, groupBy.length);
|
||||
|
||||
const onUpdate = () => {
|
||||
console.log(groupOptions);
|
||||
setGroupOptions({...db.settings?.getGroupOptions(type)});
|
||||
};
|
||||
|
||||
|
||||
@@ -381,7 +381,6 @@ export const useMessageStore = create<MessageStore>((set, get) => ({
|
||||
set({announcements: []});
|
||||
} finally {
|
||||
let all = await getFiltered(announcements);
|
||||
console.log("all", all)
|
||||
set({
|
||||
announcements: all.filter(a => a.type === 'inline'),
|
||||
dialogs: all.filter(a => a.type === 'dialog')
|
||||
|
||||
@@ -64,14 +64,11 @@ function routeNeedsUpdate(routeName, callback) {
|
||||
* @param {array} routes
|
||||
*/
|
||||
function setRoutesToUpdate(routes) {
|
||||
console.log(currentScreen, 'current');
|
||||
if (routes.indexOf(currentScreen) > -1) {
|
||||
console.log('updating screen', currentScreen);
|
||||
if (
|
||||
currentScreen === routeNames.NotesPage ||
|
||||
currentScreen === routeNames.Notebook
|
||||
) {
|
||||
console.log(currentScreen, 'CURRENT');
|
||||
eSendEvent(
|
||||
currentScreen === routeNames.NotesPage
|
||||
? refreshNotesPage
|
||||
|
||||
@@ -61,6 +61,7 @@ let waitForContent = false;
|
||||
let prevNoteContent = null;
|
||||
let timerForEditor = null;
|
||||
let sessionId = null;
|
||||
let historySessionId = null;
|
||||
|
||||
export function startClosingSession() {
|
||||
closingSession = true;
|
||||
@@ -228,6 +229,7 @@ function clearNote() {
|
||||
note = null;
|
||||
title = '';
|
||||
noteEdited = false;
|
||||
historySessionId = null;
|
||||
prevNoteContent = content.data;
|
||||
isSaving = false;
|
||||
id = null;
|
||||
@@ -817,10 +819,36 @@ export async function saveNote(preventUpdate) {
|
||||
);
|
||||
tiny.call(EditorWebView, tiny.updateSavingState(!n ? '' : 'Saved'));
|
||||
}
|
||||
|
||||
await updateSessionHistory(id, noteData.content);
|
||||
} catch (e) {}
|
||||
isSaving = false;
|
||||
}
|
||||
|
||||
async function updateSessionHistory(id, content) {
|
||||
let note = db.notes.note(id)?.data;
|
||||
if (!note) return;
|
||||
if (!historySessionId) {
|
||||
historySessionId = `${id}_${note.dateEdited}`;
|
||||
}
|
||||
|
||||
if (!historySessionId.includes(id)) {
|
||||
historySessionId = null;
|
||||
return;
|
||||
}
|
||||
if (!note.locked) {
|
||||
console.log('saving session with id: ',historySessionId);
|
||||
await db.noteHistory.add(id, historySessionId, content);
|
||||
} else {
|
||||
let content = await db.content.get(note.contentId);
|
||||
|
||||
await db.noteHistory.add(id, historySessionId, {
|
||||
data: content.data,
|
||||
type: content.type
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function onWebViewLoad(premium, colors) {
|
||||
setTimeout(() => {
|
||||
if (premium) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {Platform, TouchableOpacity, View} from 'react-native';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Platform, TouchableOpacity, View } from 'react-native';
|
||||
import ToggleSwitch from 'toggle-switch-react-native';
|
||||
import Paragraph from '../../components/Typography/Paragraph';
|
||||
import {useTracked} from '../../provider';
|
||||
import {useSettingStore, useUserStore} from '../../provider/stores';
|
||||
import { useTracked } from '../../provider';
|
||||
import { useSettingStore, useUserStore } from '../../provider/stores';
|
||||
import Backup from '../../services/Backup';
|
||||
import {
|
||||
eSendEvent,
|
||||
@@ -17,14 +17,13 @@ import {
|
||||
eOpenLoginDialog,
|
||||
eOpenRestoreDialog
|
||||
} from '../../utils/Events';
|
||||
import {openLinkInBrowser} from '../../utils/functions';
|
||||
import {MMKV} from '../../utils/mmkv';
|
||||
import {SIZE} from '../../utils/SizeUtils';
|
||||
import {sleep} from '../../utils/TimeUtils';
|
||||
import {CustomButton} from './button';
|
||||
import {verifyUser} from './functions';
|
||||
import { openLinkInBrowser } from '../../utils/functions';
|
||||
import { MMKV } from '../../utils/mmkv';
|
||||
import { SIZE } from '../../utils/SizeUtils';
|
||||
import { sleep } from '../../utils/TimeUtils';
|
||||
import { CustomButton } from './button';
|
||||
import { verifyUser } from './functions';
|
||||
import SectionHeader from './section-header';
|
||||
import * as ScopedStorage from 'react-native-scoped-storage';
|
||||
|
||||
const SettingsBackupAndRestore = ({isSheet}) => {
|
||||
const [state] = useTracked();
|
||||
|
||||
Reference in New Issue
Block a user