Electron: Handle import export

pull/265/head
Laurent Cozic 2018-02-27 20:04:38 +00:00
parent 45845f645d
commit 4046a51472
24 changed files with 562 additions and 338 deletions

View File

@ -283,7 +283,7 @@ class Application extends BaseApplication {
exit: () => {}, exit: () => {},
showModalOverlay: (text) => {}, showModalOverlay: (text) => {},
hideModalOverlay: () => {}, hideModalOverlay: () => {},
stdoutMaxWidth: () => { return 78; }, stdoutMaxWidth: () => { return 100; },
forceRender: () => {}, forceRender: () => {},
termSaveState: () => {}, termSaveState: () => {},
termRestoreState: (state) => {}, termRestoreState: (state) => {},

View File

@ -18,8 +18,13 @@ class Command extends BaseCommand {
} }
options() { options() {
const service = new InteropService();
const formats = service.modules()
.filter(m => m.type === 'exporter')
.map(m => m.format + (m.description ? ' (' + m.description + ')' : ''));
return [ return [
//['--format <format>', 'jex (default), raw'], ['--format <format>', _('Destination format: %s', formats.join(', '))],
['--note <note>', _('Exports only the given note.')], ['--note <note>', _('Exports only the given note.')],
['--notebook <notebook>', _('Exports only the given notebook.')], ['--notebook <notebook>', _('Exports only the given notebook.')],
]; ];

View File

@ -21,9 +21,12 @@ class Command extends BaseCommand {
} }
options() { options() {
const service = new InteropService();
const formats = service.modules().filter(m => m.type === 'importer').map(m => m.format);
return [ return [
['--format <format>', _('Source format: %s', (['auto'].concat(formats)).join(', '))],
['-f, --force', _('Do not ask for confirmation.')], ['-f, --force', _('Do not ask for confirmation.')],
['--format <format>', 'auto, jex, enex, md'],
]; ];
} }

View File

@ -71,37 +71,10 @@ process.stdout.on('error', function( err ) {
// async function main() { // async function main() {
// const WebDavApi = require('lib/WebDavApi'); // const InteropService = require('lib/services/InteropService');
// const api = new WebDavApi('http://nextcloud.local/remote.php/dav/files/admin/Joplin', { username: 'admin', password: '1234567' }); // const service = new InteropService();
// const { FileApiDriverWebDav } = new require('lib/file-api-driver-webdav'); // console.info(service.moduleByFormat('importer', 'enex'));
// const driver = new FileApiDriverWebDav(api); // //await service.modules();
// const stat = await driver.stat('');
// console.info(stat);
// // const stat = await driver.stat('testing.txt');
// // console.info(stat);
// // const content = await driver.get('testing.txta');
// // console.info(content);
// // const content = await driver.get('testing.txta', { target: 'file', path: '/var/www/joplin/CliClient/testing-file.txt' });
// // console.info(content);
// // const content = await driver.mkdir('newdir5');
// // console.info(content);
// //await driver.put('myfile4.md', 'this is my content');
// // await driver.put('testimg.jpg', null, { source: 'file', path: '/mnt/d/test.jpg' });
// // await driver.delete('myfile4.md');
// // const deltaResult = await driver.delta('', {
// // allItemIdsHandler: () => { return []; }
// // });
// // console.info(deltaResult);
// } // }
// main().catch((error) => { console.error(error); }); // main().catch((error) => { console.error(error); });

View File

@ -41,13 +41,14 @@ class ElectronAppWrapper {
const windowState = windowStateKeeper({ const windowState = windowStateKeeper({
defaultWidth: 800, defaultWidth: 800,
defaultHeight: 600, defaultHeight: 600,
file: 'window-state-' + this.env_ + '.json',
}); });
const windowOptions = { const windowOptions = {
'x': windowState.x, x: windowState.x,
'y': windowState.y, y: windowState.y,
'width': windowState.width, width: windowState.width,
'height': windowState.height, height: windowState.height,
}; };
// Linux icon workaround for bug https://github.com/electron-userland/electron-builder/issues/2098 // Linux icon workaround for bug https://github.com/electron-userland/electron-builder/issues/2098

View File

@ -20,6 +20,7 @@ const packageInfo = require('./packageInfo.js');
const AlarmService = require('lib/services/AlarmService.js'); const AlarmService = require('lib/services/AlarmService.js');
const AlarmServiceDriverNode = require('lib/services/AlarmServiceDriverNode'); const AlarmServiceDriverNode = require('lib/services/AlarmServiceDriverNode');
const DecryptionWorker = require('lib/services/DecryptionWorker'); const DecryptionWorker = require('lib/services/DecryptionWorker');
const InteropService = require('lib/services/InteropService');
const { bridge } = require('electron').remote.require('./bridge'); const { bridge } = require('electron').remote.require('./bridge');
const Menu = bridge().Menu; const Menu = bridge().Menu;
@ -191,6 +192,112 @@ class Application extends BaseApplication {
}); });
} }
const importItems = [];
const exportItems = [];
const ioService = new InteropService();
const ioModules = ioService.modules();
for (let i = 0; i < ioModules.length; i++) {
const module = ioModules[i];
if (module.type === 'exporter') {
exportItems.push({
label: module.format + ' - ' + module.description,
screens: ['Main'],
click: async () => {
let path = null;
if (module.target === 'file') {
path = bridge().showSaveDialog({
filters: [{ name: module.description, extensions: [module.fileExtension]}]
});
} else {
path = bridge().showOpenDialog({
properties: ['openDirectory', 'createDirectory'],
});
}
if (!path || (Array.isArray(path) && !path.length)) return;
if (Array.isArray(path)) path = path[0];
this.dispatch({
type: 'WINDOW_COMMAND',
name: 'showModalMessage',
message: _('Exporting to "%s" as "%s" format. Please wait...', path, module.format),
});
const exportOptions = {};
exportOptions.path = path;
exportOptions.format = module.format;
const service = new InteropService();
const result = await service.export(exportOptions);
console.info('Export result: ', result);
this.dispatch({
type: 'WINDOW_COMMAND',
name: 'hideModalMessage',
});
}
});
} else {
for (let j = 0; j < module.sources.length; j++) {
const moduleSource = module.sources[j];
let label = [module.format + ' - ' + module.description];
if (module.sources.length > 1) {
label.push('(' + (moduleSource === 'file' ? _('File') : _('Directory')) + ')');
}
importItems.push({
label: label.join(' '),
screens: ['Main'],
click: async () => {
let path = null;
const selectedFolderId = this.store().getState().selectedFolderId;
if (moduleSource === 'file') {
path = bridge().showOpenDialog({
filters: [{ name: module.description, extensions: [module.fileExtension]}]
});
} else {
path = bridge().showOpenDialog({
properties: ['openDirectory', 'createDirectory'],
});
}
if (!path || (Array.isArray(path) && !path.length)) return;
if (Array.isArray(path)) path = path[0];
this.dispatch({
type: 'WINDOW_COMMAND',
name: 'showModalMessage',
message: _('Importing from "%s" as "%s" format. Please wait...', path, module.format),
});
const importOptions = {};
importOptions.path = path;
importOptions.format = module.format;
importOptions.destinationFolderId = !module.isNoteArchive && moduleSource === 'file' ? selectedFolderId : null;
const service = new InteropService();
try {
const result = await service.import(importOptions);
console.info('Import result: ', result);
} catch (error) {
bridge().showErrorMessageBox(error.message);
}
this.dispatch({
type: 'WINDOW_COMMAND',
name: 'hideModalMessage',
});
}
});
}
}
}
const template = [ const template = [
{ {
label: _('File'), label: _('File'),
@ -226,25 +333,31 @@ class Application extends BaseApplication {
} }
}, { }, {
type: 'separator', type: 'separator',
}, { // }, {
label: _('Import Evernote notes'), // label: _('Import Evernote notes'),
click: () => { // click: () => {
const filePaths = bridge().showOpenDialog({ // const filePaths = bridge().showOpenDialog({
properties: ['openFile', 'createDirectory'], // properties: ['openFile', 'createDirectory'],
filters: [ // filters: [
{ name: _('Evernote Export Files'), extensions: ['enex'] }, // { name: _('Evernote Export Files'), extensions: ['enex'] },
] // ]
}); // });
if (!filePaths || !filePaths.length) return; // if (!filePaths || !filePaths.length) return;
this.dispatch({ // this.dispatch({
type: 'NAV_GO', // type: 'NAV_GO',
routeName: 'Import', // routeName: 'Import',
props: { // props: {
filePath: filePaths[0], // filePath: filePaths[0],
}, // },
}); // });
} // }
}, {
label: _('Import'),
submenu: importItems,
}, {
label: _('Export'),
submenu: exportItems,
}, { }, {
type: 'separator', type: 'separator',
platforms: ['darwin'], platforms: ['darwin'],

View File

@ -26,7 +26,7 @@ class Bridge {
if (!this.window()) return { width: 0, height: 0 }; if (!this.window()) return { width: 0, height: 0 };
const s = this.window().getContentSize(); const s = this.window().getContentSize();
return { width: s[0], height: s[1] }; return { width: s[0], height: s[1] };
} }
windowSize() { windowSize() {
if (!this.window()) return { width: 0, height: 0 }; if (!this.window()) return { width: 0, height: 0 };
@ -108,23 +108,6 @@ class Bridge {
return require('electron').shell.openItem(fullPath) return require('electron').shell.openItem(fullPath)
} }
// async checkForUpdatesAndNotify(logFilePath) {
// if (!this.autoUpdater_) {
// this.autoUpdateLogger_ = new Logger();
// this.autoUpdateLogger_.addTarget('file', { path: logFilePath });
// this.autoUpdateLogger_.setLevel(Logger.LEVEL_DEBUG);
// this.autoUpdateLogger_.info('checkForUpdatesAndNotify: Initializing...');
// this.autoUpdater_ = require("electron-updater").autoUpdater;
// this.autoUpdater_.logger = this.autoUpdateLogger_;
// }
// try {
// await this.autoUpdater_.checkForUpdatesAndNotify();
// } catch (error) {
// this.autoUpdateLogger_.error(error);
// }
// }
checkForUpdates(inBackground, window, logFilePath) { checkForUpdates(inBackground, window, logFilePath) {
const { checkForUpdates } = require('./checkForUpdates.js'); const { checkForUpdates } = require('./checkForUpdates.js');
checkForUpdates(inBackground, window, logFilePath); checkForUpdates(inBackground, window, logFilePath);

View File

@ -22,6 +22,10 @@ class MainScreenComponent extends React.Component {
componentWillMount() { componentWillMount() {
this.setState({ this.setState({
promptOptions: null, promptOptions: null,
modalLayer: {
visible: false,
message: '',
},
}); });
} }
@ -165,6 +169,10 @@ class MainScreenComponent extends React.Component {
}); });
} else if (command.name === 'toggleVisiblePanes') { } else if (command.name === 'toggleVisiblePanes') {
this.toggleVisiblePanes(); this.toggleVisiblePanes();
} else if (command.name === 'showModalMessage') {
this.setState({ modalLayer: { visible: true, message: command.message } });
} else if (command.name === 'hideModalMessage') {
this.setState({ modalLayer: { visible: false, message: '' } });
} else if (command.name === 'editAlarm') { } else if (command.name === 'editAlarm') {
const note = await Note.load(command.noteId); const note = await Note.load(command.noteId);
@ -265,6 +273,17 @@ class MainScreenComponent extends React.Component {
height: height, height: height,
}; };
this.styles_.modalLayer = Object.assign({}, theme.textStyle, {
zIndex: 10000,
position: 'absolute',
top: 0,
left: 0,
backgroundColor: theme.backgroundColorTransparent,
width: width - 20,
height: height - 20,
padding: 10,
});
return this.styles_; return this.styles_;
} }
@ -353,8 +372,12 @@ class MainScreenComponent extends React.Component {
); );
} }
const modalLayerStyle = Object.assign({}, styles.modalLayer, { display: this.state.modalLayer.visible ? 'block' : 'none' });
return ( return (
<div style={style}> <div style={style}>
<div style={modalLayerStyle}>{this.state.modalLayer.message}</div>
<PromptDialog <PromptDialog
autocomplete={promptOptions && ('autocomplete' in promptOptions) ? promptOptions.autocomplete : null} autocomplete={promptOptions && ('autocomplete' in promptOptions) ? promptOptions.autocomplete : null}
defaultValue={promptOptions && promptOptions.value ? promptOptions.value : ''} defaultValue={promptOptions && promptOptions.value ? promptOptions.value : ''}

View File

@ -42,11 +42,6 @@
"integrity": "sha512-jjpyQsKGsOF/wUElNjfPULk+d8PKvJOIXk3IUeBYYmNCy5dMWfrI+JiixYNw8ppKOlcRwWTXFl0B+i5oGrf95Q==", "integrity": "sha512-jjpyQsKGsOF/wUElNjfPULk+d8PKvJOIXk3IUeBYYmNCy5dMWfrI+JiixYNw8ppKOlcRwWTXFl0B+i5oGrf95Q==",
"dev": true "dev": true
}, },
"adm-zip": {
"version": "0.4.7",
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.7.tgz",
"integrity": "sha1-hgbCy/HEJs6MjsABdER/1Jtur8E="
},
"ajv": { "ajv": {
"version": "5.3.0", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.3.0.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.3.0.tgz",
@ -952,8 +947,7 @@
"chownr": { "chownr": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz",
"integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=", "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE="
"dev": true
}, },
"chromium-pickle-js": { "chromium-pickle-js": {
"version": "0.2.0", "version": "0.2.0",
@ -2015,6 +2009,14 @@
} }
} }
}, },
"fs-minipass": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz",
"integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==",
"requires": {
"minipass": "2.2.1"
}
},
"fs-readdir-recursive": { "fs-readdir-recursive": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz",
@ -3009,6 +3011,22 @@
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
}, },
"minipass": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.2.1.tgz",
"integrity": "sha512-u1aUllxPJUI07cOqzR7reGmQxmCqlH88uIIsf6XZFEWgw7gXKpJdR+5R9Y3KEDmWYkdIz9wXZs3C0jOPxejk/Q==",
"requires": {
"yallist": "3.0.2"
}
},
"minizlib": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.0.tgz",
"integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==",
"requires": {
"minipass": "2.2.1"
}
},
"mkdirp": { "mkdirp": {
"version": "0.5.1", "version": "0.5.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
@ -3964,6 +3982,18 @@
"nan": "2.7.0", "nan": "2.7.0",
"semver": "5.4.1", "semver": "5.4.1",
"tar": "2.2.1" "tar": "2.2.1"
},
"dependencies": {
"tar": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
"integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=",
"requires": {
"block-stream": "0.0.9",
"fstream": "1.0.11",
"inherits": "2.0.3"
}
}
} }
}, },
"shebang-command": { "shebang-command": {
@ -4936,13 +4966,16 @@
"integrity": "sha1-Kb9hXUqnEhvdiYsi1LP5vE4qoD0=" "integrity": "sha1-Kb9hXUqnEhvdiYsi1LP5vE4qoD0="
}, },
"tar": { "tar": {
"version": "2.2.1", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.0.tgz",
"integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", "integrity": "sha512-gJlTiiErwo96K904FnoYWl+5+FBgS+FimU6GMh66XLdLa55al8+d4jeDfPoGwSNHdtWI5FJP6xurmVqhBuGJpQ==",
"requires": { "requires": {
"block-stream": "0.0.9", "chownr": "1.0.1",
"fstream": "1.0.11", "fs-minipass": "1.2.5",
"inherits": "2.0.3" "minipass": "2.2.1",
"minizlib": "1.1.0",
"mkdirp": "0.5.1",
"yallist": "3.0.2"
} }
}, },
"tar-fs": { "tar-fs": {
@ -5397,6 +5430,11 @@
"integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=",
"dev": true "dev": true
}, },
"yallist": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz",
"integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k="
},
"yargs": { "yargs": {
"version": "10.0.3", "version": "10.0.3",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-10.0.3.tgz", "resolved": "https://registry.npmjs.org/yargs/-/yargs-10.0.3.tgz",

View File

@ -91,6 +91,7 @@
"sqlite3": "^3.1.13", "sqlite3": "^3.1.13",
"string-padding": "^1.0.2", "string-padding": "^1.0.2",
"string-to-stream": "^1.1.0", "string-to-stream": "^1.1.0",
"tar": "^4.4.0",
"tcp-port-used": "^0.1.2", "tcp-port-used": "^0.1.2",
"url-parse": "^1.2.0", "url-parse": "^1.2.0",
"uuid": "^3.1.0", "uuid": "^3.1.0",

View File

@ -7,6 +7,7 @@ const globalStyle = {
itemMarginTop: 10, itemMarginTop: 10,
itemMarginBottom: 10, itemMarginBottom: 10,
backgroundColor: "#ffffff", backgroundColor: "#ffffff",
backgroundColorTransparent: 'rgba(255,255,255,0.9)',
oddBackgroundColor: "#dddddd", oddBackgroundColor: "#dddddd",
color: "#222222", // For regular text color: "#222222", // For regular text
colorError: "red", colorError: "red",

View File

@ -207,18 +207,18 @@ Current translations:
<!-- LOCALE-TABLE-AUTO-GENERATED --> <!-- LOCALE-TABLE-AUTO-GENERATED -->
&nbsp; | Language | Po File | Last translator | Percent done &nbsp; | Language | Po File | Last translator | Percent done
---|---|---|---|--- ---|---|---|---|---
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/es/basque_country.png) | Basque | [eu](https://github.com/laurent22/joplin/blob/master/CliClient/locales/eu.po) | juan.abasolo@ehu.eus | 87% ![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/es/basque_country.png) | Basque | [eu](https://github.com/laurent22/joplin/blob/master/CliClient/locales/eu.po) | juan.abasolo@ehu.eus | 82%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/hr.png) | Croatian | [hr_HR](https://github.com/laurent22/joplin/blob/master/CliClient/locales/hr_HR.po) | Hrvoje Mandić <trbuhom@net.hr> | 71% ![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/hr.png) | Croatian | [hr_HR](https://github.com/laurent22/joplin/blob/master/CliClient/locales/hr_HR.po) | Hrvoje Mandić <trbuhom@net.hr> | 66%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/de.png) | Deutsch | [de_DE](https://github.com/laurent22/joplin/blob/master/CliClient/locales/de_DE.po) | Tobias Strobel <git@strobeltobias.de> | 89% ![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/de.png) | Deutsch | [de_DE](https://github.com/laurent22/joplin/blob/master/CliClient/locales/de_DE.po) | Tobias Strobel <git@strobeltobias.de> | 84%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/gb.png) | English | [en_GB](https://github.com/laurent22/joplin/blob/master/CliClient/locales/en_GB.po) | | 100% ![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/gb.png) | English | [en_GB](https://github.com/laurent22/joplin/blob/master/CliClient/locales/en_GB.po) | | 100%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/es.png) | Español | [es_ES](https://github.com/laurent22/joplin/blob/master/CliClient/locales/es_ES.po) | Fernando Martín <f@mrtn.es> | 100% ![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/es.png) | Español | [es_ES](https://github.com/laurent22/joplin/blob/master/CliClient/locales/es_ES.po) | Fernando Martín <f@mrtn.es> | 94%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/fr.png) | Français | [fr_FR](https://github.com/laurent22/joplin/blob/master/CliClient/locales/fr_FR.po) | Laurent Cozic | 100% ![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/fr.png) | Français | [fr_FR](https://github.com/laurent22/joplin/blob/master/CliClient/locales/fr_FR.po) | Laurent Cozic | 94%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/it.png) | Italiano | [it_IT](https://github.com/laurent22/joplin/blob/master/CliClient/locales/it_IT.po) | | 73% ![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/it.png) | Italiano | [it_IT](https://github.com/laurent22/joplin/blob/master/CliClient/locales/it_IT.po) | | 68%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/be.png) | Nederlands | [nl_BE](https://github.com/laurent22/joplin/blob/master/CliClient/locales/nl_BE.po) | | 87% ![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/be.png) | Nederlands | [nl_BE](https://github.com/laurent22/joplin/blob/master/CliClient/locales/nl_BE.po) | | 82%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/br.png) | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/master/CliClient/locales/pt_BR.po) | | 71% ![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/br.png) | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/master/CliClient/locales/pt_BR.po) | | 67%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/ru.png) | Русский | [ru_RU](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ru_RU.po) | Artyom Karlov <artyom.karlov@gmail.com> | 91% ![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/ru.png) | Русский | [ru_RU](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ru_RU.po) | Artyom Karlov <artyom.karlov@gmail.com> | 86%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/cn.png) | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/master/CliClient/locales/zh_CN.po) | RCJacH <RCJacH@outlook.com> | 73% ![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/cn.png) | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/master/CliClient/locales/zh_CN.po) | RCJacH <RCJacH@outlook.com> | 68%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/jp.png) | 日本語 | [ja_JP](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ja_JP.po) | | 71% ![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/jp.png) | 日本語 | [ja_JP](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ja_JP.po) | | 66%
<!-- LOCALE-TABLE-AUTO-GENERATED --> <!-- LOCALE-TABLE-AUTO-GENERATED -->
# Known bugs # Known bugs

View File

@ -274,81 +274,91 @@ The following commands are available in [command-line mode](#command-line-mode):
Possible keys/values: Possible keys/values:
editor Text editor. editor Text editor.
The editor that will be used to open a note. If The editor that will be used to open a note. If
none is provided it will try to auto-detect the none is provided it will try to auto-detect the
default editor. default editor.
Type: string. Type: string.
locale Language. locale Language.
Type: Enum. Type: Enum.
Possible values: en_GB (English), de_DE (Deutsch), Possible values: eu (Basque), hr_HR (Croatian),
es_CR (Español (Costa Rica)), es_ES (Español), eu de_DE (Deutsch), en_GB (English), es_ES
(Basque), fr_FR (Français), hr_HR (Croatian), it_IT (Español), fr_FR (Français), it_IT (Italiano),
(Italiano), ja_JP (日本語), nl_BE (Nederlands), pt_BR nl_BE (Nederlands), pt_BR (Português (Brasil)),
(Português (Brasil)), ru_RU (Русский), zh_CN (中文 ru_RU (Русский), zh_CN (中文 (简体)), ja_JP (日本語).
(简体)). Default: "en_GB"
Default: "en_GB"
dateFormat Date format. dateFormat Date format.
Type: Enum. Type: Enum.
Possible values: DD/MM/YYYY (30/01/2017), DD/MM/YY Possible values: DD/MM/YYYY (30/01/2017),
(30/01/17), MM/DD/YYYY (01/30/2017), MM/DD/YY DD/MM/YY (30/01/17), MM/DD/YYYY (01/30/2017),
(01/30/17), YYYY-MM-DD (2017-01-30). MM/DD/YY (01/30/17), YYYY-MM-DD (2017-01-30).
Default: "DD/MM/YYYY" Default: "DD/MM/YYYY"
timeFormat Time format. timeFormat Time format.
Type: Enum. Type: Enum.
Possible values: HH:mm (20:30), h:mm A (8:30 PM). Possible values: HH:mm (20:30), h:mm A (8:30 PM).
Default: "HH:mm" Default: "HH:mm"
uncompletedTodosOnTop Show uncompleted to-dos on top of the lists. uncompletedTodosOnTop Uncompleted to-dos on top.
Type: bool. Type: bool.
Default: true Default: true
trackLocation Save geo-location with notes. notes.sortOrder.field Sort notes by.
Type: bool. Type: Enum.
Default: true Possible values: user_updated_time (Updated
date), user_created_time (Created date), title
(Title).
Default: "user_updated_time"
sync.interval Synchronisation interval. notes.sortOrder.reverse Reverse sort order.
Type: Enum. Type: bool.
Possible values: 0 (Disabled), 300 (5 minutes), 600 Default: true
(10 minutes), 1800 (30 minutes), 3600 (1 hour),
43200 (12 hours), 86400 (24 hours).
Default: 300
sync.target Synchronisation target. trackLocation Save geo-location with notes.
The target to synchonise to. Each sync target may Type: bool.
have additional parameters which are named as Default: true
`sync.NUM.NAME` (all documented below).
Type: Enum.
Possible values: 2 (File system), 3 (OneDrive), 4
(OneDrive Dev (For testing only)), 5 (Nextcloud), 6
(WebDAV).
Default: 3
sync.2.path Directory to synchronise with (absolute path). sync.interval Synchronisation interval.
The path to synchronise with when file system Type: Enum.
synchronisation is enabled. See `sync.target`. Possible values: 0 (Disabled), 300 (5 minutes),
Type: string. 600 (10 minutes), 1800 (30 minutes), 3600 (1
hour), 43200 (12 hours), 86400 (24 hours).
Default: 300
sync.5.path Nextcloud WebDAV URL. sync.target Synchronisation target.
Type: string. The target to synchonise to. Each sync target may
have additional parameters which are named as
`sync.NUM.NAME` (all documented below).
Type: Enum.
Possible values: 2 (File system), 3 (OneDrive), 4
(OneDrive Dev (For testing only)), 5 (Nextcloud),
6 (WebDAV).
Default: 3
sync.5.username Nextcloud username. sync.2.path Directory to synchronise with (absolute path).
Type: string. The path to synchronise with when file system
synchronisation is enabled. See `sync.target`.
Type: string.
sync.5.password Nextcloud password. sync.5.path Nextcloud WebDAV URL.
Type: string. Type: string.
sync.6.path WebDAV URL. sync.5.username Nextcloud username.
Type: string. Type: string.
sync.6.username WebDAV username. sync.5.password Nextcloud password.
Type: string. Type: string.
sync.6.password WebDAV password. sync.6.path WebDAV URL.
Type: string. Type: string.
sync.6.username WebDAV username.
Type: string.
sync.6.password WebDAV password.
Type: string.
cp <note> [notebook] cp <note> [notebook]
@ -374,11 +384,13 @@ The following commands are available in [command-line mode](#command-line-mode):
Edit note. Edit note.
export <directory> export <path>
Exports Joplin data to the given directory. By default, it will export the Exports Joplin data to the given path. By default, it will export the
complete database including notebooks, notes, tags and resources. complete database including notebooks, notes, tags and resources.
--format <format> Destination format: jex (Joplin Export File), raw
(Joplin Export Directory)
--note <note> Exports only the given note. --note <note> Exports only the given note.
--notebook <notebook> Exports only the given notebook. --notebook <notebook> Exports only the given notebook.
@ -390,11 +402,12 @@ The following commands are available in [command-line mode](#command-line-mode):
Displays usage information. Displays usage information.
import-enex <file> [notebook] import <path> [notebook]
Imports an Evernote notebook file (.enex file). Imports data into Joplin.
-f, --force Do not ask for confirmation. --format <format> Source format: auto, jex, md, raw, enex
-f, --force Do not ask for confirmation.
mkbook <new-notebook> mkbook <new-notebook>

View File

@ -7,34 +7,111 @@ const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js'); const Tag = require('lib/models/Tag.js');
const { basename, filename } = require('lib/path-utils.js'); const { basename, filename } = require('lib/path-utils.js');
const fs = require('fs-extra'); const fs = require('fs-extra');
const md5 = require('md5');
const ArrayUtils = require('lib/ArrayUtils'); const ArrayUtils = require('lib/ArrayUtils');
const { sprintf } = require('sprintf-js'); const { sprintf } = require('sprintf-js');
const { shim } = require('lib/shim'); const { shim } = require('lib/shim');
const { _ } = require('lib/locale'); const { _ } = require('lib/locale');
const { fileExtension } = require('lib/path-utils'); const { fileExtension } = require('lib/path-utils');
const { uuid } = require('lib/uuid.js'); const { uuid } = require('lib/uuid.js');
const { importEnex } = require('lib/import-enex');
const { toTitleCase } = require('lib/string-utils'); const { toTitleCase } = require('lib/string-utils');
class InteropService { class InteropService {
newImportExportModule_(format, className) { constructor() {
try { this.modules_ = null;
const FormatClass = require('lib/services/' + className); }
return new FormatClass();
} catch (error) { modules() {
error.message = _('Cannot load module for format "%s": %s', format, error.message); if (this.modules_) return this.modules_;
throw error;
let importModules = [
{
format: 'jex',
fileExtension: 'jex',
sources: ['file'],
description: _('Joplin Export File'),
}, {
format: 'md',
fileExtension: 'md',
sources: ['file', 'directory'],
isNoteArchive: false, // Tells whether the file can contain multiple notes (eg. Enex or Jex format)
description: _('Markdown'),
}, {
format: 'raw',
sources: ['directory'],
description: _('Joplin Export Directory'),
}, {
format: 'enex',
fileExtension: 'enex',
sources: ['file'],
description: _('Evernote Export File'),
},
];
let exportModules = [
{
format: 'jex',
fileExtension: 'jex',
target: 'file',
description: _('Joplin Export File'),
}, {
format: 'raw',
target: 'directory',
description: _('Joplin Export Directory'),
},
];
importModules = importModules.map((a) => {
const className = 'InteropService_Importer_' + toTitleCase(a.format);
const output = Object.assign({}, {
type: 'importer',
path: 'lib/services/' + className,
}, a);
if (!('isNoteArchive' in output)) output.isNoteArchive = true;
return output;
});
exportModules = exportModules.map((a) => {
const className = 'InteropService_Exporter_' + toTitleCase(a.format);
return Object.assign({}, {
type: 'exporter',
path: 'lib/services/' + className,
}, a);
});
this.modules_ = importModules.concat(exportModules);
return this.modules_;
}
moduleByFormat_(type, format) {
const modules = this.modules();
for (let i = 0; i < modules.length; i++) {
const m = modules[i];
if (m.format === format && m.type === type) return modules[i];
} }
return null;
} }
newExporter_(format) { newModule_(type, format) {
return this.newImportExportModule_(format, 'InteropService_Exporter_' + toTitleCase(format)); const module = this.moduleByFormat_(type, format);
if (!module) throw new Error(_('Cannot load "%s" module for format "%s"', type, format));
const ModuleClass = require(module.path);
return new ModuleClass();
} }
newImporter_(format) { moduleByFileExtension_(type, ext) {
return this.newImportExportModule_(format, 'InteropService_Importer_' + toTitleCase(format)); ext = ext.toLowerCase();
const modules = this.modules();
for (let i = 0; i < modules.length; i++) {
const m = modules[i];
if (type !== m.type) continue;
if (m.fileExtension === ext) return m;
}
return null;
} }
async import(options) { async import(options) {
@ -47,25 +124,20 @@ class InteropService {
}, options); }, options);
if (options.format === 'auto') { if (options.format === 'auto') {
const ext = fileExtension(options.path).toLowerCase(); const module = this.moduleByFileExtension_('importer', fileExtension(options.path));
if (ext === 'jex') { if (!module) throw new Error(_('Please specify import format for %s', options.path));
options.format = 'jex'; options.format = module.format;
} else if (ext === 'enex') {
options.format = 'enex';
} else {
throw new Error('Cannot automatically detect source format from path: ' + options.path);
}
} }
if (options.destinationFolderId) { if (options.destinationFolderId) {
const folder = await Folder.load(options.destinationFolderId); const folder = await Folder.load(options.destinationFolderId);
if (!folder) throw new Error('Notebook not found: ' + options.destinationFolderId); if (!folder) throw new Error(_('Cannot find "%s".', options.destinationFolderId));
options.destinationFolder = folder; options.destinationFolder = folder;
} }
let result = { warnings: [] } let result = { warnings: [] }
const importer = this.newImporter_(options.format); const importer = this.newModule_('importer', options.format);
await importer.init(options.path, options); await importer.init(options.path, options);
result = await importer.exec(result); result = await importer.exec(result);
@ -132,7 +204,7 @@ class InteropService {
await queueExportItem(BaseModel.TYPE_TAG, exportedTagIds[i]); await queueExportItem(BaseModel.TYPE_TAG, exportedTagIds[i]);
} }
const exporter = this.newExporter_(exportFormat); const exporter = this.newModule_('exporter', exportFormat);
await exporter.init(exportPath); await exporter.init(exportPath);
for (let i = 0; i < itemsToExport.length; i++) { for (let i = 0; i < itemsToExport.length; i++) {

View File

@ -54,11 +54,4 @@ class InteropService_Exporter_Jex extends InteropService_Exporter_Base {
} }
InteropService_Exporter_Jex.metadata = function() {
return {
format: 'jex',
fileExtension: 'jex',
};
}
module.exports = InteropService_Exporter_Jex; module.exports = InteropService_Exporter_Jex;

View File

@ -27,10 +27,4 @@ class InteropService_Exporter_Raw extends InteropService_Exporter_Base {
} }
InteropService_Exporter_Raw.metadata = function() {
return {
format: 'raw',
};
}
module.exports = InteropService_Exporter_Raw; module.exports = InteropService_Exporter_Raw;

View File

@ -14,11 +14,12 @@ const { shim } = require('lib/shim');
const { _ } = require('lib/locale'); const { _ } = require('lib/locale');
const { fileExtension } = require('lib/path-utils'); const { fileExtension } = require('lib/path-utils');
const { uuid } = require('lib/uuid.js'); const { uuid } = require('lib/uuid.js');
const { importEnex } = require('lib/import-enex');
class InteropService_Importer_Enex extends InteropService_Importer_Base { class InteropService_Importer_Enex extends InteropService_Importer_Base {
async exec(result) { async exec(result) {
const { importEnex } = require('lib/import-enex');
let folder = this.options_.destinationFolder; let folder = this.options_.destinationFolder;
if (!folder) { if (!folder) {

View File

@ -47,11 +47,4 @@ class InteropService_Importer_Jex extends InteropService_Importer_Base {
} }
InteropService_Importer_Jex.metadata = function() {
return {
format: 'jex',
fileExtension: 'jex',
};
}
module.exports = InteropService_Importer_Jex; module.exports = InteropService_Importer_Jex;

View File

@ -6,7 +6,7 @@ const Folder = require('lib/models/Folder.js');
const NoteTag = require('lib/models/NoteTag.js'); const NoteTag = require('lib/models/NoteTag.js');
const Note = require('lib/models/Note.js'); const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js'); const Tag = require('lib/models/Tag.js');
const { basename, filename } = require('lib/path-utils.js'); const { basename, filename, rtrimSlashes } = require('lib/path-utils.js');
const fs = require('fs-extra'); const fs = require('fs-extra');
const md5 = require('md5'); const md5 = require('md5');
const { sprintf } = require('sprintf-js'); const { sprintf } = require('sprintf-js');
@ -19,9 +19,7 @@ const { importEnex } = require('lib/import-enex');
class InteropService_Importer_Md extends InteropService_Importer_Base { class InteropService_Importer_Md extends InteropService_Importer_Base {
async exec(result) { async exec(result) {
if (!this.options_.destinationFolder) throw new Error(_('Please specify the notebook where the notes should be imported to.')); let parentFolderId = null;
const parentFolderId = this.options_.destinationFolder.id;
const filePaths = []; const filePaths = [];
if (await shim.fsDriver().isDirectory(this.sourcePath_)) { if (await shim.fsDriver().isDirectory(this.sourcePath_)) {
@ -32,7 +30,17 @@ class InteropService_Importer_Md extends InteropService_Importer_Base {
filePaths.push(this.sourcePath_ + '/' + stat.path); filePaths.push(this.sourcePath_ + '/' + stat.path);
} }
} }
if (!this.options_.destinationFolder) {
const folderTitle = await Folder.findUniqueFolderTitle(basename(rtrimSlashes(this.sourcePath_)));
const folder = await Folder.save({ title: folderTitle });
parentFolderId = folder.id;
} else {
parentFolderId = this.options_.destinationFolder.id;
}
} else { } else {
if (!this.options_.destinationFolder) throw new Error(_('Please specify the notebook where the notes should be imported to.'));
parentFolderId = this.options_.destinationFolder.id
filePaths.push(this.sourcePath_); filePaths.push(this.sourcePath_);
} }
@ -59,11 +67,4 @@ class InteropService_Importer_Md extends InteropService_Importer_Base {
} }
InteropService_Importer_Md.metadata = function() {
return {
format: 'md',
fileExtension: 'md',
};
}
module.exports = InteropService_Importer_Md; module.exports = InteropService_Importer_Md;

View File

@ -110,30 +110,27 @@ class InteropService_Importer_Raw extends InteropService_Importer_Base {
await NoteTag.save(noteTag, { isNew: true }); await NoteTag.save(noteTag, { isNew: true });
} }
const resourceStats = await shim.fsDriver().readDirStats(this.sourcePath_ + '/resources'); if (await shim.fsDriver().isDirectory(this.sourcePath_ + '/resources')) {
const resourceStats = await shim.fsDriver().readDirStats(this.sourcePath_ + '/resources');
for (let i = 0; i < resourceStats.length; i++) { for (let i = 0; i < resourceStats.length; i++) {
const resourceFilePath = this.sourcePath_ + '/resources/' + resourceStats[i].path; const resourceFilePath = this.sourcePath_ + '/resources/' + resourceStats[i].path;
const oldId = Resource.pathToId(resourceFilePath); const oldId = Resource.pathToId(resourceFilePath);
const newId = resourceIdMap[oldId]; const newId = resourceIdMap[oldId];
if (!newId) { if (!newId) {
result.warnings.push(sprintf('Resource file is not referenced in any note and so was not imported: %s', oldId)); result.warnings.push(sprintf('Resource file is not referenced in any note and so was not imported: %s', oldId));
continue; continue;
}
const resource = createdResources[newId];
const destPath = Resource.fullPath(resource);
await shim.fsDriver().copy(resourceFilePath, destPath);
} }
const resource = createdResources[newId];
const destPath = Resource.fullPath(resource);
await shim.fsDriver().copy(resourceFilePath, destPath);
} }
return result; return result;
} }
} }
InteropService_Importer_Raw.metadata = function() {
return {
format: 'raw',
};
}
module.exports = InteropService_Importer_Raw; module.exports = InteropService_Importer_Raw;

View File

@ -18,9 +18,9 @@
} }
}, },
"fs-extra": { "fs-extra": {
"version": "4.0.2", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.2.tgz", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz",
"integrity": "sha1-+RcExT0bRh+JNFKwwwfZmXZHq2s=", "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==",
"requires": { "requires": {
"graceful-fs": "4.1.11", "graceful-fs": "4.1.11",
"jsonfile": "4.0.0", "jsonfile": "4.0.0",
@ -88,6 +88,11 @@
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
}, },
"string-padding": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-padding/-/string-padding-1.0.2.tgz",
"integrity": "sha1-OqrYVbPpc1xeQS3+chmMz5nH9I4="
},
"universalify": { "universalify": {
"version": "0.1.1", "version": "0.1.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz",

View File

@ -10,11 +10,12 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"app-module-path": "^2.2.0", "app-module-path": "^2.2.0",
"fs-extra": "^4.0.2", "fs-extra": "^4.0.3",
"gettext-parser": "^1.3.0", "gettext-parser": "^1.3.0",
"marked": "^0.3.7", "marked": "^0.3.7",
"mustache": "^2.3.0", "mustache": "^2.3.0",
"node-fetch": "^1.7.3", "node-fetch": "^1.7.3",
"string-padding": "^1.0.2",
"uri-template": "^1.0.1" "uri-template": "^1.0.1"
} }
} }

View File

@ -392,21 +392,21 @@ $$
<td>Basque</td> <td>Basque</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/eu.po">eu</a></td> <td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/eu.po">eu</a></td>
<td>juan.abasolo@ehu.eus</td> <td>juan.abasolo@ehu.eus</td>
<td>87%</td> <td>82%</td>
</tr> </tr>
<tr> <tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/hr.png" alt=""></td> <td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/hr.png" alt=""></td>
<td>Croatian</td> <td>Croatian</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/hr_HR.po">hr_HR</a></td> <td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/hr_HR.po">hr_HR</a></td>
<td>Hrvoje Mandić <a href="&#x6d;&#97;&#x69;&#x6c;&#x74;&#x6f;&#x3a;&#116;&#x72;&#x62;&#117;&#104;&#x6f;&#x6d;&#64;&#x6e;&#x65;&#x74;&#46;&#x68;&#x72;">&#116;&#x72;&#x62;&#117;&#104;&#x6f;&#x6d;&#64;&#x6e;&#x65;&#x74;&#46;&#x68;&#x72;</a></td> <td>Hrvoje Mandić <a href="&#109;&#x61;&#x69;&#x6c;&#x74;&#x6f;&#58;&#x74;&#x72;&#98;&#117;&#104;&#111;&#x6d;&#64;&#x6e;&#101;&#x74;&#x2e;&#104;&#x72;">&#x74;&#x72;&#98;&#117;&#104;&#111;&#x6d;&#64;&#x6e;&#101;&#x74;&#x2e;&#104;&#x72;</a></td>
<td>71%</td> <td>66%</td>
</tr> </tr>
<tr> <tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/de.png" alt=""></td> <td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/de.png" alt=""></td>
<td>Deutsch</td> <td>Deutsch</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/de_DE.po">de_DE</a></td> <td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/de_DE.po">de_DE</a></td>
<td>Tobias Strobel <a href="&#x6d;&#97;&#105;&#x6c;&#x74;&#111;&#x3a;&#103;&#x69;&#116;&#x40;&#115;&#116;&#114;&#111;&#98;&#x65;&#108;&#116;&#x6f;&#98;&#105;&#97;&#x73;&#46;&#100;&#101;">&#103;&#x69;&#116;&#x40;&#115;&#116;&#114;&#111;&#98;&#x65;&#108;&#116;&#x6f;&#98;&#105;&#97;&#x73;&#46;&#100;&#101;</a></td> <td>Tobias Strobel <a href="&#x6d;&#97;&#x69;&#108;&#116;&#111;&#58;&#x67;&#x69;&#x74;&#64;&#115;&#x74;&#114;&#x6f;&#98;&#101;&#x6c;&#116;&#x6f;&#x62;&#x69;&#97;&#x73;&#46;&#x64;&#x65;">&#x67;&#x69;&#x74;&#64;&#115;&#x74;&#114;&#x6f;&#98;&#101;&#x6c;&#116;&#x6f;&#x62;&#x69;&#97;&#x73;&#46;&#x64;&#x65;</a></td>
<td>89%</td> <td>84%</td>
</tr> </tr>
<tr> <tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/gb.png" alt=""></td> <td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/gb.png" alt=""></td>
@ -419,57 +419,57 @@ $$
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/es.png" alt=""></td> <td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/es.png" alt=""></td>
<td>Español</td> <td>Español</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/es_ES.po">es_ES</a></td> <td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/es_ES.po">es_ES</a></td>
<td>Fernando Martín <a href="&#109;&#x61;&#x69;&#108;&#x74;&#x6f;&#58;&#x66;&#x40;&#x6d;&#114;&#116;&#x6e;&#x2e;&#101;&#115;">&#x66;&#x40;&#x6d;&#114;&#116;&#x6e;&#x2e;&#101;&#115;</a></td> <td>Fernando Martín <a href="&#x6d;&#97;&#x69;&#108;&#116;&#111;&#58;&#102;&#64;&#x6d;&#x72;&#x74;&#x6e;&#x2e;&#101;&#115;">&#102;&#64;&#x6d;&#x72;&#x74;&#x6e;&#x2e;&#101;&#115;</a></td>
<td>100%</td> <td>94%</td>
</tr> </tr>
<tr> <tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/fr.png" alt=""></td> <td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/fr.png" alt=""></td>
<td>Français</td> <td>Français</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/fr_FR.po">fr_FR</a></td> <td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/fr_FR.po">fr_FR</a></td>
<td>Laurent Cozic</td> <td>Laurent Cozic</td>
<td>100%</td> <td>94%</td>
</tr> </tr>
<tr> <tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/it.png" alt=""></td> <td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/it.png" alt=""></td>
<td>Italiano</td> <td>Italiano</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/it_IT.po">it_IT</a></td> <td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/it_IT.po">it_IT</a></td>
<td></td> <td></td>
<td>73%</td> <td>68%</td>
</tr> </tr>
<tr> <tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/be.png" alt=""></td> <td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/be.png" alt=""></td>
<td>Nederlands</td> <td>Nederlands</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/nl_BE.po">nl_BE</a></td> <td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/nl_BE.po">nl_BE</a></td>
<td></td> <td></td>
<td>87%</td> <td>82%</td>
</tr> </tr>
<tr> <tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/br.png" alt=""></td> <td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/br.png" alt=""></td>
<td>Português (Brasil)</td> <td>Português (Brasil)</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/pt_BR.po">pt_BR</a></td> <td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/pt_BR.po">pt_BR</a></td>
<td></td> <td></td>
<td>71%</td> <td>67%</td>
</tr> </tr>
<tr> <tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/ru.png" alt=""></td> <td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/ru.png" alt=""></td>
<td>Русский</td> <td>Русский</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/ru_RU.po">ru_RU</a></td> <td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/ru_RU.po">ru_RU</a></td>
<td>Artyom Karlov <a href="&#109;&#97;&#105;&#108;&#116;&#111;&#x3a;&#x61;&#x72;&#x74;&#x79;&#111;&#x6d;&#x2e;&#x6b;&#x61;&#x72;&#x6c;&#x6f;&#x76;&#x40;&#103;&#x6d;&#97;&#105;&#108;&#x2e;&#x63;&#x6f;&#109;">&#x61;&#x72;&#x74;&#x79;&#111;&#x6d;&#x2e;&#x6b;&#x61;&#x72;&#x6c;&#x6f;&#x76;&#x40;&#103;&#x6d;&#97;&#105;&#108;&#x2e;&#x63;&#x6f;&#109;</a></td> <td>Artyom Karlov <a href="&#109;&#x61;&#105;&#108;&#x74;&#x6f;&#58;&#x61;&#114;&#116;&#121;&#x6f;&#109;&#x2e;&#107;&#x61;&#x72;&#x6c;&#x6f;&#118;&#64;&#103;&#109;&#x61;&#105;&#x6c;&#46;&#x63;&#x6f;&#109;">&#x61;&#114;&#116;&#121;&#x6f;&#109;&#x2e;&#107;&#x61;&#x72;&#x6c;&#x6f;&#118;&#64;&#103;&#109;&#x61;&#105;&#x6c;&#46;&#x63;&#x6f;&#109;</a></td>
<td>91%</td> <td>86%</td>
</tr> </tr>
<tr> <tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/cn.png" alt=""></td> <td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/cn.png" alt=""></td>
<td>中文 (简体)</td> <td>中文 (简体)</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/zh_CN.po">zh_CN</a></td> <td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/zh_CN.po">zh_CN</a></td>
<td>RCJacH <a href="&#x6d;&#x61;&#x69;&#x6c;&#x74;&#x6f;&#58;&#x52;&#x43;&#74;&#97;&#99;&#72;&#64;&#x6f;&#x75;&#x74;&#x6c;&#111;&#111;&#107;&#46;&#99;&#x6f;&#x6d;">&#x52;&#x43;&#74;&#97;&#99;&#72;&#64;&#x6f;&#x75;&#x74;&#x6c;&#111;&#111;&#107;&#46;&#99;&#x6f;&#x6d;</a></td> <td>RCJacH <a href="&#x6d;&#x61;&#x69;&#108;&#116;&#111;&#x3a;&#82;&#x43;&#74;&#97;&#99;&#72;&#64;&#x6f;&#117;&#116;&#x6c;&#111;&#x6f;&#x6b;&#46;&#x63;&#x6f;&#x6d;">&#82;&#x43;&#74;&#97;&#99;&#72;&#64;&#x6f;&#117;&#116;&#x6c;&#111;&#x6f;&#x6b;&#46;&#x63;&#x6f;&#x6d;</a></td>
<td>73%</td> <td>68%</td>
</tr> </tr>
<tr> <tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/jp.png" alt=""></td> <td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/jp.png" alt=""></td>
<td>日本語</td> <td>日本語</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/ja_JP.po">ja_JP</a></td> <td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/ja_JP.po">ja_JP</a></td>
<td></td> <td></td>
<td>71%</td> <td>66%</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@ -482,81 +482,91 @@ config [name] [value]
Possible keys/values: Possible keys/values:
editor Text editor. editor Text editor.
The editor that will be used to open a note. If The editor that will be used to open a note. If
none is provided it will try to auto-detect the none is provided it will try to auto-detect the
default editor. default editor.
Type: string. Type: string.
locale Language. locale Language.
Type: Enum. Type: Enum.
Possible values: en_GB (English), de_DE (Deutsch), Possible values: eu (Basque), hr_HR (Croatian),
es_CR (Español (Costa Rica)), es_ES (Español), eu de_DE (Deutsch), en_GB (English), es_ES
(Basque), fr_FR (Français), hr_HR (Croatian), it_IT (Español), fr_FR (Français), it_IT (Italiano),
(Italiano), ja_JP (日本語), nl_BE (Nederlands), pt_BR nl_BE (Nederlands), pt_BR (Português (Brasil)),
(Português (Brasil)), ru_RU (Русский), zh_CN (中文 ru_RU (Русский), zh_CN (中文 (简体)), ja_JP (日本語).
(简体)). Default: &quot;en_GB&quot;
Default: &quot;en_GB&quot;
dateFormat Date format. dateFormat Date format.
Type: Enum. Type: Enum.
Possible values: DD/MM/YYYY (30/01/2017), DD/MM/YY Possible values: DD/MM/YYYY (30/01/2017),
(30/01/17), MM/DD/YYYY (01/30/2017), MM/DD/YY DD/MM/YY (30/01/17), MM/DD/YYYY (01/30/2017),
(01/30/17), YYYY-MM-DD (2017-01-30). MM/DD/YY (01/30/17), YYYY-MM-DD (2017-01-30).
Default: &quot;DD/MM/YYYY&quot; Default: &quot;DD/MM/YYYY&quot;
timeFormat Time format. timeFormat Time format.
Type: Enum. Type: Enum.
Possible values: HH:mm (20:30), h:mm A (8:30 PM). Possible values: HH:mm (20:30), h:mm A (8:30 PM).
Default: &quot;HH:mm&quot; Default: &quot;HH:mm&quot;
uncompletedTodosOnTop Show uncompleted to-dos on top of the lists. uncompletedTodosOnTop Uncompleted to-dos on top.
Type: bool. Type: bool.
Default: true Default: true
trackLocation Save geo-location with notes. notes.sortOrder.field Sort notes by.
Type: bool. Type: Enum.
Default: true Possible values: user_updated_time (Updated
date), user_created_time (Created date), title
(Title).
Default: &quot;user_updated_time&quot;
sync.interval Synchronisation interval. notes.sortOrder.reverse Reverse sort order.
Type: Enum. Type: bool.
Possible values: 0 (Disabled), 300 (5 minutes), 600 Default: true
(10 minutes), 1800 (30 minutes), 3600 (1 hour),
43200 (12 hours), 86400 (24 hours).
Default: 300
sync.target Synchronisation target. trackLocation Save geo-location with notes.
The target to synchonise to. Each sync target may Type: bool.
have additional parameters which are named as Default: true
`sync.NUM.NAME` (all documented below).
Type: Enum.
Possible values: 2 (File system), 3 (OneDrive), 4
(OneDrive Dev (For testing only)), 5 (Nextcloud), 6
(WebDAV).
Default: 3
sync.2.path Directory to synchronise with (absolute path). sync.interval Synchronisation interval.
The path to synchronise with when file system Type: Enum.
synchronisation is enabled. See `sync.target`. Possible values: 0 (Disabled), 300 (5 minutes),
Type: string. 600 (10 minutes), 1800 (30 minutes), 3600 (1
hour), 43200 (12 hours), 86400 (24 hours).
Default: 300
sync.5.path Nextcloud WebDAV URL. sync.target Synchronisation target.
Type: string. The target to synchonise to. Each sync target may
have additional parameters which are named as
`sync.NUM.NAME` (all documented below).
Type: Enum.
Possible values: 2 (File system), 3 (OneDrive), 4
(OneDrive Dev (For testing only)), 5 (Nextcloud),
6 (WebDAV).
Default: 3
sync.5.username Nextcloud username. sync.2.path Directory to synchronise with (absolute path).
Type: string. The path to synchronise with when file system
synchronisation is enabled. See `sync.target`.
Type: string.
sync.5.password Nextcloud password. sync.5.path Nextcloud WebDAV URL.
Type: string. Type: string.
sync.6.path WebDAV URL. sync.5.username Nextcloud username.
Type: string. Type: string.
sync.6.username WebDAV username. sync.5.password Nextcloud password.
Type: string. Type: string.
sync.6.password WebDAV password. sync.6.path WebDAV URL.
Type: string. Type: string.
sync.6.username WebDAV username.
Type: string.
sync.6.password WebDAV password.
Type: string.
cp &lt;note&gt; [notebook] cp &lt;note&gt; [notebook]
@ -582,11 +592,13 @@ edit &lt;note&gt;
Edit note. Edit note.
export &lt;directory&gt; export &lt;path&gt;
Exports Joplin data to the given directory. By default, it will export the Exports Joplin data to the given path. By default, it will export the
complete database including notebooks, notes, tags and resources. complete database including notebooks, notes, tags and resources.
--format &lt;format&gt; Destination format: jex (Joplin Export File), raw
(Joplin Export Directory)
--note &lt;note&gt; Exports only the given note. --note &lt;note&gt; Exports only the given note.
--notebook &lt;notebook&gt; Exports only the given notebook. --notebook &lt;notebook&gt; Exports only the given notebook.
@ -598,11 +610,12 @@ help [command]
Displays usage information. Displays usage information.
import-enex &lt;file&gt; [notebook] import &lt;path&gt; [notebook]
Imports an Evernote notebook file (.enex file). Imports data into Joplin.
-f, --force Do not ask for confirmation. --format &lt;format&gt; Source format: auto, jex, md, raw, enex
-f, --force Do not ask for confirmation.
mkbook &lt;new-notebook&gt; mkbook &lt;new-notebook&gt;