diff --git a/CliClient/locales/en_GB.po b/CliClient/locales/en_GB.po index d78e3496f6..845c38dc20 100644 --- a/CliClient/locales/en_GB.po +++ b/CliClient/locales/en_GB.po @@ -321,6 +321,14 @@ msgid "" "will be shared with any third party." msgstr "" +#, javascript-format +msgid "Unknown log level: %s" +msgstr "" + +#, javascript-format +msgid "Unknown level ID: %s" +msgstr "" + msgid "" "Cannot refresh token: authentication data is missing. Starting the " "synchronisation again may fix the problem." diff --git a/CliClient/locales/fr_FR.po b/CliClient/locales/fr_FR.po index c08c09e564..687a5e65bb 100644 --- a/CliClient/locales/fr_FR.po +++ b/CliClient/locales/fr_FR.po @@ -364,6 +364,14 @@ msgid "" "will be shared with any third party." msgstr "" +#, fuzzy, javascript-format +msgid "Unknown log level: %s" +msgstr "Paramètre inconnu : %s" + +#, fuzzy, javascript-format +msgid "Unknown level ID: %s" +msgstr "Paramètre inconnu : %s" + msgid "" "Cannot refresh token: authentication data is missing. Starting the " "synchronisation again may fix the problem." diff --git a/CliClient/locales/joplin.pot b/CliClient/locales/joplin.pot index d78e3496f6..845c38dc20 100644 --- a/CliClient/locales/joplin.pot +++ b/CliClient/locales/joplin.pot @@ -321,6 +321,14 @@ msgid "" "will be shared with any third party." msgstr "" +#, javascript-format +msgid "Unknown log level: %s" +msgstr "" + +#, javascript-format +msgid "Unknown level ID: %s" +msgstr "" + msgid "" "Cannot refresh token: authentication data is missing. Starting the " "synchronisation again may fix the problem." diff --git a/CliClient/package.json b/CliClient/package.json index 988268d2fd..6f80dd2130 100644 --- a/CliClient/package.json +++ b/CliClient/package.json @@ -7,7 +7,7 @@ "url": "https://github.com/laurent22/joplin" }, "url": "git://github.com/laurent22/joplin.git", - "version": "0.9.7", + "version": "0.9.8", "bin": { "joplin": "./main.js" }, diff --git a/CliClient/package.json.md5 b/CliClient/package.json.md5 index 627f3cb62a..d10505a735 100644 --- a/CliClient/package.json.md5 +++ b/CliClient/package.json.md5 @@ -1 +1 @@ -bd9c058875b3fa6fb7091aa9f3ee0e74 \ No newline at end of file +28897a284082f5be431a27ee5b1a26cb \ No newline at end of file diff --git a/ReactNativeClient/lib/base-model.js b/ReactNativeClient/lib/base-model.js index aa61c78c90..2eed07c77a 100644 --- a/ReactNativeClient/lib/base-model.js +++ b/ReactNativeClient/lib/base-model.js @@ -217,8 +217,14 @@ class BaseModel { let query = {}; let modelId = o.id; + const timeNow = time.unixMs(); + if (options.autoTimestamp && this.hasField('updated_time')) { - o.updated_time = time.unixMs(); + o.updated_time = timeNow; + } + + if (options.autoTimestamp && this.hasField('user_updated_time')) { + o.user_updated_time = timeNow; } if (options.isNew) { @@ -228,7 +234,11 @@ class BaseModel { } if (!o.created_time && this.hasField('created_time')) { - o.created_time = time.unixMs(); + o.created_time = timeNow; + } + + if (!o.user_created_time && this.hasField('user_created_time')) { + o.user_created_time = timeNow; } query = Database.insertQuery(this.tableName(), o); @@ -266,6 +276,8 @@ class BaseModel { o.id = modelId; if ('updated_time' in saveQuery.modObject) o.updated_time = saveQuery.modObject.updated_time; if ('created_time' in saveQuery.modObject) o.created_time = saveQuery.modObject.created_time; + if ('user_updated_time' in saveQuery.modObject) o.user_updated_time = saveQuery.modObject.user_updated_time; + if ('user_created_time' in saveQuery.modObject) o.user_created_time = saveQuery.modObject.user_created_time; o = this.addModelMd(o); return this.filter(o); }).catch((error) => { diff --git a/ReactNativeClient/lib/components/note-list.js b/ReactNativeClient/lib/components/note-list.js index cfd02147fa..5f34256b70 100644 --- a/ReactNativeClient/lib/components/note-list.js +++ b/ReactNativeClient/lib/components/note-list.js @@ -61,7 +61,7 @@ class NoteListComponent extends Component { for (let i = 0; i < notes.length; i++) { const note = notes[i]; if (note.is_todo) { - if (todoFilter == 'recent' && note.updated_time < notRecentTime && !!note.todo_completed) continue; + if (todoFilter == 'recent' && note.user_updated_time < notRecentTime && !!note.todo_completed) continue; if (todoFilter == 'nonCompleted' && !!note.todo_completed) continue; } output.push(note); diff --git a/ReactNativeClient/lib/joplin-database.js b/ReactNativeClient/lib/joplin-database.js index f670f5d602..de8d6689a0 100644 --- a/ReactNativeClient/lib/joplin-database.js +++ b/ReactNativeClient/lib/joplin-database.js @@ -193,7 +193,7 @@ class JoplinDatabase extends Database { // 1. Add the new version number to the existingDatabaseVersions array // 2. Add the upgrade logic to the "switch (targetVersion)" statement below - const existingDatabaseVersions = [0, 1, 2, 3, 4]; + const existingDatabaseVersions = [0, 1, 2, 3, 4, 5]; let currentVersionIndex = existingDatabaseVersions.indexOf(fromVersion); if (currentVersionIndex == existingDatabaseVersions.length - 1) return false; @@ -233,6 +233,18 @@ class JoplinDatabase extends Database { queries.push('DELETE FROM settings WHERE `key` = "sync.context"'); } + if (targetVersion == 5) { + const tableNames = ['notes', 'folders', 'tags', 'note_tags', 'resources']; + for (let i = 0; i < tableNames.length; i++) { + const n = tableNames[i]; + queries.push('ALTER TABLE ' + n + ' ADD COLUMN user_created_time INT NOT NULL DEFAULT 0'); + queries.push('ALTER TABLE ' + n + ' ADD COLUMN user_updated_time INT NOT NULL DEFAULT 0'); + queries.push('UPDATE ' + n + ' SET user_created_time = created_time'); + queries.push('UPDATE ' + n + ' SET user_updated_time = updated_time'); + queries.push('CREATE INDEX ' + n + '_user_updated_time ON ' + n + ' (user_updated_time)'); + } + } + queries.push({ sql: 'UPDATE version SET version = ?', params: [targetVersion] }); await this.transactionExecBatch(queries); diff --git a/ReactNativeClient/lib/logger.js b/ReactNativeClient/lib/logger.js index d0d39dc03a..9a00c0a4f4 100644 --- a/ReactNativeClient/lib/logger.js +++ b/ReactNativeClient/lib/logger.js @@ -144,7 +144,29 @@ class Logger { if (s == 'warn') return Logger.LEVEL_WARN; if (s == 'info') return Logger.LEVEL_INFO; if (s == 'debug') return Logger.LEVEL_DEBUG; - throw new Error('Unknown log level: %s', s); + throw new Error(_('Unknown log level: %s', s)); + } + + static levelIdToString(id) { + if (id == Logger.LEVEL_NONE) return 'none'; + if (id == Logger.LEVEL_ERROR) return 'error'; + if (id == Logger.LEVEL_WARN) return 'warn'; + if (id == Logger.LEVEL_INFO) return 'info'; + if (id == Logger.LEVEL_DEBUG) return 'debug'; + throw new Error(_('Unknown level ID: %s', id)); + } + + static levelIds() { + return [Logger.LEVEL_NONE, Logger.LEVEL_ERROR, Logger.LEVEL_WARN, Logger.LEVEL_INFO, Logger.LEVEL_DEBUG]; + } + + static levelEnum() { + let output = {}; + const ids = this.levelIds(); + for (let i = 0; i < ids.length; i++) { + output[ids[i]] = this.levelIdToString(ids[i]); + } + return output; } } diff --git a/ReactNativeClient/lib/models/base-item.js b/ReactNativeClient/lib/models/base-item.js index bef4e32c8c..2b897fe909 100644 --- a/ReactNativeClient/lib/models/base-item.js +++ b/ReactNativeClient/lib/models/base-item.js @@ -180,7 +180,7 @@ class BaseItem extends BaseModel { } static serialize_format(propName, propValue) { - if (['created_time', 'updated_time', 'sync_time'].indexOf(propName) >= 0) { + if (['created_time', 'updated_time', 'sync_time', 'user_updated_time', 'user_created_time'].indexOf(propName) >= 0) { if (!propValue) return ''; propValue = moment.unix(propValue / 1000).utc().format('YYYY-MM-DDTHH:mm:ss.SSS') + 'Z'; } else if (propValue === null || propValue === undefined) { @@ -195,7 +195,7 @@ class BaseItem extends BaseModel { let ItemClass = this.itemClass(type); - if (['created_time', 'updated_time'].indexOf(propName) >= 0) { + if (['created_time', 'updated_time', 'user_created_time', 'user_updated_time'].indexOf(propName) >= 0) { if (!propValue) return 0; propValue = moment(propValue, 'YYYY-MM-DDTHH:mm:ss.SSSZ').format('x'); } else { diff --git a/ReactNativeClient/lib/models/folder.js b/ReactNativeClient/lib/models/folder.js index be9dfafe9a..8d895a326e 100644 --- a/ReactNativeClient/lib/models/folder.js +++ b/ReactNativeClient/lib/models/folder.js @@ -91,6 +91,7 @@ class Folder extends BaseItem { id: this.conflictFolderId(), title: this.conflictFolderTitle(), updated_time: time.unixMs(), + user_updated_time: time.unixMs(), }; } diff --git a/ReactNativeClient/lib/models/note.js b/ReactNativeClient/lib/models/note.js index 3bb7ff6d93..aeeab847bd 100644 --- a/ReactNativeClient/lib/models/note.js +++ b/ReactNativeClient/lib/models/note.js @@ -95,7 +95,7 @@ class Note extends BaseItem { } static previewFields() { - return ['id', 'title', 'body', 'is_todo', 'todo_completed', 'parent_id', 'updated_time']; + return ['id', 'title', 'body', 'is_todo', 'todo_completed', 'parent_id', 'updated_time', 'user_updated_time']; } static previewFieldsSql() { @@ -122,7 +122,7 @@ class Note extends BaseItem { // is used to sort already loaded notes. if (!options) options = {}; - if (!options.order) options.order = [{ by: 'updated_time', dir: 'DESC' }]; + if (!options.order) options.order = [{ by: 'user_updated_time', dir: 'DESC' }]; if (!options.conditions) options.conditions = []; if (!options.conditionsParams) options.conditionsParams = []; if (!options.fields) options.fields = this.previewFields(); @@ -269,11 +269,17 @@ class Note extends BaseItem { static async moveToFolder(noteId, folderId) { if (folderId == Folder.conflictFolderId()) throw new Error(_('Cannot move note to "%s" notebook', Folder.conflictFolderIdTitle())); - return Note.save({ + // When moving a note to a different folder, the user timestamp is not updated. + // However updated_time is updated so that the note can be synced later on. + + const modifiedNote = { id: noteId, parent_id: folderId, is_conflict: 0, - }); + updated_time: time.unixMs(), + }; + + return Note.save(modifiedNote, { autoTimestamp: false }); } static toggleIsTodo(note) { diff --git a/ReactNativeClient/lib/models/setting.js b/ReactNativeClient/lib/models/setting.js index c2c1fb1860..58708c5c2d 100644 --- a/ReactNativeClient/lib/models/setting.js +++ b/ReactNativeClient/lib/models/setting.js @@ -1,5 +1,6 @@ import { BaseModel } from 'lib/base-model.js'; import { Database } from 'lib/database.js'; +import { Logger } from 'lib/logger.js'; import { _, supportedLocalesToLanguages, defaultLocale } from 'lib/locale.js'; class Setting extends BaseModel { @@ -318,6 +319,9 @@ Setting.metadata_ = { 'locale': { value: defaultLocale(), type: Setting.TYPE_STRING, isEnum: true, public: true, label: () => _('Language'), options: () => { return supportedLocalesToLanguages(); }}, + // 'logLevel': { value: Logger.LEVEL_INFO, type: Setting.TYPE_STRING, isEnum: true, public: true, label: () => _('Log level'), options: () => { + // return Logger.levelEnum(); + // }}, // Not used for now: 'todoFilter': { value: 'all', type: Setting.TYPE_STRING, isEnum: true, public: false, appTypes: ['mobile'], label: () => _('Todo filter'), options: () => ({ all: _('Show all'), diff --git a/ReactNativeClient/root.js b/ReactNativeClient/root.js index 65bdef5749..5f71543942 100644 --- a/ReactNativeClient/root.js +++ b/ReactNativeClient/root.js @@ -49,7 +49,7 @@ let defaultState = { screens: {}, historyCanGoBack: false, notesOrder: [ - { by: 'updated_time', dir: 'DESC' }, + { by: 'user_updated_time', dir: 'DESC' }, ], syncStarted: false, syncReport: {},