1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-08-27 20:29:45 +02:00

Compare commits

...

54 Commits

Author SHA1 Message Date
Laurent Cozic
7a985c2c8a Android release v1.0.103 2018-03-02 18:15:09 +00:00
Laurent Cozic
b11ad30a31 Updated translations 2018-03-02 18:12:58 +00:00
Laurent Cozic
5914fc97df Merge branch 'master' of github.com:laurent22/joplin 2018-03-02 18:03:28 +00:00
Laurent Cozic
e41ae1832d Minor tweaks 2018-03-02 18:24:02 +00:00
Laurent Cozic
89b50909ed Electron: Resolves #266: Allow setting text editor font family 2018-03-02 18:16:48 +00:00
Laurent Cozic
edccd7412f Merge pull request #265 from jaredcrowe/docs-correct-checkbox-syntax
correct the documentation about adding checkboxes
2018-03-02 17:59:33 +00:00
Jared Crowe
c76beae057 correct the documentation about adding checkboxes 2018-03-02 09:30:27 +11:00
Laurent Cozic
23c5934a7d Electron: Allow exporting only selected notes or notebook 2018-03-01 20:14:06 +00:00
Laurent Cozic
a078947d6d Allow importing and exporting single notes and notebooks 2018-03-01 18:35:17 +00:00
Laurent Cozic
5ba98b4200 CLI v1.0.100 2018-02-28 21:18:27 +00:00
Laurent Cozic
c36513b99d Updated translation 2018-02-28 20:46:15 +00:00
Laurent Cozic
97814531fa Update readme downloads 2018-02-28 20:06:06 +00:00
Laurent Cozic
fd3e335a02 Electron release v1.0.70 2018-02-28 18:57:10 +00:00
Laurent Cozic
e676fa2b57 Trying to fix Electron builder 2018-02-28 18:56:57 +00:00
Laurent Cozic
122cbbf673 Electron release v1.0.69 2018-02-28 18:43:12 +00:00
Laurent Cozic
271793b324 Trying to upgrade Electron Builder 2018-02-28 18:43:03 +00:00
Laurent Cozic
134b31933b Electron release v1.0.68 2018-02-28 18:13:02 +00:00
Laurent Cozic
0ec5518a62 Android release v1.0.102 2018-02-28 18:10:33 +00:00
Laurent Cozic
76931370d7 Updated readme for import/export 2018-02-27 22:15:20 +00:00
Laurent Cozic
8cf0e4517a Merge branch 'master' of github.com:laurent22/joplin 2018-02-28 17:27:06 +00:00
Laurent Cozic
e75c62bf0f Electron: Fix logic of hidding app in macOS 2018-02-27 21:54:40 +00:00
Laurent Cozic
058285e0b9 All: Made scheduled sync delay slightly shorter 2018-02-27 21:19:11 +00:00
Laurent Cozic
795568d8c2 Electron: Fixed note sorting bug 2018-02-27 21:11:53 +00:00
Laurent Cozic
df4933fddd All: Prevent export of encrypted items 2018-02-27 20:51:07 +00:00
Laurent Cozic
4046a51472 Electron: Handle import export 2018-02-27 20:04:38 +00:00
Laurent Cozic
45845f645d Refactored Interop service to make export/import code more modular 2018-02-26 19:25:54 +00:00
Laurent Cozic
d7fd8944f7 Moved Enex import to InteropService 2018-02-26 19:16:01 +00:00
Laurent Cozic
3cee671f25 Done MD importer 2018-02-26 18:43:50 +00:00
Laurent Cozic
8f2e5faff3 Support importing JEX and raw data 2018-02-25 17:01:16 +00:00
Laurent Cozic
39ddd934f6 Changed export format extension to JEX and made it a non-compressed tar file 2018-02-25 21:08:40 +00:00
Laurent Cozic
9f8a46b9d9 All: Added more export options including jpz format 2018-02-23 19:32:19 +00:00
Laurent Cozic
c6698eaea6 Electron: Fixes #256: Check that no other instance of Joplin is running before launching a new one 2018-02-23 17:51:23 +00:00
Laurent Cozic
8a96cf3434 All: Allow sorting notes by various fields 2018-02-22 18:58:15 +00:00
Laurent Cozic
74d255c056 Updated readme 2018-02-21 18:19:22 +00:00
Laurent Cozic
71aa841265 Mobile: Fixes #244: When accessing configuration or encrypt configuration option while making a note and the going back, the note gets erased 2018-02-21 22:08:34 +00:00
Laurent Cozic
14a93a9f26 All: Fixed sync interval sorting order 2018-02-21 19:58:28 +00:00
Laurent Cozic
e1fd9c6922 Clean up 2018-02-21 19:20:33 +00:00
Laurent Cozic
b9db747b5c Minor refactoring to make function purpose clearer 2018-02-21 18:36:29 +00:00
Laurent Cozic
4a56c76901 Electron: Fixes #247: Unreadable error messages when checking for updates 2018-02-20 18:07:31 +00:00
Laurent Cozic
6bb3184a72 macOS: Resolves #243: Added black and white tray icon for macOS 2018-02-20 00:41:52 +00:00
Laurent Cozic
7fb8fbd450 iOS: Added compatibility with iPad 2018-02-19 23:41:53 +00:00
Laurent Cozic
9d5bba472e iOS v1.0.13 2018-02-19 23:41:12 +00:00
Laurent Cozic
e6d821a45f CLI v1.0.99 2018-02-19 22:59:09 +00:00
Laurent Cozic
72f0027e21 Electron release v1.0.67 2018-02-19 21:05:43 +00:00
Laurent Cozic
29a13a9943 Android release v1.0.101 2018-02-19 21:04:27 +00:00
Laurent Cozic
3691ae4d13 Electron: Fixes #217: Display a message when the note has no content and only the note viewer is visible 2018-02-19 18:56:56 +00:00
Laurent Cozic
4dda397c29 Removed es_CR translation as it's unfortunately too incomplete and there's already a 100% Spanish translation 2018-02-19 18:44:41 +00:00
Laurent Cozic
b4b058998d Updated French and Español translation 2018-02-19 18:41:39 +00:00
Laurent Cozic
10919e415e Merge branch 'master' of github.com:laurent22/joplin 2018-02-19 18:38:56 +00:00
Laurent Cozic
4966d74864 All: Fixes #240: Tags should be handled in a case-insensitive way 2018-02-19 18:38:27 +00:00
Laurent Cozic
c70ecb30a5 All: Fixes #241: Ignore response for certain calls. Also re-enabled If-None-Match fix. 2018-02-19 18:37:44 +00:00
Laurent Cozic
acc0d17e0f Minor changes 2018-02-19 18:36:31 +00:00
Laurent Cozic
b509b878bf Merge pull request #238 from fmrtn/master
Improved Spanish translations
2018-02-19 18:02:58 +00:00
Fernando
760086307b Improved Spanish translations 2018-02-18 17:15:18 +01:00
126 changed files with 5123 additions and 3006 deletions

View File

@@ -287,6 +287,8 @@ class AppGui {
addCommandToConsole(cmd) {
if (!cmd) return;
const isConfigPassword = cmd.indexOf('config ') >= 0 && cmd.indexOf('password') >= 0;
if (isConfigPassword) return;
this.stdout(chalk.cyan.bold('> ' + cmd));
}

View File

@@ -283,7 +283,7 @@ class Application extends BaseApplication {
exit: () => {},
showModalOverlay: (text) => {},
hideModalOverlay: () => {},
stdoutMaxWidth: () => { return 78; },
stdoutMaxWidth: () => { return 100; },
forceRender: () => {},
termSaveState: () => {},
termRestoreState: (state) => {},
@@ -387,10 +387,11 @@ class Application extends BaseApplication {
await this.execCommand(argv);
} catch (error) {
if (this.showStackTraces_) {
console.info(error);
console.error(error);
} else {
console.info(error.message);
}
process.exit(1);
}
} else { // Otherwise open the GUI
this.initRedux();

View File

@@ -32,10 +32,6 @@ class BaseCommand {
return this.compatibleUis().indexOf(ui) >= 0;
}
aliases() {
return [];
}
options() {
return [];
}

View File

@@ -1,5 +1,5 @@
const { BaseCommand } = require('./base-command.js');
const { Exporter } = require('lib/services/exporter.js');
const InteropService = require('lib/services/InteropService.js');
const BaseModel = require('lib/BaseModel.js');
const Note = require('lib/models/Note.js');
const { reg } = require('lib/registry.js');
@@ -10,15 +10,21 @@ const fs = require('fs-extra');
class Command extends BaseCommand {
usage() {
return 'export <directory>';
return 'export <path>';
}
description() {
return _('Exports Joplin data to the given directory. By default, it will export the complete database including notebooks, notes, tags and resources.');
return _('Exports Joplin data to the given path. By default, it will export the complete database including notebooks, notes, tags and resources.');
}
options() {
const service = new InteropService();
const formats = service.modules()
.filter(m => m.type === 'exporter')
.map(m => m.format + (m.description ? ' (' + m.description + ')' : ''));
return [
['--format <format>', _('Destination format: %s', formats.join(', '))],
['--note <note>', _('Exports only the given note.')],
['--notebook <notebook>', _('Exports only the given notebook.')],
];
@@ -26,13 +32,9 @@ class Command extends BaseCommand {
async action(args) {
let exportOptions = {};
exportOptions.destDir = args.directory;
exportOptions.writeFile = (filePath, data) => {
return fs.writeFile(filePath, data);
};
exportOptions.copyFile = (source, dest) => {
return fs.copy(source, dest, { overwrite: true });
};
exportOptions.path = args.path;
exportOptions.format = args.options.format ? args.options.format : 'jex';
if (args.options.note) {
@@ -48,10 +50,10 @@ class Command extends BaseCommand {
}
const exporter = new Exporter();
const result = await exporter.export(exportOptions);
const service = new InteropService();
const result = await service.export(exportOptions);
reg.logger().info('Export result: ', result);
result.warnings.map((w) => this.stdout(w));
}
}

View File

@@ -1,68 +0,0 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const Folder = require('lib/models/Folder.js');
const { importEnex } = require('lib/import-enex');
const { filename, basename } = require('lib/path-utils.js');
const { cliUtils } = require('./cli-utils.js');
class Command extends BaseCommand {
usage() {
return 'import-enex <file> [notebook]';
}
description() {
return _('Imports an Evernote notebook file (.enex file).');
}
options() {
return [
['-f, --force', _('Do not ask for confirmation.')],
];
}
async action(args) {
let filePath = args.file;
let folder = null;
let folderTitle = args['notebook'];
let force = args.options.force === true;
if (!folderTitle) folderTitle = filename(filePath);
folder = await Folder.loadByField('title', folderTitle);
const msg = folder ? _('File "%s" will be imported into existing notebook "%s". Continue?', basename(filePath), folderTitle) : _('New notebook "%s" will be created and file "%s" will be imported into it. Continue?', folderTitle, basename(filePath));
const ok = force ? true : await this.prompt(msg);
if (!ok) return;
let lastProgress = '';
let options = {
onProgress: (progressState) => {
let line = [];
line.push(_('Found: %d.', progressState.loaded));
line.push(_('Created: %d.', progressState.created));
if (progressState.updated) line.push(_('Updated: %d.', progressState.updated));
if (progressState.skipped) line.push(_('Skipped: %d.', progressState.skipped));
if (progressState.resourcesCreated) line.push(_('Resources: %d.', progressState.resourcesCreated));
if (progressState.notesTagged) line.push(_('Tagged: %d.', progressState.notesTagged));
lastProgress = line.join(' ');
cliUtils.redraw(lastProgress);
},
onError: (error) => {
let s = error.trace ? error.trace : error.toString();
this.stdout(s);
},
}
folder = !folder ? await Folder.save({ title: folderTitle }) : folder;
app().gui().showConsole();
this.stdout(_('Importing notes...'));
await importEnex(folder.id, filePath, options);
cliUtils.redrawDone();
this.stdout(_('The notes have been imported: %s', lastProgress));
}
}
module.exports = Command;

View File

@@ -0,0 +1,75 @@
const { BaseCommand } = require('./base-command.js');
const InteropService = require('lib/services/InteropService.js');
const BaseModel = require('lib/BaseModel.js');
const Note = require('lib/models/Note.js');
const { filename, basename, fileExtension } = require('lib/path-utils.js');
const { importEnex } = require('lib/import-enex');
const { cliUtils } = require('./cli-utils.js');
const { reg } = require('lib/registry.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const fs = require('fs-extra');
class Command extends BaseCommand {
usage() {
return 'import <path> [notebook]';
}
description() {
return _('Imports data into Joplin.');
}
options() {
const service = new InteropService();
const formats = service.modules().filter(m => m.type === 'importer').map(m => m.format);
return [
['--format <format>', _('Source format: %s', (['auto'].concat(formats)).join(', '))],
['-f, --force', _('Do not ask for confirmation.')],
];
}
async action(args) {
let folder = await app().loadItem(BaseModel.TYPE_FOLDER, args.notebook);
if (args.notebook && !folder) throw new Error(_('Cannot find "%s".', args.notebook));
const importOptions = {};
importOptions.path = args.path;
importOptions.format = args.options.format ? args.options.format : 'auto';
importOptions.destinationFolderId = folder ? folder.id : null;
let lastProgress = '';
// onProgress/onError supported by Enex import only
importOptions.onProgress = (progressState) => {
let line = [];
line.push(_('Found: %d.', progressState.loaded));
line.push(_('Created: %d.', progressState.created));
if (progressState.updated) line.push(_('Updated: %d.', progressState.updated));
if (progressState.skipped) line.push(_('Skipped: %d.', progressState.skipped));
if (progressState.resourcesCreated) line.push(_('Resources: %d.', progressState.resourcesCreated));
if (progressState.notesTagged) line.push(_('Tagged: %d.', progressState.notesTagged));
lastProgress = line.join(' ');
cliUtils.redraw(lastProgress);
};
importOptions.onError = (error) => {
let s = error.trace ? error.trace : error.toString();
this.stdout(s);
};
app().gui().showConsole();
this.stdout(_('Importing notes...'));
const service = new InteropService();
const result = await service.import(importOptions);
result.warnings.map((w) => this.stdout(w));
cliUtils.redrawDone();
if (lastProgress) this.stdout(_('The notes have been imported: %s', lastProgress));
}
}
module.exports = Command;

View File

@@ -14,10 +14,6 @@ class Command extends BaseCommand {
return _('Creates a new notebook.');
}
aliases() {
return ['mkdir'];
}
async action(args) {
let folder = await Folder.save({ title: args['new-notebook'] }, { userSideValidation: true });
app().switchCurrentFolder(folder);

View File

@@ -10,7 +10,6 @@ const { cliUtils } = require('./cli-utils.js');
const md5 = require('md5');
const locker = require('proper-lockfile');
const fs = require('fs-extra');
const osTmpdir = require('os-tmpdir');
const SyncTargetRegistry = require('lib/SyncTargetRegistry');
class Command extends BaseCommand {
@@ -101,7 +100,7 @@ class Command extends BaseCommand {
this.releaseLockFn_ = null;
// Lock is unique per profile/database
const lockFilePath = osTmpdir() + '/synclock_' + md5(escape(Setting.value('profileDir'))); // https://github.com/pvorb/node-md5/issues/41
const lockFilePath = require('os').tmpdir() + '/synclock_' + md5(escape(Setting.value('profileDir'))); // https://github.com/pvorb/node-md5/issues/41
if (!await fs.pathExists(lockFilePath)) await fs.writeFile(lockFilePath, 'synclock');
try {

View File

@@ -133,7 +133,8 @@ class StatusBarWidget extends BaseWidget {
resolveResult = input ? input.trim() : input;
// Add the command to history but only if it's longer than one character.
// Below that it's usually an answer like "y"/"n", etc.
if (!isSecurePrompt && input && input.length > 1) this.history_.push(input);
const isConfigPassword = input.indexOf('config ') >= 0 && input.indexOf('password') >= 0;
if (!isSecurePrompt && input && input.length > 1 && !isConfigPassword) this.history_.push(input);
}
}

View File

@@ -53,9 +53,8 @@ function renderCommandHelp(cmd, width = null) {
desc.push(label);
}
if (md.description) {
desc.push(md.description());
}
const description = Setting.keyDescription(md.key, 'cli');
if (description) desc.push(description);
desc.push(_('Type: %s.', md.isEnum ? _('Enum') : Setting.typeToString(md.type)));
if (md.isEnum) desc.push(_('Possible values: %s.', Setting.enumOptionsDoc(md.key, '%s (%s)')));

View File

@@ -71,37 +71,10 @@ process.stdout.on('error', function( err ) {
// async function main() {
// const WebDavApi = require('lib/WebDavApi');
// const api = new WebDavApi('http://nextcloud.local/remote.php/dav/files/admin/Joplin', { username: 'admin', password: '1234567' });
// const { FileApiDriverWebDav } = new require('lib/file-api-driver-webdav');
// const driver = new FileApiDriverWebDav(api);
// 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);
// const InteropService = require('lib/services/InteropService');
// const service = new InteropService();
// console.info(service.moduleByFormat('importer', 'enex'));
// //await service.modules();
// }
// main().catch((error) => { console.error(error); });

View File

@@ -194,14 +194,19 @@ msgstr "Die Notiz wurde gespeichert."
msgid "Exits the application."
msgstr "Schließt das Programm."
#, fuzzy
msgid ""
"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."
msgstr ""
"Exportiert Joplins Dateien zu dem angegebenen Pfad. Standardmäßig wird die "
"komplette Datenbank inklusive Notizbüchern, Notizen, Markierungen und "
"Anhängen exportiert."
#, fuzzy, javascript-format
msgid "Destination format: %s"
msgstr "Datumsformat"
msgid "Exports only the given note."
msgstr "Exportiert nur die angegebene Notiz."
@@ -269,26 +274,16 @@ msgstr ""
"Um die komplette Liste von verfügbaren Tastenkürzeln anzuzeigen, tippe `help "
"shortcuts` ein"
msgid "Imports an Evernote notebook file (.enex file)."
msgstr "Importiert eine Evernote Notizbuch-Datei (.enex Datei)."
msgid "Imports data into Joplin."
msgstr ""
#, fuzzy, javascript-format
msgid "Source format: %s"
msgstr "Ungültiger Befehl: %s"
msgid "Do not ask for confirmation."
msgstr "Nicht nach einer Bestätigung fragen."
#, javascript-format
msgid "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
msgstr ""
"Datei \"%s\" wird in das existierende Notizbuch \"%s\" importiert. "
"Fortfahren?"
#, javascript-format
msgid ""
"New notebook \"%s\" will be created and file \"%s\" will be imported into "
"it. Continue?"
msgstr ""
"Neues Notizbuch \"%s\" wird erstellt und die Datei \"%s\" wird hinein "
"importiert. Fortfahren?"
#, javascript-format
msgid "Found: %d."
msgstr "Gefunden: %d."
@@ -579,9 +574,20 @@ msgstr ""
"Wenn du das Passwort bereits eingegeben hast, werden die verschlüsselten "
"Objekte im Hintergrund entschlüsselt und stehen in Kürze zur Verfügung."
#, javascript-format
msgid "Exporting to \"%s\" as \"%s\" format. Please wait..."
msgstr ""
msgid "File"
msgstr "Datei"
msgid "Directory"
msgstr ""
#, javascript-format
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
msgstr ""
msgid "New note"
msgstr "Neue Notiz"
@@ -591,11 +597,12 @@ msgstr "Neues To-Do"
msgid "New notebook"
msgstr "Neues Notizbuch"
msgid "Import Evernote notes"
msgstr "Evernote Notizen importieren"
msgid "Import"
msgstr "Importieren"
msgid "Evernote Export Files"
msgstr "Evernote Export Dateien"
#, fuzzy
msgid "Export"
msgstr "Importieren"
#, javascript-format
msgid "Hide %s"
@@ -866,6 +873,12 @@ msgstr "Markierungen"
msgid "Set alarm"
msgstr "Alarm erstellen"
#, javascript-format
msgid ""
"This note has no content. Click on \"%s\" to toggle the editor and edit the "
"note."
msgstr ""
#, fuzzy
msgid "to-do"
msgstr "Neues To-Do"
@@ -887,9 +900,6 @@ msgstr "Leeren"
msgid "OneDrive Login"
msgstr "OneDrive Login"
msgid "Import"
msgstr "Importieren"
msgid "Options"
msgstr "Optionen"
@@ -1085,9 +1095,16 @@ msgid "Dark"
msgstr "Dunkel"
#, fuzzy
msgid "Show uncompleted to-dos on top of the lists"
msgid "Uncompleted to-dos on top"
msgstr "Zeige unvollständige To-Dos oben in der Liste"
msgid "Sort notes by"
msgstr ""
#, fuzzy
msgid "Reverse sort order"
msgstr "Dreht die Sortierreihenfolge um."
msgid "Save geo-location with notes"
msgstr "Momentanen Standort zusammen mit Notizen speichern"
@@ -1109,9 +1126,18 @@ msgstr "Erstellt eine neue Notiz."
msgid "Show tray icon"
msgstr ""
msgid "Set application zoom percentage"
#, fuzzy
msgid "Global zoom percentage"
msgstr "Einstellen des Anwendungszooms"
msgid "Editor font family"
msgstr ""
msgid ""
"The font name will not be checked. If incorrect or empty, it will default to "
"a generic monospace font."
msgstr ""
msgid "Automatically update the application"
msgstr "Die Applikation automatisch aktualisieren"
@@ -1179,6 +1205,42 @@ msgstr "Setze ein Passwort"
msgid "Invalid option value: \"%s\". Possible values are: %s."
msgstr "Ungültiger Optionswert: \"%s\". Mögliche Werte sind: %s."
#, fuzzy
msgid "Joplin Export File"
msgstr "Evernote Export Dateien"
msgid "Markdown"
msgstr ""
msgid "Joplin Export Directory"
msgstr ""
#, fuzzy
msgid "Evernote Export File"
msgstr "Evernote Export Dateien"
#, javascript-format
msgid "Cannot load \"%s\" module for format \"%s\""
msgstr ""
#, javascript-format
msgid "Please specify import format for %s"
msgstr ""
#, javascript-format
msgid ""
"This item is currently encrypted: %s \"%s\". Please wait for all items to be "
"decrypted and try again."
msgstr ""
msgid "There is no data to export."
msgstr ""
#, fuzzy
msgid "Please specify the notebook where the notes should be imported to."
msgstr ""
"Bitte wähle aus, wohin der Synchronisations Status exportiert werden soll"
msgid "Items that cannot be synchronised"
msgstr "Objekte können nicht synchronisiert werden"
@@ -1353,6 +1415,25 @@ msgstr ""
msgid "Welcome"
msgstr "Willkommen"
#~ msgid "Imports an Evernote notebook file (.enex file)."
#~ msgstr "Importiert eine Evernote Notizbuch-Datei (.enex Datei)."
#~ msgid ""
#~ "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
#~ msgstr ""
#~ "Datei \"%s\" wird in das existierende Notizbuch \"%s\" importiert. "
#~ "Fortfahren?"
#~ msgid ""
#~ "New notebook \"%s\" will be created and file \"%s\" will be imported into "
#~ "it. Continue?"
#~ msgstr ""
#~ "Neues Notizbuch \"%s\" wird erstellt und die Datei \"%s\" wird hinein "
#~ "importiert. Fortfahren?"
#~ msgid "Import Evernote notes"
#~ msgstr "Evernote Notizen importieren"
#~ msgid "Give focus to next pane"
#~ msgstr "Das nächste Fenster fokussieren"

View File

@@ -174,10 +174,14 @@ msgid "Exits the application."
msgstr ""
msgid ""
"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."
msgstr ""
#, javascript-format
msgid "Destination format: %s"
msgstr ""
msgid "Exports only the given note."
msgstr ""
@@ -232,22 +236,16 @@ msgid ""
"For the list of keyboard shortcuts and config options, type `help keymap`"
msgstr ""
msgid "Imports an Evernote notebook file (.enex file)."
msgid "Imports data into Joplin."
msgstr ""
#, javascript-format
msgid "Source format: %s"
msgstr ""
msgid "Do not ask for confirmation."
msgstr ""
#, javascript-format
msgid "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
msgstr ""
#, javascript-format
msgid ""
"New notebook \"%s\" will be created and file \"%s\" will be imported into "
"it. Continue?"
msgstr ""
#, javascript-format
msgid "Found: %d."
msgstr ""
@@ -488,9 +486,20 @@ msgid ""
"background and will be available soon."
msgstr ""
#, javascript-format
msgid "Exporting to \"%s\" as \"%s\" format. Please wait..."
msgstr ""
msgid "File"
msgstr ""
msgid "Directory"
msgstr ""
#, javascript-format
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
msgstr ""
msgid "New note"
msgstr ""
@@ -500,10 +509,10 @@ msgstr ""
msgid "New notebook"
msgstr ""
msgid "Import Evernote notes"
msgid "Import"
msgstr ""
msgid "Evernote Export Files"
msgid "Export"
msgstr ""
#, javascript-format
@@ -753,6 +762,12 @@ msgstr ""
msgid "Set alarm"
msgstr ""
#, javascript-format
msgid ""
"This note has no content. Click on \"%s\" to toggle the editor and edit the "
"note."
msgstr ""
msgid "to-do"
msgstr ""
@@ -772,9 +787,6 @@ msgstr ""
msgid "OneDrive Login"
msgstr ""
msgid "Import"
msgstr ""
msgid "Options"
msgstr ""
@@ -954,7 +966,13 @@ msgstr ""
msgid "Dark"
msgstr ""
msgid "Show uncompleted to-dos on top of the lists"
msgid "Uncompleted to-dos on top"
msgstr ""
msgid "Sort notes by"
msgstr ""
msgid "Reverse sort order"
msgstr ""
msgid "Save geo-location with notes"
@@ -975,7 +993,15 @@ msgstr ""
msgid "Show tray icon"
msgstr ""
msgid "Set application zoom percentage"
msgid "Global zoom percentage"
msgstr ""
msgid "Editor font family"
msgstr ""
msgid ""
"The font name will not be checked. If incorrect or empty, it will default to "
"a generic monospace font."
msgstr ""
msgid "Automatically update the application"
@@ -1037,6 +1063,38 @@ msgstr ""
msgid "Invalid option value: \"%s\". Possible values are: %s."
msgstr ""
msgid "Joplin Export File"
msgstr ""
msgid "Markdown"
msgstr ""
msgid "Joplin Export Directory"
msgstr ""
msgid "Evernote Export File"
msgstr ""
#, javascript-format
msgid "Cannot load \"%s\" module for format \"%s\""
msgstr ""
#, javascript-format
msgid "Please specify import format for %s"
msgstr ""
#, javascript-format
msgid ""
"This item is currently encrypted: %s \"%s\". Please wait for all items to be "
"decrypted and try again."
msgstr ""
msgid "There is no data to export."
msgstr ""
msgid "Please specify the notebook where the notes should be imported to."
msgstr ""
msgid "Items that cannot be synchronised"
msgstr ""

File diff suppressed because it is too large Load Diff

View File

@@ -16,8 +16,6 @@ msgstr ""
"X-Generator: Poedit 1.8.11\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Poedit-SourceCharset: UTF-8\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
msgid "To delete a tag, untag the associated notes."
msgstr "Desmarque las notas asociadas para eliminar una etiqueta."
@@ -37,7 +35,7 @@ msgid "No notebook selected."
msgstr "No se ha seleccionado ninguna libreta."
msgid "No notebook has been specified."
msgstr "Ninguna libre fue especificada"
msgstr "Ninguna libreta fue especificada"
msgid "Y"
msgstr "Y"
@@ -74,7 +72,7 @@ msgid "%s: %s"
msgstr "%s: %s"
msgid "Your choice: "
msgstr "Tu elección: "
msgstr "Su elección: "
#, javascript-format
msgid "Invalid answer: %s"
@@ -99,7 +97,7 @@ msgid ""
"current configuration."
msgstr ""
"Obtener o configurar un valor. Si no se provee el [valor], se mostrará el "
"valor de [nombre]. Si no se provee [nombre] ni [valor], se listara la "
"valor de [nombre]. Si no se provee [nombre] ni [valor], se listará la "
"configuración actual."
msgid "Also displays unset and hidden config variables."
@@ -131,11 +129,11 @@ msgid ""
"Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, "
"`status` and `target-status`."
msgstr ""
"Manejar la configuración E2EE. Comandos disponibles `enable`, `disable`, "
"Maneja la configuración E2EE. Comandos disponibles `enable`, `disable`, "
"`decrypt`, `status` y `target-status`."
msgid "Enter master password:"
msgstr "Introduce la contraseña maestra:"
msgstr "Introduzca la contraseña maestra:"
msgid "Operation cancelled"
msgstr "Operación cancelada"
@@ -174,10 +172,11 @@ msgstr "No hay libreta activa."
#, javascript-format
msgid "Note does not exist: \"%s\". Create it?"
msgstr "La nota no existe: \"%s\". Crearla?"
msgstr "La nota no existe: \"%s\". ¿Crearla?"
msgid "Starting to edit note. Close the editor to get back to the prompt."
msgstr "Iniciando a editar una nota. Cierra el editor para regresar al prompt."
msgstr ""
"Iniciando la edición de una nota. Cierre el editor para regresar al prompt."
#, javascript-format
msgid "Error opening note in editor: %s"
@@ -189,28 +188,33 @@ msgstr "La nota ha sido guardada."
msgid "Exits the application."
msgstr "Sale de la aplicación."
#, fuzzy
msgid ""
"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."
msgstr ""
"Exportar datos de Joplin al directorio indicado. Por defecto, se exportará "
"la base de datos completa incluyendo libretas, notas, etiquetas y recursos."
"Exporta datos de Joplin al directorio indicado. Por defecto, se exportará la "
"base de datos completa incluyendo libretas, notas, etiquetas y recursos."
#, fuzzy, javascript-format
msgid "Destination format: %s"
msgstr "Formato de fecha"
msgid "Exports only the given note."
msgstr "Exportar unicamente la nota indicada."
msgstr "Exporta únicamente la nota indicada."
msgid "Exports only the given notebook."
msgstr "Exportar unicamente la libreta indicada."
msgstr "Exporta únicamente la libreta indicada."
msgid "Displays a geolocation URL for the note."
msgstr "Mostrar geolocalización de la URL para la nota."
msgstr "Muestra la URL de la geolocalización de la nota."
msgid "Displays usage information."
msgstr "Muestra información de uso."
#, javascript-format
msgid "For information on how to customise the shortcuts please visit %s"
msgstr "Para información de como personalizar los atajos por favor visita %s"
msgstr "Para información de cómo personalizar los atajos por favor visite %s"
msgid "Shortcuts are not available in CLI mode."
msgstr "Atajos no disponibles en modo CLI."
@@ -231,54 +235,45 @@ msgid ""
"using the shortcuts `$n` or `$b` for, respectively, the currently selected "
"note or notebook. `$c` can be used to refer to the currently selected item."
msgstr ""
"Con cualquier comando, una nota o libreta puede ser referida por titulo o "
"Con cualquier comando, una nota o libreta puede ser referida por su título o "
"ID, o utilizando atajos `$n` o `$b`, respectivamente, para la nota o libreta "
"seleccionada se puede usar `$c` para hacer referencia al artículo "
"seleccionada. Se puede utilizar `$c` para hacer referencia al elemento "
"seleccionado."
msgid "To move from one pane to another, press Tab or Shift+Tab."
msgstr "Para mover desde un panel a otro, presiona Tab o Shift+Tab."
msgstr ""
"Para mover desde un panel a otro, presione Tabulador o Mayúsuclas+Tabulador."
msgid ""
"Use the arrows and page up/down to scroll the lists and text areas "
"(including this console)."
msgstr ""
"Para desplazar en las listas y areas de texto ( incluyendo la consola ) "
"Para desplazar en las listas y areas de texto (incluyendo la consola) "
"utilice las flechas y re pág/av pág."
msgid "To maximise/minimise the console, press \"TC\"."
msgstr "Para maximizar/minimizar la consola, presiona \"TC\"."
msgstr "Para maximizar/minimizar la consola, presione \"TC\"."
msgid "To enter command line mode, press \":\""
msgstr "Para entrar a modo linea de comando, presiona \":\""
msgstr "Para entrar a modo línea de comando, presione \":\""
msgid "To exit command line mode, press ESCAPE"
msgstr "Para salir de modo linea de comando, presiona ESCAPE"
msgstr "Para salir de modo línea de comando, presione ESCAPE"
msgid ""
"For the list of keyboard shortcuts and config options, type `help keymap`"
msgstr ""
"Para una lista de los atajos de teclado disponibles, escribe `help shortcuts`"
"Para una lista de los atajos de teclado disponibles, escriba `help keymap`"
msgid "Imports an Evernote notebook file (.enex file)."
msgstr "Importar una libreta de Evernote (archivo .enex)."
msgid "Imports data into Joplin."
msgstr ""
#, fuzzy, javascript-format
msgid "Source format: %s"
msgstr "El comando no existe: %s"
msgid "Do not ask for confirmation."
msgstr "No preguntar por confirmación."
#, javascript-format
msgid "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
msgstr ""
"El archivo \"%s\" será importado dentro de la libreta existente \"%s\". "
"Continuar?"
#, javascript-format
msgid ""
"New notebook \"%s\" will be created and file \"%s\" will be imported into "
"it. Continue?"
msgstr ""
"Nueva libreta \"%s\" será creada y el archivo \"%s\" será importado dentro "
"de ella. Continuar?"
msgstr "No requiere confirmación."
#, javascript-format
msgid "Found: %d."
@@ -323,8 +318,7 @@ msgstr "Muestra las primeras <num> notas."
msgid "Sorts the item by <field> (eg. title, updated_time, created_time)."
msgstr ""
"Ordena los artículos por campo ( ej. título, fecha de actualización, fecha "
"de creación)."
"Ordena los elementos por campo ( ej. title, updated_time, created_time)."
msgid "Reverses the sorting order."
msgstr "Invierte el orden."
@@ -334,7 +328,7 @@ msgid ""
"for to-dos, or `nt` for notes and to-dos (eg. `-tt` would display only the "
"to-dos, while `-ttd` would display notes and to-dos."
msgstr ""
"Muestra unicamente los artículos de los tipos especificados. Pueden ser `n` "
"Muestra únicamente los elementos de los tipos especificados. Pueden ser `n` "
"para notas, `t` para tareas, o `nt` para libretas y tareas (ej. `-tt` "
"mostrará unicamente las tareas, mientras `-ttd` mostrará notas y tareas)."
@@ -349,7 +343,7 @@ msgstr ""
"DATE,TODO_CHECKED ( para tareas), TITLE"
msgid "Please select a notebook first."
msgstr "Por favor selecciona la libreta."
msgstr "Por favor seleccione la libreta."
msgid "Creates a new notebook."
msgstr "Crea una nueva libreta."
@@ -364,10 +358,10 @@ msgid "Creates a new to-do."
msgstr "Crea una nueva lista de tareas."
msgid "Moves the notes matching <note> to [notebook]."
msgstr "Mueve las notas que coincidan con <note> para la [libreta]."
msgstr "Mueve las notas que coincidan con <note> a la [libreta]."
msgid "Renames the given <item> (note or notebook) to <name>."
msgstr "Renombre el artículo dado <item> (nota o libreta) a <name>."
msgstr "Renombra el elemento dado <item> (nota o libreta) a <name>."
msgid "Deletes the given notebook."
msgstr "Elimina la libreta dada."
@@ -388,13 +382,13 @@ msgstr "Elimina las notas sin pedir confirmación."
#, javascript-format
msgid "%d notes match this pattern. Delete them?"
msgstr "%d notas coinciden con el patron. Eliminarlas?"
msgstr "%d notas coinciden con el patrón. ¿Eliminarlas?"
msgid "Delete note?"
msgstr "¿Eliminar nota?"
msgid "Searches for the given <pattern> in all the notes."
msgstr "Buscar el patron <pattern> en todas las notas."
msgstr "Buscar el patrón <pattern> en todas las notas."
#, javascript-format
msgid ""
@@ -412,11 +406,11 @@ msgid "Displays summary about the notes and notebooks."
msgstr "Muestra un resumen acerca de las notas y las libretas."
msgid "Synchronises with remote storage."
msgstr "Sincronizar con almacenamiento remoto."
msgstr "Sincroniza con el almacenamiento remoto."
msgid "Sync to provided target (defaults to sync.target config value)"
msgstr ""
"Sincronizar con objetivo proveído ( por defecto al valor de configuración "
"Sincroniza con el destino indicado (por defecto al valor de configuración "
"sync.target)"
msgid ""
@@ -425,7 +419,7 @@ msgstr "Autenticación no completada (no se recibió token de autenticación)."
#, javascript-format
msgid "Not authentified with %s. Please provide any missing credentials."
msgstr "No autentificado con %s. Por favor provea las credenciales."
msgstr "No autenticado con %s. Por favor provea las credenciales."
msgid "Synchronisation is already in progress."
msgstr "Sincronzación en progreso."
@@ -442,7 +436,7 @@ msgstr ""
#, javascript-format
msgid "Synchronisation target: %s (%s)"
msgstr "Objetivo de sincronización: %s (%s)"
msgstr "Destino de la sincronización: %s (%s)"
msgid "Cannot initialize synchroniser."
msgstr "No se puede inicializar sincronizador."
@@ -473,12 +467,12 @@ msgid ""
"convert the to-do back to a regular note."
msgstr ""
"<todo-command> puede ser \"toggle\" o \"clear\". Usa \"toggle\" para cambiar "
"la tarea dada entre estado completado y sin completar. ( Si el objetivo es "
"la tarea dada entre estado completado y sin completar. (Si el objetivo es "
"una nota regular se convertirá en una tarea). Usa \"clear\" para convertir "
"la tarea a una nota regular. "
"la tarea a una nota regular."
msgid "Marks a to-do as non-completed."
msgstr "Marcar una tarea como no completada."
msgstr "Marca una tarea como no completada."
msgid ""
"Switches to [notebook] - all further operations will happen within this "
@@ -495,7 +489,7 @@ msgid "%s %s (%s)"
msgstr "%s %s (%s)"
msgid "Enum"
msgstr "Enumerar"
msgstr "Enumeración"
#, javascript-format
msgid "Type: %s."
@@ -510,7 +504,7 @@ msgid "Default: %s"
msgstr "Por defecto: %s"
msgid "Possible keys/values:"
msgstr "Teclas/valores posbiles:"
msgstr "Claves/valores posbiles:"
msgid "Fatal error:"
msgstr "Error fatal:"
@@ -518,11 +512,11 @@ msgstr "Error fatal:"
msgid ""
"The application has been authorised - you may now close this browser tab."
msgstr ""
"La aplicación ha sido autorizada - ahora puedes cerrar esta pestaña de tu "
"La aplicación ha sido autorizada - ahora puede cerrar esta pestaña de su "
"navegador."
msgid "The application has been successfully authorised."
msgstr "La aplicacion ha sido autorizada exitosamente."
msgstr "La aplicacion ha sido autorizada éxitosamente."
msgid ""
"Please open the following URL in your browser to authenticate the "
@@ -533,7 +527,7 @@ msgid ""
msgstr ""
"Abra la siguiente URL en su navegador para autenticar la aplicación. La "
"aplicación creará un directorio en «Apps/Joplin» y solo leerá y escribirá "
"archivos en este directorio. No tendrá acceso a ningún archivo fuera de este "
"archivos en ese directorio. No tendrá acceso a ningún archivo fuera de ese "
"directorio ni a ningún otro archivo personal. No se compartirá información "
"con terceros."
@@ -562,28 +556,40 @@ msgid ""
"supplied the password, the encrypted items are being decrypted in the "
"background and will be available soon."
msgstr ""
"Uno o más elementos están cifrados y debes proporcionar la contraseña "
"maestra. Para hacerlo por favor escribe `e2ee decrypt`. Si ya has "
"Uno o más elementos están cifrados y debe proporcionar la contraseña "
"maestra. Para hacerlo por favor escriba `e2ee decrypt`. Si ya ha "
"proporcionado la contraseña, los elementos están siendo descifrados en "
"segundo plano y estarán disponibles en breve."
#, javascript-format
msgid "Exporting to \"%s\" as \"%s\" format. Please wait..."
msgstr ""
msgid "File"
msgstr "Archivo"
msgid "Directory"
msgstr ""
#, javascript-format
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
msgstr ""
msgid "New note"
msgstr "Nota nueva"
msgstr "Nueva nota"
msgid "New to-do"
msgstr "Lista de tareas nueva"
msgstr "Nueva lista de tareas"
msgid "New notebook"
msgstr "Libreta nueva"
msgstr "Nueva libreta"
msgid "Import Evernote notes"
msgstr "Importar notas de Evernote"
msgid "Import"
msgstr "Importar"
msgid "Evernote Export Files"
msgstr "Archivos exportados de Evernote"
#, fuzzy
msgid "Export"
msgstr "Importar"
#, javascript-format
msgid "Hide %s"
@@ -691,8 +697,8 @@ msgid ""
"re-synchronised and sent unencrypted to the sync target. Do you wish to "
"continue?"
msgstr ""
"Deshabilitar el cifrado significa que *todas* tus notas y adjuntos van a ser "
"re-sincronizados y se enviarán descifrados al destino. ¿Deseas continuar?"
"Deshabilitar el cifrado significa que *todas* sus notas y adjuntos van a ser "
"re-sincronizados y se enviarán descifrados al destino. ¿Desea continuar?"
msgid ""
"Enabling encryption means *all* your notes and attachments are going to be "
@@ -700,10 +706,10 @@ msgid ""
"password as, for security purposes, this will be the *only* way to decrypt "
"the data! To enable encryption, please enter your password below."
msgstr ""
"Habilitar el cifrado significa que *todas* tus notas y adjuntos van a ser re-"
"sincronizados y se enviarán cifrados al destino. No pierdas la contraseña, "
"Habilitar el cifrado significa que *todas* sus notas y adjuntos van a ser re-"
"sincronizados y se enviarán cifrados al destino. No pierda la contraseña, "
"por cuestiones de seguridad, ¡es la *única* forma de descifrar los datos! "
"Para habilitar el cifrado, por favor introduce tu contraseña más abajo."
"Para habilitar el cifrado, por favor introduzca su contraseña más abajo."
msgid "Disable encryption"
msgstr "Deshabilitar cifrado"
@@ -772,10 +778,10 @@ msgid ""
msgstr "Se creará la nueva libreta «%s» y se importará en ella el archivo «%s»"
msgid "Please create a notebook first."
msgstr "Cree primero una libreta."
msgstr "Por favor cree una libreta primero."
msgid "Please create a notebook first"
msgstr "Por favor crea una libreta primero"
msgstr "Por favor cree una libreta primero"
msgid "Notebook title:"
msgstr "Título de libreta:"
@@ -848,6 +854,12 @@ msgstr "Etiquetas"
msgid "Set alarm"
msgstr "Establecer alarma"
#, javascript-format
msgid ""
"This note has no content. Click on \"%s\" to toggle the editor and edit the "
"note."
msgstr ""
msgid "to-do"
msgstr "lista de tareas"
@@ -867,9 +879,6 @@ msgstr "Limpiar"
msgid "OneDrive Login"
msgstr "Inicio de sesión de OneDrive"
msgid "Import"
msgstr "Importar"
msgid "Options"
msgstr "Opciones"
@@ -1004,7 +1013,7 @@ msgid "Encrypted"
msgstr "Cifrado"
msgid "Encrypted items cannot be modified"
msgstr "Los elementos cifrados no puedes ser modificados"
msgstr "Los elementos cifrados no pueden ser modificados"
msgid "Conflicts"
msgstr "Conflictos"
@@ -1060,9 +1069,17 @@ msgstr "Claro"
msgid "Dark"
msgstr "Oscuro"
msgid "Show uncompleted to-dos on top of the lists"
#, fuzzy
msgid "Uncompleted to-dos on top"
msgstr "Mostrar tareas incompletas al inicio de las listas"
msgid "Sort notes by"
msgstr ""
#, fuzzy
msgid "Reverse sort order"
msgstr "Invierte el orden."
msgid "Save geo-location with notes"
msgstr "Guardar geolocalización en las notas"
@@ -1070,7 +1087,7 @@ msgid "When creating a new to-do:"
msgstr "Al crear una nueva lista de tareas:"
msgid "Focus title"
msgstr "Foco en el título:"
msgstr "Foco en el título"
msgid "Focus body"
msgstr "Foco en el cuerpo"
@@ -1081,9 +1098,18 @@ msgstr "Cuando se crear una nota nueva:"
msgid "Show tray icon"
msgstr "Mostrar icono en la bandeja"
msgid "Set application zoom percentage"
#, fuzzy
msgid "Global zoom percentage"
msgstr "Establecer el porcentaje de aumento de la aplicación"
msgid "Editor font family"
msgstr ""
msgid ""
"The font name will not be checked. If incorrect or empty, it will default to "
"a generic monospace font."
msgstr ""
msgid "Automatically update the application"
msgstr "Actualizar la aplicación automáticamente"
@@ -1127,7 +1153,7 @@ msgstr ""
"de archivos. Vea «sync.target»."
msgid "Nextcloud WebDAV URL"
msgstr "Servidor Nextcloud WebDAV"
msgstr "Servidor WebDAV de Nextcloud"
msgid "Nextcloud username"
msgstr "Usuario de Nextcloud"
@@ -1148,6 +1174,41 @@ msgstr "Contraseña de WebDAV"
msgid "Invalid option value: \"%s\". Possible values are: %s."
msgstr "Opción inválida: «%s». Los valores posibles son: %s."
#, fuzzy
msgid "Joplin Export File"
msgstr "Archivos exportados de Evernote"
msgid "Markdown"
msgstr ""
msgid "Joplin Export Directory"
msgstr ""
#, fuzzy
msgid "Evernote Export File"
msgstr "Archivos exportados de Evernote"
#, javascript-format
msgid "Cannot load \"%s\" module for format \"%s\""
msgstr ""
#, javascript-format
msgid "Please specify import format for %s"
msgstr ""
#, javascript-format
msgid ""
"This item is currently encrypted: %s \"%s\". Please wait for all items to be "
"decrypted and try again."
msgstr ""
msgid "There is no data to export."
msgstr ""
#, fuzzy
msgid "Please specify the notebook where the notes should be imported to."
msgstr "Seleccione a dónde se debería exportar el estado de sincronización"
msgid "Items that cannot be synchronised"
msgstr "Elementos que no se pueden sincronizar"
@@ -1184,14 +1245,14 @@ msgid "To delete: %d"
msgstr "Borrar: %d"
msgid "Folders"
msgstr "Directorios"
msgstr "Carpetas"
#, javascript-format
msgid "%s: %d notes"
msgstr "%s: %d notas"
msgid "Coming alarms"
msgstr "Alarmas inminentes"
msgstr "Alarmas próximas"
#, javascript-format
msgid "On %s: %s"
@@ -1223,7 +1284,7 @@ msgid "Move %d notes to notebook \"%s\"?"
msgstr "¿Desea mover %d notas a libreta «%s»?"
msgid "Press to set the decryption password."
msgstr "Presiona para establecer la contraseña de descifrado."
msgstr "Presione para establecer la contraseña de descifrado."
msgid "Select date"
msgstr "Seleccione fecha"
@@ -1318,6 +1379,25 @@ msgstr ""
msgid "Welcome"
msgstr "Bienvenido"
#~ msgid "Imports an Evernote notebook file (.enex file)."
#~ msgstr "Importar una libreta de Evernote (archivo .enex)."
#~ msgid ""
#~ "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
#~ msgstr ""
#~ "El archivo \"%s\" será importado dentro de la libreta existente \"%s\". "
#~ "¿Continuar?"
#~ msgid ""
#~ "New notebook \"%s\" will be created and file \"%s\" will be imported into "
#~ "it. Continue?"
#~ msgstr ""
#~ "Nueva libreta \"%s\" será creada y el archivo \"%s\" será importado "
#~ "dentro de ella. ¿Continuar?"
#~ msgid "Import Evernote notes"
#~ msgstr "Importar notas de Evernote"
#~ msgid "Give focus to next pane"
#~ msgstr "Enfocar el siguiente panel"

View File

@@ -2,7 +2,7 @@
# Copyright (C) YEAR Laurent Cozic
# This file is distributed under the same license as the Joplin-CLI package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#
msgid ""
msgstr ""
"Project-Id-Version: Joplin-CLI 1.0.0\n"
@@ -189,10 +189,14 @@ msgstr "Irten aplikaziotik."
#, fuzzy
msgid ""
"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."
msgstr "Esportatu Joplineko datuak esandako karpetara"
#, fuzzy, javascript-format
msgid "Destination format: %s"
msgstr "Data-formatua"
msgid "Exports only the given note."
msgstr "Esportatu emandako oharra soilik."
@@ -256,26 +260,16 @@ msgid ""
"For the list of keyboard shortcuts and config options, type `help keymap`"
msgstr "Laster bideen zerrenda osoa ikusteko, idatzi `help shortcuts`"
msgid "Imports an Evernote notebook file (.enex file)."
msgstr "Inportatu Evernote koaderno fitxategia (.enex fitxategia)."
msgid "Imports data into Joplin."
msgstr ""
#, fuzzy, javascript-format
msgid "Source format: %s"
msgstr "Ez dago komandorik: %s"
msgid "Do not ask for confirmation."
msgstr "Ez galdetu berresteko."
#, javascript-format
msgid "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
msgstr ""
" \"%s\" izeneko fitxategia \"%s\" izeneko koadernoan inportatzekotan zaude. "
"Jarraitu nahi duzu?"
#, javascript-format
msgid ""
"New notebook \"%s\" will be created and file \"%s\" will be imported into "
"it. Continue?"
msgstr ""
" \"%s\" izeneko koaderno berria sortzekotan zaude eta bertan \"%s\" "
"fitxategia bertara inportatzekotan zaude. Jarraitu nahi duzu?"
#, javascript-format
msgid "Found: %d."
msgstr "Aurkitua: %d"
@@ -562,9 +556,20 @@ msgstr ""
"Dagoeneko pasahitza ordezkatua baduzu, itemak deszifratzen ari izango dira "
"atzeko planoan eta laster izango dira eskuragarri."
#, javascript-format
msgid "Exporting to \"%s\" as \"%s\" format. Please wait..."
msgstr ""
msgid "File"
msgstr "Fitxategia"
msgid "Directory"
msgstr ""
#, javascript-format
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
msgstr ""
msgid "New note"
msgstr "Ohar berria"
@@ -574,11 +579,12 @@ msgstr "Zeregin berria"
msgid "New notebook"
msgstr "Koaderno berria"
msgid "Import Evernote notes"
msgstr "Inportatu Evernoteko oharrak"
msgid "Import"
msgstr "Inportatu"
msgid "Evernote Export Files"
msgstr "Evernotetik esportatutako fitxategiak"
#, fuzzy
msgid "Export"
msgstr "Inportatu"
#, javascript-format
msgid "Hide %s"
@@ -844,6 +850,12 @@ msgstr "Etiketak"
msgid "Set alarm"
msgstr "Ezarri alarma"
#, javascript-format
msgid ""
"This note has no content. Click on \"%s\" to toggle the editor and edit the "
"note."
msgstr ""
#, fuzzy
msgid "to-do"
msgstr "Zeregin berria"
@@ -866,9 +878,6 @@ msgstr "Garbitu"
msgid "OneDrive Login"
msgstr "Logeatu OneDriven"
msgid "Import"
msgstr "Inportatu"
msgid "Options"
msgstr "Aukerak"
@@ -1062,9 +1071,16 @@ msgid "Dark"
msgstr "Iluna"
#, fuzzy
msgid "Show uncompleted to-dos on top of the lists"
msgid "Uncompleted to-dos on top"
msgstr "Bete gabeko zereginak erakutsi zerrendaren goiko partean"
msgid "Sort notes by"
msgstr ""
#, fuzzy
msgid "Reverse sort order"
msgstr "Alderantziz antolatzen du."
msgid "Save geo-location with notes"
msgstr "Gore geokokapena oharrekin"
@@ -1085,9 +1101,18 @@ msgstr "Ohar berria sortzen du."
msgid "Show tray icon"
msgstr ""
msgid "Set application zoom percentage"
#, fuzzy
msgid "Global zoom percentage"
msgstr "Ezarri aplikazioaren zoomaren ehunekoa"
msgid "Editor font family"
msgstr ""
msgid ""
"The font name will not be checked. If incorrect or empty, it will default to "
"a generic monospace font."
msgstr ""
msgid "Automatically update the application"
msgstr "Automatikoki eguneratu aplikazioa"
@@ -1154,6 +1179,41 @@ msgstr "Ezarri pasahitza"
msgid "Invalid option value: \"%s\". Possible values are: %s."
msgstr "Balio aukera baliogabea: \"%s\". Litezkeen balioak: %s."
#, fuzzy
msgid "Joplin Export File"
msgstr "Evernotetik esportatutako fitxategiak"
msgid "Markdown"
msgstr ""
msgid "Joplin Export Directory"
msgstr ""
#, fuzzy
msgid "Evernote Export File"
msgstr "Evernotetik esportatutako fitxategiak"
#, javascript-format
msgid "Cannot load \"%s\" module for format \"%s\""
msgstr ""
#, javascript-format
msgid "Please specify import format for %s"
msgstr ""
#, javascript-format
msgid ""
"This item is currently encrypted: %s \"%s\". Please wait for all items to be "
"decrypted and try again."
msgstr ""
msgid "There is no data to export."
msgstr ""
#, fuzzy
msgid "Please specify the notebook where the notes should be imported to."
msgstr "Aukeratu nora esportatu sinkronizazioaren egoera, mesedez"
msgid "Items that cannot be synchronised"
msgstr "Itemok ezin sinkronizatu"
@@ -1322,6 +1382,25 @@ msgstr "Oraindik ez duzu koadernorik. Sortu bat (+) botoian sakatuta."
msgid "Welcome"
msgstr "Ongi etorri!"
#~ msgid "Imports an Evernote notebook file (.enex file)."
#~ msgstr "Inportatu Evernote koaderno fitxategia (.enex fitxategia)."
#~ msgid ""
#~ "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
#~ msgstr ""
#~ " \"%s\" izeneko fitxategia \"%s\" izeneko koadernoan inportatzekotan "
#~ "zaude. Jarraitu nahi duzu?"
#~ msgid ""
#~ "New notebook \"%s\" will be created and file \"%s\" will be imported into "
#~ "it. Continue?"
#~ msgstr ""
#~ " \"%s\" izeneko koaderno berria sortzekotan zaude eta bertan \"%s\" "
#~ "fitxategia bertara inportatzekotan zaude. Jarraitu nahi duzu?"
#~ msgid "Import Evernote notes"
#~ msgstr "Inportatu Evernoteko oharrak"
#~ msgid "Give focus to next pane"
#~ msgstr "Eraman fokua hurrengo panelera"

View File

@@ -95,8 +95,8 @@ msgid ""
"current configuration."
msgstr ""
"Obtient ou modifie une valeur de configuration. Si la [valeur] n'est pas "
"fournie, la valeur de [nom] est affichée. Si ni le [nom] ni la [valeur] ne "
"sont fournis, la configuration complète est affichée."
"fournie, la valeur de [nom] sera affichée. Si ni le [nom] ni la [valeur] ne "
"sont fournis, la configuration complète sera affichée."
msgid "Also displays unset and hidden config variables."
msgstr "Afficher également les variables cachées."
@@ -188,12 +188,15 @@ msgid "Exits the application."
msgstr "Quitter le logiciel."
msgid ""
"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."
msgstr ""
"Exporter les données de Joplin vers le dossier fourni. Par défaut, la base "
"de donnée complète sera exportée, y compris les carnets, notes, tags et "
"resources."
"Exporter les données de Joplin. Par défaut, la base de donnée complète sera "
"exportée, y compris les carnets, notes, tags et ressources."
#, javascript-format
msgid "Destination format: %s"
msgstr "Format de la destination : %s"
msgid "Exports only the given note."
msgstr "Exporter uniquement la note spécifiée."
@@ -210,6 +213,7 @@ msgstr "Affiche les informations d'utilisation."
#, javascript-format
msgid "For information on how to customise the shortcuts please visit %s"
msgstr ""
"Pour personnaliser les raccourcis veuillez consulter la documentation à %s"
msgid "Shortcuts are not available in CLI mode."
msgstr "Les raccourcis ne sont pas disponible en mode de ligne de commande."
@@ -253,31 +257,20 @@ msgstr "Pour démarrer le mode ligne de commande, pressez \":\""
msgid "To exit command line mode, press ESCAPE"
msgstr "Pour sortir du mode ligne de commande, pressez ECHAP"
#, fuzzy
msgid ""
"For the list of keyboard shortcuts and config options, type `help keymap`"
msgstr ""
"Pour la liste complète des raccourcis disponibles, tapez `help shortcuts`"
msgstr "Pour la liste complète des raccourcis disponibles, tapez `help keymap`"
msgid "Imports an Evernote notebook file (.enex file)."
msgstr "Importer un carnet Evernote (fichier .enex)."
msgid "Imports data into Joplin."
msgstr "Importer des données dans Joplin."
#, javascript-format
msgid "Source format: %s"
msgstr "Format de la source : %s"
msgid "Do not ask for confirmation."
msgstr "Ne pas demander de confirmation."
#, javascript-format
msgid "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
msgstr ""
"Le fichier \"%s\" va être importé dans le carnet existant \"%s\". Continuer ?"
#, javascript-format
msgid ""
"New notebook \"%s\" will be created and file \"%s\" will be imported into "
"it. Continue?"
msgstr ""
"Un nouveau carnet \"%s\" va être créé et le fichier \"%s\" va être importé "
"dedans. Continuer ?"
#, javascript-format
msgid "Found: %d."
msgstr "Trouvés : %d."
@@ -564,9 +557,20 @@ msgstr ""
"decrypt`. Si vous avez déjà fourni ce mot de passe, les objets cryptés vont "
"être décrypté en tâche de fond et seront disponible prochainement."
#, javascript-format
msgid "Exporting to \"%s\" as \"%s\" format. Please wait..."
msgstr "Exporter vers \"%s\" au format \"%s\". Veuillez patienter..."
msgid "File"
msgstr "Fichier"
msgid "Directory"
msgstr "Dossier"
#, javascript-format
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
msgstr "Importer depuis \"%s\" au format \"%s\". Veuillez patienter..."
msgid "New note"
msgstr "Nouvelle note"
@@ -576,11 +580,11 @@ msgstr "Nouvelle tâche"
msgid "New notebook"
msgstr "Nouveau carnet"
msgid "Import Evernote notes"
msgstr "Importer notes d'Evernote"
msgid "Import"
msgstr "Importer"
msgid "Evernote Export Files"
msgstr "Fichiers d'export Evernote"
msgid "Export"
msgstr "Exporter"
#, javascript-format
msgid "Hide %s"
@@ -853,6 +857,14 @@ msgstr "Étiquettes"
msgid "Set alarm"
msgstr "Régler alarme"
#, javascript-format
msgid ""
"This note has no content. Click on \"%s\" to toggle the editor and edit the "
"note."
msgstr ""
"Cette note n'a pas de contenu. Cliquer sur \"%s\" pour basculer vers "
"l'éditeur et éditer cette note."
msgid "to-do"
msgstr "tâche"
@@ -872,9 +884,6 @@ msgstr "Supprimer"
msgid "OneDrive Login"
msgstr "Connexion OneDrive"
msgid "Import"
msgstr "Importer"
msgid "Options"
msgstr "Options"
@@ -1065,8 +1074,14 @@ msgstr "Clair"
msgid "Dark"
msgstr "Sombre"
msgid "Show uncompleted to-dos on top of the lists"
msgstr "Tâches non-terminées en haut des listes"
msgid "Uncompleted to-dos on top"
msgstr "Tâches non-terminées en haut"
msgid "Sort notes by"
msgstr "Trier les notes par"
msgid "Reverse sort order"
msgstr "Inverser l'ordre."
msgid "Save geo-location with notes"
msgstr "Enregistrer l'emplacement avec les notes"
@@ -1086,9 +1101,19 @@ msgstr "Lors de la création d'une note :"
msgid "Show tray icon"
msgstr "Afficher icône dans la zone de notifications"
msgid "Set application zoom percentage"
msgid "Global zoom percentage"
msgstr "Niveau de zoom"
msgid "Editor font family"
msgstr "Police de l'éditeur"
msgid ""
"The font name will not be checked. If incorrect or empty, it will default to "
"a generic monospace font."
msgstr ""
"Le nom de la police ne sera pas vérifié. Si incorrect ou vide une police "
"monospace sera utilisée par défaut."
msgid "Automatically update the application"
msgstr "Mettre à jour le logiciel automatiquement"
@@ -1153,6 +1178,40 @@ msgstr "WebDAV : Mot de passe"
msgid "Invalid option value: \"%s\". Possible values are: %s."
msgstr "Option invalide: \"%s\". Les valeurs possibles sont : %s."
msgid "Joplin Export File"
msgstr "Fichier d'export Joplin"
msgid "Markdown"
msgstr "Markdown"
msgid "Joplin Export Directory"
msgstr "Dossier d'export Joplin"
msgid "Evernote Export File"
msgstr "Fichiers d'export Evernote"
#, javascript-format
msgid "Cannot load \"%s\" module for format \"%s\""
msgstr "Impossible de charger module \"%s\" pour le format \"%s\""
#, javascript-format
msgid "Please specify import format for %s"
msgstr "Veuillez spécifier le format d'import pour %s"
#, javascript-format
msgid ""
"This item is currently encrypted: %s \"%s\". Please wait for all items to be "
"decrypted and try again."
msgstr ""
"Cet objet est crypté : %s \"%s\". Veuillez attendre que tout soit décrypté "
"et réessayez."
msgid "There is no data to export."
msgstr "Il n'y a pas de données à exporter."
msgid "Please specify the notebook where the notes should be imported to."
msgstr "Veuillez sélectionner le carnet où les notes doivent être importées."
msgid "Items that cannot be synchronised"
msgstr "Objets qui ne peuvent pas être synchronisés"
@@ -1326,6 +1385,25 @@ msgstr ""
msgid "Welcome"
msgstr "Bienvenue"
#~ msgid "Imports an Evernote notebook file (.enex file)."
#~ msgstr "Importer un carnet Evernote (fichier .enex)."
#~ msgid ""
#~ "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
#~ msgstr ""
#~ "Le fichier \"%s\" va être importé dans le carnet existant \"%s\". "
#~ "Continuer ?"
#~ msgid ""
#~ "New notebook \"%s\" will be created and file \"%s\" will be imported into "
#~ "it. Continue?"
#~ msgstr ""
#~ "Un nouveau carnet \"%s\" va être créé et le fichier \"%s\" va être "
#~ "importé dedans. Continuer ?"
#~ msgid "Import Evernote notes"
#~ msgstr "Importer notes d'Evernote"
#~ msgid "Give focus to next pane"
#~ msgstr "Activer le volet suivant"

View File

@@ -188,13 +188,18 @@ msgstr "Bilješka je spremljena."
msgid "Exits the application."
msgstr "Izlaz iz aplikacije."
#, fuzzy
msgid ""
"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."
msgstr ""
"Izvozi podatke u dati direktorij. Po defaultu izvozi sve podatke uključujući "
"bilježnice, bilješke, zadatke i resurse."
#, fuzzy, javascript-format
msgid "Destination format: %s"
msgstr "Format datuma"
msgid "Exports only the given note."
msgstr "Izvozi samo datu bilješku."
@@ -259,25 +264,16 @@ msgid ""
"For the list of keyboard shortcuts and config options, type `help keymap`"
msgstr "Za potpunu listu mogućih prečaca, upiši `help shortcuts`"
msgid "Imports an Evernote notebook file (.enex file)."
msgstr "Uvozi Evernote bilježnicu (.enex datoteku)."
msgid "Imports data into Joplin."
msgstr ""
#, fuzzy, javascript-format
msgid "Source format: %s"
msgstr "Ne postoji naredba: %s"
msgid "Do not ask for confirmation."
msgstr "Ne pitaj za potvrdu."
#, javascript-format
msgid "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
msgstr ""
"Datoteka \"%s\" će biti uvezena u postojeću bilježnicu \"%s\". Nastavi?"
#, javascript-format
msgid ""
"New notebook \"%s\" will be created and file \"%s\" will be imported into "
"it. Continue?"
msgstr ""
"Nova bilježnica \"%s\" će biti stvorena i datoteka \"%s\" će biti uvezena u "
"nju. Nastavi?"
#, javascript-format
msgid "Found: %d."
msgstr "Nađeno: %d."
@@ -562,9 +558,20 @@ msgid ""
"background and will be available soon."
msgstr ""
#, javascript-format
msgid "Exporting to \"%s\" as \"%s\" format. Please wait..."
msgstr ""
msgid "File"
msgstr "Datoteka"
msgid "Directory"
msgstr ""
#, javascript-format
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
msgstr ""
msgid "New note"
msgstr "Nova bilješka"
@@ -574,11 +581,12 @@ msgstr "Novi zadatak"
msgid "New notebook"
msgstr "Nova bilježnica"
msgid "Import Evernote notes"
msgstr "Uvezi Evernote bilješke"
msgid "Import"
msgstr "Uvoz"
msgid "Evernote Export Files"
msgstr "Evernote izvozne datoteke"
#, fuzzy
msgid "Export"
msgstr "Uvoz"
#, javascript-format
msgid "Hide %s"
@@ -834,6 +842,12 @@ msgstr "Oznake"
msgid "Set alarm"
msgstr "Postavi upozorenje"
#, javascript-format
msgid ""
"This note has no content. Click on \"%s\" to toggle the editor and edit the "
"note."
msgstr ""
#, fuzzy
msgid "to-do"
msgstr "Novi zadatak"
@@ -855,9 +869,6 @@ msgstr "Očisti"
msgid "OneDrive Login"
msgstr "OneDrive Login"
msgid "Import"
msgstr "Uvoz"
msgid "Options"
msgstr "Opcije"
@@ -1047,9 +1058,16 @@ msgid "Dark"
msgstr "Tamna"
#, fuzzy
msgid "Show uncompleted to-dos on top of the lists"
msgid "Uncompleted to-dos on top"
msgstr "Prikaži nezavršene zadatke na vrhu liste"
msgid "Sort notes by"
msgstr ""
#, fuzzy
msgid "Reverse sort order"
msgstr "Mijenja redoslijed."
msgid "Save geo-location with notes"
msgstr "Spremi geolokacijske podatke sa bilješkama"
@@ -1071,7 +1089,15 @@ msgstr "Stvara novu bilješku."
msgid "Show tray icon"
msgstr ""
msgid "Set application zoom percentage"
msgid "Global zoom percentage"
msgstr ""
msgid "Editor font family"
msgstr ""
msgid ""
"The font name will not be checked. If incorrect or empty, it will default to "
"a generic monospace font."
msgstr ""
msgid "Automatically update the application"
@@ -1135,6 +1161,41 @@ msgstr ""
msgid "Invalid option value: \"%s\". Possible values are: %s."
msgstr "Nevažeća vrijednost: \"%s\". Moguće vrijednosti su: %s."
#, fuzzy
msgid "Joplin Export File"
msgstr "Evernote izvozne datoteke"
msgid "Markdown"
msgstr ""
msgid "Joplin Export Directory"
msgstr ""
#, fuzzy
msgid "Evernote Export File"
msgstr "Evernote izvozne datoteke"
#, javascript-format
msgid "Cannot load \"%s\" module for format \"%s\""
msgstr ""
#, javascript-format
msgid "Please specify import format for %s"
msgstr ""
#, javascript-format
msgid ""
"This item is currently encrypted: %s \"%s\". Please wait for all items to be "
"decrypted and try again."
msgstr ""
msgid "There is no data to export."
msgstr ""
#, fuzzy
msgid "Please specify the notebook where the notes should be imported to."
msgstr "Odaberi lokaciju za izvoz statusa sinkronizacije"
msgid "Items that cannot be synchronised"
msgstr "Stavke koje se ne mogu sinkronizirati"
@@ -1302,6 +1363,24 @@ msgstr "Trenutno nemaš nijednu bilježnicu. Stvori novu klikom na (+) gumb."
msgid "Welcome"
msgstr "Dobro došli"
#~ msgid "Imports an Evernote notebook file (.enex file)."
#~ msgstr "Uvozi Evernote bilježnicu (.enex datoteku)."
#~ msgid ""
#~ "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
#~ msgstr ""
#~ "Datoteka \"%s\" će biti uvezena u postojeću bilježnicu \"%s\". Nastavi?"
#~ msgid ""
#~ "New notebook \"%s\" will be created and file \"%s\" will be imported into "
#~ "it. Continue?"
#~ msgstr ""
#~ "Nova bilježnica \"%s\" će biti stvorena i datoteka \"%s\" će biti uvezena "
#~ "u nju. Nastavi?"
#~ msgid "Import Evernote notes"
#~ msgstr "Uvezi Evernote bilješke"
#~ msgid "Give focus to next pane"
#~ msgstr "Fokusiraj sljedeće okno"

View File

@@ -183,14 +183,19 @@ msgstr "La nota è stata salvata."
msgid "Exits the application."
msgstr "Esci dall'applicazione."
#, fuzzy
msgid ""
"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."
msgstr ""
"Esporta i dati da Joplin nella directory selezionata. Come impostazione "
"predefinita verrà esportato il database completo, inclusi blocchi note, "
"note, etichette e risorse."
#, fuzzy, javascript-format
msgid "Destination format: %s"
msgstr "Formato della data"
msgid "Exports only the given note."
msgstr "Esporta solo la seguente nota."
@@ -255,25 +260,16 @@ msgid ""
msgstr ""
"Per la lista completa delle scorciatoie disponibili, digita `help shortcuts`"
msgid "Imports an Evernote notebook file (.enex file)."
msgstr "Importa un file blocco note di Evernote (.enex file)."
msgid "Imports data into Joplin."
msgstr ""
#, fuzzy, javascript-format
msgid "Source format: %s"
msgstr "Nessun comando: %s"
msgid "Do not ask for confirmation."
msgstr "Non chiedere conferma."
#, javascript-format
msgid "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
msgstr ""
"Il file \"%s\" sarà importato nel blocco note esistente \"%s\". Continuare?"
#, javascript-format
msgid ""
"New notebook \"%s\" will be created and file \"%s\" will be imported into "
"it. Continue?"
msgstr ""
"Un nuovo blocco note \"%s\" sarà creato e al suo interno verrà importato il "
"file \"%s\" . Continuare?"
#, javascript-format
msgid "Found: %d."
msgstr "Trovato: %d."
@@ -544,9 +540,20 @@ msgid ""
"background and will be available soon."
msgstr ""
#, javascript-format
msgid "Exporting to \"%s\" as \"%s\" format. Please wait..."
msgstr ""
msgid "File"
msgstr "File"
msgid "Directory"
msgstr ""
#, javascript-format
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
msgstr ""
msgid "New note"
msgstr "Nuova nota"
@@ -556,11 +563,12 @@ msgstr "Nuova attività"
msgid "New notebook"
msgstr "Nuovo blocco note"
msgid "Import Evernote notes"
msgstr "Importa le note da Evernote"
msgid "Import"
msgstr "Importa"
msgid "Evernote Export Files"
msgstr "Esposta i files di Evernote"
#, fuzzy
msgid "Export"
msgstr "Importa"
#, javascript-format
msgid "Hide %s"
@@ -817,6 +825,12 @@ msgstr "Etichette"
msgid "Set alarm"
msgstr "Imposta allarme"
#, javascript-format
msgid ""
"This note has no content. Click on \"%s\" to toggle the editor and edit the "
"note."
msgstr ""
#, fuzzy
msgid "to-do"
msgstr "Nuova attività"
@@ -838,9 +852,6 @@ msgstr "Pulisci"
msgid "OneDrive Login"
msgstr "Login OneDrive"
msgid "Import"
msgstr "Importa"
msgid "Options"
msgstr "Opzioni"
@@ -1033,9 +1044,16 @@ msgid "Dark"
msgstr "Scuro"
#, fuzzy
msgid "Show uncompleted to-dos on top of the lists"
msgid "Uncompleted to-dos on top"
msgstr "Mostra todo inclompleti in cima alla lista"
msgid "Sort notes by"
msgstr ""
#, fuzzy
msgid "Reverse sort order"
msgstr "Inverti l'ordine."
msgid "Save geo-location with notes"
msgstr "Salva geo-localizzazione con le note"
@@ -1057,7 +1075,15 @@ msgstr "Crea una nuova nota."
msgid "Show tray icon"
msgstr ""
msgid "Set application zoom percentage"
msgid "Global zoom percentage"
msgstr ""
msgid "Editor font family"
msgstr ""
msgid ""
"The font name will not be checked. If incorrect or empty, it will default to "
"a generic monospace font."
msgstr ""
msgid "Automatically update the application"
@@ -1121,6 +1147,41 @@ msgstr ""
msgid "Invalid option value: \"%s\". Possible values are: %s."
msgstr "Oprione non valida: \"%s\". I valori possibili sono: %s."
#, fuzzy
msgid "Joplin Export File"
msgstr "Esposta i files di Evernote"
msgid "Markdown"
msgstr ""
msgid "Joplin Export Directory"
msgstr ""
#, fuzzy
msgid "Evernote Export File"
msgstr "Esposta i files di Evernote"
#, javascript-format
msgid "Cannot load \"%s\" module for format \"%s\""
msgstr ""
#, javascript-format
msgid "Please specify import format for %s"
msgstr ""
#, javascript-format
msgid ""
"This item is currently encrypted: %s \"%s\". Please wait for all items to be "
"decrypted and try again."
msgstr ""
msgid "There is no data to export."
msgstr ""
#, fuzzy
msgid "Please specify the notebook where the notes should be imported to."
msgstr "Per favore seleziona la nota o il blocco note da eliminare."
msgid "Items that cannot be synchronised"
msgstr "Elementi che non possono essere sincronizzati"
@@ -1290,6 +1351,25 @@ msgstr ""
msgid "Welcome"
msgstr "Benvenuto"
#~ msgid "Imports an Evernote notebook file (.enex file)."
#~ msgstr "Importa un file blocco note di Evernote (.enex file)."
#~ msgid ""
#~ "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
#~ msgstr ""
#~ "Il file \"%s\" sarà importato nel blocco note esistente \"%s\". "
#~ "Continuare?"
#~ msgid ""
#~ "New notebook \"%s\" will be created and file \"%s\" will be imported into "
#~ "it. Continue?"
#~ msgstr ""
#~ "Un nuovo blocco note \"%s\" sarà creato e al suo interno verrà importato "
#~ "il file \"%s\" . Continuare?"
#~ msgid "Import Evernote notes"
#~ msgstr "Importa le note da Evernote"
#~ msgid "Give focus to next pane"
#~ msgstr "Pannello successivo"

View File

@@ -182,13 +182,18 @@ msgstr "ノートは保存されました。"
msgid "Exits the application."
msgstr "アプリケーションの終了。"
#, fuzzy
msgid ""
"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."
msgstr ""
"Joplinのデータを選択されたディレクトリに出力する。標準では、ノートブック・"
"ノート・タグ・添付データを含むすべてのデータベースを出力します。"
#, fuzzy, javascript-format
msgid "Destination format: %s"
msgstr "日付の形式"
msgid "Exports only the given note."
msgstr "選択されたノートのみを出力する。"
@@ -251,25 +256,16 @@ msgstr ""
"有効なすべてのキーボードショートカットを表示するには、`help shortcuts`と入力"
"してください。"
msgid "Imports an Evernote notebook file (.enex file)."
msgstr "Evernoteノートブックファイル(.enex)のインポート"
msgid "Imports data into Joplin."
msgstr ""
#, fuzzy, javascript-format
msgid "Source format: %s"
msgstr "コマンドが違います:%s"
msgid "Do not ask for confirmation."
msgstr "確認を行わない。"
#, javascript-format
msgid "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
msgstr ""
"ファイル \"%s\" はノートブック \"%s\"に取り込まれます。よろしいですか?"
#, javascript-format
msgid ""
"New notebook \"%s\" will be created and file \"%s\" will be imported into "
"it. Continue?"
msgstr ""
"新しいノートブック\"%s\"が作成され、ファイル\"%s\"が取り込まれます。よろしい"
"ですか?"
#, javascript-format
msgid "Found: %d."
msgstr "見つかりました:%d"
@@ -545,9 +541,20 @@ msgid ""
"background and will be available soon."
msgstr ""
#, javascript-format
msgid "Exporting to \"%s\" as \"%s\" format. Please wait..."
msgstr ""
msgid "File"
msgstr "ファイル"
msgid "Directory"
msgstr ""
#, javascript-format
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
msgstr ""
msgid "New note"
msgstr "新しいノート"
@@ -557,11 +564,12 @@ msgstr "新しいToDo"
msgid "New notebook"
msgstr "新しいノートブック"
msgid "Import Evernote notes"
msgstr "Evernoteのインポート"
msgid "Import"
msgstr "インポート"
msgid "Evernote Export Files"
msgstr "Evernote Exportファイル"
#, fuzzy
msgid "Export"
msgstr "インポート"
#, javascript-format
msgid "Hide %s"
@@ -820,6 +828,12 @@ msgstr "タグ"
msgid "Set alarm"
msgstr "アラームをセット"
#, javascript-format
msgid ""
"This note has no content. Click on \"%s\" to toggle the editor and edit the "
"note."
msgstr ""
#, fuzzy
msgid "to-do"
msgstr "新しいToDo"
@@ -841,9 +855,6 @@ msgstr "クリア"
msgid "OneDrive Login"
msgstr "OneDriveログイン"
msgid "Import"
msgstr "インポート"
msgid "Options"
msgstr "オプション"
@@ -1037,9 +1048,16 @@ msgid "Dark"
msgstr "暗い"
#, fuzzy
msgid "Show uncompleted to-dos on top of the lists"
msgid "Uncompleted to-dos on top"
msgstr "未完のToDoをリストの上部に表示"
msgid "Sort notes by"
msgstr ""
#, fuzzy
msgid "Reverse sort order"
msgstr "逆順に並び替える。"
msgid "Save geo-location with notes"
msgstr "ノートに位置情報を保存"
@@ -1061,7 +1079,15 @@ msgstr "あたらしいノートを作成します。"
msgid "Show tray icon"
msgstr ""
msgid "Set application zoom percentage"
msgid "Global zoom percentage"
msgstr ""
msgid "Editor font family"
msgstr ""
msgid ""
"The font name will not be checked. If incorrect or empty, it will default to "
"a generic monospace font."
msgstr ""
msgid "Automatically update the application"
@@ -1125,6 +1151,41 @@ msgstr ""
msgid "Invalid option value: \"%s\". Possible values are: %s."
msgstr "無効な設定値: \"%s\"。有効な値は: %sです。"
#, fuzzy
msgid "Joplin Export File"
msgstr "Evernote Exportファイル"
msgid "Markdown"
msgstr ""
msgid "Joplin Export Directory"
msgstr ""
#, fuzzy
msgid "Evernote Export File"
msgstr "Evernote Exportファイル"
#, javascript-format
msgid "Cannot load \"%s\" module for format \"%s\""
msgstr ""
#, javascript-format
msgid "Please specify import format for %s"
msgstr ""
#, javascript-format
msgid ""
"This item is currently encrypted: %s \"%s\". Please wait for all items to be "
"decrypted and try again."
msgstr ""
msgid "There is no data to export."
msgstr ""
#, fuzzy
msgid "Please specify the notebook where the notes should be imported to."
msgstr "同期状況の出力先を選択してください"
msgid "Items that cannot be synchronised"
msgstr "同期が出来なかったアイテム"
@@ -1294,6 +1355,24 @@ msgstr ""
msgid "Welcome"
msgstr "ようこそ"
#~ msgid "Imports an Evernote notebook file (.enex file)."
#~ msgstr "Evernoteノートブックファイル(.enex)のインポート"
#~ msgid ""
#~ "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
#~ msgstr ""
#~ "ファイル \"%s\" はノートブック \"%s\"に取り込まれます。よろしいですか?"
#~ msgid ""
#~ "New notebook \"%s\" will be created and file \"%s\" will be imported into "
#~ "it. Continue?"
#~ msgstr ""
#~ "新しいノートブック\"%s\"が作成され、ファイル\"%s\"が取り込まれます。よろし"
#~ "いですか?"
#~ msgid "Import Evernote notes"
#~ msgstr "Evernoteのインポート"
#~ msgid "Give focus to next pane"
#~ msgstr "次のペインへ"

View File

@@ -174,10 +174,14 @@ msgid "Exits the application."
msgstr ""
msgid ""
"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."
msgstr ""
#, javascript-format
msgid "Destination format: %s"
msgstr ""
msgid "Exports only the given note."
msgstr ""
@@ -232,22 +236,16 @@ msgid ""
"For the list of keyboard shortcuts and config options, type `help keymap`"
msgstr ""
msgid "Imports an Evernote notebook file (.enex file)."
msgid "Imports data into Joplin."
msgstr ""
#, javascript-format
msgid "Source format: %s"
msgstr ""
msgid "Do not ask for confirmation."
msgstr ""
#, javascript-format
msgid "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
msgstr ""
#, javascript-format
msgid ""
"New notebook \"%s\" will be created and file \"%s\" will be imported into "
"it. Continue?"
msgstr ""
#, javascript-format
msgid "Found: %d."
msgstr ""
@@ -488,9 +486,20 @@ msgid ""
"background and will be available soon."
msgstr ""
#, javascript-format
msgid "Exporting to \"%s\" as \"%s\" format. Please wait..."
msgstr ""
msgid "File"
msgstr ""
msgid "Directory"
msgstr ""
#, javascript-format
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
msgstr ""
msgid "New note"
msgstr ""
@@ -500,10 +509,10 @@ msgstr ""
msgid "New notebook"
msgstr ""
msgid "Import Evernote notes"
msgid "Import"
msgstr ""
msgid "Evernote Export Files"
msgid "Export"
msgstr ""
#, javascript-format
@@ -753,6 +762,12 @@ msgstr ""
msgid "Set alarm"
msgstr ""
#, javascript-format
msgid ""
"This note has no content. Click on \"%s\" to toggle the editor and edit the "
"note."
msgstr ""
msgid "to-do"
msgstr ""
@@ -772,9 +787,6 @@ msgstr ""
msgid "OneDrive Login"
msgstr ""
msgid "Import"
msgstr ""
msgid "Options"
msgstr ""
@@ -954,7 +966,13 @@ msgstr ""
msgid "Dark"
msgstr ""
msgid "Show uncompleted to-dos on top of the lists"
msgid "Uncompleted to-dos on top"
msgstr ""
msgid "Sort notes by"
msgstr ""
msgid "Reverse sort order"
msgstr ""
msgid "Save geo-location with notes"
@@ -975,7 +993,15 @@ msgstr ""
msgid "Show tray icon"
msgstr ""
msgid "Set application zoom percentage"
msgid "Global zoom percentage"
msgstr ""
msgid "Editor font family"
msgstr ""
msgid ""
"The font name will not be checked. If incorrect or empty, it will default to "
"a generic monospace font."
msgstr ""
msgid "Automatically update the application"
@@ -1037,6 +1063,38 @@ msgstr ""
msgid "Invalid option value: \"%s\". Possible values are: %s."
msgstr ""
msgid "Joplin Export File"
msgstr ""
msgid "Markdown"
msgstr ""
msgid "Joplin Export Directory"
msgstr ""
msgid "Evernote Export File"
msgstr ""
#, javascript-format
msgid "Cannot load \"%s\" module for format \"%s\""
msgstr ""
#, javascript-format
msgid "Please specify import format for %s"
msgstr ""
#, javascript-format
msgid ""
"This item is currently encrypted: %s \"%s\". Please wait for all items to be "
"decrypted and try again."
msgstr ""
msgid "There is no data to export."
msgstr ""
msgid "Please specify the notebook where the notes should be imported to."
msgstr ""
msgid "Items that cannot be synchronised"
msgstr ""

View File

@@ -187,14 +187,19 @@ msgstr "Notitie is opgeslaan."
msgid "Exits the application."
msgstr "Sluit de applicatie."
#, fuzzy
msgid ""
"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."
msgstr ""
"Exporteert Joplin gegevens naar de opgegeven folder. Standaard zal het de "
"volledige database exporteren, zoals notitieboeken, notities, tags en "
"middelen."
#, fuzzy, javascript-format
msgid "Destination format: %s"
msgstr "Datumnotatie"
msgid "Exports only the given note."
msgstr "Exporteert alleen de opgegeven notitie."
@@ -259,26 +264,16 @@ msgid ""
msgstr ""
"Voor de volledige lijst van beschikbare shortcuts, typ `help shortcuts`"
msgid "Imports an Evernote notebook file (.enex file)."
msgstr "Importeer een Evernote notitieboek (.enex bestand)."
msgid "Imports data into Joplin."
msgstr ""
#, fuzzy, javascript-format
msgid "Source format: %s"
msgstr "Geen commando gevonden: \"%s\""
msgid "Do not ask for confirmation."
msgstr "Vraag niet om bevestiging. "
#, javascript-format
msgid "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
msgstr ""
"Bestand \"%s\" zal toegevoegd worden aan bestaand notitieboek \"%s\". "
"Doorgaan?"
#, javascript-format
msgid ""
"New notebook \"%s\" will be created and file \"%s\" will be imported into "
"it. Continue?"
msgstr ""
"Nieuw notitieboek \"%s\" zal aangemaakt worden en bestand \"%s\" zal eraan "
"toegevoegd worden. Doorgaan?"
#, javascript-format
msgid "Found: %d."
msgstr "Gevonden: %d."
@@ -563,9 +558,20 @@ msgstr ""
"hoofdsleutel al ingegeven hebt, worden de versleutelde items ontsleuteld in "
"de achtergrond. Ze zijn binnenkort beschikbaar."
#, javascript-format
msgid "Exporting to \"%s\" as \"%s\" format. Please wait..."
msgstr ""
msgid "File"
msgstr "Bestand"
msgid "Directory"
msgstr ""
#, javascript-format
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
msgstr ""
msgid "New note"
msgstr "Nieuwe notitie"
@@ -575,11 +581,12 @@ msgstr "Nieuwe to-do"
msgid "New notebook"
msgstr "Nieuw notitieboek"
msgid "Import Evernote notes"
msgstr "Importeer Evernote notities"
msgid "Import"
msgstr "Importeer"
msgid "Evernote Export Files"
msgstr "Exporteer Evernote bestanden"
#, fuzzy
msgid "Export"
msgstr "Importeer"
#, javascript-format
msgid "Hide %s"
@@ -847,6 +854,12 @@ msgstr "Tags"
msgid "Set alarm"
msgstr "Zet melding"
#, javascript-format
msgid ""
"This note has no content. Click on \"%s\" to toggle the editor and edit the "
"note."
msgstr ""
#, fuzzy
msgid "to-do"
msgstr "Nieuwe to-do"
@@ -868,9 +881,6 @@ msgstr "Vrijmaken"
msgid "OneDrive Login"
msgstr "OneDrive Login"
msgid "Import"
msgstr "Importeer"
msgid "Options"
msgstr "Opties"
@@ -1065,9 +1075,16 @@ msgid "Dark"
msgstr "Donker"
#, fuzzy
msgid "Show uncompleted to-dos on top of the lists"
msgid "Uncompleted to-dos on top"
msgstr "Toon onvoltooide to-do's aan de top van de lijsten"
msgid "Sort notes by"
msgstr ""
#, fuzzy
msgid "Reverse sort order"
msgstr "Draait de sorteervolgorde om."
msgid "Save geo-location with notes"
msgstr "Sla geo-locatie op bij notities"
@@ -1088,7 +1105,15 @@ msgstr "Maakt een nieuwe notitie aan."
msgid "Show tray icon"
msgstr ""
msgid "Set application zoom percentage"
msgid "Global zoom percentage"
msgstr ""
msgid "Editor font family"
msgstr ""
msgid ""
"The font name will not be checked. If incorrect or empty, it will default to "
"a generic monospace font."
msgstr ""
msgid "Automatically update the application"
@@ -1154,6 +1179,41 @@ msgstr "Stel wachtwoord in"
msgid "Invalid option value: \"%s\". Possible values are: %s."
msgstr "Ongeldige optie: \"%s\". Geldige waarden zijn: %s."
#, fuzzy
msgid "Joplin Export File"
msgstr "Exporteer Evernote bestanden"
msgid "Markdown"
msgstr ""
msgid "Joplin Export Directory"
msgstr ""
#, fuzzy
msgid "Evernote Export File"
msgstr "Exporteer Evernote bestanden"
#, javascript-format
msgid "Cannot load \"%s\" module for format \"%s\""
msgstr ""
#, javascript-format
msgid "Please specify import format for %s"
msgstr ""
#, javascript-format
msgid ""
"This item is currently encrypted: %s \"%s\". Please wait for all items to be "
"decrypted and try again."
msgstr ""
msgid "There is no data to export."
msgstr ""
#, fuzzy
msgid "Please specify the notebook where the notes should be imported to."
msgstr "Selecteer waar de synchronisatie status naar geëxporteerd moet worden"
msgid "Items that cannot be synchronised"
msgstr "Items die niet gesynchroniseerd kunnen worden"
@@ -1326,6 +1386,25 @@ msgstr ""
msgid "Welcome"
msgstr "Welkom"
#~ msgid "Imports an Evernote notebook file (.enex file)."
#~ msgstr "Importeer een Evernote notitieboek (.enex bestand)."
#~ msgid ""
#~ "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
#~ msgstr ""
#~ "Bestand \"%s\" zal toegevoegd worden aan bestaand notitieboek \"%s\". "
#~ "Doorgaan?"
#~ msgid ""
#~ "New notebook \"%s\" will be created and file \"%s\" will be imported into "
#~ "it. Continue?"
#~ msgstr ""
#~ "Nieuw notitieboek \"%s\" zal aangemaakt worden en bestand \"%s\" zal "
#~ "eraan toegevoegd worden. Doorgaan?"
#~ msgid "Import Evernote notes"
#~ msgstr "Importeer Evernote notities"
#~ msgid "Give focus to next pane"
#~ msgstr "Focus op het volgende paneel"

View File

@@ -182,14 +182,19 @@ msgstr "Nota gravada."
msgid "Exits the application."
msgstr "Sai da aplicação."
#, fuzzy
msgid ""
"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."
msgstr ""
"Exporta os dados do Joplin para o diretório informado. Por padrão, ele "
"exportará o banco de dados completo, incluindo cadernos, notas, tags e "
"recursos."
#, fuzzy, javascript-format
msgid "Destination format: %s"
msgstr "Formato de data"
msgid "Exports only the given note."
msgstr "Exporta apenas a nota fornecida."
@@ -254,25 +259,16 @@ msgstr ""
"Para a lista completa de atalhos de teclado disponíveis, digite `help "
"shortcuts`"
msgid "Imports an Evernote notebook file (.enex file)."
msgstr "Importa um arquivo de caderno do Evernote (arquivo .enex)."
msgid "Imports data into Joplin."
msgstr ""
#, fuzzy, javascript-format
msgid "Source format: %s"
msgstr "Comando inválido: \"%s\""
msgid "Do not ask for confirmation."
msgstr "Não pedir confirmação."
#, javascript-format
msgid "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
msgstr ""
"O arquivo \"%s\" será importado para o caderno existente \"%s\". Continuar?"
#, javascript-format
msgid ""
"New notebook \"%s\" will be created and file \"%s\" will be imported into "
"it. Continue?"
msgstr ""
"O novo caderno \"%s\" será criado e o arquivo \"%s\" será importado para "
"ele. Continuar?"
#, javascript-format
msgid "Found: %d."
msgstr "Encontrado: %d."
@@ -540,9 +536,20 @@ msgid ""
"background and will be available soon."
msgstr ""
#, javascript-format
msgid "Exporting to \"%s\" as \"%s\" format. Please wait..."
msgstr ""
msgid "File"
msgstr "Arquivo"
msgid "Directory"
msgstr ""
#, javascript-format
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
msgstr ""
msgid "New note"
msgstr "Nova nota"
@@ -552,11 +559,12 @@ msgstr "Nova tarefa"
msgid "New notebook"
msgstr "Novo caderno"
msgid "Import Evernote notes"
msgstr "Importar notas do Evernote"
msgid "Import"
msgstr "Importar"
msgid "Evernote Export Files"
msgstr "Arquivos de Exportação do Evernote"
#, fuzzy
msgid "Export"
msgstr "Importar"
#, javascript-format
msgid "Hide %s"
@@ -816,6 +824,12 @@ msgstr "Tags"
msgid "Set alarm"
msgstr "Definir alarme"
#, javascript-format
msgid ""
"This note has no content. Click on \"%s\" to toggle the editor and edit the "
"note."
msgstr ""
#, fuzzy
msgid "to-do"
msgstr "Nova tarefa"
@@ -837,9 +851,6 @@ msgstr "Limpar (clear)"
msgid "OneDrive Login"
msgstr "Login no OneDrive"
msgid "Import"
msgstr "Importar"
msgid "Options"
msgstr "Opções"
@@ -1034,9 +1045,16 @@ msgid "Dark"
msgstr "Dark"
#, fuzzy
msgid "Show uncompleted to-dos on top of the lists"
msgid "Uncompleted to-dos on top"
msgstr "Mostrar tarefas incompletas no topo das listas"
msgid "Sort notes by"
msgstr ""
#, fuzzy
msgid "Reverse sort order"
msgstr "Inverte a ordem de classificação."
msgid "Save geo-location with notes"
msgstr "Salvar geolocalização com notas"
@@ -1058,7 +1076,15 @@ msgstr "Cria uma nova nota."
msgid "Show tray icon"
msgstr ""
msgid "Set application zoom percentage"
msgid "Global zoom percentage"
msgstr ""
msgid "Editor font family"
msgstr ""
msgid ""
"The font name will not be checked. If incorrect or empty, it will default to "
"a generic monospace font."
msgstr ""
msgid "Automatically update the application"
@@ -1122,6 +1148,41 @@ msgstr ""
msgid "Invalid option value: \"%s\". Possible values are: %s."
msgstr "Valor da opção inválida: \"%s\". Os valores possíveis são: %s."
#, fuzzy
msgid "Joplin Export File"
msgstr "Arquivos de Exportação do Evernote"
msgid "Markdown"
msgstr ""
msgid "Joplin Export Directory"
msgstr ""
#, fuzzy
msgid "Evernote Export File"
msgstr "Arquivos de Exportação do Evernote"
#, javascript-format
msgid "Cannot load \"%s\" module for format \"%s\""
msgstr ""
#, javascript-format
msgid "Please specify import format for %s"
msgstr ""
#, javascript-format
msgid ""
"This item is currently encrypted: %s \"%s\". Please wait for all items to be "
"decrypted and try again."
msgstr ""
msgid "There is no data to export."
msgstr ""
#, fuzzy
msgid "Please specify the notebook where the notes should be imported to."
msgstr "Por favor, primeiro, selecione a nota ou caderno a excluir."
msgid "Items that cannot be synchronised"
msgstr ""
@@ -1289,6 +1350,25 @@ msgstr "Você não possui cadernos. Crie um clicando no botão (+)."
msgid "Welcome"
msgstr "Bem-vindo"
#~ msgid "Imports an Evernote notebook file (.enex file)."
#~ msgstr "Importa um arquivo de caderno do Evernote (arquivo .enex)."
#~ msgid ""
#~ "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
#~ msgstr ""
#~ "O arquivo \"%s\" será importado para o caderno existente \"%s\". "
#~ "Continuar?"
#~ msgid ""
#~ "New notebook \"%s\" will be created and file \"%s\" will be imported into "
#~ "it. Continue?"
#~ msgstr ""
#~ "O novo caderno \"%s\" será criado e o arquivo \"%s\" será importado para "
#~ "ele. Continuar?"
#~ msgid "Import Evernote notes"
#~ msgstr "Importar notas do Evernote"
#~ msgid "Give focus to next pane"
#~ msgstr "Dar o foco para o próximo painel"

View File

@@ -189,13 +189,18 @@ msgstr "Заметка сохранена."
msgid "Exits the application."
msgstr "Выход из приложения."
#, fuzzy
msgid ""
"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."
msgstr ""
"Экспортирует данные Joplin в заданный каталог. По умолчанию экспортируется "
"полная база данных, включая блокноты, заметки, теги и ресурсы."
#, fuzzy, javascript-format
msgid "Destination format: %s"
msgstr "Формат даты"
msgid "Exports only the given note."
msgstr "Экспортирует только заданную заметку."
@@ -261,24 +266,16 @@ msgstr ""
"Для просмотра списка доступных клавиатурных сочетаний введите `help "
"shortcuts`"
msgid "Imports an Evernote notebook file (.enex file)."
msgstr "Импортирует файл блокнотов Evernote (.enex-файл)."
msgid "Imports data into Joplin."
msgstr ""
#, fuzzy, javascript-format
msgid "Source format: %s"
msgstr "Нет такой команды: %s"
msgid "Do not ask for confirmation."
msgstr "Не запрашивать подтверждение."
#, javascript-format
msgid "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
msgstr "Файл «%s» будет импортирован в существующий блокнот «%s». Продолжить?"
#, javascript-format
msgid ""
"New notebook \"%s\" will be created and file \"%s\" will be imported into "
"it. Continue?"
msgstr ""
"Будет создан новый блокнот «%s» и в него будет импортирован файл «%s». "
"Продолжить?"
#, javascript-format
msgid "Found: %d."
msgstr "Найдено: %d."
@@ -563,9 +560,20 @@ msgstr ""
"decrypt». Если пароль уже был вами предоставлен, зашифрованные элементы "
"расшифруются в фоновом режиме и вскоре станут доступны."
#, javascript-format
msgid "Exporting to \"%s\" as \"%s\" format. Please wait..."
msgstr ""
msgid "File"
msgstr "Файл"
msgid "Directory"
msgstr ""
#, javascript-format
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
msgstr ""
msgid "New note"
msgstr "Новая заметка"
@@ -575,11 +583,12 @@ msgstr "Новая задача"
msgid "New notebook"
msgstr "Новый блокнот"
msgid "Import Evernote notes"
msgstr "Импортировать заметки из Evernote"
msgid "Import"
msgstr "Импорт"
msgid "Evernote Export Files"
msgstr "Файлы экспорта Evernote"
#, fuzzy
msgid "Export"
msgstr "Импорт"
#, javascript-format
msgid "Hide %s"
@@ -845,6 +854,12 @@ msgstr "Теги"
msgid "Set alarm"
msgstr "Установить напоминание"
#, javascript-format
msgid ""
"This note has no content. Click on \"%s\" to toggle the editor and edit the "
"note."
msgstr ""
#, fuzzy
msgid "to-do"
msgstr "Новая задача"
@@ -866,9 +881,6 @@ msgstr "Очистить"
msgid "OneDrive Login"
msgstr "Вход в OneDrive"
msgid "Import"
msgstr "Импорт"
msgid "Options"
msgstr "Настройки"
@@ -1061,9 +1073,16 @@ msgid "Dark"
msgstr "Тёмная"
#, fuzzy
msgid "Show uncompleted to-dos on top of the lists"
msgid "Uncompleted to-dos on top"
msgstr "Показывать незавершённые задачи вверху списков"
msgid "Sort notes by"
msgstr ""
#, fuzzy
msgid "Reverse sort order"
msgstr "Обращает порядок сортировки."
msgid "Save geo-location with notes"
msgstr "Сохранять информацию о геолокации в заметках"
@@ -1082,9 +1101,18 @@ msgstr "При создании новой заметки:"
msgid "Show tray icon"
msgstr ""
msgid "Set application zoom percentage"
#, fuzzy
msgid "Global zoom percentage"
msgstr "Масштаб приложения в процентах"
msgid "Editor font family"
msgstr ""
msgid ""
"The font name will not be checked. If incorrect or empty, it will default to "
"a generic monospace font."
msgstr ""
msgid "Automatically update the application"
msgstr "Автоматически обновлять приложение"
@@ -1151,6 +1179,41 @@ msgstr "Установить пароль"
msgid "Invalid option value: \"%s\". Possible values are: %s."
msgstr "Неверное значение параметра: «%s». Доступные значения: %s."
#, fuzzy
msgid "Joplin Export File"
msgstr "Файлы экспорта Evernote"
msgid "Markdown"
msgstr ""
msgid "Joplin Export Directory"
msgstr ""
#, fuzzy
msgid "Evernote Export File"
msgstr "Файлы экспорта Evernote"
#, javascript-format
msgid "Cannot load \"%s\" module for format \"%s\""
msgstr ""
#, javascript-format
msgid "Please specify import format for %s"
msgstr ""
#, javascript-format
msgid ""
"This item is currently encrypted: %s \"%s\". Please wait for all items to be "
"decrypted and try again."
msgstr ""
msgid "There is no data to export."
msgstr ""
#, fuzzy
msgid "Please specify the notebook where the notes should be imported to."
msgstr "Выберите, куда должен быть экспортирован статус синхронизации"
msgid "Items that cannot be synchronised"
msgstr "Элементы, которые не могут быть синхронизированы"
@@ -1321,6 +1384,24 @@ msgstr "У вас сейчас нет блокнота. Создайте его
msgid "Welcome"
msgstr "Добро пожаловать"
#~ msgid "Imports an Evernote notebook file (.enex file)."
#~ msgstr "Импортирует файл блокнотов Evernote (.enex-файл)."
#~ msgid ""
#~ "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
#~ msgstr ""
#~ "Файл «%s» будет импортирован в существующий блокнот «%s». Продолжить?"
#~ msgid ""
#~ "New notebook \"%s\" will be created and file \"%s\" will be imported into "
#~ "it. Continue?"
#~ msgstr ""
#~ "Будет создан новый блокнот «%s» и в него будет импортирован файл «%s». "
#~ "Продолжить?"
#~ msgid "Import Evernote notes"
#~ msgstr "Импортировать заметки из Evernote"
#~ msgid "Give focus to next pane"
#~ msgstr "Переключиться на следующую панель"

View File

@@ -177,13 +177,18 @@ msgstr "笔记已被保存。"
msgid "Exits the application."
msgstr "退出程序。"
#, fuzzy
msgid ""
"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."
msgstr ""
"导出Joplin数据至给定文件目录。默认为导出所有的数据库,包含笔记本、笔记、标签"
"及资源。"
#, fuzzy, javascript-format
msgid "Destination format: %s"
msgstr "日期格式"
msgid "Exports only the given note."
msgstr "仅导出给定笔记。"
@@ -242,22 +247,16 @@ msgid ""
"For the list of keyboard shortcuts and config options, type `help keymap`"
msgstr "输入`help shortcuts`显示全部可用的快捷键列表。"
msgid "Imports an Evernote notebook file (.enex file)."
msgstr "导入Evernote笔记本文件(.enex文件)。"
msgid "Imports data into Joplin."
msgstr ""
#, fuzzy, javascript-format
msgid "Source format: %s"
msgstr "无以下命令:%s"
msgid "Do not ask for confirmation."
msgstr "不再要求确认。"
#, javascript-format
msgid "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
msgstr "文件\"%s\"将会被导入至现有笔记本\"%s\"。是否继续?"
#, javascript-format
msgid ""
"New notebook \"%s\" will be created and file \"%s\" will be imported into "
"it. Continue?"
msgstr "将创建新笔记本\"%s\"并将文件\"%s\"导入至其中。是否继续?"
#, javascript-format
msgid "Found: %d."
msgstr "已找到:%d条。"
@@ -512,9 +511,20 @@ msgid ""
"background and will be available soon."
msgstr ""
#, javascript-format
msgid "Exporting to \"%s\" as \"%s\" format. Please wait..."
msgstr ""
msgid "File"
msgstr "文件"
msgid "Directory"
msgstr ""
#, javascript-format
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
msgstr ""
msgid "New note"
msgstr "新笔记"
@@ -524,11 +534,12 @@ msgstr "新待办事项"
msgid "New notebook"
msgstr "新笔记本"
msgid "Import Evernote notes"
msgstr "导入Evernote笔记"
msgid "Import"
msgstr "导入"
msgid "Evernote Export Files"
msgstr "Evernote导出文件"
#, fuzzy
msgid "Export"
msgstr "导入"
#, javascript-format
msgid "Hide %s"
@@ -785,6 +796,12 @@ msgstr "标签"
msgid "Set alarm"
msgstr "设置提醒"
#, javascript-format
msgid ""
"This note has no content. Click on \"%s\" to toggle the editor and edit the "
"note."
msgstr ""
#, fuzzy
msgid "to-do"
msgstr "新待办事项"
@@ -806,9 +823,6 @@ msgstr "清除"
msgid "OneDrive Login"
msgstr "登陆OneDrive"
msgid "Import"
msgstr "导入"
msgid "Options"
msgstr "选项"
@@ -996,9 +1010,16 @@ msgid "Dark"
msgstr "深色"
#, fuzzy
msgid "Show uncompleted to-dos on top of the lists"
msgid "Uncompleted to-dos on top"
msgstr "在列表上方显示未完成的待办事项"
msgid "Sort notes by"
msgstr ""
#, fuzzy
msgid "Reverse sort order"
msgstr "反转排序顺序。"
msgid "Save geo-location with notes"
msgstr "保存笔记时同时保存地理定位信息"
@@ -1020,7 +1041,15 @@ msgstr "创建新笔记。"
msgid "Show tray icon"
msgstr ""
msgid "Set application zoom percentage"
msgid "Global zoom percentage"
msgstr ""
msgid "Editor font family"
msgstr ""
msgid ""
"The font name will not be checked. If incorrect or empty, it will default to "
"a generic monospace font."
msgstr ""
msgid "Automatically update the application"
@@ -1082,6 +1111,41 @@ msgstr ""
msgid "Invalid option value: \"%s\". Possible values are: %s."
msgstr "无效的选项值:\"%s\"。可用值为:%s。"
#, fuzzy
msgid "Joplin Export File"
msgstr "Evernote导出文件"
msgid "Markdown"
msgstr ""
msgid "Joplin Export Directory"
msgstr ""
#, fuzzy
msgid "Evernote Export File"
msgstr "Evernote导出文件"
#, javascript-format
msgid "Cannot load \"%s\" module for format \"%s\""
msgstr ""
#, javascript-format
msgid "Please specify import format for %s"
msgstr ""
#, javascript-format
msgid ""
"This item is currently encrypted: %s \"%s\". Please wait for all items to be "
"decrypted and try again."
msgstr ""
msgid "There is no data to export."
msgstr ""
#, fuzzy
msgid "Please specify the notebook where the notes should be imported to."
msgstr "请选择最先删除的笔记或笔记本。"
msgid "Items that cannot be synchronised"
msgstr "项目无法被同步。"
@@ -1247,6 +1311,21 @@ msgstr "您当前没有任何笔记本。点击(+)按钮创建新笔记本。"
msgid "Welcome"
msgstr "欢迎"
#~ msgid "Imports an Evernote notebook file (.enex file)."
#~ msgstr "导入Evernote笔记本文件(.enex文件)。"
#~ msgid ""
#~ "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
#~ msgstr "文件\"%s\"将会被导入至现有笔记本\"%s\"。是否继续?"
#~ msgid ""
#~ "New notebook \"%s\" will be created and file \"%s\" will be imported into "
#~ "it. Continue?"
#~ msgstr "将创建新笔记本\"%s\"并将文件\"%s\"导入至其中。是否继续?"
#~ msgid "Import Evernote notes"
#~ msgstr "导入Evernote笔记"
#~ msgid "Give focus to next pane"
#~ msgstr "聚焦于下个面板"

View File

@@ -1,6 +1,6 @@
{
"name": "joplin",
"version": "1.0.98",
"version": "1.0.100",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -435,6 +435,14 @@
"universalify": "0.1.1"
}
},
"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.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -858,9 +866,9 @@
}
},
"minizlib": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.0.4.tgz",
"integrity": "sha512-sN4U9tIJtBRwKbwgFh9qJfrPIQ/GGTRr1MGqkgOeMTLy8/lM0FcWU//FqlnZ3Vb7gJ+Mxh3FOg1EklibdajbaQ==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.0.tgz",
"integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==",
"requires": {
"minipass": "2.2.1"
}
@@ -1161,6 +1169,20 @@
"semver": "5.4.1",
"simple-get": "2.7.0",
"tar": "3.2.1"
},
"dependencies": {
"tar": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-3.2.1.tgz",
"integrity": "sha512-ZSzds1E0IqutvMU8HxjMaU8eB7urw2fGwTq88ukDOVuUIh0656l7/P7LiVPxhO5kS4flcRJQk8USG+cghQbTUQ==",
"requires": {
"chownr": "1.0.1",
"minipass": "2.2.1",
"minizlib": "1.1.0",
"mkdirp": "0.5.1",
"yallist": "3.0.2"
}
}
}
},
"simple-concat": {
@@ -2005,13 +2027,14 @@
"integrity": "sha1-Kb9hXUqnEhvdiYsi1LP5vE4qoD0="
},
"tar": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-3.2.1.tgz",
"integrity": "sha512-ZSzds1E0IqutvMU8HxjMaU8eB7urw2fGwTq88ukDOVuUIh0656l7/P7LiVPxhO5kS4flcRJQk8USG+cghQbTUQ==",
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.0.tgz",
"integrity": "sha512-gJlTiiErwo96K904FnoYWl+5+FBgS+FimU6GMh66XLdLa55al8+d4jeDfPoGwSNHdtWI5FJP6xurmVqhBuGJpQ==",
"requires": {
"chownr": "1.0.1",
"fs-minipass": "1.2.5",
"minipass": "2.2.1",
"minizlib": "1.0.4",
"minizlib": "1.1.0",
"mkdirp": "0.5.1",
"yallist": "3.0.2"
}

View File

@@ -19,7 +19,7 @@
],
"owner": "Laurent Cozic"
},
"version": "1.0.98",
"version": "1.0.100",
"bin": {
"joplin": "./main.js"
},
@@ -44,7 +44,6 @@
"node-emoji": "^1.8.1",
"node-fetch": "^1.7.1",
"node-persist": "^2.1.0",
"os-tmpdir": "^1.0.2",
"promise": "^7.1.1",
"proper-lockfile": "^2.0.1",
"query-string": "4.3.4",
@@ -57,6 +56,7 @@
"string-padding": "^1.0.2",
"string-to-stream": "^1.1.0",
"strip-ansi": "^4.0.0",
"tar": "^4.4.0",
"tcp-port-used": "^0.1.2",
"tkwidgets": "^0.5.25",
"url-parse": "^1.2.0",

View File

@@ -9,7 +9,7 @@ rsync -a "$ROOT_DIR/build/locales/" "$BUILD_DIR/locales/"
mkdir -p "$BUILD_DIR/data"
if [[ $TEST_FILE == "" ]]; then
(cd "$ROOT_DIR" && npm test tests-build/synchronizer.js tests-build/encryption.js tests-build/ArrayUtils.js tests-build/models_Setting.js)
(cd "$ROOT_DIR" && npm test tests-build/synchronizer.js tests-build/encryption.js tests-build/ArrayUtils.js tests-build/models_Setting.js tests-build/services_InteropService.js)
else
(cd "$ROOT_DIR" && npm test tests-build/$TEST_FILE.js)
fi

View File

@@ -0,0 +1,252 @@
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const InteropService = require('lib/services/InteropService.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');
const NoteTag = require('lib/models/NoteTag.js');
const Resource = require('lib/models/Resource.js');
const fs = require('fs-extra');
const ArrayUtils = require('lib/ArrayUtils');
const ObjectUtils = require('lib/ObjectUtils');
const { shim } = require('lib/shim.js');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
function exportDir() {
return __dirname + '/export';
}
function fieldsEqual(model1, model2, fieldNames) {
for (let i = 0; i < fieldNames.length; i++) {
const f = fieldNames[i];
expect(model1[f]).toBe(model2[f], 'For key ' + f);
}
}
describe('services_InteropService', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
const dir = exportDir();
await fs.remove(dir);
await fs.mkdirp(dir);
done();
});
it('should export and import folders', asyncTest(async () => {
const service = new InteropService();
let folder1 = await Folder.save({ title: "folder1" });
folder1 = await Folder.load(folder1.id);
const filePath = exportDir() + '/test.jex';
await service.export({ path: filePath });
await Folder.delete(folder1.id);
await service.import({ path: filePath });
// Check that a new folder, with a new ID, has been created
expect(await Folder.count()).toBe(1);
let folder2 = (await Folder.all())[0];
expect(folder2.id).not.toBe(folder1.id);
expect(folder2.title).toBe(folder1.title);
await service.import({ path: filePath });
// As there was already a folder with the same title, check that the new one has been renamed
await Folder.delete(folder2.id);
let folder3 = (await Folder.all())[0];
expect(await Folder.count()).toBe(1);
expect(folder3.title).not.toBe(folder2.title);
let fieldNames = Folder.fieldNames();
fieldNames = ArrayUtils.removeElement(fieldNames, 'id');
fieldNames = ArrayUtils.removeElement(fieldNames, 'title');
fieldsEqual(folder3, folder1, fieldNames);
}));
it('should export and import folders and notes', asyncTest(async () => {
const service = new InteropService();
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
note1 = await Note.load(note1.id);
const filePath = exportDir() + '/test.jex';
await service.export({ path: filePath });
await Folder.delete(folder1.id);
await Note.delete(note1.id);
await service.import({ path: filePath });
expect(await Note.count()).toBe(1);
let note2 = (await Note.all())[0];
let folder2 = (await Folder.all())[0];
expect(note1.parent_id).not.toBe(note2.parent_id);
expect(note1.id).not.toBe(note2.id);
expect(note2.parent_id).toBe(folder2.id);
let fieldNames = Note.fieldNames();
fieldNames = ArrayUtils.removeElement(fieldNames, 'id');
fieldNames = ArrayUtils.removeElement(fieldNames, 'parent_id');
fieldsEqual(note1, note2, fieldNames);
await service.import({ path: filePath });
note2 = (await Note.all())[0];
let note3 = (await Note.all())[1];
expect(note2.id).not.toBe(note3.id);
expect(note2.parent_id).not.toBe(note3.parent_id);
fieldsEqual(note2, note3, fieldNames);
}));
it('should export and import notes to specific folder', asyncTest(async () => {
const service = new InteropService();
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
note1 = await Note.load(note1.id);
const filePath = exportDir() + '/test.jex';
await service.export({ path: filePath });
await Note.delete(note1.id);
await service.import({ path: filePath, destinationFolderId: folder1.id });
expect(await Note.count()).toBe(1);
expect(await Folder.count()).toBe(1);
expect(await checkThrowAsync(async () => await service.import({ path: filePath, destinationFolderId: 'oops' }))).toBe(true);
}));
it('should export and import tags', asyncTest(async () => {
const service = new InteropService();
const filePath = exportDir() + '/test.jex';
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
let tag1 = await Tag.save({ title: 'mon tag' });
tag1 = await Tag.load(tag1.id);
await Tag.addNote(tag1.id, note1.id);
await service.export({ path: filePath });
await Folder.delete(folder1.id);
await Note.delete(note1.id);
await Tag.delete(tag1.id);
await service.import({ path: filePath });
expect(await Tag.count()).toBe(1);
let tag2 = (await Tag.all())[0];
let note2 = (await Note.all())[0];
expect(tag1.id).not.toBe(tag2.id);
let fieldNames = Note.fieldNames();
fieldNames = ArrayUtils.removeElement(fieldNames, 'id');
fieldsEqual(tag1, tag2, fieldNames);
let noteIds = await Tag.noteIds(tag2.id);
expect(noteIds.length).toBe(1);
expect(noteIds[0]).toBe(note2.id);
await service.import({ path: filePath });
// If importing again, no new tag should be created as one with
// the same name already existed. The newly imported note should
// however go under that already existing tag.
expect(await Tag.count()).toBe(1);
noteIds = await Tag.noteIds(tag2.id);
expect(noteIds.length).toBe(2);
}));
it('should export and import resources', asyncTest(async () => {
const service = new InteropService();
const filePath = exportDir() + '/test.jex';
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
note1 = await Note.load(note1.id);
let resourceIds = Note.linkedResourceIds(note1.body);
let resource1 = await Resource.load(resourceIds[0]);
await service.export({ path: filePath });
await Note.delete(note1.id);
await service.import({ path: filePath });
expect(await Resource.count()).toBe(2);
let note2 = (await Note.all())[0];
expect(note2.body).not.toBe(note1.body);
resourceIds = Note.linkedResourceIds(note2.body);
expect(resourceIds.length).toBe(1);
let resource2 = await Resource.load(resourceIds[0]);
expect(resource2.id).not.toBe(resource1.id);
let fieldNames = Note.fieldNames();
fieldNames = ArrayUtils.removeElement(fieldNames, 'id');
fieldsEqual(resource1, resource2, fieldNames);
const resourcePath1 = Resource.fullPath(resource1);
const resourcePath2 = Resource.fullPath(resource2);
expect(resourcePath1).not.toBe(resourcePath2);
expect(fileContentEqual(resourcePath1, resourcePath2)).toBe(true);
}));
it('should export and import single notes', asyncTest(async () => {
const service = new InteropService();
const filePath = exportDir() + '/test.jex';
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
await service.export({ path: filePath, sourceNoteIds: [note1.id] });
await Note.delete(note1.id);
await Folder.delete(folder1.id);
await service.import({ path: filePath });
expect(await Note.count()).toBe(1);
expect(await Folder.count()).toBe(1);
let folder2 = (await Folder.all())[0];
expect(folder2.title).toBe('test');
}));
it('should export and import single folders', asyncTest(async () => {
const service = new InteropService();
const filePath = exportDir() + '/test.jex';
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
await service.export({ path: filePath, sourceFolderIds: [folder1.id] });
await Note.delete(note1.id);
await Folder.delete(folder1.id);
await service.import({ path: filePath });
expect(await Note.count()).toBe(1);
expect(await Folder.count()).toBe(1);
let folder2 = (await Folder.all())[0];
expect(folder2.title).toBe('folder1');
}));
});

View File

@@ -41,13 +41,14 @@ class ElectronAppWrapper {
const windowState = windowStateKeeper({
defaultWidth: 800,
defaultHeight: 600,
file: 'window-state-' + this.env_ + '.json',
});
const windowOptions = {
'x': windowState.x,
'y': windowState.y,
'width': windowState.width,
'height': windowState.height,
x: windowState.x,
y: windowState.y,
width: windowState.width,
height: windowState.height,
};
// Linux icon workaround for bug https://github.com/electron-userland/electron-builder/issues/2098
@@ -78,7 +79,7 @@ class ElectronAppWrapper {
this.win_ = null;
} else {
event.preventDefault();
this.win_.hide();
this.hide();
}
} else {
if (this.trayShown() && !this.willQuitApp_) {
@@ -117,6 +118,12 @@ class ElectronAppWrapper {
return !!this.tray_;
}
// This method is used in macOS only to hide the whole app (and not just the main window)
// including the menu bar. This follows the macOS way of hidding an app.
hide() {
this.electronApp_.hide();
}
buildDir() {
if (this.buildDir_) return this.buildDir_;
let dir = __dirname + '/build';
@@ -129,10 +136,24 @@ class ElectronAppWrapper {
return dir;
}
trayIconFilename_() {
let output = '';
if (process.platform === 'darwin') {
output = 'macos-16x16Template.png'; // Electron Template Image format
} else {
output = '16x16.png';
}
if (this.env_ === 'dev') output = '16x16-dev.png'
return output;
}
// Note: this must be called only after the "ready" event of the app has been dispatched
createTray(contextMenu) {
try {
this.tray_ = new Tray(this.buildDir() + '/icons/16x16.png')
this.tray_ = new Tray(this.buildDir() + '/icons/' + this.trayIconFilename_())
this.tray_.setToolTip(this.electronApp_.getName())
this.tray_.setContextMenu(contextMenu)
@@ -150,11 +171,32 @@ class ElectronAppWrapper {
this.tray_ = null;
}
ensureSingleInstance() {
if (this.env_ === 'dev') return false;
return new Promise((resolve, reject) => {
const alreadyRunning = this.electronApp_.makeSingleInstance((commandLine, workingDirectory) => {
const win = this.window();
if (!win) return;
if (win.isMinimized()) win.restore();
win.show();
win.focus();
});
if (alreadyRunning) this.electronApp_.quit();
resolve(alreadyRunning);
});
}
async start() {
// Since we are doing other async things before creating the window, we might miss
// the "ready" event. So we use the function below to make sure that the app is ready.
await this.waitForElectronAppReady();
const alreadyRunning = await this.ensureSingleInstance();
if (alreadyRunning) return;
this.createWindow();
this.electronApp_.on('before-quit', () => {

View File

@@ -0,0 +1,51 @@
const { _ } = require('lib/locale');
const { bridge } = require('electron').remote.require('./bridge');
const InteropService = require('lib/services/InteropService');
class InteropServiceHelper {
static async export(dispatch, module, options = null) {
if (!options) options = {};
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];
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;
if (options.sourceFolderIds) exportOptions.sourceFolderIds = options.sourceFolderIds;
if (options.sourceNoteIds) exportOptions.sourceNoteIds = options.sourceNoteIds;
const service = new InteropService();
const result = await service.export(exportOptions);
console.info('Export result: ', result);
dispatch({
type: 'WINDOW_COMMAND',
name: 'hideModalMessage',
});
}
}
module.exports = InteropServiceHelper;

View File

@@ -20,6 +20,8 @@ const packageInfo = require('./packageInfo.js');
const AlarmService = require('lib/services/AlarmService.js');
const AlarmServiceDriverNode = require('lib/services/AlarmServiceDriverNode');
const DecryptionWorker = require('lib/services/DecryptionWorker');
const InteropService = require('lib/services/InteropService');
const InteropServiceHelper = require('./InteropServiceHelper.js');
const { bridge } = require('electron').remote.require('./bridge');
const Menu = bridge().Menu;
@@ -144,6 +146,10 @@ class Application extends BaseApplication {
this.updateTray();
}
if (action.type == 'SETTING_UPDATE_ONE' && action.key == 'style.editor.fontFamily' || action.type == 'SETTING_UPDATE_ALL') {
this.updateEditorFont();
}
if (['NOTE_UPDATE_ONE', 'NOTE_DELETE', 'FOLDER_UPDATE_ONE', 'FOLDER_DELETE'].indexOf(action.type) >= 0) {
if (!await reg.syncTarget().syncStarted()) reg.scheduleSync();
}
@@ -175,6 +181,94 @@ class Application extends BaseApplication {
updateMenu(screen) {
if (this.lastMenuScreen_ === screen) return;
const sortNoteItems = [];
const sortNoteOptions = Setting.enumOptions('notes.sortOrder.field');
for (let field in sortNoteOptions) {
if (!sortNoteOptions.hasOwnProperty(field)) continue;
sortNoteItems.push({
label: sortNoteOptions[field],
screens: ['Main'],
type: 'checkbox',
checked: Setting.value('notes.sortOrder.field') === field,
click: () => {
Setting.setValue('notes.sortOrder.field', field);
this.refreshMenu();
}
});
}
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 () => {
await InteropServiceHelper.export(this.dispatch.bind(this), module);
}
});
} 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 = [
{
label: _('File'),
@@ -210,25 +304,31 @@ class Application extends BaseApplication {
}
}, {
type: 'separator',
}, {
label: _('Import Evernote notes'),
click: () => {
const filePaths = bridge().showOpenDialog({
properties: ['openFile', 'createDirectory'],
filters: [
{ name: _('Evernote Export Files'), extensions: ['enex'] },
]
});
if (!filePaths || !filePaths.length) return;
// }, {
// label: _('Import Evernote notes'),
// click: () => {
// const filePaths = bridge().showOpenDialog({
// properties: ['openFile', 'createDirectory'],
// filters: [
// { name: _('Evernote Export Files'), extensions: ['enex'] },
// ]
// });
// if (!filePaths || !filePaths.length) return;
this.dispatch({
type: 'NAV_GO',
routeName: 'Import',
props: {
filePath: filePaths[0],
},
});
}
// this.dispatch({
// type: 'NAV_GO',
// routeName: 'Import',
// props: {
// filePath: filePaths[0],
// },
// });
// }
}, {
label: _('Import'),
submenu: importItems,
}, {
label: _('Export'),
submenu: exportItems,
}, {
type: 'separator',
platforms: ['darwin'],
@@ -236,7 +336,7 @@ class Application extends BaseApplication {
label: _('Hide %s', 'Joplin'),
platforms: ['darwin'],
accelerator: 'CommandOrControl+H',
click: () => { bridge().window().hide() }
click: () => { bridge().electronApp().hide() }
}, {
type: 'separator',
}, {
@@ -287,6 +387,29 @@ class Application extends BaseApplication {
name: 'toggleVisiblePanes',
});
}
}, {
type: 'separator',
screens: ['Main'],
}, {
label: Setting.settingMetadata('notes.sortOrder.field').label(),
screens: ['Main'],
submenu: sortNoteItems,
}, {
label: Setting.settingMetadata('notes.sortOrder.reverse').label(),
type: 'checkbox',
checked: Setting.value('notes.sortOrder.reverse'),
screens: ['Main'],
click: () => {
Setting.setValue('notes.sortOrder.reverse', !Setting.value('notes.sortOrder.reverse'));
},
}, {
label: Setting.settingMetadata('uncompletedTodosOnTop').label(),
type: 'checkbox',
checked: Setting.value('uncompletedTodosOnTop'),
screens: ['Main'],
click: () => {
Setting.setValue('uncompletedTodosOnTop', !Setting.value('uncompletedTodosOnTop'));
},
}],
}, {
label: _('Tools'),
@@ -340,8 +463,8 @@ class Application extends BaseApplication {
'Copyright © 2016-2018 Laurent Cozic',
_('%s %s (%s, %s)', p.name, p.version, Setting.value('env'), process.platform),
];
bridge().showMessageBox({
message: message.join('\n'),
bridge().showInfoMessageBox(message.join('\n'), {
icon: bridge().electronApp().buildDir() + '/icons/32x32.png',
});
}
}]
@@ -401,6 +524,21 @@ class Application extends BaseApplication {
}
}
updateEditorFont() {
const fontFamilies = [];
if (Setting.value('style.editor.fontFamily')) fontFamilies.push('"' + Setting.value('style.editor.fontFamily') + '"');
fontFamilies.push('monospace');
// The '*' and '!important' parts are necessary to make sure Russian text is displayed properly
// https://github.com/laurent22/joplin/issues/155
const css = '.ace_editor * { font-family: ' + fontFamilies.join(', ') + ' !important; }';
const styleTag = document.createElement('style');
styleTag.type = 'text/css';
styleTag.appendChild(document.createTextNode(css));
document.head.appendChild(styleTag);
}
async start(argv) {
argv = await super.start(argv);

View File

@@ -62,20 +62,23 @@ class Bridge {
return filePaths;
}
showMessageBox(window, options) {
// Don't use this directly - call one of the showXxxxxxxMessageBox() instead
showMessageBox_(window, options) {
const {dialog} = require('electron');
const nativeImage = require('electron').nativeImage
if (!window) window = this.window();
return dialog.showMessageBox(window, options);
}
showErrorMessageBox(message) {
return this.showMessageBox(this.window(), {
return this.showMessageBox_(this.window(), {
type: 'error',
message: message,
});
}
showConfirmMessageBox(message) {
const result = this.showMessageBox(this.window(), {
const result = this.showMessageBox_(this.window(), {
type: 'question',
message: message,
buttons: [_('OK'), _('Cancel')],
@@ -83,12 +86,12 @@ class Bridge {
return result === 0;
}
showInfoMessageBox(message) {
const result = this.showMessageBox(this.window(), {
showInfoMessageBox(message, options = {}) {
const result = this.showMessageBox_(this.window(), Object.assign({}, {
type: 'info',
message: message,
buttons: [_('OK')],
});
}, options));
return result === 0;
}
@@ -108,23 +111,6 @@ class Bridge {
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) {
const { checkForUpdates } = require('./checkForUpdates.js');
checkForUpdates(inBackground, window, logFilePath);

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 B

View File

@@ -46,7 +46,13 @@ function onCheckEnded() {
autoUpdater.on('error', (error) => {
autoUpdateLogger_.error(error);
if (checkInBackground_) return onCheckEnded();
showErrorMessageBox(error == null ? "unknown" : (error.stack || error).toString())
let msg = error == null ? "unknown" : (error.stack || error).toString();
// Error messages can be very long even without stack trace so shorten
// then so that the dialog box doesn't take the whole screen.
msg = msg.substr(0,512).replace(/\\n/g, '\n');
showErrorMessageBox(msg)
onCheckEnded();
})

View File

@@ -40,10 +40,6 @@ class ConfigScreenComponent extends React.Component {
});
}
output.sort((a, b) => {
return a.label.toLowerCase() < b.label.toLowerCase() ? -1 : +1;
});
return output;
}
@@ -63,6 +59,12 @@ class ConfigScreenComponent extends React.Component {
display: 'inline-block',
};
const descriptionStyle = Object.assign({}, theme.textStyle, {
color: theme.colorFaded,
marginTop: 5,
fontStyle: 'italic',
});
const updateSettingValue = (key, value) => {
return shared.updateSettingValue(this, key, value);
}
@@ -71,6 +73,13 @@ class ConfigScreenComponent extends React.Component {
const md = Setting.settingMetadata(key);
const descriptionText = Setting.keyDescription(key, 'desktop');
const descriptionComp = descriptionText ? (
<div style={descriptionStyle}>
{descriptionText}
</div>
) : null;
if (md.isEnum) {
let items = [];
const settingOptions = md.options();
@@ -86,6 +95,7 @@ class ConfigScreenComponent extends React.Component {
<select value={value} style={controlStyle} onChange={(event) => { updateSettingValue(key, event.target.value) }}>
{items}
</select>
{ descriptionComp }
</div>
);
} else if (md.type === Setting.TYPE_BOOL) {
@@ -100,6 +110,7 @@ class ConfigScreenComponent extends React.Component {
<div key={key+value.toString()} style={rowStyle}>
<div style={controlStyle}>
<input id={'setting_checkbox_' + key} type="checkbox" checked={!!value} onChange={(event) => { onCheckboxClick(event) }}/><label onClick={(event) => { onCheckboxClick(event) }} style={labelStyle} htmlFor={'setting_checkbox_' + key}>{md.label()}</label>
{ descriptionComp }
</div>
</div>
);
@@ -115,6 +126,7 @@ class ConfigScreenComponent extends React.Component {
<div key={key} style={rowStyle}>
<div style={labelStyle}><label>{md.label()}</label></div>
<input type={inputType} style={inputStyle} value={this.state.settings[key]} onChange={(event) => {onTextChange(event)}} />
{ descriptionComp }
</div>
);
} else if (md.type === Setting.TYPE_INT) {
@@ -126,6 +138,7 @@ class ConfigScreenComponent extends React.Component {
<div key={key} style={rowStyle}>
<div style={labelStyle}><label>{md.label()}</label></div>
<input type="number" style={controlStyle} value={this.state.settings[key]} onChange={(event) => {onNumChange(event)}} min={md.minimum} max={md.maximum} step={md.step}/>
{ descriptionComp }
</div>
);
} else {

View File

@@ -22,6 +22,10 @@ class MainScreenComponent extends React.Component {
componentWillMount() {
this.setState({
promptOptions: null,
modalLayer: {
visible: false,
message: '',
},
});
}
@@ -165,6 +169,10 @@ class MainScreenComponent extends React.Component {
});
} else if (command.name === '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') {
const note = await Note.load(command.noteId);
@@ -265,6 +273,17 @@ class MainScreenComponent extends React.Component {
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_;
}
@@ -353,8 +372,12 @@ class MainScreenComponent extends React.Component {
);
}
const modalLayerStyle = Object.assign({}, styles.modalLayer, { display: this.state.modalLayer.visible ? 'block' : 'none' });
return (
<div style={style}>
<div style={modalLayerStyle}>{this.state.modalLayer.message}</div>
<PromptDialog
autocomplete={promptOptions && ('autocomplete' in promptOptions) ? promptOptions.autocomplete : null}
defaultValue={promptOptions && promptOptions.value ? promptOptions.value : ''}

View File

@@ -9,6 +9,8 @@ const { bridge } = require('electron').remote.require('./bridge');
const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem;
const eventManager = require('../eventManager');
const InteropService = require('lib/services/InteropService');
const InteropServiceHelper = require('../InteropServiceHelper.js');
class NoteListComponent extends React.Component {
@@ -91,6 +93,12 @@ class NoteListComponent extends React.Component {
eventManager.emit('noteTypeToggle', { noteId: note.id });
}
}}));
menu.append(new MenuItem({label: _('Export'), click: async () => {
const ioService = new InteropService();
const module = ioService.moduleByFormat_('exporter', 'jex');
await InteropServiceHelper.export(this.props.dispatch.bind(this), module, { sourceNoteIds: noteIds });
}}));
}
menu.append(new MenuItem({label: _('Delete'), click: async () => {

View File

@@ -320,10 +320,7 @@ class NoteTextComponent extends React.Component {
bridge().openItem(filePath);
});
} else {
bridge().showMessageBox({
type: 'error',
message: _('Unsupported link or message: %s', msg),
});
bridge().showErrorMessageBox(_('Unsupported link or message: %s', msg));
}
}
@@ -600,7 +597,14 @@ class NoteTextComponent extends React.Component {
},
postMessageSyntax: 'ipcRenderer.sendToHost',
};
const html = this.mdToHtml().render(body, theme, mdOptions);
let bodyToRender = body;
if (!bodyToRender.trim() && visiblePanes.indexOf('viewer') >= 0 && visiblePanes.indexOf('editor') < 0) {
// Fixes https://github.com/laurent22/joplin/issues/217
bodyToRender = '*' + _('This note has no content. Click on "%s" to toggle the editor and edit the note.', _('Layout')) + '*';
}
const html = this.mdToHtml().render(bodyToRender, theme, mdOptions);
this.webview_.send('setHtml', html);
}

View File

@@ -54,10 +54,7 @@ class OneDriveLoginScreenComponent extends React.Component {
this.props.dispatch({ type: 'NAV_BACK' });
reg.scheduleSync(0);
} catch (error) {
bridge().showMessageBox({
type: 'error',
message: 'Could not login to OneDrive. Please try again.\n\n' + error.message + "\n\n" + url.match(/.{1,64}/g).join('\n'),
});
bridge().showErrorMessageBox('Could not login to OneDrive. Please try again.\n\n' + error.message + "\n\n" + url.match(/.{1,64}/g).join('\n'));
}
this.authCode_ = null;

View File

@@ -11,6 +11,7 @@ const { themeStyle } = require('../theme.js');
const { bridge } = require('electron').remote.require('./bridge');
const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem;
const InteropServiceHelper = require('../InteropServiceHelper.js');
class SideBarComponent extends React.Component {
@@ -136,7 +137,17 @@ class SideBarComponent extends React.Component {
name: 'renameFolder',
id: itemId,
});
}}))
}}));
menu.append(new MenuItem({ type: 'separator' }));
const InteropService = require('lib/services/InteropService.js');
menu.append(new MenuItem({label: _('Export'), click: async () => {
const ioService = new InteropService();
const module = ioService.moduleByFormat_('exporter', 'jex');
await InteropServiceHelper.export(this.props.dispatch.bind(this), module, { sourceFolderIds: [itemId] });
}}));
}
menu.popup(bridge().window());

View File

@@ -17,11 +17,6 @@
.smalltalk .page {
max-width: 30em;
}
.ace_editor * {
/* Necessary to make sure Russian text is displayed properly */
/* https://github.com/laurent22/joplin/issues/155 */
font-family: monospace !important;
}
</style>
</head>
<body>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,6 @@
var locales = {};
locales['en_GB'] = require('./en_GB.json');
locales['de_DE'] = require('./de_DE.json');
locales['es_CR'] = require('./es_CR.json');
locales['es_ES'] = require('./es_ES.json');
locales['eu'] = require('./eu.json');
locales['fr_FR'] = require('./fr_FR.json');

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "Joplin",
"version": "1.0.66",
"version": "1.0.70",
"description": "Joplin for Desktop",
"main": "main.js",
"scripts": {
@@ -43,10 +43,12 @@
},
"homepage": "https://github.com/laurent22/joplin#readme",
"devDependencies": {
"ajv": "^6.2.0",
"app-builder-bin": "^1.5.0",
"babel-cli": "^6.26.0",
"babel-preset-react": "^6.24.1",
"electron": "^1.7.11",
"electron-builder": "^19.45.4"
"electron-builder": "^20.2.0"
},
"optionalDependencies": {
"7zip-bin-mac": "^1.0.1",
@@ -59,7 +61,7 @@
"base-64": "^0.1.0",
"electron-context-menu": "^0.9.1",
"electron-log": "^2.2.11",
"electron-updater": "^2.16.1",
"electron-updater": "^2.18.2",
"electron-window-state": "^4.1.1",
"follow-redirects": "^1.2.5",
"form-data": "^2.3.1",
@@ -91,6 +93,7 @@
"sqlite3": "^3.1.13",
"string-padding": "^1.0.2",
"string-to-stream": "^1.1.0",
"tar": "^4.4.0",
"tcp-port-used": "^0.1.2",
"url-parse": "^1.2.0",
"uuid": "^3.1.0",

View File

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

View File

@@ -90,6 +90,8 @@ async function main(argv) {
if (linuxUrl) content = content.replace(/(https:\/\/github.com\/laurent22\/joplin\/releases\/download\/.*?\.AppImage)/, linuxUrl);
setReadmeContent(content);
console.info("git pull && git add -A && git commit -m 'Update readme downloads' && git push")
}
main(process.argv).catch((error) => {

View File

@@ -2,7 +2,7 @@
Joplin is a free, open source note taking and to-do application, which can handle a large number of notes organised into notebooks. The notes are searchable, can be copied, tagged and modified either from the applications directly or from your own text editor. The notes are in [Markdown format](https://daringfireball.net/projects/markdown/basics).
Notes exported from Evernote via .enex files [can be imported](#importing-notes-from-evernote) into Joplin, including the formatted content (which is converted to Markdown), resources (images, attachments, etc.) and complete metadata (geolocation, updated time, created time, etc.).
Notes exported from Evernote via .enex files [can be imported](#importing) into Joplin, including the formatted content (which is converted to Markdown), resources (images, attachments, etc.) and complete metadata (geolocation, updated time, created time, etc.). Plain Markdown files can also be imported.
The notes can be [synchronised](#synchronisation) with various targets including [Nextcloud](https://nextcloud.com/), the file system (for example with a network directory) or with Microsoft OneDrive. When synchronising the notes, notebooks, tags and other metadata are saved to plain text files which can be easily inspected, backed up and moved around.
@@ -18,15 +18,15 @@ Three types of applications are available: for the **desktop** (Windows, macOS a
Operating System | Download
-----------------|--------
Windows | <a href='https://github.com/laurent22/joplin/releases/download/v1.0.64/Joplin-Setup-1.0.64.exe'><img alt='Get it on Windows' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeWindows.png'/></a>
macOS | <a href='https://github.com/laurent22/joplin/releases/download/v1.0.64/Joplin-1.0.64.dmg'><img alt='Get it on macOS' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeMacOS.png'/></a>
Linux | <a href='https://github.com/laurent22/joplin/releases/download/v1.0.64/Joplin-1.0.64-x86_64.AppImage'><img alt='Get it on macOS' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeLinux.png'/></a>
Windows | <a href='https://github.com/laurent22/joplin/releases/download/v1.0.70/Joplin-Setup-1.0.70.exe'><img alt='Get it on Windows' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeWindows.png'/></a>
macOS | <a href='https://github.com/laurent22/joplin/releases/download/v1.0.70/Joplin-1.0.70.dmg'><img alt='Get it on macOS' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeMacOS.png'/></a>
Linux | <a href='https://github.com/laurent22/joplin/releases/download/v1.0.70/Joplin-1.0.70-x86_64.AppImage'><img alt='Get it on macOS' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeLinux.png'/></a>
## Mobile applications
Operating System | Download | Alt. Download
-----------------|----------|----------------
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeAndroid.png'/></a> | or [Download APK File](https://github.com/laurent22/joplin-android/releases/download/android-v1.0.100/joplin-v1.0.100.apk)
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeAndroid.png'/></a> | or [Download APK File](https://github.com/laurent22/joplin-android/releases/download/android-v1.0.103/joplin-v1.0.103.apk)
iOS | <a href='https://itunes.apple.com/us/app/joplin/id1315599797'><img alt='Get it on the App Store' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeIOS.png'/></a> | -
## Terminal application
@@ -51,20 +51,23 @@ For usage information, please refer to the full [Joplin Terminal Application Doc
# Features
- Desktop, mobile and terminal applications.
- Import Enex files (Evernote export format)
- End To End Encryption (E2EE)
- Synchronisation with various services, including NextCloud, WebDAV and OneDrive. Dropbox is planned.
- Import Enex files (Evernote export format) and Markdown files.
- Export JEX files (Joplin Export format) and raw files.
- Support notes, to-dos, tags and notebooks.
- Sort notes by multiple criteria - title, updated time, etc.
- Support for alarms (notifications) in mobile and desktop applications.
- Offline first, so the entire data is always available on the device even without an internet connection.
- Synchronisation with various services, including NextCloud, WebDAV and OneDrive. Dropbox is planned.
- End To End Encryption (E2EE)
- Synchronises to a plain text format, which can be easily manipulated, backed up, or exported to a different format.
- Markdown notes, which are rendered with images and formatting in the desktop and mobile applications. Support for extra features such as math notation and checkboxes.
- File attachment support - images are displayed, and other files are linked and can be opened in the relevant application.
- Search functionality.
- Geo-location support.
- Supports multiple languages
# Importing notes from Evernote
# Importing
## Importing from Evernote
Joplin was designed as a replacement for Evernote and so can import complete Evernote notebooks, as well as notes, tags, resources (attached files) and note metadata (such as author, geo-location, etc.) via ENEX files. In terms of data, the only two things that might slightly differ are:
@@ -74,17 +77,29 @@ Joplin was designed as a replacement for Evernote and so can import complete Eve
To import Evernote data, first export your Evernote notebooks to ENEX files as described [here](https://help.evernote.com/hc/en-us/articles/209005557-How-to-back-up-export-and-restore-import-notes-and-notebooks). Then follow these steps:
On the **desktop application**, open the "File" menu, click "Import Evernote notes" and select your ENEX file. This will open a new screen which will display the import progress. The notes will be imported into a new separate notebook (so that, in case of a mistake, the notes are not mixed up with any existing notes). If needed then can then be moved to a different notebook, or the notebook can be renamed, etc.
On the **desktop application**, open File > Import > ENEX and select your file. The notes will be imported into a new separate notebook. If needed they can then be moved to a different notebook, or the notebook can be renamed, etc.
On the **terminal application**, in [command-line mode](/terminal#command-line-mode), type `import-enex /path/to/file.enex`. This will import the notes into a new notebook named after the filename.
On the **terminal application**, in [command-line mode](/terminal#command-line-mode), type `import /path/to/file.enex`. This will import the notes into a new notebook named after the filename.
# Importing notes from other applications
## Importing from Markdown files
Joplin can import notes from plain Markdown file. You can either import a complete directory of Markdown files or individual files.
On the **desktop application**, open File > Import > MD and select your Markdown file or directory.
On the **terminal application**, in [command-line mode](/terminal#command-line-mode), type `import --format md /path/to/file.md` or `import --format md /path/to/directory/`.
## Importing from other applications
In general the way to import notes from any application into Joplin is to convert the notes to ENEX files (Evernote format) and to import these ENEX files into Joplin using the method above. Most note-taking applications support ENEX files so it should be relatively straightforward. For help about specific applications, see below:
* Standard Notes: Please see [this tutorial](https://programadorwebvalencia.com/migrate-notes-from-standard-notes-to-joplin/)
* Tomboy Notes: Export the notes to ENEX files [as described here](https://askubuntu.com/questions/243691/how-can-i-export-my-tomboy-notes-into-evernote/608551) for example, and import these ENEX files into Joplin.
# Exporting
Joplin can export to the JEX format (Joplin Export file), which is a tar file that can contain multiple notes, notebooks, etc. This is a lossless format in that all the notes, but also metadata such as geo-location, updated time, tags, etc. are preserved. This format is convenient for backup purposes and can be re-imported into Joplin. A "raw" format is also available. This is the same as the JEX format except that the data is saved to a directory and each item represented by a single file.
# Synchronisation
One of the goals of Joplin was to avoid being tied to any particular company or service, whether it is Evernote, Google or Microsoft. As such the synchronisation is designed without any hard dependency to any particular service. Most of the synchronisation process is done at an abstract level and access to external services, such as Nextcloud or OneDrive, is done via lightweight drivers. It is easy to support new services by creating simple drivers that provide a filesystem-like interface, i.e. the ability to read, write, delete and list items. It is also simple to switch from one service to another or to even sync to multiple services at once. Each note, notebook, tags, as well as the relation between items is transmitted as plain text files during synchronisation, which means the data can also be moved to a different application, can be easily backed up, inspected, etc.
@@ -108,12 +123,14 @@ If synchronisation does not work, please consult the logs in the app profile dir
Select the "WebDAV" synchronisation target and follow the same instructions as for Nextcloud above.
Known compatible services that use WebDAV:
WebDAV-compatible services that are known to work with Joplin:
- [Box.com](https://www.box.com/)
- [DriveHQ](https://www.drivehq.com)
- [Zimbra](https://www.zimbra.com/)
- [OwnCloud](https://owncloud.org/)
- [Seafile](https://www.seafile.com/)
- [Stack](https://www.transip.nl/stack/)
- [Zimbra](https://www.zimbra.com/)
## OneDrive synchronisation
@@ -177,9 +194,9 @@ Here is an example with the Markdown and rendered result side by side:
Checkboxes can be added like so:
-[ ] Milk
-[ ] Rice
-[ ] Eggs
- [ ] Milk
- [ ] Rice
- [ ] Eggs
The checkboxes can then be ticked in the mobile and desktop applications.
@@ -205,19 +222,18 @@ Current translations:
<!-- LOCALE-TABLE-AUTO-GENERATED -->
&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/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/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/es/basque_country.png) | Basque | [eu](https://github.com/laurent22/joplin/blob/master/CliClient/locales/eu.po) | juan.abasolo@ehu.eus | 81%
![](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> | 83%
![](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> | 97%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/cr.png) | Español (Costa Rica) | [es_CR](https://github.com/laurent22/joplin/blob/master/CliClient/locales/es_CR.po) | | 65%
![](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 | 99%
![](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/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/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/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/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/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/es.png) | Español | [es_ES](https://github.com/laurent22/joplin/blob/master/CliClient/locales/es_ES.po) | Fernando Martín <f@mrtn.es> | 93%
![](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/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) | | 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) | | 66%
![](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> | 85%
![](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) | | 66%
<!-- LOCALE-TABLE-AUTO-GENERATED -->
# Known bugs

View File

@@ -2,7 +2,7 @@
Joplin is a free, open source note taking and to-do application, which can handle a large number of notes organised into notebooks. The notes are searchable, can be copied, tagged and modified with your own text editor.
Notes exported from Evernote via .enex files [can be imported](#importing-notes-from-evernote) into Joplin, including the formatted content (which is converted to markdown), resources (images, attachments, etc.) and complete metadata (geolocation, updated time, created time, etc.).
Notes exported from Evernote via .enex files [can be imported](http://joplin.cozic.net/#importing) into Joplin, including the formatted content (which is converted to Markdown), resources (images, attachments, etc.) and complete metadata (geolocation, updated time, created time, etc.). Plain Markdown files can also be imported.
The notes can be [synchronised](#synchronisation) with various targets including the file system (for example with a network directory) or with Microsoft OneDrive. When synchronising the notes, notebooks, tags and other metadata are saved to plain text files which can be easily inspected, backed up and moved around.
@@ -106,7 +106,7 @@ If the help is not fully visible, press `Tab` multiple times till the console is
To import Evernote data, follow these steps:
* First, export your Evernote notebooks to ENEX files as described [here](https://help.evernote.com/hc/en-us/articles/209005557-How-to-back-up-export-and-restore-import-notes-and-notebooks).
* In Joplin, in [command-line mode](#command-line-mode), type `import-enex /path/to/file.enex`. This will import the notes into a new notebook named after the filename.
* In Joplin, in [command-line mode](#command-line-mode), type `import /path/to/file.enex`. This will import the notes into a new notebook named after the filename.
* Then repeat the process for each notebook that needs to be imported.
# Synchronisation
@@ -274,81 +274,91 @@ The following commands are available in [command-line mode](#command-line-mode):
Possible keys/values:
editor Text editor.
The editor that will be used to open a note. If
none is provided it will try to auto-detect the
default editor.
Type: string.
locale Language.
Type: Enum.
Possible values: en_GB (English), de_DE (Deutsch),
es_CR (Español (Costa Rica)), es_ES (Español), eu
(Basque), fr_FR (Français), hr_HR (Croatian), it_IT
(Italiano), ja_JP (日本語), nl_BE (Nederlands), pt_BR
(Português (Brasil)), ru_RU (Русский), zh_CN (中文
(简体)).
Default: "en_GB"
dateFormat Date format.
Type: Enum.
Possible values: DD/MM/YYYY (30/01/2017), DD/MM/YY
(30/01/17), MM/DD/YYYY (01/30/2017), MM/DD/YY
(01/30/17), YYYY-MM-DD (2017-01-30).
Default: "DD/MM/YYYY"
timeFormat Time format.
Type: Enum.
Possible values: HH:mm (20:30), h:mm A (8:30 PM).
Default: "HH:mm"
uncompletedTodosOnTop Show uncompleted to-dos on top of the lists.
Type: bool.
Default: true
trackLocation Save geo-location with notes.
Type: bool.
Default: true
sync.interval Synchronisation interval.
Type: Enum.
Possible values: 0 (Disabled), 300 (5 minutes), 600
(10 minutes), 1800 (30 minutes), 3600 (1 hour),
43200 (12 hours), 86400 (24 hours).
Default: 300
sync.target Synchronisation target.
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.2.path Directory to synchronise with (absolute path).
The path to synchronise with when file system
synchronisation is enabled. See `sync.target`.
Type: string.
sync.5.path Nextcloud WebDAV URL.
Type: string.
sync.5.username Nextcloud username.
Type: string.
sync.5.password Nextcloud password.
Type: string.
sync.6.path WebDAV URL.
Type: string.
sync.6.username WebDAV username.
Type: string.
sync.6.password WebDAV password.
Type: string.
editor Text editor.
The editor that will be used to open a note. If
none is provided it will try to auto-detect the
default editor.
Type: string.
locale Language.
Type: Enum.
Possible values: eu (Basque), hr_HR (Croatian),
de_DE (Deutsch), en_GB (English), es_ES
(Español), fr_FR (Français), it_IT (Italiano),
nl_BE (Nederlands), pt_BR (Português (Brasil)),
ru_RU (Русский), zh_CN (中文 (简体)), ja_JP (日本語).
Default: "en_GB"
dateFormat Date format.
Type: Enum.
Possible values: DD/MM/YYYY (30/01/2017),
DD/MM/YY (30/01/17), MM/DD/YYYY (01/30/2017),
MM/DD/YY (01/30/17), YYYY-MM-DD (2017-01-30).
Default: "DD/MM/YYYY"
timeFormat Time format.
Type: Enum.
Possible values: HH:mm (20:30), h:mm A (8:30 PM).
Default: "HH:mm"
uncompletedTodosOnTop Uncompleted to-dos on top.
Type: bool.
Default: true
notes.sortOrder.field Sort notes by.
Type: Enum.
Possible values: user_updated_time (Updated
date), user_created_time (Created date), title
(Title).
Default: "user_updated_time"
notes.sortOrder.reverse Reverse sort order.
Type: bool.
Default: true
trackLocation Save geo-location with notes.
Type: bool.
Default: true
sync.interval Synchronisation interval.
Type: Enum.
Possible values: 0 (Disabled), 300 (5 minutes),
600 (10 minutes), 1800 (30 minutes), 3600 (1
hour), 43200 (12 hours), 86400 (24 hours).
Default: 300
sync.target Synchronisation target.
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.2.path Directory to synchronise with (absolute path).
The path to synchronise with when file system
synchronisation is enabled. See `sync.target`.
Type: string.
sync.5.path Nextcloud WebDAV URL.
Type: string.
sync.5.username Nextcloud username.
Type: string.
sync.5.password Nextcloud password.
Type: string.
sync.6.path WebDAV URL.
Type: string.
sync.6.username WebDAV username.
Type: string.
sync.6.password WebDAV password.
Type: string.
cp <note> [notebook]
@@ -374,11 +384,13 @@ The following commands are available in [command-line mode](#command-line-mode):
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.
--format <format> Destination format: jex (Joplin Export File), raw
(Joplin Export Directory)
--note <note> Exports only the given note.
--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.
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>

View File

@@ -90,8 +90,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion 16
targetSdkVersion 22
versionCode 2097278
versionName "1.0.100"
versionCode 2097281
versionName "1.0.103"
ndk {
abiFilters "armeabi-v7a", "x86"
}

View File

@@ -1302,7 +1302,7 @@
PRODUCT_NAME = Joplin;
PROVISIONING_PROFILE = "";
PROVISIONING_PROFILE_SPECIFIER = "";
TARGETED_DEVICE_FAMILY = 1;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
@@ -1342,7 +1342,7 @@
PRODUCT_NAME = Joplin;
PROVISIONING_PROFILE = "";
PROVISIONING_PROFILE_SPECIFIER = "";
TARGETED_DEVICE_FAMILY = 1;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;

View File

@@ -17,11 +17,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0.12</string>
<string>1.0.13</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>12</string>
<string>13</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>

View File

@@ -15,7 +15,6 @@ ArrayUtils.removeElement = function(array, element) {
// https://stackoverflow.com/a/10264318/561309
ArrayUtils.binarySearch = function(items, value) {
var startIndex = 0,
stopIndex = items.length - 1,
middle = Math.floor((stopIndex + startIndex)/2);
@@ -37,4 +36,13 @@ ArrayUtils.binarySearch = function(items, value) {
return (items[middle] != value) ? -1 : middle;
}
ArrayUtils.findByKey = function(array, key, value) {
for (let i = 0; i < array.length; i++) {
const o = array[i];
if (typeof o !== 'object') continue;
if (o[key] === value) return o;
}
return null;
}
module.exports = ArrayUtils;

View File

@@ -1,5 +1,5 @@
const { createStore, applyMiddleware } = require('redux');
const { reducer, defaultState } = require('lib/reducer.js');
const { reducer, defaultState, stateUtils } = require('lib/reducer.js');
const { JoplinDatabase } = require('lib/joplin-database.js');
const { Database } = require('lib/database.js');
const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
@@ -184,8 +184,9 @@ class BaseApplication {
this.logger().debug('Refreshing notes:', parentType, parentId);
let options = {
order: state.notesOrder,
order: stateUtils.notesOrder(state.settings),
uncompletedTodosOnTop: Setting.value('uncompletedTodosOnTop'),
caseInsensitive: true,
};
const source = JSON.stringify({
@@ -255,14 +256,31 @@ class BaseApplication {
const result = next(action);
const newState = store.getState();
let refreshNotes = false;
if (action.type == 'FOLDER_SELECT' || action.type === 'FOLDER_DELETE') {
Setting.setValue('activeFolderId', newState.selectedFolderId);
this.currentFolder_ = newState.selectedFolderId ? await Folder.load(newState.selectedFolderId) : null;
await this.refreshNotes(newState);
refreshNotes = true;
}
if (this.hasGui() && action.type == 'SETTING_UPDATE_ONE' && action.key == 'uncompletedTodosOnTop' || action.type == 'SETTING_UPDATE_ALL') {
if (this.hasGui() && ((action.type == 'SETTING_UPDATE_ONE' && action.key == 'uncompletedTodosOnTop') || action.type == 'SETTING_UPDATE_ALL')) {
refreshNotes = true;
}
if (this.hasGui() && ((action.type == 'SETTING_UPDATE_ONE' && action.key.indexOf('notes.sortOrder') === 0) || action.type == 'SETTING_UPDATE_ALL')) {
refreshNotes = true;
}
if (action.type == 'TAG_SELECT' || action.type === 'TAG_DELETE') {
refreshNotes = true;
}
if (action.type == 'SEARCH_SELECT' || action.type === 'SEARCH_DELETE') {
refreshNotes = true;
}
if (refreshNotes) {
await this.refreshNotes(newState);
}
@@ -288,14 +306,6 @@ class BaseApplication {
}
}
if (action.type == 'TAG_SELECT' || action.type === 'TAG_DELETE') {
await this.refreshNotes(newState);
}
if (action.type == 'SEARCH_SELECT' || action.type === 'SEARCH_DELETE') {
await this.refreshNotes(newState);
}
if (action.type === 'NOTE_UPDATE_ONE') {
// If there is a conflict, we refresh the folders so as to display "Conflicts" folder
if (action.note && action.note.is_conflict) {
@@ -303,11 +313,6 @@ class BaseApplication {
}
}
// if (action.type === 'NOTE_DELETE') {
// // Update folders if a note is deleted in case the deleted note was a conflict
// await FoldersScreenUtils.refreshFolders();
// }
if (this.hasGui() && action.type == 'SETTING_UPDATE_ONE' && action.key == 'sync.interval' || action.type == 'SETTING_UPDATE_ALL') {
reg.setupRecurrentSync();
}

View File

@@ -45,6 +45,14 @@ class BaseModel {
return null;
}
static modelTypeToName(type) {
for (let i = 0; i < BaseModel.typeEnum_.length; i++) {
const e = BaseModel.typeEnum_[i];
if (e[1] === type) return e[0].substr(5).toLowerCase();
}
throw new Error('Unknown model type: ' + type);
}
static hasField(name) {
let fields = this.fieldNames();
return fields.indexOf(name) >= 0;
@@ -487,15 +495,32 @@ class BaseModel {
}
BaseModel.TYPE_NOTE = 1;
BaseModel.TYPE_FOLDER = 2;
BaseModel.TYPE_SETTING = 3;
BaseModel.TYPE_RESOURCE = 4;
BaseModel.TYPE_TAG = 5;
BaseModel.TYPE_NOTE_TAG = 6;
BaseModel.TYPE_SEARCH = 7;
BaseModel.TYPE_ALARM = 8;
BaseModel.TYPE_MASTER_KEY = 9;
BaseModel.typeEnum_ = [
['TYPE_NOTE', 1],
['TYPE_FOLDER', 2],
['TYPE_SETTING', 3],
['TYPE_RESOURCE', 4],
['TYPE_TAG', 5],
['TYPE_NOTE_TAG', 6],
['TYPE_SEARCH', 7],
['TYPE_ALARM', 8],
['TYPE_MASTER_KEY', 9],
];
for (let i = 0; i < BaseModel.typeEnum_.length; i++) {
const e = BaseModel.typeEnum_[i];
BaseModel[e[0]] = e[1];
}
// BaseModel.TYPE_NOTE = 1;
// BaseModel.TYPE_FOLDER = 2;
// BaseModel.TYPE_SETTING = 3;
// BaseModel.TYPE_RESOURCE = 4;
// BaseModel.TYPE_TAG = 5;
// BaseModel.TYPE_NOTE_TAG = 6;
// BaseModel.TYPE_SEARCH = 7;
// BaseModel.TYPE_ALARM = 8;
// BaseModel.TYPE_MASTER_KEY = 9;
BaseModel.db_ = null;
BaseModel.dispatch = function(o) {};

View File

@@ -28,8 +28,7 @@ class Cache {
Cache.storage = async function() {
if (Cache.storage_) return Cache.storage_;
Cache.storage_ = require('node-persist');
const osTmpdir = require('os-tmpdir');
await Cache.storage_.init({ dir: osTmpdir() + '/joplin-cache', ttl: 1000 * 60 });
await Cache.storage_.init({ dir: require('os').tmpdir() + '/joplin-cache', ttl: 1000 * 60 });
return Cache.storage_;
}

View File

@@ -0,0 +1,47 @@
const ObjectUtils = {};
ObjectUtils.sortByValue = function(object) {
const temp = [];
for (let k in object) {
if (!object.hasOwnProperty(k)) continue;
temp.push({
key: k,
value: object[k],
});
}
temp.sort(function(a, b) {
let v1 = a.value;
let v2 = b.value;
if (typeof v1 === 'string') v1 = v1.toLowerCase();
if (typeof v2 === 'string') v2 = v2.toLowerCase();
if (v1 === v2) return 0;
return v1 < v2 ? -1 : +1;
});
const output = {};
for (let i = 0; i < temp.length; i++) {
const item = temp[i];
output[item.key] = item.value;
}
return output;
}
ObjectUtils.fieldsEqual = function(o1, o2) {
if ((!o1 || !o2) && (o1 !== o2)) return false;
for (let k in o1) {
if (!o1.hasOwnProperty(k)) continue;
if (o1[k] !== o2[k]) return false;
}
const c1 = Object.getOwnPropertyNames(o1);
const c2 = Object.getOwnPropertyNames(o2);
if (c1.length !== c2.length) return false;
return true;
}
module.exports = ObjectUtils;

View File

@@ -37,7 +37,7 @@ class SyncTargetNextcloud extends BaseSyncTarget {
}
async initFileApi() {
const fileApi = await SyncTargetWebDAV.initFileApi_(SyncTargetNextcloud.id(), {
const fileApi = await SyncTargetWebDAV.newFileApi_(SyncTargetNextcloud.id(), {
path: Setting.value('sync.5.path'),
username: Setting.value('sync.5.username'),
password: Setting.value('sync.5.password'),

View File

@@ -28,7 +28,7 @@ class SyncTargetWebDAV extends BaseSyncTarget {
return true;
}
static async initFileApi_(syncTargetId, options) {
static async newFileApi_(syncTargetId, options) {
const apiOptions = {
baseUrl: () => options.path,
username: () => options.username,
@@ -43,7 +43,8 @@ class SyncTargetWebDAV extends BaseSyncTarget {
}
static async checkConfig(options) {
const fileApi = await SyncTargetWebDAV.initFileApi_(SyncTargetWebDAV.id(), options);
const fileApi = await SyncTargetWebDAV.newFileApi_(SyncTargetWebDAV.id(), options);
fileApi.requestRepeatCount_ = 0;
const output = {
ok: false,
@@ -52,7 +53,7 @@ class SyncTargetWebDAV extends BaseSyncTarget {
try {
const result = await fileApi.stat('');
if (!result) throw new Error('Could not access WebDAV directory');
if (!result) throw new Error('WebDAV directory not found: ' + options.path);
output.ok = true;
} catch (error) {
output.errorMessage = error.message;
@@ -63,7 +64,7 @@ class SyncTargetWebDAV extends BaseSyncTarget {
}
async initFileApi() {
const fileApi = await SyncTargetWebDAV.initFileApi_(SyncTargetWebDAV.id(), {
const fileApi = await SyncTargetWebDAV.newFileApi_(SyncTargetWebDAV.id(), {
path: Setting.value('sync.6.path'),
username: Setting.value('sync.6.username'),
password: Setting.value('sync.6.password'),

View File

@@ -29,7 +29,15 @@ class WebDavApi {
authToken() {
if (!this.options_.username() || !this.options_.password()) return null;
return base64.encode(this.options_.username() + ':' + this.options_.password());
try {
// Note: Non-ASCII passwords will throw an error about Latin1 characters - https://github.com/laurent22/joplin/issues/246
// Tried various things like the below, but it didn't work on React Native:
//return base64.encode(utf8.encode(this.options_.username() + ':' + this.options_.password()));
return base64.encode(this.options_.username() + ':' + this.options_.password());
} catch (error) {
error.message = 'Cannot encode username/password: ' + error.message;
throw error;
}
}
baseUrl() {
@@ -236,7 +244,7 @@ class WebDavApi {
// The "solution", an ugly one, is to send a purposely invalid string as eTag, which will bypass the If-None-Match check - Seafile
// finds out that no resource has this ID and simply sends the requested data.
// Also add a random value to make sure the eTag is unique for each call.
//if (['GET', 'HEAD'].indexOf(method) < 0) headers['If-None-Match'] = 'JoplinIgnore-' + Math.floor(Math.random() * 100000);
if (['GET', 'HEAD'].indexOf(method) < 0) headers['If-None-Match'] = 'JoplinIgnore-' + Math.floor(Math.random() * 100000);
const fetchOptions = {};
fetchOptions.headers = headers;
@@ -281,7 +289,7 @@ class WebDavApi {
if (!responseText) return null;
if (responseJson_) return responseJson_;
responseJson_ = await this.xmlToJson(responseText);
if (!responseJson_) throw newError('Cannot parse JSON response', response.status);
if (!responseJson_) throw newError('Cannot parse XML response', response.status);
return responseJson_;
}
@@ -289,12 +297,17 @@ class WebDavApi {
// When using fetchBlob we only get a string (not xml or json) back
if (options.target === 'file') throw newError('fetchBlob error', response.status);
const json = await loadResponseJson();
let json = null;
try {
json = await loadResponseJson();
} catch (error) {
// Just send back the plain text in newErro()
}
if (json && json['d:error']) {
const code = json['d:error']['s:exception'] ? json['d:error']['s:exception'].join(' ') : response.status;
const message = json['d:error']['s:message'] ? json['d:error']['s:message'].join("\n") : 'Unknown error 1';
throw newError(message + '(Exception ' + code + ')', response.status);
throw newError(message + ' (Exception ' + code + ')', response.status);
}
throw newError('Unknown error 2', response.status);
@@ -302,11 +315,16 @@ class WebDavApi {
if (options.responseFormat === 'text') return responseText;
// The following methods may have a response depending on the server but it's not
// standard (some return a plain string, other XML, etc.) and we don't check the
// response anyway since we rely on the HTTP status code so return null.
if (['MKCOL', 'DELETE', 'PUT', 'MOVE'].indexOf(method) >= 0) return null;
const output = await loadResponseJson();
// Check that we didn't get for example an HTML page (as an error) instead of the JSON response
// null responses are possible, for example for DELETE calls
if (output !== null && typeof output === 'object' && !('d:multistatus' in output)) throw newError('Not a valid JSON response');
if (output !== null && typeof output === 'object' && !('d:multistatus' in output)) throw newError('Not a valid WebDAV response');
return output;
}

View File

@@ -0,0 +1,81 @@
const React = require('react');
const { Text, Modal, View, StyleSheet, Button } = require('react-native');
const { themeStyle } = require('lib/components/global-style.js');
const { _ } = require('lib/locale');
class ModalDialog extends React.Component {
constructor() {
super();
this.styles_ = {};
}
styles() {
const themeId = this.props.theme;
const theme = themeStyle(themeId);
if (this.styles_[themeId]) return this.styles_[themeId];
this.styles_ = {};
let styles = {
modalWrapper: {
flex: 1,
justifyContent: 'center',
},
modalContentWrapper: {
flex:1,
flexDirection: 'column',
backgroundColor: theme.backgroundColor,
borderWidth: 1,
borderColor:theme.dividerColor,
margin: 20,
padding: 10,
},
modalContentWrapper2: {
paddingTop: 10,
flex:1,
},
title: {
borderBottomWidth: 1,
borderBottomColor: theme.dividerColor,
paddingBottom: 10,
},
buttonRow: {
flexDirection: 'row',
borderTopWidth: 1,
borderTopColor: theme.dividerColor,
paddingTop: 10,
},
};
this.styles_[themeId] = StyleSheet.create(styles);
return this.styles_[themeId];
}
render() {
const ContentComponent = this.props.ContentComponent;
return (
<View style={this.styles().modalWrapper}>
<Modal transparent={true} visible={true} onRequestClose={() => { }} >
<View style={this.styles().modalContentWrapper}>
<Text style={this.styles().title}>Title</Text>
<View style={this.styles().modalContentWrapper2}>
{ContentComponent}
</View>
<View style={this.styles().buttonRow}>
<View style={{flex:1}}>
<Button title={_('OK')} onPress={() => {}}></Button>
</View>
<View style={{flex:1, marginLeft: 5}}>
<Button title={_('Cancel')} onPress={() => {}}></Button>
</View>
</View>
</View>
</Modal>
</View>
);
}
}
module.exports = ModalDialog;

View File

@@ -4,6 +4,7 @@ const { Platform, View, Text, Button, StyleSheet, TouchableOpacity, Image, Scrol
const Icon = require('react-native-vector-icons/Ionicons').default;
const { Log } = require('lib/log.js');
const { BackButtonService } = require('lib/services/back-button.js');
const NavService = require('lib/services/NavService.js');
const { ReportService } = require('lib/services/report.js');
const { Menu, MenuOptions, MenuOption, MenuTrigger } = require('react-native-popup-menu');
const { _ } = require('lib/locale.js');
@@ -160,10 +161,7 @@ class ScreenHeaderComponent extends Component {
}
searchButton_press() {
this.props.dispatch({
type: 'NAV_GO',
routeName: 'Search',
});
NavService.go('Search');
}
async deleteButton_press() {
@@ -184,38 +182,23 @@ class ScreenHeaderComponent extends Component {
}
log_press() {
this.props.dispatch({
type: 'NAV_GO',
routeName: 'Log',
});
NavService.go('Log');
}
status_press() {
this.props.dispatch({
type: 'NAV_GO',
routeName: 'Status',
});
NavService.go('Status');
}
config_press() {
this.props.dispatch({
type: 'NAV_GO',
routeName: 'Config',
});
NavService.go('Config');
}
encryptionConfig_press() {
this.props.dispatch({
type: 'NAV_GO',
routeName: 'EncryptionConfig',
});
NavService.go('EncryptionConfig');
}
warningBox_press() {
this.props.dispatch({
type: 'NAV_GO',
routeName: 'EncryptionConfig',
});
NavService.go('EncryptionConfig');
}
async debugReport_press() {
@@ -300,6 +283,16 @@ class ScreenHeaderComponent extends Component {
);
}
function sortButton(styles, onPress) {
return (
<TouchableOpacity onPress={onPress}>
<View style={styles.iconButton}>
<Icon name='md-funnel' style={styles.topIcon} />
</View>
</TouchableOpacity>
);
}
let key = 0;
let menuOptionComponents = [];
@@ -441,6 +434,7 @@ class ScreenHeaderComponent extends Component {
const backButtonComp = backButton(this.styles(), () => this.backButton_press(), !this.props.historyCanGoBack);
const searchButtonComp = this.props.noteSelectionEnabled ? null : searchButton(this.styles(), () => this.searchButton_press());
const deleteButtonComp = this.props.noteSelectionEnabled ? deleteButton(this.styles(), () => this.deleteButton_press()) : null;
const sortButtonComp = this.props.sortButton_press ? sortButton(this.styles(), () => this.props.sortButton_press()) : null;
const windowHeight = Dimensions.get('window').height - 50;
const menuComp = (
@@ -465,6 +459,7 @@ class ScreenHeaderComponent extends Component {
{ titleComp }
{ searchButtonComp }
{ deleteButtonComp }
{ sortButtonComp }
{ menuComp }
</View>
{ warningComp }

View File

@@ -9,6 +9,7 @@ const Setting = require('lib/models/Setting.js');
const Resource = require('lib/models/Resource.js');
const Folder = require('lib/models/Folder.js');
const { BackButtonService } = require('lib/services/back-button.js');
const NavService = require('lib/services/NavService.js');
const BaseModel = require('lib/BaseModel.js');
const { ActionButton } = require('lib/components/action-button.js');
const Icon = require('react-native-vector-icons/Ionicons').default;
@@ -61,18 +62,29 @@ class NoteScreenComponent extends BaseScreenComponent {
this.styles_ = {};
this.backHandler = async () => {
const saveDialog = async () => {
if (this.isModified()) {
let buttonId = await dialogs.pop(this, _('This note has been modified:'), [
{ title: _('Save changes'), id: 'save' },
{ title: _('Discard changes'), id: 'discard' },
{ title: _('Cancel'), id: 'cancel' },
{ text: _('Save changes'), id: 'save' },
{ text: _('Discard changes'), id: 'discard' },
{ text: _('Cancel'), id: 'cancel' },
]);
if (buttonId == 'cancel') return true;
if (buttonId == 'save') await this.saveNoteButton_press();
}
return false;
}
this.navHandler = async () => {
return await saveDialog();
}
this.backHandler = async () => {
const r = await saveDialog();
if (r) return r;
if (!this.state.note.id) {
return false;
}
@@ -145,6 +157,7 @@ class NoteScreenComponent extends BaseScreenComponent {
async componentWillMount() {
BackButtonService.addHandler(this.backHandler);
NavService.addHandler(this.navHandler);
await shared.initState(this);
@@ -157,6 +170,7 @@ class NoteScreenComponent extends BaseScreenComponent {
componentWillUnmount() {
BackButtonService.removeHandler(this.backHandler);
NavService.removeHandler(this.navHandler);
}
title_changeText(text) {

View File

@@ -1,5 +1,6 @@
const React = require('react'); const Component = React.Component;
const { View, Button } = require('react-native');
const { View, Button, Text } = require('react-native');
const { stateUtils } = require('lib/reducer.js');
const { connect } = require('react-redux');
const { reg } = require('lib/registry.js');
const { Log } = require('lib/log.js');
@@ -10,7 +11,7 @@ const Note = require('lib/models/Note.js');
const Setting = require('lib/models/Setting.js');
const { themeStyle } = require('lib/components/global-style.js');
const { ScreenHeader } = require('lib/components/screen-header.js');
const { MenuOption, Text } = require('react-native-popup-menu');
const { MenuOption } = require('react-native-popup-menu');
const { _ } = require('lib/locale.js');
const { ActionButton } = require('lib/components/action-button.js');
const { dialogs } = require('lib/dialogs.js');
@@ -23,6 +24,43 @@ class NotesScreenComponent extends BaseScreenComponent {
return { header: null };
}
constructor() {
super();
this.sortButton_press = async () => {
const buttons = [];
const sortNoteOptions = Setting.enumOptions('notes.sortOrder.field');
const makeCheckboxText = function(selected, sign, label) {
const s = sign === 'tick' ? '✓' : '⬤'
return (selected ? (s + ' ') : '') + label;
}
for (let field in sortNoteOptions) {
if (!sortNoteOptions.hasOwnProperty(field)) continue;
buttons.push({
text: makeCheckboxText(Setting.value('notes.sortOrder.field') === field, 'bullet', sortNoteOptions[field]),
id: { name: 'notes.sortOrder.field', value: field },
});
}
buttons.push({
text: makeCheckboxText(Setting.value('notes.sortOrder.reverse'), 'tick', '[ ' + Setting.settingMetadata('notes.sortOrder.reverse').label() + ' ]'),
id: { name: 'notes.sortOrder.reverse', value: !Setting.value('notes.sortOrder.reverse') },
});
buttons.push({
text: makeCheckboxText(Setting.value('uncompletedTodosOnTop'), 'tick', '[ ' + Setting.settingMetadata('uncompletedTodosOnTop').label() + ' ]'),
id: { name: 'uncompletedTodosOnTop', value: !Setting.value('uncompletedTodosOnTop') },
});
const r = await dialogs.pop(this, Setting.settingMetadata('notes.sortOrder.field').label(), buttons);
if (!r) return;
Setting.setValue(r.name, r.value);
}
}
async componentDidMount() {
await this.refreshNotes();
}
@@ -42,6 +80,7 @@ class NotesScreenComponent extends BaseScreenComponent {
let options = {
order: props.notesOrder,
uncompletedTodosOnTop: props.uncompletedTodosOnTop,
caseInsensitive: true,
};
const parent = this.parentItem(props);
@@ -155,6 +194,7 @@ class NotesScreenComponent extends BaseScreenComponent {
title={title}
menuOptions={this.menuOptions()}
parentComponent={thisComp}
sortButton_press={this.sortButton_press}
folderPickerOptions={{
enabled: this.props.noteSelectionEnabled,
mustSelect: true,
@@ -178,11 +218,11 @@ const NotesScreen = connect(
selectedTagId: state.selectedTagId,
notesParentType: state.notesParentType,
notes: state.notes,
notesOrder: state.notesOrder,
notesSource: state.notesSource,
uncompletedTodosOnTop: state.settings.uncompletedTodosOnTop,
theme: state.settings.theme,
noteSelectionEnabled: state.noteSelectionEnabled,
notesOrder: stateUtils.notesOrder(state.settings),
};
}
)(NotesScreenComponent)

View File

@@ -33,17 +33,20 @@ dialogs.confirm = (parentComponent, message) => {
});
};
dialogs.pop = (parentComponent, message, buttons) => {
dialogs.pop = (parentComponent, message, buttons, options = null) => {
if (!parentComponent) throw new Error('parentComponent is required');
if (!('dialogbox' in parentComponent)) throw new Error('A "dialogbox" component must be defined on the parent component!');
if (!options) options = {};
if (!('buttonFlow' in options)) options.buttonFlow = 'auto';
return new Promise((resolve, reject) => {
Keyboard.dismiss();
let btns = [];
for (let i = 0; i < buttons.length; i++) {
btns.push({
text: buttons[i].title,
text: buttons[i].text,
callback: () => {
parentComponent.dialogbox.close();
resolve(buttons[i].id);
@@ -54,6 +57,7 @@ dialogs.pop = (parentComponent, message, buttons) => {
parentComponent.dialogbox.pop({
content: message,
btns: btns,
buttonFlow: options.buttonFlow,
});
});
}

View File

@@ -27,7 +27,6 @@ class FileApiDriverWebDav {
const result = await this.api().execPropFind(path, 0, [
'd:getlastmodified',
'd:resourcetype',
// 'd:getcontentlength', // Remove this once PUT call issue is sorted out
]);
const resource = this.api().objectFromJson(result, ['d:multistatus', 'd:response', 0]);
@@ -56,11 +55,7 @@ class FileApiDriverWebDav {
}
}
const lastModifiedString = this.api().resourcePropByName(resource, 'string', 'd:getlastmodified');
// const sizeDONOTUSE = Number(this.api().stringFromJson(resource, ['d:propstat', 0, 'd:prop', 0, 'd:getcontentlength', 0]));
// if (isNaN(sizeDONOTUSE)) throw new Error('Cannot get content size: ' + JSON.stringify(resource));
const lastModifiedString = this.api().resourcePropByName(resource, 'string', 'd:getlastmodified');
// Note: Not all WebDAV servers return a getlastmodified date (eg. Seafile, which doesn't return the
// property for folders) so we can only throw an error if it's a file.
@@ -70,10 +65,8 @@ class FileApiDriverWebDav {
return {
path: path,
// created_time: lastModifiedDate.getTime(),
updated_time: lastModifiedDate.getTime(),
isDir: isDir,
// sizeDONOTUSE: sizeDONOTUSE, // This property is used only for the WebDAV PUT hack (see below) so mark it as such so that it can be removed with the hack later on.
};
}

View File

@@ -0,0 +1,24 @@
class FsDriverBase {
async isDirectory(path) {
const stat = await this.stat(path);
return !stat ? false : stat.isDirectory();
}
async readDirStatsHandleRecursion_(basePath, stat, output, options) {
if (options.recursive && stat.isDirectory()) {
const subPath = basePath + '/' + stat.path;
const subStats = await this.readDirStats(subPath, options);
for (let j = 0; j < subStats.length; j++) {
const subStat = subStats[j];
subStat.path = stat.path + '/' + subStat.path;
output.push(subStat);
}
}
return output;
}
}
module.exports = FsDriverBase;

View File

@@ -1,7 +1,8 @@
const fs = require('fs-extra');
const { time } = require('lib/time-utils.js');
const FsDriverBase = require('lib/fs-driver-base');
class FsDriverNode {
class FsDriverNode extends FsDriverBase {
fsErrorToJsError_(error, path = null) {
let msg = error.toString();
@@ -81,9 +82,14 @@ class FsDriverNode {
async stat(path) {
try {
const s = await fs.stat(path);
s.path = path;
return s;
const stat = await fs.stat(path);
return {
birthtime: stat.birthtime,
mtime: stat.mtime,
isDirectory: () => stat.isDirectory(),
path: path,
size: stat.size,
};
} catch (error) {
if (error.code == 'ENOENT') return null;
throw error;
@@ -94,14 +100,26 @@ class FsDriverNode {
return fs.utimes(path, timestampDate, timestampDate);
}
async readDirStats(path) {
let items = await fs.readdir(path);
async readDirStats(path, options = null) {
if (!options) options = {};
if (!('recursive' in options)) options.recursive = false;
let items = [];
try {
items = await fs.readdir(path);
} catch (error) {
throw this.fsErrorToJsError_(error);
}
let output = [];
for (let i = 0; i < items.length; i++) {
let stat = await this.stat(path + '/' + items[i]);
const item = items[i];
let stat = await this.stat(path + '/' + item);
if (!stat) continue; // Has been deleted between the readdir() call and now
stat.path = stat.path.substr(path.length + 1);
output.push(stat);
output = await this.readDirStatsHandleRecursion_(path, stat, output, options);
}
return output;
}
@@ -122,14 +140,22 @@ class FsDriverNode {
}
}
readFile(path, encoding = 'utf8') {
if (encoding === 'Buffer') return fs.readFile(path); // Returns the raw buffer
return fs.readFile(path, encoding);
async readFile(path, encoding = 'utf8') {
try {
if (encoding === 'Buffer') return await fs.readFile(path); // Returns the raw buffer
return await fs.readFile(path, encoding);
} catch (error) {
throw this.fsErrorToJsError_(error, path);
}
}
// Always overwrite destination
async copy(source, dest) {
return fs.copy(source, dest, { overwrite: true });
try {
return await fs.copy(source, dest, { overwrite: true });
} catch (error) {
throw this.fsErrorToJsError_(error, source);
}
}
async unlink(path) {

View File

@@ -1,6 +1,7 @@
const RNFS = require('react-native-fs');
const FsDriverBase = require('lib/fs-driver-base');
class FsDriverRN {
class FsDriverRN extends FsDriverBase {
appendFileSync(path, string) {
throw new Error('Not implemented');
@@ -34,13 +35,18 @@ class FsDriverRN {
};
}
async readDirStats(path) {
async readDirStats(path, options = null) {
if (!options) options = {};
if (!('recursive' in options)) options.recursive = false;
let items = await RNFS.readDir(path);
let output = [];
for (let i = 0; i < items.length; i++) {
const item = items[i];
const relativePath = item.path.substr(path.length + 1);
output.push(this.rnfsStatToStd_(item, relativePath));
output = await this.readDirStatsHandleRecursion_(path, item, output, options);
}
return output;
}

View File

@@ -15,6 +15,16 @@ class Note extends BaseItem {
return 'notes';
}
static fieldToLabel(field) {
const fieldsToLabels = {
title: 'title',
user_updated_time: 'updated date',
user_created_time: 'created date',
};
return field in fieldsToLabels ? fieldsToLabels[field] : field;
}
static async serialize(note, type = null, shownKeys = null) {
let fieldNames = this.fieldNames();
fieldNames.push('type_');

View File

@@ -4,6 +4,8 @@ const { Logger } = require('lib/logger.js');
const SyncTargetRegistry = require('lib/SyncTargetRegistry.js');
const { time } = require('lib/time-utils.js');
const { sprintf } = require('sprintf-js');
const ObjectUtils = require('lib/ObjectUtils');
const { toTitleCase } = require('lib/string-utils.js');
const { _, supportedLocalesToLanguages, defaultLocale } = require('lib/locale.js');
class Setting extends BaseModel {
@@ -19,12 +21,16 @@ class Setting extends BaseModel {
static metadata() {
if (this.metadata_) return this.metadata_;
// A "public" setting means that it will show up in the various config screens (or config command for the CLI tool), however
// if if private a setting might still be handled and modified by the app. For instance, the settings related to sorting notes are not
// public for the mobile and desktop apps because they are handled separately in menus.
this.metadata_ = {
'activeFolderId': { value: '', type: Setting.TYPE_STRING, public: false },
'firstStart': { value: true, type: Setting.TYPE_BOOL, public: false },
'editor': { value: '', type: Setting.TYPE_STRING, public: true, appTypes: ['cli'], label: () => _('Text editor'), description: () => _('The editor that will be used to open a note. If none is provided it will try to auto-detect the default editor.') },
'locale': { value: defaultLocale(), type: Setting.TYPE_STRING, isEnum: true, public: true, label: () => _('Language'), options: () => {
return supportedLocalesToLanguages();
return ObjectUtils.sortByValue(supportedLocalesToLanguages());
}},
'dateFormat': { value: Setting.DATE_FORMAT_1, type: Setting.TYPE_STRING, isEnum: true, public: true, label: () => _('Date format'), options: () => {
let options = {}
@@ -49,16 +55,17 @@ class Setting extends BaseModel {
output[Setting.THEME_DARK] = _('Dark');
return output;
}},
// 'logLevel': { value: Logger.LEVEL_INFO, type: Setting.TYPE_STRING, isEnum: true, public: true, label: () => _('Log level'), options: () => {
// return Logger.levelEnum();
// }},
// Not used for now:
// 'todoFilter': { value: 'all', type: Setting.TYPE_STRING, isEnum: true, public: false, appTypes: ['mobile'], label: () => _('Todo filter'), options: () => ({
// all: _('Show all'),
// recent: _('Non-completed and recently completed ones'),
// nonCompleted: _('Non-completed ones only'),
// })},
'uncompletedTodosOnTop': { value: true, type: Setting.TYPE_BOOL, public: true, label: () => _('Show uncompleted to-dos on top of the lists') },
'uncompletedTodosOnTop': { value: true, type: Setting.TYPE_BOOL, public: true, appTypes: ['cli'], label: () => _('Uncompleted to-dos on top') },
'notes.sortOrder.field': { value: 'user_updated_time', type: Setting.TYPE_STRING, isEnum: true, public: true, appTypes: ['cli'], label: () => _('Sort notes by'), options: () => {
const Note = require('lib/models/Note');
const noteSortFields = ['user_updated_time', 'user_created_time', 'title'];
const options = {};
for (let i = 0; i < noteSortFields.length; i++) {
options[noteSortFields[i]] = toTitleCase(Note.fieldToLabel(noteSortFields[i]));
}
return options;
}},
'notes.sortOrder.reverse': { value: true, type: Setting.TYPE_BOOL, public: true, label: () => _('Reverse sort order'), appTypes: ['cli'] },
'trackLocation': { value: true, type: Setting.TYPE_BOOL, public: true, label: () => _('Save geo-location with notes') },
'newTodoFocus': { value: 'title', type: Setting.TYPE_STRING, isEnum: true, public: true, appTypes: ['desktop'], label: () => _('When creating a new to-do:'), options: () => {
return {
@@ -76,7 +83,8 @@ class Setting extends BaseModel {
'encryption.enabled': { value: false, type: Setting.TYPE_BOOL, public: false },
'encryption.activeMasterKeyId': { value: '', type: Setting.TYPE_STRING, public: false },
'encryption.passwordCache': { value: {}, type: Setting.TYPE_OBJECT, public: false },
'style.zoom': {value: "100", type: Setting.TYPE_INT, public: true, appTypes: ['desktop'], label: () => _('Set application zoom percentage'), minimum: "50", maximum: "500", step: "10"},
'style.zoom': {value: "100", type: Setting.TYPE_INT, public: true, appTypes: ['desktop'], label: () => _('Global zoom percentage'), minimum: "50", maximum: "500", step: "10"},
'style.editor.fontFamily': {value: "", type: Setting.TYPE_STRING, public: true, appTypes: ['desktop'], label: () => _('Editor font family'), description: () => _('The font name will not be checked. If incorrect or empty, it will default to a generic monospace font.')},
'autoUpdateEnabled': { value: true, type: Setting.TYPE_BOOL, public: true, appTypes: ['desktop'], label: () => _('Automatically update the application') },
'sync.interval': { value: 300, type: Setting.TYPE_INT, isEnum: true, public: true, label: () => _('Synchronisation interval'), options: () => {
return {
@@ -91,7 +99,7 @@ class Setting extends BaseModel {
}},
'noteVisiblePanes': { value: ['editor', 'viewer'], type: Setting.TYPE_ARRAY, public: false, appTypes: ['desktop'] },
'showAdvancedOptions': { value: false, type: Setting.TYPE_BOOL, public: true, appTypes: ['mobile' ], label: () => _('Show advanced options') },
'sync.target': { value: SyncTargetRegistry.nameToId('onedrive'), type: Setting.TYPE_INT, isEnum: true, public: true, label: () => _('Synchronisation target'), description: () => _('The target to synchonise to. Each sync target may have additional parameters which are named as `sync.NUM.NAME` (all documented below).'), options: () => {
'sync.target': { value: SyncTargetRegistry.nameToId('onedrive'), type: Setting.TYPE_INT, isEnum: true, public: true, label: () => _('Synchronisation target'), description: (appType) => { return appType !== 'cli' ? null : _('The target to synchonise to. Each sync target may have additional parameters which are named as `sync.NUM.NAME` (all documented below).') }, options: () => {
return SyncTargetRegistry.idAndLabelPlainObject();
}},
@@ -101,7 +109,7 @@ class Setting extends BaseModel {
} catch (error) {
return false;
}
}, public: true, label: () => _('Directory to synchronise with (absolute path)'), description: () => _('The path to synchronise with when file system synchronisation is enabled. See `sync.target`.') },
}, public: true, label: () => _('Directory to synchronise with (absolute path)'), description: (appType) => { return appType !== 'cli' ? null : _('The path to synchronise with when file system synchronisation is enabled. See `sync.target`.'); } },
'sync.5.path': { value: '', type: Setting.TYPE_STRING, show: (settings) => { return settings['sync.target'] == SyncTargetRegistry.nameToId('nextcloud') }, public: true, label: () => _('Nextcloud WebDAV URL') },
'sync.5.username': { value: '', type: Setting.TYPE_STRING, show: (settings) => { return settings['sync.target'] == SyncTargetRegistry.nameToId('nextcloud') }, public: true, label: () => _('Nextcloud username') },
@@ -136,6 +144,12 @@ class Setting extends BaseModel {
return key in this.metadata();
}
static keyDescription(key, appType = null) {
const md = this.settingMetadata(key);
if (!md.description) return null;
return md.description(appType);
}
static keys(publicOnly = false, appType = null) {
if (!this.keys_) {
const metadata = this.metadata();

View File

@@ -102,6 +102,10 @@ class Tag extends BaseItem {
return this.modelSelectAll('SELECT * FROM tags WHERE id IN ("' + tagIds.join('","') + '")');
}
static async loadByTitle(title) {
return this.loadByField('title', title, { caseInsensitive: true });
}
static async setNoteTagsByTitles(noteId, tagTitles) {
const previousTags = await this.tagsByNoteId(noteId);
const addedTitles = [];
@@ -109,14 +113,14 @@ class Tag extends BaseItem {
for (let i = 0; i < tagTitles.length; i++) {
const title = tagTitles[i].trim().toLowerCase();
if (!title) continue;
let tag = await this.loadByField('title', title, { caseInsensitive: true });
let tag = await this.loadByTitle(title);
if (!tag) tag = await Tag.save({ title: title }, { userSideValidation: true });
await this.addNote(tag.id, noteId);
addedTitles.push(title);
}
for (let i = 0; i < previousTags.length; i++) {
if (addedTitles.indexOf(previousTags[i].title) < 0) {
if (addedTitles.indexOf(previousTags[i].title.toLowerCase()) < 0) {
await this.removeNote(previousTags[i].id, noteId);
}
}

View File

@@ -19,19 +19,24 @@ const defaultState = {
showSideMenu: false,
screens: {},
historyCanGoBack: false,
notesOrder: [
{ by: 'user_updated_time', dir: 'DESC' },
],
syncStarted: false,
syncReport: {},
searchQuery: '',
settings: {},
appState: 'starting',
//windowContentSize: { width: 0, height: 0 },
hasDisabledSyncItems: false,
newNote: null,
};
const stateUtils = {};
stateUtils.notesOrder = function(stateSettings) {
return [{
by: stateSettings['notes.sortOrder.field'],
dir: stateSettings['notes.sortOrder.reverse'] ? 'DESC' : 'ASC',
}];
}
function arrayHasEncryptedItems(array) {
for (let i = 0; i < array.length; i++) {
if (!!array[i].encryption_applied) return true;
@@ -90,8 +95,6 @@ function handleItemDelete(state, action) {
}
function updateOneItem(state, action) {
// let newItems = action.type === 'TAG_UPDATE_ONE' ? state.tags.splice(0) : state.folders.splice(0);
// let item = action.type === 'TAG_UPDATE_ONE' ? action.tag : action.folder;
let itemsKey = null;
if (action.type === 'TAG_UPDATE_ONE') itemsKey = 'tags';
if (action.type === 'FOLDER_UPDATE_ONE') itemsKey = 'folders';
@@ -116,12 +119,6 @@ function updateOneItem(state, action) {
newState[itemsKey] = newItems;
// if (action.type === 'TAG_UPDATE_ONE') {
// newState.tags = newItems;
// } else {
// newState.folders = newItems;
// }
return newState;
}
@@ -316,7 +313,8 @@ const reducer = (state = defaultState, action) => {
}
}
newNotes = Note.sortNotes(newNotes, state.notesOrder, newState.settings.uncompletedTodosOnTop);
//newNotes = Note.sortNotes(newNotes, state.notesOrder, newState.settings.uncompletedTodosOnTop);
newNotes = Note.sortNotes(newNotes, stateUtils.notesOrder(state.settings), newState.settings.uncompletedTodosOnTop);
newState = Object.assign({}, state);
newState.notes = newNotes;
@@ -481,4 +479,4 @@ const reducer = (state = defaultState, action) => {
return newState;
}
module.exports = { reducer, defaultState };
module.exports = { reducer, defaultState, stateUtils };

View File

@@ -44,7 +44,7 @@ reg.syncTarget = (syncTargetId = null) => {
}
reg.scheduleSync = async (delay = null) => {
if (delay === null) delay = 1000 * 30;
if (delay === null) delay = 1000 * 10;
let promiseResolve = null;
const promise = new Promise((resolve, reject) => {
@@ -58,10 +58,10 @@ reg.scheduleSync = async (delay = null) => {
reg.logger().info('Scheduling sync operation...');
// if (Setting.value('env') === 'dev') {
// reg.logger().info('Scheduling sync operation DISABLED!!!');
// return;
// }
if (Setting.value('env') === 'dev' && delay !== 0) {
reg.logger().info('Schedule sync DISABLED!!!');
return;
}
const timeoutCallback = async () => {
reg.scheduleSyncId_ = null;
@@ -147,6 +147,11 @@ reg.setupRecurrentSync = () => {
} else {
reg.logger().debug('Setting up recurrent sync with interval ' + Setting.value('sync.interval'));
if (Setting.value('env') === 'dev') {
reg.logger().info('Recurrent sync operation DISABLED!!!');
return;
}
reg.recurrentSyncId_ = shim.setInterval(() => {
reg.logger().info('Running background sync on timer...');
reg.scheduleSync(0);

View File

@@ -96,6 +96,11 @@ class DecryptionWorker {
}
continue;
}
if (error.code === 'masterKeyNotLoaded' && options.materKeyNotLoadedHandler === 'throw') {
throw error;
}
this.logger().warn('DecryptionWorker: error for: ' + item.id + ' (' + ItemClass.tableName() + ')', error);
}
}

View File

@@ -0,0 +1,246 @@
const BaseItem = require('lib/models/BaseItem.js');
const BaseModel = require('lib/BaseModel.js');
const Resource = require('lib/models/Resource.js');
const Folder = require('lib/models/Folder.js');
const NoteTag = require('lib/models/NoteTag.js');
const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');
const { basename, filename } = require('lib/path-utils.js');
const fs = require('fs-extra');
const ArrayUtils = require('lib/ArrayUtils');
const { sprintf } = require('sprintf-js');
const { shim } = require('lib/shim');
const { _ } = require('lib/locale');
const { fileExtension } = require('lib/path-utils');
const { uuid } = require('lib/uuid.js');
const { toTitleCase } = require('lib/string-utils');
class InteropService {
constructor() {
this.modules_ = null;
}
modules() {
if (this.modules_) return this.modules_;
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;
}
newModule_(type, 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();
}
moduleByFileExtension_(type, ext) {
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) {
if (!await shim.fsDriver().exists(options.path)) throw new Error(_('Cannot find "%s".', options.path));
options = Object.assign({}, {
format: 'auto',
destinationFolderId: null,
destinationFolder: null,
}, options);
if (options.format === 'auto') {
const module = this.moduleByFileExtension_('importer', fileExtension(options.path));
if (!module) throw new Error(_('Please specify import format for %s', options.path));
options.format = module.format;
}
if (options.destinationFolderId) {
const folder = await Folder.load(options.destinationFolderId);
if (!folder) throw new Error(_('Cannot find "%s".', options.destinationFolderId));
options.destinationFolder = folder;
}
let result = { warnings: [] }
const importer = this.newModule_('importer', options.format);
await importer.init(options.path, options);
result = await importer.exec(result);
return result;
}
async export(options) {
const exportPath = options.path ? options.path : null;
const sourceFolderIds = options.sourceFolderIds ? options.sourceFolderIds : [];
const sourceNoteIds = options.sourceNoteIds ? options.sourceNoteIds : [];
const exportFormat = options.format ? options.format : 'jex';
const result = { warnings: [] }
const itemsToExport = [];
const queueExportItem = (itemType, itemOrId) => {
itemsToExport.push({
type: itemType,
itemOrId: itemOrId
});
}
let exportedNoteIds = [];
let resourceIds = [];
const folderIds = await Folder.allIds();
for (let folderIndex = 0; folderIndex < folderIds.length; folderIndex++) {
const folderId = folderIds[folderIndex];
if (sourceFolderIds.length && sourceFolderIds.indexOf(folderId) < 0) continue;
if (!sourceNoteIds.length) await queueExportItem(BaseModel.TYPE_FOLDER, folderId);
const noteIds = await Folder.noteIds(folderId);
for (let noteIndex = 0; noteIndex < noteIds.length; noteIndex++) {
const noteId = noteIds[noteIndex];
if (sourceNoteIds.length && sourceNoteIds.indexOf(noteId) < 0) continue;
const note = await Note.load(noteId);
await queueExportItem(BaseModel.TYPE_NOTE, note);
exportedNoteIds.push(noteId);
const rids = Note.linkedResourceIds(note.body);
resourceIds = resourceIds.concat(rids);
}
}
resourceIds = ArrayUtils.unique(resourceIds);
for (let i = 0; i < resourceIds.length; i++) {
await queueExportItem(BaseModel.TYPE_RESOURCE, resourceIds[i]);
}
const noteTags = await NoteTag.all();
let exportedTagIds = [];
for (let i = 0; i < noteTags.length; i++) {
const noteTag = noteTags[i];
if (exportedNoteIds.indexOf(noteTag.note_id) < 0) continue;
await queueExportItem(BaseModel.TYPE_NOTE_TAG, noteTag.id);
exportedTagIds.push(noteTag.tag_id);
}
for (let i = 0; i < exportedTagIds.length; i++) {
await queueExportItem(BaseModel.TYPE_TAG, exportedTagIds[i]);
}
const exporter = this.newModule_('exporter', exportFormat);
await exporter.init(exportPath);
for (let i = 0; i < itemsToExport.length; i++) {
const itemType = itemsToExport[i].type;
const ItemClass = BaseItem.getClassByItemType(itemType);
const itemOrId = itemsToExport[i].itemOrId;
const item = typeof itemOrId === 'object' ? itemOrId : await ItemClass.load(itemOrId);
if (!item) {
if (itemType === BaseModel.TYPE_RESOURCE) {
result.warnings.push(sprintf('A resource that does not exist is referenced in a note. The resource was skipped. Resource ID: %s', itemOrId));
} else {
result.warnings.push(sprintf('Cannot find item with type "%s" and ID %s. Item was skipped.', ItemClass.tableName(), JSON.stringify(itemOrId)));
}
continue;
}
if (item.encryption_applied || item.encryption_blob_encrypted) throw new Error(_('This item is currently encrypted: %s "%s". Please wait for all items to be decrypted and try again.', BaseModel.modelTypeToName(itemType), item.title ? item.title : item.id));
try {
if (itemType == BaseModel.TYPE_RESOURCE) {
const resourcePath = Resource.fullPath(item);
await exporter.processResource(item, resourcePath);
}
await exporter.processItem(ItemClass, item);
} catch (error) {
result.warnings.push(error.message);
}
}
await exporter.close();
return result;
}
}
module.exports = InteropService;

View File

@@ -0,0 +1,17 @@
class InteropService_Exporter_Base {
async init(destDir) {}
async processItem(ItemClass, item) {}
async processResource(resource, filePath) {}
async close() {}
async temporaryDirectory_(createIt) {
const md5 = require('md5');
const tempDir = require('os').tmpdir() + '/' + md5(Math.random() + Date.now());
if (createIt) await require('fs-extra').mkdirp(tempDir);
return tempDir;
}
}
module.exports = InteropService_Exporter_Base;

View File

@@ -0,0 +1,57 @@
const InteropService_Exporter_Base = require('lib/services/InteropService_Exporter_Base');
const InteropService_Exporter_Raw = require('lib/services/InteropService_Exporter_Raw');
const BaseItem = require('lib/models/BaseItem.js');
const BaseModel = require('lib/BaseModel.js');
const Resource = require('lib/models/Resource.js');
const Folder = require('lib/models/Folder.js');
const NoteTag = require('lib/models/NoteTag.js');
const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');
const { basename, filename } = require('lib/path-utils.js');
const fs = require('fs-extra');
const md5 = require('md5');
const { sprintf } = require('sprintf-js');
const { shim } = require('lib/shim');
const { _ } = require('lib/locale');
const { fileExtension } = require('lib/path-utils');
const { uuid } = require('lib/uuid.js');
const { importEnex } = require('lib/import-enex');
class InteropService_Exporter_Jex extends InteropService_Exporter_Base {
async init(destPath) {
if (await shim.fsDriver().isDirectory(destPath)) throw new Error('Path is a directory: ' + destPath);
this.tempDir_ = await this.temporaryDirectory_(false);
this.destPath_ = destPath;
this.rawExporter_ = new InteropService_Exporter_Raw();
await this.rawExporter_.init(this.tempDir_);
}
async processItem(ItemClass, item) {
return this.rawExporter_.processItem(ItemClass, item);
}
async processResource(resource, filePath) {
return this.rawExporter_.processResource(resource, filePath);
}
async close() {
const stats = await shim.fsDriver().readDirStats(this.tempDir_, { recursive: true });
const filePaths = stats.filter((a) => !a.isDirectory()).map((a) => a.path);
if (!filePaths.length) throw new Error(_('There is no data to export.'));
await require('tar').create({
strict: true,
portable: true,
file: this.destPath_,
cwd: this.tempDir_,
}, filePaths);
await fs.remove(this.tempDir_);
}
}
module.exports = InteropService_Exporter_Jex;

View File

@@ -0,0 +1,30 @@
const InteropService_Exporter_Base = require('lib/services/InteropService_Exporter_Base');
const { basename, filename } = require('lib/path-utils.js');
const { shim } = require('lib/shim');
class InteropService_Exporter_Raw extends InteropService_Exporter_Base {
async init(destDir) {
this.destDir_ = destDir;
this.resourceDir_ = destDir ? destDir + '/resources' : null;
await shim.fsDriver().mkdir(this.destDir_);
await shim.fsDriver().mkdir(this.resourceDir_);
}
async processItem(ItemClass, item) {
const serialized = await ItemClass.serialize(item);
const filePath = this.destDir_ + '/' + ItemClass.systemPath(item);
await shim.fsDriver().writeFile(filePath, serialized, 'utf-8');
}
async processResource(resource, filePath) {
const destResourcePath = this.resourceDir_ + '/' + basename(filePath);
await shim.fsDriver().copy(filePath, destResourcePath);
}
async close() {}
}
module.exports = InteropService_Exporter_Raw;

View File

@@ -0,0 +1,19 @@
class InteropService_Importer_Base {
async init(sourcePath, options) {
this.sourcePath_ = sourcePath;
this.options_ = options;
}
async exec(result) {}
async temporaryDirectory_(createIt) {
const md5 = require('md5');
const tempDir = require('os').tmpdir() + '/' + md5(Math.random() + Date.now());
if (createIt) await require('fs-extra').mkdirp(tempDir);
return tempDir;
}
}
module.exports = InteropService_Importer_Base;

View File

@@ -0,0 +1,37 @@
const InteropService_Importer_Base = require('lib/services/InteropService_Importer_Base');
const BaseItem = require('lib/models/BaseItem.js');
const BaseModel = require('lib/BaseModel.js');
const Resource = require('lib/models/Resource.js');
const Folder = require('lib/models/Folder.js');
const NoteTag = require('lib/models/NoteTag.js');
const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');
const { basename, filename } = require('lib/path-utils.js');
const fs = require('fs-extra');
const md5 = require('md5');
const { sprintf } = require('sprintf-js');
const { shim } = require('lib/shim');
const { _ } = require('lib/locale');
const { fileExtension } = require('lib/path-utils');
const { uuid } = require('lib/uuid.js');
class InteropService_Importer_Enex extends InteropService_Importer_Base {
async exec(result) {
const { importEnex } = require('lib/import-enex');
let folder = this.options_.destinationFolder;
if (!folder) {
const folderTitle = await Folder.findUniqueFolderTitle(filename(this.sourcePath_));
folder = await Folder.save({ title: folderTitle });
}
await importEnex(folder.id, this.sourcePath_, this.options_);
return result;
}
}
module.exports = InteropService_Importer_Enex;

View File

@@ -0,0 +1,52 @@
const InteropService_Importer_Base = require('lib/services/InteropService_Importer_Base');
const InteropService_Importer_Raw = require('lib/services/InteropService_Importer_Raw');
const BaseItem = require('lib/models/BaseItem.js');
const BaseModel = require('lib/BaseModel.js');
const Resource = require('lib/models/Resource.js');
const Folder = require('lib/models/Folder.js');
const NoteTag = require('lib/models/NoteTag.js');
const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');
const { basename, filename } = require('lib/path-utils.js');
const fs = require('fs-extra');
const md5 = require('md5');
const { sprintf } = require('sprintf-js');
const { shim } = require('lib/shim');
const { _ } = require('lib/locale');
const { fileExtension } = require('lib/path-utils');
const { uuid } = require('lib/uuid.js');
const { importEnex } = require('lib/import-enex');
class InteropService_Importer_Jex extends InteropService_Importer_Base {
async exec(result) {
const tempDir = await this.temporaryDirectory_(true);
try {
await require('tar').extract({
strict: true,
portable: true,
file: this.sourcePath_,
cwd: tempDir,
});
} catch (error) {
let msg = ['Cannot untar file ' + this.sourcePath_, error.message];
if (error.data) msg.push(JSON.stringify(error.data));
let e = new Error(msg.join(': '));
throw e;
}
if (!('defaultFolderTitle' in this.options_)) this.options_.defaultFolderTitle = filename(this.sourcePath_);
const importer = new InteropService_Importer_Raw();
await importer.init(tempDir, this.options_);
result = await importer.exec(result);
await fs.remove(tempDir);
return result;
}
}
module.exports = InteropService_Importer_Jex;

Some files were not shown because too many files have changed in this diff Show More