mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Change folder from RN
This commit is contained in:
parent
24f61177d1
commit
c2ba2105ff
@ -260,8 +260,8 @@ class Application {
|
||||
} else if (syncTarget == 'memory') {
|
||||
fileApi = new FileApi('joplin', new FileApiDriverMemory());
|
||||
fileApi.setLogger(this.logger_);
|
||||
} else if (syncTarget == 'file') {
|
||||
let syncDir = Setting.value('sync.local.path');
|
||||
} else if (syncTarget == 'filesystem') {
|
||||
let syncDir = Setting.value('sync.filesystem.path');
|
||||
if (!syncDir) syncDir = Setting.value('profileDir') + '/sync';
|
||||
this.vorpal().log(_('Synchronizing with directory "%s"', syncDir));
|
||||
await fs.mkdirp(syncDir, 0o755);
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { BaseCommand } from './base-command.js';
|
||||
import { Database } from 'lib/database.js';
|
||||
import { Setting } from 'lib/models/setting.js';
|
||||
import { _ } from 'lib/locale.js';
|
||||
import { ReportService } from 'lib/services/report.js';
|
||||
|
||||
@ -14,7 +16,7 @@ class Command extends BaseCommand {
|
||||
|
||||
async action(args) {
|
||||
let service = new ReportService();
|
||||
let report = await service.status();
|
||||
let report = await service.status(Database.enumId('syncTarget', Setting.value('sync.target')));
|
||||
|
||||
for (let i = 0; i < report.length; i++) {
|
||||
let section = report[i];
|
||||
|
@ -42,7 +42,7 @@ async function createClients() {
|
||||
for (let clientId = 0; clientId < 2; clientId++) {
|
||||
let client = createClient(clientId);
|
||||
promises.push(fs.remove(client.profileDir));
|
||||
promises.push(execCommand(client, 'config sync.target local').then(() => { return execCommand(client, 'config sync.local.path ' + syncDir); }));
|
||||
promises.push(execCommand(client, 'config sync.target filesystem').then(() => { return execCommand(client, 'config sync.filesystem.path ' + syncDir); }));
|
||||
output.push(client);
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
"url": "https://github.com/laurent22/joplin"
|
||||
},
|
||||
"url": "git://github.com/laurent22/joplin.git",
|
||||
"version": "0.8.43",
|
||||
"version": "0.8.44",
|
||||
"bin": {
|
||||
"joplin": "./main_launcher.js"
|
||||
},
|
||||
|
@ -75,8 +75,6 @@ describe('Synchronizer', function() {
|
||||
let note = await Note.save({ title: "un", parent_id: folder.id });
|
||||
await synchronizer().start();
|
||||
|
||||
await sleep(0.1);
|
||||
|
||||
await Note.save({ title: "un UPDATE", id: note.id });
|
||||
|
||||
let all = await allItems();
|
||||
|
@ -90,8 +90,8 @@ android {
|
||||
applicationId "net.cozic.joplin"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 22
|
||||
versionCode 16
|
||||
versionName "0.9.3"
|
||||
versionCode 18
|
||||
versionName "0.9.5"
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", "x86"
|
||||
}
|
||||
|
@ -218,6 +218,7 @@ class BaseModel {
|
||||
}
|
||||
|
||||
query.id = modelId;
|
||||
query.modObject = o;
|
||||
|
||||
return query;
|
||||
}
|
||||
@ -241,6 +242,8 @@ class BaseModel {
|
||||
return this.db().transactionExecBatch(queries).then(() => {
|
||||
o = Object.assign({}, o);
|
||||
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;
|
||||
o = this.addModelMd(o);
|
||||
return this.filter(o);
|
||||
}).catch((error) => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux'
|
||||
import { View, Text, Button, StyleSheet, TouchableOpacity } from 'react-native';
|
||||
import { View, Text, Button, StyleSheet, TouchableOpacity, Picker } from 'react-native';
|
||||
import { Log } from 'lib/log.js';
|
||||
import { Menu, MenuOptions, MenuOption, MenuTrigger } from 'react-native-popup-menu';
|
||||
import { _ } from 'lib/locale.js';
|
||||
@ -150,14 +150,33 @@ class ScreenHeaderComponent extends Component {
|
||||
<Text>{_('Status')}</Text>
|
||||
</MenuOption>);
|
||||
|
||||
let title = 'title' in this.props && this.props.title !== null ? this.props.title : _(this.props.navState.routeName);
|
||||
const createTitleComponent = () => {
|
||||
const p = this.props.titlePicker;
|
||||
if (p) {
|
||||
let items = [];
|
||||
for (let i = 0; i < p.items.length; i++) {
|
||||
let item = p.items[i];
|
||||
items.push(<Picker.Item label={item.label} value={item.value} key={item.value} />);
|
||||
}
|
||||
return (
|
||||
<Picker style={{height: 30, flex:1}} selectedValue={p.selectedValue} onValueChange={(itemValue, itemIndex) => { if (p.onValueChange) p.onValueChange(itemValue, itemIndex); }}>
|
||||
{ items }
|
||||
</Picker>
|
||||
);
|
||||
} else {
|
||||
let title = 'title' in this.props && this.props.title !== null ? this.props.title : _(this.props.navState.routeName);
|
||||
return <Text style={{ flex:1, marginLeft: 10 }}>{title}</Text>
|
||||
}
|
||||
}
|
||||
|
||||
const titleComp = createTitleComponent();
|
||||
|
||||
return (
|
||||
<View style={{ flexDirection: 'row', paddingLeft: 10, paddingTop: 10, paddingBottom: 10, paddingRight: 0, backgroundColor: '#ffffff', alignItems: 'center' }} >
|
||||
{ sideMenuButton(styles, () => this.sideMenuButton_press()) }
|
||||
{ backButton(styles, () => this.backButton_press(), !this.props.historyCanGoBack) }
|
||||
{ saveButton(styles, () => { if (this.props.onSaveButtonPress) this.props.onSaveButtonPress() }, this.props.saveButtonDisabled === true, this.props.showSaveButton === true) }
|
||||
<Text style={{ flex:1, marginLeft: 10 }} >{title}</Text>
|
||||
{ titleComp }
|
||||
<Menu onSelect={(value) => this.menu_select(value)}>
|
||||
<MenuTrigger>
|
||||
<Text style={{ fontSize: 25 }}> ⋮ </Text>
|
||||
|
@ -103,8 +103,12 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
return Folder.load(folderId);
|
||||
}
|
||||
|
||||
async refreshFolder() {
|
||||
this.setState({ folder: await this.currentFolder() });
|
||||
async refreshFolder(folderId = null) {
|
||||
if (!folderId) {
|
||||
this.setState({ folder: await this.currentFolder() });
|
||||
} else {
|
||||
this.setState({ folder: await Folder.load(folderId) });
|
||||
}
|
||||
}
|
||||
|
||||
noteComponent_change(propName, propValue) {
|
||||
@ -183,23 +187,27 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
];
|
||||
}
|
||||
|
||||
async todoCheckbox_change(checked) {
|
||||
async saveOneProperty(name, value) {
|
||||
let note = Object.assign({}, this.state.note);
|
||||
|
||||
const todoCompleted = checked ? time.unixMs() : 0;
|
||||
|
||||
if (note.id) {
|
||||
note = await Note.save({ id: note.id, todo_completed: todoCompleted });
|
||||
let toSave = { id: note.id };
|
||||
toSave[name] = value;
|
||||
toSave = await Note.save(toSave);
|
||||
note[name] = toSave[name];
|
||||
|
||||
this.setState({
|
||||
lastSavedNote: Object.assign({}, note),
|
||||
note: note,
|
||||
});
|
||||
} else {
|
||||
note.todo_completed = todoCompleted;
|
||||
note[name] = value;
|
||||
this.setState({ note: note });
|
||||
}
|
||||
}
|
||||
|
||||
async todoCheckbox_change(checked) {
|
||||
return this.saveOneProperty('todo_completed', checked ? time.unixMs() : 0);
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -271,9 +279,6 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
);
|
||||
}
|
||||
|
||||
let headerTitle = ''
|
||||
if (folder) headerTitle = folder.title;
|
||||
|
||||
const renderActionButton = () => {
|
||||
let buttons = [];
|
||||
|
||||
@ -290,6 +295,15 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
return <ActionButton multiStates={true} buttons={buttons} buttonIndex={0} />
|
||||
}
|
||||
|
||||
const titlePickerItems = () => {
|
||||
let output = [];
|
||||
for (let i = 0; i < this.props.folders.length; i++) {
|
||||
let f = this.props.folders[i];
|
||||
output.push({ label: f.title, value: f.id });
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
const actionButtonComp = renderActionButton();
|
||||
|
||||
let showSaveButton = this.state.mode == 'edit';
|
||||
@ -298,7 +312,23 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
return (
|
||||
<View style={this.styles().screen}>
|
||||
<ScreenHeader
|
||||
title={headerTitle}
|
||||
titlePicker={{
|
||||
items: titlePickerItems(),
|
||||
selectedValue: folder ? folder.id : null,
|
||||
onValueChange: async (itemValue, itemIndex) => {
|
||||
let note = Object.assign({}, this.state.note);
|
||||
if (note.id) await Note.moveToFolder(note.id, itemValue);
|
||||
note.parent_id = itemValue;
|
||||
|
||||
const folder = await Folder.load(note.parent_id);
|
||||
|
||||
this.setState({
|
||||
lastSavedNote: Object.assign({}, note),
|
||||
note: note,
|
||||
folder: folder,
|
||||
});
|
||||
}
|
||||
}}
|
||||
navState={this.props.navigation.state}
|
||||
menuOptions={this.menuOptions()}
|
||||
showSaveButton={showSaveButton}
|
||||
@ -324,6 +354,7 @@ const NoteScreen = connect(
|
||||
noteId: state.selectedNoteId,
|
||||
folderId: state.selectedFolderId,
|
||||
itemType: state.selectedItemType,
|
||||
folders: state.folders,
|
||||
};
|
||||
}
|
||||
)(NoteScreenComponent)
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import { ListView, View, Text, Button } from 'react-native';
|
||||
import { Setting } from 'lib/models/setting.js';
|
||||
import { connect } from 'react-redux'
|
||||
import { Log } from 'lib/log.js'
|
||||
import { reg } from 'lib/registry.js'
|
||||
@ -7,6 +8,7 @@ import { ScreenHeader } from 'lib/components/screen-header.js';
|
||||
import { time } from 'lib/time-utils'
|
||||
import { Logger } from 'lib/logger.js';
|
||||
import { BaseItem } from 'lib/models/base-item.js';
|
||||
import { Database } from 'lib/database.js';
|
||||
import { Folder } from 'lib/models/folder.js';
|
||||
import { ReportService } from 'lib/services/report.js';
|
||||
import { _ } from 'lib/locale.js';
|
||||
@ -31,7 +33,7 @@ class StatusScreenComponent extends BaseScreenComponent {
|
||||
|
||||
async resfreshScreen() {
|
||||
let service = new ReportService();
|
||||
let report = await service.status();
|
||||
let report = await service.status(Database.enumId('syncTarget', Setting.value('sync.target')));
|
||||
this.setState({ report: report });
|
||||
}
|
||||
|
||||
|
@ -151,7 +151,7 @@ class Database {
|
||||
}
|
||||
if (type == 'syncTarget') {
|
||||
if (s == 'memory') return 1;
|
||||
if (s == 'file') return 2;
|
||||
if (s == 'filesystem') return 2;
|
||||
if (s == 'onedrive') return 3;
|
||||
}
|
||||
throw new Error('Unknown enum type or value: ' + type + ', ' + s);
|
||||
@ -160,7 +160,7 @@ class Database {
|
||||
static enumName(type, id) {
|
||||
if (type == 'syncTarget') {
|
||||
if (id === 1) return 'memory';
|
||||
if (id === 2) return 'file';
|
||||
if (id === 2) return 'filesystem';
|
||||
if (id === 3) return 'onedrive';
|
||||
}
|
||||
throw new Error('Unknown enum type or id: ' + type + ', ' + id);
|
||||
|
@ -10,7 +10,7 @@ class FileApiDriverLocal {
|
||||
}
|
||||
|
||||
syncTargetName() {
|
||||
return 'file';
|
||||
return 'filesystem';
|
||||
}
|
||||
|
||||
fsErrorToJsError_(error) {
|
||||
|
@ -192,6 +192,17 @@ class JoplinDatabase extends Database {
|
||||
|
||||
for (let initLoopCount = 1; initLoopCount <= 2; initLoopCount++) {
|
||||
try {
|
||||
// await this.exec('DROP TABLE folders');
|
||||
// await this.exec('DROP TABLE notes');
|
||||
// await this.exec('DROP TABLE deleted_items');
|
||||
// await this.exec('DROP TABLE tags');
|
||||
// await this.exec('DROP TABLE note_tags');
|
||||
// await this.exec('DROP TABLE resources');
|
||||
// await this.exec('DROP TABLE settings');
|
||||
// await this.exec('DROP TABLE table_fields');
|
||||
// await this.exec('DROP TABLE version');
|
||||
// await this.exec('DROP TABLE sync_items');
|
||||
|
||||
let row = await this.selectOne('SELECT * FROM version LIMIT 1');
|
||||
this.logger().info('Current database version', row);
|
||||
|
||||
|
@ -32,14 +32,14 @@ class BaseItem extends BaseModel {
|
||||
throw new Error('Invalid class name: ' + name);
|
||||
}
|
||||
|
||||
static async syncedCount() {
|
||||
// TODO
|
||||
return 0;
|
||||
// const ItemClass = this.itemClass(this.modelType());
|
||||
// let sql = 'SELECT count(*) as total FROM `' + ItemClass.tableName() + '` WHERE updated_time <= sync_time';
|
||||
// if (this.modelType() == BaseModel.TYPE_NOTE) sql += ' AND is_conflict = 0';
|
||||
// const r = await this.db().selectOne(sql);
|
||||
// return r.total;
|
||||
static async syncedCount(syncTarget) {
|
||||
const ItemClass = this.itemClass(this.modelType());
|
||||
const itemType = ItemClass.modelType();
|
||||
// The fact that we don't check if the item_id still exist in the corresponding item table, means
|
||||
// that the returned number might be innaccurate (for example if a sync operation was cancelled)
|
||||
const sql = 'SELECT count(*) as total FROM sync_items WHERE sync_target = ? AND item_type = ?';
|
||||
const r = await this.db().selectOne(sql, [ syncTarget, itemType ]);
|
||||
return r.total;
|
||||
}
|
||||
|
||||
static systemPath(itemOrId) {
|
||||
@ -279,23 +279,6 @@ class BaseItem extends BaseModel {
|
||||
}
|
||||
|
||||
throw new Error('Unreachable');
|
||||
|
||||
//return this.modelSelectAll('SELECT * FROM folders WHERE sync_time < updated_time LIMIT ' + limit);
|
||||
|
||||
// let items = await this.getClass('Folder').modelSelectAll('SELECT * FROM folders WHERE sync_time < updated_time LIMIT ' + limit);
|
||||
// if (items.length) return { hasMore: true, items: items };
|
||||
|
||||
// items = await this.getClass('Resource').modelSelectAll('SELECT * FROM resources WHERE sync_time < updated_time LIMIT ' + limit);
|
||||
// if (items.length) return { hasMore: true, items: items };
|
||||
|
||||
// items = await this.getClass('Note').modelSelectAll('SELECT * FROM notes WHERE sync_time < updated_time AND is_conflict = 0 LIMIT ' + limit);
|
||||
// if (items.length) return { hasMore: true, items: items };
|
||||
|
||||
// items = await this.getClass('Tag').modelSelectAll('SELECT * FROM tags WHERE sync_time < updated_time LIMIT ' + limit);
|
||||
// if (items.length) return { hasMore: true, items: items };
|
||||
|
||||
// items = await this.getClass('NoteTag').modelSelectAll('SELECT * FROM note_tags WHERE sync_time < updated_time LIMIT ' + limit);
|
||||
// return { hasMore: items.length >= limit, items: items };
|
||||
}
|
||||
|
||||
static syncItemClassNames() {
|
||||
@ -341,7 +324,10 @@ class BaseItem extends BaseModel {
|
||||
const className = classNames[i];
|
||||
const ItemClass = this.getClass(className);
|
||||
|
||||
queries.push('DELETE FROM sync_items WHERE item_type = ' + ItemClass.modelType() + ' AND item_id NOT IN (SELECT id FROM ' + ItemClass.tableName() + ')');
|
||||
let selectSql = 'SELECT id FROM ' + ItemClass.tableName();
|
||||
if (ItemClass.modelType() == this.TYPE_NOTE) selectSql += ' WHERE is_conflict = 0';
|
||||
|
||||
queries.push('DELETE FROM sync_items WHERE item_type = ' + ItemClass.modelType() + ' AND item_id NOT IN (' + selectSql + ')');
|
||||
}
|
||||
|
||||
await this.db().transactionExecBatch(queries);
|
||||
|
@ -146,7 +146,7 @@ class Setting extends BaseModel {
|
||||
Setting.defaults_ = {
|
||||
'activeFolderId': { value: '', type: 'string', public: false },
|
||||
'sync.onedrive.auth': { value: '', type: 'string', public: false },
|
||||
'sync.local.path': { value: '', type: 'string', public: true },
|
||||
'sync.filesystem.path': { value: '', type: 'string', public: true },
|
||||
'sync.target': { value: 'onedrive', type: 'string', public: true },
|
||||
'editor': { value: '', type: 'string', public: true },
|
||||
};
|
||||
|
@ -6,7 +6,7 @@ import { _ } from 'lib/locale.js';
|
||||
|
||||
class ReportService {
|
||||
|
||||
async syncStatus() {
|
||||
async syncStatus(syncTarget) {
|
||||
let output = {
|
||||
items: {},
|
||||
total: {},
|
||||
@ -19,8 +19,7 @@ class ReportService {
|
||||
let ItemClass = BaseItem.getClass(d.className);
|
||||
let o = {
|
||||
total: await ItemClass.count(),
|
||||
// synced: await ItemClass.syncedCount(), // TODO
|
||||
synced: 0,
|
||||
synced: await ItemClass.syncedCount(syncTarget),
|
||||
};
|
||||
output.items[d.className] = o;
|
||||
itemCount += o.total;
|
||||
@ -47,8 +46,8 @@ class ReportService {
|
||||
return output;
|
||||
}
|
||||
|
||||
async status() {
|
||||
let r = await this.syncStatus();
|
||||
async status(syncTarget) {
|
||||
let r = await this.syncStatus(syncTarget);
|
||||
let sections = [];
|
||||
let section = {};
|
||||
|
||||
|
@ -68,6 +68,17 @@ function historyCanGoBackTo(route) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function reducerActionsAreSame(a1, a2) {
|
||||
if (Object.getOwnPropertyNames(a1).length !== Object.getOwnPropertyNames(a2).length) return false;
|
||||
|
||||
for (let n in a1) {
|
||||
if (!a1.hasOwnProperty(n)) continue;
|
||||
if (a1[n] !== a2[n]) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const reducer = (state = defaultState, action) => {
|
||||
reg.logger().info('Reducer action', action.type);
|
||||
|
||||
@ -116,10 +127,16 @@ const reducer = (state = defaultState, action) => {
|
||||
newState.selectedItemType = action.itemType;
|
||||
}
|
||||
|
||||
newState.route = action;
|
||||
|
||||
// If the route *name* is the same (even if the other parameters are different), we
|
||||
// overwrite the last route in the history with the current one. If the route name
|
||||
// is different, we push a new history entry.
|
||||
|
||||
if (currentRouteName == action.routeName) {
|
||||
if (navHistory.length) navHistory[navHistory.length - 1] = action;
|
||||
// If the current screen is already the requested screen, don't do anything
|
||||
} else {
|
||||
newState.route = action;
|
||||
if (action.routeName == 'Welcome') navHistory = [];
|
||||
navHistory.push(action);
|
||||
}
|
||||
@ -151,20 +168,27 @@ const reducer = (state = defaultState, action) => {
|
||||
// update it within the note array if it already exists.
|
||||
case 'NOTES_UPDATE_ONE':
|
||||
|
||||
if (action.note.parent_id != state.selectedFolderId) break;
|
||||
const modNote = action.note;
|
||||
|
||||
let newNotes = state.notes.splice(0);
|
||||
var found = false;
|
||||
for (let i = 0; i < newNotes.length; i++) {
|
||||
let n = newNotes[i];
|
||||
if (n.id == action.note.id) {
|
||||
newNotes[i] = Object.assign(newNotes[i], action.note);
|
||||
if (n.id == modNote.id) {
|
||||
|
||||
if (!('parent_id' in modNote) || modNote.parent_id == n.parent_id) {
|
||||
// Merge the properties that have changed (in modNote) into
|
||||
// the object we already have.
|
||||
newNotes[i] = Object.assign(newNotes[i], action.note);
|
||||
} else {
|
||||
newNotes.splice(i, 1);
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) newNotes.push(action.note);
|
||||
if (!found && ('parent_id' in modNote) && modNote.parent_id == state.selectedFolderId) newNotes.push(modNote);
|
||||
|
||||
newNotes = Note.sortNotes(newNotes, state.notesOrder);
|
||||
newState = Object.assign({}, state);
|
||||
|
Loading…
Reference in New Issue
Block a user