diff --git a/CliClient/tests/synchronizer.js b/CliClient/tests/synchronizer.js index 6734f5c01..c25bfff0a 100644 --- a/CliClient/tests/synchronizer.js +++ b/CliClient/tests/synchronizer.js @@ -322,6 +322,53 @@ describe('Synchronizer', function() { done(); }); + it('should resolve conflict if remote folder has been deleted, but note has been added to folder locally', async (done) => { + let folder1 = await Folder.save({ title: "folder1" }); + await synchronizer().start(); + + await switchClient(2); + + await synchronizer().start(); + await Folder.delete(folder1.id); + await synchronizer().start(); + + await switchClient(1); + + let note = await Note.save({ title: "note1", parent_id: folder1.id }); + await synchronizer().start(); + let items = await allItems(); + expect(items.length).toBe(1); + expect(items[0].title).toBe('note1'); + expect(items[0].is_conflict).toBe(1); + + done(); + }); + + it('should resolve conflict if note has been deleted remotely and locally', async (done) => { + let folder = await Folder.save({ title: "folder" }); + let note = await Note.save({ title: "note", parent_id: folder.title }); + await synchronizer().start(); + + await switchClient(2); + + await synchronizer().start(); + await Note.delete(note.id); + await synchronizer().start(); + + await switchClient(1); + + await Note.delete(note.id); + await synchronizer().start(); + + let items = await allItems(); + expect(items.length).toBe(1); + expect(items[0].title).toBe('folder'); + + localItemsSameAsRemote(items, expect); + + done(); + }); + it('should cross delete all folders', async (done) => { // If client1 and 2 have two folders, client 1 deletes item 1 and client // 2 deletes item 2, they should both end up with no items after sync. diff --git a/ReactNativeClient/lib/components/screens/log.js b/ReactNativeClient/lib/components/screens/log.js index 31fcb7d9d..561c214b6 100644 --- a/ReactNativeClient/lib/components/screens/log.js +++ b/ReactNativeClient/lib/components/screens/log.js @@ -47,7 +47,7 @@ class LogScreenComponent extends React.Component { }; return ( - {time.unixMsToIsoSec(item.timestamp) + ': ' + item.message} + {time.formatMsToLocal(item.timestamp, 'MM-DDTHH:mm') + ': ' + item.message} ); } diff --git a/ReactNativeClient/lib/components/screens/note.js b/ReactNativeClient/lib/components/screens/note.js index 7126c3649..8e223c22c 100644 --- a/ReactNativeClient/lib/components/screens/note.js +++ b/ReactNativeClient/lib/components/screens/note.js @@ -1,5 +1,5 @@ import React, { Component } from 'react'; -import { View, Button, TextInput, WebView } from 'react-native'; +import { View, Button, TextInput, WebView, Text } from 'react-native'; import { connect } from 'react-redux' import { Log } from 'lib/log.js' import { Note } from 'lib/models/note.js' @@ -19,6 +19,8 @@ class NoteScreenComponent extends React.Component { this.state = { note: Note.new(), mode: 'view', + noteMetadata: '', + showNoteMetadata: false, } } @@ -26,9 +28,11 @@ class NoteScreenComponent extends React.Component { if (!this.props.noteId) { let note = this.props.itemType == 'todo' ? Note.newTodo(this.props.folderId) : Note.new(this.props.folderId); this.setState({ note: note }); + this.refreshNoteMetadata(); } else { Note.load(this.props.noteId).then((note) => { this.setState({ note: note }); + this.refreshNoteMetadata(); }); } } @@ -41,6 +45,13 @@ class NoteScreenComponent extends React.Component { }); } + async refreshNoteMetadata(force = null) { + if (force !== true && !this.state.showNoteMetadata) return; + + let noteMetadata = await Note.serializeAllProps(this.state.note); + this.setState({ noteMetadata: noteMetadata }); + } + title_changeText(text) { this.noteComponent_change('title', text); } @@ -54,6 +65,7 @@ class NoteScreenComponent extends React.Component { let note = await Note.save(this.state.note); this.setState({ note: note }); if (isNew) Note.updateGeolocation(note.id); + this.refreshNoteMetadata(); } deleteNote_onPress(noteId) { @@ -61,12 +73,19 @@ class NoteScreenComponent extends React.Component { } attachFile_onPress(noteId) { + + } + + showMetadata_onPress() { + this.setState({ showNoteMetadata: !this.state.showNoteMetadata }); + this.refreshNoteMetadata(true); } menuOptions() { return [ { title: _('Attach file'), onPress: () => { this.attachFile_onPress(this.state.note.id); } }, { title: _('Delete note'), onPress: () => { this.deleteNote_onPress(this.state.note.id); } }, + { title: _('Toggle metadata'), onPress: () => { this.showMetadata_onPress(); } }, ]; } @@ -99,15 +118,18 @@ class NoteScreenComponent extends React.Component { bodyComponent = this.body_changeText(text)} /> } + console.info(this.state.noteMetadata); + return ( { isTodo && } this.title_changeText(text)} /> - { bodyComponent } + { bodyComponent } { todoComponents }