diff --git a/CliClient/app/import-enex.js b/CliClient/app/import-enex.js
index 737e23101b..17f27e88d6 100644
--- a/CliClient/app/import-enex.js
+++ b/CliClient/app/import-enex.js
@@ -350,8 +350,8 @@ function importEnex(parentFolderId, filePath, importOptions = null) {
id: noteResource.id,
data: decodedData,
mime: noteResource.mime,
- title: noteResource.filename,
- filename: noteResource.filename,
+ title: noteResource.filename ? noteResource.filename : '',
+ filename: noteResource.filename ? noteResource.filename : '',
};
note.resources.push(r);
diff --git a/CliClient/app/main.js b/CliClient/app/main.js
index 44eae6c756..6c2f0ac482 100644
--- a/CliClient/app/main.js
+++ b/CliClient/app/main.js
@@ -8,6 +8,7 @@ import { FileApiDriverOneDrive } from 'lib/file-api-driver-onedrive.js';
import { FileApiDriverMemory } from 'lib/file-api-driver-memory.js';
import { FileApiDriverLocal } from 'lib/file-api-driver-local.js';
import { OneDriveApiNodeUtils } from './onedrive-api-node-utils.js';
+import { JoplinDatabase } from 'lib/joplin-database.js';
import { Database } from 'lib/database.js';
import { DatabaseDriverNode } from 'lib/database-driver-node.js';
import { BaseModel } from 'lib/base-model.js';
@@ -927,7 +928,12 @@ async function main() {
await fs.mkdirp(resourceDir, 0o755);
await fs.mkdirp(tempDir, 0o755);
+ // let logDatabase = new Database(new DatabaseDriverNode());
+ // await logDatabase.open({ name: profileDir + '/database-log.sqlite' });
+ // await logDatabase.exec(Logger.databaseCreateTableSql());
+
logger.addTarget('file', { path: profileDir + '/log.txt' });
+ // logger.addTarget('database', { database: logDatabase, source: 'main' });
logger.setLevel(logLevel);
dbLogger.addTarget('file', { path: profileDir + '/log-database.txt' });
@@ -947,7 +953,7 @@ async function main() {
BaseItem.loadClass('Tag', Tag);
BaseItem.loadClass('NoteTag', NoteTag);
- database_ = new Database(new DatabaseDriverNode());
+ database_ = new JoplinDatabase(new DatabaseDriverNode());
database_.setLogger(dbLogger);
await database_.open({ name: profileDir + '/database.sqlite' });
BaseModel.db_ = database_;
diff --git a/CliClient/tests/test-utils.js b/CliClient/tests/test-utils.js
index a9d878105d..53830f2c62 100644
--- a/CliClient/tests/test-utils.js
+++ b/CliClient/tests/test-utils.js
@@ -1,5 +1,5 @@
import fs from 'fs-extra';
-import { Database } from 'lib/database.js';
+import { JoplinDatabase } from 'lib/joplin-database.js';
import { DatabaseDriverNode } from 'lib/database-driver-node.js';
import { BaseModel } from 'lib/base-model.js';
import { Folder } from 'lib/models/folder.js';
@@ -25,8 +25,11 @@ const fsDriver = new FsDriverNode();
Logger.fsDriver_ = fsDriver;
Resource.fsDriver_ = fsDriver;
+const logDir = __dirname + '/../tests/logs';
+fs.mkdirpSync(logDir, 0o755);
+
const logger = new Logger();
-logger.addTarget('file', { path: __dirname + '/../tests/logs/log.txt' });
+logger.addTarget('file', { path: logDir + '/log.txt' });
logger.setLevel(Logger.LEVEL_DEBUG);
BaseItem.loadClass('Note', Note);
@@ -87,7 +90,7 @@ function setupDatabase(id = null) {
return fs.unlink(filePath).catch(() => {
// Don't care if the file doesn't exist
}).then(() => {
- databases_[id] = new Database(new DatabaseDriverNode());
+ databases_[id] = new JoplinDatabase(new DatabaseDriverNode());
databases_[id].setLogger(logger);
return databases_[id].open({ name: filePath }).then(() => {
BaseModel.db_ = databases_[id];
diff --git a/ReactNativeClient/android/app/build.gradle b/ReactNativeClient/android/app/build.gradle
index 57a428764e..b572a55ba7 100644
--- a/ReactNativeClient/android/app/build.gradle
+++ b/ReactNativeClient/android/app/build.gradle
@@ -83,62 +83,73 @@ def enableSeparateBuildPerCPUArchitecture = false
def enableProguardInReleaseBuilds = false
android {
- compileSdkVersion 23
- buildToolsVersion "23.0.1"
+ compileSdkVersion 23
+ buildToolsVersion "23.0.1"
- defaultConfig {
- applicationId "com.awesomeproject"
- minSdkVersion 16
- targetSdkVersion 22
- versionCode 1
- versionName "1.0"
- ndk {
- abiFilters "armeabi-v7a", "x86"
- }
- }
- splits {
- abi {
- reset()
- enable enableSeparateBuildPerCPUArchitecture
- universalApk false // If true, also generate a universal APK
- include "armeabi-v7a", "x86"
- }
- }
- buildTypes {
- release {
- minifyEnabled enableProguardInReleaseBuilds
- proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
- }
- }
- // applicationVariants are e.g. debug, release
- applicationVariants.all { variant ->
- variant.outputs.each { output ->
- // For each separate APK per architecture, set a unique version code as described here:
- // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits
- def versionCodes = ["armeabi-v7a":1, "x86":2]
- def abi = output.getFilter(OutputFile.ABI)
- if (abi != null) { // null for the universal-debug, universal-release variants
- output.versionCodeOverride =
- versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
- }
- }
- }
+ defaultConfig {
+ applicationId "com.awesomeproject"
+ minSdkVersion 16
+ targetSdkVersion 22
+ versionCode 1
+ versionName "1.0"
+ ndk {
+ abiFilters "armeabi-v7a", "x86"
+ }
+ }
+ splits {
+ abi {
+ reset()
+ enable enableSeparateBuildPerCPUArchitecture
+ universalApk false // If true, also generate a universal APK
+ include "armeabi-v7a", "x86"
+ }
+ }
+ signingConfigs {
+ release {
+ if (project.hasProperty('JOPLIN_RELEASE_STORE_FILE')) {
+ storeFile file(JOPLIN_RELEASE_STORE_FILE)
+ storePassword JOPLIN_RELEASE_STORE_PASSWORD
+ keyAlias JOPLIN_RELEASE_KEY_ALIAS
+ keyPassword JOPLIN_RELEASE_KEY_PASSWORD
+ }
+ }
+ }
+ buildTypes {
+ release {
+ minifyEnabled enableProguardInReleaseBuilds
+ proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
+ signingConfig signingConfigs.release
+ }
+ }
+ // applicationVariants are e.g. debug, release
+ applicationVariants.all { variant ->
+ variant.outputs.each { output ->
+ // For each separate APK per architecture, set a unique version code as described here:
+ // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits
+ def versionCodes = ["armeabi-v7a":1, "x86":2]
+ def abi = output.getFilter(OutputFile.ABI)
+ if (abi != null) { // null for the universal-debug, universal-release variants
+ output.versionCodeOverride =
+ versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
+ }
+ }
+ }
}
dependencies {
- compile project(':react-native-fs')
- compile fileTree(dir: "libs", include: ["*.jar"])
- compile "com.android.support:appcompat-v7:23.0.1"
- compile "com.facebook.react:react-native:+" // From node_modules
- compile project(':react-native-sqlite-storage')
- compile project(':react-native-fetch-blob')
+ compile project(':react-native-fs')
+ compile fileTree(dir: "libs", include: ["*.jar"])
+ compile "com.android.support:appcompat-v7:23.0.1"
+ compile "com.facebook.react:react-native:+" // From node_modules
+ compile project(':react-native-sqlite-storage')
+ compile project(':react-native-fetch-blob')
}
// Run this once to be able to run the application with BUCK
// puts all compile dependencies into folder libs for BUCK to use
task copyDownloadableDepsToLibs(type: Copy) {
- from configurations.compile
- into 'libs'
+ from configurations.compile
+ into 'libs'
}
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
diff --git a/ReactNativeClient/android/app/src/main/java/com/awesomeproject/MainActivity.java b/ReactNativeClient/android/app/src/main/java/com/awesomeproject/MainActivity.java
index 74accd2174..39cf7cb7f3 100644
--- a/ReactNativeClient/android/app/src/main/java/com/awesomeproject/MainActivity.java
+++ b/ReactNativeClient/android/app/src/main/java/com/awesomeproject/MainActivity.java
@@ -4,12 +4,12 @@ import com.facebook.react.ReactActivity;
public class MainActivity extends ReactActivity {
- /**
- * Returns the name of the main component registered from JavaScript.
- * This is used to schedule rendering of the component.
- */
- @Override
- protected String getMainComponentName() {
- return "AwesomeProject";
- }
+ /**
+ * Returns the name of the main component registered from JavaScript.
+ * This is used to schedule rendering of the component.
+ */
+ @Override
+ protected String getMainComponentName() {
+ return "AwesomeProject";
+ }
}
diff --git a/ReactNativeClient/lib/components/item-list.js b/ReactNativeClient/lib/components/item-list.js
index 5fc1ffd994..53d9ab44fa 100644
--- a/ReactNativeClient/lib/components/item-list.js
+++ b/ReactNativeClient/lib/components/item-list.js
@@ -37,6 +37,7 @@ class ItemListComponent extends Component {
await Note.save({ id: note.id, todo_completed: checked });
}
+ listView_itemLongPress(itemId) {}
listView_itemPress(itemId) {}
render() {
@@ -50,8 +51,8 @@ class ItemListComponent extends Component {
return (
-
- { !!Number(item.is_todo) && { this.todoCheckbox_change(item.id, checked) }}/> }{item.title} [{item.id}]
+
+ { !!Number(item.is_todo) && { this.todoCheckbox_change(item.id, checked) }}/> }{item.title}
);
diff --git a/ReactNativeClient/lib/components/screen-header.js b/ReactNativeClient/lib/components/screen-header.js
index 677cbb7fff..5fdd97e514 100644
--- a/ReactNativeClient/lib/components/screen-header.js
+++ b/ReactNativeClient/lib/components/screen-header.js
@@ -41,22 +41,6 @@ class ScreenHeaderComponent extends Component {
}
}
- async menu_synchronize() {
- if (reg.oneDriveApi().auth()) {
- const sync = await reg.synchronizer();
- try {
- sync.start();
- } catch (error) {
- Log.error(error);
- }
- } else {
- this.props.dispatch({
- type: 'Navigation/NAVIGATE',
- routeName: 'OneDriveLogin',
- });
- }
- }
-
render() {
let key = 0;
let menuOptionComponents = [];
@@ -72,15 +56,10 @@ class ScreenHeaderComponent extends Component {
menuOptionComponents.push();
}
- menuOptionComponents.push(
- this.menu_synchronize()} key={'menuOption_' + key++}>
- {_('Synchronize')}
- );
-
- menuOptionComponents.push(
-
- {_('Configuration')}
- );
+ // menuOptionComponents.push(
+ //
+ // {_('Configuration')}
+ // );
let title = 'title' in this.props && this.props.title !== null ? this.props.title : _(this.props.navState.routeName);
diff --git a/ReactNativeClient/lib/components/screens/note.js b/ReactNativeClient/lib/components/screens/note.js
index f87d560c01..be7c569aa1 100644
--- a/ReactNativeClient/lib/components/screens/note.js
+++ b/ReactNativeClient/lib/components/screens/note.js
@@ -85,7 +85,7 @@ class NoteScreenComponent extends React.Component {
{ isTodo && } this.title_changeText(text)} />
- this.body_changeText(text)} />
+ this.body_changeText(text)} />
{ todoComponents }
diff --git a/ReactNativeClient/lib/components/screens/onedrive-login.js b/ReactNativeClient/lib/components/screens/onedrive-login.js
index 7750dfed12..008b368199 100644
--- a/ReactNativeClient/lib/components/screens/onedrive-login.js
+++ b/ReactNativeClient/lib/components/screens/onedrive-login.js
@@ -1,6 +1,6 @@
import React, { Component } from 'react';
import { View } from 'react-native';
-import { WebView, Button } from 'react-native';
+import { WebView, Button, Text } from 'react-native';
import { connect } from 'react-redux'
import { Log } from 'lib/log.js'
import { Setting } from 'lib/models/setting.js'
@@ -22,10 +22,14 @@ class OneDriveLoginScreenComponent extends React.Component {
componentWillMount() {
this.setState({
- webviewUrl: reg.oneDriveApi().authCodeUrl(this.redirectUrl()),
+ webviewUrl: this.startUrl(),
});
}
+ startUrl() {
+ return reg.oneDriveApi().authCodeUrl(this.redirectUrl());
+ }
+
redirectUrl() {
return 'https://login.microsoftonline.com/common/oauth2/nativeclient';
}
@@ -37,7 +41,7 @@ class OneDriveLoginScreenComponent extends React.Component {
const url = noIdeaWhatThisIs.url;
if (!this.authCode_ && url.indexOf(this.redirectUrl() + '?code=') === 0) {
- console.info('URL: ' + url);
+ Log.info('URL: ' + url);
let code = url.split('?code=');
this.authCode_ = code[1];
@@ -48,6 +52,28 @@ class OneDriveLoginScreenComponent extends React.Component {
}
}
+ async webview_error(error) {
+ Log.error(error);
+ }
+
+ retryButton_click() {
+ // It seems the only way it would reload the page is by loading an unrelated
+ // URL, waiting a bit, and then loading the actual URL. There's probably
+ // a better way to do this.
+
+ this.setState({
+ webviewUrl: 'https://microsoft.com',
+ });
+ this.forceUpdate();
+
+ setTimeout(() => {
+ this.setState({
+ webviewUrl: this.startUrl(),
+ });
+ this.forceUpdate();
+ }, 1000);
+ }
+
render() {
const source = {
uri: this.state.webviewUrl,
@@ -60,7 +86,10 @@ class OneDriveLoginScreenComponent extends React.Component {
source={source}
style={{marginTop: 20}}
onNavigationStateChange={(o) => { this.webview_load(o); }}
+ onError={(error) => { this.webview_error(error); }}
/>
+
+
);
}
diff --git a/ReactNativeClient/lib/components/side-menu-content.js b/ReactNativeClient/lib/components/side-menu-content.js
index 5aae3b5d51..cffeb9d040 100644
--- a/ReactNativeClient/lib/components/side-menu-content.js
+++ b/ReactNativeClient/lib/components/side-menu-content.js
@@ -1,8 +1,10 @@
import { connect } from 'react-redux'
-import { Button } from 'react-native';
+import { Button, Text } from 'react-native';
import { Log } from 'lib/log.js';
import { Note } from 'lib/models/note.js';
import { NotesScreenUtils } from 'lib/components/screens/notes-utils.js'
+import { reg } from 'lib/registry.js';
+import { _ } from 'lib/locale.js';
const React = require('react');
const {
@@ -11,7 +13,6 @@ const {
ScrollView,
View,
Image,
- Text,
} = require('react-native');
const { Component } = React;
@@ -41,6 +42,11 @@ const styles = StyleSheet.create({
class SideMenuContentComponent extends Component {
+ constructor() {
+ super();
+ this.state = { syncReportText: '' };
+ }
+
folder_press(folder) {
this.props.dispatch({
type: 'SIDE_MENU_CLOSE',
@@ -49,19 +55,57 @@ class SideMenuContentComponent extends Component {
NotesScreenUtils.openNoteList(folder.id);
}
+ async synchronize_press() {
+ if (reg.oneDriveApi().auth()) {
+ let options = {
+ onProgress: (report) => {
+ let line = [];
+ line.push(_('Items to upload: %d/%d.', report.createRemote + report.updateRemote, report.remotesToUpdate));
+ line.push(_('Remote items to delete: %d/%d.', report.deleteRemote, report.remotesToDelete));
+ line.push(_('Items to download: %d/%d.', report.createLocal + report.updateLocal, report.localsToUdpate));
+ line.push(_('Local items to delete: %d/%d.', report.deleteLocal, report.localsToDelete));
+ this.setState({ syncReportText: line.join("\n") });
+ },
+ };
+
+ try {
+ const sync = await reg.synchronizer()
+ sync.start(options);
+ } catch (error) {
+ Log.error(error);
+ }
+ } else {
+ this.props.dispatch({
+ type: 'Navigation/NAVIGATE',
+ routeName: 'OneDriveLogin',
+ });
+ }
+ }
+
render() {
- let buttons = [];
+ let keyIndex = 0;
+ let key = () => {
+ return 'smitem_' + (keyIndex++);
+ }
+
+ let items = [];
for (let i = 0; i < this.props.folders.length; i++) {
let f = this.props.folders[i];
let title = f.title ? f.title : '';
- buttons.push(
-