1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-08-30 20:39:46 +02:00

Compare commits

...

41 Commits

Author SHA1 Message Date
Laurent Cozic
1bc4d6b423 CLI v1.0.97 2018-02-16 23:08:57 +00:00
Laurent Cozic
baa9ca7ea3 Updated terminal readme 2018-02-16 23:08:04 +00:00
Laurent Cozic
e4d477fb4c CLI: Resolve #63: Allow customizing shortcuts 2018-02-16 22:53:53 +00:00
Laurent Cozic
71319eee28 Also add link to po file in translation table 2018-02-16 21:45:28 +00:00
Laurent Cozic
68b31526f8 Updated translations 2018-02-16 21:39:54 +00:00
Laurent Cozic
0b2b7324d9 Merge pull request #224 from fmrtn/master
Fixed and updated Spanish translation
2018-02-16 20:35:46 +00:00
Fernando
43512cf27b Fixed and updated Spanish translation 2018-02-16 20:36:58 +01:00
Laurent Cozic
4218b65969 Electron: Added several keyboard shortcuts 2018-02-16 18:08:02 +00:00
Laurent Cozic
7244e12b78 Electron: Fixed: Confirmation message boxes, and release notes text 2018-02-16 18:06:02 +00:00
Laurent Cozic
a796ef5c66 All: Fixed: Notify DecryptionWorker when item added due to conflict, to make sure it is decrypted as early as possible 2018-02-16 17:55:50 +00:00
Laurent Cozic
9474a05aaa Update readme 2018-02-16 00:17:05 +00:00
Laurent Cozic
41df355a7e CLI v1.0.96 2018-02-15 23:51:00 +00:00
Laurent Cozic
4f3ab87914 Electron release v1.0.64 2018-02-15 23:21:47 +00:00
Laurent Cozic
5d1a08707c Android release v1.0.98 2018-02-15 23:20:28 +00:00
Laurent Cozic
4f822df80e Merge branch 'master' of github.com:laurent22/joplin 2018-02-15 23:05:34 +00:00
Laurent Cozic
951be5cbf6 Electron: Fixes #201, Fixes #216: Make sure only one update check can run at a time, and improved modal dialog boxes 2018-02-15 23:05:04 +00:00
Laurent Cozic
b6c2341542 Merge pull request #219 from fmrtn/patch-1
Update README.md
2018-02-15 18:33:48 +00:00
Laurent Cozic
a6e6b49a9d Merge branch 'master' of github.com:laurent22/joplin 2018-02-15 18:33:21 +00:00
Laurent Cozic
3a4bbd571e All: Provide Content-Length header for WebDAV for better compatibility with more servers 2018-02-15 18:33:08 +00:00
Laurent Cozic
feccc6150e Merge branch 'master' of github.com:laurent22/joplin 2018-02-15 18:19:11 +00:00
Laurent Cozic
a37b599a6b macOS: Allow copy and paste from config and encryption screen 2018-02-15 18:19:00 +00:00
Laurent Cozic
9347683fe3 All: Fixed Nextcloud sync target ID (which was incorrectly set to WebDAV sync ID) 2018-02-15 18:01:05 +00:00
Laurent Cozic
3551c26e28 Fixed race condition when testing with memory driver and fixed encoding issue 2018-02-15 17:12:09 +00:00
Fernando
cfca0107eb Update README.md 2018-02-15 13:09:59 +01:00
Laurent Cozic
81bc975193 Update readme 2018-02-14 21:54:34 +00:00
Laurent Cozic
7908fda451 Android release v1.0.97 2018-02-14 19:11:35 +00:00
Laurent Cozic
cdbb7c4b0d Merge branch 'master' of github.com:laurent22/joplin 2018-02-14 19:10:42 +00:00
Laurent Cozic
414e57ec55 Electron release v1.0.63 2018-02-14 19:08:24 +00:00
Laurent Cozic
1871123066 All: Improved WebDAV driver compatibility with some services (eg. Seafile) 2018-02-14 19:08:07 +00:00
Laurent Cozic
87bc08bef5 iOS: Fixed resource decryption issue, log page crash and text input rendering issue 2018-02-14 15:28:56 +00:00
Laurent Cozic
214a39c3d3 All: Improved the way settings are changed. Should also fixed issue with sync context being accidentally broken. 2018-02-13 18:26:33 +00:00
Laurent Cozic
ef0cc5e33e Update readme 2018-02-12 20:23:16 +00:00
Laurent Cozic
3a1fa583ab CLI v1.0.95 2018-02-12 18:07:20 +00:00
Laurent Cozic
c1161ae017 Android release v1.0.95 2018-02-12 17:58:55 +00:00
Laurent Cozic
1023ec6206 Electron release v1.0.62 2018-02-12 17:56:42 +00:00
Laurent Cozic
7841421c0d All: Fixes #209: Items with non-ASCII characters end up truncated on Nextcloud 2018-02-12 18:15:22 +00:00
Laurent Cozic
995d8c35dd Updated readme 2018-02-11 20:49:28 +00:00
Laurent Cozic
b179471eff Electron: Fixes #205: Importing Evernote notes while on import page re-imports previous import 2018-02-11 20:31:26 +00:00
Laurent Cozic
19a126ebfe Android release v1.0.94 2018-02-11 17:29:09 +00:00
Laurent Cozic
7e56e5b587 Mobile: Fixes #207: Crash when changing dropdown value in config screen 2018-02-11 17:26:20 +00:00
Laurent Cozic
acf0c79341 Graduated E2EE and WebDAV from beta, and moved to v1.0 2018-02-11 13:50:59 +00:00
83 changed files with 1434 additions and 801 deletions

View File

@@ -35,38 +35,55 @@ const ConsoleWidget = require('./gui/ConsoleWidget.js');
class AppGui {
constructor(app, store) {
this.app_ = app;
this.store_ = store;
constructor(app, store, keymap) {
try {
this.app_ = app;
this.store_ = store;
BaseWidget.setLogger(app.logger());
BaseWidget.setLogger(app.logger());
this.term_ = new TermWrapper(tk.terminal);
this.term_ = new TermWrapper(tk.terminal);
this.renderer_ = null;
this.logger_ = new Logger();
this.buildUi();
// Some keys are directly handled by the tkwidget framework
// so they need to be remapped in a different way.
this.tkWidgetKeys_ = {
'focus_next': 'TAB',
'focus_previous': 'SHIFT_TAB',
'move_up': 'UP',
'move_down': 'DOWN',
'page_down': 'PAGE_DOWN',
'page_up': 'PAGE_UP',
};
this.renderer_ = new Renderer(this.term(), this.rootWidget_);
this.renderer_ = null;
this.logger_ = new Logger();
this.buildUi();
this.app_.on('modelAction', async (event) => {
await this.handleModelAction(event.action);
});
this.renderer_ = new Renderer(this.term(), this.rootWidget_);
this.shortcuts_ = this.setupShortcuts();
this.app_.on('modelAction', async (event) => {
await this.handleModelAction(event.action);
});
this.inputMode_ = AppGui.INPUT_MODE_NORMAL;
this.keymap_ = this.setupKeymap(keymap);
this.commandCancelCalled_ = false;
this.inputMode_ = AppGui.INPUT_MODE_NORMAL;
this.currentShortcutKeys_ = [];
this.lastShortcutKeyTime_ = 0;
this.commandCancelCalled_ = false;
// Recurrent sync is setup only when the GUI is started. In
// a regular command it's not necessary since the process
// exits right away.
reg.setupRecurrentSync();
DecryptionWorker.instance().scheduleStart();
this.currentShortcutKeys_ = [];
this.lastShortcutKeyTime_ = 0;
// Recurrent sync is setup only when the GUI is started. In
// a regular command it's not necessary since the process
// exits right away.
reg.setupRecurrentSync();
DecryptionWorker.instance().scheduleStart();
} catch (error) {
this.fullScreen(false);
console.error(error);
process.exit(1);
}
}
store() {
@@ -105,6 +122,7 @@ class AppGui {
buildUi() {
this.rootWidget_ = new ReduxRootWidget(this.store_);
this.rootWidget_.name = 'root';
this.rootWidget_.autoShortcutsEnabled = false;
const folderList = new FolderListWidget();
folderList.style = {
@@ -272,152 +290,26 @@ class AppGui {
this.stdout(chalk.cyan.bold('> ' + cmd));
}
setupShortcuts() {
const shortcuts = {};
setupKeymap(keymap) {
const output = [];
shortcuts['TAB'] = {
friendlyName: 'Tab',
description: () => _('Give focus to next pane'),
isDocOnly: true,
}
for (let i = 0; i < keymap.length; i++) {
const item = Object.assign({}, keymap[i]);
shortcuts['SHIFT_TAB'] = {
friendlyName: 'Shift+Tab',
description: () => _('Give focus to previous pane'),
isDocOnly: true,
}
if (!item.command) throw new Error('Missing command for keymap item: ' + JSON.stringify(item));
shortcuts[':'] = {
description: () => _('Enter command line mode'),
action: async () => {
const cmd = await this.widget('statusBar').prompt();
if (!cmd) return;
this.addCommandToConsole(cmd);
await this.processCommand(cmd);
},
};
if (!('type' in item)) item.type = 'exec';
shortcuts['ESC'] = { // Built into terminal-kit inputField
description: () => _('Exit command line mode'),
isDocOnly: true,
};
shortcuts['ENTER'] = {
description: () => _('Edit the selected note'),
action: () => {
const w = this.widget('mainWindow').focusedWidget;
if (w.name === 'folderList') {
this.widget('noteList').focus();
} else if (w.name === 'noteList' || w.name === 'noteText') {
this.processCommand('edit $n');
}
},
}
shortcuts['CTRL_C'] = {
description: () => _('Cancel the current command.'),
friendlyName: 'Ctrl+C',
isDocOnly: true,
}
shortcuts['CTRL_D'] = {
description: () => _('Exit the application.'),
friendlyName: 'Ctrl+D',
isDocOnly: true,
}
shortcuts['DELETE'] = {
description: () => _('Delete the currently selected note or notebook.'),
action: async () => {
if (this.widget('folderList').hasFocus) {
const item = this.widget('folderList').selectedJoplinItem;
if (!item) return;
if (item.type_ === BaseModel.TYPE_FOLDER) {
await this.processCommand('rmbook ' + item.id);
} else if (item.type_ === BaseModel.TYPE_TAG) {
this.stdout(_('To delete a tag, untag the associated notes.'));
} else if (item.type_ === BaseModel.TYPE_SEARCH) {
this.store().dispatch({
type: 'SEARCH_DELETE',
id: item.id,
});
}
} else if (this.widget('noteList').hasFocus) {
await this.processCommand('rmnote $n');
} else {
this.stdout(_('Please select the note or notebook to be deleted first.'));
}
if (item.command in this.tkWidgetKeys_) {
item.type = 'tkwidgets';
}
};
shortcuts['BACKSPACE'] = {
alias: 'DELETE',
};
item.canRunAlongOtherCommands = item.type === 'function' && ['toggle_metadata', 'toggle_console'].indexOf(item.command) >= 0;
shortcuts[' '] = {
friendlyName: 'SPACE',
description: () => _('Set a to-do as completed / not completed'),
action: 'todo toggle $n',
output.push(item);
}
shortcuts['tc'] = {
description: () => _('[t]oggle [c]onsole between maximized/minimized/hidden/visible.'),
action: () => {
if (!this.consoleIsShown()) {
this.showConsole();
this.minimizeConsole();
} else {
if (this.consoleIsMaximized()) {
this.hideConsole();
} else {
this.maximizeConsole();
}
}
},
canRunAlongOtherCommands: true,
}
shortcuts['/'] = {
description: () => _('Search'),
action: { type: 'prompt', initialText: 'search ""', cursorPosition: -2 },
};
shortcuts['tm'] = {
description: () => _('[t]oggle note [m]etadata.'),
action: () => {
this.toggleNoteMetadata();
},
canRunAlongOtherCommands: true,
}
shortcuts['mn'] = {
description: () => _('[M]ake a new [n]ote'),
action: { type: 'prompt', initialText: 'mknote ""', cursorPosition: -2 },
}
shortcuts['mt'] = {
description: () => _('[M]ake a new [t]odo'),
action: { type: 'prompt', initialText: 'mktodo ""', cursorPosition: -2 },
}
shortcuts['mb'] = {
description: () => _('[M]ake a new note[b]ook'),
action: { type: 'prompt', initialText: 'mkbook ""', cursorPosition: -2 },
}
shortcuts['yn'] = {
description: () => _('Copy ([Y]ank) the [n]ote to a notebook.'),
action: { type: 'prompt', initialText: 'cp $n ""', cursorPosition: -2 },
}
shortcuts['dn'] = {
description: () => _('Move the note to a notebook.'),
action: { type: 'prompt', initialText: 'mv $n ""', cursorPosition: -2 },
}
return shortcuts;
return output;
}
toggleConsole() {
@@ -492,8 +384,16 @@ class AppGui {
return this.logger_;
}
shortcuts() {
return this.shortcuts_;
keymap() {
return this.keymap_;
}
keymapItemByKey(key) {
for (let i = 0; i < this.keymap_.length; i++) {
const item = this.keymap_[i];
if (item.keys.indexOf(key) >= 0) return item;
}
return null;
}
term() {
@@ -524,18 +424,78 @@ class AppGui {
}
}
async processCommand(cmd) {
async processFunctionCommand(cmd) {
if (cmd === 'activate') {
const w = this.widget('mainWindow').focusedWidget;
if (w.name === 'folderList') {
this.widget('noteList').focus();
} else if (w.name === 'noteList' || w.name === 'noteText') {
this.processPromptCommand('edit $n');
}
} else if (cmd === 'delete') {
if (this.widget('folderList').hasFocus) {
const item = this.widget('folderList').selectedJoplinItem;
if (!item) return;
if (item.type_ === BaseModel.TYPE_FOLDER) {
await this.processPromptCommand('rmbook ' + item.id);
} else if (item.type_ === BaseModel.TYPE_TAG) {
this.stdout(_('To delete a tag, untag the associated notes.'));
} else if (item.type_ === BaseModel.TYPE_SEARCH) {
this.store().dispatch({
type: 'SEARCH_DELETE',
id: item.id,
});
}
} else if (this.widget('noteList').hasFocus) {
await this.processPromptCommand('rmnote $n');
} else {
this.stdout(_('Please select the note or notebook to be deleted first.'));
}
} else if (cmd === 'toggle_console') {
if (!this.consoleIsShown()) {
this.showConsole();
this.minimizeConsole();
} else {
if (this.consoleIsMaximized()) {
this.hideConsole();
} else {
this.maximizeConsole();
}
}
} else if (cmd === 'toggle_metadata') {
this.toggleNoteMetadata();
} else if (cmd === 'enter_command_line_mode') {
const cmd = await this.widget('statusBar').prompt();
if (!cmd) return;
this.addCommandToConsole(cmd);
await this.processPromptCommand(cmd);
} else {
throw new Error('Unknown command: ' + cmd);
}
}
async processPromptCommand(cmd) {
if (!cmd) return;
cmd = cmd.trim();
if (!cmd.length) return;
this.logger().info('Got command: ' + cmd);
if (cmd === 'q' || cmd === 'wq' || cmd === 'qa') { // Vim bonus
await this.app().exit();
return;
}
try {
let note = this.widget('noteList').currentItem;
let folder = this.widget('folderList').currentItem;
@@ -786,35 +746,34 @@ class AppGui {
// -------------------------------------------------------------------------
const shortcutKey = this.currentShortcutKeys_.join('');
let cmd = shortcutKey in this.shortcuts_ ? this.shortcuts_[shortcutKey] : null;
let keymapItem = this.keymapItemByKey(shortcutKey);
// If this command is an alias to another command, resolve to the actual command
if (cmd && cmd.alias) cmd = this.shortcuts_[cmd.alias];
let processShortcutKeys = !this.app().currentCommand() && cmd;
if (cmd && cmd.canRunAlongOtherCommands) processShortcutKeys = true;
let processShortcutKeys = !this.app().currentCommand() && keymapItem;
if (keymapItem && keymapItem.canRunAlongOtherCommands) processShortcutKeys = true;
if (statusBar.promptActive) processShortcutKeys = false;
if (cmd && cmd.isDocOnly) processShortcutKeys = false;
if (processShortcutKeys) {
this.logger().info('Shortcut:', shortcutKey, cmd.description());
this.logger().info('Shortcut:', shortcutKey, keymapItem);
this.currentShortcutKeys_ = [];
if (typeof cmd.action === 'function') {
await cmd.action();
} else if (typeof cmd.action === 'object') {
if (cmd.action.type === 'prompt') {
let promptOptions = {};
if ('cursorPosition' in cmd.action) promptOptions.cursorPosition = cmd.action.cursorPosition;
const commandString = await statusBar.prompt(cmd.action.initialText ? cmd.action.initialText : '', null, promptOptions);
this.addCommandToConsole(commandString);
await this.processCommand(commandString);
} else {
throw new Error('Unknown command: ' + JSON.stringify(cmd.action));
}
} else { // String
this.stdout(cmd.action);
await this.processCommand(cmd.action);
if (keymapItem.type === 'function') {
this.processFunctionCommand(keymapItem.command);
} else if (keymapItem.type === 'prompt') {
let promptOptions = {};
if ('cursorPosition' in keymapItem) promptOptions.cursorPosition = keymapItem.cursorPosition;
const commandString = await statusBar.prompt(keymapItem.command ? keymapItem.command : '', null, promptOptions);
this.addCommandToConsole(commandString);
await this.processPromptCommand(commandString);
} else if (keymapItem.type === 'exec') {
this.stdout(keymapItem.command);
await this.processPromptCommand(keymapItem.command);
} else if (keymapItem.type === 'tkwidgets') {
this.widget('root').handleKey(this.tkWidgetKeys_[keymapItem.command]);
} else {
throw new Error('Unknown command type: ' + JSON.stringify(keymapItem));
}
}

View File

@@ -312,6 +312,63 @@ class Application extends BaseApplication {
return this.activeCommand_;
}
async loadKeymaps() {
const defaultKeyMap = [
{ "keys": [":"], "type": "function", "command": "enter_command_line_mode" },
{ "keys": ["TAB"], "type": "function", "command": "focus_next" },
{ "keys": ["SHIFT_TAB"], "type": "function", "command": "focus_previous" },
{ "keys": ["UP"], "type": "function", "command": "move_up" },
{ "keys": ["DOWN"], "type": "function", "command": "move_down" },
{ "keys": ["PAGE_UP"], "type": "function", "command": "page_up" },
{ "keys": ["PAGE_DOWN"], "type": "function", "command": "page_down" },
{ "keys": ["ENTER"], "type": "function", "command": "activate" },
{ "keys": ["DELETE", "BACKSPACE"], "type": "function", "command": "delete" },
{ "keys": [" "], "command": "todo toggle $n" },
{ "keys": ["tc"], "type": "function", "command": "toggle_console" },
{ "keys": ["tm"], "type": "function", "command": "toggle_metadata" },
{ "keys": ["/"], "type": "prompt", "command": "search \"\"", "cursorPosition": -2 },
{ "keys": ["mn"], "type": "prompt", "command": "mknote \"\"", "cursorPosition": -2 },
{ "keys": ["mt"], "type": "prompt", "command": "mktodo \"\"", "cursorPosition": -2 },
{ "keys": ["mb"], "type": "prompt", "command": "mkbook \"\"", "cursorPosition": -2 },
{ "keys": ["yn"], "type": "prompt", "command": "cp $n \"\"", "cursorPosition": -2 },
{ "keys": ["dn"], "type": "prompt", "command": "mv $n \"\"", "cursorPosition": -2 }
];
// Filter the keymap item by command so that items in keymap.json can override
// the default ones.
const itemsByCommand = {};
for (let i = 0; i < defaultKeyMap.length; i++) {
itemsByCommand[defaultKeyMap[i].command] = defaultKeyMap[i]
}
const filePath = Setting.value('profileDir') + '/keymap.json';
if (await fs.pathExists(filePath)) {
try {
let configString = await fs.readFile(filePath, 'utf-8');
configString = configString.replace(/^\s*\/\/.*/, ''); // Strip off comments
const keymap = JSON.parse(configString);
for (let keymapIndex = 0; keymapIndex < keymap.length; keymapIndex++) {
const item = keymap[keymapIndex];
itemsByCommand[item.command] = item;
}
} catch (error) {
let msg = error.message ? error.message : '';
msg = 'Could not load keymap ' + filePath + '\n' + msg;
error.message = msg;
throw error;
}
}
const output = [];
for (let n in itemsByCommand) {
if (!itemsByCommand.hasOwnProperty(n)) continue;
output.push(itemsByCommand[n]);
}
return output;
}
async start(argv) {
argv = await super.start(argv);
@@ -338,8 +395,10 @@ class Application extends BaseApplication {
} else { // Otherwise open the GUI
this.initRedux();
const keymap = await this.loadKeymaps();
const AppGui = require('./app-gui.js');
this.gui_ = new AppGui(this, this.store());
this.gui_ = new AppGui(this, this.store(), keymap);
this.gui_.setLogger(this.logger_);
await this.gui_.start();

View File

@@ -36,21 +36,19 @@ class Command extends BaseCommand {
async action(args) {
const stdoutWidth = app().commandStdoutMaxWidth();
if (args.command === 'shortcuts') {
if (args.command === 'shortcuts' || args.command === 'keymap') {
if (app().gui().isDummy()) {
throw new Error(_('Shortcuts are not available in CLI mode.'));
}
const shortcuts = app().gui().shortcuts();
const keymap = app().gui().keymap();
let rows = [];
for (let n in shortcuts) {
if (!shortcuts.hasOwnProperty(n)) continue;
const shortcut = shortcuts[n];
if (!shortcut.description) continue;
n = shortcut.friendlyName ? shortcut.friendlyName : n;
rows.push([n, shortcut.description()]);
for (let i = 0; i < keymap.length; i++) {
const item = keymap[i];
const keys = item.keys.map((k) => k === ' ' ? '(SPACE)' : k);
rows.push([keys.join(', '), item.command]);
}
cliUtils.printArray(this.stdout.bind(this), rows);
@@ -78,7 +76,7 @@ class Command extends BaseCommand {
this.stdout(_('To maximise/minimise the console, press "TC".'));
this.stdout(_('To enter command line mode, press ":"'));
this.stdout(_('To exit command line mode, press ESCAPE'));
this.stdout(_('For the complete list of available keyboard shortcuts, type `help shortcuts`'));
this.stdout(_('For the complete list of available keyboard shortcuts, type `help keymap`'));
}
app().gui().showConsole();

View File

@@ -645,6 +645,10 @@ msgstr "Evernote Notizen importieren"
msgid "Evernote Export Files"
msgstr "Evernote Export Dateien"
#, javascript-format
msgid "Hide %s"
msgstr ""
msgid "Quit"
msgstr "Verlassen"
@@ -663,6 +667,12 @@ msgstr "Einfügen"
msgid "Search in all the notes"
msgstr "Alle Notizen durchsuchen"
msgid "View"
msgstr ""
msgid "Toggle editor layout"
msgstr ""
msgid "Tools"
msgstr "Werkzeuge"
@@ -704,9 +714,6 @@ msgstr "OK"
msgid "Cancel"
msgstr "Abbrechen"
msgid "Error"
msgstr ""
#, fuzzy, javascript-format
msgid ""
"Release notes:\n"
@@ -970,7 +977,8 @@ msgstr "Unbekanntes Argument: %s"
msgid "File system"
msgstr "Dateisystem"
msgid "Nextcloud (Beta)"
#, fuzzy
msgid "Nextcloud"
msgstr "Nextcloud (Beta)"
msgid "OneDrive"
@@ -979,8 +987,9 @@ msgstr "OneDrive"
msgid "OneDrive Dev (For testing only)"
msgstr "OneDrive Dev (Nur für Tests)"
msgid "WebDAV (Beta)"
msgstr ""
#, fuzzy
msgid "WebDAV"
msgstr "Nexcloud WebDAV URL"
#, javascript-format
msgid "Unknown log level: %s"
@@ -1120,7 +1129,8 @@ msgstr "Hell"
msgid "Dark"
msgstr "Dunkel"
msgid "Show uncompleted todos on top of the lists"
#, fuzzy
msgid "Show uncompleted to-dos on top of the lists"
msgstr "Zeige unvollständige To-Dos oben in der Liste"
msgid "Save geo-location with notes"

View File

@@ -553,6 +553,10 @@ msgstr ""
msgid "Evernote Export Files"
msgstr ""
#, javascript-format
msgid "Hide %s"
msgstr ""
msgid "Quit"
msgstr ""
@@ -571,6 +575,12 @@ msgstr ""
msgid "Search in all the notes"
msgstr ""
msgid "View"
msgstr ""
msgid "Toggle editor layout"
msgstr ""
msgid "Tools"
msgstr ""
@@ -612,9 +622,6 @@ msgstr ""
msgid "Cancel"
msgstr ""
msgid "Error"
msgstr ""
#, javascript-format
msgid ""
"Release notes:\n"
@@ -853,7 +860,7 @@ msgstr ""
msgid "File system"
msgstr ""
msgid "Nextcloud (Beta)"
msgid "Nextcloud"
msgstr ""
msgid "OneDrive"
@@ -862,7 +869,7 @@ msgstr ""
msgid "OneDrive Dev (For testing only)"
msgstr ""
msgid "WebDAV (Beta)"
msgid "WebDAV"
msgstr ""
#, javascript-format
@@ -991,7 +998,7 @@ msgstr ""
msgid "Dark"
msgstr ""
msgid "Show uncompleted todos on top of the lists"
msgid "Show uncompleted to-dos on top of the lists"
msgstr ""
msgid "Save geo-location with notes"

View File

@@ -609,6 +609,10 @@ msgstr "Importar notas de Evernote"
msgid "Evernote Export Files"
msgstr "Exportar archivos de Evernote"
#, javascript-format
msgid "Hide %s"
msgstr ""
msgid "Quit"
msgstr "Salir"
@@ -627,6 +631,12 @@ msgstr "Pegar"
msgid "Search in all the notes"
msgstr "Buscar en todas las notas"
msgid "View"
msgstr ""
msgid "Toggle editor layout"
msgstr ""
msgid "Tools"
msgstr "Herramientas"
@@ -670,9 +680,6 @@ msgstr "Ok"
msgid "Cancel"
msgstr "Cancelar"
msgid "Error"
msgstr ""
#, fuzzy, javascript-format
msgid ""
"Release notes:\n"
@@ -927,7 +934,7 @@ msgstr "Etiqueta desconocida: %s"
msgid "File system"
msgstr "Sistema de archivos"
msgid "Nextcloud (Beta)"
msgid "Nextcloud"
msgstr ""
msgid "OneDrive"
@@ -937,7 +944,7 @@ msgstr "OneDrive"
msgid "OneDrive Dev (For testing only)"
msgstr "OneDrive Dev(Solo para pruebas)"
msgid "WebDAV (Beta)"
msgid "WebDAV"
msgstr ""
#, javascript-format
@@ -1082,7 +1089,7 @@ msgid "Dark"
msgstr "Oscuro"
#, fuzzy
msgid "Show uncompleted todos on top of the lists"
msgid "Show uncompleted to-dos on top of the lists"
msgstr "Mostrar lista de tareas incompletas al inio de las listas"
msgid "Save geo-location with notes"

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Joplin-CLI 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: Lucas Vieites\n"
"Last-Translator: Fernando Martín <f@mrtn.es>\n"
"Language-Team: Spanish <lucas.vieites@gmail.com>\n"
"Language: es_ES\n"
"MIME-Version: 1.0\n"
@@ -112,7 +112,7 @@ msgid "The command \"%s\" is only available in GUI mode"
msgstr "El comando «%s» solamente está disponible en modo GUI"
msgid "Cannot change encrypted item"
msgstr ""
msgstr "No se puede cambiar el elemento cifrado"
#, javascript-format
msgid "Missing required argument: %s"
@@ -174,37 +174,40 @@ msgstr "Marca una tarea como hecha."
#, javascript-format
msgid "Note is not a to-do: \"%s\""
msgstr "Una nota no es una tarea: \"%s\""
msgstr "La nota no es una tarea: \"%s\""
msgid ""
"Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, "
"`status` and `target-status`."
msgstr ""
"Manejar la configuración E2EE. Comandos disponibles `enable`, `disable`, "
"`decrypt`, `status` y `target-status`."
msgid "Enter master password:"
msgstr ""
msgstr "Introduce la contraseña maestra:"
msgid "Operation cancelled"
msgstr ""
msgstr "Operación cancelada"
msgid ""
"Starting decryption... Please wait as it may take several minutes depending "
"on how much there is to decrypt."
msgstr ""
"Iniciando descifrado... Por favor espere, puede tardar varios minutos "
"dependiendo de cuanto haya que descifrar."
msgid "Completed decryption."
msgstr ""
msgstr "Descifrado completado."
#, fuzzy
msgid "Enabled"
msgstr "Deshabilitado"
msgstr "Habilitado"
msgid "Disabled"
msgstr "Deshabilitado"
#, javascript-format
msgid "Encryption is: %s"
msgstr ""
msgstr "El cifrado es: %s"
msgid "Edit note."
msgstr "Editar una nota."
@@ -227,10 +230,10 @@ msgstr "Iniciando a editar una nota. Cierra el editor para regresar al prompt."
#, javascript-format
msgid "Error opening note in editor: %s"
msgstr ""
msgstr "Error abriendo la nota en el editor: %s"
msgid "Note has been saved."
msgstr "La nota a sido guardada."
msgstr "La nota ha sido guardada."
msgid "Exits the application."
msgstr "Sale de la aplicación."
@@ -434,7 +437,7 @@ msgid "%d notes match this pattern. Delete them?"
msgstr "%d notas coinciden con el patron. Eliminarlas?"
msgid "Delete note?"
msgstr "Eliminar nota?"
msgstr "¿Eliminar nota?"
msgid "Searches for the given <pattern> in all the notes."
msgstr "Buscar el patron <pattern> en todas las notas."
@@ -468,7 +471,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 ""
msgstr "No autentificado con %s. Por favor provea las credenciales."
msgid "Synchronisation is already in progress."
msgstr "Sincronzación en progreso."
@@ -605,6 +608,10 @@ 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 "
"proporcionado la contraseña, los elementos están siendo descifrados en "
"segundo plano y estarán disponibles en breve."
msgid "File"
msgstr "Archivo"
@@ -624,6 +631,10 @@ msgstr "Importar notas de Evernote"
msgid "Evernote Export Files"
msgstr "Archivos exportados de Evernote"
#, javascript-format
msgid "Hide %s"
msgstr ""
msgid "Quit"
msgstr "Salir"
@@ -642,6 +653,12 @@ msgstr "Pegar"
msgid "Search in all the notes"
msgstr "Buscar en todas las notas"
msgid "View"
msgstr ""
msgid "Toggle editor layout"
msgstr ""
msgid "Tools"
msgstr "Herramientas"
@@ -649,11 +666,10 @@ msgid "Synchronisation status"
msgstr "Estado de la sincronización"
msgid "Encryption options"
msgstr ""
msgstr "Opciones de cifrado"
#, fuzzy
msgid "General Options"
msgstr "Opciones"
msgstr "Opciones generales"
msgid "Help"
msgstr "Ayuda"
@@ -662,7 +678,7 @@ msgid "Website and documentation"
msgstr "Sitio web y documentación"
msgid "Check for updates..."
msgstr ""
msgstr "Comprobar actualizaciones..."
msgid "About Joplin"
msgstr "Acerca de Joplin"
@@ -671,12 +687,12 @@ msgstr "Acerca de Joplin"
msgid "%s %s (%s, %s)"
msgstr "%s %s (%s, %s)"
#, fuzzy, javascript-format
#, javascript-format
msgid "Open %s"
msgstr "En %s: %s"
msgstr "Abrir %s"
msgid "Exit"
msgstr ""
msgstr "Salir"
msgid "OK"
msgstr "OK"
@@ -684,32 +700,30 @@ msgstr "OK"
msgid "Cancel"
msgstr "Cancelar"
msgid "Error"
msgstr ""
#, fuzzy, javascript-format
#, javascript-format
msgid ""
"Release notes:\n"
"\n"
"%s"
msgstr "¿Desea eliminar notas?"
msgstr ""
"Notas de la versión:\n"
"\n"
"%s"
msgid "An update is available, do you want to download it now?"
msgstr ""
msgstr "Hay disponible una actualización. ¿Quiere descargarla ahora?"
msgid "Yes"
msgstr ""
msgstr ""
#, fuzzy
msgid "No"
msgstr "N"
msgstr "No"
msgid "Current version is up-to-date."
msgstr ""
msgstr "La versión actual está actualizada."
#, fuzzy
msgid "Check synchronisation configuration"
msgstr "Cancelar sincronización"
msgstr "Comprobar sincronización"
#, javascript-format
msgid "Notes and settings are stored in: %s"
@@ -723,6 +737,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?"
msgid ""
"Enabling encryption means *all* your notes and attachments are going to be "
@@ -730,18 +746,22 @@ 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, "
"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."
msgid "Disable encryption"
msgstr ""
msgstr "Deshabilitar cifrado"
msgid "Enable encryption"
msgstr ""
msgstr "Habilitar cifrado"
msgid "Master Keys"
msgstr ""
msgstr "Clave maestra"
msgid "Active"
msgstr ""
msgstr "Activo"
msgid "ID"
msgstr "ID"
@@ -756,31 +776,38 @@ msgid "Updated"
msgstr "Actualizado"
msgid "Password"
msgstr ""
msgstr "Contraseña"
msgid "Password OK"
msgstr ""
msgstr "Contraseña OK"
msgid ""
"Note: Only one master key is going to be used for encryption (the one marked "
"as \"active\"). Any of the keys might be used for decryption, depending on "
"how the notes or notebooks were originally encrypted."
msgstr ""
"Nota: Solo una clave maestra va a ser utilizar para el cifrado (la marcada "
"como \"activa\"). Cualquiera de las claves puede ser utilizada para "
"descifrar, dependiendo de como fueron cifradas originalmente las notas o las "
"libretas."
msgid "Missing Master Keys"
msgstr ""
msgstr "No se encuentra la clave maestra"
msgid ""
"The master keys with these IDs are used to encrypt some of your items, "
"however the application does not currently have access to them. It is likely "
"they will eventually be downloaded via synchronisation."
msgstr ""
"La clave maestra con estos ID son utilizadas para descifrar algunos de tus "
"elementos, pero la apliación no tiene acceso a ellas. Serán descargadas a "
"través de la sincronización."
msgid "Status"
msgstr "Estado"
msgid "Encryption is:"
msgstr ""
msgstr "El cifrado está:"
msgid "Back"
msgstr "Atrás"
@@ -820,12 +847,11 @@ msgstr "No se han podido sincronizar algunos de los elementos."
msgid "View them now"
msgstr "Verlos ahora"
#, fuzzy
msgid "Some items cannot be decrypted."
msgstr "No se han podido sincronizar algunos de los elementos."
msgstr "No se han podido descifrar algunos elementos."
msgid "Set the password"
msgstr ""
msgstr "Establecer la contraseña"
msgid "Add or remove tags"
msgstr "Añadir o borrar etiquetas"
@@ -847,11 +873,10 @@ msgid ""
msgstr "No hay ninguna libreta. Cree una pulsando en «Libreta nueva»."
msgid "Open..."
msgstr ""
msgstr "Abrir..."
#, fuzzy
msgid "Save as..."
msgstr "Guardar cambios"
msgstr "Guardar como..."
#, javascript-format
msgid "Unsupported link or message: %s"
@@ -864,19 +889,17 @@ msgid "Tags"
msgstr "Etiquetas"
msgid "Set alarm"
msgstr "Fijar alarma"
msgstr "Establecer alarma"
#, fuzzy
msgid "to-do"
msgstr "Lista de tareas nueva"
msgstr "lista de tareas"
#, fuzzy
msgid "note"
msgstr "Nota nueva"
msgstr "nota"
#, fuzzy, javascript-format
#, javascript-format
msgid "Creating new %s..."
msgstr "Importando notas..."
msgstr "Creando nuevo %s..."
msgid "Refresh"
msgstr "Refrescar"
@@ -897,7 +920,7 @@ msgid "Synchronisation Status"
msgstr "Estado de la sincronización"
msgid "Encryption Options"
msgstr ""
msgstr "Opciones de cifrado"
msgid "Remove this tag from all the notes?"
msgstr "¿Desea eliminar esta etiqueta de todas las notas?"
@@ -931,8 +954,9 @@ msgstr "Etiqueta desconocida: %s"
msgid "File system"
msgstr "Sistema de archivos"
msgid "Nextcloud (Beta)"
msgstr ""
#, fuzzy
msgid "Nextcloud"
msgstr "Nextcloud (Beta)"
msgid "OneDrive"
msgstr "OneDrive"
@@ -940,8 +964,9 @@ msgstr "OneDrive"
msgid "OneDrive Dev (For testing only)"
msgstr "OneDrive Dev (Solo para pruebas)"
msgid "WebDAV (Beta)"
msgstr ""
#, fuzzy
msgid "WebDAV"
msgstr "Servidor WebDAV"
#, javascript-format
msgid "Unknown log level: %s"
@@ -1001,9 +1026,9 @@ msgstr "Elementos locales borrados: %d."
msgid "Deleted remote items: %d."
msgstr "Elementos remotos borrados: %d."
#, fuzzy, javascript-format
#, javascript-format
msgid "Fetched items: %d/%d."
msgstr "Elementos locales creados: %d."
msgstr "Elementos obtenidos: %d/%d."
#, javascript-format
msgid "State: \"%s\"."
@@ -1021,11 +1046,10 @@ msgid "Synchronisation is already in progress. State: %s"
msgstr "La sincronización ya está en progreso. Estado: %s"
msgid "Encrypted"
msgstr ""
msgstr "Cifrado"
#, fuzzy
msgid "Encrypted items cannot be modified"
msgstr "No se han podido sincronizar algunos de los elementos."
msgstr "Los elementos cifrados no puedes ser modificados"
msgid "Conflicts"
msgstr "Conflictos"
@@ -1081,32 +1105,30 @@ msgstr "Claro"
msgid "Dark"
msgstr "Oscuro"
msgid "Show uncompleted todos on top of the lists"
#, fuzzy
msgid "Show uncompleted to-dos on top of the lists"
msgstr "Mostrar tareas incompletas al inicio de las listas"
msgid "Save geo-location with notes"
msgstr "Guardar geolocalización en las notas"
#, fuzzy
msgid "When creating a new to-do:"
msgstr "Crea una nueva lista de tareas."
msgstr "Al crear una nueva lista de tareas:"
#, fuzzy
msgid "Focus title"
msgstr "Título de la nota:"
msgstr "Foco en el título:"
msgid "Focus body"
msgstr ""
msgstr "Foco en el cuerpo"
#, fuzzy
msgid "When creating a new note:"
msgstr "Crea una nueva nota."
msgstr "Cuando se crear una nota nueva:"
msgid "Show tray icon"
msgstr ""
msgstr "Mostrar icono en la bandeja"
msgid "Set application zoom percentage"
msgstr ""
msgstr "Establecer el porcentaje de aumento de la aplicación"
msgid "Automatically update the application"
msgstr "Actualizar la aplicación automáticamente"
@@ -1136,6 +1158,9 @@ msgid ""
"The target to synchonise to. Each sync target may have additional parameters "
"which are named as `sync.NUM.NAME` (all documented below)."
msgstr ""
"El destino de la sincronización. Cada destino de la sincronización puede "
"tener parámetros adicionales los cuales son llamados como `sync.NUM.NAME` "
"(todos abajo documentados)."
msgid "Directory to synchronise with (absolute path)"
msgstr "Directorio con el que sincronizarse (ruta completa)"
@@ -1148,22 +1173,22 @@ msgstr ""
"de archivos. Vea «sync.target»."
msgid "Nexcloud WebDAV URL"
msgstr ""
msgstr "Servidor Nexcloud WebDAV"
msgid "Nexcloud username"
msgstr ""
msgstr "Usuario de Nexcloud"
msgid "Nexcloud password"
msgstr ""
msgstr "Contraseña de Nexcloud"
msgid "WebDAV URL"
msgstr ""
msgstr "Servidor WebDAV"
msgid "WebDAV username"
msgstr ""
msgstr "Usuario de WebDAV"
msgid "WebDAV password"
msgstr ""
msgstr "Contraseña de WebDAV"
#, javascript-format
msgid "Invalid option value: \"%s\". Possible values are: %s."
@@ -1172,15 +1197,18 @@ msgstr "Opción inválida: «%s». Los valores posibles son: %s."
msgid "Items that cannot be synchronised"
msgstr "Elementos que no se pueden sincronizar"
#, fuzzy, javascript-format
#, javascript-format
msgid "%s (%s): %s"
msgstr "%s %s (%s)"
msgstr "%s (%s): %s"
msgid ""
"These items will remain on the device but will not be uploaded to the sync "
"target. In order to find these items, either search for the title or the ID "
"(which is displayed in brackets above)."
msgstr ""
"Estos elementos se mantendrán en el dispositivo pero no serán enviados al "
"destino de sincronización. Para encontrar dichos elementos busca en el "
"título o en el ID (el cual se muestra arriba entre corchetes)."
msgid "Sync status (synced items / total items)"
msgstr "Estado de sincronización (elementos sincronizados/elementos totales)"
@@ -1228,7 +1256,7 @@ msgid "Export Debug Report"
msgstr "Exportar informe de depuración"
msgid "Encryption Config"
msgstr ""
msgstr "Configuración de cifrado"
msgid "Configuration"
msgstr "Configuración"
@@ -1241,7 +1269,7 @@ msgid "Move %d notes to notebook \"%s\"?"
msgstr "¿Desea mover %d notas a libreta «%s»?"
msgid "Press to set the decryption password."
msgstr ""
msgstr "Presiona para establecer la contraseña de descifrado."
msgid "Select date"
msgstr "Seleccione fecha"
@@ -1254,21 +1282,20 @@ msgstr "Cancelar sincronización"
#, javascript-format
msgid "Master Key %s"
msgstr ""
msgstr "Clave maestra %s"
#, fuzzy, javascript-format
#, javascript-format
msgid "Created: %s"
msgstr "Creado: %d."
msgstr "Creado: %s"
msgid "Password:"
msgstr ""
msgstr "Contraseña:"
msgid "Password cannot be empty"
msgstr ""
msgstr "La contraseña no puede estar vacía"
#, fuzzy
msgid "Enable"
msgstr "Deshabilitado"
msgstr "Habilitado"
#, javascript-format
msgid "The notebook could not be saved: %s"
@@ -1278,10 +1305,10 @@ msgid "Edit notebook"
msgstr "Editar libreta"
msgid "Show all"
msgstr ""
msgstr "Mostrar todo"
msgid "Errors only"
msgstr ""
msgstr "Solo errores"
msgid "This note has been modified:"
msgstr "Esta nota ha sido modificada:"
@@ -1337,6 +1364,12 @@ msgstr ""
msgid "Welcome"
msgstr "Bienvenido"
#~ msgid "Error"
#~ msgstr "Error"
#~ msgid "WebDAV (Beta)"
#~ msgstr "WebDAV (Beta)"
#~ msgid ""
#~ "The target to synchonise to. If synchronising with the file system, set "
#~ "`sync.2.path` to specify the target directory."

View File

@@ -626,6 +626,10 @@ msgstr "Inportatu Evernoteko oharrak"
msgid "Evernote Export Files"
msgstr "Evernotetik esportatutako fitxategiak"
#, javascript-format
msgid "Hide %s"
msgstr ""
msgid "Quit"
msgstr "Irten"
@@ -644,6 +648,12 @@ msgstr "Itsatsi"
msgid "Search in all the notes"
msgstr "Bilatu ohar guztietan"
msgid "View"
msgstr ""
msgid "Toggle editor layout"
msgstr ""
msgid "Tools"
msgstr "Tresnak"
@@ -685,9 +695,6 @@ msgstr "OK"
msgid "Cancel"
msgstr "Utzi"
msgid "Error"
msgstr ""
#, fuzzy, javascript-format
msgid ""
"Release notes:\n"
@@ -947,7 +954,8 @@ msgstr "Marka ezezaguna: %s"
msgid "File system"
msgstr "Fitxategi sistema"
msgid "Nextcloud (Beta)"
#, fuzzy
msgid "Nextcloud"
msgstr "NextCloud (Beta)"
msgid "OneDrive"
@@ -956,8 +964,9 @@ msgstr "OneDrive"
msgid "OneDrive Dev (For testing only)"
msgstr "OneDrive Dev (aprobetarako soilik)"
msgid "WebDAV (Beta)"
msgstr ""
#, fuzzy
msgid "WebDAV"
msgstr "Nexcloud WebDAV URL"
#, javascript-format
msgid "Unknown log level: %s"
@@ -1095,7 +1104,8 @@ msgstr "Argia"
msgid "Dark"
msgstr "Iluna"
msgid "Show uncompleted todos on top of the lists"
#, fuzzy
msgid "Show uncompleted to-dos on top of the lists"
msgstr "Bete gabeko zereginak erakutsi zerrendaren goiko partean"
msgid "Save geo-location with notes"

View File

@@ -628,6 +628,10 @@ msgstr "Importer notes d'Evernote"
msgid "Evernote Export Files"
msgstr "Fichiers d'export Evernote"
#, javascript-format
msgid "Hide %s"
msgstr "Cacher %s"
msgid "Quit"
msgstr "Quitter"
@@ -646,6 +650,12 @@ msgstr "Coller"
msgid "Search in all the notes"
msgstr "Chercher dans toutes les notes"
msgid "View"
msgstr "Affichage"
msgid "Toggle editor layout"
msgstr "Basculer l'agencement de l'éditeur"
msgid "Tools"
msgstr "Outils"
@@ -687,9 +697,6 @@ msgstr "OK"
msgid "Cancel"
msgstr "Annuler"
msgid "Error"
msgstr "Erreur"
#, javascript-format
msgid ""
"Release notes:\n"
@@ -953,8 +960,8 @@ msgstr "Paramètre inconnu : %s"
msgid "File system"
msgstr "Système de fichier"
msgid "Nextcloud (Beta)"
msgstr "Nextcloud (Beta)"
msgid "Nextcloud"
msgstr "Nextcloud"
msgid "OneDrive"
msgstr "OneDrive"
@@ -962,8 +969,8 @@ msgstr "OneDrive"
msgid "OneDrive Dev (For testing only)"
msgstr "OneDrive Dév (Pour tester uniquement)"
msgid "WebDAV (Beta)"
msgstr "WebDAV (Bêta)"
msgid "WebDAV"
msgstr "WebDAV"
#, javascript-format
msgid "Unknown log level: %s"
@@ -1101,7 +1108,7 @@ msgstr "Clair"
msgid "Dark"
msgstr "Sombre"
msgid "Show uncompleted todos on top of the lists"
msgid "Show uncompleted to-dos on top of the lists"
msgstr "Tâches non-terminées en haut des listes"
msgid "Save geo-location with notes"
@@ -1362,6 +1369,12 @@ msgstr ""
msgid "Welcome"
msgstr "Bienvenue"
#~ msgid "Error"
#~ msgstr "Erreur"
#~ msgid "WebDAV (Beta)"
#~ msgstr "WebDAV (Bêta)"
#~ msgid "Could not download the update: %s"
#~ msgstr "Impossible de télécharger la mise à jour : %s"

View File

@@ -632,6 +632,10 @@ msgstr "Uvezi Evernote bilješke"
msgid "Evernote Export Files"
msgstr "Evernote izvozne datoteke"
#, javascript-format
msgid "Hide %s"
msgstr ""
msgid "Quit"
msgstr "Izađi"
@@ -650,6 +654,12 @@ msgstr "Zalijepi"
msgid "Search in all the notes"
msgstr "Pretraži u svim bilješkama"
msgid "View"
msgstr ""
msgid "Toggle editor layout"
msgstr ""
msgid "Tools"
msgstr "Alati"
@@ -692,9 +702,6 @@ msgstr "U redu"
msgid "Cancel"
msgstr "Odustani"
msgid "Error"
msgstr ""
#, fuzzy, javascript-format
msgid ""
"Release notes:\n"
@@ -941,7 +948,7 @@ msgstr "Nepoznata zastavica: %s"
msgid "File system"
msgstr "Datotečni sustav"
msgid "Nextcloud (Beta)"
msgid "Nextcloud"
msgstr ""
msgid "OneDrive"
@@ -950,7 +957,7 @@ msgstr "OneDrive"
msgid "OneDrive Dev (For testing only)"
msgstr "OneDrive Dev (Samo za testiranje)"
msgid "WebDAV (Beta)"
msgid "WebDAV"
msgstr ""
#, javascript-format
@@ -1088,7 +1095,8 @@ msgstr "Svijetla"
msgid "Dark"
msgstr "Tamna"
msgid "Show uncompleted todos on top of the lists"
#, fuzzy
msgid "Show uncompleted to-dos on top of the lists"
msgstr "Prikaži nezavršene zadatke na vrhu liste"
msgid "Save geo-location with notes"

View File

@@ -610,6 +610,10 @@ msgstr "Importa le note da Evernote"
msgid "Evernote Export Files"
msgstr "Esposta i files di Evernote"
#, javascript-format
msgid "Hide %s"
msgstr ""
msgid "Quit"
msgstr "Esci"
@@ -628,6 +632,12 @@ msgstr "Incolla"
msgid "Search in all the notes"
msgstr "Cerca in tutte le note"
msgid "View"
msgstr ""
msgid "Toggle editor layout"
msgstr ""
msgid "Tools"
msgstr "Strumenti"
@@ -670,9 +680,6 @@ msgstr "OK"
msgid "Cancel"
msgstr "Cancella"
msgid "Error"
msgstr ""
#, fuzzy, javascript-format
msgid ""
"Release notes:\n"
@@ -921,7 +928,7 @@ msgstr "Etichetta sconosciuta: %s"
msgid "File system"
msgstr "File system"
msgid "Nextcloud (Beta)"
msgid "Nextcloud"
msgstr ""
msgid "OneDrive"
@@ -930,7 +937,7 @@ msgstr "OneDrive"
msgid "OneDrive Dev (For testing only)"
msgstr "OneDrive Dev (solo per test)"
msgid "WebDAV (Beta)"
msgid "WebDAV"
msgstr ""
#, javascript-format
@@ -1070,7 +1077,8 @@ msgstr "Chiaro"
msgid "Dark"
msgstr "Scuro"
msgid "Show uncompleted todos on top of the lists"
#, fuzzy
msgid "Show uncompleted to-dos on top of the lists"
msgstr "Mostra todo inclompleti in cima alla lista"
msgid "Save geo-location with notes"

View File

@@ -609,6 +609,10 @@ msgstr "Evernoteのインポート"
msgid "Evernote Export Files"
msgstr "Evernote Exportファイル"
#, javascript-format
msgid "Hide %s"
msgstr ""
msgid "Quit"
msgstr "終了"
@@ -627,6 +631,12 @@ msgstr "貼り付け"
msgid "Search in all the notes"
msgstr "すべてのノートを検索"
msgid "View"
msgstr ""
msgid "Toggle editor layout"
msgstr ""
msgid "Tools"
msgstr "ツール"
@@ -669,9 +679,6 @@ msgstr ""
msgid "Cancel"
msgstr "キャンセル"
msgid "Error"
msgstr ""
#, fuzzy, javascript-format
msgid ""
"Release notes:\n"
@@ -921,7 +928,7 @@ msgstr "不明なフラグ: %s"
msgid "File system"
msgstr "ファイルシステム"
msgid "Nextcloud (Beta)"
msgid "Nextcloud"
msgstr ""
msgid "OneDrive"
@@ -930,7 +937,7 @@ msgstr ""
msgid "OneDrive Dev (For testing only)"
msgstr ""
msgid "WebDAV (Beta)"
msgid "WebDAV"
msgstr ""
#, javascript-format
@@ -1072,7 +1079,8 @@ msgstr "明るい"
msgid "Dark"
msgstr "暗い"
msgid "Show uncompleted todos on top of the lists"
#, fuzzy
msgid "Show uncompleted to-dos on top of the lists"
msgstr "未完のToDoをリストの上部に表示"
msgid "Save geo-location with notes"

View File

@@ -553,6 +553,10 @@ msgstr ""
msgid "Evernote Export Files"
msgstr ""
#, javascript-format
msgid "Hide %s"
msgstr ""
msgid "Quit"
msgstr ""
@@ -571,6 +575,12 @@ msgstr ""
msgid "Search in all the notes"
msgstr ""
msgid "View"
msgstr ""
msgid "Toggle editor layout"
msgstr ""
msgid "Tools"
msgstr ""
@@ -612,9 +622,6 @@ msgstr ""
msgid "Cancel"
msgstr ""
msgid "Error"
msgstr ""
#, javascript-format
msgid ""
"Release notes:\n"
@@ -853,7 +860,7 @@ msgstr ""
msgid "File system"
msgstr ""
msgid "Nextcloud (Beta)"
msgid "Nextcloud"
msgstr ""
msgid "OneDrive"
@@ -862,7 +869,7 @@ msgstr ""
msgid "OneDrive Dev (For testing only)"
msgstr ""
msgid "WebDAV (Beta)"
msgid "WebDAV"
msgstr ""
#, javascript-format
@@ -991,7 +998,7 @@ msgstr ""
msgid "Dark"
msgstr ""
msgid "Show uncompleted todos on top of the lists"
msgid "Show uncompleted to-dos on top of the lists"
msgstr ""
msgid "Save geo-location with notes"

View File

@@ -628,6 +628,10 @@ msgstr "Importeer Evernote notities"
msgid "Evernote Export Files"
msgstr "Exporteer Evernote bestanden"
#, javascript-format
msgid "Hide %s"
msgstr ""
msgid "Quit"
msgstr "Stop"
@@ -646,6 +650,12 @@ msgstr "Plak"
msgid "Search in all the notes"
msgstr "Zoek in alle notities"
msgid "View"
msgstr ""
msgid "Toggle editor layout"
msgstr ""
msgid "Tools"
msgstr "Tools"
@@ -687,9 +697,6 @@ msgstr "OK"
msgid "Cancel"
msgstr "Annuleer"
msgid "Error"
msgstr ""
#, fuzzy, javascript-format
msgid ""
"Release notes:\n"
@@ -949,8 +956,9 @@ msgstr "Onbekende optie: %s"
msgid "File system"
msgstr "Bestandssysteem"
msgid "Nextcloud (Beta)"
msgstr ""
#, fuzzy
msgid "Nextcloud"
msgstr "Stel wachtwoord in"
msgid "OneDrive"
msgstr "OneDrive"
@@ -958,7 +966,7 @@ msgstr "OneDrive"
msgid "OneDrive Dev (For testing only)"
msgstr "OneDrive Dev (Alleen voor testen)"
msgid "WebDAV (Beta)"
msgid "WebDAV"
msgstr ""
#, javascript-format
@@ -1100,7 +1108,8 @@ msgstr "Licht"
msgid "Dark"
msgstr "Donker"
msgid "Show uncompleted todos on top of the lists"
#, fuzzy
msgid "Show uncompleted to-dos on top of the lists"
msgstr "Toon onvoltooide to-do's aan de top van de lijsten"
msgid "Save geo-location with notes"

View File

@@ -604,6 +604,10 @@ msgstr "Importar notas do Evernote"
msgid "Evernote Export Files"
msgstr "Arquivos de Exportação do Evernote"
#, javascript-format
msgid "Hide %s"
msgstr ""
msgid "Quit"
msgstr "Sair"
@@ -622,6 +626,12 @@ msgstr "Colar"
msgid "Search in all the notes"
msgstr "Pesquisar em todas as notas"
msgid "View"
msgstr ""
msgid "Toggle editor layout"
msgstr ""
msgid "Tools"
msgstr "Ferramentas"
@@ -665,9 +675,6 @@ msgstr "OK"
msgid "Cancel"
msgstr "Cancelar"
msgid "Error"
msgstr ""
#, fuzzy, javascript-format
msgid ""
"Release notes:\n"
@@ -919,7 +926,7 @@ msgstr "Flag desconhecido: %s"
msgid "File system"
msgstr "Sistema de arquivos"
msgid "Nextcloud (Beta)"
msgid "Nextcloud"
msgstr ""
msgid "OneDrive"
@@ -928,7 +935,7 @@ msgstr "OneDrive"
msgid "OneDrive Dev (For testing only)"
msgstr "OneDrive Dev (apenas para testes)"
msgid "WebDAV (Beta)"
msgid "WebDAV"
msgstr ""
#, javascript-format
@@ -1069,7 +1076,8 @@ msgstr "Light"
msgid "Dark"
msgstr "Dark"
msgid "Show uncompleted todos on top of the lists"
#, fuzzy
msgid "Show uncompleted to-dos on top of the lists"
msgstr "Mostrar tarefas incompletas no topo das listas"
msgid "Save geo-location with notes"

View File

@@ -627,6 +627,10 @@ msgstr "Импортировать заметки из Evernote"
msgid "Evernote Export Files"
msgstr "Файлы экспорта Evernote"
#, javascript-format
msgid "Hide %s"
msgstr ""
msgid "Quit"
msgstr "Выход"
@@ -645,6 +649,12 @@ msgstr "Вставить"
msgid "Search in all the notes"
msgstr "Поиск во всех заметках"
msgid "View"
msgstr ""
msgid "Toggle editor layout"
msgstr ""
msgid "Tools"
msgstr "Инструменты"
@@ -686,9 +696,6 @@ msgstr "OK"
msgid "Cancel"
msgstr "Отмена"
msgid "Error"
msgstr "Ошибка"
#, fuzzy, javascript-format
msgid ""
"Release notes:\n"
@@ -946,7 +953,8 @@ msgstr "Неизвестный флаг: %s"
msgid "File system"
msgstr "Файловая система"
msgid "Nextcloud (Beta)"
#, fuzzy
msgid "Nextcloud"
msgstr "Nextcloud (Beta)"
msgid "OneDrive"
@@ -955,8 +963,9 @@ msgstr "OneDrive"
msgid "OneDrive Dev (For testing only)"
msgstr "OneDrive Dev (только для тестирования)"
msgid "WebDAV (Beta)"
msgstr ""
#, fuzzy
msgid "WebDAV"
msgstr "Nexcloud WebDAV URL"
#, javascript-format
msgid "Unknown log level: %s"
@@ -1094,7 +1103,8 @@ msgstr "Светлая"
msgid "Dark"
msgstr "Тёмная"
msgid "Show uncompleted todos on top of the lists"
#, fuzzy
msgid "Show uncompleted to-dos on top of the lists"
msgstr "Показывать незавершённые задачи вверху списков"
msgid "Save geo-location with notes"
@@ -1354,6 +1364,9 @@ msgstr "У вас сейчас нет блокнота. Создайте его
msgid "Welcome"
msgstr "Добро пожаловать"
#~ msgid "Error"
#~ msgstr "Ошибка"
#~ msgid "Could not download the update: %s"
#~ msgstr "Не удалось загрузить обновление: %s"

View File

@@ -576,6 +576,10 @@ msgstr "导入Evernote笔记"
msgid "Evernote Export Files"
msgstr "Evernote导出文件"
#, javascript-format
msgid "Hide %s"
msgstr ""
msgid "Quit"
msgstr "退出"
@@ -594,6 +598,12 @@ msgstr "粘贴"
msgid "Search in all the notes"
msgstr "在所有笔记内搜索"
msgid "View"
msgstr ""
msgid "Toggle editor layout"
msgstr ""
msgid "Tools"
msgstr "工具"
@@ -636,9 +646,6 @@ msgstr "确认"
msgid "Cancel"
msgstr "取消"
msgid "Error"
msgstr ""
#, fuzzy, javascript-format
msgid ""
"Release notes:\n"
@@ -887,7 +894,7 @@ msgstr "未知标记:%s"
msgid "File system"
msgstr "文件系统"
msgid "Nextcloud (Beta)"
msgid "Nextcloud"
msgstr ""
msgid "OneDrive"
@@ -896,7 +903,7 @@ msgstr "OneDrive"
msgid "OneDrive Dev (For testing only)"
msgstr "OneDrive开发员(仅测试用)"
msgid "WebDAV (Beta)"
msgid "WebDAV"
msgstr ""
#, javascript-format
@@ -1031,7 +1038,8 @@ msgstr "浅色"
msgid "Dark"
msgstr "深色"
msgid "Show uncompleted todos on top of the lists"
#, fuzzy
msgid "Show uncompleted to-dos on top of the lists"
msgstr "在列表上方显示未完成的待办事项"
msgid "Save geo-location with notes"

View File

@@ -1,6 +1,6 @@
{
"name": "joplin",
"version": "0.10.93",
"version": "1.0.97",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -2057,9 +2057,9 @@
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
},
"tkwidgets": {
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/tkwidgets/-/tkwidgets-0.5.21.tgz",
"integrity": "sha512-gJfpYq3UM6AZ23ZM+D9BZ1PhsJLLHgjCOf487/lS9pO0uDdnkMcVXkkKEfRl00EyjPnGc88QZhEkVOvrtKsuPA==",
"version": "0.5.25",
"resolved": "https://registry.npmjs.org/tkwidgets/-/tkwidgets-0.5.25.tgz",
"integrity": "sha512-f+12QbxNCLg9Jou5JoPJxATGLmzpDAQeM7QRTXvuqdEB/QvPD9+UlPUL7eYJP1QJv2zzT6EIWWbdpDkXPEtzCQ==",
"requires": {
"chalk": "2.3.0",
"emphasize": "1.5.0",

View File

@@ -19,7 +19,7 @@
],
"owner": "Laurent Cozic"
},
"version": "0.10.93",
"version": "1.0.97",
"bin": {
"joplin": "./main.js"
},
@@ -58,7 +58,7 @@
"string-to-stream": "^1.1.0",
"strip-ansi": "^4.0.0",
"tcp-port-used": "^0.1.2",
"tkwidgets": "^0.5.21",
"tkwidgets": "^0.5.25",
"url-parse": "^1.2.0",
"uuid": "^3.0.1",
"word-wrap": "^1.2.3",

View File

@@ -19,7 +19,7 @@ process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; // The first test is slow because the database needs to be built
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000 + 30000; // The first test is slow because the database needs to be built
async function allItems() {
let folders = await Folder.all();
@@ -60,6 +60,7 @@ async function allSyncTargetItemsEncrypted() {
}
async function localItemsSameAsRemote(locals, expect) {
let error = null;
try {
let files = await fileApi().list();
files = files.items;
@@ -81,12 +82,15 @@ async function localItemsSameAsRemote(locals, expect) {
// }
let remoteContent = await fileApi().get(path);
remoteContent = dbItem.type_ == BaseModel.TYPE_NOTE ? await Note.unserialize(remoteContent) : await Folder.unserialize(remoteContent);
expect(remoteContent.title).toBe(dbItem.title);
}
} catch (error) {
console.error(error);
} catch (e) {
error = e;
}
expect(error).toBe(null);
}
let insideBeforeEach = false;
@@ -985,4 +989,14 @@ describe('Synchronizer', function() {
expect(resource1.encryption_blob_encrypted).toBe(0);
}));
it('should create remote items with UTF-8 content', asyncTest(async () => {
let folder = await Folder.save({ title: "Fahrräder" });
await Note.save({ title: "Fahrräder", body: "Fahrräder", parent_id: folder.id });
let all = await allItems();
await synchronizer().start();
await localItemsSameAsRemote(all, expect);
}));
});

View File

@@ -56,7 +56,7 @@ const syncTargetId_ = SyncTargetRegistry.nameToId('nextcloud');
//const syncTargetId_ = SyncTargetRegistry.nameToId('filesystem');
const syncDir = __dirname + '/../tests/sync';
const sleepTime = syncTargetId_ == SyncTargetRegistry.nameToId('filesystem') ? 1001 : 10;//400;
const sleepTime = syncTargetId_ == SyncTargetRegistry.nameToId('filesystem') ? 1001 : 100;//400;
console.info('Testing with sync target: ' + SyncTargetRegistry.idToName(syncTargetId_));
@@ -75,6 +75,8 @@ BaseItem.loadClass('MasterKey', MasterKey);
Setting.setConstant('appId', 'net.cozic.joplin-cli');
Setting.setConstant('appType', 'cli');
Setting.autoSaveEnabled = false;
function syncTargetId() {
return syncTargetId_;
}
@@ -262,6 +264,7 @@ function fileApi() {
fileApi_.setLogger(logger);
fileApi_.setSyncTargetId(syncTargetId_);
fileApi_.requestRepeatCount_ = 0;
return fileApi_;
}

View File

@@ -63,7 +63,7 @@ class ElectronAppWrapper {
}))
// Uncomment this to view errors if the application does not start
if (this.env_ === 'dev') this.win_.webContents.openDevTools();
// if (this.env_ === 'dev') this.win_.webContents.openDevTools();
this.win_.on('close', (event) => {
// If it's on macOS, the app is completely closed only if the user chooses to close the app (willQuitApp_ will be true)

View File

@@ -200,6 +200,7 @@ class Application extends BaseApplication {
}
}, {
label: _('New notebook'),
accelerator: 'CommandOrControl+B',
screens: ['Main'],
click: () => {
this.dispatch({
@@ -228,6 +229,14 @@ class Application extends BaseApplication {
},
});
}
}, {
type: 'separator',
platforms: ['darwin'],
}, {
label: _('Hide %s', 'Joplin'),
platforms: ['darwin'],
accelerator: 'CommandOrControl+H',
click: () => { bridge().window().hide() }
}, {
type: 'separator',
}, {
@@ -239,17 +248,17 @@ class Application extends BaseApplication {
label: _('Edit'),
submenu: [{
label: _('Copy'),
screens: ['Main', 'OneDriveLogin'],
screens: ['Main', 'OneDriveLogin', 'Config', 'EncryptionConfig'],
role: 'copy',
accelerator: 'CommandOrControl+C',
}, {
label: _('Cut'),
screens: ['Main', 'OneDriveLogin'],
screens: ['Main', 'OneDriveLogin', 'Config', 'EncryptionConfig'],
role: 'cut',
accelerator: 'CommandOrControl+X',
}, {
label: _('Paste'),
screens: ['Main', 'OneDriveLogin'],
screens: ['Main', 'OneDriveLogin', 'Config', 'EncryptionConfig'],
role: 'paste',
accelerator: 'CommandOrControl+V',
}, {
@@ -266,6 +275,19 @@ class Application extends BaseApplication {
});
},
}],
}, {
label: _('View'),
submenu: [{
label: _('Toggle editor layout'),
screens: ['Main'],
accelerator: 'CommandOrControl+L',
click: () => {
this.dispatch({
type: 'WINDOW_COMMAND',
name: 'toggleVisiblePanes',
});
}
}],
}, {
label: _('Tools'),
submenu: [{
@@ -289,6 +311,7 @@ class Application extends BaseApplication {
}
},{
label: _('General Options'),
accelerator: 'CommandOrControl+,',
click: () => {
this.dispatch({
type: 'NAV_GO',
@@ -305,7 +328,7 @@ class Application extends BaseApplication {
}, {
label: _('Check for updates...'),
click: () => {
bridge().checkForUpdates(false, this.checkForUpdateLoggerPath());
bridge().checkForUpdates(false, bridge().window(), this.checkForUpdateLoggerPath());
}
}, {
label: _('About Joplin'),
@@ -334,10 +357,13 @@ class Application extends BaseApplication {
}
function removeUnwantedItems(template, screen) {
const platform = shim.platformName();
let output = [];
for (let i = 0; i < template.length; i++) {
const t = Object.assign({}, template[i]);
if (t.screens && t.screens.indexOf(screen) < 0) continue;
if (t.platforms && t.platforms.indexOf(platform) < 0) continue;
if (t.submenu) t.submenu = removeUnwantedItems(t.submenu, screen);
if (('submenu' in t) && isEmptyMenu(t.submenu)) continue;
output.push(t);
@@ -422,13 +448,14 @@ class Application extends BaseApplication {
if (shim.isWindows() || shim.isMac()) {
const runAutoUpdateCheck = () => {
if (Setting.value('autoUpdateEnabled')) {
bridge().checkForUpdates(true, this.checkForUpdateLoggerPath());
bridge().checkForUpdates(true, bridge().window(), this.checkForUpdateLoggerPath());
}
}
// Initial check on startup
setTimeout(() => { runAutoUpdateCheck() }, 5000);
// For those who leave the app always open
setInterval(() => { runAutoUpdateCheck() }, 2 * 60 * 60 * 1000);
// Then every x hours
setInterval(() => { runAutoUpdateCheck() }, 12 * 60 * 60 * 1000);
}
this.updateTray();

View File

@@ -43,7 +43,7 @@ class Bridge {
const {dialog} = require('electron');
if (!options) options = {};
if (!('defaultPath' in options) && this.lastSelectedPath_) options.defaultPath = this.lastSelectedPath_;
const filePath = dialog.showSaveDialog(options);
const filePath = dialog.showSaveDialog(this.window(), options);
if (filePath) {
this.lastSelectedPath_ = filePath;
}
@@ -55,27 +55,27 @@ class Bridge {
if (!options) options = {};
if (!('defaultPath' in options) && this.lastSelectedPath_) options.defaultPath = this.lastSelectedPath_;
if (!('createDirectory' in options)) options.createDirectory = true;
const filePaths = dialog.showOpenDialog(options);
const filePaths = dialog.showOpenDialog(this.window(), options);
if (filePaths && filePaths.length) {
this.lastSelectedPath_ = dirname(filePaths[0]);
}
return filePaths;
}
showMessageBox(options) {
showMessageBox(window, options) {
const {dialog} = require('electron');
return dialog.showMessageBox(options);
return dialog.showMessageBox(window, options);
}
showErrorMessageBox(message) {
return this.showMessageBox({
return this.showMessageBox(this.window(), {
type: 'error',
message: message,
});
}
showConfirmMessageBox(message) {
const result = this.showMessageBox({
const result = this.showMessageBox(this.window(), {
type: 'question',
message: message,
buttons: [_('OK'), _('Cancel')],
@@ -84,7 +84,7 @@ class Bridge {
}
showInfoMessageBox(message) {
const result = this.showMessageBox({
const result = this.showMessageBox(this.window(), {
type: 'info',
message: message,
buttons: [_('OK')],
@@ -108,26 +108,26 @@ 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_;
}
// 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);
}
}
// try {
// await this.autoUpdater_.checkForUpdatesAndNotify();
// } catch (error) {
// this.autoUpdateLogger_.error(error);
// }
// }
checkForUpdates(inBackground, logFilePath) {
checkForUpdates(inBackground, window, logFilePath) {
const { checkForUpdates } = require('./checkForUpdates.js');
checkForUpdates(inBackground, logFilePath);
checkForUpdates(inBackground, window, logFilePath);
}
}

View File

@@ -5,21 +5,19 @@ const { _ } = require('lib/locale.js');
let autoUpdateLogger_ = new Logger();
let checkInBackground_ = false;
let isCheckingForUpdate_ = false;
let parentWindow_ = null;
// Note: Electron Builder's autoUpdater is incredibly buggy so currently it's only used
// to detect if a new version is present. If it is, the download link is simply opened
// in a new browser window.
autoUpdater.autoDownload = false;
autoUpdater.on('error', (error) => {
autoUpdateLogger_.error(error);
if (checkInBackground_) return;
dialog.showErrorBox(_('Error'), error == null ? "unknown" : (error.stack || error).toString())
})
function htmlToText_(html) {
let output = html.replace(/\n/g, '');
output = output.replace(/<li>/g, '- ');
output = output.replace(/<p>/g, '');
output = output.replace(/<\/p>/g, '\n');
output = output.replace(/<\/li>/g, '\n');
output = output.replace(/<ul>/g, '');
output = output.replace(/<\/ul>/g, '');
@@ -28,49 +26,102 @@ function htmlToText_(html) {
return output;
}
autoUpdater.on('update-available', (info) => {
if (!info.version || !info.path) {
if (checkInBackground_) return;
dialog.showErrorBox(_('Error'), ('Could not get version info: ' + JSON.stringify(info)));
return;
function showErrorMessageBox(message) {
return dialog.showMessageBox(parentWindow_, {
type: 'error',
message: message,
});
}
function onCheckStarted() {
autoUpdateLogger_.info('checkForUpdates: Starting...');
isCheckingForUpdate_ = true;
}
function onCheckEnded() {
autoUpdateLogger_.info('checkForUpdates: Done.');
isCheckingForUpdate_ = false;
}
autoUpdater.on('error', (error) => {
autoUpdateLogger_.error(error);
if (checkInBackground_) return onCheckEnded();
showErrorMessageBox(error == null ? "unknown" : (error.stack || error).toString())
onCheckEnded();
})
function findDownloadFilename_(info) {
// { version: '1.0.64',
// files:
// [ { url: 'Joplin-1.0.64-mac.zip',
// sha512: 'OlemXqhq/fSifx7EutvMzfoCI/1kGNl10i8nkvACEDfVXwP8hankDBXEC0+GxSArsZuxOh3U1+C+4j72SfIUew==' },
// { url: 'Joplin-1.0.64.dmg',
// sha512: 'jAewQQoJ3nCaOj8hWDgf0sc3LBbAWQtiKqfTflK8Hc3Dh7fAy9jRHfFAZKFUZ9ll95Bun0DVsLq8wLSUrdsMXw==',
// size: 77104485 } ],
// path: 'Joplin-1.0.64-mac.zip',
// sha512: 'OlemXqhq/fSifx7EutvMzfoCI/1kGNl10i8nkvACEDfVXwP8hankDBXEC0+GxSArsZuxOh3U1+C+4j72SfIUew==',
// releaseDate: '2018-02-16T00:13:01.634Z',
// releaseName: 'v1.0.64',
// releaseNotes: '<p>Still more fixes and im...' }
if (!info) return null;
if (!info.files) {
// info.path seems to contain a default, though not a good one,
// so the loop below if preferable to find the right file.
return info.path;
}
const downloadUrl = 'https://github.com/laurent22/joplin/releases/download/v' + info.version + '/' + info.path;
for (let i = 0; i < info.files.length; i++) {
const f = info.files[i].url; // Annoyingly this is called "url" but it's obviously not a url, so hopefully it won't change later on and become one.
if (f.indexOf('.exe') >= 0) return f;
if (f.indexOf('.dmg') >= 0) return f;
}
return info.path;
}
autoUpdater.on('update-available', (info) => {
const filename = findDownloadFilename_(info);
if (!info.version || !filename) {
if (checkInBackground_) return onCheckEnded();
showErrorMessageBox(('Could not get version info: ' + JSON.stringify(info)));
return onCheckEnded();
}
const downloadUrl = 'https://github.com/laurent22/joplin/releases/download/v' + info.version + '/' + filename;
let releaseNotes = info.releaseNotes + '';
if (releaseNotes) releaseNotes = '\n\n' + _('Release notes:\n\n%s', htmlToText_(releaseNotes));
dialog.showMessageBox({
const buttonIndex = dialog.showMessageBox(parentWindow_, {
type: 'info',
message: _('An update is available, do you want to download it now?' + releaseNotes),
buttons: [_('Yes'), _('No')]
}, (buttonIndex) => {
if (buttonIndex === 0) {
require('electron').shell.openExternal(downloadUrl);
}
})
});
onCheckEnded();
if (buttonIndex === 0) require('electron').shell.openExternal(downloadUrl);
})
autoUpdater.on('update-not-available', () => {
if (checkInBackground_) return;
if (checkInBackground_) return onCheckEnded();
dialog.showMessageBox({ message: _('Current version is up-to-date.') })
onCheckEnded();
})
// autoUpdater.on('update-downloaded', () => {
// dialog.showMessageBox({ message: _('New version downloaded - application will quit now and update...') }, () => {
// setTimeout(() => {
// try {
// autoUpdater.quitAndInstall();
// } catch (error) {
// autoUpdateLogger_.error(error);
// dialog.showErrorBox(_('Error'), _('Could not install the update: %s', error.message));
// }
// }, 100);
// })
// })
function checkForUpdates(inBackground, window, logFilePath) {
if (isCheckingForUpdate_) {
autoUpdateLogger_.info('checkForUpdates: Skipping check because it is already running');
return;
}
parentWindow_ = window;
onCheckStarted();
function checkForUpdates(inBackground, logFilePath) {
if (logFilePath && !autoUpdateLogger_.targets().length) {
autoUpdateLogger_ = new Logger();
autoUpdateLogger_.addTarget('file', { path: logFilePath });
@@ -85,7 +136,8 @@ function checkForUpdates(inBackground, logFilePath) {
autoUpdater.checkForUpdates()
} catch (error) {
autoUpdateLogger_.error(error);
if (!checkInBackground_) dialog.showErrorBox(_('Error'), error.message);
if (!checkInBackground_) showErrorMessageBox(error.message);
onCheckEnded();
}
}

View File

@@ -15,10 +15,6 @@ class ConfigScreenComponent extends React.Component {
constructor() {
super();
this.state = {
settings: {},
};
shared.init(this);
this.checkSyncConfig_ = async () => {
@@ -68,9 +64,7 @@ class ConfigScreenComponent extends React.Component {
};
const updateSettingValue = (key, value) => {
const settings = Object.assign({}, this.state.settings);
settings[key] = Setting.formatValue(key, value);
this.setState({ settings: settings });
return shared.updateSettingValue(this, key, value);
}
// Component key needs to be key+value otherwise it doesn't update when the settings change.
@@ -142,10 +136,7 @@ class ConfigScreenComponent extends React.Component {
}
onSaveClick() {
for (let n in this.state.settings) {
if (!this.state.settings.hasOwnProperty(n)) continue;
Setting.setValue(n, this.state.settings[n]);
}
shared.saveSettings(this);
this.props.dispatch({ type: 'NAV_BACK' });
}
@@ -167,24 +158,11 @@ class ConfigScreenComponent extends React.Component {
};
const buttonStyle = {
display: this.state.settings === this.props.settings ? 'none' : 'inline-block',
display: this.state.changedSettingKeys.length ? 'inline-block' : 'none',
marginRight: 10,
}
let settingComps = [];
let keys = Setting.keys(true, 'desktop');
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (!(key in settings)) {
console.warn('Missing setting: ' + key);
continue;
}
const md = Setting.settingMetadata(key);
if (md.show && !md.show(settings)) continue;
const comp = this.settingToComponent(key, settings[key]);
if (!comp) continue;
settingComps.push(comp);
}
const settingComps = shared.settingsToComponents(this, 'desktop', settings);
const syncTargetMd = SyncTargetRegistry.idToMetadata(settings['sync.target']);

View File

@@ -182,7 +182,7 @@ class EncryptionConfigScreenComponent extends React.Component {
<div>
<Header style={headerStyle} />
<div style={containerStyle}>
<div style={{backgroundColor: theme.warningBackgroundColor, paddingLeft: 10, paddingRight: 10, paddingTop: 2, paddingBottom: 2 }}>
{/*<div style={{backgroundColor: theme.warningBackgroundColor, paddingLeft: 10, paddingRight: 10, paddingTop: 2, paddingBottom: 2 }}>
<p style={theme.textStyle}>
Important: This is a <b>beta</b> feature. It has been extensively tested and is already in use by some users, but it is possible that some bugs remain.
</p>
@@ -192,7 +192,7 @@ class EncryptionConfigScreenComponent extends React.Component {
<p style={theme.textStyle}>
For more information about End-To-End Encryption (E2EE) and how it is going to work, please check the documentation: <a onClick={() => {bridge().openExternal('http://joplin.cozic.net/help/e2ee.html')}} href="#">http://joplin.cozic.net/help/e2ee.html</a>
</p>
</div>
</div>*/}
<h1 style={theme.h1Style}>{_('Status')}</h1>
<p style={theme.textStyle}>{_('Encryption is:')} <strong>{this.props.encryptionEnabled ? _('Enabled') : _('Disabled')}</strong></p>
{decryptedItemsInfo}

View File

@@ -25,9 +25,7 @@ class ImportScreenComponent extends React.Component {
doImport: true,
filePath: newProps.filePath,
messages: [],
});
this.doImport();
}, () => { this.doImport() });
}
}

View File

@@ -163,6 +163,8 @@ class MainScreenComponent extends React.Component {
}
},
});
} else if (command.name === 'toggleVisiblePanes') {
this.toggleVisiblePanes();
} else if (command.name === 'editAlarm') {
const note = await Note.load(command.noteId);
@@ -309,9 +311,7 @@ class MainScreenComponent extends React.Component {
title: _('Layout'),
iconName: 'fa-columns',
enabled: !!notes.length,
onClick: () => {
this.toggleVisiblePanes();
},
onClick: () => { this.doCommand({ name: 'toggleVisiblePanes' }) },
});
if (!this.promptOnClose_) {

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

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,6 +1,6 @@
{
"name": "Joplin",
"version": "0.10.61",
"version": "1.0.64",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "Joplin",
"version": "0.10.61",
"version": "1.0.64",
"description": "Joplin for Desktop",
"main": "main.js",
"scripts": {

View File

@@ -79,6 +79,10 @@ async function main(argv) {
const macOsUrl = downloadUrl(release, 'macos');
const linuxUrl = downloadUrl(release, 'linux');
console.info('Windows: ', winUrl);
console.info('macOS: ', macOsUrl);
console.info('Linux: ', linuxUrl);
let content = readmeContent();
if (winUrl) content = content.replace(/(https:\/\/github.com\/laurent22\/joplin\/releases\/download\/.*?\.exe)/, winUrl);

View File

@@ -6,7 +6,7 @@ Notes exported from Evernote via .enex files [can be imported](#importing-notes-
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.
Joplin is still under development but is out of Beta and should be suitable for every day use. The UI of the terminal client is built on top of the great [terminal-kit](https://github.com/cronvel/terminal-kit) library, the desktop client using [Electron](https://electronjs.org/), and the Android client front end is done using [React Native](https://facebook.github.io/react-native/).
The UI of the terminal client is built on top of the great [terminal-kit](https://github.com/cronvel/terminal-kit) library, the desktop client using [Electron](https://electronjs.org/), and the Android client front end is done using [React Native](https://facebook.github.io/react-native/).
<div class="top-screenshot"><img src="https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/AllClients.jpg" style="max-width: 100%; max-height: 35em;"></div>
@@ -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/v0.10.61/Joplin-Setup-0.10.61.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/v0.10.61/Joplin-0.10.61.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/v0.10.61/Joplin-0.10.61-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.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>
## 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-v0.10.92/joplin-v0.10.92.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.98/joplin-v1.0.98.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,13 +51,15 @@ For usage information, please refer to the full [Joplin Terminal Application Doc
# Features
- Desktop, mobile and terminal applications.
- Import Enex files (Evernote export format)
- Support notes, to-dos, tags and notebooks.
- 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.
- Ability to synchronise with multiple targets, including NextCloud, the file system and OneDrive (Dropbox is planned).
- 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.
- Tag support
- File attachment support (images are displayed, and other files are linked and can be opened in the relevant application).
- 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
@@ -91,8 +93,6 @@ Currently, synchronisation is possible with Nextcloud and OneDrive (by default)
## Nextcloud synchronisation
**Important: This is a beta feature. It has been extensively tested and is already in use by some users, but it is possible that some bugs remain. If you wish to you use it, it is recommended that you keep a backup of your data. The simplest way is to regularly backup the profile directory of the desktop or terminal application.**
On the **desktop application** or **mobile application**, go to the config screen and select Nextcloud as the synchronisation target. Then input [the WebDAV URL](https://docs.nextcloud.com/server/9/user_manual/files/access_webdav.html), this is normally `https://example.com/nextcloud/remote.php/dav/files/USERNAME/Joplin` (make sure to create the "Joplin" directory in Nextcloud and to replace USERNAME by your Nextcloud username), and set the username and password.
On the **terminal application**, you will need to set the `sync.target` config variable and all the `sync.5.path`, `sync.5.username` and `sync.5.password` config variables to, respectively the Nextcloud WebDAV URL, your username and your password. This can be done from the command line mode using:
@@ -106,10 +106,15 @@ If synchronisation does not work, please consult the logs in the app profile dir
## WebDAV synchronisation
**Important: This is a beta feature. It has been extensively tested and is already in use by some users, but it is possible that some bugs remain. If you wish to you use it, it is recommended that you keep a backup of your data. The simplest way is to regularly backup the profile directory of the desktop or terminal application.**
Select the "WebDAV" synchronisation target and follow the same instructions as for Nextcloud above.
Known compatible services that use WebDAV:
- [Box.com](https://www.box.com/)
- [DriveHQ](https://www.drivehq.com)
- [Zimbra](https://www.zimbra.com/)
- [Seafile](https://www.seafile.com/)
## OneDrive synchronisation
When syncing with OneDrive, Joplin creates a sub-directory in OneDrive, in /Apps/Joplin and read/write the notes and notebooks from it. The application does not have access to anything outside this directory.
@@ -191,42 +196,33 @@ Joplin is currently available in the languages below. If you would like to contr
- In Poedit, open this .pot file, go into the Catalog menu and click Configuration. Change "Country" and "Language" to your own country and language.
- From then you can translate the file. Once it is done, please either [open a pull request](https://github.com/laurent22/joplin/pulls) or send the file to [this address](https://raw.githubusercontent.com/laurent22/joplin/master/Assets/Adresse.png).
To **update a translation**, follow the same steps as above but instead of getting the .pot file, get the .po file for your language from there: https://github.com/laurent22/joplin/tree/master/CliClient/locales
This translation will apply to the three applications - desktop, mobile and terminal.
To **update a translation**, follow the same steps as above but instead of getting the .pot file, get the .po file for your language from the table below.
Current translations:
<!-- LOCALE-TABLE-AUTO-GENERATED -->
&nbsp; | Language | Code | Last translator | Percent done
&nbsp; | Language | Po File | Last translator | Percent done
---|---|---|---|---
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/es/basque_country.png) | Basque | eu | juan.abasolo@ehu.eus | 89%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/hr.png) | Croatian | hr_HR | Hrvoje Mandić <trbuhom@net.hr> | 72%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/de.png) | Deutsch | de_DE | Tobias Strobel <git@strobeltobias.de> | 91%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/gb.png) | English | en_GB | | 100%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/es.png) | Español | es_ES | Lucas Vieites | 79%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/cr.png) | Español (Costa Rica) | es_CR | | 68%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/fr.png) | Français | fr_FR | Laurent Cozic | 100%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/it.png) | Italiano | it_IT | | 75%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/be.png) | Nederlands | nl_BE | | 89%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/br.png) | Português (Brasil) | pt_BR | | 74%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/ru.png) | Русский | ru_RU | Artyom Karlov <artyom.karlov@gmail.com> | 94%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/cn.png) | 中文 (简体) | zh_CN | RCJacH <RCJacH@outlook.com> | 75%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/jp.png) | 日本語 | ja_JP | | 73%
![](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 | 88%
![](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> | 90%
![](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> | 98%
![](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) | | 67%
![](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) | | 75%
![](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) | | 88%
![](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) | | 73%
![](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> | 92%
![](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> | 75%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/masWter/png/16/country-4x3/jp.png) | 日本語 | [ja_JP](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ja_JP.po) | | 73%
<!-- LOCALE-TABLE-AUTO-GENERATED -->
# Coming features
- Mobile: manage tags
- Windows: Tray icon
- Desktop apps: Tag auto-complete
- Desktop apps: Dark theme
- Linux: Enable auto-update for desktop app
# Known bugs
- Non-alphabetical characters such as Chinese or Arabic might create glitches in the terminal on Windows. This is a limitation of the current Windows console.
- Auto-update is not working in the Linux desktop application.
- While the mobile can sync and load tags, it is not currently possible to create new ones. The desktop and terminal apps can create, delete and edit tags.
# License

View File

@@ -35,7 +35,7 @@ To start it, type `demo-joplin`.
# Usage
To start the application type `joplin`. This will open the user interface, which has three main panes: Notebooks, Notes and the text of the current note. There are also additional panels that can be toggled on and off via [shortcuts](#available-shortcuts).
To start the application type `joplin`. This will open the user interface, which has three main panes: Notebooks, Notes and the text of the current note. There are also additional panels that can be toggled on and off via [shortcuts](#shortcuts).
<img src="https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/ScreenshotTerminalCaptions.png" height="450px">
@@ -45,11 +45,11 @@ Joplin user interface is partly based on the text editor Vim and offers two diff
### Normal mode
Allows moving from one pane to another using the `Tab` and `Shift-Tab` keys, and to select/view notes using the arrow keys. Text area can be scrolled using the arrow keys too. Press `Enter` to edit a note. Various other [shortcuts](#available-shortcuts) are available.
Allows moving from one pane to another using the `Tab` and `Shift-Tab` keys, and to select/view notes using the arrow keys. Text area can be scrolled using the arrow keys too. Press `Enter` to edit a note. Various other [shortcuts](#shortcuts) are available.
### Command-line mode
Press `:` to enter command line mode. From there, the Joplin commands such as `mknote` or `search` are available. See the [full list of commands](#available-commands).
Press `:` to enter command line mode. From there, the Joplin commands such as `mknote` or `search` are available. See the [full list of commands](#commands).
It is possible to refer to a note or notebook by title or ID. However the simplest way is to refer to the currently selected item using one of these shortcuts:
@@ -96,7 +96,7 @@ The complete usage information is available from command-line mode, by typing on
Command | Description
--------|-------------------
`help` | General help information
`help shortcuts` | Lists the available shortcuts
`help keymap` | Lists the available shortcuts
`help [command]` | Displays information about a particular command
If the help is not fully visible, press `Tab` multiple times till the console is in focus and use the arrow keys or page up/down to scroll the text.
@@ -175,29 +175,82 @@ Give a new title to the note:
$ joplin set fe889 title "New title"
# Available shortcuts
# Shortcuts
There are two types of shortcuts: those that manipulate the user interface directly, such as `TAB` to move from one pane to another, and those that are simply shortcuts to actual commands. In a way similar to Vim, these shortcuts are generally a verb followed by an object. For example, typing `mn` ([m]ake [n]ote), is used to create a new note: it will switch the interface to command line mode and pre-fill it with `mknote ""` from where the title of the note can be entered. See below for the full list of shortcuts:
There are two types of shortcuts: those that manipulate the user interface directly, such as `TAB` to move from one pane to another, and those that are simply shortcuts to actual commands. In a way similar to Vim, these shortcuts are generally a verb followed by an object. For example, typing `mn` ([m]ake [n]ote), is used to create a new note: it will switch the interface to command line mode and pre-fill it with `mknote ""` from where the title of the note can be entered. See below for the full list of default shortcuts:
Tab Give focus to next pane
Shift+Tab Give focus to previous pane
: Enter command line mode
ESC Exit command line mode
ENTER Edit the selected note
Ctrl+C Cancel the current command.
Ctrl+D Exit the application.
DELETE Delete the currently selected note or notebook.
SPACE Set a to-do as completed / not completed
tc [t]oggle [c]onsole between maximized/minimized/hidden/visible.
/ Search
tm [t]oggle note [m]etadata.
mn [M]ake a new [n]ote
mt [M]ake a new [t]odo
mb [M]ake a new note[b]ook
yn Copy ([Y]ank) the [n]ote to a notebook.
dn Move the note to a notebook.
: enter_command_line_mode
TAB focus_next
SHIFT_TAB focus_previous
UP move_up
DOWN move_down
PAGE_UP page_up
PAGE_DOWN page_down
ENTER activate
DELETE, BACKSPACE delete
(SPACE) todo toggle $n
tc toggle_console
tm toggle_metadata
/ search ""
mn mknote ""
mt mktodo ""
mb mkbook ""
yn cp $n ""
dn mv $n ""
# Available commands
Shortcut can be configured by adding a file `keymap.json` to the profile directory. The content of this file is a JSON array with each entry defining a command and the keys associated with it.
As an example, this is the default keymap, but read below for a detailed explanation of each property.
```json
[
{ "keys": [":"], "type": "function", "command": "enter_command_line_mode" },
{ "keys": ["TAB"], "type": "function", "command": "focus_next" },
{ "keys": ["SHIFT_TAB"], "type": "function", "command": "focus_previous" },
{ "keys": ["UP"], "type": "function", "command": "move_up" },
{ "keys": ["DOWN"], "type": "function", "command": "move_down" },
{ "keys": ["PAGE_UP"], "type": "function", "command": "page_up" },
{ "keys": ["PAGE_DOWN"], "type": "function", "command": "page_down" },
{ "keys": ["ENTER"], "type": "function", "command": "activate" },
{ "keys": ["DELETE", "BACKSPACE"], "type": "function", "command": "delete" },
{ "keys": [" "], "command": "todo toggle $n" },
{ "keys": ["tc"], "type": "function", "command": "toggle_console" },
{ "keys": ["tm"], "type": "function", "command": "toggle_metadata" },
{ "keys": ["/"], "type": "prompt", "command": "search \"\"", "cursorPosition": -2 },
{ "keys": ["mn"], "type": "prompt", "command": "mknote \"\"", "cursorPosition": -2 },
{ "keys": ["mt"], "type": "prompt", "command": "mktodo \"\"", "cursorPosition": -2 },
{ "keys": ["mb"], "type": "prompt", "command": "mkbook \"\"", "cursorPosition": -2 },
{ "keys": ["yn"], "type": "prompt", "command": "cp $n \"\"", "cursorPosition": -2 },
{ "keys": ["dn"], "type": "prompt", "command": "mv $n \"\"", "cursorPosition": -2 }
]
```
Each entry can have the following properties:
Name | Description
-----|------------
`keys` | The array of keys that will trigger the action. Special keys such as page up, down arrow, etc. needs to be specified UPPERCASE. See the [list of available special keys](https://github.com/cronvel/terminal-kit/blob/3114206a9556f518cc63abbcb3d188fe1995100d/lib/termconfig/xterm.js#L531). For example, `['DELETE', 'BACKSPACE']` means the command will run if the user pressed either the delete or backspace key. Key combinations can also be provided - in that case specify them lowercase. For example "tc" means that the command will be executed when the user pressed "t" then "c". Special keys can also be used in this fashion - simply write them one after the other. For instance, `CTRL_WCTRL_W` means the action would be executed if the user pressed "ctrl-w ctrl-w".
`type` | The command type. It can have the value "exec", "function" or "prompt". **exec**: Simply execute the provided [command](#commands). For example `edit $n` would edit the selected note. **function**: Run a special commands (see below for the list of functions). **prompt**: A bit similar to "exec", except that the command is not going to be executed immediately - this allows the user to provide additional data. For example `mknote ""` would fill the command line with this command and allow the user to set the title. A prompt command can also take a `cursorPosition` parameter (see below)
`command` | The command that needs to be executed
`cusorPosition` | An integer. For prompt commands, tells where the cursor (caret) should start at. This is convenient for example to position the cursor between quotes. Use a negative value to set a position starting from the end. A value of "0" means positioning the caret at the first character. A value of "-1" means positioning it at the end.
This is the list of special functions:
Name | Description
-----|------------
enter_command_line_mode | Enter command line mode
focus_next | Focus next pane (or widget)
focus_previous | Focus previous pane (or widget)
move_up | Move up (in a list for example)
move_down | Move down (in a list for example)
page_up | Page up
page_down | Page down
activate | Activates the selected item. If the item is a note for example it will be open in the editor
delete | Deletes the selected item
toggle_console | Toggle the console
toggle_metadata | Toggle note metadata
# Commands
The following commands are available in [command-line mode](#command-line-mode):
@@ -205,6 +258,12 @@ The following commands are available in [command-line mode](#command-line-mode):
Attaches the given file to the note.
cat <note>
Displays the given note.
-v, --verbose Displays the complete information about note.
config [name] [value]
Gets or sets a config value. If [value] is not provided, it will show the
@@ -215,11 +274,6 @@ The following commands are available in [command-line mode](#command-line-mode):
Possible keys/values:
sync.2.path File system synchronisation target directory.
The path to synchronise with when file system
synchronisation is enabled. See `sync.target`.
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
@@ -228,8 +282,12 @@ The following commands are available in [command-line mode](#command-line-mode):
locale Language.
Type: Enum.
Possible values: en_GB (English), es_CR (Español),
fr_FR (Français).
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.
@@ -244,7 +302,7 @@ The following commands are available in [command-line mode](#command-line-mode):
Possible values: HH:mm (20:30), h:mm A (8:30 PM).
Default: "HH:mm"
uncompletedTodosOnTop Show uncompleted todos on top of the lists.
uncompletedTodosOnTop Show uncompleted to-dos on top of the lists.
Type: bool.
Default: true
@@ -260,13 +318,37 @@ The following commands are available in [command-line mode](#command-line-mode):
Default: 300
sync.target Synchronisation target.
The target to synchonise to. If synchronising with
the file system, set `sync.2.path` to specify the
target directory.
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)).
(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 Nexcloud WebDAV URL.
Type: string.
sync.5.username Nexcloud username.
Type: string.
sync.5.password Nexcloud 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]
@@ -277,13 +359,36 @@ The following commands are available in [command-line mode](#command-line-mode):
Marks a to-do as done.
e2ee <command> [path]
Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`,
`status` and `target-status`.
-p, --password <password> Use this password as master password (For
security reasons, it is not recommended to use
this option).
-v, --verbose More verbose output for the `target-status`
command
edit <note>
Edit note.
exit
encrypt-config <command>
Exits the application.
Manages encryption configuration.
-p, --password <password> Use this password as master password (For
security reasons, it is not recommended to use
this option).
encryption <command>
Manages encryption configuration.
-p, --password <password> Use this password as master password (For
security reasons, it is not recommended to use
this option).
export <directory>
@@ -339,9 +444,18 @@ The following commands are available in [command-line mode](#command-line-mode):
-f, --force Deletes the notes without asking for confirmation.
search <pattern> [notebook]
set <note> <name> [value]
Searches for the given <pattern> in all the notes.
Sets the property <name> of the given <note> to the given [value].
Possible properties are:
parent_id (text), title (text), body (text), created_time (int),
updated_time (int), is_conflict (int), latitude (numeric), longitude
(numeric), altitude (numeric), author (text), source_url (text), is_todo
(int), todo_due (int), todo_completed (int), source (text),
source_application (text), application_data (text), order (int),
user_created_time (int), user_updated_time (int), encryption_cipher_text
(text), encryption_applied (int)
status
@@ -353,7 +467,6 @@ The following commands are available in [command-line mode](#command-line-mode):
--target <target> Sync to provided target (defaults to sync.target config
value)
--random-failures For debugging purposes. Do not use.
tag <tag-command> [tag] [note]
@@ -372,6 +485,11 @@ The following commands are available in [command-line mode](#command-line-mode):
Marks a to-do as non-completed.
use <notebook>
Switches to [notebook] - all further operations will happen within this
notebook.
version
Displays version information

View File

@@ -90,8 +90,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion 16
targetSdkVersion 22
versionCode 2097270
versionName "0.10.92"
versionCode 2097276
versionName "1.0.98"
ndk {
abiFilters "armeabi-v7a", "x86"
}

View File

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

View File

@@ -25,7 +25,7 @@ class SyncTargetNextcloud extends BaseSyncTarget {
}
static label() {
return _('Nextcloud (Beta)');
return _('Nextcloud');
}
isAuthenticated() {
@@ -37,7 +37,7 @@ class SyncTargetNextcloud extends BaseSyncTarget {
}
async initFileApi() {
const fileApi = await SyncTargetWebDAV.initFileApi_({
const fileApi = await SyncTargetWebDAV.initFileApi_(SyncTargetNextcloud.id(), {
path: Setting.value('sync.5.path'),
username: Setting.value('sync.5.username'),
password: Setting.value('sync.5.password'),

View File

@@ -21,14 +21,14 @@ class SyncTargetWebDAV extends BaseSyncTarget {
}
static label() {
return _('WebDAV (Beta)');
return _('WebDAV');
}
isAuthenticated() {
return true;
}
static async initFileApi_(options) {
static async initFileApi_(syncTargetId, options) {
const apiOptions = {
baseUrl: () => options.path,
username: () => options.username,
@@ -38,12 +38,12 @@ class SyncTargetWebDAV extends BaseSyncTarget {
const api = new WebDavApi(apiOptions);
const driver = new FileApiDriverWebDav(api);
const fileApi = new FileApi('', driver);
fileApi.setSyncTargetId(this.id());
fileApi.setSyncTargetId(syncTargetId);
return fileApi;
}
static async checkConfig(options) {
const fileApi = await SyncTargetWebDAV.initFileApi_(options);
const fileApi = await SyncTargetWebDAV.initFileApi_(SyncTargetWebDAV.id(), options);
const output = {
ok: false,
@@ -63,7 +63,7 @@ class SyncTargetWebDAV extends BaseSyncTarget {
}
async initFileApi() {
const fileApi = await SyncTargetWebDAV.initFileApi_({
const fileApi = await SyncTargetWebDAV.initFileApi_(SyncTargetWebDAV.id(), {
path: Setting.value('sync.6.path'),
username: Setting.value('sync.6.username'),
password: Setting.value('sync.6.password'),

View File

@@ -133,6 +133,42 @@ class WebDavApi {
return this.valueFromJson(json, keys, 'array');
}
resourcePropByName(resource, outputType, propName) {
const propStats = resource['d:propstat'];
let output = null;
if (!Array.isArray(propStats)) throw new Error('Missing d:propstat property');
for (let i = 0; i < propStats.length; i++) {
const props = propStats[i]['d:prop'];
if (!Array.isArray(props) || !props.length) continue;
const prop = props[0];
if (Array.isArray(prop[propName])) {
output = prop[propName];
break;
}
}
if (outputType === 'string') {
// If the XML has not attribute the value is directly a string
// If the XML node has attributes, the value is under "_".
// Eg for this XML, the string will be under {"_":"Thu, 01 Feb 2018 17:24:05 GMT"}:
// <a:getlastmodified b:dt="dateTime.rfc1123">Thu, 01 Feb 2018 17:24:05 GMT</a:getlastmodified>
// For this XML, the value will be "Thu, 01 Feb 2018 17:24:05 GMT"
// <a:getlastmodified>Thu, 01 Feb 2018 17:24:05 GMT</a:getlastmodified>
output = output[0];
if (typeof output === 'object' && '_' in output) output = output['_'];
if (typeof output !== 'string') return null;
return output;
}
if (outputType === 'array') {
return output;
}
throw new Error('Invalid output type: ' + outputType);
}
async execPropFind(path, depth, fields = null, options = null) {
if (fields === null) fields = ['d:getlastmodified'];
@@ -158,6 +194,22 @@ class WebDavApi {
return this.exec('PROPFIND', path, body, { 'Depth': depth }, options);
}
requestToCurl_(url, options) {
let output = [];
output.push('curl');
if (options.method) output.push('-X ' + options.method);
if (options.headers) {
for (let n in options.headers) {
if (!options.headers.hasOwnProperty(n)) continue;
output.push('-H ' + '"' + n + ': ' + options.headers[n] + '"');
}
}
if (options.body) output.push('--data ' + "'" + options.body + "'");
output.push(url);
return output.join(' ');
}
// curl -u admin:123456 'http://nextcloud.local/remote.php/dav/files/admin/' -X PROPFIND --data '<?xml version="1.0" encoding="UTF-8"?>
// <d:propfind xmlns:d="DAV:">
// <d:prop xmlns:oc="http://owncloud.org/ns">
@@ -175,8 +227,6 @@ class WebDavApi {
if (authToken) headers['Authorization'] = 'Basic ' + authToken;
if (typeof body === 'string') headers['Content-Length'] = body.length;
const fetchOptions = {};
fetchOptions.headers = headers;
fetchOptions.method = method;
@@ -188,10 +238,16 @@ class WebDavApi {
let response = null;
// console.info('WebDAV Call', method + ' ' + url, headers, options);
// console.info(this.requestToCurl_(url, fetchOptions));
if (options.source == 'file' && (method == 'POST' || method == 'PUT')) {
if (fetchOptions.path) {
const fileStat = await shim.fsDriver().stat(fetchOptions.path);
if (fileStat) fetchOptions.headers['Content-Length'] = fileStat.size + '';
}
response = await shim.uploadBlob(url, fetchOptions);
} else if (options.target == 'string') {
if (typeof body === 'string') fetchOptions.headers['Content-Length'] = shim.stringByteLength(body) + '';
response = await shim.fetch(url, fetchOptions);
} else { // file
response = await shim.fetchBlob(url, fetchOptions);

View File

@@ -20,11 +20,6 @@ class ConfigScreenComponent extends BaseScreenComponent {
super();
this.styles_ = {};
this.state = {
settings: {},
settingsChanged: false,
};
shared.init(this);
this.checkSyncConfig_ = async () => {
@@ -32,11 +27,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
}
this.saveButton_press = () => {
for (let n in this.state.settings) {
if (!this.state.settings.hasOwnProperty(n)) continue;
Setting.setValue(n, this.state.settings[n]);
}
this.setState({settingsChanged:false});
return shared.saveSettings(this);
};
}
@@ -74,6 +65,11 @@ class ConfigScreenComponent extends BaseScreenComponent {
fontSize: theme.fontSize,
flex: 1,
},
descriptionText: {
color: theme.color,
fontSize: theme.fontSize,
flex: 1,
},
settingControl: {
color: theme.color,
flex: 1,
@@ -113,12 +109,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
let output = null;
const updateSettingValue = (key, value) => {
const settings = Object.assign({}, this.state.settings);
settings[key] = value;
this.setState({
settings: settings,
settingsChanged: true,
});
return shared.updateSettingValue(this, key, value);
}
const md = Setting.settingMetadata(key);
@@ -187,20 +178,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
render() {
const settings = this.state.settings;
const keys = Setting.keys(true, 'mobile');
let settingComps = [];
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
//if (key == 'sync.target' && !settings.showAdvancedOptions) continue;
if (!Setting.isPublic(key)) continue;
const md = Setting.settingMetadata(key);
if (md.show && !md.show(settings)) continue;
const comp = this.settingToComponent(key, settings[key]);
if (!comp) continue;
settingComps.push(comp);
}
const settingComps = shared.settingsToComponents(this, 'mobile', settings);
const syncTargetMd = SyncTargetRegistry.idToMetadata(settings['sync.target']);
@@ -208,8 +186,8 @@ class ConfigScreenComponent extends BaseScreenComponent {
const messages = shared.checkSyncConfigMessages(this);
const statusComp = !messages.length ? null : (
<View style={{flex:1, marginTop: 10}}>
<Text>{messages[0]}</Text>
{messages.length >= 1 ? (<Text style={{marginTop:10}}>{messages[1]}</Text>) : null}
<Text style={this.styles().descriptionText}>{messages[0]}</Text>
{messages.length >= 1 ? (<View style={{marginTop:10}}><Text style={this.styles().descriptionText}>{messages[1]}</Text></View>) : null}
</View>);
settingComps.push(
@@ -244,7 +222,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
<ScreenHeader
title={_('Configuration')}
showSaveButton={true}
saveButtonDisabled={!this.state.settingsChanged}
saveButtonDisabled={!this.state.changedSettingKeys.length}
onSaveButtonPress={this.saveButton_press}
/>
<ScrollView >

View File

@@ -1,5 +1,5 @@
const React = require('react'); const Component = React.Component;
const { TextInput, TouchableOpacity, Linking, View, Switch, Slider, StyleSheet, Text, Button, ScrollView } = require('react-native');
const { TextInput, TouchableOpacity, Linking, View, Switch, Slider, StyleSheet, Text, Button, ScrollView, Platform } = require('react-native');
const EncryptionService = require('lib/services/EncryptionService');
const { connect } = require('react-redux');
const { ScreenHeader } = require('lib/components/screen-header.js');
@@ -109,13 +109,20 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent {
const passwordOk = this.state.passwordChecks[mk.id] === true ? '✔' : '❌';
const active = this.props.activeMasterKeyId === mk.id ? '✔' : '';
const inputStyle = {flex:1, marginRight: 10, color: theme.color};
if (Platform.OS === 'ios') {
inputStyle.borderBottomWidth = 1;
inputStyle.borderBottomColor = theme.dividerColor;
}
return (
<View key={mk.id}>
<Text style={this.styles().titleText}>{_('Master Key %s', mk.id.substr(0,6))}</Text>
<Text style={this.styles().normalText}>{_('Created: %s', time.formatMsToLocal(mk.created_time))}</Text>
<View style={{flexDirection: 'row', alignItems: 'center'}}>
<Text style={{flex:0, fontSize: theme.fontSize, marginRight: 10, color: theme.color}}>{_('Password:')}</Text>
<TextInput secureTextEntry={true} value={password} onChangeText={(text) => onPasswordChange(text)} style={{flex:1, marginRight: 10, color: theme.color}}></TextInput>
<TextInput secureTextEntry={true} value={password} onChangeText={(text) => onPasswordChange(text)} style={inputStyle}></TextInput>
<Text style={{fontSize: theme.fontSize, marginRight: 10, color: theme.color}}>{passwordOk}</Text>
<Button title={_('Save')} onPress={() => onSaveClick()}></Button>
</View>
@@ -215,12 +222,12 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent {
<ScreenHeader title={_('Encryption Config')}/>
<ScrollView style={this.styles().container}>
<View style={{backgroundColor: theme.warningBackgroundColor, padding: 5}}>
{/*<View style={{backgroundColor: theme.warningBackgroundColor, padding: 5}}>
<Text>Important: This is a *beta* feature. It has been extensively tested and is already in use by some users, but it is possible that some bugs remain.</Text>
<Text>If you wish to you use it, it is recommended that you keep a backup of your data. The simplest way is to regularly backup your notes from the desktop or terminal application.</Text>
<Text>For more information about End-To-End Encryption (E2EE) and how it is going to work, please check the documentation:</Text>
<TouchableOpacity onPress={() => { Linking.openURL('http://joplin.cozic.net/help/e2ee.html') }}><Text>http://joplin.cozic.net/help/e2ee.html</Text></TouchableOpacity>
</View>
</View>*/}
<Text style={this.styles().titleText}>{_('Status')}</Text>
<Text style={this.styles().normalText}>{_('Encryption is: %s', this.props.encryptionEnabled ? _('Enabled') : _('Disabled'))}</Text>

View File

@@ -1,5 +1,5 @@
const React = require('react'); const Component = React.Component;
const { ListView, View, Text, Button, StyleSheet } = require('react-native');
const { ListView, View, Text, Button, StyleSheet, Platform } = require('react-native');
const { connect } = require('react-redux');
const { Log } = require('lib/log.js');
const { reg } = require('lib/registry.js');
@@ -43,12 +43,15 @@ class LogScreenComponent extends BaseScreenComponent {
paddingBottom:0,
},
rowText: {
fontFamily: 'monospace',
fontSize: 10,
color: theme.color,
},
};
if (Platform.OS !== 'ios') { // Crashes on iOS with error "Unrecognized font family 'monospace'"
styles.rowText.fontFamily = 'monospace';
}
styles.rowTextError = Object.assign({}, styles.rowText);
styles.rowTextError.color = theme.colorError;

View File

@@ -7,6 +7,8 @@ const shared = {}
shared.init = function(comp) {
if (!comp.state) comp.state = {};
comp.state.checkSyncConfigResult = null;
comp.state.settings = {};
comp.state.changedSettingKeys = [];
}
shared.checkSyncConfig = async function(comp, settings) {
@@ -15,7 +17,6 @@ shared.checkSyncConfig = async function(comp, settings) {
const options = Setting.subValues('sync.' + syncTargetId, settings);
comp.setState({ checkSyncConfigResult: 'checking' });
const result = await SyncTargetClass.checkConfig(options);
console.info(result);
comp.setState({ checkSyncConfigResult: result });
}
@@ -35,4 +36,46 @@ shared.checkSyncConfigMessages = function(comp) {
return output;
}
shared.updateSettingValue = function(comp, key, value) {
const settings = Object.assign({}, comp.state.settings);
const changedSettingKeys = comp.state.changedSettingKeys.slice();
settings[key] = Setting.formatValue(key, value);
if (changedSettingKeys.indexOf(key) < 0) changedSettingKeys.push(key);
comp.setState({
settings: settings,
changedSettingKeys: changedSettingKeys,
});
}
shared.saveSettings = function(comp) {
for (let key in comp.state.settings) {
if (!comp.state.settings.hasOwnProperty(key)) continue;
if (comp.state.changedSettingKeys.indexOf(key) < 0) continue;
console.info("Saving", key, comp.state.settings[key]);
Setting.setValue(key, comp.state.settings[key]);
}
comp.setState({ changedSettingKeys: [] });
}
shared.settingsToComponents = function(comp, device, settings) {
const keys = Setting.keys(true, device);
const settingComps = [];
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (!Setting.isPublic(key)) continue;
const md = Setting.settingMetadata(key);
if (md.show && !md.show(settings)) continue;
const settingComp = comp.settingToComponent(key, settings[key]);
if (!settingComp) continue;
settingComps.push(settingComp);
}
return settingComps
}
module.exports = shared;

View File

@@ -18,7 +18,7 @@ class FileApiDriverMemory {
}
decodeContent_(content) {
return Buffer.from(content, 'base64').toString('ascii');
return Buffer.from(content, 'base64').toString('utf-8');
}
itemIndexByPath(path) {
@@ -49,14 +49,13 @@ class FileApiDriverMemory {
return Promise.resolve(item ? Object.assign({}, item) : null);
}
setTimestamp(path, timestampMs) {
async setTimestamp(path, timestampMs) {
let item = this.itemByPath(path);
if (!item) return Promise.reject(new Error('File not found: ' + path));
item.updated_time = timestampMs;
return Promise.resolve();
}
list(path, options) {
async list(path, options) {
let output = [];
for (let i = 0; i < this.items_.length; i++) {
@@ -95,11 +94,10 @@ class FileApiDriverMemory {
return output;
}
mkdir(path) {
async mkdir(path) {
let index = this.itemIndexByPath(path);
if (index >= 0) return Promise.resolve();
if (index >= 0) return;
this.items_.push(this.newItem(path, true));
return Promise.resolve();
}
async put(path, content, options = null) {
@@ -116,10 +114,9 @@ class FileApiDriverMemory {
this.items_[index].content = this.encodeContent_(content);
this.items_[index].updated_time = time.unix();
}
return Promise.resolve();
}
delete(path) {
async delete(path) {
let index = this.itemIndexByPath(path);
if (index >= 0) {
let item = Object.assign({}, this.items_[index]);
@@ -128,20 +125,17 @@ class FileApiDriverMemory {
this.deletedItems_.push(item);
this.items_.splice(index, 1);
}
return Promise.resolve();
}
move(oldPath, newPath) {
async move(oldPath, newPath) {
let sourceItem = this.itemByPath(oldPath);
if (!sourceItem) return Promise.reject(new Error('Path not found: ' + oldPath));
this.delete(newPath); // Overwrite if newPath already exists
sourceItem.path = newPath;
return Promise.resolve();
}
format() {
async format() {
this.items_ = [];
return Promise.resolve();
}
async delta(path, options = null) {
@@ -159,9 +153,8 @@ class FileApiDriverMemory {
return output;
}
clearRoot() {
async clearRoot() {
this.items_ = [];
return Promise.resolve();
}
}

View File

@@ -27,7 +27,7 @@ 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
// 'd:getcontentlength', // Remove this once PUT call issue is sorted out
]);
const resource = this.api().objectFromJson(result, ['d:multistatus', 'd:response', 0]);
@@ -39,23 +39,41 @@ class FileApiDriverWebDav {
}
statFromResource_(resource, path) {
const isCollection = this.api().stringFromJson(resource, ['d:propstat', 0, 'd:prop', 0, 'd:resourcetype', 0, 'd:collection', 0]);
const lastModifiedString = this.api().stringFromJson(resource, ['d:propstat', 0, 'd:prop', 0, 'd:getlastmodified', 0]);
// WebDAV implementations are always slighly different from one server to another but, at the minimum,
// a resource should have a propstat key - if not it's probably an error.
const propStat = this.api().arrayFromJson(resource, ['d:propstat']);
if (!Array.isArray(propStat)) throw new Error('Invalid WebDAV resource format: ' + JSON.stringify(resource));
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 resourceTypes = this.api().resourcePropByName(resource, 'array', 'd:resourcetype');
let isDir = false;
if (Array.isArray(resourceTypes)) {
for (let i = 0; i < resourceTypes.length; i++) {
const t = resourceTypes[i];
if (typeof t === 'object' && 'd:collection' in t) {
isDir = true;
break;
}
}
}
if (!lastModifiedString) throw new Error('Could not get lastModified date: ' + JSON.stringify(resource));
const lastModifiedString = this.api().resourcePropByName(resource, 'string', 'd:getlastmodified');
const lastModifiedDate = new Date(lastModifiedString);
// 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));
// 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.
if (!lastModifiedString && !isDir) throw new Error('Could not get lastModified date for resource: ' + JSON.stringify(resource));
const lastModifiedDate = lastModifiedString ? new Date(lastModifiedString) : new Date();
if (isNaN(lastModifiedDate.getTime())) throw new Error('Invalid date: ' + lastModifiedString);
return {
path: path,
// created_time: lastModifiedDate.getTime(),
updated_time: lastModifiedDate.getTime(),
isDir: isCollection === '',
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.
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.
};
}
@@ -260,7 +278,7 @@ class FileApiDriverWebDav {
]);
const resources = this.api().arrayFromJson(result, ['d:multistatus', 'd:response']);
const stats = this.statsFromResources_(resources)
const stats = this.statsFromResources_(resources);
return {
items: stats,
@@ -304,32 +322,7 @@ class FileApiDriverWebDav {
}
async put(path, content, options = null) {
// In theory, if a client doesn't complete an upload, the file will not appear in the Nextcloud app. Likewise if
// the server interrupts the upload midway, the client should receive some kind of error and try uploading the
// file again next time. At the very least the file should not appear half-uploaded on the server. In practice
// however it seems some files might end up half uploaded on the server (at least on ocloud.de) so, for now,
// instead of doing a simple PUT, we do it to a temp file on Nextcloud, then check the file size and, if it
// matches, move it its actual place (hoping the server won't mess up and only copy half of the file).
// This is innefficient so once the bug is better understood it should hopefully be possible to go back to
// using a single PUT call.
let contentSize = 0;
if (content) contentSize = content.length;
if (options && options.path) {
const stat = await shim.fsDriver().stat(options.path);
contentSize = stat.size;
}
const tempPath = this.fileApi_.tempDirName() + '/' + basename(path) + '_' + Date.now();
await this.api().exec('PUT', tempPath, content, null, options);
const stat = await this.stat(tempPath);
if (stat.sizeDONOTUSE != contentSize) {
// await this.delete(tempPath);
throw new Error('WebDAV PUT - Size check failed for ' + tempPath + ' Expected: ' + contentSize + '. Found: ' + stat.sizeDONOTUSE);
}
await this.move(tempPath, path);
return await this.api().exec('PUT', path, content, null, options);
}
async delete(path) {

View File

@@ -39,12 +39,14 @@ class FileApi {
this.syncTargetId_ = null;
this.tempDirName_ = null;
this.driver_.fileApi_ = this;
this.requestRepeatCount_ = null; // For testing purpose only - normally this value should come from the driver
}
// Ideally all requests repeating should be done at the FileApi level to remove duplicate code in the drivers, but
// historically some drivers (eg. OneDrive) are already handling request repeating, so this is optional, per driver,
// and it defaults to no repeating.
requestRepeatCount() {
if (this.requestRepeatCount_ !== null) return this.requestRepeatCount_;
if (this.driver_.requestRepeatCount) return this.driver_.requestRepeatCount();
return 0;
}

View File

@@ -62,7 +62,7 @@ class FsDriverRN {
const r = await RNFS.stat(path);
return this.rnfsStatToStd_(r, path);
} catch (error) {
if (error && error.message && error.message.indexOf('exist') >= 0) {
if (error && ((error.message && error.message.indexOf('exist') >= 0) || error.code === 'ENOENT')) {
// Probably { [Error: File does not exist] framesToPop: 1, code: 'EUNSPECIFIED' }
// which unfortunately does not have a proper error code. Can be ignored.
return null;
@@ -120,7 +120,7 @@ class FsDriverRN {
try {
await RNFS.unlink(path);
} catch (error) {
if (error && error.message && error.message.indexOf('exist') >= 0) {
if (error && ((error.message && error.message.indexOf('exist') >= 0) || error.code === 'ENOENT')) {
// Probably { [Error: File does not exist] framesToPop: 1, code: 'EUNSPECIFIED' }
// which unfortunately does not have a proper error code. Can be ignored.
} else {

View File

@@ -58,7 +58,7 @@ class Setting extends BaseModel {
// recent: _('Non-completed and recently completed ones'),
// nonCompleted: _('Non-completed ones only'),
// })},
'uncompletedTodosOnTop': { value: true, type: Setting.TYPE_BOOL, public: true, label: () => _('Show uncompleted todos on top of the lists') },
'uncompletedTodosOnTop': { value: true, type: Setting.TYPE_BOOL, public: true, label: () => _('Show uncompleted to-dos on top of the lists') },
'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 {
@@ -429,7 +429,7 @@ class Setting extends BaseModel {
// }
// }
static saveAll() {
static async saveAll() {
if (!this.saveTimeoutId_) return Promise.resolve();
this.logger().info('Saving settings...');
@@ -444,12 +444,14 @@ class Setting extends BaseModel {
queries.push(Database.insertQuery(this.tableName(), s));
}
return BaseModel.db().transactionExecBatch(queries).then(() => {
this.logger().info('Settings have been saved.');
});
await BaseModel.db().transactionExecBatch(queries);
this.logger().info('Settings have been saved.');
}
static scheduleSave() {
if (!Setting.autoSaveEnabled) return;
if (this.saveTimeoutId_) clearTimeout(this.saveTimeoutId_);
this.saveTimeoutId_ = setTimeout(() => {
@@ -521,4 +523,6 @@ Setting.constants_ = {
openDevTools: false,
}
Setting.autoSaveEnabled = true;
module.exports = Setting;

View File

@@ -74,18 +74,19 @@ class DecryptionWorker {
const item = items[i];
// Temp hack
if (['edf44b7a0e4f8cbf248e206cd8dfa800', '2ccb3c9af0b1adac2ec6b66a5961fbb1'].indexOf(item.id) >= 0) {
excludedIds.push(item.id);
continue;
}
// if (['edf44b7a0e4f8cbf248e206cd8dfa800', '2ccb3c9af0b1adac2ec6b66a5961fbb1'].indexOf(item.id) >= 0) {
// excludedIds.push(item.id);
// continue;
// }
const ItemClass = BaseItem.itemClass(item);
this.logger().info('DecryptionWorker: decrypting: ' + item.id + ' (' + ItemClass.tableName() + ')');
try {
await ItemClass.decrypt(item);
} catch (error) {
excludedIds.push(item.id);
if (error.code === 'masterKeyNotLoaded' && options.materKeyNotLoadedHandler === 'dispatch') {
excludedIds.push(item.id);
if (notLoadedMasterKeyDisptaches.indexOf(error.masterKeyId) < 0) {
this.dispatch({
type: 'MASTERKEY_ADD_NOT_LOADED',

View File

@@ -172,6 +172,10 @@ function shimInit() {
return shim.fetch(url, options);
}
shim.stringByteLength = function(string) {
return Buffer.byteLength(string, 'utf-8');
}
}
module.exports = { shimInit };

View File

@@ -5,6 +5,7 @@ const RNFetchBlob = require('react-native-fetch-blob').default;
const { generateSecureRandom } = require('react-native-securerandom');
const FsDriverRN = require('lib/fs-driver-rn.js').FsDriverRN;
const urlValidator = require('valid-url');
const { Buffer } = require('buffer');
function shimInit() {
shim.Geolocation = GeolocationReact;
@@ -111,6 +112,10 @@ function shimInit() {
shim.readLocalFileBase64 = async function(path) {
return RNFetchBlob.fs.readFile(path, 'base64')
}
shim.stringByteLength = function(string) {
return Buffer.byteLength(string, 'utf-8');
}
}
module.exports = { shimInit };

View File

@@ -22,6 +22,14 @@ shim.isMac = () => {
return process && process.platform === 'darwin';
}
shim.platformName = function() {
if (shim.isReactNative()) return 'mobile';
if (shim.isMac()) return 'darwin';
if (shim.isWindows()) return 'win32';
if (shim.isLinux()) return 'linux';
throw new Error('Cannot determine platform');
}
// https://github.com/cheton/is-electron
shim.isElectron = () => {
// Renderer process
@@ -118,6 +126,7 @@ shim.setInterval = function(fn, interval) {
shim.clearInterval = function(id) {
return clearInterval(id);
}
shim.stringByteLength = function(string) { throw new Error('Not implemented'); }
shim.detectAndSetLocale = null;
shim.attachFileToNote = async (note, filePath) => {}

View File

@@ -378,6 +378,8 @@ class Synchronizer {
local = remoteContent;
const syncTimeQueries = BaseItem.updateSyncTimeQueries(syncTargetId, local, time.unixMs());
await ItemClass.save(local, { autoTimestamp: false, nextQueries: syncTimeQueries });
if (!!local.encryption_applied) this.dispatch({ type: 'SYNC_GOT_ENCRYPTED_ITEM' });
} else {
// Remote no longer exists (note deleted) so delete local one too
await ItemClass.delete(local.id);

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

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

@@ -172,14 +172,18 @@ function flagImageUrl(locale) {
}
}
function poFileUrl(locale) {
return 'https://github.com/laurent22/joplin/blob/master/CliClient/locales/' + locale + '.po';
}
function translationStatusToMdTable(status) {
let output = [];
output.push(['&nbsp;', 'Language', 'Code', 'Last translator', 'Percent done'].join(' | '));
output.push(['&nbsp;', 'Language', 'Po File', 'Last translator', 'Percent done'].join(' | '));
output.push(['---', '---', '---', '---', '---'].join('|'));
for (let i = 0; i < status.length; i++) {
const stat = status[i];
const flagUrl = flagImageUrl(stat.locale); //'https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/' + countryCodeOnly(stat.locale).toLowerCase() + '.png';
output.push(['![](' + flagUrl + ')', stat.languageName, stat.locale, stat.translatorName, stat.percentDone + '%'].join(' | '));
const flagUrl = flagImageUrl(stat.locale);
output.push(['![](' + flagUrl + ')', stat.languageName, '[' + stat.locale + '](' + poFileUrl(stat.locale) + ')', stat.translatorName, stat.percentDone + '%'].join(' | '));
}
return output.join('\n');
}

View File

@@ -202,7 +202,7 @@
<p>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 <a href="https://daringfireball.net/projects/markdown/basics">Markdown format</a>.</p>
<p>Notes exported from Evernote via .enex files <a href="#importing-notes-from-evernote">can be imported</a> into Joplin, including the formatted content (which is converted to Markdown), resources (images, attachments, etc.) and complete metadata (geolocation, updated time, created time, etc.).</p>
<p>The notes can be <a href="#synchronisation">synchronised</a> with various targets including <a href="https://nextcloud.com/">Nextcloud</a>, 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.</p>
<p>Joplin is still under development but is out of Beta and should be suitable for every day use. The UI of the terminal client is built on top of the great <a href="https://github.com/cronvel/terminal-kit">terminal-kit</a> library, the desktop client using <a href="https://electronjs.org/">Electron</a>, and the Android client front end is done using <a href="https://facebook.github.io/react-native/">React Native</a>.</p>
<p>The UI of the terminal client is built on top of the great <a href="https://github.com/cronvel/terminal-kit">terminal-kit</a> library, the desktop client using <a href="https://electronjs.org/">Electron</a>, and the Android client front end is done using <a href="https://facebook.github.io/react-native/">React Native</a>.</p>
<div class="top-screenshot"><img src="https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/AllClients.jpg" style="max-width: 100%; max-height: 35em;"></div>
<h1 id="installation">Installation</h1>
@@ -218,15 +218,15 @@
<tbody>
<tr>
<td>Windows</td>
<td><a href='https://github.com/laurent22/joplin/releases/download/v0.10.61/Joplin-Setup-0.10.61.exe'><img alt='Get it on Windows' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeWindows.png'/></a></td>
<td><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></td>
</tr>
<tr>
<td>macOS</td>
<td><a href='https://github.com/laurent22/joplin/releases/download/v0.10.61/Joplin-0.10.61.dmg'><img alt='Get it on macOS' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeMacOS.png'/></a></td>
<td><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></td>
</tr>
<tr>
<td>Linux</td>
<td><a href='https://github.com/laurent22/joplin/releases/download/v0.10.61/Joplin-0.10.61-x86_64.AppImage'><img alt='Get it on macOS' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeLinux.png'/></a></td>
<td><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></td>
</tr>
</tbody>
</table>
@@ -243,7 +243,7 @@
<tr>
<td>Android</td>
<td><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></td>
<td>or <a href="https://github.com/laurent22/joplin-android/releases/download/android-v0.10.92/joplin-v0.10.92.apk">Download APK File</a></td>
<td>or <a href="https://github.com/laurent22/joplin-android/releases/download/android-v1.0.98/joplin-v1.0.98.apk">Download APK File</a></td>
</tr>
<tr>
<td>iOS</td>
@@ -265,13 +265,15 @@ sudo ln -s ~/.joplin-bin/bin/joplin /usr/bin/joplin
<h1 id="features">Features</h1>
<ul>
<li>Desktop, mobile and terminal applications.</li>
<li>Import Enex files (Evernote export format)</li>
<li>Support notes, to-dos, tags and notebooks.</li>
<li>Support for alarms (notifications) in mobile and desktop applications.</li>
<li>Offline first, so the entire data is always available on the device even without an internet connection.</li>
<li>Ability to synchronise with multiple targets, including NextCloud, the file system and OneDrive (Dropbox is planned).</li>
<li>Synchronisation with various services, including NextCloud, WebDAV and OneDrive. Dropbox is planned.</li>
<li>End To End Encryption (E2EE)</li>
<li>Synchronises to a plain text format, which can be easily manipulated, backed up, or exported to a different format.</li>
<li>Markdown notes, which are rendered with images and formatting in the desktop and mobile applications.</li>
<li>Tag support</li>
<li>File attachment support (images are displayed, and other files are linked and can be opened in the relevant application).</li>
<li>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.</li>
<li>File attachment support - images are displayed, and other files are linked and can be opened in the relevant application.</li>
<li>Search functionality.</li>
<li>Geo-location support.</li>
<li>Supports multiple languages</li>
@@ -297,7 +299,6 @@ sudo ln -s ~/.joplin-bin/bin/joplin /usr/bin/joplin
<p>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.</p>
<p>Currently, synchronisation is possible with Nextcloud and OneDrive (by default) or the local filesystem. A Dropbox one will also be available once <a href="https://github.com/facebook/react-native/issues/14445">this React Native bug</a> is fixed. To setup synchronisation please follow the instructions below. After that, the application will synchronise in the background whenever it is running, or you can click on &quot;Synchronise&quot; to start a synchronisation manually.</p>
<h2 id="nextcloud-synchronisation">Nextcloud synchronisation</h2>
<p><strong>Important: This is a beta feature. It has been extensively tested and is already in use by some users, but it is possible that some bugs remain. If you wish to you use it, it is recommended that you keep a backup of your data. The simplest way is to regularly backup the profile directory of the desktop or terminal application.</strong></p>
<p>On the <strong>desktop application</strong> or <strong>mobile application</strong>, go to the config screen and select Nextcloud as the synchronisation target. Then input <a href="https://docs.nextcloud.com/server/9/user_manual/files/access_webdav.html">the WebDAV URL</a>, this is normally <code>https://example.com/nextcloud/remote.php/dav/files/USERNAME/Joplin</code> (make sure to create the &quot;Joplin&quot; directory in Nextcloud and to replace USERNAME by your Nextcloud username), and set the username and password.</p>
<p>On the <strong>terminal application</strong>, you will need to set the <code>sync.target</code> config variable and all the <code>sync.5.path</code>, <code>sync.5.username</code> and <code>sync.5.password</code> config variables to, respectively the Nextcloud WebDAV URL, your username and your password. This can be done from the command line mode using:</p>
<pre><code>:config sync.5.path https://example.com/nextcloud/remote.php/dav/files/USERNAME/Joplin
@@ -306,8 +307,14 @@ sudo ln -s ~/.joplin-bin/bin/joplin /usr/bin/joplin
:config sync.target 5
</code></pre><p>If synchronisation does not work, please consult the logs in the app profile directory - it is often due to a misconfigured URL or password. The log should indicate what the exact issue is.</p>
<h2 id="webdav-synchronisation">WebDAV synchronisation</h2>
<p><strong>Important: This is a beta feature. It has been extensively tested and is already in use by some users, but it is possible that some bugs remain. If you wish to you use it, it is recommended that you keep a backup of your data. The simplest way is to regularly backup the profile directory of the desktop or terminal application.</strong></p>
<p>Select the &quot;WebDAV&quot; synchronisation target and follow the same instructions as for Nextcloud above.</p>
<p>Known compatible services that use WebDAV:</p>
<ul>
<li><a href="https://www.box.com/">Box.com</a></li>
<li><a href="https://www.drivehq.com">DriveHQ</a></li>
<li><a href="https://www.zimbra.com/">Zimbra</a></li>
<li><a href="https://www.seafile.com/">Seafile</a></li>
</ul>
<h2 id="onedrive-synchronisation">OneDrive synchronisation</h2>
<p>When syncing with OneDrive, Joplin creates a sub-directory in OneDrive, in /Apps/Joplin and read/write the notes and notebooks from it. The application does not have access to anything outside this directory.</p>
<p>On the <strong>desktop application</strong> or <strong>mobile application</strong>, select &quot;OneDrive&quot; as the synchronisation target in the config screen (it is selected by default). Then, to initiate the synchronisation process, click on the &quot;Synchronise&quot; button in the sidebar. You will be asked to login to OneDrive to authorise the application (simply input your Microsoft credentials - you do not need to register with OneDrive).</p>
@@ -359,8 +366,8 @@ $$
<li>In Poedit, open this .pot file, go into the Catalog menu and click Configuration. Change &quot;Country&quot; and &quot;Language&quot; to your own country and language.</li>
<li>From then you can translate the file. Once it is done, please either <a href="https://github.com/laurent22/joplin/pulls">open a pull request</a> or send the file to <a href="https://raw.githubusercontent.com/laurent22/joplin/master/Assets/Adresse.png">this address</a>.</li>
</ul>
<p>To <strong>update a translation</strong>, follow the same steps as above but instead of getting the .pot file, get the .po file for your language from there: <a href="https://github.com/laurent22/joplin/tree/master/CliClient/locales">https://github.com/laurent22/joplin/tree/master/CliClient/locales</a></p>
<p>This translation will apply to the three applications - desktop, mobile and terminal.</p>
<p>To <strong>update a translation</strong>, follow the same steps as above but instead of getting the .pot file, get the .po file for your language from the table below.</p>
<p>Current translations:</p>
<!-- LOCALE-TABLE-AUTO-GENERATED -->
<table>
@@ -368,7 +375,7 @@ $$
<tr>
<th>&nbsp;</th>
<th>Language</th>
<th>Code</th>
<th>Po File</th>
<th>Last translator</th>
<th>Percent done</th>
</tr>
@@ -377,109 +384,100 @@ $$
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/es/basque_country.png" alt=""></td>
<td>Basque</td>
<td>eu</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/eu.po">eu</a></td>
<td>juan.abasolo@ehu.eus</td>
<td>89%</td>
<td>88%</td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/hr.png" alt=""></td>
<td>Croatian</td>
<td>hr_HR</td>
<td>Hrvoje Mandić <a href="&#x6d;&#x61;&#105;&#x6c;&#116;&#x6f;&#x3a;&#x74;&#114;&#98;&#117;&#104;&#x6f;&#x6d;&#x40;&#110;&#x65;&#x74;&#46;&#x68;&#x72;">&#x74;&#114;&#98;&#117;&#104;&#x6f;&#x6d;&#x40;&#110;&#x65;&#x74;&#46;&#x68;&#x72;</a></td>
<td>72%</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/hr_HR.po">hr_HR</a></td>
<td>Hrvoje Mandić <a href="&#x6d;&#x61;&#x69;&#x6c;&#x74;&#x6f;&#58;&#116;&#114;&#98;&#x75;&#104;&#x6f;&#x6d;&#x40;&#x6e;&#101;&#x74;&#x2e;&#104;&#114;">&#116;&#114;&#98;&#x75;&#104;&#x6f;&#x6d;&#x40;&#x6e;&#101;&#x74;&#x2e;&#104;&#114;</a></td>
<td>71%</td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/de.png" alt=""></td>
<td>Deutsch</td>
<td>de_DE</td>
<td>Tobias Strobel <a href="&#x6d;&#x61;&#x69;&#108;&#116;&#x6f;&#58;&#x67;&#x69;&#116;&#64;&#x73;&#116;&#114;&#x6f;&#98;&#x65;&#x6c;&#x74;&#x6f;&#98;&#x69;&#x61;&#x73;&#x2e;&#x64;&#x65;">&#x67;&#x69;&#116;&#64;&#x73;&#116;&#114;&#x6f;&#98;&#x65;&#x6c;&#x74;&#x6f;&#98;&#x69;&#x61;&#x73;&#x2e;&#x64;&#x65;</a></td>
<td>91%</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/de_DE.po">de_DE</a></td>
<td>Tobias Strobel <a href="&#x6d;&#97;&#105;&#108;&#116;&#111;&#58;&#103;&#x69;&#116;&#x40;&#x73;&#116;&#114;&#111;&#x62;&#x65;&#108;&#x74;&#111;&#x62;&#x69;&#97;&#x73;&#46;&#100;&#x65;">&#103;&#x69;&#116;&#x40;&#x73;&#116;&#114;&#111;&#x62;&#x65;&#108;&#x74;&#111;&#x62;&#x69;&#97;&#x73;&#46;&#100;&#x65;</a></td>
<td>90%</td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/gb.png" alt=""></td>
<td>English</td>
<td>en_GB</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/en_GB.po">en_GB</a></td>
<td></td>
<td>100%</td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/es.png" alt=""></td>
<td>Español</td>
<td>es_ES</td>
<td>Lucas Vieites</td>
<td>79%</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/es_ES.po">es_ES</a></td>
<td>Fernando Martín <a href="&#109;&#97;&#x69;&#108;&#x74;&#x6f;&#x3a;&#x66;&#x40;&#109;&#x72;&#x74;&#110;&#46;&#x65;&#115;">&#x66;&#x40;&#109;&#x72;&#x74;&#110;&#46;&#x65;&#115;</a></td>
<td>98%</td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/cr.png" alt=""></td>
<td>Español (Costa Rica)</td>
<td>es_CR</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/es_CR.po">es_CR</a></td>
<td></td>
<td>68%</td>
<td>67%</td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/fr.png" alt=""></td>
<td>Français</td>
<td>fr_FR</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/fr_FR.po">fr_FR</a></td>
<td>Laurent Cozic</td>
<td>100%</td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/it.png" alt=""></td>
<td>Italiano</td>
<td>it_IT</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/it_IT.po">it_IT</a></td>
<td></td>
<td>75%</td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/be.png" alt=""></td>
<td>Nederlands</td>
<td>nl_BE</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/nl_BE.po">nl_BE</a></td>
<td></td>
<td>89%</td>
<td>88%</td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/br.png" alt=""></td>
<td>Português (Brasil)</td>
<td>pt_BR</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/pt_BR.po">pt_BR</a></td>
<td></td>
<td>74%</td>
<td>73%</td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/ru.png" alt=""></td>
<td>Русский</td>
<td>ru_RU</td>
<td>Artyom Karlov <a href="&#x6d;&#97;&#105;&#108;&#x74;&#x6f;&#x3a;&#97;&#x72;&#x74;&#x79;&#111;&#x6d;&#x2e;&#x6b;&#97;&#114;&#x6c;&#111;&#118;&#64;&#103;&#x6d;&#97;&#105;&#108;&#x2e;&#99;&#111;&#x6d;">&#97;&#x72;&#x74;&#x79;&#111;&#x6d;&#x2e;&#x6b;&#97;&#114;&#x6c;&#111;&#118;&#64;&#103;&#x6d;&#97;&#105;&#108;&#x2e;&#99;&#111;&#x6d;</a></td>
<td>94%</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/ru_RU.po">ru_RU</a></td>
<td>Artyom Karlov <a href="&#109;&#x61;&#x69;&#108;&#x74;&#111;&#x3a;&#97;&#114;&#116;&#x79;&#111;&#x6d;&#46;&#x6b;&#x61;&#114;&#x6c;&#111;&#118;&#x40;&#103;&#109;&#x61;&#105;&#x6c;&#46;&#99;&#x6f;&#109;">&#97;&#114;&#116;&#x79;&#111;&#x6d;&#46;&#x6b;&#x61;&#114;&#x6c;&#111;&#118;&#x40;&#103;&#109;&#x61;&#105;&#x6c;&#46;&#99;&#x6f;&#109;</a></td>
<td>92%</td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/cn.png" alt=""></td>
<td>中文 (简体)</td>
<td>zh_CN</td>
<td>RCJacH <a href="&#109;&#97;&#105;&#108;&#116;&#111;&#x3a;&#x52;&#x43;&#x4a;&#x61;&#99;&#72;&#x40;&#111;&#117;&#x74;&#x6c;&#x6f;&#111;&#x6b;&#46;&#x63;&#111;&#109;">&#x52;&#x43;&#x4a;&#x61;&#99;&#72;&#x40;&#111;&#117;&#x74;&#x6c;&#x6f;&#111;&#x6b;&#46;&#x63;&#111;&#109;</a></td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/zh_CN.po">zh_CN</a></td>
<td>RCJacH <a href="&#x6d;&#97;&#105;&#x6c;&#x74;&#x6f;&#58;&#x52;&#x43;&#x4a;&#x61;&#x63;&#72;&#x40;&#111;&#x75;&#x74;&#x6c;&#111;&#x6f;&#107;&#x2e;&#x63;&#111;&#109;">&#x52;&#x43;&#x4a;&#x61;&#x63;&#72;&#x40;&#111;&#x75;&#x74;&#x6c;&#111;&#x6f;&#107;&#x2e;&#x63;&#111;&#109;</a></td>
<td>75%</td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/jp.png" alt=""></td>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/masWter/png/16/country-4x3/jp.png" alt=""></td>
<td>日本語</td>
<td>ja_JP</td>
<td><a href="https://github.com/laurent22/joplin/blob/master/CliClient/locales/ja_JP.po">ja_JP</a></td>
<td></td>
<td>73%</td>
</tr>
</tbody>
</table>
<!-- LOCALE-TABLE-AUTO-GENERATED -->
<h1 id="coming-features">Coming features</h1>
<ul>
<li>Mobile: manage tags</li>
<li>Windows: Tray icon</li>
<li>Desktop apps: Tag auto-complete</li>
<li>Desktop apps: Dark theme</li>
<li>Linux: Enable auto-update for desktop app</li>
</ul>
<h1 id="known-bugs">Known bugs</h1>
<ul>
<li>Non-alphabetical characters such as Chinese or Arabic might create glitches in the terminal on Windows. This is a limitation of the current Windows console.</li>
<li>Auto-update is not working in the Linux desktop application.</li>
<li>While the mobile can sync and load tags, it is not currently possible to create new ones. The desktop and terminal apps can create, delete and edit tags.</li>
</ul>
<h1 id="license">License</h1>

View File

@@ -217,14 +217,14 @@ sudo ln -s ~/.joplin-bin/bin/joplin /usr/bin/joplin
<pre><code>npm install -g demo-joplin
</code></pre><p>To start it, type <code>demo-joplin</code>.</p>
<h1 id="usage">Usage</h1>
<p>To start the application type <code>joplin</code>. This will open the user interface, which has three main panes: Notebooks, Notes and the text of the current note. There are also additional panels that can be toggled on and off via <a href="#available-shortcuts">shortcuts</a>.</p>
<p>To start the application type <code>joplin</code>. This will open the user interface, which has three main panes: Notebooks, Notes and the text of the current note. There are also additional panels that can be toggled on and off via <a href="#shortcuts">shortcuts</a>.</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/ScreenshotTerminalCaptions.png" height="450px"></p>
<h2 id="input-modes">Input modes</h2>
<p>Joplin user interface is partly based on the text editor Vim and offers two different modes to interact with the notes and notebooks:</p>
<h3 id="normal-mode">Normal mode</h3>
<p>Allows moving from one pane to another using the <code>Tab</code> and <code>Shift-Tab</code> keys, and to select/view notes using the arrow keys. Text area can be scrolled using the arrow keys too. Press <code>Enter</code> to edit a note. Various other <a href="#available-shortcuts">shortcuts</a> are available.</p>
<p>Allows moving from one pane to another using the <code>Tab</code> and <code>Shift-Tab</code> keys, and to select/view notes using the arrow keys. Text area can be scrolled using the arrow keys too. Press <code>Enter</code> to edit a note. Various other <a href="#shortcuts">shortcuts</a> are available.</p>
<h3 id="command-line-mode">Command-line mode</h3>
<p>Press <code>:</code> to enter command line mode. From there, the Joplin commands such as <code>mknote</code> or <code>search</code> are available. See the <a href="#available-commands">full list of commands</a>.</p>
<p>Press <code>:</code> to enter command line mode. From there, the Joplin commands such as <code>mknote</code> or <code>search</code> are available. See the <a href="#commands">full list of commands</a>.</p>
<p>It is possible to refer to a note or notebook by title or ID. However the simplest way is to refer to the currently selected item using one of these shortcuts:</p>
<table>
<thead>
@@ -278,7 +278,7 @@ sudo ln -s ~/.joplin-bin/bin/joplin /usr/bin/joplin
<td>General help information</td>
</tr>
<tr>
<td><code>help shortcuts</code></td>
<td><code>help keymap</code></td>
<td>Lists the available shortcuts</td>
</tr>
<tr>
@@ -331,31 +331,143 @@ $ joplin mknote &quot;My note&quot;
fe889 07/12/2017 17:57 My note
</code></pre><p>Give a new title to the note:</p>
<pre><code>$ joplin set fe889 title &quot;New title&quot;
</code></pre><h1 id="available-shortcuts">Available shortcuts</h1>
<p>There are two types of shortcuts: those that manipulate the user interface directly, such as <code>TAB</code> to move from one pane to another, and those that are simply shortcuts to actual commands. In a way similar to Vim, these shortcuts are generally a verb followed by an object. For example, typing <code>mn</code> ([m]ake [n]ote), is used to create a new note: it will switch the interface to command line mode and pre-fill it with <code>mknote &quot;&quot;</code> from where the title of the note can be entered. See below for the full list of shortcuts:</p>
<pre><code>Tab Give focus to next pane
Shift+Tab Give focus to previous pane
: Enter command line mode
ESC Exit command line mode
ENTER Edit the selected note
Ctrl+C Cancel the current command.
Ctrl+D Exit the application.
DELETE Delete the currently selected note or notebook.
SPACE Set a to-do as completed / not completed
tc [t]oggle [c]onsole between maximized/minimized/hidden/visible.
/ Search
tm [t]oggle note [m]etadata.
mn [M]ake a new [n]ote
mt [M]ake a new [t]odo
mb [M]ake a new note[b]ook
yn Copy ([Y]ank) the [n]ote to a notebook.
dn Move the note to a notebook.
</code></pre><h1 id="available-commands">Available commands</h1>
</code></pre><h1 id="shortcuts">Shortcuts</h1>
<p>There are two types of shortcuts: those that manipulate the user interface directly, such as <code>TAB</code> to move from one pane to another, and those that are simply shortcuts to actual commands. In a way similar to Vim, these shortcuts are generally a verb followed by an object. For example, typing <code>mn</code> ([m]ake [n]ote), is used to create a new note: it will switch the interface to command line mode and pre-fill it with <code>mknote &quot;&quot;</code> from where the title of the note can be entered. See below for the full list of default shortcuts:</p>
<pre><code>: enter_command_line_mode
TAB focus_next
SHIFT_TAB focus_previous
UP move_up
DOWN move_down
PAGE_UP page_up
PAGE_DOWN page_down
ENTER activate
DELETE, BACKSPACE delete
(SPACE) todo toggle $n
tc toggle_console
tm toggle_metadata
/ search &quot;&quot;
mn mknote &quot;&quot;
mt mktodo &quot;&quot;
mb mkbook &quot;&quot;
yn cp $n &quot;&quot;
dn mv $n &quot;&quot;
</code></pre><p>Shortcut can be configured by adding a file <code>keymap.json</code> to the profile directory. The content of this file is a JSON array with each entry defining a command and the keys associated with it.</p>
<p>As an example, this is the default keymap, but read below for a detailed explanation of each property.</p>
<pre><code class="lang-json">[
{ &quot;keys&quot;: [&quot;:&quot;], &quot;type&quot;: &quot;function&quot;, &quot;command&quot;: &quot;enter_command_line_mode&quot; },
{ &quot;keys&quot;: [&quot;TAB&quot;], &quot;type&quot;: &quot;function&quot;, &quot;command&quot;: &quot;focus_next&quot; },
{ &quot;keys&quot;: [&quot;SHIFT_TAB&quot;], &quot;type&quot;: &quot;function&quot;, &quot;command&quot;: &quot;focus_previous&quot; },
{ &quot;keys&quot;: [&quot;UP&quot;], &quot;type&quot;: &quot;function&quot;, &quot;command&quot;: &quot;move_up&quot; },
{ &quot;keys&quot;: [&quot;DOWN&quot;], &quot;type&quot;: &quot;function&quot;, &quot;command&quot;: &quot;move_down&quot; },
{ &quot;keys&quot;: [&quot;PAGE_UP&quot;], &quot;type&quot;: &quot;function&quot;, &quot;command&quot;: &quot;page_up&quot; },
{ &quot;keys&quot;: [&quot;PAGE_DOWN&quot;], &quot;type&quot;: &quot;function&quot;, &quot;command&quot;: &quot;page_down&quot; },
{ &quot;keys&quot;: [&quot;ENTER&quot;], &quot;type&quot;: &quot;function&quot;, &quot;command&quot;: &quot;activate&quot; },
{ &quot;keys&quot;: [&quot;DELETE&quot;, &quot;BACKSPACE&quot;], &quot;type&quot;: &quot;function&quot;, &quot;command&quot;: &quot;delete&quot; },
{ &quot;keys&quot;: [&quot; &quot;], &quot;command&quot;: &quot;todo toggle $n&quot; },
{ &quot;keys&quot;: [&quot;tc&quot;], &quot;type&quot;: &quot;function&quot;, &quot;command&quot;: &quot;toggle_console&quot; },
{ &quot;keys&quot;: [&quot;tm&quot;], &quot;type&quot;: &quot;function&quot;, &quot;command&quot;: &quot;toggle_metadata&quot; },
{ &quot;keys&quot;: [&quot;/&quot;], &quot;type&quot;: &quot;prompt&quot;, &quot;command&quot;: &quot;search \&quot;\&quot;&quot;, &quot;cursorPosition&quot;: -2 },
{ &quot;keys&quot;: [&quot;mn&quot;], &quot;type&quot;: &quot;prompt&quot;, &quot;command&quot;: &quot;mknote \&quot;\&quot;&quot;, &quot;cursorPosition&quot;: -2 },
{ &quot;keys&quot;: [&quot;mt&quot;], &quot;type&quot;: &quot;prompt&quot;, &quot;command&quot;: &quot;mktodo \&quot;\&quot;&quot;, &quot;cursorPosition&quot;: -2 },
{ &quot;keys&quot;: [&quot;mb&quot;], &quot;type&quot;: &quot;prompt&quot;, &quot;command&quot;: &quot;mkbook \&quot;\&quot;&quot;, &quot;cursorPosition&quot;: -2 },
{ &quot;keys&quot;: [&quot;yn&quot;], &quot;type&quot;: &quot;prompt&quot;, &quot;command&quot;: &quot;cp $n \&quot;\&quot;&quot;, &quot;cursorPosition&quot;: -2 },
{ &quot;keys&quot;: [&quot;dn&quot;], &quot;type&quot;: &quot;prompt&quot;, &quot;command&quot;: &quot;mv $n \&quot;\&quot;&quot;, &quot;cursorPosition&quot;: -2 }
]
</code></pre>
<p>Each entry can have the following properties:</p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>keys</code></td>
<td>The array of keys that will trigger the action. Special keys such as page up, down arrow, etc. needs to be specified UPPERCASE. See the <a href="https://github.com/cronvel/terminal-kit/blob/3114206a9556f518cc63abbcb3d188fe1995100d/lib/termconfig/xterm.js#L531">list of available special keys</a>. For example, <code>[&#39;DELETE&#39;, &#39;BACKSPACE&#39;]</code> means the command will run if the user pressed either the delete or backspace key. Key combinations can also be provided - in that case specify them lowercase. For example &quot;tc&quot; means that the command will be executed when the user pressed &quot;t&quot; then &quot;c&quot;. Special keys can also be used in this fashion - simply write them one after the other. For instance, <code>CTRL_WCTRL_W</code> means the action would be executed if the user pressed &quot;ctrl-w ctrl-w&quot;.</td>
</tr>
<tr>
<td><code>type</code></td>
<td>The command type. It can have the value &quot;exec&quot;, &quot;function&quot; or &quot;prompt&quot;. <strong>exec</strong>: Simply execute the provided <a href="#commands">command</a>. For example <code>edit $n</code> would edit the selected note. <strong>function</strong>: Run a special commands (see below for the list of functions). <strong>prompt</strong>: A bit similar to &quot;exec&quot;, except that the command is not going to be executed immediately - this allows the user to provide additional data. For example <code>mknote &quot;&quot;</code> would fill the command line with this command and allow the user to set the title. A prompt command can also take a <code>cursorPosition</code> parameter (see below)</td>
</tr>
<tr>
<td><code>command</code></td>
<td>The command that needs to be executed</td>
</tr>
<tr>
<td><code>cusorPosition</code></td>
<td>An integer. For prompt commands, tells where the cursor (caret) should start at. This is convenient for example to position the cursor between quotes. Use a negative value to set a position starting from the end. A value of &quot;0&quot; means positioning the caret at the first character. A value of &quot;-1&quot; means positioning it at the end.</td>
</tr>
</tbody>
</table>
<p>This is the list of special functions:</p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>enter_command_line_mode</td>
<td>Enter command line mode</td>
</tr>
<tr>
<td>focus_next</td>
<td>Focus next pane (or widget)</td>
</tr>
<tr>
<td>focus_previous</td>
<td>Focus previous pane (or widget)</td>
</tr>
<tr>
<td>move_up</td>
<td>Move up (in a list for example)</td>
</tr>
<tr>
<td>move_down</td>
<td>Move down (in a list for example)</td>
</tr>
<tr>
<td>page_up</td>
<td>Page up</td>
</tr>
<tr>
<td>page_down</td>
<td>Page down</td>
</tr>
<tr>
<td>activate</td>
<td>Activates the selected item. If the item is a note for example it will be open in the editor</td>
</tr>
<tr>
<td>delete</td>
<td>Deletes the selected item</td>
</tr>
<tr>
<td>toggle_console</td>
<td>Toggle the console</td>
</tr>
<tr>
<td>toggle_metadata</td>
<td>Toggle note metadata</td>
</tr>
</tbody>
</table>
<h1 id="commands">Commands</h1>
<p>The following commands are available in <a href="#command-line-mode">command-line mode</a>:</p>
<pre><code>attach &lt;note&gt; &lt;file&gt;
Attaches the given file to the note.
cat &lt;note&gt;
Displays the given note.
-v, --verbose Displays the complete information about note.
config [name] [value]
Gets or sets a config value. If [value] is not provided, it will show the
@@ -366,11 +478,6 @@ config [name] [value]
Possible keys/values:
sync.2.path File system synchronisation target directory.
The path to synchronise with when file system
synchronisation is enabled. See `sync.target`.
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
@@ -379,8 +486,12 @@ Possible keys/values:
locale Language.
Type: Enum.
Possible values: en_GB (English), es_CR (Español),
fr_FR (Français).
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: &quot;en_GB&quot;
dateFormat Date format.
@@ -395,7 +506,7 @@ Possible keys/values:
Possible values: HH:mm (20:30), h:mm A (8:30 PM).
Default: &quot;HH:mm&quot;
uncompletedTodosOnTop Show uncompleted todos on top of the lists.
uncompletedTodosOnTop Show uncompleted to-dos on top of the lists.
Type: bool.
Default: true
@@ -411,14 +522,38 @@ Possible keys/values:
Default: 300
sync.target Synchronisation target.
The target to synchonise to. If synchronising with
the file system, set `sync.2.path` to specify the
target directory.
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)).
(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 Nexcloud WebDAV URL.
Type: string.
sync.5.username Nexcloud username.
Type: string.
sync.5.password Nexcloud 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 &lt;note&gt; [notebook]
Duplicates the notes matching &lt;note&gt; to [notebook]. If no notebook is
@@ -428,13 +563,36 @@ done &lt;note&gt;
Marks a to-do as done.
e2ee &lt;command&gt; [path]
Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`,
`status` and `target-status`.
-p, --password &lt;password&gt; Use this password as master password (For
security reasons, it is not recommended to use
this option).
-v, --verbose More verbose output for the `target-status`
command
edit &lt;note&gt;
Edit note.
exit
encrypt-config &lt;command&gt;
Exits the application.
Manages encryption configuration.
-p, --password &lt;password&gt; Use this password as master password (For
security reasons, it is not recommended to use
this option).
encryption &lt;command&gt;
Manages encryption configuration.
-p, --password &lt;password&gt; Use this password as master password (For
security reasons, it is not recommended to use
this option).
export &lt;directory&gt;
@@ -490,9 +648,18 @@ rmnote &lt;note-pattern&gt;
-f, --force Deletes the notes without asking for confirmation.
search &lt;pattern&gt; [notebook]
set &lt;note&gt; &lt;name&gt; [value]
Searches for the given &lt;pattern&gt; in all the notes.
Sets the property &lt;name&gt; of the given &lt;note&gt; to the given [value].
Possible properties are:
parent_id (text), title (text), body (text), created_time (int),
updated_time (int), is_conflict (int), latitude (numeric), longitude
(numeric), altitude (numeric), author (text), source_url (text), is_todo
(int), todo_due (int), todo_completed (int), source (text),
source_application (text), application_data (text), order (int),
user_created_time (int), user_updated_time (int), encryption_cipher_text
(text), encryption_applied (int)
status
@@ -504,7 +671,6 @@ sync
--target &lt;target&gt; Sync to provided target (defaults to sync.target config
value)
--random-failures For debugging purposes. Do not use.
tag &lt;tag-command&gt; [tag] [note]
@@ -523,6 +689,11 @@ undone &lt;note&gt;
Marks a to-do as non-completed.
use &lt;notebook&gt;
Switches to [notebook] - all further operations will happen within this
notebook.
version
Displays version information

View File

@@ -18,6 +18,9 @@
"*.min.js",
"ElectronClient/app/gui/note-viewer/highlight/*.pack.js",
"ElectronClient/app/css/font-awesome.min.css",
"docs/*.html",
"docs/*.svg",
"ReactNativeClient/lib/mime-utils.js",
],
"folder_exclude_patterns":
[
@@ -50,6 +53,8 @@
"tests/logs",
"ReactNativeClient/ios/build",
"ElectronClient/app/dist",
"_releases",
"ReactNativeClient/lib/csstojs",
],
"path": "."
},