mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Attach resource from mobile
This commit is contained in:
parent
7fe70696bb
commit
19266206f4
@ -164,6 +164,8 @@ function isImageMimeType(m) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addResourceTag(lines, resource, alt = "") {
|
function addResourceTag(lines, resource, alt = "") {
|
||||||
|
// TODO: refactor to use Resource.markdownTag
|
||||||
|
|
||||||
let tagAlt = alt == "" ? resource.alt : alt;
|
let tagAlt = alt == "" ? resource.alt : alt;
|
||||||
if (!tagAlt) tagAlt = '';
|
if (!tagAlt) tagAlt = '';
|
||||||
if (isImageMimeType(resource.mime)) {
|
if (isImageMimeType(resource.mime)) {
|
||||||
|
@ -143,6 +143,7 @@ dependencies {
|
|||||||
compile "com.facebook.react:react-native:+" // From node_modules
|
compile "com.facebook.react:react-native:+" // From node_modules
|
||||||
compile project(':react-native-sqlite-storage')
|
compile project(':react-native-sqlite-storage')
|
||||||
compile project(':react-native-fetch-blob')
|
compile project(':react-native-fetch-blob')
|
||||||
|
compile project(':react-native-document-picker')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run this once to be able to run the application with BUCK
|
// Run this once to be able to run the application with BUCK
|
||||||
|
@ -10,6 +10,7 @@ import com.facebook.react.shell.MainReactPackage;
|
|||||||
import com.facebook.soloader.SoLoader;
|
import com.facebook.soloader.SoLoader;
|
||||||
import org.pgsqlite.SQLitePluginPackage;
|
import org.pgsqlite.SQLitePluginPackage;
|
||||||
import com.RNFetchBlob.RNFetchBlobPackage;
|
import com.RNFetchBlob.RNFetchBlobPackage;
|
||||||
|
import com.reactnativedocumentpicker.ReactNativeDocumentPicker;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -28,7 +29,8 @@ public class MainApplication extends Application implements ReactApplication {
|
|||||||
new SQLitePluginPackage(),
|
new SQLitePluginPackage(),
|
||||||
new MainReactPackage(),
|
new MainReactPackage(),
|
||||||
new RNFSPackage(),
|
new RNFSPackage(),
|
||||||
new RNFetchBlobPackage()
|
new RNFetchBlobPackage(),
|
||||||
|
new ReactNativeDocumentPicker()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -9,3 +9,6 @@ project(':react-native-sqlite-storage').projectDir = new File(rootProject.projec
|
|||||||
|
|
||||||
include ':react-native-fetch-blob'
|
include ':react-native-fetch-blob'
|
||||||
project(':react-native-fetch-blob').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fetch-blob/android')
|
project(':react-native-fetch-blob').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fetch-blob/android')
|
||||||
|
|
||||||
|
include ':react-native-document-picker'
|
||||||
|
project(':react-native-document-picker').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-document-picker/android')
|
@ -126,13 +126,14 @@ class NoteBodyViewer extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const r = this.state.resources[resourceId];
|
const r = this.state.resources[resourceId];
|
||||||
if (r.mime == 'image/png' || r.mime == 'image/jpg' || r.mime == 'image/gif') {
|
const mime = r.mime.toLowerCase();
|
||||||
|
if (mime == 'image/png' || mime == 'image/jpg' || mime == 'image/jpeg' || mime == 'image/gif') {
|
||||||
const src = 'data:' + r.mime + ';base64,' + r.base64;
|
const src = 'data:' + r.mime + ';base64,' + r.base64;
|
||||||
let output = '<img title="' + htmlentities(title) + '" src="' + src + '"/>';
|
let output = '<img title="' + htmlentities(title) + '" src="' + htmlentities(src) + '"/>';
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
return '[Image: ' + htmlentities(r.title) + '(' + htmlentities(r.mime) + ')]';
|
return '[Image: ' + htmlentities(r.title) + ' (' + htmlentities(mime) + ')]';
|
||||||
}
|
}
|
||||||
|
|
||||||
let styleHtml = '<style>' + normalizeCss + "\n" + css + '</style>';
|
let styleHtml = '<style>' + normalizeCss + "\n" + css + '</style>';
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { BackHandler, View, Button, TextInput, WebView, Text, StyleSheet, Linking } from 'react-native';
|
import { BackHandler, View, Button, TextInput, WebView, Text, StyleSheet, Linking } from 'react-native';
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
|
import { uuid } from 'lib/uuid.js';
|
||||||
import { Log } from 'lib/log.js'
|
import { Log } from 'lib/log.js'
|
||||||
import { Note } from 'lib/models/note.js'
|
import { Note } from 'lib/models/note.js'
|
||||||
import { Resource } from 'lib/models/resource.js'
|
import { Resource } from 'lib/models/resource.js'
|
||||||
@ -19,6 +20,9 @@ import { dialogs } from 'lib/dialogs.js';
|
|||||||
import { globalStyle, themeStyle } from 'lib/components/global-style.js';
|
import { globalStyle, themeStyle } from 'lib/components/global-style.js';
|
||||||
import DialogBox from 'react-native-dialogbox';
|
import DialogBox from 'react-native-dialogbox';
|
||||||
import { NoteBodyViewer } from 'lib/components/note-body-viewer.js';
|
import { NoteBodyViewer } from 'lib/components/note-body-viewer.js';
|
||||||
|
import RNFetchBlob from 'react-native-fetch-blob';
|
||||||
|
import { DocumentPicker, DocumentPickerUtil } from 'react-native-document-picker';
|
||||||
|
|
||||||
|
|
||||||
class NoteScreenComponent extends BaseScreenComponent {
|
class NoteScreenComponent extends BaseScreenComponent {
|
||||||
|
|
||||||
@ -247,8 +251,42 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
attachFile_onPress() {
|
async pickDocument() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
DocumentPicker.show({ filetype: [DocumentPickerUtil.images()] }, (error,res) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(res);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async attachFile_onPress() {
|
||||||
|
const res = await this.pickDocument();
|
||||||
|
|
||||||
|
// res.uri,
|
||||||
|
// res.type, // mime type
|
||||||
|
// res.fileName,
|
||||||
|
// res.fileSize
|
||||||
|
|
||||||
|
let resource = Resource.new();
|
||||||
|
resource.id = uuid.create();
|
||||||
|
resource.mime = res.type;
|
||||||
|
resource.title = res.fileName ? res.fileName : _('Untitled');
|
||||||
|
|
||||||
|
const targetPath = Resource.fullPath(resource);
|
||||||
|
RNFetchBlob.fs.cp(res.uri, targetPath);
|
||||||
|
|
||||||
|
await Resource.save(resource, { isNew: true });
|
||||||
|
|
||||||
|
const resourceTag = Resource.markdownTag(resource);
|
||||||
|
|
||||||
|
const newNote = Object.assign({}, this.state.note);
|
||||||
|
newNote.body += "\n" + resourceTag;
|
||||||
|
this.setState({ note: newNote });
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleIsTodo_onPress() {
|
toggleIsTodo_onPress() {
|
||||||
@ -279,7 +317,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
const note = this.state.note;
|
const note = this.state.note;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
// { title: _('Attach file'), onPress: () => { this.attachFile_onPress(); } },
|
{ title: _('Attach file'), onPress: () => { this.attachFile_onPress(); } },
|
||||||
{ title: _('Delete note'), onPress: () => { this.deleteNote_onPress(); } },
|
{ title: _('Delete note'), onPress: () => { this.deleteNote_onPress(); } },
|
||||||
{ title: note && !!note.is_todo ? _('Convert to regular note') : _('Convert to todo'), onPress: () => { this.toggleIsTodo_onPress(); } },
|
{ title: note && !!note.is_todo ? _('Convert to regular note') : _('Convert to todo'), onPress: () => { this.toggleIsTodo_onPress(); } },
|
||||||
{ title: this.state.showNoteMetadata ? _('Hide metadata') : _('Show metadata'), onPress: () => { this.showMetadata_onPress(); } },
|
{ title: this.state.showNoteMetadata ? _('Hide metadata') : _('Show metadata'), onPress: () => { this.showMetadata_onPress(); } },
|
||||||
|
@ -126,12 +126,16 @@ class FileApiDriverOneDrive {
|
|||||||
return this.makeItem_(item);
|
return this.makeItem_(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
put(path, content) {
|
put(path, content, options = null) {
|
||||||
let options = {
|
if (!options) options = {};
|
||||||
headers: { 'Content-Type': 'text/plain' },
|
|
||||||
};
|
if (options.source == 'file') {
|
||||||
|
return this.api_.exec('PUT', this.makePath_(path) + ':/content', null, null, options);
|
||||||
|
} else {
|
||||||
|
options.headers = { 'Content-Type': 'text/plain' };
|
||||||
return this.api_.exec('PUT', this.makePath_(path) + ':/content', null, content, options);
|
return this.api_.exec('PUT', this.makePath_(path) + ':/content', null, content, options);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
delete(path) {
|
delete(path) {
|
||||||
return this.api_.exec('DELETE', this.makePath_(path));
|
return this.api_.exec('DELETE', this.makePath_(path));
|
||||||
|
@ -87,9 +87,9 @@ class FileApi {
|
|||||||
return this.driver_.get(this.fullPath_(path), options);
|
return this.driver_.get(this.fullPath_(path), options);
|
||||||
}
|
}
|
||||||
|
|
||||||
put(path, content) {
|
put(path, content, options = null) {
|
||||||
this.logger().debug('put ' + this.fullPath_(path));
|
this.logger().debug('put ' + this.fullPath_(path));
|
||||||
return this.driver_.put(this.fullPath_(path), content);
|
return this.driver_.put(this.fullPath_(path), content, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(path) {
|
delete(path) {
|
||||||
|
@ -3,6 +3,7 @@ import { BaseItem } from 'lib/models/base-item.js';
|
|||||||
import { Setting } from 'lib/models/setting.js';
|
import { Setting } from 'lib/models/setting.js';
|
||||||
import { mime } from 'lib/mime-utils.js';
|
import { mime } from 'lib/mime-utils.js';
|
||||||
import { filename } from 'lib/path-utils.js';
|
import { filename } from 'lib/path-utils.js';
|
||||||
|
import { FsDriverDummy } from 'lib/fs-driver-dummy.js';
|
||||||
import lodash from 'lodash';
|
import lodash from 'lodash';
|
||||||
|
|
||||||
class Resource extends BaseItem {
|
class Resource extends BaseItem {
|
||||||
@ -15,6 +16,11 @@ class Resource extends BaseItem {
|
|||||||
return BaseModel.TYPE_RESOURCE;
|
return BaseModel.TYPE_RESOURCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static isSupportedImageMimeType(type) {
|
||||||
|
const imageMimeTypes = ["image/jpg", "image/jpeg", "image/png", "image/gif"];
|
||||||
|
return imageMimeTypes.indexOf(type.toLowerCase()) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
static fsDriver() {
|
static fsDriver() {
|
||||||
if (!Resource.fsDriver_) Resource.fsDriver_ = new FsDriverDummy();
|
if (!Resource.fsDriver_) Resource.fsDriver_ = new FsDriverDummy();
|
||||||
return Resource.fsDriver_;
|
return Resource.fsDriver_;
|
||||||
@ -32,6 +38,22 @@ class Resource extends BaseItem {
|
|||||||
return Setting.value('resourceDir') + '/' + resource.id + extension;
|
return Setting.value('resourceDir') + '/' + resource.id + extension;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static markdownTag(resource) {
|
||||||
|
let tagAlt = resource.alt ? resource.alt : resource.title;
|
||||||
|
if (!tagAlt) tagAlt = '';
|
||||||
|
let lines = [];
|
||||||
|
if (Resource.isSupportedImageMimeType(resource.mime)) {
|
||||||
|
lines.push("![");
|
||||||
|
lines.push(tagAlt);
|
||||||
|
lines.push("](:/" + resource.id + ")");
|
||||||
|
} else {
|
||||||
|
lines.push("[");
|
||||||
|
lines.push(tagAlt);
|
||||||
|
lines.push("](:/" + resource.id + ")");
|
||||||
|
}
|
||||||
|
return lines.join('');
|
||||||
|
}
|
||||||
|
|
||||||
static pathToId(path) {
|
static pathToId(path) {
|
||||||
return filename(path);
|
return filename(path);
|
||||||
}
|
}
|
||||||
|
@ -163,7 +163,9 @@ class OneDriveApi {
|
|||||||
|
|
||||||
let response = null;
|
let response = null;
|
||||||
try {
|
try {
|
||||||
if (options.target == 'string') {
|
if (options.source == 'file' && (method == 'POST' || method == 'PUT')) {
|
||||||
|
response = await shim.uploadBlob(url, options);
|
||||||
|
} else if (options.target == 'string') {
|
||||||
response = await shim.fetch(url, options);
|
response = await shim.fetch(url, options);
|
||||||
} else { // file
|
} else { // file
|
||||||
response = await shim.fetchBlob(url, options);
|
response = await shim.fetchBlob(url, options);
|
||||||
|
@ -7,7 +7,6 @@ function shimInit() {
|
|||||||
|
|
||||||
shim.fetchBlob = async function(url, options) {
|
shim.fetchBlob = async function(url, options) {
|
||||||
if (!options || !options.path) throw new Error('fetchBlob: target file path is missing');
|
if (!options || !options.path) throw new Error('fetchBlob: target file path is missing');
|
||||||
if (!options.method) options.method = 'GET';
|
|
||||||
|
|
||||||
let headers = options.headers ? options.headers : {};
|
let headers = options.headers ? options.headers : {};
|
||||||
let method = options.method ? options.method : 'GET';
|
let method = options.method ? options.method : 'GET';
|
||||||
@ -39,6 +38,29 @@ function shimInit() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shim.uploadBlob = async function(url, options) {
|
||||||
|
if (!options || !options.path) throw new Error('uploadBlob: source file path is missing');
|
||||||
|
|
||||||
|
const headers = options.headers ? options.headers : {};
|
||||||
|
const method = options.method ? options.method : 'POST';
|
||||||
|
|
||||||
|
try {
|
||||||
|
let response = await RNFetchBlob.fetch(method, url, headers, RNFetchBlob.wrap(options.path));
|
||||||
|
|
||||||
|
// Returns an object that's roughtly compatible with a standard Response object
|
||||||
|
return {
|
||||||
|
ok: response.respInfo.status < 400,
|
||||||
|
data: response.data,
|
||||||
|
text: response.text,
|
||||||
|
json: response.json,
|
||||||
|
status: response.respInfo.status,
|
||||||
|
headers: response.respInfo.headers,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('uploadBlob: ' + method + ' ' + url + ': ' + error.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
shim.readLocalFileBase64 = async function(path) {
|
shim.readLocalFileBase64 = async function(path) {
|
||||||
return RNFetchBlob.fs.readFile(path, 'base64')
|
return RNFetchBlob.fs.readFile(path, 'base64')
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ let shim = {};
|
|||||||
|
|
||||||
shim.isNode = () => {
|
shim.isNode = () => {
|
||||||
if (typeof process === 'undefined') return false;
|
if (typeof process === 'undefined') return false;
|
||||||
return process.title = 'node';
|
return process.title == 'node';
|
||||||
};
|
};
|
||||||
|
|
||||||
shim.isReactNative = () => {
|
shim.isReactNative = () => {
|
||||||
@ -14,5 +14,6 @@ shim.FormData = typeof FormData !== 'undefined' ? FormData : null;
|
|||||||
shim.fs = null;
|
shim.fs = null;
|
||||||
shim.FileApiDriverLocal = null;
|
shim.FileApiDriverLocal = null;
|
||||||
shim.readLocalFileBase64 = () => { throw new Error('Not implemented'); }
|
shim.readLocalFileBase64 = () => { throw new Error('Not implemented'); }
|
||||||
|
shim.uploadBlob = () => { throw new Error('Not implemented'); }
|
||||||
|
|
||||||
export { shim };
|
export { shim };
|
@ -7,6 +7,7 @@ import { sprintf } from 'sprintf-js';
|
|||||||
import { time } from 'lib/time-utils.js';
|
import { time } from 'lib/time-utils.js';
|
||||||
import { Logger } from 'lib/logger.js'
|
import { Logger } from 'lib/logger.js'
|
||||||
import { _ } from 'lib/locale.js';
|
import { _ } from 'lib/locale.js';
|
||||||
|
import { shim } from 'lib/shim.js';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
class Synchronizer {
|
class Synchronizer {
|
||||||
@ -235,6 +236,8 @@ class Synchronizer {
|
|||||||
|
|
||||||
if (local.type_ == BaseModel.TYPE_RESOURCE && (action == 'createRemote' || (action == 'itemConflict' && remote))) {
|
if (local.type_ == BaseModel.TYPE_RESOURCE && (action == 'createRemote' || (action == 'itemConflict' && remote))) {
|
||||||
let remoteContentPath = this.resourceDirName_ + '/' + local.id;
|
let remoteContentPath = this.resourceDirName_ + '/' + local.id;
|
||||||
|
// TODO: handle node and mobile in the same way
|
||||||
|
if (shim.isNode()) {
|
||||||
let resourceContent = '';
|
let resourceContent = '';
|
||||||
try {
|
try {
|
||||||
resourceContent = await Resource.content(local);
|
resourceContent = await Resource.content(local);
|
||||||
@ -244,6 +247,10 @@ class Synchronizer {
|
|||||||
this.progressReport_.errors.push(error);
|
this.progressReport_.errors.push(error);
|
||||||
}
|
}
|
||||||
await this.api().put(remoteContentPath, resourceContent);
|
await this.api().put(remoteContentPath, resourceContent);
|
||||||
|
} else {
|
||||||
|
const localResourceContentPath = Resource.fullPath(local);
|
||||||
|
await this.api().put(remoteContentPath, null, { path: localResourceContentPath, source: 'file' });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action == 'createRemote' || action == 'updateRemote') {
|
if (action == 'createRemote' || action == 'updateRemote') {
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
"react-native": "0.46.0",
|
"react-native": "0.46.0",
|
||||||
"react-native-action-button": "^2.6.9",
|
"react-native-action-button": "^2.6.9",
|
||||||
"react-native-dialogbox": "^0.6.6",
|
"react-native-dialogbox": "^0.6.6",
|
||||||
|
"react-native-document-picker": "^2.0.0",
|
||||||
"react-native-fetch-blob": "^0.10.6",
|
"react-native-fetch-blob": "^0.10.6",
|
||||||
"react-native-fs": "^2.3.3",
|
"react-native-fs": "^2.3.3",
|
||||||
"react-native-popup-menu": "^0.7.4",
|
"react-native-popup-menu": "^0.7.4",
|
||||||
|
Loading…
Reference in New Issue
Block a user