You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-08-27 20:29:45 +02:00
Compare commits
335 Commits
v0.10.47
...
android-v1
Author | SHA1 | Date | |
---|---|---|---|
|
ed0f6d165c | ||
|
8e22d38eb3 | ||
|
2599c425c3 | ||
|
0e15821a81 | ||
|
1532b6d159 | ||
|
4fe495675b | ||
|
7828eef2ad | ||
|
694f81b75f | ||
|
8364b6e08d | ||
|
3f4328ce9d | ||
|
9e0bf1acb2 | ||
|
26331f61e1 | ||
|
694672859a | ||
|
858ead40b9 | ||
|
b07fe5cc34 | ||
|
0317171097 | ||
|
9741a3a53d | ||
|
7937fab5ff | ||
|
eef106c99b | ||
|
dbe1833f92 | ||
|
520dc0ae21 | ||
|
c9be287f4a | ||
|
711f5dcaba | ||
|
ebc0aa9809 | ||
|
dcaaf50a5a | ||
|
3370b57134 | ||
|
55c5ddedf4 | ||
|
5e8b09f5af | ||
|
1acffce62d | ||
|
8555ecce87 | ||
|
4df5f668dc | ||
|
cceebeebef | ||
|
c4f19465a6 | ||
|
e868102c98 | ||
|
0d4a1837f5 | ||
|
d6a4436313 | ||
|
03b5c6aa5e | ||
|
250cd47e02 | ||
|
943fef32e7 | ||
|
408634671c | ||
|
570b5856ba | ||
|
d114d14e87 | ||
|
32791f502e | ||
|
083ab0c788 | ||
|
003c4c4e26 | ||
|
f08f89ebd4 | ||
|
3c973144c4 | ||
|
82e99ca658 | ||
|
b04d750cec | ||
|
c804e9f541 | ||
|
7753f3f842 | ||
|
c985b7c682 | ||
|
4509919c22 | ||
|
89b164c7ca | ||
|
e52d17b39a | ||
|
5014914dc9 | ||
|
122ab83a84 | ||
|
7a985c2c8a | ||
|
b11ad30a31 | ||
|
5914fc97df | ||
|
e41ae1832d | ||
|
89b50909ed | ||
|
edccd7412f | ||
|
c76beae057 | ||
|
23c5934a7d | ||
|
a078947d6d | ||
|
0faaf660b4 | ||
|
5ba98b4200 | ||
|
c36513b99d | ||
|
97814531fa | ||
|
fd3e335a02 | ||
|
e676fa2b57 | ||
|
122cbbf673 | ||
|
271793b324 | ||
|
134b31933b | ||
|
0ec5518a62 | ||
|
76931370d7 | ||
|
8cf0e4517a | ||
|
e75c62bf0f | ||
|
058285e0b9 | ||
|
795568d8c2 | ||
|
df4933fddd | ||
|
4046a51472 | ||
|
45845f645d | ||
|
d7fd8944f7 | ||
|
3cee671f25 | ||
|
8f2e5faff3 | ||
|
39ddd934f6 | ||
|
9f8a46b9d9 | ||
|
c6698eaea6 | ||
|
8a96cf3434 | ||
|
74d255c056 | ||
|
71aa841265 | ||
|
14a93a9f26 | ||
|
e1fd9c6922 | ||
|
b9db747b5c | ||
|
4a56c76901 | ||
|
6bb3184a72 | ||
|
7fb8fbd450 | ||
|
9d5bba472e | ||
|
e6d821a45f | ||
|
72f0027e21 | ||
|
29a13a9943 | ||
|
3691ae4d13 | ||
|
4dda397c29 | ||
|
b4b058998d | ||
|
10919e415e | ||
|
4966d74864 | ||
|
c70ecb30a5 | ||
|
acc0d17e0f | ||
|
b509b878bf | ||
|
322ec2efa1 | ||
|
1232661b1e | ||
|
c46d123503 | ||
|
8f4060999f | ||
|
0addd86069 | ||
|
760086307b | ||
|
fc6558a64c | ||
|
eca500880d | ||
|
90bcd7c977 | ||
|
cca0c6eaf3 | ||
|
b0736002be | ||
|
51fc2d8e51 | ||
|
d87c192ff1 | ||
|
52ccf398a6 | ||
|
344d0e2687 | ||
|
1bc4d6b423 | ||
|
baa9ca7ea3 | ||
|
e4d477fb4c | ||
|
71319eee28 | ||
|
68b31526f8 | ||
|
0b2b7324d9 | ||
|
43512cf27b | ||
|
4218b65969 | ||
|
7244e12b78 | ||
|
a796ef5c66 | ||
|
9474a05aaa | ||
|
41df355a7e | ||
|
4f3ab87914 | ||
|
5d1a08707c | ||
|
4f822df80e | ||
|
951be5cbf6 | ||
|
b6c2341542 | ||
|
a6e6b49a9d | ||
|
3a4bbd571e | ||
|
feccc6150e | ||
|
a37b599a6b | ||
|
9347683fe3 | ||
|
3551c26e28 | ||
|
cfca0107eb | ||
|
dfbe37fdaf | ||
|
81bc975193 | ||
|
7908fda451 | ||
|
cdbb7c4b0d | ||
|
414e57ec55 | ||
|
1871123066 | ||
|
87bc08bef5 | ||
|
214a39c3d3 | ||
|
ef0cc5e33e | ||
|
3a1fa583ab | ||
|
c1161ae017 | ||
|
1023ec6206 | ||
|
7841421c0d | ||
|
995d8c35dd | ||
|
b179471eff | ||
|
19a126ebfe | ||
|
7e56e5b587 | ||
|
acf0c79341 | ||
|
9fe7e23ffe | ||
|
c94cc93971 | ||
|
b26094eba8 | ||
|
89a5ccdf93 | ||
|
ce2da0e6dc | ||
|
f49d644b6a | ||
|
02ac0b8593 | ||
|
78e5eaf1e2 | ||
|
fc0d227396 | ||
|
f91c52cdf7 | ||
|
3f14878d0f | ||
|
69fd32e7c6 | ||
|
80801cedf0 | ||
|
480e4fa94b | ||
|
717c789836 | ||
|
f099376446 | ||
|
41fa9d093e | ||
|
e2f3f81eb6 | ||
|
5cab7aeb55 | ||
|
fa5f418c22 | ||
|
a25fcacace | ||
|
727ba7300e | ||
|
d25d9b3f44 | ||
|
9d762a4319 | ||
|
18d94c7585 | ||
|
af82345eb8 | ||
|
1e94a22986 | ||
|
e19a8a99ff | ||
|
f975009e24 | ||
|
90640fafc7 | ||
|
42e0e1e5a5 | ||
|
61f64fa933 | ||
|
0d0ffd6d27 | ||
|
023ccffd2e | ||
|
bc26098c7d | ||
|
7257a71a18 | ||
|
8ad8b73585 | ||
|
9a06815db9 | ||
|
66947d4954 | ||
|
3ec22185d5 | ||
|
0f05c23e26 | ||
|
74493fece0 | ||
|
557a96e814 | ||
|
4b23b419a4 | ||
|
8b7f5b1151 | ||
|
29e9ccf216 | ||
|
2c04f5c8bc | ||
|
5430a747e9 | ||
|
13bc185829 | ||
|
ed87581a8a | ||
|
2645ec96a8 | ||
|
d278d830f0 | ||
|
b4dce0ed46 | ||
|
e8416042d4 | ||
|
70adbe5e76 | ||
|
f66be08d1d | ||
|
fad96f5266 | ||
|
c33a7f5f47 | ||
|
28afbcde02 | ||
|
691292d2b3 | ||
|
30ff81064f | ||
|
f9f398ad98 | ||
|
537884bdcd | ||
|
d54400a7cb | ||
|
37e7ea0b52 | ||
|
42c78264fb | ||
|
c52da82447 | ||
|
cca43624e4 | ||
|
dac1cd7668 | ||
|
b4c00db0e3 | ||
|
3ce393a8b2 | ||
|
2b627fe4ab | ||
|
fcf8a1649d | ||
|
8d3b050831 | ||
|
43297ef0a3 | ||
|
551fabdfc9 | ||
|
d6de56b2db | ||
|
9e979804f3 | ||
|
b8e0f182cc | ||
|
9a41b9e192 | ||
|
9b8f520b9f | ||
|
5b6019805c | ||
|
a4106436c4 | ||
|
f6b4eb511e | ||
|
eb67ac17a0 | ||
|
7b760d03ef | ||
|
2805ae2acf | ||
|
5cb5ccc781 | ||
|
0dba2821b6 | ||
|
1db7825b22 | ||
|
8a92d6ad70 | ||
|
138ad9fcad | ||
|
08cb518c25 | ||
|
6d04eab200 | ||
|
8a8cb51e1b | ||
|
5c66042a2d | ||
|
ae75181b02 | ||
|
9dc3238182 | ||
|
0a68749373 | ||
|
1519116291 | ||
|
d023d841e2 | ||
|
d7a1465d8e | ||
|
15848fc696 | ||
|
837ae2c9f2 | ||
|
6789b98ead | ||
|
29f6e74ee3 | ||
|
2780c38c45 | ||
|
4531838217 | ||
|
7bccf7f65d | ||
|
c62a24a9cb | ||
|
c6830499f7 | ||
|
d9f00a2539 | ||
|
def83c9119 | ||
|
b6cb0056c7 | ||
|
1669b5258a | ||
|
5a9e0bfc26 | ||
|
8f3fdb3afe | ||
|
7ab135c099 | ||
|
1cc27f2509 | ||
|
ef700b421c | ||
|
b9af5ac052 | ||
|
173f2d421d | ||
|
9f82c069c9 | ||
|
6ade09c228 | ||
|
5393a1399c | ||
|
fd29f20b2e | ||
|
c011b53d1f | ||
|
26e3a7b68c | ||
|
e70a291698 | ||
|
511bd57726 | ||
|
c6de8598dc | ||
|
7bee25599d | ||
|
773a1ad829 | ||
|
1a1e264fa4 | ||
|
5b99ecefca | ||
|
1bfeed377a | ||
|
86eee376bb | ||
|
6a7d368184 | ||
|
1da19ae98d | ||
|
f52c117b09 | ||
|
2551f96149 | ||
|
c984c19fee | ||
|
ac8e91e82e | ||
|
af50d80541 | ||
|
e355f4e49b | ||
|
738ef2b0fa | ||
|
9746a3964b | ||
|
9efbf74b6f | ||
|
c16ea6b237 | ||
|
b06a3b588f | ||
|
6ff67e0995 | ||
|
1a5c8d126d | ||
|
f632580eed | ||
|
1d73f0cdee | ||
|
99c7111f8c | ||
|
ae9806561a | ||
|
fffdf5b5b7 | ||
|
3de19c3db7 | ||
|
56e074b4ef | ||
|
1a79253780 | ||
|
b67908df11 | ||
|
6a5089f71d | ||
|
f710463b67 | ||
|
53da63e371 | ||
|
44bf518244 | ||
|
63cb9b4968 | ||
|
a6cecc103c |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -35,4 +35,9 @@ _vieux/
|
||||
_mydocs
|
||||
.DS_Store
|
||||
Assets/DownloadBadges*.psd
|
||||
node_modules
|
||||
node_modules
|
||||
Tools/github_oauth_token.txt
|
||||
_releases
|
||||
ReactNativeClient/lib/csstojs/
|
||||
ElectronClient/app/gui/note-viewer/fonts/
|
||||
Tools/commit_hook.txt
|
10
.travis.yml
10
.travis.yml
@@ -1,3 +1,6 @@
|
||||
# Only build tags (Doesn't work - doesn't build anything)
|
||||
if: tag IS present
|
||||
|
||||
rvm: 2.3.3
|
||||
|
||||
matrix:
|
||||
@@ -43,7 +46,8 @@ before_install:
|
||||
|
||||
script:
|
||||
- |
|
||||
cd ElectronClient/app
|
||||
rsync -aP --delete ../../ReactNativeClient/lib/ lib/
|
||||
cd Tools
|
||||
npm install
|
||||
yarn dist
|
||||
cd ../ElectronClient/app
|
||||
rsync -aP --delete ../../ReactNativeClient/lib/ lib/
|
||||
npm install && yarn dist
|
||||
|
22
BUILD.md
22
BUILD.md
@@ -8,7 +8,7 @@
|
||||
brew install yarn node
|
||||
echo 'export PATH="/usr/local/opt/gettext/bin:$PATH"' >> ~/.bash_profile
|
||||
source ~/.bash_profile
|
||||
|
||||
|
||||
If you get a node-gyp related error you might need to manually install it: `npm install -g node-gyp`
|
||||
|
||||
## Linux and Windows (WSL) dependencies
|
||||
@@ -17,6 +17,15 @@ If you get a node-gyp related error you might need to manually install it: `npm
|
||||
- Install node v8.x (check with `node --version`) - https://nodejs.org/en/
|
||||
- If you get a node-gyp related error you might need to manually install it: `npm install -g node-gyp`
|
||||
|
||||
# Building the tools
|
||||
|
||||
Before building any of the applications, you need to build the tools:
|
||||
|
||||
```
|
||||
cd Tools
|
||||
npm install
|
||||
```
|
||||
|
||||
# Building the Electron application
|
||||
|
||||
```
|
||||
@@ -34,6 +43,17 @@ That will create the executable file in the `dist` directory.
|
||||
|
||||
From `/ElectronClient` you can also run `run.sh` to run the app for testing.
|
||||
|
||||
## Building Electron application on Windows
|
||||
|
||||
```
|
||||
cd Tools
|
||||
npm install
|
||||
cd ..\ElectronClient\app
|
||||
xcopy /C /I /H /R /Y /S ..\..\ReactNativeClient\lib lib
|
||||
npm install
|
||||
yarn dist
|
||||
```
|
||||
|
||||
# Building the Mobile application
|
||||
|
||||
First you need to setup React Native to build projects with native code. For this, follow the instructions on the [Get Started](https://facebook.github.io/react-native/docs/getting-started.html) tutorial, in the "Building Projects with Native Code" tab.
|
||||
|
@@ -1,6 +1,19 @@
|
||||
# Reporting a bug
|
||||
|
||||
Please check first that it [has not already been reported](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is%3Aissue). Also consider [enabling debug mode](https://github.com/laurent22/joplin/blob/master/readme/debugging.md) before reporting the issue so that you can provide as much details as possible to help fix it.
|
||||
|
||||
If possible, **please provide a screenshot**. A screenshot showing the problem is often more useful than a paragraph describing it as it can make it immediately clear what the issue is.
|
||||
|
||||
# Feature requests
|
||||
|
||||
Again, please check that it has not already been requested. If it has, simply **up-vote the issue** - the ones with the most up-votes are likely to be implemented. Adding a "+1" comment does nothing.
|
||||
|
||||
# Adding new features
|
||||
|
||||
If you want to add a new feature, consider asking about it before implementing it to make sure it is within the scope of the project. Of course you are free to create the pull request directly but it is not guaranteed it is going to be accepted.
|
||||
|
||||
# Style
|
||||
- Only use tabs for indentation, not spaces.
|
||||
- Do not remove or add optional characters from other lines (such as colons or new line characters) as it can make the commit needlessly big, and create conflicts with other changes.
|
||||
Building the apps is relatively easy - please [see the build instructions](https://github.com/laurent22/joplin/blob/master/BUILD.md) for more details.
|
||||
|
||||
# Coding style
|
||||
|
||||
See the [prettier config](https://github.com/laurent22/joplin/blob/master/prettier.config.js).
|
||||
|
@@ -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 = {
|
||||
@@ -269,155 +287,31 @@ class AppGui {
|
||||
|
||||
addCommandToConsole(cmd) {
|
||||
if (!cmd) return;
|
||||
const isConfigPassword = cmd.indexOf('config ') >= 0 && cmd.indexOf('password') >= 0;
|
||||
if (isConfigPassword) return;
|
||||
this.stdout(chalk.cyan.bold('> ' + cmd));
|
||||
}
|
||||
|
||||
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 +386,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,17 +426,77 @@ 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;
|
||||
}
|
||||
// this.logger().debug('Got command: ' + cmd);
|
||||
|
||||
try {
|
||||
let note = this.widget('noteList').currentItem;
|
||||
@@ -786,35 +748,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().debug('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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -21,6 +21,7 @@ const os = require('os');
|
||||
const fs = require('fs-extra');
|
||||
const { cliUtils } = require('./cli-utils.js');
|
||||
const EventEmitter = require('events');
|
||||
const Cache = require('lib/Cache');
|
||||
|
||||
class Application extends BaseApplication {
|
||||
|
||||
@@ -34,6 +35,7 @@ class Application extends BaseApplication {
|
||||
this.allCommandsLoaded_ = false;
|
||||
this.showStackTraces_ = false;
|
||||
this.gui_ = null;
|
||||
this.cache_ = new Cache();
|
||||
}
|
||||
|
||||
gui() {
|
||||
@@ -223,12 +225,8 @@ class Application extends BaseApplication {
|
||||
async commandMetadata() {
|
||||
if (this.commandMetadata_) return this.commandMetadata_;
|
||||
|
||||
const osTmpdir = require('os-tmpdir');
|
||||
const storage = require('node-persist');
|
||||
await storage.init({ dir: osTmpdir() + '/commandMetadata', ttl: 1000 * 60 * 60 * 24 });
|
||||
|
||||
let output = await storage.getItem('metadata');
|
||||
if (Setting.value('env') != 'dev' && output) {
|
||||
let output = await this.cache_.getItem('metadata');
|
||||
if (output) {
|
||||
this.commandMetadata_ = output;
|
||||
return Object.assign({}, this.commandMetadata_);
|
||||
}
|
||||
@@ -242,7 +240,7 @@ class Application extends BaseApplication {
|
||||
output[n] = cmd.metadata();
|
||||
}
|
||||
|
||||
await storage.setItem('metadata', output);
|
||||
await this.cache_.setItem('metadata', output, 1000 * 60 * 60 * 24);
|
||||
|
||||
this.commandMetadata_ = output;
|
||||
return Object.assign({}, this.commandMetadata_);
|
||||
@@ -285,7 +283,7 @@ class Application extends BaseApplication {
|
||||
exit: () => {},
|
||||
showModalOverlay: (text) => {},
|
||||
hideModalOverlay: () => {},
|
||||
stdoutMaxWidth: () => { return 78; },
|
||||
stdoutMaxWidth: () => { return 100; },
|
||||
forceRender: () => {},
|
||||
termSaveState: () => {},
|
||||
termRestoreState: (state) => {},
|
||||
@@ -294,7 +292,7 @@ class Application extends BaseApplication {
|
||||
|
||||
async execCommand(argv) {
|
||||
if (!argv.length) return this.execCommand(['help']);
|
||||
reg.logger().info('execCommand()', argv);
|
||||
// reg.logger().debug('execCommand()', argv);
|
||||
const commandName = argv[0];
|
||||
this.activeCommand_ = this.findCommandByName(commandName);
|
||||
|
||||
@@ -314,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);
|
||||
|
||||
@@ -332,16 +387,19 @@ class Application extends BaseApplication {
|
||||
await this.execCommand(argv);
|
||||
} catch (error) {
|
||||
if (this.showStackTraces_) {
|
||||
console.info(error);
|
||||
console.error(error);
|
||||
} else {
|
||||
console.info(error.message);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
} else { // Otherwise open the GUI
|
||||
this.initRedux();
|
||||
|
||||
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();
|
||||
|
||||
|
@@ -4,6 +4,7 @@ var Folder = require('lib/models/Folder.js');
|
||||
var Tag = require('lib/models/Tag.js');
|
||||
var { cliUtils } = require('./cli-utils.js');
|
||||
var yargParser = require('yargs-parser');
|
||||
var fs = require('fs-extra');
|
||||
|
||||
async function handleAutocompletionPromise(line) {
|
||||
// Auto-complete the command name
|
||||
@@ -48,7 +49,7 @@ async function handleAutocompletionPromise(line) {
|
||||
if (options.length > 1 && options[1].indexOf(next) === 0) {
|
||||
l.push(options[1]);
|
||||
} else if (options[0].indexOf(next) === 0) {
|
||||
l.push(options[2]);
|
||||
l.push(options[0]);
|
||||
}
|
||||
}
|
||||
if (l.length === 0) {
|
||||
@@ -71,8 +72,10 @@ async function handleAutocompletionPromise(line) {
|
||||
let argName = cmdUsage[positionalArgs - 1];
|
||||
argName = cliUtils.parseCommandArg(argName).name;
|
||||
|
||||
if (argName == 'note' || argName == 'note-pattern' && app().currentFolder()) {
|
||||
const notes = await Note.previews(app().currentFolder().id, { titlePattern: next + '*' });
|
||||
const currentFolder = app().currentFolder();
|
||||
|
||||
if (argName == 'note' || argName == 'note-pattern') {
|
||||
const notes = currentFolder ? await Note.previews(currentFolder.id, { titlePattern: next + '*' }) : [];
|
||||
l.push(...notes.map((n) => n.title));
|
||||
}
|
||||
|
||||
@@ -81,11 +84,22 @@ async function handleAutocompletionPromise(line) {
|
||||
l.push(...folders.map((n) => n.title));
|
||||
}
|
||||
|
||||
if (argName == 'item') {
|
||||
const notes = currentFolder ? await Note.previews(currentFolder.id, { titlePattern: next + '*' }) : [];
|
||||
const folders = await Folder.search({ titlePattern: next + '*' });
|
||||
l.push(...notes.map((n) => n.title), folders.map((n) => n.title));
|
||||
}
|
||||
|
||||
if (argName == 'tag') {
|
||||
let tags = await Tag.search({ titlePattern: next + '*' });
|
||||
l.push(...tags.map((n) => n.title));
|
||||
}
|
||||
|
||||
if (argName == 'file') {
|
||||
let files = await fs.readdir('.');
|
||||
l.push(...files);
|
||||
}
|
||||
|
||||
if (argName == 'tag-command') {
|
||||
let c = filterList(['add', 'remove', 'list'], next);
|
||||
l.push(...c);
|
||||
|
@@ -32,10 +32,6 @@ class BaseCommand {
|
||||
return this.compatibleUis().indexOf(ui) >= 0;
|
||||
}
|
||||
|
||||
aliases() {
|
||||
return [];
|
||||
}
|
||||
|
||||
options() {
|
||||
return [];
|
||||
}
|
||||
|
@@ -23,8 +23,11 @@ class Command extends BaseCommand {
|
||||
const verbose = args.options.verbose;
|
||||
|
||||
const renderKeyValue = (name) => {
|
||||
const md = Setting.settingMetadata(name);
|
||||
let value = Setting.value(name);
|
||||
if (typeof value === 'object' || Array.isArray(value)) value = JSON.stringify(value);
|
||||
if (md.secure) value = '********';
|
||||
|
||||
if (Setting.isEnum(name)) {
|
||||
return _('%s = %s (%s)', name, value, Setting.enumOptionsDoc(name));
|
||||
} else {
|
||||
|
@@ -131,7 +131,6 @@ class Command extends BaseCommand {
|
||||
} else if (stat.isDirectory()) {
|
||||
continue;
|
||||
} else {
|
||||
itemCount++;
|
||||
const content = await fs.readFile(fullPath, 'utf8');
|
||||
const item = await BaseItem.unserialize(content);
|
||||
const ItemClass = BaseItem.itemClass(item);
|
||||
@@ -141,6 +140,8 @@ class Command extends BaseCommand {
|
||||
continue;
|
||||
}
|
||||
|
||||
itemCount++;
|
||||
|
||||
const isEncrypted = await EncryptionService.instance().itemIsEncrypted(item);
|
||||
|
||||
if (isEncrypted) {
|
||||
|
@@ -81,7 +81,9 @@ class Command extends BaseCommand {
|
||||
const termState = app().gui().termSaveState();
|
||||
|
||||
const spawnSync = require('child_process').spawnSync;
|
||||
spawnSync(editorPath, editorArgs, { stdio: 'inherit' });
|
||||
const result = spawnSync(editorPath, editorArgs, { stdio: 'inherit' });
|
||||
|
||||
if (result.error) this.stdout(_('Error opening note in editor: %s', result.error.message));
|
||||
|
||||
app().gui().termRestoreState(termState);
|
||||
app().gui().hideModalOverlay();
|
||||
|
@@ -1,5 +1,5 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const { Exporter } = require('lib/services/exporter.js');
|
||||
const InteropService = require('lib/services/InteropService.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const { reg } = require('lib/registry.js');
|
||||
@@ -10,15 +10,21 @@ const fs = require('fs-extra');
|
||||
class Command extends BaseCommand {
|
||||
|
||||
usage() {
|
||||
return 'export <directory>';
|
||||
return 'export <path>';
|
||||
}
|
||||
|
||||
description() {
|
||||
return _('Exports Joplin data to the given directory. By default, it will export the complete database including notebooks, notes, tags and resources.');
|
||||
return _('Exports Joplin data to the given path. By default, it will export the complete database including notebooks, notes, tags and resources.');
|
||||
}
|
||||
|
||||
options() {
|
||||
const service = new InteropService();
|
||||
const formats = service.modules()
|
||||
.filter(m => m.type === 'exporter')
|
||||
.map(m => m.format + (m.description ? ' (' + m.description + ')' : ''));
|
||||
|
||||
return [
|
||||
['--format <format>', _('Destination format: %s', formats.join(', '))],
|
||||
['--note <note>', _('Exports only the given note.')],
|
||||
['--notebook <notebook>', _('Exports only the given notebook.')],
|
||||
];
|
||||
@@ -26,13 +32,9 @@ class Command extends BaseCommand {
|
||||
|
||||
async action(args) {
|
||||
let exportOptions = {};
|
||||
exportOptions.destDir = args.directory;
|
||||
exportOptions.writeFile = (filePath, data) => {
|
||||
return fs.writeFile(filePath, data);
|
||||
};
|
||||
exportOptions.copyFile = (source, dest) => {
|
||||
return fs.copy(source, dest, { overwrite: true });
|
||||
};
|
||||
exportOptions.path = args.path;
|
||||
|
||||
exportOptions.format = args.options.format ? args.options.format : 'jex';
|
||||
|
||||
if (args.options.note) {
|
||||
|
||||
@@ -48,10 +50,10 @@ class Command extends BaseCommand {
|
||||
|
||||
}
|
||||
|
||||
const exporter = new Exporter();
|
||||
const result = await exporter.export(exportOptions);
|
||||
const service = new InteropService();
|
||||
const result = await service.export(exportOptions);
|
||||
|
||||
reg.logger().info('Export result: ', result);
|
||||
result.warnings.map((w) => this.stdout(w));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -36,21 +36,22 @@ class Command extends BaseCommand {
|
||||
async action(args) {
|
||||
const stdoutWidth = app().commandStdoutMaxWidth();
|
||||
|
||||
if (args.command === 'shortcuts') {
|
||||
if (args.command === 'shortcuts' || args.command === 'keymap') {
|
||||
this.stdout(_('For information on how to customise the shortcuts please visit %s', 'http://joplin.cozic.net/terminal/#shortcuts'));
|
||||
this.stdout('');
|
||||
|
||||
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 +79,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 list of keyboard shortcuts and config options, type `help keymap`'));
|
||||
}
|
||||
|
||||
app().gui().showConsole();
|
||||
|
@@ -1,68 +0,0 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const { importEnex } = require('lib/import-enex');
|
||||
const { filename, basename } = require('lib/path-utils.js');
|
||||
const { cliUtils } = require('./cli-utils.js');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
|
||||
usage() {
|
||||
return 'import-enex <file> [notebook]';
|
||||
}
|
||||
|
||||
description() {
|
||||
return _('Imports an Evernote notebook file (.enex file).');
|
||||
}
|
||||
|
||||
options() {
|
||||
return [
|
||||
['-f, --force', _('Do not ask for confirmation.')],
|
||||
];
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
let filePath = args.file;
|
||||
let folder = null;
|
||||
let folderTitle = args['notebook'];
|
||||
let force = args.options.force === true;
|
||||
|
||||
if (!folderTitle) folderTitle = filename(filePath);
|
||||
folder = await Folder.loadByField('title', folderTitle);
|
||||
const msg = folder ? _('File "%s" will be imported into existing notebook "%s". Continue?', basename(filePath), folderTitle) : _('New notebook "%s" will be created and file "%s" will be imported into it. Continue?', folderTitle, basename(filePath));
|
||||
const ok = force ? true : await this.prompt(msg);
|
||||
if (!ok) return;
|
||||
|
||||
let lastProgress = '';
|
||||
|
||||
let options = {
|
||||
onProgress: (progressState) => {
|
||||
let line = [];
|
||||
line.push(_('Found: %d.', progressState.loaded));
|
||||
line.push(_('Created: %d.', progressState.created));
|
||||
if (progressState.updated) line.push(_('Updated: %d.', progressState.updated));
|
||||
if (progressState.skipped) line.push(_('Skipped: %d.', progressState.skipped));
|
||||
if (progressState.resourcesCreated) line.push(_('Resources: %d.', progressState.resourcesCreated));
|
||||
if (progressState.notesTagged) line.push(_('Tagged: %d.', progressState.notesTagged));
|
||||
lastProgress = line.join(' ');
|
||||
cliUtils.redraw(lastProgress);
|
||||
},
|
||||
onError: (error) => {
|
||||
let s = error.trace ? error.trace : error.toString();
|
||||
this.stdout(s);
|
||||
},
|
||||
}
|
||||
|
||||
folder = !folder ? await Folder.save({ title: folderTitle }) : folder;
|
||||
|
||||
app().gui().showConsole();
|
||||
this.stdout(_('Importing notes...'));
|
||||
await importEnex(folder.id, filePath, options);
|
||||
cliUtils.redrawDone();
|
||||
this.stdout(_('The notes have been imported: %s', lastProgress));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Command;
|
75
CliClient/app/command-import.js
Normal file
75
CliClient/app/command-import.js
Normal file
@@ -0,0 +1,75 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const InteropService = require('lib/services/InteropService.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const { filename, basename, fileExtension } = require('lib/path-utils.js');
|
||||
const { importEnex } = require('lib/import-enex');
|
||||
const { cliUtils } = require('./cli-utils.js');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const fs = require('fs-extra');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
|
||||
usage() {
|
||||
return 'import <path> [notebook]';
|
||||
}
|
||||
|
||||
description() {
|
||||
return _('Imports data into Joplin.');
|
||||
}
|
||||
|
||||
options() {
|
||||
const service = new InteropService();
|
||||
const formats = service.modules().filter(m => m.type === 'importer').map(m => m.format);
|
||||
|
||||
return [
|
||||
['--format <format>', _('Source format: %s', (['auto'].concat(formats)).join(', '))],
|
||||
['-f, --force', _('Do not ask for confirmation.')],
|
||||
];
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
let folder = await app().loadItem(BaseModel.TYPE_FOLDER, args.notebook);
|
||||
|
||||
if (args.notebook && !folder) throw new Error(_('Cannot find "%s".', args.notebook));
|
||||
|
||||
const importOptions = {};
|
||||
importOptions.path = args.path;
|
||||
importOptions.format = args.options.format ? args.options.format : 'auto';
|
||||
importOptions.destinationFolderId = folder ? folder.id : null;
|
||||
|
||||
let lastProgress = '';
|
||||
|
||||
// onProgress/onError supported by Enex import only
|
||||
|
||||
importOptions.onProgress = (progressState) => {
|
||||
let line = [];
|
||||
line.push(_('Found: %d.', progressState.loaded));
|
||||
line.push(_('Created: %d.', progressState.created));
|
||||
if (progressState.updated) line.push(_('Updated: %d.', progressState.updated));
|
||||
if (progressState.skipped) line.push(_('Skipped: %d.', progressState.skipped));
|
||||
if (progressState.resourcesCreated) line.push(_('Resources: %d.', progressState.resourcesCreated));
|
||||
if (progressState.notesTagged) line.push(_('Tagged: %d.', progressState.notesTagged));
|
||||
lastProgress = line.join(' ');
|
||||
cliUtils.redraw(lastProgress);
|
||||
};
|
||||
|
||||
importOptions.onError = (error) => {
|
||||
let s = error.trace ? error.trace : error.toString();
|
||||
this.stdout(s);
|
||||
};
|
||||
|
||||
app().gui().showConsole();
|
||||
this.stdout(_('Importing notes...'));
|
||||
const service = new InteropService();
|
||||
const result = await service.import(importOptions);
|
||||
result.warnings.map((w) => this.stdout(w));
|
||||
cliUtils.redrawDone();
|
||||
if (lastProgress) this.stdout(_('The notes have been imported: %s', lastProgress));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Command;
|
@@ -14,10 +14,6 @@ class Command extends BaseCommand {
|
||||
return _('Creates a new notebook.');
|
||||
}
|
||||
|
||||
aliases() {
|
||||
return ['mkdir'];
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
let folder = await Folder.save({ title: args['new-notebook'] }, { userSideValidation: true });
|
||||
app().switchCurrentFolder(folder);
|
||||
|
@@ -10,7 +10,7 @@ const { cliUtils } = require('./cli-utils.js');
|
||||
const md5 = require('md5');
|
||||
const locker = require('proper-lockfile');
|
||||
const fs = require('fs-extra');
|
||||
const osTmpdir = require('os-tmpdir');
|
||||
const SyncTargetRegistry = require('lib/SyncTargetRegistry');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
|
||||
@@ -61,14 +61,28 @@ class Command extends BaseCommand {
|
||||
});
|
||||
}
|
||||
|
||||
async doAuth(syncTargetId) {
|
||||
async doAuth() {
|
||||
const syncTarget = reg.syncTarget(this.syncTargetId_);
|
||||
this.oneDriveApiUtils_ = new OneDriveApiNodeUtils(syncTarget.api());
|
||||
const auth = await this.oneDriveApiUtils_.oauthDance({
|
||||
log: (...s) => { return this.stdout(...s); }
|
||||
});
|
||||
this.oneDriveApiUtils_ = null;
|
||||
return auth;
|
||||
const syncTargetMd = SyncTargetRegistry.idToMetadata(this.syncTargetId_);
|
||||
|
||||
if (this.syncTargetId_ === 3 || this.syncTargetId_ === 4) { // OneDrive
|
||||
this.oneDriveApiUtils_ = new OneDriveApiNodeUtils(syncTarget.api());
|
||||
const auth = await this.oneDriveApiUtils_.oauthDance({
|
||||
log: (...s) => { return this.stdout(...s); }
|
||||
});
|
||||
this.oneDriveApiUtils_ = null;
|
||||
|
||||
Setting.setValue('sync.' + this.syncTargetId_ + '.auth', auth ? JSON.stringify(auth) : null);
|
||||
if (!auth) {
|
||||
this.stdout(_('Authentication was not completed (did not receive an authentication token).'));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
this.stdout(_('Not authentified with %s. Please provide any missing credentials.', syncTarget.label()));
|
||||
return false;
|
||||
}
|
||||
|
||||
cancelAuth() {
|
||||
@@ -86,7 +100,7 @@ class Command extends BaseCommand {
|
||||
this.releaseLockFn_ = null;
|
||||
|
||||
// Lock is unique per profile/database
|
||||
const lockFilePath = osTmpdir() + '/synclock_' + md5(Setting.value('profileDir'));
|
||||
const lockFilePath = require('os').tmpdir() + '/synclock_' + md5(escape(Setting.value('profileDir'))); // https://github.com/pvorb/node-md5/issues/41
|
||||
if (!await fs.pathExists(lockFilePath)) await fs.writeFile(lockFilePath, 'synclock');
|
||||
|
||||
try {
|
||||
@@ -120,12 +134,8 @@ class Command extends BaseCommand {
|
||||
app().gui().showConsole();
|
||||
app().gui().maximizeConsole();
|
||||
|
||||
const auth = await this.doAuth(this.syncTargetId_);
|
||||
Setting.setValue('sync.' + this.syncTargetId_ + '.auth', auth ? JSON.stringify(auth) : null);
|
||||
if (!auth) {
|
||||
this.stdout(_('Authentication was not completed (did not receive an authentication token).'));
|
||||
return cleanUp();
|
||||
}
|
||||
const authDone = await this.doAuth();
|
||||
if (!authDone) return cleanUp();
|
||||
}
|
||||
|
||||
const sync = await syncTarget.synchronizer();
|
||||
|
@@ -133,7 +133,8 @@ class StatusBarWidget extends BaseWidget {
|
||||
resolveResult = input ? input.trim() : input;
|
||||
// Add the command to history but only if it's longer than one character.
|
||||
// Below that it's usually an answer like "y"/"n", etc.
|
||||
if (!isSecurePrompt && input && input.length > 1) this.history_.push(input);
|
||||
const isConfigPassword = input.indexOf('config ') >= 0 && input.indexOf('password') >= 0;
|
||||
if (!isSecurePrompt && input && input.length > 1 && !isConfigPassword) this.history_.push(input);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -53,9 +53,8 @@ function renderCommandHelp(cmd, width = null) {
|
||||
desc.push(label);
|
||||
}
|
||||
|
||||
if (md.description) {
|
||||
desc.push(md.description());
|
||||
}
|
||||
const description = Setting.keyDescription(md.key, 'cli');
|
||||
if (description) desc.push(description);
|
||||
|
||||
desc.push(_('Type: %s.', md.isEnum ? _('Enum') : Setting.typeToString(md.type)));
|
||||
if (md.isEnum) desc.push(_('Possible values: %s.', Setting.enumOptionsDoc(md.key, '%s (%s)')));
|
||||
|
@@ -23,12 +23,14 @@ const { Logger } = require('lib/logger.js');
|
||||
const { FsDriverNode } = require('lib/fs-driver-node.js');
|
||||
const { shimInit } = require('lib/shim-init-node.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { FileApiDriverLocal } = require('lib/file-api-driver-local.js');
|
||||
const EncryptionService = require('lib/services/EncryptionService');
|
||||
|
||||
const fsDriver = new FsDriverNode();
|
||||
Logger.fsDriver_ = fsDriver;
|
||||
Resource.fsDriver_ = fsDriver;
|
||||
EncryptionService.fsDriver_ = fsDriver;
|
||||
FileApiDriverLocal.fsDriver_ = fsDriver;
|
||||
|
||||
// That's not good, but it's to avoid circular dependency issues
|
||||
// in the BaseItem class.
|
||||
@@ -64,7 +66,34 @@ process.stdout.on('error', function( err ) {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// async function main() {
|
||||
// const InteropService = require('lib/services/InteropService');
|
||||
// const service = new InteropService();
|
||||
// console.info(service.moduleByFormat('importer', 'enex'));
|
||||
// //await service.modules();
|
||||
// }
|
||||
|
||||
// main().catch((error) => { console.error(error); });
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
application.start(process.argv).catch((error) => {
|
||||
console.error(_('Fatal error:'));
|
||||
console.error(error);
|
||||
if (error.code == 'flagError') {
|
||||
console.error(error.message);
|
||||
console.error(_('Type `joplin help` for usage information.'));
|
||||
} else {
|
||||
console.error(_('Fatal error:'));
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
});
|
File diff suppressed because it is too large
Load Diff
@@ -15,63 +15,12 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "Give focus to next pane"
|
||||
msgstr ""
|
||||
|
||||
msgid "Give focus to previous pane"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enter command line mode"
|
||||
msgstr ""
|
||||
|
||||
msgid "Exit command line mode"
|
||||
msgstr ""
|
||||
|
||||
msgid "Edit the selected note"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cancel the current command."
|
||||
msgstr ""
|
||||
|
||||
msgid "Exit the application."
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete the currently selected note or notebook."
|
||||
msgstr ""
|
||||
|
||||
msgid "To delete a tag, untag the associated notes."
|
||||
msgstr ""
|
||||
|
||||
msgid "Please select the note or notebook to be deleted first."
|
||||
msgstr ""
|
||||
|
||||
msgid "Set a to-do as completed / not completed"
|
||||
msgstr ""
|
||||
|
||||
msgid "[t]oggle [c]onsole between maximized/minimized/hidden/visible."
|
||||
msgstr ""
|
||||
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
msgid "[t]oggle note [m]etadata."
|
||||
msgstr ""
|
||||
|
||||
msgid "[M]ake a new [n]ote"
|
||||
msgstr ""
|
||||
|
||||
msgid "[M]ake a new [t]odo"
|
||||
msgstr ""
|
||||
|
||||
msgid "[M]ake a new note[b]ook"
|
||||
msgstr ""
|
||||
|
||||
msgid "Copy ([Y]ank) the [n]ote to a notebook."
|
||||
msgstr ""
|
||||
|
||||
msgid "Move the note to a notebook."
|
||||
msgstr ""
|
||||
|
||||
msgid "Press Ctrl+D or type \"exit\" to exit the application"
|
||||
msgstr ""
|
||||
|
||||
@@ -214,6 +163,10 @@ msgstr ""
|
||||
msgid "Starting to edit note. Close the editor to get back to the prompt."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Error opening note in editor: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note has been saved."
|
||||
msgstr ""
|
||||
|
||||
@@ -221,10 +174,14 @@ msgid "Exits the application."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Exports Joplin data to the given directory. By default, it will export the "
|
||||
"Exports Joplin data to the given path. By default, it will export the "
|
||||
"complete database including notebooks, notes, tags and resources."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Destination format: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Exports only the given note."
|
||||
msgstr ""
|
||||
|
||||
@@ -237,6 +194,10 @@ msgstr ""
|
||||
msgid "Displays usage information."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "For information on how to customise the shortcuts please visit %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Shortcuts are not available in CLI mode."
|
||||
msgstr ""
|
||||
|
||||
@@ -272,25 +233,19 @@ msgid "To exit command line mode, press ESCAPE"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"For the complete list of available keyboard shortcuts, type `help shortcuts`"
|
||||
"For the list of keyboard shortcuts and config options, type `help keymap`"
|
||||
msgstr ""
|
||||
|
||||
msgid "Imports an Evernote notebook file (.enex file)."
|
||||
msgid "Imports data into Joplin."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Source format: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Do not ask for confirmation."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"New notebook \"%s\" will be created and file \"%s\" will be imported into "
|
||||
"it. Continue?"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Found: %d."
|
||||
msgstr ""
|
||||
@@ -413,6 +368,14 @@ msgstr ""
|
||||
msgid "Sync to provided target (defaults to sync.target config value)"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Authentication was not completed (did not receive an authentication token)."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Not authentified with %s. Please provide any missing credentials."
|
||||
msgstr ""
|
||||
|
||||
msgid "Synchronisation is already in progress."
|
||||
msgstr ""
|
||||
|
||||
@@ -423,10 +386,6 @@ msgid ""
|
||||
"operation."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Authentication was not completed (did not receive an authentication token)."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Synchronisation target: %s (%s)"
|
||||
msgstr ""
|
||||
@@ -490,6 +449,9 @@ msgstr ""
|
||||
msgid "Possible keys/values:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Type `joplin help` for usage information."
|
||||
msgstr ""
|
||||
|
||||
msgid "Fatal error:"
|
||||
msgstr ""
|
||||
|
||||
@@ -527,6 +489,17 @@ msgid ""
|
||||
"background and will be available soon."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Exporting to \"%s\" as \"%s\" format. Please wait..."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
|
||||
msgstr ""
|
||||
|
||||
msgid "PDF File"
|
||||
msgstr ""
|
||||
|
||||
msgid "File"
|
||||
msgstr ""
|
||||
|
||||
@@ -539,10 +512,17 @@ msgstr ""
|
||||
msgid "New notebook"
|
||||
msgstr ""
|
||||
|
||||
msgid "Import Evernote notes"
|
||||
msgid "Import"
|
||||
msgstr ""
|
||||
|
||||
msgid "Evernote Export Files"
|
||||
msgid "Export"
|
||||
msgstr ""
|
||||
|
||||
msgid "Print"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Hide %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Quit"
|
||||
@@ -563,6 +543,12 @@ msgstr ""
|
||||
msgid "Search in all the notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "View"
|
||||
msgstr ""
|
||||
|
||||
msgid "Toggle editor layout"
|
||||
msgstr ""
|
||||
|
||||
msgid "Tools"
|
||||
msgstr ""
|
||||
|
||||
@@ -581,6 +567,12 @@ msgstr ""
|
||||
msgid "Website and documentation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Make a donation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Check for updates..."
|
||||
msgstr ""
|
||||
|
||||
msgid "About Joplin"
|
||||
msgstr ""
|
||||
|
||||
@@ -588,12 +580,34 @@ msgstr ""
|
||||
msgid "%s %s (%s, %s)"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Exit"
|
||||
msgstr ""
|
||||
|
||||
msgid "OK"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
msgid "Current version is up-to-date."
|
||||
msgstr ""
|
||||
|
||||
msgid "An update is available, do you want to download it now?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Yes"
|
||||
msgstr ""
|
||||
|
||||
msgid "No"
|
||||
msgstr ""
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr ""
|
||||
@@ -650,6 +664,15 @@ msgid ""
|
||||
"how the notes or notebooks were originally encrypted."
|
||||
msgstr ""
|
||||
|
||||
msgid "Missing Master Keys"
|
||||
msgstr ""
|
||||
|
||||
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 ""
|
||||
|
||||
msgid "Status"
|
||||
msgstr ""
|
||||
|
||||
@@ -685,6 +708,9 @@ msgstr ""
|
||||
msgid "Set alarm:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
msgid "Layout"
|
||||
msgstr ""
|
||||
|
||||
@@ -719,6 +745,12 @@ msgid ""
|
||||
"There is currently no notebook. Create one by clicking on \"New notebook\"."
|
||||
msgstr ""
|
||||
|
||||
msgid "Open..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Save as..."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Unsupported link or message: %s"
|
||||
msgstr ""
|
||||
@@ -726,9 +758,28 @@ msgstr ""
|
||||
msgid "Attach file"
|
||||
msgstr ""
|
||||
|
||||
msgid "Tags"
|
||||
msgstr ""
|
||||
|
||||
msgid "Set alarm"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"This note has no content. Click on \"%s\" to toggle the editor and edit the "
|
||||
"note."
|
||||
msgstr ""
|
||||
|
||||
msgid "to-do"
|
||||
msgstr ""
|
||||
|
||||
msgid "note"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Creating new %s..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Refresh"
|
||||
msgstr ""
|
||||
|
||||
@@ -738,9 +789,6 @@ msgstr ""
|
||||
msgid "OneDrive Login"
|
||||
msgstr ""
|
||||
|
||||
msgid "Import"
|
||||
msgstr ""
|
||||
|
||||
msgid "Options"
|
||||
msgstr ""
|
||||
|
||||
@@ -765,9 +813,6 @@ msgstr ""
|
||||
msgid "Notebooks"
|
||||
msgstr ""
|
||||
|
||||
msgid "Tags"
|
||||
msgstr ""
|
||||
|
||||
msgid "Searches"
|
||||
msgstr ""
|
||||
|
||||
@@ -785,12 +830,18 @@ msgstr ""
|
||||
msgid "File system"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud"
|
||||
msgstr ""
|
||||
|
||||
msgid "OneDrive"
|
||||
msgstr ""
|
||||
|
||||
msgid "OneDrive Dev (For testing only)"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Unknown log level: %s"
|
||||
msgstr ""
|
||||
@@ -856,6 +907,16 @@ msgstr ""
|
||||
msgid "Completed: %s"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Last error: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Idle"
|
||||
msgstr ""
|
||||
|
||||
msgid "In progress"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr ""
|
||||
@@ -917,12 +978,47 @@ msgstr ""
|
||||
msgid "Dark"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show uncompleted todos on top of the lists"
|
||||
msgid "Uncompleted to-dos on top"
|
||||
msgstr ""
|
||||
|
||||
msgid "Sort notes by"
|
||||
msgstr ""
|
||||
|
||||
msgid "Reverse sort order"
|
||||
msgstr ""
|
||||
|
||||
msgid "Save geo-location with notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "When creating a new to-do:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Focus title"
|
||||
msgstr ""
|
||||
|
||||
msgid "Focus body"
|
||||
msgstr ""
|
||||
|
||||
msgid "When creating a new note:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show tray icon"
|
||||
msgstr ""
|
||||
|
||||
msgid "Global zoom percentage"
|
||||
msgstr ""
|
||||
|
||||
msgid "Editor font family"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"The font name will not be checked. If incorrect or empty, it will default to "
|
||||
"a generic monospace font."
|
||||
msgstr ""
|
||||
|
||||
msgid "Automatically update the application"
|
||||
msgstr ""
|
||||
|
||||
msgid "Synchronisation interval"
|
||||
msgstr ""
|
||||
|
||||
@@ -938,9 +1034,6 @@ msgstr ""
|
||||
msgid "%d hours"
|
||||
msgstr ""
|
||||
|
||||
msgid "Automatically update the application"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show advanced options"
|
||||
msgstr ""
|
||||
|
||||
@@ -948,8 +1041,8 @@ msgid "Synchronisation target"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"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)."
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
@@ -960,10 +1053,63 @@ msgid ""
|
||||
"See `sync.target`."
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud WebDAV URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud username"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud password"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV username"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV password"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr ""
|
||||
|
||||
msgid "Joplin Export File"
|
||||
msgstr ""
|
||||
|
||||
msgid "Markdown"
|
||||
msgstr ""
|
||||
|
||||
msgid "Joplin Export Directory"
|
||||
msgstr ""
|
||||
|
||||
msgid "Evernote Export File"
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Cannot load \"%s\" module for format \"%s\""
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Please specify import format for %s"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"This item is currently encrypted: %s \"%s\". Please wait for all items to be "
|
||||
"decrypted and try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "There is no data to export."
|
||||
msgstr ""
|
||||
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr ""
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr ""
|
||||
|
||||
@@ -1047,6 +1193,9 @@ msgstr ""
|
||||
msgid "Cancel synchronisation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Joplin website"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Master Key %s"
|
||||
msgstr ""
|
||||
@@ -1071,6 +1220,12 @@ msgstr ""
|
||||
msgid "Edit notebook"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show all"
|
||||
msgstr ""
|
||||
|
||||
msgid "Errors only"
|
||||
msgstr ""
|
||||
|
||||
msgid "This note has been modified:"
|
||||
msgstr ""
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1479
CliClient/locales/eu.po
Normal file
1479
CliClient/locales/eu.po
Normal file
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Joplin-CLI 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"Last-Translator: \n"
|
||||
"Last-Translator: Laurent Cozic\n"
|
||||
"Language-Team: \n"
|
||||
"Language: fr_FR\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -15,63 +15,12 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 2.0.3\n"
|
||||
|
||||
msgid "Give focus to next pane"
|
||||
msgstr "Activer le volet suivant"
|
||||
|
||||
msgid "Give focus to previous pane"
|
||||
msgstr "Activer le volet précédent"
|
||||
|
||||
msgid "Enter command line mode"
|
||||
msgstr "Démarrer le mode de ligne de commande"
|
||||
|
||||
msgid "Exit command line mode"
|
||||
msgstr "Sortir du mode de ligne de commande"
|
||||
|
||||
msgid "Edit the selected note"
|
||||
msgstr "Éditer la note sélectionnée"
|
||||
|
||||
msgid "Cancel the current command."
|
||||
msgstr "Annuler la commande en cours."
|
||||
|
||||
msgid "Exit the application."
|
||||
msgstr "Quitter le logiciel."
|
||||
|
||||
msgid "Delete the currently selected note or notebook."
|
||||
msgstr "Supprimer la note ou carnet sélectionné."
|
||||
|
||||
msgid "To delete a tag, untag the associated notes."
|
||||
msgstr "Pour supprimer une vignette, enlever là des notes associées."
|
||||
|
||||
msgid "Please select the note or notebook to be deleted first."
|
||||
msgstr "Veuillez d'abord sélectionner un carnet."
|
||||
|
||||
msgid "Set a to-do as completed / not completed"
|
||||
msgstr "Marquer une tâches comme complétée / non-complétée"
|
||||
|
||||
msgid "[t]oggle [c]onsole between maximized/minimized/hidden/visible."
|
||||
msgstr "Maximiser, minimiser, cacher ou rendre visible la console."
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Chercher"
|
||||
|
||||
msgid "[t]oggle note [m]etadata."
|
||||
msgstr "Afficher/Cacher les métadonnées des notes."
|
||||
|
||||
msgid "[M]ake a new [n]ote"
|
||||
msgstr "Créer une nouvelle note"
|
||||
|
||||
msgid "[M]ake a new [t]odo"
|
||||
msgstr "Créer une nouvelle tâche"
|
||||
|
||||
msgid "[M]ake a new note[b]ook"
|
||||
msgstr "Créer un nouveau carnet"
|
||||
|
||||
msgid "Copy ([Y]ank) the [n]ote to a notebook."
|
||||
msgstr "Copier la note dans un autre carnet."
|
||||
|
||||
msgid "Move the note to a notebook."
|
||||
msgstr "Déplacer la note vers un carnet."
|
||||
|
||||
msgid "Press Ctrl+D or type \"exit\" to exit the application"
|
||||
msgstr "Appuyez sur Ctrl+D ou tapez \"exit\" pour sortir du logiciel"
|
||||
|
||||
@@ -146,8 +95,8 @@ msgid ""
|
||||
"current configuration."
|
||||
msgstr ""
|
||||
"Obtient ou modifie une valeur de configuration. Si la [valeur] n'est pas "
|
||||
"fournie, la valeur de [nom] est affichée. Si ni le [nom] ni la [valeur] ne "
|
||||
"sont fournis, la configuration complète est affichée."
|
||||
"fournie, la valeur de [nom] sera affichée. Si ni le [nom] ni la [valeur] ne "
|
||||
"sont fournis, la configuration complète sera affichée."
|
||||
|
||||
msgid "Also displays unset and hidden config variables."
|
||||
msgstr "Afficher également les variables cachées."
|
||||
@@ -228,6 +177,10 @@ msgstr ""
|
||||
"Édition de la note en cours. Fermez l'éditeur de texte pour retourner à "
|
||||
"l'invite de commande."
|
||||
|
||||
#, javascript-format
|
||||
msgid "Error opening note in editor: %s"
|
||||
msgstr "Erreur lors de l'ouverture de la note dans l'éditeur de texte : %s"
|
||||
|
||||
msgid "Note has been saved."
|
||||
msgstr "La note a été enregistrée."
|
||||
|
||||
@@ -235,12 +188,15 @@ msgid "Exits the application."
|
||||
msgstr "Quitter le logiciel."
|
||||
|
||||
msgid ""
|
||||
"Exports Joplin data to the given directory. By default, it will export the "
|
||||
"Exports Joplin data to the given path. By default, it will export the "
|
||||
"complete database including notebooks, notes, tags and resources."
|
||||
msgstr ""
|
||||
"Exporter les données de Joplin vers le dossier fourni. Par défaut, la base "
|
||||
"de donnée complète sera exportée, y compris les carnets, notes, tags et "
|
||||
"resources."
|
||||
"Exporter les données de Joplin. Par défaut, la base de donnée complète sera "
|
||||
"exportée, y compris les carnets, notes, tags et ressources."
|
||||
|
||||
#, javascript-format
|
||||
msgid "Destination format: %s"
|
||||
msgstr "Format de la destination : %s"
|
||||
|
||||
msgid "Exports only the given note."
|
||||
msgstr "Exporter uniquement la note spécifiée."
|
||||
@@ -254,6 +210,11 @@ msgstr "Afficher l'URL de l'emplacement de la note."
|
||||
msgid "Displays usage information."
|
||||
msgstr "Affiche les informations d'utilisation."
|
||||
|
||||
#, javascript-format
|
||||
msgid "For information on how to customise the shortcuts please visit %s"
|
||||
msgstr ""
|
||||
"Pour personnaliser les raccourcis veuillez consulter la documentation à %s"
|
||||
|
||||
msgid "Shortcuts are not available in CLI mode."
|
||||
msgstr "Les raccourcis ne sont pas disponible en mode de ligne de commande."
|
||||
|
||||
@@ -297,29 +258,19 @@ msgid "To exit command line mode, press ESCAPE"
|
||||
msgstr "Pour sortir du mode ligne de commande, pressez ECHAP"
|
||||
|
||||
msgid ""
|
||||
"For the complete list of available keyboard shortcuts, type `help shortcuts`"
|
||||
msgstr ""
|
||||
"Pour la liste complète des raccourcis disponibles, tapez `help shortcuts`"
|
||||
"For the list of keyboard shortcuts and config options, type `help keymap`"
|
||||
msgstr "Pour la liste complète des raccourcis disponibles, tapez `help keymap`"
|
||||
|
||||
msgid "Imports an Evernote notebook file (.enex file)."
|
||||
msgstr "Importer un carnet Evernote (fichier .enex)."
|
||||
msgid "Imports data into Joplin."
|
||||
msgstr "Importer des données dans Joplin."
|
||||
|
||||
#, javascript-format
|
||||
msgid "Source format: %s"
|
||||
msgstr "Format de la source : %s"
|
||||
|
||||
msgid "Do not ask for confirmation."
|
||||
msgstr "Ne pas demander de confirmation."
|
||||
|
||||
#, javascript-format
|
||||
msgid "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
|
||||
msgstr ""
|
||||
"Le fichier \"%s\" va être importé dans le carnet existant \"%s\". Continuer ?"
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"New notebook \"%s\" will be created and file \"%s\" will be imported into "
|
||||
"it. Continue?"
|
||||
msgstr ""
|
||||
"Un nouveau carnet \"%s\" va être créé et le fichier \"%s\" va être importé "
|
||||
"dedans. Continuer ?"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Found: %d."
|
||||
msgstr "Trouvés : %d."
|
||||
@@ -458,6 +409,16 @@ msgstr ""
|
||||
"Synchroniser avec la cible donnée (par défaut, la valeur de configuration "
|
||||
"`sync.target`)."
|
||||
|
||||
msgid ""
|
||||
"Authentication was not completed (did not receive an authentication token)."
|
||||
msgstr "Impossible d'autoriser le logiciel (jeton d'identification non-reçu)."
|
||||
|
||||
#, javascript-format
|
||||
msgid "Not authentified with %s. Please provide any missing credentials."
|
||||
msgstr ""
|
||||
"Non-connecté à %s. Veuillez fournir les identifiants et mots de passe "
|
||||
"manquants."
|
||||
|
||||
msgid "Synchronisation is already in progress."
|
||||
msgstr "La synchronisation est déjà en cours."
|
||||
|
||||
@@ -471,10 +432,6 @@ msgstr ""
|
||||
"correctement. Si vous savez qu'aucune autre synchronisation est en cours, "
|
||||
"vous pouvez supprimer le fichier \"%s\" pour reprendre l'opération."
|
||||
|
||||
msgid ""
|
||||
"Authentication was not completed (did not receive an authentication token)."
|
||||
msgstr "Impossible d'autoriser le logiciel (jeton d'identification non-reçu)."
|
||||
|
||||
#, javascript-format
|
||||
msgid "Synchronisation target: %s (%s)"
|
||||
msgstr "Cible de la synchronisation : %s (%s)"
|
||||
@@ -547,6 +504,9 @@ msgstr "Défaut : %s"
|
||||
msgid "Possible keys/values:"
|
||||
msgstr "Clefs/Valeurs possibles :"
|
||||
|
||||
msgid "Type `joplin help` for usage information."
|
||||
msgstr "Tapez `Joplin help` pour afficher l'aide."
|
||||
|
||||
msgid "Fatal error:"
|
||||
msgstr "Erreur fatale :"
|
||||
|
||||
@@ -600,6 +560,17 @@ msgstr ""
|
||||
"decrypt`. Si vous avez déjà fourni ce mot de passe, les objets cryptés vont "
|
||||
"être décrypté en tâche de fond et seront disponible prochainement."
|
||||
|
||||
#, javascript-format
|
||||
msgid "Exporting to \"%s\" as \"%s\" format. Please wait..."
|
||||
msgstr "Exporter vers \"%s\" au format \"%s\". Veuillez patienter..."
|
||||
|
||||
#, javascript-format
|
||||
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
|
||||
msgstr "Importer depuis \"%s\" au format \"%s\". Veuillez patienter..."
|
||||
|
||||
msgid "PDF File"
|
||||
msgstr "Fichier PDF"
|
||||
|
||||
msgid "File"
|
||||
msgstr "Fichier"
|
||||
|
||||
@@ -612,11 +583,18 @@ msgstr "Nouvelle tâche"
|
||||
msgid "New notebook"
|
||||
msgstr "Nouveau carnet"
|
||||
|
||||
msgid "Import Evernote notes"
|
||||
msgstr "Importer notes d'Evernote"
|
||||
msgid "Import"
|
||||
msgstr "Importer"
|
||||
|
||||
msgid "Evernote Export Files"
|
||||
msgstr "Fichiers d'export Evernote"
|
||||
msgid "Export"
|
||||
msgstr "Exporter"
|
||||
|
||||
msgid "Print"
|
||||
msgstr "Imprimer"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Hide %s"
|
||||
msgstr "Cacher %s"
|
||||
|
||||
msgid "Quit"
|
||||
msgstr "Quitter"
|
||||
@@ -636,6 +614,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"
|
||||
|
||||
@@ -654,6 +638,12 @@ msgstr "Aide"
|
||||
msgid "Website and documentation"
|
||||
msgstr "Documentation en ligne"
|
||||
|
||||
msgid "Make a donation"
|
||||
msgstr "Faire un don"
|
||||
|
||||
msgid "Check for updates..."
|
||||
msgstr "Vérifier les mises à jour..."
|
||||
|
||||
msgid "About Joplin"
|
||||
msgstr "A propos de Joplin"
|
||||
|
||||
@@ -661,11 +651,34 @@ msgstr "A propos de Joplin"
|
||||
msgid "%s %s (%s, %s)"
|
||||
msgstr "%s %s (%s, %s)"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr "Ouvrir %s"
|
||||
|
||||
msgid "Exit"
|
||||
msgstr "Quitter"
|
||||
|
||||
msgid "OK"
|
||||
msgstr "OK"
|
||||
|
||||
msgid "Cancel"
|
||||
msgstr "Annulation"
|
||||
msgstr "Annuler"
|
||||
|
||||
msgid "Current version is up-to-date."
|
||||
msgstr "La version actuelle est à jour."
|
||||
|
||||
msgid "An update is available, do you want to download it now?"
|
||||
msgstr ""
|
||||
"Une mise à jour est disponible, souhaitez vous la télécharger maintenant ?"
|
||||
|
||||
msgid "Yes"
|
||||
msgstr "Oui"
|
||||
|
||||
msgid "No"
|
||||
msgstr "Non"
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr "Vérifier config synchronisation"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
@@ -735,6 +748,18 @@ msgstr ""
|
||||
"pour le décryptage, selon la façon dont les notes ou carnets étaient cryptés "
|
||||
"à l'origine."
|
||||
|
||||
msgid "Missing Master Keys"
|
||||
msgstr "Clefs maître manquantes"
|
||||
|
||||
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 ""
|
||||
"Les clefs maître avec ces identifiants sont utilisées pour crypter certains "
|
||||
"de vos objets, cependant le logiciel n'y a pour l'instant pas accès. Il est "
|
||||
"probable qu'elle vont être prochainement disponible via la synchronisation."
|
||||
|
||||
msgid "Status"
|
||||
msgstr "État"
|
||||
|
||||
@@ -770,7 +795,10 @@ msgid "Rename notebook:"
|
||||
msgstr "Renommer le carnet :"
|
||||
|
||||
msgid "Set alarm:"
|
||||
msgstr "Définir ou modifier alarme :"
|
||||
msgstr "Régler alarme :"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Chercher"
|
||||
|
||||
msgid "Layout"
|
||||
msgstr "Disposition"
|
||||
@@ -809,6 +837,12 @@ msgstr ""
|
||||
"Il n'y a pour l'instant aucun carnet. Créez-en un en cliquant sur \"Nouveau "
|
||||
"carnet\"."
|
||||
|
||||
msgid "Open..."
|
||||
msgstr "Ouvrir..."
|
||||
|
||||
msgid "Save as..."
|
||||
msgstr "Enregistrer sous..."
|
||||
|
||||
#, javascript-format
|
||||
msgid "Unsupported link or message: %s"
|
||||
msgstr "Lien ou message non géré : %s"
|
||||
@@ -816,8 +850,29 @@ msgstr "Lien ou message non géré : %s"
|
||||
msgid "Attach file"
|
||||
msgstr "Attacher un fichier"
|
||||
|
||||
msgid "Tags"
|
||||
msgstr "Étiquettes"
|
||||
|
||||
msgid "Set alarm"
|
||||
msgstr "Définir ou modifier alarme"
|
||||
msgstr "Régler alarme"
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"This note has no content. Click on \"%s\" to toggle the editor and edit the "
|
||||
"note."
|
||||
msgstr ""
|
||||
"Cette note n'a pas de contenu. Cliquer sur \"%s\" pour basculer vers "
|
||||
"l'éditeur et éditer cette note."
|
||||
|
||||
msgid "to-do"
|
||||
msgstr "tâche"
|
||||
|
||||
msgid "note"
|
||||
msgstr "note"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Creating new %s..."
|
||||
msgstr "Création de %s..."
|
||||
|
||||
msgid "Refresh"
|
||||
msgstr "Rafraîchir"
|
||||
@@ -828,9 +883,6 @@ msgstr "Supprimer"
|
||||
msgid "OneDrive Login"
|
||||
msgstr "Connexion OneDrive"
|
||||
|
||||
msgid "Import"
|
||||
msgstr "Importer"
|
||||
|
||||
msgid "Options"
|
||||
msgstr "Options"
|
||||
|
||||
@@ -855,9 +907,6 @@ msgstr "Synchroniser"
|
||||
msgid "Notebooks"
|
||||
msgstr "Carnets"
|
||||
|
||||
msgid "Tags"
|
||||
msgstr "Étiquettes"
|
||||
|
||||
msgid "Searches"
|
||||
msgstr "Recherches"
|
||||
|
||||
@@ -876,12 +925,18 @@ msgstr "Paramètre inconnu : %s"
|
||||
msgid "File system"
|
||||
msgstr "Système de fichier"
|
||||
|
||||
msgid "Nextcloud"
|
||||
msgstr "Nextcloud"
|
||||
|
||||
msgid "OneDrive"
|
||||
msgstr "OneDrive"
|
||||
|
||||
msgid "OneDrive Dev (For testing only)"
|
||||
msgstr "OneDrive Dév (Pour tester uniquement)"
|
||||
|
||||
msgid "WebDAV"
|
||||
msgstr "WebDAV"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Unknown log level: %s"
|
||||
msgstr "Paramètre inconnu : %s"
|
||||
@@ -955,6 +1010,16 @@ msgstr "Annulation..."
|
||||
msgid "Completed: %s"
|
||||
msgstr "Terminé : %s"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Last error: %s"
|
||||
msgstr "Dernière erreur : %s"
|
||||
|
||||
msgid "Idle"
|
||||
msgstr "Arrêté"
|
||||
|
||||
msgid "In progress"
|
||||
msgstr "En cours"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr "La synchronisation est déjà en cours. État : %s"
|
||||
@@ -1018,12 +1083,49 @@ msgstr "Clair"
|
||||
msgid "Dark"
|
||||
msgstr "Sombre"
|
||||
|
||||
msgid "Show uncompleted todos on top of the lists"
|
||||
msgstr "Tâches non-terminées en haut des listes"
|
||||
msgid "Uncompleted to-dos on top"
|
||||
msgstr "Tâches non-terminées en haut"
|
||||
|
||||
msgid "Sort notes by"
|
||||
msgstr "Trier les notes par"
|
||||
|
||||
msgid "Reverse sort order"
|
||||
msgstr "Inverser l'ordre"
|
||||
|
||||
msgid "Save geo-location with notes"
|
||||
msgstr "Enregistrer l'emplacement avec les notes"
|
||||
|
||||
msgid "When creating a new to-do:"
|
||||
msgstr "Lors de la création d'une tâche :"
|
||||
|
||||
msgid "Focus title"
|
||||
msgstr "Curseur sur le titre"
|
||||
|
||||
msgid "Focus body"
|
||||
msgstr "Curseur sur corps du message"
|
||||
|
||||
msgid "When creating a new note:"
|
||||
msgstr "Lors de la création d'une note :"
|
||||
|
||||
msgid "Show tray icon"
|
||||
msgstr "Afficher icône dans la zone de notifications"
|
||||
|
||||
msgid "Global zoom percentage"
|
||||
msgstr "Niveau de zoom"
|
||||
|
||||
msgid "Editor font family"
|
||||
msgstr "Police de l'éditeur"
|
||||
|
||||
msgid ""
|
||||
"The font name will not be checked. If incorrect or empty, it will default to "
|
||||
"a generic monospace font."
|
||||
msgstr ""
|
||||
"Le nom de la police ne sera pas vérifié. Si incorrect ou vide une police "
|
||||
"monospace sera utilisée par défaut."
|
||||
|
||||
msgid "Automatically update the application"
|
||||
msgstr "Mettre à jour le logiciel automatiquement"
|
||||
|
||||
msgid "Synchronisation interval"
|
||||
msgstr "Intervalle de synchronisation"
|
||||
|
||||
@@ -1039,9 +1141,6 @@ msgstr "%d heure"
|
||||
msgid "%d hours"
|
||||
msgstr "%d heures"
|
||||
|
||||
msgid "Automatically update the application"
|
||||
msgstr "Mettre à jour le logiciel automatiquement"
|
||||
|
||||
msgid "Show advanced options"
|
||||
msgstr "Montrer les options avancées"
|
||||
|
||||
@@ -1049,11 +1148,12 @@ msgid "Synchronisation target"
|
||||
msgstr "Cible de la synchronisation"
|
||||
|
||||
msgid ""
|
||||
"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)."
|
||||
msgstr ""
|
||||
"La cible avec laquelle synchroniser. Pour synchroniser avec le système de "
|
||||
"fichier, veuillez spécifier le répertoire avec `sync.2.path`."
|
||||
"La cible avec laquelle synchroniser. Chaque cible de synchronisation peut "
|
||||
"avoir des paramètres supplémentaires sous le nom `sync.NUM.NOM` (documentés "
|
||||
"ci-dessous)."
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr "Répertoire avec lequel synchroniser (chemin absolu)"
|
||||
@@ -1065,10 +1165,65 @@ msgstr ""
|
||||
"Le chemin du répertoire avec lequel synchroniser lorsque la synchronisation "
|
||||
"par système de fichier est activée. Voir `sync.target`."
|
||||
|
||||
msgid "Nextcloud WebDAV URL"
|
||||
msgstr "Nextcloud : URL WebDAV"
|
||||
|
||||
msgid "Nextcloud username"
|
||||
msgstr "Nextcloud : Nom utilisateur"
|
||||
|
||||
msgid "Nextcloud password"
|
||||
msgstr "Nextcloud : Mot de passe"
|
||||
|
||||
msgid "WebDAV URL"
|
||||
msgstr "WebDAV : URL"
|
||||
|
||||
msgid "WebDAV username"
|
||||
msgstr "WebDAV : Nom utilisateur"
|
||||
|
||||
msgid "WebDAV password"
|
||||
msgstr "WebDAV : Mot de passe"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr "Option invalide: \"%s\". Les valeurs possibles sont : %s."
|
||||
|
||||
msgid "Joplin Export File"
|
||||
msgstr "Fichier d'export Joplin"
|
||||
|
||||
msgid "Markdown"
|
||||
msgstr "Markdown"
|
||||
|
||||
msgid "Joplin Export Directory"
|
||||
msgstr "Dossier d'export Joplin"
|
||||
|
||||
msgid "Evernote Export File"
|
||||
msgstr "Fichiers d'export Evernote"
|
||||
|
||||
msgid "Directory"
|
||||
msgstr "Dossier"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Cannot load \"%s\" module for format \"%s\""
|
||||
msgstr "Impossible de charger module \"%s\" pour le format \"%s\""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Please specify import format for %s"
|
||||
msgstr "Veuillez spécifier le format d'import pour %s"
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"This item is currently encrypted: %s \"%s\". Please wait for all items to be "
|
||||
"decrypted and try again."
|
||||
msgstr ""
|
||||
"Cet objet est crypté : %s \"%s\". Veuillez attendre que tout soit décrypté "
|
||||
"et réessayez."
|
||||
|
||||
msgid "There is no data to export."
|
||||
msgstr "Il n'y a pas de données à exporter."
|
||||
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr "Veuillez sélectionner le carnet où les notes doivent être importées."
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "Objets qui ne peuvent pas être synchronisés"
|
||||
|
||||
@@ -1139,7 +1294,7 @@ msgid "Configuration"
|
||||
msgstr "Configuration"
|
||||
|
||||
msgid "Move to notebook..."
|
||||
msgstr "Déplacer la note vers carnet..."
|
||||
msgstr "Déplacer vers..."
|
||||
|
||||
#, javascript-format
|
||||
msgid "Move %d notes to notebook \"%s\"?"
|
||||
@@ -1157,6 +1312,9 @@ msgstr "Confirmer"
|
||||
msgid "Cancel synchronisation"
|
||||
msgstr "Annuler synchronisation"
|
||||
|
||||
msgid "Joplin website"
|
||||
msgstr "Site web de Joplin"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Master Key %s"
|
||||
msgstr "Clef maître %s"
|
||||
@@ -1181,6 +1339,12 @@ msgstr "Ce carnet n'a pas pu être sauvegardé : %s"
|
||||
msgid "Edit notebook"
|
||||
msgstr "Éditer le carnet"
|
||||
|
||||
msgid "Show all"
|
||||
msgstr "Afficher tous"
|
||||
|
||||
msgid "Errors only"
|
||||
msgstr "Erreurs seulement"
|
||||
|
||||
msgid "This note has been modified:"
|
||||
msgstr "Cette note a été modifiée :"
|
||||
|
||||
@@ -1236,8 +1400,105 @@ msgstr ""
|
||||
msgid "Welcome"
|
||||
msgstr "Bienvenue"
|
||||
|
||||
#~ msgid "Note title:"
|
||||
#~ msgstr "Titre de la note :"
|
||||
#~ msgid ""
|
||||
#~ "Release notes:\n"
|
||||
#~ "\n"
|
||||
#~ "%s"
|
||||
#~ msgstr ""
|
||||
#~ "Notes de version :\n"
|
||||
#~ "\n"
|
||||
#~ "%s"
|
||||
|
||||
#~ msgid "Imports an Evernote notebook file (.enex file)."
|
||||
#~ msgstr "Importer un carnet Evernote (fichier .enex)."
|
||||
|
||||
#~ msgid ""
|
||||
#~ "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
|
||||
#~ msgstr ""
|
||||
#~ "Le fichier \"%s\" va être importé dans le carnet existant \"%s\". "
|
||||
#~ "Continuer ?"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "New notebook \"%s\" will be created and file \"%s\" will be imported into "
|
||||
#~ "it. Continue?"
|
||||
#~ msgstr ""
|
||||
#~ "Un nouveau carnet \"%s\" va être créé et le fichier \"%s\" va être "
|
||||
#~ "importé dedans. Continuer ?"
|
||||
|
||||
#~ msgid "Import Evernote notes"
|
||||
#~ msgstr "Importer notes d'Evernote"
|
||||
|
||||
#~ msgid "Give focus to next pane"
|
||||
#~ msgstr "Activer le volet suivant"
|
||||
|
||||
#~ msgid "Give focus to previous pane"
|
||||
#~ msgstr "Activer le volet précédent"
|
||||
|
||||
#~ msgid "Enter command line mode"
|
||||
#~ msgstr "Démarrer le mode de ligne de commande"
|
||||
|
||||
#~ msgid "Exit command line mode"
|
||||
#~ msgstr "Sortir du mode de ligne de commande"
|
||||
|
||||
#~ msgid "Edit the selected note"
|
||||
#~ msgstr "Éditer la note sélectionnée"
|
||||
|
||||
#~ msgid "Cancel the current command."
|
||||
#~ msgstr "Annuler la commande en cours."
|
||||
|
||||
#~ msgid "Exit the application."
|
||||
#~ msgstr "Quitter le logiciel."
|
||||
|
||||
#~ msgid "Delete the currently selected note or notebook."
|
||||
#~ msgstr "Supprimer la note ou carnet sélectionné."
|
||||
|
||||
#~ msgid "Set a to-do as completed / not completed"
|
||||
#~ msgstr "Marquer une tâches comme complétée / non-complétée"
|
||||
|
||||
#~ msgid "[t]oggle [c]onsole between maximized/minimized/hidden/visible."
|
||||
#~ msgstr "Maximiser, minimiser, cacher ou rendre visible la console."
|
||||
|
||||
#~ msgid "[t]oggle note [m]etadata."
|
||||
#~ msgstr "Afficher/Cacher les métadonnées des notes."
|
||||
|
||||
#~ msgid "[M]ake a new [n]ote"
|
||||
#~ msgstr "Créer une nouvelle note"
|
||||
|
||||
#~ msgid "[M]ake a new [t]odo"
|
||||
#~ msgstr "Créer une nouvelle tâche"
|
||||
|
||||
#~ msgid "[M]ake a new note[b]ook"
|
||||
#~ msgstr "Créer un nouveau carnet"
|
||||
|
||||
#~ msgid "Copy ([Y]ank) the [n]ote to a notebook."
|
||||
#~ msgstr "Copier la note dans un autre carnet."
|
||||
|
||||
#~ msgid "Move the note to a notebook."
|
||||
#~ msgstr "Déplacer la note vers un carnet."
|
||||
|
||||
#~ 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"
|
||||
|
||||
#~ msgid "New version downloaded - application will quit now and update..."
|
||||
#~ msgstr ""
|
||||
#~ "La nouvelle version a été téléchargée - le programme va se fermer et se "
|
||||
#~ "mettre à jour..."
|
||||
|
||||
#~ msgid "Could not install the update: %s"
|
||||
#~ msgstr "Impossible d'installer la mise à jour : %s"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "The target to synchonise to. If synchronising with the file system, set "
|
||||
#~ "`sync.2.path` to specify the target directory."
|
||||
#~ msgstr ""
|
||||
#~ "La cible avec laquelle synchroniser. Pour synchroniser avec le système de "
|
||||
#~ "fichier, veuillez spécifier le répertoire avec `sync.2.path`."
|
||||
|
||||
#~ msgid "To-do title:"
|
||||
#~ msgstr "Titre de la tâche :"
|
||||
@@ -1303,9 +1564,6 @@ msgstr "Bienvenue"
|
||||
#~ msgid "Todo filter"
|
||||
#~ msgstr "Filtre des tâches"
|
||||
|
||||
#~ msgid "Show all"
|
||||
#~ msgstr "Afficher tous"
|
||||
|
||||
#~ msgid "Non-completed and recently completed ones"
|
||||
#~ msgstr "Tâches non-complétées et récentes"
|
||||
|
||||
|
@@ -17,69 +17,12 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
|
||||
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
|
||||
msgid "Give focus to next pane"
|
||||
msgstr "Fokusiraj sljedeće okno"
|
||||
|
||||
msgid "Give focus to previous pane"
|
||||
msgstr "Fokusiraj prethodno okno"
|
||||
|
||||
msgid "Enter command line mode"
|
||||
msgstr "Otvori naredbeni redak"
|
||||
|
||||
msgid "Exit command line mode"
|
||||
msgstr "Napusti naredbeni redak"
|
||||
|
||||
msgid "Edit the selected note"
|
||||
msgstr "Uredi odabranu bilješku"
|
||||
|
||||
msgid "Cancel the current command."
|
||||
msgstr "Prekini trenutnu naredbu."
|
||||
|
||||
msgid "Exit the application."
|
||||
msgstr "Izađi iz aplikacije."
|
||||
|
||||
msgid "Delete the currently selected note or notebook."
|
||||
msgstr "Obriši odabranu bilješku ili bilježnicu."
|
||||
|
||||
msgid "To delete a tag, untag the associated notes."
|
||||
msgstr "Da bi mogao obrisati oznaku, skini oznaku s povezanih bilješki."
|
||||
|
||||
msgid "Please select the note or notebook to be deleted first."
|
||||
msgstr "Odaberi bilješku ili bilježnicu za brisanje."
|
||||
|
||||
msgid "Set a to-do as completed / not completed"
|
||||
msgstr "Postavi zadatak kao završen/nezavršen"
|
||||
|
||||
#, fuzzy
|
||||
msgid "[t]oggle [c]onsole between maximized/minimized/hidden/visible."
|
||||
msgstr "[t]oggle [c]onsole between maximized/minimized/hidden/visible."
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Traži"
|
||||
|
||||
#, fuzzy
|
||||
msgid "[t]oggle note [m]etadata."
|
||||
msgstr "[t]oggle note [m]etadata."
|
||||
|
||||
#, fuzzy
|
||||
msgid "[M]ake a new [n]ote"
|
||||
msgstr "[M]ake a new [n]ote"
|
||||
|
||||
#, fuzzy
|
||||
msgid "[M]ake a new [t]odo"
|
||||
msgstr "[M]ake a new [t]odo"
|
||||
|
||||
#, fuzzy
|
||||
msgid "[M]ake a new note[b]ook"
|
||||
msgstr "[M]ake a new note[b]ook"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Copy ([Y]ank) the [n]ote to a notebook."
|
||||
msgstr "Copy ([Y]ank) the [n]ote to a notebook."
|
||||
|
||||
msgid "Move the note to a notebook."
|
||||
msgstr "Premjesti bilješku u bilježnicu."
|
||||
|
||||
msgid "Press Ctrl+D or type \"exit\" to exit the application"
|
||||
msgstr "Pritisni Ctrl+D ili napiši \"exit\" za izlazak iz aplikacije"
|
||||
|
||||
@@ -235,19 +178,28 @@ msgid "Starting to edit note. Close the editor to get back to the prompt."
|
||||
msgstr ""
|
||||
"Počinjem uređivati bilješku. Za povratak u naredbeni redak, zatvori uređivač."
|
||||
|
||||
#, javascript-format
|
||||
msgid "Error opening note in editor: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note has been saved."
|
||||
msgstr "Bilješka je spremljena."
|
||||
|
||||
msgid "Exits the application."
|
||||
msgstr "Izlaz iz aplikacije."
|
||||
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"Exports Joplin data to the given directory. By default, it will export the "
|
||||
"Exports Joplin data to the given path. By default, it will export the "
|
||||
"complete database including notebooks, notes, tags and resources."
|
||||
msgstr ""
|
||||
"Izvozi podatke u dati direktorij. Po defaultu izvozi sve podatke uključujući "
|
||||
"bilježnice, bilješke, zadatke i resurse."
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Destination format: %s"
|
||||
msgstr "Format datuma"
|
||||
|
||||
msgid "Exports only the given note."
|
||||
msgstr "Izvozi samo datu bilješku."
|
||||
|
||||
@@ -260,6 +212,10 @@ msgstr "Prikazuje geolokacijski URL bilješke."
|
||||
msgid "Displays usage information."
|
||||
msgstr "Prikazuje informacije o korištenju."
|
||||
|
||||
#, javascript-format
|
||||
msgid "For information on how to customise the shortcuts please visit %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Shortcuts are not available in CLI mode."
|
||||
msgstr "Prečaci nisu podržani u naredbenom retku."
|
||||
|
||||
@@ -303,29 +259,21 @@ msgstr "Za ulaz u naredbeni redak, pritisni \":\""
|
||||
msgid "To exit command line mode, press ESCAPE"
|
||||
msgstr "Za izlaz iz naredbenog retka, pritisni Esc"
|
||||
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"For the complete list of available keyboard shortcuts, type `help shortcuts`"
|
||||
"For the list of keyboard shortcuts and config options, type `help keymap`"
|
||||
msgstr "Za potpunu listu mogućih prečaca, upiši `help shortcuts`"
|
||||
|
||||
msgid "Imports an Evernote notebook file (.enex file)."
|
||||
msgstr "Uvozi Evernote bilježnicu (.enex datoteku)."
|
||||
msgid "Imports data into Joplin."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Source format: %s"
|
||||
msgstr "Ne postoji naredba: %s"
|
||||
|
||||
msgid "Do not ask for confirmation."
|
||||
msgstr "Ne pitaj za potvrdu."
|
||||
|
||||
#, javascript-format
|
||||
msgid "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
|
||||
msgstr ""
|
||||
"Datoteka \"%s\" će biti uvezena u postojeću bilježnicu \"%s\". Nastavi?"
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"New notebook \"%s\" will be created and file \"%s\" will be imported into "
|
||||
"it. Continue?"
|
||||
msgstr ""
|
||||
"Nova bilježnica \"%s\" će biti stvorena i datoteka \"%s\" će biti uvezena u "
|
||||
"nju. Nastavi?"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Found: %d."
|
||||
msgstr "Nađeno: %d."
|
||||
@@ -466,6 +414,16 @@ msgstr "Sinkronizira sa udaljenom pohranom podataka."
|
||||
msgid "Sync to provided target (defaults to sync.target config value)"
|
||||
msgstr "Sinkroniziraj sa metom (default je polje sync.target u konfiguraciji)"
|
||||
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"Authentication was not completed (did not receive an authentication token)."
|
||||
msgstr ""
|
||||
"Ovjera nije dovršena (nije dobivena potvrda ovjere - authentication token)."
|
||||
|
||||
#, javascript-format
|
||||
msgid "Not authentified with %s. Please provide any missing credentials."
|
||||
msgstr ""
|
||||
|
||||
msgid "Synchronisation is already in progress."
|
||||
msgstr "Sinkronizacija je već u toku."
|
||||
|
||||
@@ -477,12 +435,6 @@ msgid ""
|
||||
msgstr ""
|
||||
"Ako sinkronizacija nije u toku, obriši lock datoteku u \"%s\" i nastavi..."
|
||||
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"Authentication was not completed (did not receive an authentication token)."
|
||||
msgstr ""
|
||||
"Ovjera nije dovršena (nije dobivena potvrda ovjere - authentication token)."
|
||||
|
||||
#, javascript-format
|
||||
msgid "Synchronisation target: %s (%s)"
|
||||
msgstr "Meta sinkronizacije: %s (%s)"
|
||||
@@ -558,6 +510,10 @@ msgstr "Default: %s"
|
||||
msgid "Possible keys/values:"
|
||||
msgstr "Mogući ključevi/vrijednosti:"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Type `joplin help` for usage information."
|
||||
msgstr "Prikazuje informacije o korištenju."
|
||||
|
||||
msgid "Fatal error:"
|
||||
msgstr "Fatalna greška:"
|
||||
|
||||
@@ -606,6 +562,18 @@ msgid ""
|
||||
"background and will be available soon."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Exporting to \"%s\" as \"%s\" format. Please wait..."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "PDF File"
|
||||
msgstr "Datoteka"
|
||||
|
||||
msgid "File"
|
||||
msgstr "Datoteka"
|
||||
|
||||
@@ -618,11 +586,19 @@ msgstr "Novi zadatak"
|
||||
msgid "New notebook"
|
||||
msgstr "Nova bilježnica"
|
||||
|
||||
msgid "Import Evernote notes"
|
||||
msgstr "Uvezi Evernote bilješke"
|
||||
msgid "Import"
|
||||
msgstr "Uvoz"
|
||||
|
||||
msgid "Evernote Export Files"
|
||||
msgstr "Evernote izvozne datoteke"
|
||||
#, fuzzy
|
||||
msgid "Export"
|
||||
msgstr "Uvoz"
|
||||
|
||||
msgid "Print"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Hide %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Quit"
|
||||
msgstr "Izađi"
|
||||
@@ -642,6 +618,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"
|
||||
|
||||
@@ -661,6 +643,13 @@ msgstr "Pomoć"
|
||||
msgid "Website and documentation"
|
||||
msgstr "Website i dokumentacija"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Make a donation"
|
||||
msgstr "Website i dokumentacija"
|
||||
|
||||
msgid "Check for updates..."
|
||||
msgstr ""
|
||||
|
||||
msgid "About Joplin"
|
||||
msgstr "O Joplinu"
|
||||
|
||||
@@ -668,12 +657,36 @@ msgstr "O Joplinu"
|
||||
msgid "%s %s (%s, %s)"
|
||||
msgstr "%s %s (%s, %s)"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr "On %s: %s"
|
||||
|
||||
msgid "Exit"
|
||||
msgstr ""
|
||||
|
||||
msgid "OK"
|
||||
msgstr "U redu"
|
||||
|
||||
msgid "Cancel"
|
||||
msgstr "Odustani"
|
||||
|
||||
msgid "Current version is up-to-date."
|
||||
msgstr ""
|
||||
|
||||
msgid "An update is available, do you want to download it now?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Yes"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "No"
|
||||
msgstr "N"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr "Prekini sinkronizaciju"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr "Bilješke i postavke su pohranjene u: %s"
|
||||
@@ -730,6 +743,15 @@ msgid ""
|
||||
"how the notes or notebooks were originally encrypted."
|
||||
msgstr ""
|
||||
|
||||
msgid "Missing Master Keys"
|
||||
msgstr ""
|
||||
|
||||
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 ""
|
||||
|
||||
msgid "Status"
|
||||
msgstr "Status"
|
||||
|
||||
@@ -767,6 +789,9 @@ msgstr "Preimenuj bilježnicu:"
|
||||
msgid "Set alarm:"
|
||||
msgstr "Postavi upozorenje:"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Traži"
|
||||
|
||||
msgid "Layout"
|
||||
msgstr "Izgled"
|
||||
|
||||
@@ -802,6 +827,13 @@ msgid ""
|
||||
"There is currently no notebook. Create one by clicking on \"New notebook\"."
|
||||
msgstr "Ovdje nema bilježnica. Stvori novu pritiskom na \"Nova bilježnica\"."
|
||||
|
||||
msgid "Open..."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Save as..."
|
||||
msgstr "Spremi promjene"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Unsupported link or message: %s"
|
||||
msgstr "Nepodržana poveznica ili poruka: %s"
|
||||
@@ -809,9 +841,30 @@ msgstr "Nepodržana poveznica ili poruka: %s"
|
||||
msgid "Attach file"
|
||||
msgstr "Priloži datoteku"
|
||||
|
||||
msgid "Tags"
|
||||
msgstr "Oznake"
|
||||
|
||||
msgid "Set alarm"
|
||||
msgstr "Postavi upozorenje"
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"This note has no content. Click on \"%s\" to toggle the editor and edit the "
|
||||
"note."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "to-do"
|
||||
msgstr "Novi zadatak"
|
||||
|
||||
#, fuzzy
|
||||
msgid "note"
|
||||
msgstr "Nova bilješka"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Creating new %s..."
|
||||
msgstr "Uvozim bilješke..."
|
||||
|
||||
msgid "Refresh"
|
||||
msgstr "Osvježi"
|
||||
|
||||
@@ -821,9 +874,6 @@ msgstr "Očisti"
|
||||
msgid "OneDrive Login"
|
||||
msgstr "OneDrive Login"
|
||||
|
||||
msgid "Import"
|
||||
msgstr "Uvoz"
|
||||
|
||||
msgid "Options"
|
||||
msgstr "Opcije"
|
||||
|
||||
@@ -848,9 +898,6 @@ msgstr "Sinkroniziraj"
|
||||
msgid "Notebooks"
|
||||
msgstr "Bilježnice"
|
||||
|
||||
msgid "Tags"
|
||||
msgstr "Oznake"
|
||||
|
||||
msgid "Searches"
|
||||
msgstr "Pretraživanja"
|
||||
|
||||
@@ -868,12 +915,18 @@ msgstr "Nepoznata zastavica: %s"
|
||||
msgid "File system"
|
||||
msgstr "Datotečni sustav"
|
||||
|
||||
msgid "Nextcloud"
|
||||
msgstr ""
|
||||
|
||||
msgid "OneDrive"
|
||||
msgstr "OneDrive"
|
||||
|
||||
msgid "OneDrive Dev (For testing only)"
|
||||
msgstr "OneDrive Dev (Samo za testiranje)"
|
||||
|
||||
msgid "WebDAV"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Unknown log level: %s"
|
||||
msgstr "Nepoznata razina logiranja: %s"
|
||||
@@ -945,6 +998,16 @@ msgstr "Prekidam..."
|
||||
msgid "Completed: %s"
|
||||
msgstr "Dovršeno: %s"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Last error: %s"
|
||||
msgstr "Fatalna greška:"
|
||||
|
||||
msgid "Idle"
|
||||
msgstr ""
|
||||
|
||||
msgid "In progress"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr "Sinkronizacija je već u toku. Stanje: %s"
|
||||
@@ -1009,12 +1072,52 @@ msgstr "Svijetla"
|
||||
msgid "Dark"
|
||||
msgstr "Tamna"
|
||||
|
||||
msgid "Show uncompleted todos on top of the lists"
|
||||
#, fuzzy
|
||||
msgid "Uncompleted to-dos on top"
|
||||
msgstr "Prikaži nezavršene zadatke na vrhu liste"
|
||||
|
||||
msgid "Sort notes by"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Reverse sort order"
|
||||
msgstr "Mijenja redoslijed."
|
||||
|
||||
msgid "Save geo-location with notes"
|
||||
msgstr "Spremi geolokacijske podatke sa bilješkama"
|
||||
|
||||
#, fuzzy
|
||||
msgid "When creating a new to-do:"
|
||||
msgstr "Stvara novi zadatak."
|
||||
|
||||
#, fuzzy
|
||||
msgid "Focus title"
|
||||
msgstr "Naslov bilješke:"
|
||||
|
||||
msgid "Focus body"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "When creating a new note:"
|
||||
msgstr "Stvara novu bilješku."
|
||||
|
||||
msgid "Show tray icon"
|
||||
msgstr ""
|
||||
|
||||
msgid "Global zoom percentage"
|
||||
msgstr ""
|
||||
|
||||
msgid "Editor font family"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"The font name will not be checked. If incorrect or empty, it will default to "
|
||||
"a generic monospace font."
|
||||
msgstr ""
|
||||
|
||||
msgid "Automatically update the application"
|
||||
msgstr "Automatsko instaliranje nove verzije"
|
||||
|
||||
msgid "Synchronisation interval"
|
||||
msgstr "Interval sinkronizacije"
|
||||
|
||||
@@ -1030,9 +1133,6 @@ msgstr "%d sat"
|
||||
msgid "%d hours"
|
||||
msgstr "%d sati"
|
||||
|
||||
msgid "Automatically update the application"
|
||||
msgstr "Automatsko instaliranje nove verzije"
|
||||
|
||||
msgid "Show advanced options"
|
||||
msgstr "Prikaži napredne opcije"
|
||||
|
||||
@@ -1040,11 +1140,9 @@ msgid "Synchronisation target"
|
||||
msgstr "Sinkroniziraj sa"
|
||||
|
||||
msgid ""
|
||||
"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)."
|
||||
msgstr ""
|
||||
"Meta sinkronizacije. U slučaju sinkroniziranja s vlastitim datotečnim "
|
||||
"sustavom, postavi `sync.2.path` na ciljani direktorij."
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr "Direktorij za sinkroniziranje (apsolutna putanja)"
|
||||
@@ -1056,10 +1154,66 @@ msgstr ""
|
||||
"Putanja do direktorija za sinkronizaciju u slučaju kad je sinkronizacija sa "
|
||||
"datotečnim sustavom omogućena. Vidi `sync.target`."
|
||||
|
||||
msgid "Nextcloud WebDAV URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud username"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud password"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV username"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV password"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr "Nevažeća vrijednost: \"%s\". Moguće vrijednosti su: %s."
|
||||
|
||||
#, fuzzy
|
||||
msgid "Joplin Export File"
|
||||
msgstr "Evernote izvozne datoteke"
|
||||
|
||||
msgid "Markdown"
|
||||
msgstr ""
|
||||
|
||||
msgid "Joplin Export Directory"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Evernote Export File"
|
||||
msgstr "Evernote izvozne datoteke"
|
||||
|
||||
msgid "Directory"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Cannot load \"%s\" module for format \"%s\""
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Please specify import format for %s"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"This item is currently encrypted: %s \"%s\". Please wait for all items to be "
|
||||
"decrypted and try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "There is no data to export."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr "Odaberi lokaciju za izvoz statusa sinkronizacije"
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "Stavke koje se ne mogu sinkronizirati"
|
||||
|
||||
@@ -1143,6 +1297,9 @@ msgstr "Potvrdi"
|
||||
msgid "Cancel synchronisation"
|
||||
msgstr "Prekini sinkronizaciju"
|
||||
|
||||
msgid "Joplin website"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Master Key %s"
|
||||
msgstr ""
|
||||
@@ -1168,6 +1325,12 @@ msgstr "Bilježnicu nije moguće snimiti: %s"
|
||||
msgid "Edit notebook"
|
||||
msgstr "Uredi bilježnicu"
|
||||
|
||||
msgid "Show all"
|
||||
msgstr ""
|
||||
|
||||
msgid "Errors only"
|
||||
msgstr ""
|
||||
|
||||
msgid "This note has been modified:"
|
||||
msgstr "Bilješka je promijenjena:"
|
||||
|
||||
@@ -1221,8 +1384,91 @@ msgstr "Trenutno nemaš nijednu bilježnicu. Stvori novu klikom na (+) gumb."
|
||||
msgid "Welcome"
|
||||
msgstr "Dobro došli"
|
||||
|
||||
#~ msgid "Note title:"
|
||||
#~ msgstr "Naslov bilješke:"
|
||||
#, fuzzy
|
||||
#~ msgid ""
|
||||
#~ "Release notes:\n"
|
||||
#~ "\n"
|
||||
#~ "%s"
|
||||
#~ msgstr "Obriši bilješke?"
|
||||
|
||||
#~ msgid "Imports an Evernote notebook file (.enex file)."
|
||||
#~ msgstr "Uvozi Evernote bilježnicu (.enex datoteku)."
|
||||
|
||||
#~ msgid ""
|
||||
#~ "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
|
||||
#~ msgstr ""
|
||||
#~ "Datoteka \"%s\" će biti uvezena u postojeću bilježnicu \"%s\". Nastavi?"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "New notebook \"%s\" will be created and file \"%s\" will be imported into "
|
||||
#~ "it. Continue?"
|
||||
#~ msgstr ""
|
||||
#~ "Nova bilježnica \"%s\" će biti stvorena i datoteka \"%s\" će biti uvezena "
|
||||
#~ "u nju. Nastavi?"
|
||||
|
||||
#~ msgid "Import Evernote notes"
|
||||
#~ msgstr "Uvezi Evernote bilješke"
|
||||
|
||||
#~ msgid "Give focus to next pane"
|
||||
#~ msgstr "Fokusiraj sljedeće okno"
|
||||
|
||||
#~ msgid "Give focus to previous pane"
|
||||
#~ msgstr "Fokusiraj prethodno okno"
|
||||
|
||||
#~ msgid "Enter command line mode"
|
||||
#~ msgstr "Otvori naredbeni redak"
|
||||
|
||||
#~ msgid "Exit command line mode"
|
||||
#~ msgstr "Napusti naredbeni redak"
|
||||
|
||||
#~ msgid "Edit the selected note"
|
||||
#~ msgstr "Uredi odabranu bilješku"
|
||||
|
||||
#~ msgid "Cancel the current command."
|
||||
#~ msgstr "Prekini trenutnu naredbu."
|
||||
|
||||
#~ msgid "Exit the application."
|
||||
#~ msgstr "Izađi iz aplikacije."
|
||||
|
||||
#~ msgid "Delete the currently selected note or notebook."
|
||||
#~ msgstr "Obriši odabranu bilješku ili bilježnicu."
|
||||
|
||||
#~ msgid "Set a to-do as completed / not completed"
|
||||
#~ msgstr "Postavi zadatak kao završen/nezavršen"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "[t]oggle [c]onsole between maximized/minimized/hidden/visible."
|
||||
#~ msgstr "[t]oggle [c]onsole between maximized/minimized/hidden/visible."
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "[t]oggle note [m]etadata."
|
||||
#~ msgstr "[t]oggle note [m]etadata."
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "[M]ake a new [n]ote"
|
||||
#~ msgstr "[M]ake a new [n]ote"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "[M]ake a new [t]odo"
|
||||
#~ msgstr "[M]ake a new [t]odo"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "[M]ake a new note[b]ook"
|
||||
#~ msgstr "[M]ake a new note[b]ook"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "Copy ([Y]ank) the [n]ote to a notebook."
|
||||
#~ msgstr "Copy ([Y]ank) the [n]ote to a notebook."
|
||||
|
||||
#~ msgid "Move the note to a notebook."
|
||||
#~ msgstr "Premjesti bilješku u bilježnicu."
|
||||
|
||||
#~ msgid ""
|
||||
#~ "The target to synchonise to. If synchronising with the file system, set "
|
||||
#~ "`sync.2.path` to specify the target directory."
|
||||
#~ msgstr ""
|
||||
#~ "Meta sinkronizacije. U slučaju sinkroniziranja s vlastitim datotečnim "
|
||||
#~ "sustavom, postavi `sync.2.path` na ciljani direktorij."
|
||||
|
||||
#~ msgid "To-do title:"
|
||||
#~ msgstr "Naslov zadatka:"
|
||||
|
@@ -16,65 +16,12 @@ msgstr ""
|
||||
"X-Generator: Poedit 2.0.3\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "Give focus to next pane"
|
||||
msgstr "Pannello successivo"
|
||||
|
||||
msgid "Give focus to previous pane"
|
||||
msgstr "Pannello precedente"
|
||||
|
||||
msgid "Enter command line mode"
|
||||
msgstr "Accedi alla modalità linea di comando"
|
||||
|
||||
msgid "Exit command line mode"
|
||||
msgstr "Esci dalla modalità linea di comando"
|
||||
|
||||
msgid "Edit the selected note"
|
||||
msgstr "Modifica la nota selezionata"
|
||||
|
||||
msgid "Cancel the current command."
|
||||
msgstr "Cancella il comando corrente."
|
||||
|
||||
msgid "Exit the application."
|
||||
msgstr "Esci dall'applicazione."
|
||||
|
||||
msgid "Delete the currently selected note or notebook."
|
||||
msgstr "Elimina la nota o il blocco note selezionato."
|
||||
|
||||
msgid "To delete a tag, untag the associated notes."
|
||||
msgstr "Elimina un'etichetta, togli l'etichetta associata alle note."
|
||||
|
||||
msgid "Please select the note or notebook to be deleted first."
|
||||
msgstr "Per favore seleziona la nota o il blocco note da eliminare."
|
||||
|
||||
msgid "Set a to-do as completed / not completed"
|
||||
msgstr "Imposta un'attività come completata / non completata"
|
||||
|
||||
msgid "[t]oggle [c]onsole between maximized/minimized/hidden/visible."
|
||||
msgstr ""
|
||||
"Scegli lo s[t]ato della [c]onsole: massimizzato/minimizzato/nascosto/"
|
||||
"visibile."
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Cerca"
|
||||
|
||||
msgid "[t]oggle note [m]etadata."
|
||||
msgstr "mos[t]ra/nascondi i [m]etadata nelle note."
|
||||
|
||||
msgid "[M]ake a new [n]ote"
|
||||
msgstr "Crea ([M]ake) una nuova [n]ota"
|
||||
|
||||
msgid "[M]ake a new [t]odo"
|
||||
msgstr "Crea ([M]ake) una nuova at[t]ività"
|
||||
|
||||
msgid "[M]ake a new note[b]ook"
|
||||
msgstr "Crea ([M]ake) un nuovo [b]locco note"
|
||||
|
||||
msgid "Copy ([Y]ank) the [n]ote to a notebook."
|
||||
msgstr "Copia ([Y]) la [n]ota in un blocco note."
|
||||
|
||||
msgid "Move the note to a notebook."
|
||||
msgstr "Sposta la nota in un blocco note."
|
||||
|
||||
msgid "Press Ctrl+D or type \"exit\" to exit the application"
|
||||
msgstr "Premi Ctrl+D o digita \"exit\" per uscire dall'applicazione"
|
||||
|
||||
@@ -226,20 +173,29 @@ msgstr "Non esiste la nota: \"%s\". Desideri crearla?"
|
||||
msgid "Starting to edit note. Close the editor to get back to the prompt."
|
||||
msgstr "Comincia a modificare la nota. Chiudi l'editor per tornare al prompt."
|
||||
|
||||
#, javascript-format
|
||||
msgid "Error opening note in editor: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note has been saved."
|
||||
msgstr "La nota è stata salvata."
|
||||
|
||||
msgid "Exits the application."
|
||||
msgstr "Esci dall'applicazione."
|
||||
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"Exports Joplin data to the given directory. By default, it will export the "
|
||||
"Exports Joplin data to the given path. By default, it will export the "
|
||||
"complete database including notebooks, notes, tags and resources."
|
||||
msgstr ""
|
||||
"Esporta i dati da Joplin nella directory selezionata. Come impostazione "
|
||||
"predefinita verrà esportato il database completo, inclusi blocchi note, "
|
||||
"note, etichette e risorse."
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Destination format: %s"
|
||||
msgstr "Formato della data"
|
||||
|
||||
msgid "Exports only the given note."
|
||||
msgstr "Esporta solo la seguente nota."
|
||||
|
||||
@@ -252,6 +208,10 @@ msgstr "Mostra l'URL di geolocalizzazione per la nota."
|
||||
msgid "Displays usage information."
|
||||
msgstr "Mostra le informazioni di utilizzo."
|
||||
|
||||
#, javascript-format
|
||||
msgid "For information on how to customise the shortcuts please visit %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Shortcuts are not available in CLI mode."
|
||||
msgstr "Le scorciatoie non sono disponibili nella modalità CLI."
|
||||
|
||||
@@ -294,30 +254,22 @@ msgstr "Per entrare nella modalità command line, premi \":\""
|
||||
msgid "To exit command line mode, press ESCAPE"
|
||||
msgstr "Per uscire dalla modalità command line, premi ESC"
|
||||
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"For the complete list of available keyboard shortcuts, type `help shortcuts`"
|
||||
"For the list of keyboard shortcuts and config options, type `help keymap`"
|
||||
msgstr ""
|
||||
"Per la lista completa delle scorciatoie disponibili, digita `help shortcuts`"
|
||||
|
||||
msgid "Imports an Evernote notebook file (.enex file)."
|
||||
msgstr "Importa un file blocco note di Evernote (.enex file)."
|
||||
msgid "Imports data into Joplin."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Source format: %s"
|
||||
msgstr "Nessun comando: %s"
|
||||
|
||||
msgid "Do not ask for confirmation."
|
||||
msgstr "Non chiedere conferma."
|
||||
|
||||
#, javascript-format
|
||||
msgid "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
|
||||
msgstr ""
|
||||
"Il file \"%s\" sarà importato nel blocco note esistente \"%s\". Continuare?"
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"New notebook \"%s\" will be created and file \"%s\" will be imported into "
|
||||
"it. Continue?"
|
||||
msgstr ""
|
||||
"Un nuovo blocco note \"%s\" sarà creato e al suo interno verrà importato il "
|
||||
"file \"%s\" . Continuare?"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Found: %d."
|
||||
msgstr "Trovato: %d."
|
||||
@@ -449,6 +401,16 @@ msgstr ""
|
||||
"Sincronizza con l'obiettivo fornito (come predefinito il valore di "
|
||||
"configurazione sync.target)"
|
||||
|
||||
msgid ""
|
||||
"Authentication was not completed (did not receive an authentication token)."
|
||||
msgstr ""
|
||||
"Autenticazione non completata (non è stato ricevuto alcun token di "
|
||||
"autenticazione)."
|
||||
|
||||
#, javascript-format
|
||||
msgid "Not authentified with %s. Please provide any missing credentials."
|
||||
msgstr ""
|
||||
|
||||
msgid "Synchronisation is already in progress."
|
||||
msgstr "La sincronizzazione è in corso."
|
||||
|
||||
@@ -462,12 +424,6 @@ msgstr ""
|
||||
"sincronizzazione, è possibile eliminare il file di blocco in \"% s\" e "
|
||||
"riprendere l'operazione."
|
||||
|
||||
msgid ""
|
||||
"Authentication was not completed (did not receive an authentication token)."
|
||||
msgstr ""
|
||||
"Autenticazione non completata (non è stato ricevuto alcun token di "
|
||||
"autenticazione)."
|
||||
|
||||
#, javascript-format
|
||||
msgid "Synchronisation target: %s (%s)"
|
||||
msgstr "Posizione di sincronizzazione: %s (%s)"
|
||||
@@ -540,6 +496,10 @@ msgstr "Predefinito: %s"
|
||||
msgid "Possible keys/values:"
|
||||
msgstr "Chiave/valore possibili:"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Type `joplin help` for usage information."
|
||||
msgstr "Mostra le informazioni di utilizzo."
|
||||
|
||||
msgid "Fatal error:"
|
||||
msgstr "Errore fatale:"
|
||||
|
||||
@@ -584,6 +544,18 @@ msgid ""
|
||||
"background and will be available soon."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Exporting to \"%s\" as \"%s\" format. Please wait..."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "PDF File"
|
||||
msgstr "File"
|
||||
|
||||
msgid "File"
|
||||
msgstr "File"
|
||||
|
||||
@@ -596,11 +568,19 @@ msgstr "Nuova attività"
|
||||
msgid "New notebook"
|
||||
msgstr "Nuovo blocco note"
|
||||
|
||||
msgid "Import Evernote notes"
|
||||
msgstr "Importa le note da Evernote"
|
||||
msgid "Import"
|
||||
msgstr "Importa"
|
||||
|
||||
msgid "Evernote Export Files"
|
||||
msgstr "Esposta i files di Evernote"
|
||||
#, fuzzy
|
||||
msgid "Export"
|
||||
msgstr "Importa"
|
||||
|
||||
msgid "Print"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Hide %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Quit"
|
||||
msgstr "Esci"
|
||||
@@ -620,6 +600,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"
|
||||
|
||||
@@ -639,6 +625,13 @@ msgstr "Aiuto"
|
||||
msgid "Website and documentation"
|
||||
msgstr "Sito web e documentazione"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Make a donation"
|
||||
msgstr "Sito web e documentazione"
|
||||
|
||||
msgid "Check for updates..."
|
||||
msgstr ""
|
||||
|
||||
msgid "About Joplin"
|
||||
msgstr "Informazione si Joplin"
|
||||
|
||||
@@ -646,12 +639,36 @@ msgstr "Informazione si Joplin"
|
||||
msgid "%s %s (%s, %s)"
|
||||
msgstr "%s %s (%s, %s)"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr "Su %s: %s"
|
||||
|
||||
msgid "Exit"
|
||||
msgstr ""
|
||||
|
||||
msgid "OK"
|
||||
msgstr "OK"
|
||||
|
||||
msgid "Cancel"
|
||||
msgstr "Cancella"
|
||||
|
||||
msgid "Current version is up-to-date."
|
||||
msgstr ""
|
||||
|
||||
msgid "An update is available, do you want to download it now?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Yes"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "No"
|
||||
msgstr "N"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr "Cancella la sincronizzazione"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr ""
|
||||
@@ -710,6 +727,15 @@ msgid ""
|
||||
"how the notes or notebooks were originally encrypted."
|
||||
msgstr ""
|
||||
|
||||
msgid "Missing Master Keys"
|
||||
msgstr ""
|
||||
|
||||
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 ""
|
||||
|
||||
msgid "Status"
|
||||
msgstr "Stato"
|
||||
|
||||
@@ -745,6 +771,9 @@ msgstr "Rinomina il blocco note:"
|
||||
msgid "Set alarm:"
|
||||
msgstr "Imposta allarme:"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Cerca"
|
||||
|
||||
msgid "Layout"
|
||||
msgstr "Disposizione"
|
||||
|
||||
@@ -781,6 +810,13 @@ msgid ""
|
||||
"There is currently no notebook. Create one by clicking on \"New notebook\"."
|
||||
msgstr "Al momento non ci sono note. Creane una cliccando sul bottone (+)."
|
||||
|
||||
msgid "Open..."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Save as..."
|
||||
msgstr "Salva i cambiamenti"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Unsupported link or message: %s"
|
||||
msgstr "Collegamento o messaggio non supportato: %s"
|
||||
@@ -788,9 +824,30 @@ msgstr "Collegamento o messaggio non supportato: %s"
|
||||
msgid "Attach file"
|
||||
msgstr "Allega file"
|
||||
|
||||
msgid "Tags"
|
||||
msgstr "Etichette"
|
||||
|
||||
msgid "Set alarm"
|
||||
msgstr "Imposta allarme"
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"This note has no content. Click on \"%s\" to toggle the editor and edit the "
|
||||
"note."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "to-do"
|
||||
msgstr "Nuova attività"
|
||||
|
||||
#, fuzzy
|
||||
msgid "note"
|
||||
msgstr "Nuova nota"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Creating new %s..."
|
||||
msgstr "Importazione delle note..."
|
||||
|
||||
msgid "Refresh"
|
||||
msgstr "Aggiorna"
|
||||
|
||||
@@ -800,9 +857,6 @@ msgstr "Pulisci"
|
||||
msgid "OneDrive Login"
|
||||
msgstr "Login OneDrive"
|
||||
|
||||
msgid "Import"
|
||||
msgstr "Importa"
|
||||
|
||||
msgid "Options"
|
||||
msgstr "Opzioni"
|
||||
|
||||
@@ -827,9 +881,6 @@ msgstr "Sincronizza"
|
||||
msgid "Notebooks"
|
||||
msgstr "Blocchi note"
|
||||
|
||||
msgid "Tags"
|
||||
msgstr "Etichette"
|
||||
|
||||
msgid "Searches"
|
||||
msgstr "Ricerche"
|
||||
|
||||
@@ -848,12 +899,18 @@ msgstr "Etichetta sconosciuta: %s"
|
||||
msgid "File system"
|
||||
msgstr "File system"
|
||||
|
||||
msgid "Nextcloud"
|
||||
msgstr ""
|
||||
|
||||
msgid "OneDrive"
|
||||
msgstr "OneDrive"
|
||||
|
||||
msgid "OneDrive Dev (For testing only)"
|
||||
msgstr "OneDrive Dev (solo per test)"
|
||||
|
||||
msgid "WebDAV"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Unknown log level: %s"
|
||||
msgstr "Livello di log sconosciuto: %s"
|
||||
@@ -927,6 +984,16 @@ msgstr "Cancellazione..."
|
||||
msgid "Completed: %s"
|
||||
msgstr "Completata: %s"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Last error: %s"
|
||||
msgstr "Errore fatale:"
|
||||
|
||||
msgid "Idle"
|
||||
msgstr ""
|
||||
|
||||
msgid "In progress"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr "La sincronizzazione è già in corso. Stato: %s"
|
||||
@@ -991,12 +1058,52 @@ msgstr "Chiaro"
|
||||
msgid "Dark"
|
||||
msgstr "Scuro"
|
||||
|
||||
msgid "Show uncompleted todos on top of the lists"
|
||||
#, fuzzy
|
||||
msgid "Uncompleted to-dos on top"
|
||||
msgstr "Mostra todo inclompleti in cima alla lista"
|
||||
|
||||
msgid "Sort notes by"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Reverse sort order"
|
||||
msgstr "Inverti l'ordine."
|
||||
|
||||
msgid "Save geo-location with notes"
|
||||
msgstr "Salva geo-localizzazione con le note"
|
||||
|
||||
#, fuzzy
|
||||
msgid "When creating a new to-do:"
|
||||
msgstr "Crea una nuova attività."
|
||||
|
||||
#, fuzzy
|
||||
msgid "Focus title"
|
||||
msgstr "Titolo della Nota:"
|
||||
|
||||
msgid "Focus body"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "When creating a new note:"
|
||||
msgstr "Crea una nuova nota."
|
||||
|
||||
msgid "Show tray icon"
|
||||
msgstr ""
|
||||
|
||||
msgid "Global zoom percentage"
|
||||
msgstr ""
|
||||
|
||||
msgid "Editor font family"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"The font name will not be checked. If incorrect or empty, it will default to "
|
||||
"a generic monospace font."
|
||||
msgstr ""
|
||||
|
||||
msgid "Automatically update the application"
|
||||
msgstr "Aggiorna automaticamente l'applicazione"
|
||||
|
||||
msgid "Synchronisation interval"
|
||||
msgstr "Intervallo di sincronizzazione"
|
||||
|
||||
@@ -1012,9 +1119,6 @@ msgstr "%d ora"
|
||||
msgid "%d hours"
|
||||
msgstr "%d ore"
|
||||
|
||||
msgid "Automatically update the application"
|
||||
msgstr "Aggiorna automaticamente l'applicazione"
|
||||
|
||||
msgid "Show advanced options"
|
||||
msgstr "Mostra opzioni avanzate"
|
||||
|
||||
@@ -1022,12 +1126,9 @@ msgid "Synchronisation target"
|
||||
msgstr "Destinazione di sincronizzazione"
|
||||
|
||||
msgid ""
|
||||
"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)."
|
||||
msgstr ""
|
||||
"La destinazione della sincronizzazione. Se si sincronizza con il file "
|
||||
"system, impostare ' Sync. 2. Path ' per specificare la directory di "
|
||||
"destinazione."
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr ""
|
||||
@@ -1039,10 +1140,66 @@ msgstr ""
|
||||
"Il percorso di sincronizzazione quando la sincronizzazione è abilitata. Vedi "
|
||||
"`sync.target`."
|
||||
|
||||
msgid "Nextcloud WebDAV URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud username"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud password"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV username"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV password"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr "Oprione non valida: \"%s\". I valori possibili sono: %s."
|
||||
|
||||
#, fuzzy
|
||||
msgid "Joplin Export File"
|
||||
msgstr "Esposta i files di Evernote"
|
||||
|
||||
msgid "Markdown"
|
||||
msgstr ""
|
||||
|
||||
msgid "Joplin Export Directory"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Evernote Export File"
|
||||
msgstr "Esposta i files di Evernote"
|
||||
|
||||
msgid "Directory"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Cannot load \"%s\" module for format \"%s\""
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Please specify import format for %s"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"This item is currently encrypted: %s \"%s\". Please wait for all items to be "
|
||||
"decrypted and try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "There is no data to export."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr "Per favore seleziona la nota o il blocco note da eliminare."
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "Elementi che non possono essere sincronizzati"
|
||||
|
||||
@@ -1126,6 +1283,9 @@ msgstr "Conferma"
|
||||
msgid "Cancel synchronisation"
|
||||
msgstr "Cancella la sincronizzazione"
|
||||
|
||||
msgid "Joplin website"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Master Key %s"
|
||||
msgstr ""
|
||||
@@ -1151,6 +1311,12 @@ msgstr "Il blocco note non può essere salvato: %s"
|
||||
msgid "Edit notebook"
|
||||
msgstr "Modifica blocco note"
|
||||
|
||||
msgid "Show all"
|
||||
msgstr ""
|
||||
|
||||
msgid "Errors only"
|
||||
msgstr ""
|
||||
|
||||
msgid "This note has been modified:"
|
||||
msgstr "Questa note è stata modificata:"
|
||||
|
||||
@@ -1206,8 +1372,89 @@ msgstr ""
|
||||
msgid "Welcome"
|
||||
msgstr "Benvenuto"
|
||||
|
||||
#~ msgid "Note title:"
|
||||
#~ msgstr "Titolo della Nota:"
|
||||
#, fuzzy
|
||||
#~ msgid ""
|
||||
#~ "Release notes:\n"
|
||||
#~ "\n"
|
||||
#~ "%s"
|
||||
#~ msgstr "Eliminare le note?"
|
||||
|
||||
#~ msgid "Imports an Evernote notebook file (.enex file)."
|
||||
#~ msgstr "Importa un file blocco note di Evernote (.enex file)."
|
||||
|
||||
#~ msgid ""
|
||||
#~ "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
|
||||
#~ msgstr ""
|
||||
#~ "Il file \"%s\" sarà importato nel blocco note esistente \"%s\". "
|
||||
#~ "Continuare?"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "New notebook \"%s\" will be created and file \"%s\" will be imported into "
|
||||
#~ "it. Continue?"
|
||||
#~ msgstr ""
|
||||
#~ "Un nuovo blocco note \"%s\" sarà creato e al suo interno verrà importato "
|
||||
#~ "il file \"%s\" . Continuare?"
|
||||
|
||||
#~ msgid "Import Evernote notes"
|
||||
#~ msgstr "Importa le note da Evernote"
|
||||
|
||||
#~ msgid "Give focus to next pane"
|
||||
#~ msgstr "Pannello successivo"
|
||||
|
||||
#~ msgid "Give focus to previous pane"
|
||||
#~ msgstr "Pannello precedente"
|
||||
|
||||
#~ msgid "Enter command line mode"
|
||||
#~ msgstr "Accedi alla modalità linea di comando"
|
||||
|
||||
#~ msgid "Exit command line mode"
|
||||
#~ msgstr "Esci dalla modalità linea di comando"
|
||||
|
||||
#~ msgid "Edit the selected note"
|
||||
#~ msgstr "Modifica la nota selezionata"
|
||||
|
||||
#~ msgid "Cancel the current command."
|
||||
#~ msgstr "Cancella il comando corrente."
|
||||
|
||||
#~ msgid "Exit the application."
|
||||
#~ msgstr "Esci dall'applicazione."
|
||||
|
||||
#~ msgid "Delete the currently selected note or notebook."
|
||||
#~ msgstr "Elimina la nota o il blocco note selezionato."
|
||||
|
||||
#~ msgid "Set a to-do as completed / not completed"
|
||||
#~ msgstr "Imposta un'attività come completata / non completata"
|
||||
|
||||
#~ msgid "[t]oggle [c]onsole between maximized/minimized/hidden/visible."
|
||||
#~ msgstr ""
|
||||
#~ "Scegli lo s[t]ato della [c]onsole: massimizzato/minimizzato/nascosto/"
|
||||
#~ "visibile."
|
||||
|
||||
#~ msgid "[t]oggle note [m]etadata."
|
||||
#~ msgstr "mos[t]ra/nascondi i [m]etadata nelle note."
|
||||
|
||||
#~ msgid "[M]ake a new [n]ote"
|
||||
#~ msgstr "Crea ([M]ake) una nuova [n]ota"
|
||||
|
||||
#~ msgid "[M]ake a new [t]odo"
|
||||
#~ msgstr "Crea ([M]ake) una nuova at[t]ività"
|
||||
|
||||
#~ msgid "[M]ake a new note[b]ook"
|
||||
#~ msgstr "Crea ([M]ake) un nuovo [b]locco note"
|
||||
|
||||
#~ msgid "Copy ([Y]ank) the [n]ote to a notebook."
|
||||
#~ msgstr "Copia ([Y]) la [n]ota in un blocco note."
|
||||
|
||||
#~ msgid "Move the note to a notebook."
|
||||
#~ msgstr "Sposta la nota in un blocco note."
|
||||
|
||||
#~ msgid ""
|
||||
#~ "The target to synchonise to. If synchronising with the file system, set "
|
||||
#~ "`sync.2.path` to specify the target directory."
|
||||
#~ msgstr ""
|
||||
#~ "La destinazione della sincronizzazione. Se si sincronizza con il file "
|
||||
#~ "system, impostare ' Sync. 2. Path ' per specificare la directory di "
|
||||
#~ "destinazione."
|
||||
|
||||
#~ msgid "To-do title:"
|
||||
#~ msgstr "Titolo dell'attività:"
|
||||
|
@@ -16,63 +16,12 @@ msgstr ""
|
||||
"X-Generator: Poedit 2.0.5\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
msgid "Give focus to next pane"
|
||||
msgstr "次のペインへ"
|
||||
|
||||
msgid "Give focus to previous pane"
|
||||
msgstr "前のペインへ"
|
||||
|
||||
msgid "Enter command line mode"
|
||||
msgstr "コマンドラインモードに入る"
|
||||
|
||||
msgid "Exit command line mode"
|
||||
msgstr "コマンドラインモードの終了"
|
||||
|
||||
msgid "Edit the selected note"
|
||||
msgstr "選択したノートを編集"
|
||||
|
||||
msgid "Cancel the current command."
|
||||
msgstr "現在のコマンドをキャンセル"
|
||||
|
||||
msgid "Exit the application."
|
||||
msgstr "アプリケーションを終了する"
|
||||
|
||||
msgid "Delete the currently selected note or notebook."
|
||||
msgstr "選択中のノートまたはノートブックを削除"
|
||||
|
||||
msgid "To delete a tag, untag the associated notes."
|
||||
msgstr "タグを削除するには、関連するノートからタグを外してください。"
|
||||
|
||||
msgid "Please select the note or notebook to be deleted first."
|
||||
msgstr "ます削除するノートかノートブックを選択してください。"
|
||||
|
||||
msgid "Set a to-do as completed / not completed"
|
||||
msgstr "ToDoを完了/未完に設定"
|
||||
|
||||
msgid "[t]oggle [c]onsole between maximized/minimized/hidden/visible."
|
||||
msgstr "コンソールを最大表示/最小表示/非表示/可視で切り替える([t][c])"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "検索"
|
||||
|
||||
msgid "[t]oggle note [m]etadata."
|
||||
msgstr "ノートのメタ情報を切り替える [tm]"
|
||||
|
||||
msgid "[M]ake a new [n]ote"
|
||||
msgstr "新しいノートの作成 [mn]"
|
||||
|
||||
msgid "[M]ake a new [t]odo"
|
||||
msgstr "新しいToDoの作成 [mt]"
|
||||
|
||||
msgid "[M]ake a new note[b]ook"
|
||||
msgstr "新しいノートブックの作成 [mb]"
|
||||
|
||||
msgid "Copy ([Y]ank) the [n]ote to a notebook."
|
||||
msgstr "ノートをノートブックにコピー [yn]"
|
||||
|
||||
msgid "Move the note to a notebook."
|
||||
msgstr "ノートをノートブックに移動"
|
||||
|
||||
msgid "Press Ctrl+D or type \"exit\" to exit the application"
|
||||
msgstr "アプリケーションを終了するには、Ctrl+Dまたは\"exit\"と入力してください"
|
||||
|
||||
@@ -223,19 +172,28 @@ msgstr "\"%s\"というノートはありません。お作りいたしますか
|
||||
msgid "Starting to edit note. Close the editor to get back to the prompt."
|
||||
msgstr "ノートの編集の開始。エディタを閉じると元の画面に戻ることが出来ます。"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Error opening note in editor: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note has been saved."
|
||||
msgstr "ノートは保存されました。"
|
||||
|
||||
msgid "Exits the application."
|
||||
msgstr "アプリケーションの終了。"
|
||||
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"Exports Joplin data to the given directory. By default, it will export the "
|
||||
"Exports Joplin data to the given path. By default, it will export the "
|
||||
"complete database including notebooks, notes, tags and resources."
|
||||
msgstr ""
|
||||
"Joplinのデータを選択されたディレクトリに出力する。標準では、ノートブック・"
|
||||
"ノート・タグ・添付データを含むすべてのデータベースを出力します。"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Destination format: %s"
|
||||
msgstr "日付の形式"
|
||||
|
||||
msgid "Exports only the given note."
|
||||
msgstr "選択されたノートのみを出力する。"
|
||||
|
||||
@@ -248,6 +206,10 @@ msgstr "ノートの位置情報URLを表示する。"
|
||||
msgid "Displays usage information."
|
||||
msgstr "使い方を表示する。"
|
||||
|
||||
#, javascript-format
|
||||
msgid "For information on how to customise the shortcuts please visit %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Shortcuts are not available in CLI mode."
|
||||
msgstr "CLIモードではショートカットは使用できません。"
|
||||
|
||||
@@ -287,31 +249,23 @@ msgstr "コマンドラインモードに入るには、\":\"を入力してく
|
||||
msgid "To exit command line mode, press ESCAPE"
|
||||
msgstr "コマンドラインモードを終了するには、ESCキーを押してください。"
|
||||
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"For the complete list of available keyboard shortcuts, type `help shortcuts`"
|
||||
"For the list of keyboard shortcuts and config options, type `help keymap`"
|
||||
msgstr ""
|
||||
"有効なすべてのキーボードショートカットを表示するには、`help shortcuts`と入力"
|
||||
"してください。"
|
||||
|
||||
msgid "Imports an Evernote notebook file (.enex file)."
|
||||
msgstr "Evernoteノートブックファイル(.enex)のインポート"
|
||||
msgid "Imports data into Joplin."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Source format: %s"
|
||||
msgstr "コマンドが違います:%s"
|
||||
|
||||
msgid "Do not ask for confirmation."
|
||||
msgstr "確認を行わない。"
|
||||
|
||||
#, javascript-format
|
||||
msgid "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
|
||||
msgstr ""
|
||||
"ファイル \"%s\" はノートブック \"%s\"に取り込まれます。よろしいですか?"
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"New notebook \"%s\" will be created and file \"%s\" will be imported into "
|
||||
"it. Continue?"
|
||||
msgstr ""
|
||||
"新しいノートブック\"%s\"が作成され、ファイル\"%s\"が取り込まれます。よろしい"
|
||||
"ですか?"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Found: %d."
|
||||
msgstr "見つかりました:%d"
|
||||
@@ -446,6 +400,14 @@ msgstr "リモート保存領域と同期します。"
|
||||
msgid "Sync to provided target (defaults to sync.target config value)"
|
||||
msgstr "指定のターゲットと同期します。(標準: sync.targetの設定値)"
|
||||
|
||||
msgid ""
|
||||
"Authentication was not completed (did not receive an authentication token)."
|
||||
msgstr "認証は完了していません(認証トークンが得られませんでした)"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Not authentified with %s. Please provide any missing credentials."
|
||||
msgstr ""
|
||||
|
||||
msgid "Synchronisation is already in progress."
|
||||
msgstr "同期はすでに実行中です。"
|
||||
|
||||
@@ -458,10 +420,6 @@ msgstr ""
|
||||
"ロックファイルがすでに保持されています。同期作業が行われていない場合は、\"%s"
|
||||
"\"にあるロックファイルを削除して、作業を再度行ってください。"
|
||||
|
||||
msgid ""
|
||||
"Authentication was not completed (did not receive an authentication token)."
|
||||
msgstr "認証は完了していません(認証トークンが得られませんでした)"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Synchronisation target: %s (%s)"
|
||||
msgstr "同期先: %s (%s)"
|
||||
@@ -534,6 +492,10 @@ msgstr "規定値: %s"
|
||||
msgid "Possible keys/values:"
|
||||
msgstr "取り得るキーバリュー: "
|
||||
|
||||
#, fuzzy
|
||||
msgid "Type `joplin help` for usage information."
|
||||
msgstr "使い方を表示する。"
|
||||
|
||||
msgid "Fatal error:"
|
||||
msgstr "致命的なエラー: "
|
||||
|
||||
@@ -583,6 +545,18 @@ msgid ""
|
||||
"background and will be available soon."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Exporting to \"%s\" as \"%s\" format. Please wait..."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "PDF File"
|
||||
msgstr "ファイル"
|
||||
|
||||
msgid "File"
|
||||
msgstr "ファイル"
|
||||
|
||||
@@ -595,11 +569,19 @@ msgstr "新しいToDo"
|
||||
msgid "New notebook"
|
||||
msgstr "新しいノートブック"
|
||||
|
||||
msgid "Import Evernote notes"
|
||||
msgstr "Evernoteのインポート"
|
||||
msgid "Import"
|
||||
msgstr "インポート"
|
||||
|
||||
msgid "Evernote Export Files"
|
||||
msgstr "Evernote Exportファイル"
|
||||
#, fuzzy
|
||||
msgid "Export"
|
||||
msgstr "インポート"
|
||||
|
||||
msgid "Print"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Hide %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Quit"
|
||||
msgstr "終了"
|
||||
@@ -619,6 +601,12 @@ msgstr "貼り付け"
|
||||
msgid "Search in all the notes"
|
||||
msgstr "すべてのノートを検索"
|
||||
|
||||
msgid "View"
|
||||
msgstr ""
|
||||
|
||||
msgid "Toggle editor layout"
|
||||
msgstr ""
|
||||
|
||||
msgid "Tools"
|
||||
msgstr "ツール"
|
||||
|
||||
@@ -638,6 +626,13 @@ msgstr "ヘルプ"
|
||||
msgid "Website and documentation"
|
||||
msgstr "Webサイトとドキュメント"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Make a donation"
|
||||
msgstr "Webサイトとドキュメント"
|
||||
|
||||
msgid "Check for updates..."
|
||||
msgstr ""
|
||||
|
||||
msgid "About Joplin"
|
||||
msgstr "Joplinについて"
|
||||
|
||||
@@ -645,12 +640,35 @@ msgstr "Joplinについて"
|
||||
msgid "%s %s (%s, %s)"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Exit"
|
||||
msgstr ""
|
||||
|
||||
msgid "OK"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cancel"
|
||||
msgstr "キャンセル"
|
||||
|
||||
msgid "Current version is up-to-date."
|
||||
msgstr ""
|
||||
|
||||
msgid "An update is available, do you want to download it now?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Yes"
|
||||
msgstr ""
|
||||
|
||||
msgid "No"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr "同期の中止"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr "ノートと設定は、%sに保存されます。"
|
||||
@@ -711,6 +729,15 @@ msgstr ""
|
||||
"注意:\"active\"に指定されたマスターキーのみが暗号化に使用されます。暗号化に"
|
||||
"使用されたキーの応じて、すべてのキーが暗号解除のために使用されます。"
|
||||
|
||||
msgid "Missing Master Keys"
|
||||
msgstr ""
|
||||
|
||||
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 ""
|
||||
|
||||
msgid "Status"
|
||||
msgstr "状態"
|
||||
|
||||
@@ -748,6 +775,9 @@ msgstr "ノートブックの名前を変更:"
|
||||
msgid "Set alarm:"
|
||||
msgstr "アラームをセット:"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "検索"
|
||||
|
||||
msgid "Layout"
|
||||
msgstr "レイアウト"
|
||||
|
||||
@@ -783,6 +813,13 @@ msgid ""
|
||||
"There is currently no notebook. Create one by clicking on \"New notebook\"."
|
||||
msgstr "ノートブックがありません。新しいノートブックを作成してください。"
|
||||
|
||||
msgid "Open..."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Save as..."
|
||||
msgstr "変更を保存"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Unsupported link or message: %s"
|
||||
msgstr ""
|
||||
@@ -790,9 +827,30 @@ msgstr ""
|
||||
msgid "Attach file"
|
||||
msgstr "ファイルを添付"
|
||||
|
||||
msgid "Tags"
|
||||
msgstr "タグ"
|
||||
|
||||
msgid "Set alarm"
|
||||
msgstr "アラームをセット"
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"This note has no content. Click on \"%s\" to toggle the editor and edit the "
|
||||
"note."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "to-do"
|
||||
msgstr "新しいToDo"
|
||||
|
||||
#, fuzzy
|
||||
msgid "note"
|
||||
msgstr "新しいノート"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Creating new %s..."
|
||||
msgstr "ノートのインポート…"
|
||||
|
||||
msgid "Refresh"
|
||||
msgstr "更新"
|
||||
|
||||
@@ -802,9 +860,6 @@ msgstr "クリア"
|
||||
msgid "OneDrive Login"
|
||||
msgstr "OneDriveログイン"
|
||||
|
||||
msgid "Import"
|
||||
msgstr "インポート"
|
||||
|
||||
msgid "Options"
|
||||
msgstr "オプション"
|
||||
|
||||
@@ -829,9 +884,6 @@ msgstr "同期"
|
||||
msgid "Notebooks"
|
||||
msgstr "ノートブック"
|
||||
|
||||
msgid "Tags"
|
||||
msgstr "タグ"
|
||||
|
||||
msgid "Searches"
|
||||
msgstr "検索"
|
||||
|
||||
@@ -849,12 +901,18 @@ msgstr "不明なフラグ: %s"
|
||||
msgid "File system"
|
||||
msgstr "ファイルシステム"
|
||||
|
||||
msgid "Nextcloud"
|
||||
msgstr ""
|
||||
|
||||
msgid "OneDrive"
|
||||
msgstr ""
|
||||
|
||||
msgid "OneDrive Dev (For testing only)"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Unknown log level: %s"
|
||||
msgstr ""
|
||||
@@ -928,6 +986,16 @@ msgstr "中止中..."
|
||||
msgid "Completed: %s"
|
||||
msgstr "完了: %s"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Last error: %s"
|
||||
msgstr "致命的なエラー: "
|
||||
|
||||
msgid "Idle"
|
||||
msgstr ""
|
||||
|
||||
msgid "In progress"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr "同期作業はすでに実行中です。状態: %s"
|
||||
@@ -994,12 +1062,52 @@ msgstr "明るい"
|
||||
msgid "Dark"
|
||||
msgstr "暗い"
|
||||
|
||||
msgid "Show uncompleted todos on top of the lists"
|
||||
#, fuzzy
|
||||
msgid "Uncompleted to-dos on top"
|
||||
msgstr "未完のToDoをリストの上部に表示"
|
||||
|
||||
msgid "Sort notes by"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Reverse sort order"
|
||||
msgstr "逆順に並び替える。"
|
||||
|
||||
msgid "Save geo-location with notes"
|
||||
msgstr "ノートに位置情報を保存"
|
||||
|
||||
#, fuzzy
|
||||
msgid "When creating a new to-do:"
|
||||
msgstr "新しいToDoを作成します。"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Focus title"
|
||||
msgstr "ノートの題名:"
|
||||
|
||||
msgid "Focus body"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "When creating a new note:"
|
||||
msgstr "あたらしいノートを作成します。"
|
||||
|
||||
msgid "Show tray icon"
|
||||
msgstr ""
|
||||
|
||||
msgid "Global zoom percentage"
|
||||
msgstr ""
|
||||
|
||||
msgid "Editor font family"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"The font name will not be checked. If incorrect or empty, it will default to "
|
||||
"a generic monospace font."
|
||||
msgstr ""
|
||||
|
||||
msgid "Automatically update the application"
|
||||
msgstr "アプリケーションの自動更新"
|
||||
|
||||
msgid "Synchronisation interval"
|
||||
msgstr "同期間隔"
|
||||
|
||||
@@ -1015,9 +1123,6 @@ msgstr "%d 時間"
|
||||
msgid "%d hours"
|
||||
msgstr "%d 時間"
|
||||
|
||||
msgid "Automatically update the application"
|
||||
msgstr "アプリケーションの自動更新"
|
||||
|
||||
msgid "Show advanced options"
|
||||
msgstr "詳細な設定の表示"
|
||||
|
||||
@@ -1025,11 +1130,9 @@ msgid "Synchronisation target"
|
||||
msgstr "同期先"
|
||||
|
||||
msgid ""
|
||||
"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)."
|
||||
msgstr ""
|
||||
"同期先です。ローカルのファイルシステムと同期する場合は、`sync.2.path`を同期先"
|
||||
"のディレクトリに設定してください。"
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr "同期先のディレクトリ(絶対パス)"
|
||||
@@ -1041,10 +1144,66 @@ msgstr ""
|
||||
"ファイルシステム同期の有効時に同期を行うパスです。`sync.target`も参考にしてく"
|
||||
"ださい。"
|
||||
|
||||
msgid "Nextcloud WebDAV URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud username"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud password"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV username"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV password"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr "無効な設定値: \"%s\"。有効な値は: %sです。"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Joplin Export File"
|
||||
msgstr "Evernote Exportファイル"
|
||||
|
||||
msgid "Markdown"
|
||||
msgstr ""
|
||||
|
||||
msgid "Joplin Export Directory"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Evernote Export File"
|
||||
msgstr "Evernote Exportファイル"
|
||||
|
||||
msgid "Directory"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Cannot load \"%s\" module for format \"%s\""
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Please specify import format for %s"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"This item is currently encrypted: %s \"%s\". Please wait for all items to be "
|
||||
"decrypted and try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "There is no data to export."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr "同期状況の出力先を選択してください"
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "同期が出来なかったアイテム"
|
||||
|
||||
@@ -1128,6 +1287,9 @@ msgstr "確認"
|
||||
msgid "Cancel synchronisation"
|
||||
msgstr "同期の中止"
|
||||
|
||||
msgid "Joplin website"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Master Key %s"
|
||||
msgstr ""
|
||||
@@ -1153,6 +1315,12 @@ msgstr "ノートブックは保存できませんでした:%s"
|
||||
msgid "Edit notebook"
|
||||
msgstr "ノートブックの編集"
|
||||
|
||||
msgid "Show all"
|
||||
msgstr ""
|
||||
|
||||
msgid "Errors only"
|
||||
msgstr ""
|
||||
|
||||
msgid "This note has been modified:"
|
||||
msgstr "ノートは変更されています:"
|
||||
|
||||
@@ -1208,8 +1376,85 @@ msgstr ""
|
||||
msgid "Welcome"
|
||||
msgstr "ようこそ"
|
||||
|
||||
#~ msgid "Note title:"
|
||||
#~ msgstr "ノートの題名:"
|
||||
#, fuzzy
|
||||
#~ msgid ""
|
||||
#~ "Release notes:\n"
|
||||
#~ "\n"
|
||||
#~ "%s"
|
||||
#~ msgstr "ノートを削除しますか?"
|
||||
|
||||
#~ msgid "Imports an Evernote notebook file (.enex file)."
|
||||
#~ msgstr "Evernoteノートブックファイル(.enex)のインポート"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
|
||||
#~ msgstr ""
|
||||
#~ "ファイル \"%s\" はノートブック \"%s\"に取り込まれます。よろしいですか?"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "New notebook \"%s\" will be created and file \"%s\" will be imported into "
|
||||
#~ "it. Continue?"
|
||||
#~ msgstr ""
|
||||
#~ "新しいノートブック\"%s\"が作成され、ファイル\"%s\"が取り込まれます。よろし"
|
||||
#~ "いですか?"
|
||||
|
||||
#~ msgid "Import Evernote notes"
|
||||
#~ msgstr "Evernoteのインポート"
|
||||
|
||||
#~ msgid "Give focus to next pane"
|
||||
#~ msgstr "次のペインへ"
|
||||
|
||||
#~ msgid "Give focus to previous pane"
|
||||
#~ msgstr "前のペインへ"
|
||||
|
||||
#~ msgid "Enter command line mode"
|
||||
#~ msgstr "コマンドラインモードに入る"
|
||||
|
||||
#~ msgid "Exit command line mode"
|
||||
#~ msgstr "コマンドラインモードの終了"
|
||||
|
||||
#~ msgid "Edit the selected note"
|
||||
#~ msgstr "選択したノートを編集"
|
||||
|
||||
#~ msgid "Cancel the current command."
|
||||
#~ msgstr "現在のコマンドをキャンセル"
|
||||
|
||||
#~ msgid "Exit the application."
|
||||
#~ msgstr "アプリケーションを終了する"
|
||||
|
||||
#~ msgid "Delete the currently selected note or notebook."
|
||||
#~ msgstr "選択中のノートまたはノートブックを削除"
|
||||
|
||||
#~ msgid "Set a to-do as completed / not completed"
|
||||
#~ msgstr "ToDoを完了/未完に設定"
|
||||
|
||||
#~ msgid "[t]oggle [c]onsole between maximized/minimized/hidden/visible."
|
||||
#~ msgstr "コンソールを最大表示/最小表示/非表示/可視で切り替える([t][c])"
|
||||
|
||||
#~ msgid "[t]oggle note [m]etadata."
|
||||
#~ msgstr "ノートのメタ情報を切り替える [tm]"
|
||||
|
||||
#~ msgid "[M]ake a new [n]ote"
|
||||
#~ msgstr "新しいノートの作成 [mn]"
|
||||
|
||||
#~ msgid "[M]ake a new [t]odo"
|
||||
#~ msgstr "新しいToDoの作成 [mt]"
|
||||
|
||||
#~ msgid "[M]ake a new note[b]ook"
|
||||
#~ msgstr "新しいノートブックの作成 [mb]"
|
||||
|
||||
#~ msgid "Copy ([Y]ank) the [n]ote to a notebook."
|
||||
#~ msgstr "ノートをノートブックにコピー [yn]"
|
||||
|
||||
#~ msgid "Move the note to a notebook."
|
||||
#~ msgstr "ノートをノートブックに移動"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "The target to synchonise to. If synchronising with the file system, set "
|
||||
#~ "`sync.2.path` to specify the target directory."
|
||||
#~ msgstr ""
|
||||
#~ "同期先です。ローカルのファイルシステムと同期する場合は、`sync.2.path`を同"
|
||||
#~ "期先のディレクトリに設定してください。"
|
||||
|
||||
#~ msgid "To-do title:"
|
||||
#~ msgstr "ToDoの題名:"
|
||||
|
@@ -15,63 +15,12 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "Give focus to next pane"
|
||||
msgstr ""
|
||||
|
||||
msgid "Give focus to previous pane"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enter command line mode"
|
||||
msgstr ""
|
||||
|
||||
msgid "Exit command line mode"
|
||||
msgstr ""
|
||||
|
||||
msgid "Edit the selected note"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cancel the current command."
|
||||
msgstr ""
|
||||
|
||||
msgid "Exit the application."
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete the currently selected note or notebook."
|
||||
msgstr ""
|
||||
|
||||
msgid "To delete a tag, untag the associated notes."
|
||||
msgstr ""
|
||||
|
||||
msgid "Please select the note or notebook to be deleted first."
|
||||
msgstr ""
|
||||
|
||||
msgid "Set a to-do as completed / not completed"
|
||||
msgstr ""
|
||||
|
||||
msgid "[t]oggle [c]onsole between maximized/minimized/hidden/visible."
|
||||
msgstr ""
|
||||
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
msgid "[t]oggle note [m]etadata."
|
||||
msgstr ""
|
||||
|
||||
msgid "[M]ake a new [n]ote"
|
||||
msgstr ""
|
||||
|
||||
msgid "[M]ake a new [t]odo"
|
||||
msgstr ""
|
||||
|
||||
msgid "[M]ake a new note[b]ook"
|
||||
msgstr ""
|
||||
|
||||
msgid "Copy ([Y]ank) the [n]ote to a notebook."
|
||||
msgstr ""
|
||||
|
||||
msgid "Move the note to a notebook."
|
||||
msgstr ""
|
||||
|
||||
msgid "Press Ctrl+D or type \"exit\" to exit the application"
|
||||
msgstr ""
|
||||
|
||||
@@ -214,6 +163,10 @@ msgstr ""
|
||||
msgid "Starting to edit note. Close the editor to get back to the prompt."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Error opening note in editor: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note has been saved."
|
||||
msgstr ""
|
||||
|
||||
@@ -221,10 +174,14 @@ msgid "Exits the application."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Exports Joplin data to the given directory. By default, it will export the "
|
||||
"Exports Joplin data to the given path. By default, it will export the "
|
||||
"complete database including notebooks, notes, tags and resources."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Destination format: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Exports only the given note."
|
||||
msgstr ""
|
||||
|
||||
@@ -237,6 +194,10 @@ msgstr ""
|
||||
msgid "Displays usage information."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "For information on how to customise the shortcuts please visit %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Shortcuts are not available in CLI mode."
|
||||
msgstr ""
|
||||
|
||||
@@ -272,25 +233,19 @@ msgid "To exit command line mode, press ESCAPE"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"For the complete list of available keyboard shortcuts, type `help shortcuts`"
|
||||
"For the list of keyboard shortcuts and config options, type `help keymap`"
|
||||
msgstr ""
|
||||
|
||||
msgid "Imports an Evernote notebook file (.enex file)."
|
||||
msgid "Imports data into Joplin."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Source format: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Do not ask for confirmation."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"New notebook \"%s\" will be created and file \"%s\" will be imported into "
|
||||
"it. Continue?"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Found: %d."
|
||||
msgstr ""
|
||||
@@ -413,6 +368,14 @@ msgstr ""
|
||||
msgid "Sync to provided target (defaults to sync.target config value)"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Authentication was not completed (did not receive an authentication token)."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Not authentified with %s. Please provide any missing credentials."
|
||||
msgstr ""
|
||||
|
||||
msgid "Synchronisation is already in progress."
|
||||
msgstr ""
|
||||
|
||||
@@ -423,10 +386,6 @@ msgid ""
|
||||
"operation."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Authentication was not completed (did not receive an authentication token)."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Synchronisation target: %s (%s)"
|
||||
msgstr ""
|
||||
@@ -490,6 +449,9 @@ msgstr ""
|
||||
msgid "Possible keys/values:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Type `joplin help` for usage information."
|
||||
msgstr ""
|
||||
|
||||
msgid "Fatal error:"
|
||||
msgstr ""
|
||||
|
||||
@@ -527,6 +489,17 @@ msgid ""
|
||||
"background and will be available soon."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Exporting to \"%s\" as \"%s\" format. Please wait..."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
|
||||
msgstr ""
|
||||
|
||||
msgid "PDF File"
|
||||
msgstr ""
|
||||
|
||||
msgid "File"
|
||||
msgstr ""
|
||||
|
||||
@@ -539,10 +512,17 @@ msgstr ""
|
||||
msgid "New notebook"
|
||||
msgstr ""
|
||||
|
||||
msgid "Import Evernote notes"
|
||||
msgid "Import"
|
||||
msgstr ""
|
||||
|
||||
msgid "Evernote Export Files"
|
||||
msgid "Export"
|
||||
msgstr ""
|
||||
|
||||
msgid "Print"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Hide %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Quit"
|
||||
@@ -563,6 +543,12 @@ msgstr ""
|
||||
msgid "Search in all the notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "View"
|
||||
msgstr ""
|
||||
|
||||
msgid "Toggle editor layout"
|
||||
msgstr ""
|
||||
|
||||
msgid "Tools"
|
||||
msgstr ""
|
||||
|
||||
@@ -581,6 +567,12 @@ msgstr ""
|
||||
msgid "Website and documentation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Make a donation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Check for updates..."
|
||||
msgstr ""
|
||||
|
||||
msgid "About Joplin"
|
||||
msgstr ""
|
||||
|
||||
@@ -588,12 +580,34 @@ msgstr ""
|
||||
msgid "%s %s (%s, %s)"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Exit"
|
||||
msgstr ""
|
||||
|
||||
msgid "OK"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
msgid "Current version is up-to-date."
|
||||
msgstr ""
|
||||
|
||||
msgid "An update is available, do you want to download it now?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Yes"
|
||||
msgstr ""
|
||||
|
||||
msgid "No"
|
||||
msgstr ""
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr ""
|
||||
@@ -650,6 +664,15 @@ msgid ""
|
||||
"how the notes or notebooks were originally encrypted."
|
||||
msgstr ""
|
||||
|
||||
msgid "Missing Master Keys"
|
||||
msgstr ""
|
||||
|
||||
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 ""
|
||||
|
||||
msgid "Status"
|
||||
msgstr ""
|
||||
|
||||
@@ -685,6 +708,9 @@ msgstr ""
|
||||
msgid "Set alarm:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
msgid "Layout"
|
||||
msgstr ""
|
||||
|
||||
@@ -719,6 +745,12 @@ msgid ""
|
||||
"There is currently no notebook. Create one by clicking on \"New notebook\"."
|
||||
msgstr ""
|
||||
|
||||
msgid "Open..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Save as..."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Unsupported link or message: %s"
|
||||
msgstr ""
|
||||
@@ -726,9 +758,28 @@ msgstr ""
|
||||
msgid "Attach file"
|
||||
msgstr ""
|
||||
|
||||
msgid "Tags"
|
||||
msgstr ""
|
||||
|
||||
msgid "Set alarm"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"This note has no content. Click on \"%s\" to toggle the editor and edit the "
|
||||
"note."
|
||||
msgstr ""
|
||||
|
||||
msgid "to-do"
|
||||
msgstr ""
|
||||
|
||||
msgid "note"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Creating new %s..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Refresh"
|
||||
msgstr ""
|
||||
|
||||
@@ -738,9 +789,6 @@ msgstr ""
|
||||
msgid "OneDrive Login"
|
||||
msgstr ""
|
||||
|
||||
msgid "Import"
|
||||
msgstr ""
|
||||
|
||||
msgid "Options"
|
||||
msgstr ""
|
||||
|
||||
@@ -765,9 +813,6 @@ msgstr ""
|
||||
msgid "Notebooks"
|
||||
msgstr ""
|
||||
|
||||
msgid "Tags"
|
||||
msgstr ""
|
||||
|
||||
msgid "Searches"
|
||||
msgstr ""
|
||||
|
||||
@@ -785,12 +830,18 @@ msgstr ""
|
||||
msgid "File system"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud"
|
||||
msgstr ""
|
||||
|
||||
msgid "OneDrive"
|
||||
msgstr ""
|
||||
|
||||
msgid "OneDrive Dev (For testing only)"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Unknown log level: %s"
|
||||
msgstr ""
|
||||
@@ -856,6 +907,16 @@ msgstr ""
|
||||
msgid "Completed: %s"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Last error: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Idle"
|
||||
msgstr ""
|
||||
|
||||
msgid "In progress"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr ""
|
||||
@@ -917,12 +978,47 @@ msgstr ""
|
||||
msgid "Dark"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show uncompleted todos on top of the lists"
|
||||
msgid "Uncompleted to-dos on top"
|
||||
msgstr ""
|
||||
|
||||
msgid "Sort notes by"
|
||||
msgstr ""
|
||||
|
||||
msgid "Reverse sort order"
|
||||
msgstr ""
|
||||
|
||||
msgid "Save geo-location with notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "When creating a new to-do:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Focus title"
|
||||
msgstr ""
|
||||
|
||||
msgid "Focus body"
|
||||
msgstr ""
|
||||
|
||||
msgid "When creating a new note:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show tray icon"
|
||||
msgstr ""
|
||||
|
||||
msgid "Global zoom percentage"
|
||||
msgstr ""
|
||||
|
||||
msgid "Editor font family"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"The font name will not be checked. If incorrect or empty, it will default to "
|
||||
"a generic monospace font."
|
||||
msgstr ""
|
||||
|
||||
msgid "Automatically update the application"
|
||||
msgstr ""
|
||||
|
||||
msgid "Synchronisation interval"
|
||||
msgstr ""
|
||||
|
||||
@@ -938,9 +1034,6 @@ msgstr ""
|
||||
msgid "%d hours"
|
||||
msgstr ""
|
||||
|
||||
msgid "Automatically update the application"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show advanced options"
|
||||
msgstr ""
|
||||
|
||||
@@ -948,8 +1041,8 @@ msgid "Synchronisation target"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"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)."
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
@@ -960,10 +1053,63 @@ msgid ""
|
||||
"See `sync.target`."
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud WebDAV URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud username"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud password"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV username"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV password"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr ""
|
||||
|
||||
msgid "Joplin Export File"
|
||||
msgstr ""
|
||||
|
||||
msgid "Markdown"
|
||||
msgstr ""
|
||||
|
||||
msgid "Joplin Export Directory"
|
||||
msgstr ""
|
||||
|
||||
msgid "Evernote Export File"
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Cannot load \"%s\" module for format \"%s\""
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Please specify import format for %s"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"This item is currently encrypted: %s \"%s\". Please wait for all items to be "
|
||||
"decrypted and try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "There is no data to export."
|
||||
msgstr ""
|
||||
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr ""
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr ""
|
||||
|
||||
@@ -1047,6 +1193,9 @@ msgstr ""
|
||||
msgid "Cancel synchronisation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Joplin website"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Master Key %s"
|
||||
msgstr ""
|
||||
@@ -1071,6 +1220,12 @@ msgstr ""
|
||||
msgid "Edit notebook"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show all"
|
||||
msgstr ""
|
||||
|
||||
msgid "Errors only"
|
||||
msgstr ""
|
||||
|
||||
msgid "This note has been modified:"
|
||||
msgstr ""
|
||||
|
||||
|
1492
CliClient/locales/nl_BE.po
Normal file
1492
CliClient/locales/nl_BE.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -7,73 +7,22 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Joplin-CLI 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"Last-Translator: rtmkrlv <artyom.karlov@gmail.com>\n"
|
||||
"Last-Translator: Artyom Karlov <artyom.karlov@gmail.com>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: ru_RU\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 2.0.5\n"
|
||||
"X-Generator: Poedit 2.0.6\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
|
||||
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
|
||||
msgid "Give focus to next pane"
|
||||
msgstr "Переключиться на следующую панель"
|
||||
|
||||
msgid "Give focus to previous pane"
|
||||
msgstr "Переключиться на предыдущую панель"
|
||||
|
||||
msgid "Enter command line mode"
|
||||
msgstr "Войти в режим командной строки"
|
||||
|
||||
msgid "Exit command line mode"
|
||||
msgstr "Выйти из режима командной строки"
|
||||
|
||||
msgid "Edit the selected note"
|
||||
msgstr "Редактировать выбранную заметку"
|
||||
|
||||
msgid "Cancel the current command."
|
||||
msgstr "Отменить текущую команду."
|
||||
|
||||
msgid "Exit the application."
|
||||
msgstr "Выйти из приложения."
|
||||
|
||||
msgid "Delete the currently selected note or notebook."
|
||||
msgstr "Удалить текущую выбранную заметку или блокнот."
|
||||
|
||||
msgid "To delete a tag, untag the associated notes."
|
||||
msgstr "Чтобы удалить тег, уберите его с ассоциированных с ним заметок."
|
||||
|
||||
msgid "Please select the note or notebook to be deleted first."
|
||||
msgstr "Сначала выберите заметку или блокнот, которые должны быть удалены."
|
||||
|
||||
msgid "Set a to-do as completed / not completed"
|
||||
msgstr "Отметить задачу как завершённую/незавершённую"
|
||||
|
||||
msgid "[t]oggle [c]onsole between maximized/minimized/hidden/visible."
|
||||
msgstr "[tc] переключить консоль между развёрнутой/свёрнутой/скрытой/видимой."
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Поиск"
|
||||
|
||||
msgid "[t]oggle note [m]etadata."
|
||||
msgstr "[tm] переключить отображение метаданных заметки."
|
||||
|
||||
msgid "[M]ake a new [n]ote"
|
||||
msgstr "[mn] создать новую заметку"
|
||||
|
||||
msgid "[M]ake a new [t]odo"
|
||||
msgstr "[mt] создать новую задачу"
|
||||
|
||||
msgid "[M]ake a new note[b]ook"
|
||||
msgstr "[mb] создать новый блокнот"
|
||||
|
||||
msgid "Copy ([Y]ank) the [n]ote to a notebook."
|
||||
msgstr "[yn] копировать заметку в блокнот."
|
||||
|
||||
msgid "Move the note to a notebook."
|
||||
msgstr "Переместить заметку в блокнот."
|
||||
|
||||
msgid "Press Ctrl+D or type \"exit\" to exit the application"
|
||||
msgstr "Для выхода из приложения нажмите Ctrl+D или введите «exit»"
|
||||
|
||||
@@ -112,7 +61,7 @@ msgid "The command \"%s\" is only available in GUI mode"
|
||||
msgstr "Команда «%s» доступна только в режиме GUI"
|
||||
|
||||
msgid "Cannot change encrypted item"
|
||||
msgstr ""
|
||||
msgstr "Не удалось изменить зашифрованный элемент"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Missing required argument: %s"
|
||||
@@ -123,7 +72,7 @@ msgid "%s: %s"
|
||||
msgstr "%s: %s"
|
||||
|
||||
msgid "Your choice: "
|
||||
msgstr "Ваш выбор:"
|
||||
msgstr "Ваш выбор: "
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid answer: %s"
|
||||
@@ -180,22 +129,24 @@ msgid ""
|
||||
"Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, "
|
||||
"`status` and `target-status`."
|
||||
msgstr ""
|
||||
"Управляет конфигурацией E2EE. Команды: `enable`, `disable`, `decrypt`, "
|
||||
"`status` и `target-status`."
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enter master password:"
|
||||
msgstr "Установить пароль"
|
||||
msgstr "Введите мастер-пароль:"
|
||||
|
||||
msgid "Operation cancelled"
|
||||
msgstr ""
|
||||
msgstr "Операция отменена"
|
||||
|
||||
msgid ""
|
||||
"Starting decryption... Please wait as it may take several minutes depending "
|
||||
"on how much there is to decrypt."
|
||||
msgstr ""
|
||||
"Запуск расшифровки... Пожалуйста, ожидайте. Время расшифровки зависит от "
|
||||
"объёма расшифровываемых данных."
|
||||
|
||||
#, fuzzy
|
||||
msgid "Completed decryption."
|
||||
msgstr "Включить шифрование"
|
||||
msgstr "Расшифровка завершена."
|
||||
|
||||
msgid "Enabled"
|
||||
msgstr "Включено"
|
||||
@@ -203,9 +154,9 @@ msgstr "Включено"
|
||||
msgid "Disabled"
|
||||
msgstr "Отключено"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
#, javascript-format
|
||||
msgid "Encryption is: %s"
|
||||
msgstr "Шифрование:"
|
||||
msgstr "Шифрование: %s"
|
||||
|
||||
msgid "Edit note."
|
||||
msgstr "Редактировать заметку."
|
||||
@@ -228,6 +179,10 @@ msgstr ""
|
||||
"Запуск редактирования заметки. Закройте редактор, чтобы вернуться к "
|
||||
"командной строке."
|
||||
|
||||
#, javascript-format
|
||||
msgid "Error opening note in editor: %s"
|
||||
msgstr "Ошибка при открытии заметки в редакторе: %s"
|
||||
|
||||
msgid "Note has been saved."
|
||||
msgstr "Заметка сохранена."
|
||||
|
||||
@@ -235,12 +190,16 @@ msgid "Exits the application."
|
||||
msgstr "Выход из приложения."
|
||||
|
||||
msgid ""
|
||||
"Exports Joplin data to the given directory. By default, it will export the "
|
||||
"Exports Joplin data to the given path. By default, it will export the "
|
||||
"complete database including notebooks, notes, tags and resources."
|
||||
msgstr ""
|
||||
"Экспортирует данные Joplin в заданный каталог. По умолчанию экспортируется "
|
||||
"Экспортирует данные Joplin по заданному пути. По умолчанию экспортируется "
|
||||
"полная база данных, включая блокноты, заметки, теги и ресурсы."
|
||||
|
||||
#, javascript-format
|
||||
msgid "Destination format: %s"
|
||||
msgstr "Целевой формат: %s"
|
||||
|
||||
msgid "Exports only the given note."
|
||||
msgstr "Экспортирует только заданную заметку."
|
||||
|
||||
@@ -253,6 +212,10 @@ msgstr "Выводит URL геолокации для заметки."
|
||||
msgid "Displays usage information."
|
||||
msgstr "Выводит информацию об использовании."
|
||||
|
||||
#, javascript-format
|
||||
msgid "For information on how to customise the shortcuts please visit %s"
|
||||
msgstr "Информацию по настройке сочетаний можно получить, посетив %s"
|
||||
|
||||
msgid "Shortcuts are not available in CLI mode."
|
||||
msgstr "Ярлыки недоступны в режиме командной строки."
|
||||
|
||||
@@ -277,7 +240,7 @@ msgstr ""
|
||||
"элемент."
|
||||
|
||||
msgid "To move from one pane to another, press Tab or Shift+Tab."
|
||||
msgstr "Чтобы переключаться между панелями, нажимайте Tab или Shift+Tab"
|
||||
msgstr "Чтобы переключаться между панелями, нажимайте Tab или Shift+Tab."
|
||||
|
||||
msgid ""
|
||||
"Use the arrows and page up/down to scroll the lists and text areas "
|
||||
@@ -296,29 +259,21 @@ msgid "To exit command line mode, press ESCAPE"
|
||||
msgstr "Чтобы выйти из режима командной строки, нажмите ESCAPE"
|
||||
|
||||
msgid ""
|
||||
"For the complete list of available keyboard shortcuts, type `help shortcuts`"
|
||||
"For the list of keyboard shortcuts and config options, type `help keymap`"
|
||||
msgstr ""
|
||||
"Для просмотра списка доступных клавиатурных сочетаний введите `help "
|
||||
"shortcuts`."
|
||||
"Для просмотра списка клавиатурных сочетаний и настроек конфигурации введите "
|
||||
"`help keymap`"
|
||||
|
||||
msgid "Imports an Evernote notebook file (.enex file)."
|
||||
msgstr "Импортирует файл блокнотов Evernote (.enex-файл)."
|
||||
msgid "Imports data into Joplin."
|
||||
msgstr "Импортирует данные в Joplin."
|
||||
|
||||
#, javascript-format
|
||||
msgid "Source format: %s"
|
||||
msgstr "Исходный формат: %s"
|
||||
|
||||
msgid "Do not ask for confirmation."
|
||||
msgstr "Не запрашивать подтверждение."
|
||||
|
||||
#, javascript-format
|
||||
msgid "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
|
||||
msgstr "Файл «%s» будет импортирован в существующий блокнот «%s». Продолжить?"
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"New notebook \"%s\" will be created and file \"%s\" will be imported into "
|
||||
"it. Continue?"
|
||||
msgstr ""
|
||||
"Будет создан новый блокнот «%s» и в него будет импортирован файл «%s». "
|
||||
"Продолжить?"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Found: %d."
|
||||
msgstr "Найдено: %d."
|
||||
@@ -455,6 +410,15 @@ msgstr ""
|
||||
"Синхронизация с заданной целью (по умолчанию — значение конфигурации sync."
|
||||
"target)"
|
||||
|
||||
msgid ""
|
||||
"Authentication was not completed (did not receive an authentication token)."
|
||||
msgstr "Аутентификация не была завершена (не получен токен аутентификации)."
|
||||
|
||||
#, javascript-format
|
||||
msgid "Not authentified with %s. Please provide any missing credentials."
|
||||
msgstr ""
|
||||
"Не аутентифицировано с %s. Пожалуйста, предоставьте все недостающие данные."
|
||||
|
||||
msgid "Synchronisation is already in progress."
|
||||
msgstr "Синхронизация уже выполняется."
|
||||
|
||||
@@ -468,10 +432,6 @@ msgstr ""
|
||||
"производится, вы можете удалить файл блокировки в «%s» и возобновить "
|
||||
"операцию."
|
||||
|
||||
msgid ""
|
||||
"Authentication was not completed (did not receive an authentication token)."
|
||||
msgstr "Аутентификация не была завершена (не получен токен аутентификации)."
|
||||
|
||||
#, javascript-format
|
||||
msgid "Synchronisation target: %s (%s)"
|
||||
msgstr "Цель синхронизации: %s (%s)"
|
||||
@@ -545,6 +505,9 @@ msgstr "По умолчанию: %s"
|
||||
msgid "Possible keys/values:"
|
||||
msgstr "Возможные ключи/значения:"
|
||||
|
||||
msgid "Type `joplin help` for usage information."
|
||||
msgstr "Введите `joplin help` для получения информации об использовании."
|
||||
|
||||
msgid "Fatal error:"
|
||||
msgstr "Фатальная ошибка:"
|
||||
|
||||
@@ -593,6 +556,21 @@ msgid ""
|
||||
"supplied the password, the encrypted items are being decrypted in the "
|
||||
"background and will be available soon."
|
||||
msgstr ""
|
||||
"Один или несколько элементов сейчас зашифрованы и может потребоваться, чтобы "
|
||||
"вы предоставили мастер-пароль. Для этого введите, пожалуйста, «e2ee "
|
||||
"decrypt». Если пароль уже был вами предоставлен, зашифрованные элементы "
|
||||
"расшифруются в фоновом режиме и вскоре станут доступны."
|
||||
|
||||
#, javascript-format
|
||||
msgid "Exporting to \"%s\" as \"%s\" format. Please wait..."
|
||||
msgstr "Экспорт в «%s» в формате «%s». Пожалуйста, ожидайте..."
|
||||
|
||||
#, javascript-format
|
||||
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
|
||||
msgstr "Импорт из «%s» в формате «%s». Пожалуйста, ожидайте..."
|
||||
|
||||
msgid "PDF File"
|
||||
msgstr "PDF-файл"
|
||||
|
||||
msgid "File"
|
||||
msgstr "Файл"
|
||||
@@ -606,11 +584,18 @@ msgstr "Новая задача"
|
||||
msgid "New notebook"
|
||||
msgstr "Новый блокнот"
|
||||
|
||||
msgid "Import Evernote notes"
|
||||
msgstr "Импортировать заметки из Evernote"
|
||||
msgid "Import"
|
||||
msgstr "Импорт"
|
||||
|
||||
msgid "Evernote Export Files"
|
||||
msgstr "Файлы экспорта Evernote"
|
||||
msgid "Export"
|
||||
msgstr "Экспорт"
|
||||
|
||||
msgid "Print"
|
||||
msgstr "Печать"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Hide %s"
|
||||
msgstr "Скрыть %s"
|
||||
|
||||
msgid "Quit"
|
||||
msgstr "Выход"
|
||||
@@ -630,19 +615,23 @@ msgstr "Вставить"
|
||||
msgid "Search in all the notes"
|
||||
msgstr "Поиск во всех заметках"
|
||||
|
||||
msgid "View"
|
||||
msgstr "Вид"
|
||||
|
||||
msgid "Toggle editor layout"
|
||||
msgstr "Переключить вид редактора"
|
||||
|
||||
msgid "Tools"
|
||||
msgstr "Инструменты"
|
||||
|
||||
msgid "Synchronisation status"
|
||||
msgstr "Статус синхронизации"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Encryption options"
|
||||
msgstr "Настройки шифрования"
|
||||
|
||||
#, fuzzy
|
||||
msgid "General Options"
|
||||
msgstr "Настройки"
|
||||
msgstr "Основные настройки"
|
||||
|
||||
msgid "Help"
|
||||
msgstr "Помощь"
|
||||
@@ -650,6 +639,12 @@ msgstr "Помощь"
|
||||
msgid "Website and documentation"
|
||||
msgstr "Сайт и документация"
|
||||
|
||||
msgid "Make a donation"
|
||||
msgstr "Сделать пожертвование"
|
||||
|
||||
msgid "Check for updates..."
|
||||
msgstr "Проверить обновления..."
|
||||
|
||||
msgid "About Joplin"
|
||||
msgstr "О Joplin"
|
||||
|
||||
@@ -657,12 +652,34 @@ msgstr "О Joplin"
|
||||
msgid "%s %s (%s, %s)"
|
||||
msgstr "%s %s (%s, %s)"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr "Открыть %s"
|
||||
|
||||
msgid "Exit"
|
||||
msgstr "Выход"
|
||||
|
||||
msgid "OK"
|
||||
msgstr "OK"
|
||||
|
||||
msgid "Cancel"
|
||||
msgstr "Отмена"
|
||||
|
||||
msgid "Current version is up-to-date."
|
||||
msgstr "Вы используете самую свежую версию."
|
||||
|
||||
msgid "An update is available, do you want to download it now?"
|
||||
msgstr "Доступно обновление. Желаете скачать его сейчас?"
|
||||
|
||||
msgid "Yes"
|
||||
msgstr "Да"
|
||||
|
||||
msgid "No"
|
||||
msgstr "Нет"
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr "Проверить настройки синхронизации"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr "Заметки и настройки сохранены в: %s"
|
||||
@@ -731,6 +748,18 @@ msgstr ""
|
||||
"ключей, в зависимости от того, как изначально были зашифрованы заметки или "
|
||||
"блокноты."
|
||||
|
||||
msgid "Missing Master Keys"
|
||||
msgstr "Недостающие мастер-ключи"
|
||||
|
||||
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 ""
|
||||
"Мастер-ключи с такими ID используются для шифрования некоторых из ваших "
|
||||
"элементов, однако у приложения сейчас нет к ним доступа. Скорее всего, они "
|
||||
"загрузятся при синхронизации."
|
||||
|
||||
msgid "Status"
|
||||
msgstr "Статус"
|
||||
|
||||
@@ -766,6 +795,9 @@ msgstr "Переименовать блокнот:"
|
||||
msgid "Set alarm:"
|
||||
msgstr "Установить напоминание:"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Поиск"
|
||||
|
||||
msgid "Layout"
|
||||
msgstr "Вид"
|
||||
|
||||
@@ -800,6 +832,12 @@ msgid ""
|
||||
"There is currently no notebook. Create one by clicking on \"New notebook\"."
|
||||
msgstr "Сейчас здесь нет блокнотов. Создайте новый нажав «Новый блокнот»."
|
||||
|
||||
msgid "Open..."
|
||||
msgstr "Открыть..."
|
||||
|
||||
msgid "Save as..."
|
||||
msgstr "Сохранить как..."
|
||||
|
||||
#, javascript-format
|
||||
msgid "Unsupported link or message: %s"
|
||||
msgstr "Неподдерживаемая ссыка или сообщение: %s"
|
||||
@@ -807,9 +845,30 @@ msgstr "Неподдерживаемая ссыка или сообщение: %
|
||||
msgid "Attach file"
|
||||
msgstr "Прикрепить файл"
|
||||
|
||||
msgid "Tags"
|
||||
msgstr "Теги"
|
||||
|
||||
msgid "Set alarm"
|
||||
msgstr "Установить напоминание"
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"This note has no content. Click on \"%s\" to toggle the editor and edit the "
|
||||
"note."
|
||||
msgstr ""
|
||||
"Заметка пуста. Нажмите на «%s», чтобы переключиться в редактор и "
|
||||
"отредактировать её."
|
||||
|
||||
msgid "to-do"
|
||||
msgstr "задача"
|
||||
|
||||
msgid "note"
|
||||
msgstr "заметка"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Creating new %s..."
|
||||
msgstr "Создание новой %s..."
|
||||
|
||||
msgid "Refresh"
|
||||
msgstr "Обновить"
|
||||
|
||||
@@ -819,9 +878,6 @@ msgstr "Очистить"
|
||||
msgid "OneDrive Login"
|
||||
msgstr "Вход в OneDrive"
|
||||
|
||||
msgid "Import"
|
||||
msgstr "Импорт"
|
||||
|
||||
msgid "Options"
|
||||
msgstr "Настройки"
|
||||
|
||||
@@ -846,9 +902,6 @@ msgstr "Синхронизировать"
|
||||
msgid "Notebooks"
|
||||
msgstr "Блокноты"
|
||||
|
||||
msgid "Tags"
|
||||
msgstr "Теги"
|
||||
|
||||
msgid "Searches"
|
||||
msgstr "Запросы"
|
||||
|
||||
@@ -866,12 +919,18 @@ msgstr "Неизвестный флаг: %s"
|
||||
msgid "File system"
|
||||
msgstr "Файловая система"
|
||||
|
||||
msgid "Nextcloud"
|
||||
msgstr "Nextcloud"
|
||||
|
||||
msgid "OneDrive"
|
||||
msgstr "OneDrive"
|
||||
|
||||
msgid "OneDrive Dev (For testing only)"
|
||||
msgstr "OneDrive Dev (только для тестирования)"
|
||||
|
||||
msgid "WebDAV"
|
||||
msgstr "WebDAV"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Unknown log level: %s"
|
||||
msgstr "Неизвестный уровень лога: %s"
|
||||
@@ -930,9 +989,9 @@ msgstr "Удалено локальных элементов: %d."
|
||||
msgid "Deleted remote items: %d."
|
||||
msgstr "Удалено удалённых элементов: %d."
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
#, javascript-format
|
||||
msgid "Fetched items: %d/%d."
|
||||
msgstr "Создано локальных элементов: %d."
|
||||
msgstr "Получено элементов: %d/%d."
|
||||
|
||||
#, javascript-format
|
||||
msgid "State: \"%s\"."
|
||||
@@ -945,17 +1004,25 @@ msgstr "Отмена..."
|
||||
msgid "Completed: %s"
|
||||
msgstr "Завершено: %s"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Last error: %s"
|
||||
msgstr "Последняя ошибка: %s"
|
||||
|
||||
msgid "Idle"
|
||||
msgstr "Простой"
|
||||
|
||||
msgid "In progress"
|
||||
msgstr "Выполнение"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr "Синхронизация уже выполняется. Статус: %s"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Encrypted"
|
||||
msgstr "Шифрование:"
|
||||
msgstr "Зашифровано"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Encrypted items cannot be modified"
|
||||
msgstr "Некоторые элементы не могут быть синхронизированы."
|
||||
msgstr "Зашифрованные элементы не могут быть изменены"
|
||||
|
||||
msgid "Conflicts"
|
||||
msgstr "Конфликты"
|
||||
@@ -1010,12 +1077,49 @@ msgstr "Светлая"
|
||||
msgid "Dark"
|
||||
msgstr "Тёмная"
|
||||
|
||||
msgid "Show uncompleted todos on top of the lists"
|
||||
msgstr "Показывать незавершённые задачи вверху списков"
|
||||
msgid "Uncompleted to-dos on top"
|
||||
msgstr "Незавершённые задачи сверху"
|
||||
|
||||
msgid "Sort notes by"
|
||||
msgstr "Сортировать заметки по"
|
||||
|
||||
msgid "Reverse sort order"
|
||||
msgstr "Обратный порядок сортировки"
|
||||
|
||||
msgid "Save geo-location with notes"
|
||||
msgstr "Сохранять информацию о геолокации в заметках"
|
||||
|
||||
msgid "When creating a new to-do:"
|
||||
msgstr "При создании новой задачи:"
|
||||
|
||||
msgid "Focus title"
|
||||
msgstr "Фокус на названии"
|
||||
|
||||
msgid "Focus body"
|
||||
msgstr "Фокус на содержимом"
|
||||
|
||||
msgid "When creating a new note:"
|
||||
msgstr "При создании новой заметки:"
|
||||
|
||||
msgid "Show tray icon"
|
||||
msgstr "Показывать иконку в панели задач"
|
||||
|
||||
msgid "Global zoom percentage"
|
||||
msgstr "Глобальный масштаб в процентах"
|
||||
|
||||
msgid "Editor font family"
|
||||
msgstr "Семейство шрифтов редактора"
|
||||
|
||||
msgid ""
|
||||
"The font name will not be checked. If incorrect or empty, it will default to "
|
||||
"a generic monospace font."
|
||||
msgstr ""
|
||||
"Название шрифта не проверяется. Если оно указано некорректно или не задано, "
|
||||
"будет использоваться стандартный моноширинный шрифт."
|
||||
|
||||
msgid "Automatically update the application"
|
||||
msgstr "Автоматически обновлять приложение"
|
||||
|
||||
msgid "Synchronisation interval"
|
||||
msgstr "Интервал синхронизации"
|
||||
|
||||
@@ -1031,9 +1135,6 @@ msgstr "%d час"
|
||||
msgid "%d hours"
|
||||
msgstr "%d часов"
|
||||
|
||||
msgid "Automatically update the application"
|
||||
msgstr "Автоматически обновлять приложение"
|
||||
|
||||
msgid "Show advanced options"
|
||||
msgstr "Показывать расширенные настройки"
|
||||
|
||||
@@ -1041,11 +1142,11 @@ msgid "Synchronisation target"
|
||||
msgstr "Цель синхронизации"
|
||||
|
||||
msgid ""
|
||||
"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)."
|
||||
msgstr ""
|
||||
"То, с чем будет осуществляться синхронизация. При синхронизации с файловой "
|
||||
"системой в `sync.2.path` указывается целевой каталог."
|
||||
"Цель синхронизации. Каждая цель синхронизации может иметь дополнительные "
|
||||
"параметры, именованные как «sync.NUM.NAME» (все описаны ниже)."
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr "Каталог синхронизации (абсолютный путь)"
|
||||
@@ -1057,22 +1158,81 @@ msgstr ""
|
||||
"Путь для синхронизации при включённой синхронизации с файловой системой. См. "
|
||||
"`sync.target`."
|
||||
|
||||
msgid "Nextcloud WebDAV URL"
|
||||
msgstr "Nextcloud WebDAV URL"
|
||||
|
||||
msgid "Nextcloud username"
|
||||
msgstr "Имя пользователя Nextcloud"
|
||||
|
||||
msgid "Nextcloud password"
|
||||
msgstr "Пароль Nextcloud"
|
||||
|
||||
msgid "WebDAV URL"
|
||||
msgstr "URL WebDAV"
|
||||
|
||||
msgid "WebDAV username"
|
||||
msgstr "Имя пользователя WebDAV"
|
||||
|
||||
msgid "WebDAV password"
|
||||
msgstr "Пароль WebDAV"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr "Неверное значение параметра: «%s». Доступные значения: %s."
|
||||
|
||||
msgid "Joplin Export File"
|
||||
msgstr "Файл экспорта Joplin"
|
||||
|
||||
msgid "Markdown"
|
||||
msgstr "Markdown"
|
||||
|
||||
msgid "Joplin Export Directory"
|
||||
msgstr "Папка экспорта Joplin"
|
||||
|
||||
msgid "Evernote Export File"
|
||||
msgstr "Файл экспорта Evernote"
|
||||
|
||||
msgid "Directory"
|
||||
msgstr "Директория"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Cannot load \"%s\" module for format \"%s\""
|
||||
msgstr "Не удалось загрузить модуль «%s» для формата «%s»"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Please specify import format for %s"
|
||||
msgstr "Пожалуйста, укажите формат импорта для %s"
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"This item is currently encrypted: %s \"%s\". Please wait for all items to be "
|
||||
"decrypted and try again."
|
||||
msgstr ""
|
||||
"Этот элемент сейчас зашифрован: %s «%s». Пожалуйста, дождитесь расшифровки "
|
||||
"всех элементов и попробуйте снова."
|
||||
|
||||
msgid "There is no data to export."
|
||||
msgstr "Нет данных для экспорта."
|
||||
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr ""
|
||||
"Пожалуйста, укажите блокнот, в который должны быть импортированы заметки."
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "Элементы, которые не могут быть синхронизированы"
|
||||
|
||||
#, 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 ""
|
||||
"Эти элементы будут оставаться на устройстве, но не будут загружены в целевой "
|
||||
"объект синхронизации. Чтобы найти эти элементы, воспользуйтесь поиском по "
|
||||
"названию или ID (который указывается в скобках выше)."
|
||||
|
||||
msgid "Sync status (synced items / total items)"
|
||||
msgstr "Статус синхронизации (элементов синхронизировано/всего)"
|
||||
@@ -1119,9 +1279,8 @@ msgstr "Лог"
|
||||
msgid "Export Debug Report"
|
||||
msgstr "Экспортировать отладочный отчёт"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Encryption Config"
|
||||
msgstr "Шифрование:"
|
||||
msgstr "Конфигурация шифрования"
|
||||
|
||||
msgid "Configuration"
|
||||
msgstr "Конфигурация"
|
||||
@@ -1134,7 +1293,7 @@ msgid "Move %d notes to notebook \"%s\"?"
|
||||
msgstr "Переместить %d заметок в блокнот «%s»?"
|
||||
|
||||
msgid "Press to set the decryption password."
|
||||
msgstr ""
|
||||
msgstr "Нажмите, чтобы установить пароль для расшифровки."
|
||||
|
||||
msgid "Select date"
|
||||
msgstr "Выбрать дату"
|
||||
@@ -1145,22 +1304,23 @@ msgstr "Подтвердить"
|
||||
msgid "Cancel synchronisation"
|
||||
msgstr "Отменить синхронизацию"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Joplin website"
|
||||
msgstr "Сайт Joplin"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Master Key %s"
|
||||
msgstr "Мастер-ключи"
|
||||
msgstr "Мастер-ключ %s"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
#, javascript-format
|
||||
msgid "Created: %s"
|
||||
msgstr "Создано: %d."
|
||||
msgstr "Создано: %s"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Password:"
|
||||
msgstr "Пароль"
|
||||
msgstr "Пароль:"
|
||||
|
||||
msgid "Password cannot be empty"
|
||||
msgstr ""
|
||||
msgstr "Пароль не может быть пустым"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable"
|
||||
msgstr "Включено"
|
||||
|
||||
@@ -1171,6 +1331,12 @@ msgstr "Не удалось сохранить блокнот: %s"
|
||||
msgid "Edit notebook"
|
||||
msgstr "Редактировать блокнот"
|
||||
|
||||
msgid "Show all"
|
||||
msgstr "Показать всё"
|
||||
|
||||
msgid "Errors only"
|
||||
msgstr "Только ошибки"
|
||||
|
||||
msgid "This note has been modified:"
|
||||
msgstr "Эта заметка была изменена:"
|
||||
|
||||
@@ -1224,8 +1390,99 @@ msgstr "У вас сейчас нет блокнота. Создайте его
|
||||
msgid "Welcome"
|
||||
msgstr "Добро пожаловать"
|
||||
|
||||
#~ msgid "Note title:"
|
||||
#~ msgstr "Название заметки:"
|
||||
#, fuzzy
|
||||
#~ msgid ""
|
||||
#~ "Release notes:\n"
|
||||
#~ "\n"
|
||||
#~ "%s"
|
||||
#~ msgstr "Удалить заметки?"
|
||||
|
||||
#~ msgid "Imports an Evernote notebook file (.enex file)."
|
||||
#~ msgstr "Импортирует файл блокнотов Evernote (.enex-файл)."
|
||||
|
||||
#~ msgid ""
|
||||
#~ "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
|
||||
#~ msgstr ""
|
||||
#~ "Файл «%s» будет импортирован в существующий блокнот «%s». Продолжить?"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "New notebook \"%s\" will be created and file \"%s\" will be imported into "
|
||||
#~ "it. Continue?"
|
||||
#~ msgstr ""
|
||||
#~ "Будет создан новый блокнот «%s» и в него будет импортирован файл «%s». "
|
||||
#~ "Продолжить?"
|
||||
|
||||
#~ msgid "Import Evernote notes"
|
||||
#~ msgstr "Импортировать заметки из Evernote"
|
||||
|
||||
#~ msgid "Give focus to next pane"
|
||||
#~ msgstr "Переключиться на следующую панель"
|
||||
|
||||
#~ msgid "Give focus to previous pane"
|
||||
#~ msgstr "Переключиться на предыдущую панель"
|
||||
|
||||
#~ msgid "Enter command line mode"
|
||||
#~ msgstr "Войти в режим командной строки"
|
||||
|
||||
#~ msgid "Exit command line mode"
|
||||
#~ msgstr "Выйти из режима командной строки"
|
||||
|
||||
#~ msgid "Edit the selected note"
|
||||
#~ msgstr "Редактировать выбранную заметку"
|
||||
|
||||
#~ msgid "Cancel the current command."
|
||||
#~ msgstr "Отменить текущую команду."
|
||||
|
||||
#~ msgid "Exit the application."
|
||||
#~ msgstr "Выйти из приложения."
|
||||
|
||||
#~ msgid "Delete the currently selected note or notebook."
|
||||
#~ msgstr "Удалить текущую выбранную заметку или блокнот."
|
||||
|
||||
#~ msgid "Set a to-do as completed / not completed"
|
||||
#~ msgstr "Отметить задачу как завершённую/незавершённую"
|
||||
|
||||
#~ msgid "[t]oggle [c]onsole between maximized/minimized/hidden/visible."
|
||||
#~ msgstr ""
|
||||
#~ "[tc] переключить консоль между развёрнутой/свёрнутой/скрытой/видимой."
|
||||
|
||||
#~ msgid "[t]oggle note [m]etadata."
|
||||
#~ msgstr "[tm] переключить отображение метаданных заметки."
|
||||
|
||||
#~ msgid "[M]ake a new [n]ote"
|
||||
#~ msgstr "[mn] создать новую заметку"
|
||||
|
||||
#~ msgid "[M]ake a new [t]odo"
|
||||
#~ msgstr "[mt] создать новую задачу"
|
||||
|
||||
#~ msgid "[M]ake a new note[b]ook"
|
||||
#~ msgstr "[mb] создать новый блокнот"
|
||||
|
||||
#~ msgid "Copy ([Y]ank) the [n]ote to a notebook."
|
||||
#~ msgstr "[yn] копировать заметку в блокнот."
|
||||
|
||||
#~ msgid "Move the note to a notebook."
|
||||
#~ msgstr "Переместить заметку в блокнот."
|
||||
|
||||
#~ msgid "Error"
|
||||
#~ msgstr "Ошибка"
|
||||
|
||||
#~ msgid "Could not download the update: %s"
|
||||
#~ msgstr "Не удалось загрузить обновление: %s"
|
||||
|
||||
#~ msgid "New version downloaded - application will quit now and update..."
|
||||
#~ msgstr ""
|
||||
#~ "Новая версия загружена — приложение сейчас будет закрыто и обновлено..."
|
||||
|
||||
#~ msgid "Could not install the update: %s"
|
||||
#~ msgstr "Не удалось установить обновление: %s"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "The target to synchonise to. If synchronising with the file system, set "
|
||||
#~ "`sync.2.path` to specify the target directory."
|
||||
#~ msgstr ""
|
||||
#~ "То, с чем будет осуществляться синхронизация. При синхронизации с "
|
||||
#~ "файловой системой в `sync.2.path` указывается целевой каталог."
|
||||
|
||||
#~ msgid "To-do title:"
|
||||
#~ msgstr "Название задачи:"
|
||||
|
@@ -15,63 +15,12 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "Give focus to next pane"
|
||||
msgstr "聚焦于下个面板"
|
||||
|
||||
msgid "Give focus to previous pane"
|
||||
msgstr "聚焦于上个面板"
|
||||
|
||||
msgid "Enter command line mode"
|
||||
msgstr "进入命令行模式"
|
||||
|
||||
msgid "Exit command line mode"
|
||||
msgstr "退出命令行模式"
|
||||
|
||||
msgid "Edit the selected note"
|
||||
msgstr "编辑所选笔记"
|
||||
|
||||
msgid "Cancel the current command."
|
||||
msgstr "取消当前命令。"
|
||||
|
||||
msgid "Exit the application."
|
||||
msgstr "退出程序。"
|
||||
|
||||
msgid "Delete the currently selected note or notebook."
|
||||
msgstr "删除当前所选笔记或笔记本。"
|
||||
|
||||
msgid "To delete a tag, untag the associated notes."
|
||||
msgstr "移除相关笔记的标签后才可删除此标签。"
|
||||
|
||||
msgid "Please select the note or notebook to be deleted first."
|
||||
msgstr "请选择最先删除的笔记或笔记本。"
|
||||
|
||||
msgid "Set a to-do as completed / not completed"
|
||||
msgstr "设置待办事项为已完成或未完成"
|
||||
|
||||
msgid "[t]oggle [c]onsole between maximized/minimized/hidden/visible."
|
||||
msgstr "在最大化/最小化/隐藏/显示间切换[t]控制台[c]。"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "搜索"
|
||||
|
||||
msgid "[t]oggle note [m]etadata."
|
||||
msgstr "切换[t]笔记元数据[m]。"
|
||||
|
||||
msgid "[M]ake a new [n]ote"
|
||||
msgstr "创建[M]新笔记[n]"
|
||||
|
||||
msgid "[M]ake a new [t]odo"
|
||||
msgstr "创建[M]新待办事项[t]"
|
||||
|
||||
msgid "[M]ake a new note[b]ook"
|
||||
msgstr "创建[M]新笔记本[b]"
|
||||
|
||||
msgid "Copy ([Y]ank) the [n]ote to a notebook."
|
||||
msgstr "复制[Y]笔记[n]至笔记本。"
|
||||
|
||||
msgid "Move the note to a notebook."
|
||||
msgstr "移动笔记至笔记本。"
|
||||
|
||||
msgid "Press Ctrl+D or type \"exit\" to exit the application"
|
||||
msgstr "按Ctrl+D或输入\"exit\"退出程序"
|
||||
|
||||
@@ -218,19 +167,28 @@ msgstr "此笔记不存在:\"%s\"。是否创建?"
|
||||
msgid "Starting to edit note. Close the editor to get back to the prompt."
|
||||
msgstr "开始编辑笔记。关闭编辑器则返回提示。"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Error opening note in editor: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note has been saved."
|
||||
msgstr "笔记已被保存。"
|
||||
|
||||
msgid "Exits the application."
|
||||
msgstr "退出程序。"
|
||||
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"Exports Joplin data to the given directory. By default, it will export the "
|
||||
"Exports Joplin data to the given path. By default, it will export the "
|
||||
"complete database including notebooks, notes, tags and resources."
|
||||
msgstr ""
|
||||
"导出Joplin数据至给定文件目录。默认为导出所有的数据库,包含笔记本、笔记、标签"
|
||||
"及资源。"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Destination format: %s"
|
||||
msgstr "日期格式"
|
||||
|
||||
msgid "Exports only the given note."
|
||||
msgstr "仅导出给定笔记。"
|
||||
|
||||
@@ -243,6 +201,10 @@ msgstr "显示此笔记的地理定位URL地址。"
|
||||
msgid "Displays usage information."
|
||||
msgstr "显示使用信息。"
|
||||
|
||||
#, javascript-format
|
||||
msgid "For information on how to customise the shortcuts please visit %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Shortcuts are not available in CLI mode."
|
||||
msgstr "快捷键在CLI模式下不可用。"
|
||||
|
||||
@@ -280,26 +242,21 @@ msgstr "按\":\"键进入命令行模式"
|
||||
msgid "To exit command line mode, press ESCAPE"
|
||||
msgstr "按ESC键退出命令行模式"
|
||||
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"For the complete list of available keyboard shortcuts, type `help shortcuts`"
|
||||
"For the list of keyboard shortcuts and config options, type `help keymap`"
|
||||
msgstr "输入`help shortcuts`显示全部可用的快捷键列表。"
|
||||
|
||||
msgid "Imports an Evernote notebook file (.enex file)."
|
||||
msgstr "导入Evernote笔记本文件(.enex文件)。"
|
||||
msgid "Imports data into Joplin."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Source format: %s"
|
||||
msgstr "无以下命令:%s"
|
||||
|
||||
msgid "Do not ask for confirmation."
|
||||
msgstr "不再要求确认。"
|
||||
|
||||
#, javascript-format
|
||||
msgid "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
|
||||
msgstr "文件\"%s\"将会被导入至现有笔记本\"%s\"。是否继续?"
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"New notebook \"%s\" will be created and file \"%s\" will be imported into "
|
||||
"it. Continue?"
|
||||
msgstr "将创建新笔记本\"%s\"并将文件\"%s\"导入至其中。是否继续?"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Found: %d."
|
||||
msgstr "已找到:%d条。"
|
||||
@@ -426,6 +383,14 @@ msgstr "与远程储存空间同步。"
|
||||
msgid "Sync to provided target (defaults to sync.target config value)"
|
||||
msgstr "同步至所提供的目标(默认为同步目标配置值)"
|
||||
|
||||
msgid ""
|
||||
"Authentication was not completed (did not receive an authentication token)."
|
||||
msgstr "认证未完成(未收到认证令牌)。"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Not authentified with %s. Please provide any missing credentials."
|
||||
msgstr ""
|
||||
|
||||
msgid "Synchronisation is already in progress."
|
||||
msgstr "同步正在进行中。"
|
||||
|
||||
@@ -438,10 +403,6 @@ msgstr ""
|
||||
"锁定文件已被保留。若当前没有任何正在进行的同步,您可以在\"%s\"删除锁定文件并"
|
||||
"继续操作。"
|
||||
|
||||
msgid ""
|
||||
"Authentication was not completed (did not receive an authentication token)."
|
||||
msgstr "认证未完成(未收到认证令牌)。"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Synchronisation target: %s (%s)"
|
||||
msgstr "同步目标:%s (%s)"
|
||||
@@ -510,6 +471,10 @@ msgstr "默认值: %s"
|
||||
msgid "Possible keys/values:"
|
||||
msgstr "可用键/值:"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Type `joplin help` for usage information."
|
||||
msgstr "显示使用信息。"
|
||||
|
||||
msgid "Fatal error:"
|
||||
msgstr "严重错误:"
|
||||
|
||||
@@ -550,6 +515,18 @@ msgid ""
|
||||
"background and will be available soon."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Exporting to \"%s\" as \"%s\" format. Please wait..."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "PDF File"
|
||||
msgstr "文件"
|
||||
|
||||
msgid "File"
|
||||
msgstr "文件"
|
||||
|
||||
@@ -562,11 +539,19 @@ msgstr "新待办事项"
|
||||
msgid "New notebook"
|
||||
msgstr "新笔记本"
|
||||
|
||||
msgid "Import Evernote notes"
|
||||
msgstr "导入Evernote笔记"
|
||||
msgid "Import"
|
||||
msgstr "导入"
|
||||
|
||||
msgid "Evernote Export Files"
|
||||
msgstr "Evernote导出文件"
|
||||
#, fuzzy
|
||||
msgid "Export"
|
||||
msgstr "导入"
|
||||
|
||||
msgid "Print"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Hide %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Quit"
|
||||
msgstr "退出"
|
||||
@@ -586,6 +571,12 @@ msgstr "粘贴"
|
||||
msgid "Search in all the notes"
|
||||
msgstr "在所有笔记内搜索"
|
||||
|
||||
msgid "View"
|
||||
msgstr ""
|
||||
|
||||
msgid "Toggle editor layout"
|
||||
msgstr ""
|
||||
|
||||
msgid "Tools"
|
||||
msgstr "工具"
|
||||
|
||||
@@ -605,6 +596,13 @@ msgstr "帮助"
|
||||
msgid "Website and documentation"
|
||||
msgstr "网站与文档"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Make a donation"
|
||||
msgstr "网站与文档"
|
||||
|
||||
msgid "Check for updates..."
|
||||
msgstr ""
|
||||
|
||||
msgid "About Joplin"
|
||||
msgstr "关于Joplin"
|
||||
|
||||
@@ -612,12 +610,36 @@ msgstr "关于Joplin"
|
||||
msgid "%s %s (%s, %s)"
|
||||
msgstr "%s %s (%s, %s)"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr "%s:%s"
|
||||
|
||||
msgid "Exit"
|
||||
msgstr ""
|
||||
|
||||
msgid "OK"
|
||||
msgstr "确认"
|
||||
|
||||
msgid "Cancel"
|
||||
msgstr "取消"
|
||||
|
||||
msgid "Current version is up-to-date."
|
||||
msgstr ""
|
||||
|
||||
msgid "An update is available, do you want to download it now?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Yes"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "No"
|
||||
msgstr "否"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr "取消同步"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr ""
|
||||
@@ -676,6 +698,15 @@ msgid ""
|
||||
"how the notes or notebooks were originally encrypted."
|
||||
msgstr ""
|
||||
|
||||
msgid "Missing Master Keys"
|
||||
msgstr ""
|
||||
|
||||
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 ""
|
||||
|
||||
msgid "Status"
|
||||
msgstr "状态"
|
||||
|
||||
@@ -711,6 +742,9 @@ msgstr "重命名笔记本:"
|
||||
msgid "Set alarm:"
|
||||
msgstr "设置提醒:"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "搜索"
|
||||
|
||||
msgid "Layout"
|
||||
msgstr "布局"
|
||||
|
||||
@@ -747,6 +781,13 @@ msgid ""
|
||||
"There is currently no notebook. Create one by clicking on \"New notebook\"."
|
||||
msgstr "当前无笔记。点击(+)创建新笔记。"
|
||||
|
||||
msgid "Open..."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Save as..."
|
||||
msgstr "保存更改"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Unsupported link or message: %s"
|
||||
msgstr "不支持的链接或信息:%s"
|
||||
@@ -754,9 +795,30 @@ msgstr "不支持的链接或信息:%s"
|
||||
msgid "Attach file"
|
||||
msgstr "附加文件"
|
||||
|
||||
msgid "Tags"
|
||||
msgstr "标签"
|
||||
|
||||
msgid "Set alarm"
|
||||
msgstr "设置提醒"
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"This note has no content. Click on \"%s\" to toggle the editor and edit the "
|
||||
"note."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "to-do"
|
||||
msgstr "新待办事项"
|
||||
|
||||
#, fuzzy
|
||||
msgid "note"
|
||||
msgstr "新笔记"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Creating new %s..."
|
||||
msgstr "正在导入笔记..."
|
||||
|
||||
msgid "Refresh"
|
||||
msgstr "刷新"
|
||||
|
||||
@@ -766,9 +828,6 @@ msgstr "清除"
|
||||
msgid "OneDrive Login"
|
||||
msgstr "登陆OneDrive"
|
||||
|
||||
msgid "Import"
|
||||
msgstr "导入"
|
||||
|
||||
msgid "Options"
|
||||
msgstr "选项"
|
||||
|
||||
@@ -793,9 +852,6 @@ msgstr "同步"
|
||||
msgid "Notebooks"
|
||||
msgstr "笔记本"
|
||||
|
||||
msgid "Tags"
|
||||
msgstr "标签"
|
||||
|
||||
msgid "Searches"
|
||||
msgstr "搜索历史"
|
||||
|
||||
@@ -814,12 +870,18 @@ msgstr "未知标记:%s"
|
||||
msgid "File system"
|
||||
msgstr "文件系统"
|
||||
|
||||
msgid "Nextcloud"
|
||||
msgstr ""
|
||||
|
||||
msgid "OneDrive"
|
||||
msgstr "OneDrive"
|
||||
|
||||
msgid "OneDrive Dev (For testing only)"
|
||||
msgstr "OneDrive开发员(仅测试用)"
|
||||
|
||||
msgid "WebDAV"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Unknown log level: %s"
|
||||
msgstr "未知日志level:%s"
|
||||
@@ -890,6 +952,16 @@ msgstr "正在取消..."
|
||||
msgid "Completed: %s"
|
||||
msgstr "已完成:\"%s\""
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Last error: %s"
|
||||
msgstr "严重错误:"
|
||||
|
||||
msgid "Idle"
|
||||
msgstr ""
|
||||
|
||||
msgid "In progress"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr "同步正在进行中。状态:%s"
|
||||
@@ -952,12 +1024,52 @@ msgstr "浅色"
|
||||
msgid "Dark"
|
||||
msgstr "深色"
|
||||
|
||||
msgid "Show uncompleted todos on top of the lists"
|
||||
#, fuzzy
|
||||
msgid "Uncompleted to-dos on top"
|
||||
msgstr "在列表上方显示未完成的待办事项"
|
||||
|
||||
msgid "Sort notes by"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Reverse sort order"
|
||||
msgstr "反转排序顺序。"
|
||||
|
||||
msgid "Save geo-location with notes"
|
||||
msgstr "保存笔记时同时保存地理定位信息"
|
||||
|
||||
#, fuzzy
|
||||
msgid "When creating a new to-do:"
|
||||
msgstr "创建新待办事项。"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Focus title"
|
||||
msgstr "笔记标题:"
|
||||
|
||||
msgid "Focus body"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "When creating a new note:"
|
||||
msgstr "创建新笔记。"
|
||||
|
||||
msgid "Show tray icon"
|
||||
msgstr ""
|
||||
|
||||
msgid "Global zoom percentage"
|
||||
msgstr ""
|
||||
|
||||
msgid "Editor font family"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"The font name will not be checked. If incorrect or empty, it will default to "
|
||||
"a generic monospace font."
|
||||
msgstr ""
|
||||
|
||||
msgid "Automatically update the application"
|
||||
msgstr "自动更新此程序"
|
||||
|
||||
msgid "Synchronisation interval"
|
||||
msgstr "同步间隔"
|
||||
|
||||
@@ -973,9 +1085,6 @@ msgstr "%d小时"
|
||||
msgid "%d hours"
|
||||
msgstr "%d小时"
|
||||
|
||||
msgid "Automatically update the application"
|
||||
msgstr "自动更新此程序"
|
||||
|
||||
msgid "Show advanced options"
|
||||
msgstr "显示高级选项"
|
||||
|
||||
@@ -983,9 +1092,9 @@ msgid "Synchronisation target"
|
||||
msgstr "同步目标"
|
||||
|
||||
msgid ""
|
||||
"The target to synchonise to. If synchronising with the file system, set "
|
||||
"`sync.2.path` to specify the target directory."
|
||||
msgstr "同步的目标。若与文件系统同步,设置`sync.2.path`为指定目标目录。"
|
||||
"The target to synchonise to. Each sync target may have additional parameters "
|
||||
"which are named as `sync.NUM.NAME` (all documented below)."
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr ""
|
||||
@@ -995,10 +1104,66 @@ msgid ""
|
||||
"See `sync.target`."
|
||||
msgstr "当文件系统同步开启时的同步路径。参考`sync.target`。"
|
||||
|
||||
msgid "Nextcloud WebDAV URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud username"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud password"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV username"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV password"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr "无效的选项值:\"%s\"。可用值为:%s。"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Joplin Export File"
|
||||
msgstr "Evernote导出文件"
|
||||
|
||||
msgid "Markdown"
|
||||
msgstr ""
|
||||
|
||||
msgid "Joplin Export Directory"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Evernote Export File"
|
||||
msgstr "Evernote导出文件"
|
||||
|
||||
msgid "Directory"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Cannot load \"%s\" module for format \"%s\""
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Please specify import format for %s"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"This item is currently encrypted: %s \"%s\". Please wait for all items to be "
|
||||
"decrypted and try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "There is no data to export."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr "请选择最先删除的笔记或笔记本。"
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "项目无法被同步。"
|
||||
|
||||
@@ -1082,6 +1247,9 @@ msgstr "确认"
|
||||
msgid "Cancel synchronisation"
|
||||
msgstr "取消同步"
|
||||
|
||||
msgid "Joplin website"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Master Key %s"
|
||||
msgstr ""
|
||||
@@ -1107,6 +1275,12 @@ msgstr "此笔记本无法保存:%s"
|
||||
msgid "Edit notebook"
|
||||
msgstr "编辑笔记本"
|
||||
|
||||
msgid "Show all"
|
||||
msgstr ""
|
||||
|
||||
msgid "Errors only"
|
||||
msgstr ""
|
||||
|
||||
msgid "This note has been modified:"
|
||||
msgstr "此笔记已被修改:"
|
||||
|
||||
@@ -1158,8 +1332,80 @@ msgstr "您当前没有任何笔记本。点击(+)按钮创建新笔记本。"
|
||||
msgid "Welcome"
|
||||
msgstr "欢迎"
|
||||
|
||||
#~ msgid "Note title:"
|
||||
#~ msgstr "笔记标题:"
|
||||
#, fuzzy
|
||||
#~ msgid ""
|
||||
#~ "Release notes:\n"
|
||||
#~ "\n"
|
||||
#~ "%s"
|
||||
#~ msgstr "是否删除笔记?"
|
||||
|
||||
#~ msgid "Imports an Evernote notebook file (.enex file)."
|
||||
#~ msgstr "导入Evernote笔记本文件(.enex文件)。"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
|
||||
#~ msgstr "文件\"%s\"将会被导入至现有笔记本\"%s\"。是否继续?"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "New notebook \"%s\" will be created and file \"%s\" will be imported into "
|
||||
#~ "it. Continue?"
|
||||
#~ msgstr "将创建新笔记本\"%s\"并将文件\"%s\"导入至其中。是否继续?"
|
||||
|
||||
#~ msgid "Import Evernote notes"
|
||||
#~ msgstr "导入Evernote笔记"
|
||||
|
||||
#~ msgid "Give focus to next pane"
|
||||
#~ msgstr "聚焦于下个面板"
|
||||
|
||||
#~ msgid "Give focus to previous pane"
|
||||
#~ msgstr "聚焦于上个面板"
|
||||
|
||||
#~ msgid "Enter command line mode"
|
||||
#~ msgstr "进入命令行模式"
|
||||
|
||||
#~ msgid "Exit command line mode"
|
||||
#~ msgstr "退出命令行模式"
|
||||
|
||||
#~ msgid "Edit the selected note"
|
||||
#~ msgstr "编辑所选笔记"
|
||||
|
||||
#~ msgid "Cancel the current command."
|
||||
#~ msgstr "取消当前命令。"
|
||||
|
||||
#~ msgid "Exit the application."
|
||||
#~ msgstr "退出程序。"
|
||||
|
||||
#~ msgid "Delete the currently selected note or notebook."
|
||||
#~ msgstr "删除当前所选笔记或笔记本。"
|
||||
|
||||
#~ msgid "Set a to-do as completed / not completed"
|
||||
#~ msgstr "设置待办事项为已完成或未完成"
|
||||
|
||||
#~ msgid "[t]oggle [c]onsole between maximized/minimized/hidden/visible."
|
||||
#~ msgstr "在最大化/最小化/隐藏/显示间切换[t]控制台[c]。"
|
||||
|
||||
#~ msgid "[t]oggle note [m]etadata."
|
||||
#~ msgstr "切换[t]笔记元数据[m]。"
|
||||
|
||||
#~ msgid "[M]ake a new [n]ote"
|
||||
#~ msgstr "创建[M]新笔记[n]"
|
||||
|
||||
#~ msgid "[M]ake a new [t]odo"
|
||||
#~ msgstr "创建[M]新待办事项[t]"
|
||||
|
||||
#~ msgid "[M]ake a new note[b]ook"
|
||||
#~ msgstr "创建[M]新笔记本[b]"
|
||||
|
||||
#~ msgid "Copy ([Y]ank) the [n]ote to a notebook."
|
||||
#~ msgstr "复制[Y]笔记[n]至笔记本。"
|
||||
|
||||
#~ msgid "Move the note to a notebook."
|
||||
#~ msgstr "移动笔记至笔记本。"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "The target to synchonise to. If synchronising with the file system, set "
|
||||
#~ "`sync.2.path` to specify the target directory."
|
||||
#~ msgstr "同步的目标。若与文件系统同步,设置`sync.2.path`为指定目标目录。"
|
||||
|
||||
#~ msgid "To-do title:"
|
||||
#~ msgstr "待办事项标题:"
|
||||
|
120
CliClient/package-lock.json
generated
120
CliClient/package-lock.json
generated
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "joplin",
|
||||
"version": "0.10.87",
|
||||
"version": "1.0.100",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"ajv": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.3.0.tgz",
|
||||
"integrity": "sha1-RBT/dKUIecII7l/cgm4ywwNUnto=",
|
||||
"version": "5.5.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
|
||||
"integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
|
||||
"requires": {
|
||||
"co": "4.6.0",
|
||||
"fast-deep-equal": "1.0.0",
|
||||
@@ -64,6 +64,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"async-mutex": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.1.3.tgz",
|
||||
"integrity": "sha1-Cq0hEjaXlas/F+M3RFVtLs9UdWY="
|
||||
},
|
||||
"asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
@@ -85,6 +90,11 @@
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
|
||||
"dev": true
|
||||
},
|
||||
"base-64": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
|
||||
"integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs="
|
||||
},
|
||||
"bcrypt-pbkdf": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
|
||||
@@ -425,6 +435,14 @@
|
||||
"universalify": "0.1.1"
|
||||
}
|
||||
},
|
||||
"fs-minipass": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz",
|
||||
"integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==",
|
||||
"requires": {
|
||||
"minipass": "2.2.1"
|
||||
}
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
@@ -442,7 +460,7 @@
|
||||
"ndarray": "1.0.18",
|
||||
"ndarray-pack": "1.2.1",
|
||||
"node-bitmap": "0.0.1",
|
||||
"omggif": "1.0.8",
|
||||
"omggif": "1.0.9",
|
||||
"parse-data-uri": "0.2.0",
|
||||
"pngjs": "2.3.1",
|
||||
"request": "2.83.0",
|
||||
@@ -494,7 +512,7 @@
|
||||
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz",
|
||||
"integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=",
|
||||
"requires": {
|
||||
"ajv": "5.3.0",
|
||||
"ajv": "5.5.2",
|
||||
"har-schema": "2.0.0"
|
||||
}
|
||||
},
|
||||
@@ -848,9 +866,9 @@
|
||||
}
|
||||
},
|
||||
"minizlib": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.0.4.tgz",
|
||||
"integrity": "sha512-sN4U9tIJtBRwKbwgFh9qJfrPIQ/GGTRr1MGqkgOeMTLy8/lM0FcWU//FqlnZ3Vb7gJ+Mxh3FOg1EklibdajbaQ==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.0.tgz",
|
||||
"integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==",
|
||||
"requires": {
|
||||
"minipass": "2.2.1"
|
||||
}
|
||||
@@ -953,9 +971,9 @@
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
|
||||
},
|
||||
"omggif": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.8.tgz",
|
||||
"integrity": "sha1-F483sqsLPXtG7ToORr0HkLWNNTA="
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.9.tgz",
|
||||
"integrity": "sha1-3LcCTazVDFK00wPwSALJHAV8dl8="
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
@@ -1050,6 +1068,11 @@
|
||||
"strict-uri-encode": "1.1.0"
|
||||
}
|
||||
},
|
||||
"querystringify": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-1.0.0.tgz",
|
||||
"integrity": "sha1-YoYkIRLFtxL6ZU5SZlK/ahP/Bcs="
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
|
||||
@@ -1104,6 +1127,11 @@
|
||||
"uuid": "3.1.0"
|
||||
}
|
||||
},
|
||||
"requires-port": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
|
||||
},
|
||||
"retry": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz",
|
||||
@@ -1141,6 +1169,20 @@
|
||||
"semver": "5.4.1",
|
||||
"simple-get": "2.7.0",
|
||||
"tar": "3.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"tar": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-3.2.1.tgz",
|
||||
"integrity": "sha512-ZSzds1E0IqutvMU8HxjMaU8eB7urw2fGwTq88ukDOVuUIh0656l7/P7LiVPxhO5kS4flcRJQk8USG+cghQbTUQ==",
|
||||
"requires": {
|
||||
"chownr": "1.0.1",
|
||||
"minipass": "2.2.1",
|
||||
"minizlib": "1.1.0",
|
||||
"mkdirp": "0.5.1",
|
||||
"yallist": "3.0.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"simple-concat": {
|
||||
@@ -1920,9 +1962,9 @@
|
||||
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM="
|
||||
},
|
||||
"string-kit": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/string-kit/-/string-kit-0.6.3.tgz",
|
||||
"integrity": "sha512-G2T92klsuE+S9mqdKQyWurFweNQV5X+FRzSKTqYHRdaVUN/4dL6urbYJJ+xb9ep/4XWm+4RNT8j3acncNhFRBg==",
|
||||
"version": "0.6.4",
|
||||
"resolved": "https://registry.npmjs.org/string-kit/-/string-kit-0.6.4.tgz",
|
||||
"integrity": "sha512-imrOojdsXlL6xzfERCxvc/iA9Zwpzbfs+qeP6VB0s0rQVnMc3Nwkyhge0e8Uoayph7PVAwPNmLpohox27G3fgA==",
|
||||
"requires": {
|
||||
"xregexp": "3.2.0"
|
||||
}
|
||||
@@ -1985,13 +2027,14 @@
|
||||
"integrity": "sha1-Kb9hXUqnEhvdiYsi1LP5vE4qoD0="
|
||||
},
|
||||
"tar": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-3.2.1.tgz",
|
||||
"integrity": "sha512-ZSzds1E0IqutvMU8HxjMaU8eB7urw2fGwTq88ukDOVuUIh0656l7/P7LiVPxhO5kS4flcRJQk8USG+cghQbTUQ==",
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.0.tgz",
|
||||
"integrity": "sha512-gJlTiiErwo96K904FnoYWl+5+FBgS+FimU6GMh66XLdLa55al8+d4jeDfPoGwSNHdtWI5FJP6xurmVqhBuGJpQ==",
|
||||
"requires": {
|
||||
"chownr": "1.0.1",
|
||||
"fs-minipass": "1.2.5",
|
||||
"minipass": "2.2.1",
|
||||
"minizlib": "1.0.4",
|
||||
"minizlib": "1.1.0",
|
||||
"mkdirp": "0.5.1",
|
||||
"yallist": "3.0.2"
|
||||
}
|
||||
@@ -2019,15 +2062,15 @@
|
||||
}
|
||||
},
|
||||
"terminal-kit": {
|
||||
"version": "1.14.0",
|
||||
"resolved": "https://registry.npmjs.org/terminal-kit/-/terminal-kit-1.14.0.tgz",
|
||||
"integrity": "sha512-ir0I2QtcBDSg2w0UvohlqdDpGlS3S2UYBG4NnYKnK/4VywgnbfxgdpXN3el0uCH3OeH6fG38luW7RmDM96FqUw==",
|
||||
"version": "1.14.3",
|
||||
"resolved": "https://registry.npmjs.org/terminal-kit/-/terminal-kit-1.14.3.tgz",
|
||||
"integrity": "sha512-ZHtuElnBhK0IXOYNvQ7eYgaArwEoOv7saQc4Q0Z9p02JeC7iajC20/odV77BKB3jw/Qthvf9mpASf8gNDYv7xQ==",
|
||||
"requires": {
|
||||
"async-kit": "2.2.3",
|
||||
"get-pixels": "3.3.0",
|
||||
"ndarray": "1.0.18",
|
||||
"nextgen-events": "0.10.2",
|
||||
"string-kit": "0.6.3",
|
||||
"string-kit": "0.6.4",
|
||||
"tree-kit": "0.5.26"
|
||||
}
|
||||
},
|
||||
@@ -2037,16 +2080,16 @@
|
||||
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
|
||||
},
|
||||
"tkwidgets": {
|
||||
"version": "0.5.20",
|
||||
"resolved": "https://registry.npmjs.org/tkwidgets/-/tkwidgets-0.5.20.tgz",
|
||||
"integrity": "sha512-9wGsMrrFJvE/6TKUc0dEFFhwxvZLeNsYOxnpy1JCwyk/hYCEF70nuvk7VvJeG4TPaQBaGKPj6c7pCgdREvz4Jw==",
|
||||
"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",
|
||||
"node-emoji": "git+https://github.com/laurent22/node-emoji.git#9fa01eac463e94dde1316ef8c53089eeef4973b5",
|
||||
"slice-ansi": "1.0.0",
|
||||
"string-width": "2.1.1",
|
||||
"terminal-kit": "1.14.0",
|
||||
"terminal-kit": "1.14.3",
|
||||
"wrap-ansi": "3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -2100,6 +2143,15 @@
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz",
|
||||
"integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc="
|
||||
},
|
||||
"url-parse": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.2.0.tgz",
|
||||
"integrity": "sha512-DT1XbYAfmQP65M/mE6OALxmXzZ/z1+e5zk2TcSKe/KiYbNGZxgtttzC0mR/sjopbpOXcbniq7eIKmocJnUWlEw==",
|
||||
"requires": {
|
||||
"querystringify": "1.0.0",
|
||||
"requires-port": "1.0.0"
|
||||
}
|
||||
},
|
||||
"url-to-options": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz",
|
||||
@@ -2144,6 +2196,20 @@
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
},
|
||||
"xml2js": {
|
||||
"version": "0.4.19",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
|
||||
"integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
|
||||
"requires": {
|
||||
"sax": "1.2.4",
|
||||
"xmlbuilder": "9.0.4"
|
||||
}
|
||||
},
|
||||
"xmlbuilder": {
|
||||
"version": "9.0.4",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.4.tgz",
|
||||
"integrity": "sha1-UZy0ymhtAFqEINNJbz8MruzKWA8="
|
||||
},
|
||||
"xregexp": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/xregexp/-/xregexp-3.2.0.tgz",
|
||||
|
@@ -19,7 +19,7 @@
|
||||
],
|
||||
"owner": "Laurent Cozic"
|
||||
},
|
||||
"version": "0.10.87",
|
||||
"version": "1.0.100",
|
||||
"bin": {
|
||||
"joplin": "./main.js"
|
||||
},
|
||||
@@ -28,6 +28,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"app-module-path": "^2.2.0",
|
||||
"async-mutex": "^0.1.3",
|
||||
"base-64": "^0.1.0",
|
||||
"compare-version": "^0.1.2",
|
||||
"follow-redirects": "^1.2.4",
|
||||
"form-data": "^2.1.4",
|
||||
@@ -42,7 +44,6 @@
|
||||
"node-emoji": "^1.8.1",
|
||||
"node-fetch": "^1.7.1",
|
||||
"node-persist": "^2.1.0",
|
||||
"os-tmpdir": "^1.0.2",
|
||||
"promise": "^7.1.1",
|
||||
"proper-lockfile": "^2.0.1",
|
||||
"query-string": "4.3.4",
|
||||
@@ -55,10 +56,13 @@
|
||||
"string-padding": "^1.0.2",
|
||||
"string-to-stream": "^1.1.0",
|
||||
"strip-ansi": "^4.0.0",
|
||||
"tar": "^4.4.0",
|
||||
"tcp-port-used": "^0.1.2",
|
||||
"tkwidgets": "^0.5.20",
|
||||
"tkwidgets": "^0.5.25",
|
||||
"url-parse": "^1.2.0",
|
||||
"uuid": "^3.0.1",
|
||||
"word-wrap": "^1.2.3",
|
||||
"xml2js": "^0.4.19",
|
||||
"yargs-parser": "^7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@@ -11,7 +11,7 @@ cp "$SCRIPT_DIR/../README.md" build/
|
||||
cd "$SCRIPT_DIR/build"
|
||||
npm publish
|
||||
|
||||
NEW_VERSION=$("cat package.json | jq -r .version")
|
||||
NEW_VERSION=$(cat package.json | jq -r .version)
|
||||
git add -A
|
||||
git commit -m "CLI v$NEW_VERSION"
|
||||
git tag "cli-v$NEW_VERSION"
|
||||
|
@@ -9,9 +9,7 @@ rsync -a "$ROOT_DIR/build/locales/" "$BUILD_DIR/locales/"
|
||||
mkdir -p "$BUILD_DIR/data"
|
||||
|
||||
if [[ $TEST_FILE == "" ]]; then
|
||||
(cd "$ROOT_DIR" && npm test tests-build/synchronizer.js)
|
||||
(cd "$ROOT_DIR" && npm test tests-build/encryption.js)
|
||||
(cd "$ROOT_DIR" && npm test tests-build/ArrayUtils.js)
|
||||
(cd "$ROOT_DIR" && npm test tests-build/synchronizer.js tests-build/encryption.js tests-build/ArrayUtils.js tests-build/models_Setting.js tests-build/services_InteropService.js)
|
||||
else
|
||||
(cd "$ROOT_DIR" && npm test tests-build/$TEST_FILE.js)
|
||||
fi
|
@@ -8,7 +8,7 @@ process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
});
|
||||
|
||||
describe('Encryption', function() {
|
||||
describe('ArrayUtils', function() {
|
||||
|
||||
beforeEach(async (done) => {
|
||||
done();
|
||||
@@ -29,4 +29,19 @@ describe('Encryption', function() {
|
||||
done();
|
||||
});
|
||||
|
||||
it('should find items using binary search', async (done) => {
|
||||
let items = ['aaa', 'ccc', 'bbb'];
|
||||
expect(ArrayUtils.binarySearch(items, 'bbb')).toBe(-1); // Array not sorted!
|
||||
items.sort();
|
||||
expect(ArrayUtils.binarySearch(items, 'bbb')).toBe(1);
|
||||
expect(ArrayUtils.binarySearch(items, 'ccc')).toBe(2);
|
||||
expect(ArrayUtils.binarySearch(items, 'oops')).toBe(-1);
|
||||
expect(ArrayUtils.binarySearch(items, 'aaa')).toBe(0);
|
||||
|
||||
items = [];
|
||||
expect(ArrayUtils.binarySearch(items, 'aaa')).toBe(-1);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
32
CliClient/tests/models_Setting.js
Normal file
32
CliClient/tests/models_Setting.js
Normal file
@@ -0,0 +1,32 @@
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
});
|
||||
|
||||
describe('models_Setting', function() {
|
||||
|
||||
beforeEach(async (done) => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('should return only sub-values', asyncTest(async () => {
|
||||
const settings = {
|
||||
'sync.5.path': 'http://example.com',
|
||||
'sync.5.username': 'testing',
|
||||
}
|
||||
|
||||
let output = Setting.subValues('sync.5', settings);
|
||||
expect(output['path']).toBe('http://example.com');
|
||||
expect(output['username']).toBe('testing');
|
||||
|
||||
output = Setting.subValues('sync.4', settings);
|
||||
expect('path' in output).toBe(false);
|
||||
expect('username' in output).toBe(false);
|
||||
}));
|
||||
|
||||
});
|
252
CliClient/tests/services_InteropService.js
Normal file
252
CliClient/tests/services_InteropService.js
Normal file
@@ -0,0 +1,252 @@
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const InteropService = require('lib/services/InteropService.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const Tag = require('lib/models/Tag.js');
|
||||
const NoteTag = require('lib/models/NoteTag.js');
|
||||
const Resource = require('lib/models/Resource.js');
|
||||
const fs = require('fs-extra');
|
||||
const ArrayUtils = require('lib/ArrayUtils');
|
||||
const ObjectUtils = require('lib/ObjectUtils');
|
||||
const { shim } = require('lib/shim.js');
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
});
|
||||
|
||||
function exportDir() {
|
||||
return __dirname + '/export';
|
||||
}
|
||||
|
||||
function fieldsEqual(model1, model2, fieldNames) {
|
||||
for (let i = 0; i < fieldNames.length; i++) {
|
||||
const f = fieldNames[i];
|
||||
expect(model1[f]).toBe(model2[f], 'For key ' + f);
|
||||
}
|
||||
}
|
||||
|
||||
describe('services_InteropService', function() {
|
||||
|
||||
beforeEach(async (done) => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
await switchClient(1);
|
||||
|
||||
const dir = exportDir();
|
||||
await fs.remove(dir);
|
||||
await fs.mkdirp(dir);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should export and import folders', asyncTest(async () => {
|
||||
const service = new InteropService();
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
folder1 = await Folder.load(folder1.id);
|
||||
const filePath = exportDir() + '/test.jex';
|
||||
|
||||
await service.export({ path: filePath });
|
||||
|
||||
await Folder.delete(folder1.id);
|
||||
|
||||
await service.import({ path: filePath });
|
||||
|
||||
// Check that a new folder, with a new ID, has been created
|
||||
|
||||
expect(await Folder.count()).toBe(1);
|
||||
let folder2 = (await Folder.all())[0];
|
||||
expect(folder2.id).not.toBe(folder1.id);
|
||||
expect(folder2.title).toBe(folder1.title);
|
||||
|
||||
await service.import({ path: filePath });
|
||||
|
||||
// As there was already a folder with the same title, check that the new one has been renamed
|
||||
|
||||
await Folder.delete(folder2.id);
|
||||
let folder3 = (await Folder.all())[0];
|
||||
expect(await Folder.count()).toBe(1);
|
||||
expect(folder3.title).not.toBe(folder2.title);
|
||||
|
||||
let fieldNames = Folder.fieldNames();
|
||||
fieldNames = ArrayUtils.removeElement(fieldNames, 'id');
|
||||
fieldNames = ArrayUtils.removeElement(fieldNames, 'title');
|
||||
|
||||
fieldsEqual(folder3, folder1, fieldNames);
|
||||
}));
|
||||
|
||||
it('should export and import folders and notes', asyncTest(async () => {
|
||||
const service = new InteropService();
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
|
||||
note1 = await Note.load(note1.id);
|
||||
const filePath = exportDir() + '/test.jex';
|
||||
|
||||
await service.export({ path: filePath });
|
||||
|
||||
await Folder.delete(folder1.id);
|
||||
await Note.delete(note1.id);
|
||||
|
||||
await service.import({ path: filePath });
|
||||
|
||||
expect(await Note.count()).toBe(1);
|
||||
let note2 = (await Note.all())[0];
|
||||
let folder2 = (await Folder.all())[0];
|
||||
|
||||
expect(note1.parent_id).not.toBe(note2.parent_id);
|
||||
expect(note1.id).not.toBe(note2.id);
|
||||
expect(note2.parent_id).toBe(folder2.id);
|
||||
|
||||
let fieldNames = Note.fieldNames();
|
||||
fieldNames = ArrayUtils.removeElement(fieldNames, 'id');
|
||||
fieldNames = ArrayUtils.removeElement(fieldNames, 'parent_id');
|
||||
|
||||
fieldsEqual(note1, note2, fieldNames);
|
||||
|
||||
await service.import({ path: filePath });
|
||||
|
||||
note2 = (await Note.all())[0];
|
||||
let note3 = (await Note.all())[1];
|
||||
|
||||
expect(note2.id).not.toBe(note3.id);
|
||||
expect(note2.parent_id).not.toBe(note3.parent_id);
|
||||
|
||||
fieldsEqual(note2, note3, fieldNames);
|
||||
}));
|
||||
|
||||
it('should export and import notes to specific folder', asyncTest(async () => {
|
||||
const service = new InteropService();
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
|
||||
note1 = await Note.load(note1.id);
|
||||
const filePath = exportDir() + '/test.jex';
|
||||
|
||||
await service.export({ path: filePath });
|
||||
|
||||
await Note.delete(note1.id);
|
||||
|
||||
await service.import({ path: filePath, destinationFolderId: folder1.id });
|
||||
|
||||
expect(await Note.count()).toBe(1);
|
||||
expect(await Folder.count()).toBe(1);
|
||||
|
||||
expect(await checkThrowAsync(async () => await service.import({ path: filePath, destinationFolderId: 'oops' }))).toBe(true);
|
||||
}));
|
||||
|
||||
it('should export and import tags', asyncTest(async () => {
|
||||
const service = new InteropService();
|
||||
const filePath = exportDir() + '/test.jex';
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
|
||||
let tag1 = await Tag.save({ title: 'mon tag' });
|
||||
tag1 = await Tag.load(tag1.id);
|
||||
await Tag.addNote(tag1.id, note1.id);
|
||||
|
||||
await service.export({ path: filePath });
|
||||
|
||||
await Folder.delete(folder1.id);
|
||||
await Note.delete(note1.id);
|
||||
await Tag.delete(tag1.id);
|
||||
|
||||
await service.import({ path: filePath });
|
||||
|
||||
expect(await Tag.count()).toBe(1);
|
||||
let tag2 = (await Tag.all())[0];
|
||||
let note2 = (await Note.all())[0];
|
||||
expect(tag1.id).not.toBe(tag2.id);
|
||||
|
||||
let fieldNames = Note.fieldNames();
|
||||
fieldNames = ArrayUtils.removeElement(fieldNames, 'id');
|
||||
fieldsEqual(tag1, tag2, fieldNames);
|
||||
|
||||
let noteIds = await Tag.noteIds(tag2.id);
|
||||
expect(noteIds.length).toBe(1);
|
||||
expect(noteIds[0]).toBe(note2.id);
|
||||
|
||||
await service.import({ path: filePath });
|
||||
|
||||
// If importing again, no new tag should be created as one with
|
||||
// the same name already existed. The newly imported note should
|
||||
// however go under that already existing tag.
|
||||
expect(await Tag.count()).toBe(1);
|
||||
noteIds = await Tag.noteIds(tag2.id);
|
||||
expect(noteIds.length).toBe(2);
|
||||
}));
|
||||
|
||||
it('should export and import resources', asyncTest(async () => {
|
||||
const service = new InteropService();
|
||||
const filePath = exportDir() + '/test.jex';
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
|
||||
await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
|
||||
note1 = await Note.load(note1.id);
|
||||
let resourceIds = Note.linkedResourceIds(note1.body);
|
||||
let resource1 = await Resource.load(resourceIds[0]);
|
||||
|
||||
await service.export({ path: filePath });
|
||||
|
||||
await Note.delete(note1.id);
|
||||
|
||||
await service.import({ path: filePath });
|
||||
|
||||
expect(await Resource.count()).toBe(2);
|
||||
|
||||
let note2 = (await Note.all())[0];
|
||||
expect(note2.body).not.toBe(note1.body);
|
||||
resourceIds = Note.linkedResourceIds(note2.body);
|
||||
expect(resourceIds.length).toBe(1);
|
||||
let resource2 = await Resource.load(resourceIds[0]);
|
||||
expect(resource2.id).not.toBe(resource1.id);
|
||||
|
||||
let fieldNames = Note.fieldNames();
|
||||
fieldNames = ArrayUtils.removeElement(fieldNames, 'id');
|
||||
fieldsEqual(resource1, resource2, fieldNames);
|
||||
|
||||
const resourcePath1 = Resource.fullPath(resource1);
|
||||
const resourcePath2 = Resource.fullPath(resource2);
|
||||
|
||||
expect(resourcePath1).not.toBe(resourcePath2);
|
||||
expect(fileContentEqual(resourcePath1, resourcePath2)).toBe(true);
|
||||
}));
|
||||
|
||||
it('should export and import single notes', asyncTest(async () => {
|
||||
const service = new InteropService();
|
||||
const filePath = exportDir() + '/test.jex';
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
|
||||
|
||||
await service.export({ path: filePath, sourceNoteIds: [note1.id] });
|
||||
|
||||
await Note.delete(note1.id);
|
||||
await Folder.delete(folder1.id);
|
||||
|
||||
await service.import({ path: filePath });
|
||||
|
||||
expect(await Note.count()).toBe(1);
|
||||
expect(await Folder.count()).toBe(1);
|
||||
|
||||
let folder2 = (await Folder.all())[0];
|
||||
expect(folder2.title).toBe('test');
|
||||
}));
|
||||
|
||||
it('should export and import single folders', asyncTest(async () => {
|
||||
const service = new InteropService();
|
||||
const filePath = exportDir() + '/test.jex';
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
|
||||
|
||||
await service.export({ path: filePath, sourceFolderIds: [folder1.id] });
|
||||
|
||||
await Note.delete(note1.id);
|
||||
await Folder.delete(folder1.id);
|
||||
|
||||
await service.import({ path: filePath });
|
||||
|
||||
expect(await Note.count()).toBe(1);
|
||||
expect(await Folder.count()).toBe(1);
|
||||
|
||||
let folder2 = (await Folder.all())[0];
|
||||
expect(folder2.title).toBe('folder1');
|
||||
}));
|
||||
|
||||
});
|
@@ -19,7 +19,7 @@ process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
});
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000; // 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;
|
||||
@@ -74,19 +75,22 @@ async function localItemsSameAsRemote(locals, expect) {
|
||||
expect(!!remote).toBe(true);
|
||||
if (!remote) continue;
|
||||
|
||||
if (syncTargetId() == SyncTargetRegistry.nameToId('filesystem')) {
|
||||
expect(remote.updated_time).toBe(Math.floor(dbItem.updated_time / 1000) * 1000);
|
||||
} else {
|
||||
expect(remote.updated_time).toBe(dbItem.updated_time);
|
||||
}
|
||||
// if (syncTargetId() == SyncTargetRegistry.nameToId('filesystem')) {
|
||||
// expect(remote.updated_time).toBe(Math.floor(dbItem.updated_time / 1000) * 1000);
|
||||
// } else {
|
||||
// expect(remote.updated_time).toBe(dbItem.updated_time);
|
||||
// }
|
||||
|
||||
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;
|
||||
@@ -115,7 +119,7 @@ describe('Synchronizer', function() {
|
||||
await localItemsSameAsRemote(all, expect);
|
||||
}));
|
||||
|
||||
it('should update remote item', asyncTest(async () => {
|
||||
it('should update remote items', asyncTest(async () => {
|
||||
let folder = await Folder.save({ title: "folder1" });
|
||||
let note = await Note.save({ title: "un", parent_id: folder.id });
|
||||
await synchronizer().start();
|
||||
@@ -287,24 +291,31 @@ describe('Synchronizer', function() {
|
||||
}));
|
||||
|
||||
it('should delete local notes', asyncTest(async () => {
|
||||
// For these tests we pass the context around for each user. This is to make sure that the "deletedItemsProcessed"
|
||||
// property of the basicDelta() function is cleared properly at the end of a sync operation. If it is not cleared
|
||||
// it means items will no longer be deleted locally via sync.
|
||||
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
let note1 = await Note.save({ title: "un", parent_id: folder1.id });
|
||||
await synchronizer().start();
|
||||
let note2 = await Note.save({ title: "deux", parent_id: folder1.id });
|
||||
let context1 = await synchronizer().start();
|
||||
|
||||
await switchClient(2);
|
||||
|
||||
await synchronizer().start();
|
||||
let context2 = await synchronizer().start();
|
||||
await Note.delete(note1.id);
|
||||
await synchronizer().start();
|
||||
context2 = await synchronizer().start({ context: context2 });
|
||||
|
||||
await switchClient(1);
|
||||
|
||||
await synchronizer().start();
|
||||
context1 = await synchronizer().start({ context: context1 });
|
||||
let items = await allItems();
|
||||
expect(items.length).toBe(1);
|
||||
expect(items.length).toBe(2);
|
||||
let deletedItems = await BaseItem.deletedItems(syncTargetId());
|
||||
expect(deletedItems.length).toBe(0);
|
||||
}));
|
||||
await Note.delete(note2.id);
|
||||
context1 = await synchronizer().start({ context: context1 });
|
||||
}));
|
||||
|
||||
it('should delete remote folder', asyncTest(async () => {
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
@@ -322,8 +333,8 @@ describe('Synchronizer', function() {
|
||||
await synchronizer().start();
|
||||
|
||||
let all = await allItems();
|
||||
localItemsSameAsRemote(all, expect);
|
||||
}));
|
||||
await localItemsSameAsRemote(all, expect);
|
||||
}));
|
||||
|
||||
it('should delete local folder', asyncTest(async () => {
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
@@ -345,8 +356,8 @@ describe('Synchronizer', function() {
|
||||
await synchronizer().start();
|
||||
|
||||
let items = await allItems();
|
||||
localItemsSameAsRemote(items, expect);
|
||||
}));
|
||||
await localItemsSameAsRemote(items, expect);
|
||||
}));
|
||||
|
||||
it('should resolve conflict if remote folder has been deleted, but note has been added to folder locally', asyncTest(async () => {
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
@@ -388,8 +399,8 @@ describe('Synchronizer', function() {
|
||||
expect(items.length).toBe(1);
|
||||
expect(items[0].title).toBe('folder');
|
||||
|
||||
localItemsSameAsRemote(items, expect);
|
||||
}));
|
||||
await localItemsSameAsRemote(items, expect);
|
||||
}));
|
||||
|
||||
it('should cross delete all folders', asyncTest(async () => {
|
||||
// If client1 and 2 have two folders, client 1 deletes item 1 and client
|
||||
@@ -459,7 +470,7 @@ describe('Synchronizer', function() {
|
||||
let unconflictedNotes = await Note.unconflictedNotes();
|
||||
|
||||
expect(unconflictedNotes.length).toBe(0);
|
||||
}));
|
||||
}));
|
||||
|
||||
it('should handle conflict when remote folder is deleted then local folder is renamed', asyncTest(async () => {
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
@@ -489,7 +500,7 @@ describe('Synchronizer', function() {
|
||||
let items = await allItems();
|
||||
|
||||
expect(items.length).toBe(1);
|
||||
}));
|
||||
}));
|
||||
|
||||
it('should allow duplicate folder titles', asyncTest(async () => {
|
||||
let localF1 = await Folder.save({ title: "folder" });
|
||||
@@ -575,10 +586,12 @@ describe('Synchronizer', function() {
|
||||
}
|
||||
|
||||
it('should sync tags', asyncTest(async () => {
|
||||
await shoudSyncTagTest(false); }));
|
||||
await shoudSyncTagTest(false);
|
||||
}));
|
||||
|
||||
it('should sync encrypted tags', asyncTest(async () => {
|
||||
await shoudSyncTagTest(true); }));
|
||||
await shoudSyncTagTest(true);
|
||||
}));
|
||||
|
||||
it('should not sync notes with conflicts', asyncTest(async () => {
|
||||
let f1 = await Folder.save({ title: "folder" });
|
||||
@@ -683,12 +696,12 @@ describe('Synchronizer', function() {
|
||||
|
||||
await switchClient(2);
|
||||
|
||||
synchronizer().debugFlags_ = ['cancelDeltaLoop2'];
|
||||
synchronizer().testingHooks_ = ['cancelDeltaLoop2'];
|
||||
let context = await synchronizer().start();
|
||||
let notes = await Note.all();
|
||||
expect(notes.length).toBe(0);
|
||||
|
||||
synchronizer().debugFlags_ = [];
|
||||
synchronizer().testingHooks_ = [];
|
||||
await synchronizer().start({ context: context });
|
||||
notes = await Note.all();
|
||||
expect(notes.length).toBe(1);
|
||||
@@ -702,9 +715,9 @@ describe('Synchronizer', function() {
|
||||
let disabledItems = await BaseItem.syncDisabledItems(syncTargetId());
|
||||
expect(disabledItems.length).toBe(0);
|
||||
await Note.save({ id: noteId, title: "un mod", });
|
||||
synchronizer().debugFlags_ = ['rejectedByTarget'];
|
||||
synchronizer().testingHooks_ = ['rejectedByTarget'];
|
||||
await synchronizer().start();
|
||||
synchronizer().debugFlags_ = [];
|
||||
synchronizer().testingHooks_ = [];
|
||||
await synchronizer().start(); // Another sync to check that this item is now excluded from sync
|
||||
|
||||
await switchClient(2);
|
||||
@@ -848,7 +861,7 @@ describe('Synchronizer', function() {
|
||||
}));
|
||||
|
||||
it('should sync resources', asyncTest(async () => {
|
||||
while (insideBeforeEach) await time.msleep(100);
|
||||
while (insideBeforeEach) await time.msleep(500);
|
||||
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
|
||||
@@ -983,4 +996,39 @@ 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);
|
||||
}));
|
||||
|
||||
it("should update remote items but not pull remote changes", asyncTest(async () => {
|
||||
let folder = await Folder.save({ title: "folder1" });
|
||||
let note = await Note.save({ title: "un", parent_id: folder.id });
|
||||
await synchronizer().start();
|
||||
|
||||
await switchClient(2);
|
||||
|
||||
await synchronizer().start();
|
||||
await Note.save({ title: "deux", parent_id: folder.id });
|
||||
await synchronizer().start();
|
||||
|
||||
await switchClient(1);
|
||||
|
||||
await Note.save({ title: "un UPDATE", id: note.id });
|
||||
await synchronizer().start({ syncSteps: ["update_remote"] });
|
||||
let all = await allItems();
|
||||
expect(all.length).toBe(2);
|
||||
|
||||
await switchClient(2);
|
||||
|
||||
await synchronizer().start();
|
||||
let note2 = await Note.load(note.id);
|
||||
expect(note2.title).toBe("un UPDATE");
|
||||
}));
|
||||
|
||||
});
|
||||
|
@@ -15,6 +15,7 @@ const { Synchronizer } = require('lib/synchronizer.js');
|
||||
const { FileApi } = require('lib/file-api.js');
|
||||
const { FileApiDriverMemory } = require('lib/file-api-driver-memory.js');
|
||||
const { FileApiDriverLocal } = require('lib/file-api-driver-local.js');
|
||||
const { FileApiDriverWebDav } = require('lib/file-api-driver-webdav.js');
|
||||
const { FsDriverNode } = require('lib/fs-driver-node.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const { shimInit } = require('lib/shim-init-node.js');
|
||||
@@ -22,8 +23,10 @@ const SyncTargetRegistry = require('lib/SyncTargetRegistry.js');
|
||||
const SyncTargetMemory = require('lib/SyncTargetMemory.js');
|
||||
const SyncTargetFilesystem = require('lib/SyncTargetFilesystem.js');
|
||||
const SyncTargetOneDrive = require('lib/SyncTargetOneDrive.js');
|
||||
const SyncTargetNextcloud = require('lib/SyncTargetNextcloud.js');
|
||||
const EncryptionService = require('lib/services/EncryptionService.js');
|
||||
const DecryptionWorker = require('lib/services/DecryptionWorker.js');
|
||||
const WebDavApi = require('lib/WebDavApi');
|
||||
|
||||
let databases_ = [];
|
||||
let synchronizers_ = [];
|
||||
@@ -38,6 +41,7 @@ const fsDriver = new FsDriverNode();
|
||||
Logger.fsDriver_ = fsDriver;
|
||||
Resource.fsDriver_ = fsDriver;
|
||||
EncryptionService.fsDriver_ = fsDriver;
|
||||
FileApiDriverLocal.fsDriver_ = fsDriver;
|
||||
|
||||
const logDir = __dirname + '/../tests/logs';
|
||||
fs.mkdirpSync(logDir, 0o755);
|
||||
@@ -45,12 +49,16 @@ fs.mkdirpSync(logDir, 0o755);
|
||||
SyncTargetRegistry.addClass(SyncTargetMemory);
|
||||
SyncTargetRegistry.addClass(SyncTargetFilesystem);
|
||||
SyncTargetRegistry.addClass(SyncTargetOneDrive);
|
||||
SyncTargetRegistry.addClass(SyncTargetNextcloud);
|
||||
|
||||
//const syncTargetId_ = SyncTargetRegistry.nameToId('memory');
|
||||
const syncTargetId_ = SyncTargetRegistry.nameToId('filesystem');
|
||||
// const syncTargetId_ = SyncTargetRegistry.nameToId("nextcloud");
|
||||
const syncTargetId_ = SyncTargetRegistry.nameToId("memory");
|
||||
//const syncTargetId_ = SyncTargetRegistry.nameToId('filesystem');
|
||||
const syncDir = __dirname + '/../tests/sync';
|
||||
|
||||
const sleepTime = syncTargetId_ == SyncTargetRegistry.nameToId('filesystem') ? 1001 : 400;
|
||||
const sleepTime = syncTargetId_ == SyncTargetRegistry.nameToId('filesystem') ? 1001 : 100;//400;
|
||||
|
||||
console.info('Testing with sync target: ' + SyncTargetRegistry.idToName(syncTargetId_));
|
||||
|
||||
const logger = new Logger();
|
||||
logger.addTarget('console');
|
||||
@@ -67,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_;
|
||||
}
|
||||
@@ -142,25 +152,6 @@ async function setupDatabase(id = null) {
|
||||
|
||||
BaseModel.db_ = databases_[id];
|
||||
await Setting.load();
|
||||
//return setupDatabase(id);
|
||||
|
||||
|
||||
|
||||
// return databases_[id].open({ name: filePath }).then(() => {
|
||||
// BaseModel.db_ = databases_[id];
|
||||
// return setupDatabase(id);
|
||||
// });
|
||||
|
||||
|
||||
// return fs.unlink(filePath).catch(() => {
|
||||
// // Don't care if the file doesn't exist
|
||||
// }).then(() => {
|
||||
// databases_[id] = new JoplinDatabase(new DatabaseDriverNode());
|
||||
// return databases_[id].open({ name: filePath }).then(() => {
|
||||
// BaseModel.db_ = databases_[id];
|
||||
// return setupDatabase(id);
|
||||
// });
|
||||
// });
|
||||
}
|
||||
|
||||
function resourceDir(id = null) {
|
||||
@@ -192,12 +183,7 @@ async function setupDatabaseAndSynchronizer(id = null) {
|
||||
decryptionWorkers_[id] = new DecryptionWorker();
|
||||
decryptionWorkers_[id].setEncryptionService(encryptionServices_[id]);
|
||||
|
||||
if (syncTargetId_ == SyncTargetRegistry.nameToId('filesystem')) {
|
||||
fs.removeSync(syncDir)
|
||||
fs.mkdirpSync(syncDir, 0o755);
|
||||
} else {
|
||||
await fileApi().format();
|
||||
}
|
||||
await fileApi().clearRoot();
|
||||
}
|
||||
|
||||
function db(id = null) {
|
||||
@@ -248,7 +234,17 @@ function fileApi() {
|
||||
fileApi_ = new FileApi(syncDir, new FileApiDriverLocal());
|
||||
} else if (syncTargetId_ == SyncTargetRegistry.nameToId('memory')) {
|
||||
fileApi_ = new FileApi('/root', new FileApiDriverMemory());
|
||||
} else if (syncTargetId_ == SyncTargetRegistry.nameToId('nextcloud')) {
|
||||
const options = {
|
||||
baseUrl: () => 'http://nextcloud.local/remote.php/dav/files/admin/JoplinTest',
|
||||
username: () => 'admin',
|
||||
password: () => '123456',
|
||||
};
|
||||
|
||||
const api = new WebDavApi(options);
|
||||
fileApi_ = new FileApi('', new FileApiDriverWebDav(api));
|
||||
}
|
||||
|
||||
// } else if (syncTargetId == Setting.SYNC_TARGET_ONEDRIVE) {
|
||||
// let auth = require('./onedrive-auth.json');
|
||||
// if (!auth) {
|
||||
@@ -268,6 +264,7 @@ function fileApi() {
|
||||
|
||||
fileApi_.setLogger(logger);
|
||||
fileApi_.setSyncTargetId(syncTargetId_);
|
||||
fileApi_.requestRepeatCount_ = 0;
|
||||
return fileApi_;
|
||||
}
|
||||
|
||||
|
@@ -1,9 +1,11 @@
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { BrowserWindow } = require('electron');
|
||||
const { BrowserWindow, Menu, Tray } = require('electron');
|
||||
const { shim } = require('lib/shim');
|
||||
const url = require('url')
|
||||
const path = require('path')
|
||||
const urlUtils = require('lib/urlUtils.js');
|
||||
const { dirname, basename } = require('lib/path-utils');
|
||||
const fs = require('fs-extra');
|
||||
|
||||
class ElectronAppWrapper {
|
||||
|
||||
@@ -12,6 +14,8 @@ class ElectronAppWrapper {
|
||||
this.env_ = env;
|
||||
this.win_ = null;
|
||||
this.willQuitApp_ = false;
|
||||
this.tray_ = null;
|
||||
this.buildDir_ = null;
|
||||
}
|
||||
|
||||
electronApp() {
|
||||
@@ -37,13 +41,14 @@ class ElectronAppWrapper {
|
||||
const windowState = windowStateKeeper({
|
||||
defaultWidth: 800,
|
||||
defaultHeight: 600,
|
||||
file: 'window-state-' + this.env_ + '.json',
|
||||
});
|
||||
|
||||
const windowOptions = {
|
||||
'x': windowState.x,
|
||||
'y': windowState.y,
|
||||
'width': windowState.width,
|
||||
'height': windowState.height,
|
||||
x: windowState.x,
|
||||
y: windowState.y,
|
||||
width: windowState.width,
|
||||
height: windowState.height,
|
||||
};
|
||||
|
||||
// Linux icon workaround for bug https://github.com/electron-userland/electron-builder/issues/2098
|
||||
@@ -59,14 +64,30 @@ 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 (this.willQuitApp_ || process.platform !== 'darwin') {
|
||||
this.win_ = null;
|
||||
// If it's on macOS, the app is completely closed only if the user chooses to close the app (willQuitApp_ will be true)
|
||||
// otherwise the window is simply hidden, and will be re-open once the app is "activated" (which happens when the
|
||||
// user clicks on the icon in the task bar).
|
||||
|
||||
// On Windows and Linux, the app is closed when the window is closed *except* if the tray icon is used. In which
|
||||
// case the app must be explicitely closed with Ctrl+Q or by right-clicking on the tray icon and selecting "Exit".
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
if (this.willQuitApp_) {
|
||||
this.win_ = null;
|
||||
} else {
|
||||
event.preventDefault();
|
||||
this.hide();
|
||||
}
|
||||
} else {
|
||||
event.preventDefault();
|
||||
this.win_.hide();
|
||||
if (this.trayShown() && !this.willQuitApp_) {
|
||||
event.preventDefault();
|
||||
this.win_.hide();
|
||||
} else {
|
||||
this.win_ = null;
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -89,15 +110,97 @@ class ElectronAppWrapper {
|
||||
});
|
||||
}
|
||||
|
||||
async exit() {
|
||||
async quit() {
|
||||
this.electronApp_.quit();
|
||||
}
|
||||
|
||||
exit(errorCode = 0) {
|
||||
this.electronApp_.exit(errorCode);
|
||||
}
|
||||
|
||||
trayShown() {
|
||||
return !!this.tray_;
|
||||
}
|
||||
|
||||
// This method is used in macOS only to hide the whole app (and not just the main window)
|
||||
// including the menu bar. This follows the macOS way of hidding an app.
|
||||
hide() {
|
||||
this.electronApp_.hide();
|
||||
}
|
||||
|
||||
buildDir() {
|
||||
if (this.buildDir_) return this.buildDir_;
|
||||
let dir = __dirname + '/build';
|
||||
if (!fs.pathExistsSync(dir)) {
|
||||
dir = dirname(__dirname) + '/build';
|
||||
if (!fs.pathExistsSync(dir)) throw new Error('Cannot find build dir');
|
||||
}
|
||||
|
||||
this.buildDir_ = dir;
|
||||
return dir;
|
||||
}
|
||||
|
||||
trayIconFilename_() {
|
||||
let output = '';
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
output = 'macos-16x16Template.png'; // Electron Template Image format
|
||||
} else {
|
||||
output = '16x16.png';
|
||||
}
|
||||
|
||||
if (this.env_ === 'dev') output = '16x16-dev.png'
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
// Note: this must be called only after the "ready" event of the app has been dispatched
|
||||
createTray(contextMenu) {
|
||||
try {
|
||||
this.tray_ = new Tray(this.buildDir() + '/icons/' + this.trayIconFilename_())
|
||||
this.tray_.setToolTip(this.electronApp_.getName())
|
||||
this.tray_.setContextMenu(contextMenu)
|
||||
|
||||
this.tray_.on('click', () => {
|
||||
this.window().show();
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Cannot create tray", error);
|
||||
}
|
||||
}
|
||||
|
||||
destroyTray() {
|
||||
if (!this.tray_) return;
|
||||
this.tray_.destroy();
|
||||
this.tray_ = null;
|
||||
}
|
||||
|
||||
ensureSingleInstance() {
|
||||
if (this.env_ === 'dev') return false;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const alreadyRunning = this.electronApp_.makeSingleInstance((commandLine, workingDirectory) => {
|
||||
const win = this.window();
|
||||
if (!win) return;
|
||||
if (win.isMinimized()) win.restore();
|
||||
win.show();
|
||||
win.focus();
|
||||
});
|
||||
|
||||
if (alreadyRunning) this.electronApp_.quit();
|
||||
|
||||
resolve(alreadyRunning);
|
||||
});
|
||||
}
|
||||
|
||||
async start() {
|
||||
// Since we are doing other async things before creating the window, we might miss
|
||||
// the "ready" event. So we use the function below to make sure that the app is ready.
|
||||
await this.waitForElectronAppReady();
|
||||
|
||||
const alreadyRunning = await this.ensureSingleInstance();
|
||||
if (alreadyRunning) return;
|
||||
|
||||
this.createWindow();
|
||||
|
||||
this.electronApp_.on('before-quit', () => {
|
||||
|
51
ElectronClient/app/InteropServiceHelper.js
Normal file
51
ElectronClient/app/InteropServiceHelper.js
Normal file
@@ -0,0 +1,51 @@
|
||||
const { _ } = require('lib/locale');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
const InteropService = require('lib/services/InteropService');
|
||||
|
||||
class InteropServiceHelper {
|
||||
|
||||
static async export(dispatch, module, options = null) {
|
||||
if (!options) options = {};
|
||||
|
||||
let path = null;
|
||||
|
||||
if (module.target === 'file') {
|
||||
path = bridge().showSaveDialog({
|
||||
filters: [{ name: module.description, extensions: [module.fileExtension]}]
|
||||
});
|
||||
} else {
|
||||
path = bridge().showOpenDialog({
|
||||
properties: ['openDirectory', 'createDirectory'],
|
||||
});
|
||||
}
|
||||
|
||||
if (!path || (Array.isArray(path) && !path.length)) return;
|
||||
|
||||
if (Array.isArray(path)) path = path[0];
|
||||
|
||||
dispatch({
|
||||
type: 'WINDOW_COMMAND',
|
||||
name: 'showModalMessage',
|
||||
message: _('Exporting to "%s" as "%s" format. Please wait...', path, module.format),
|
||||
});
|
||||
|
||||
const exportOptions = {};
|
||||
exportOptions.path = path;
|
||||
exportOptions.format = module.format;
|
||||
if (options.sourceFolderIds) exportOptions.sourceFolderIds = options.sourceFolderIds;
|
||||
if (options.sourceNoteIds) exportOptions.sourceNoteIds = options.sourceNoteIds;
|
||||
|
||||
const service = new InteropService();
|
||||
const result = await service.export(exportOptions);
|
||||
|
||||
console.info('Export result: ', result);
|
||||
|
||||
dispatch({
|
||||
type: 'WINDOW_COMMAND',
|
||||
name: 'hideModalMessage',
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = InteropServiceHelper;
|
@@ -20,6 +20,8 @@ const packageInfo = require('./packageInfo.js');
|
||||
const AlarmService = require('lib/services/AlarmService.js');
|
||||
const AlarmServiceDriverNode = require('lib/services/AlarmServiceDriverNode');
|
||||
const DecryptionWorker = require('lib/services/DecryptionWorker');
|
||||
const InteropService = require('lib/services/InteropService');
|
||||
const InteropServiceHelper = require('./InteropServiceHelper.js');
|
||||
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
const Menu = bridge().Menu;
|
||||
@@ -49,6 +51,10 @@ class Application extends BaseApplication {
|
||||
return true;
|
||||
}
|
||||
|
||||
checkForUpdateLoggerPath() {
|
||||
return Setting.value('profileDir') + '/log-autoupdater.txt';
|
||||
}
|
||||
|
||||
reducer(state = appDefaultState, action) {
|
||||
let newState = state;
|
||||
|
||||
@@ -136,8 +142,16 @@ class Application extends BaseApplication {
|
||||
this.refreshMenu();
|
||||
}
|
||||
|
||||
if (['NOTE_UPDATE_ONE', 'NOTE_DELETE', 'FOLDER_UPDATE_ONE', 'FOLDER_DELETE'].indexOf(action.type) >= 0) {
|
||||
if (!await reg.syncTarget().syncStarted()) reg.scheduleSync();
|
||||
if (action.type == 'SETTING_UPDATE_ONE' && action.key == 'showTrayIcon' || action.type == 'SETTING_UPDATE_ALL') {
|
||||
this.updateTray();
|
||||
}
|
||||
|
||||
if (action.type == 'SETTING_UPDATE_ONE' && action.key == 'style.editor.fontFamily' || action.type == 'SETTING_UPDATE_ALL') {
|
||||
this.updateEditorFont();
|
||||
}
|
||||
|
||||
if (["NOTE_UPDATE_ONE", "NOTE_DELETE", "FOLDER_UPDATE_ONE", "FOLDER_DELETE"].indexOf(action.type) >= 0) {
|
||||
if (!await reg.syncTarget().syncStarted()) reg.scheduleSync(5, { syncSteps: ["update_remote", "delete_remote"] });
|
||||
}
|
||||
|
||||
if (['EVENT_NOTE_ALARM_FIELD_CHANGE', 'NOTE_DELETE'].indexOf(action.type) >= 0) {
|
||||
@@ -167,6 +181,101 @@ class Application extends BaseApplication {
|
||||
updateMenu(screen) {
|
||||
if (this.lastMenuScreen_ === screen) return;
|
||||
|
||||
const sortNoteItems = [];
|
||||
const sortNoteOptions = Setting.enumOptions('notes.sortOrder.field');
|
||||
for (let field in sortNoteOptions) {
|
||||
if (!sortNoteOptions.hasOwnProperty(field)) continue;
|
||||
sortNoteItems.push({
|
||||
label: sortNoteOptions[field],
|
||||
screens: ['Main'],
|
||||
type: 'checkbox',
|
||||
checked: Setting.value('notes.sortOrder.field') === field,
|
||||
click: () => {
|
||||
Setting.setValue('notes.sortOrder.field', field);
|
||||
this.refreshMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const importItems = [];
|
||||
const exportItems = [];
|
||||
const ioService = new InteropService();
|
||||
const ioModules = ioService.modules();
|
||||
for (let i = 0; i < ioModules.length; i++) {
|
||||
const module = ioModules[i];
|
||||
if (module.type === 'exporter') {
|
||||
exportItems.push({
|
||||
label: module.fullLabel(),
|
||||
screens: ['Main'],
|
||||
click: async () => {
|
||||
await InteropServiceHelper.export(this.dispatch.bind(this), module);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
for (let j = 0; j < module.sources.length; j++) {
|
||||
const moduleSource = module.sources[j];
|
||||
importItems.push({
|
||||
label: module.fullLabel(moduleSource),
|
||||
screens: ['Main'],
|
||||
click: async () => {
|
||||
let path = null;
|
||||
|
||||
const selectedFolderId = this.store().getState().selectedFolderId;
|
||||
|
||||
if (moduleSource === 'file') {
|
||||
path = bridge().showOpenDialog({
|
||||
filters: [{ name: module.description, extensions: [module.fileExtension]}]
|
||||
});
|
||||
} else {
|
||||
path = bridge().showOpenDialog({
|
||||
properties: ['openDirectory', 'createDirectory'],
|
||||
});
|
||||
}
|
||||
|
||||
if (!path || (Array.isArray(path) && !path.length)) return;
|
||||
|
||||
if (Array.isArray(path)) path = path[0];
|
||||
|
||||
this.dispatch({
|
||||
type: 'WINDOW_COMMAND',
|
||||
name: 'showModalMessage',
|
||||
message: _('Importing from "%s" as "%s" format. Please wait...', path, module.format),
|
||||
});
|
||||
|
||||
const importOptions = {};
|
||||
importOptions.path = path;
|
||||
importOptions.format = module.format;
|
||||
importOptions.destinationFolderId = !module.isNoteArchive && moduleSource === 'file' ? selectedFolderId : null;
|
||||
|
||||
const service = new InteropService();
|
||||
try {
|
||||
const result = await service.import(importOptions);
|
||||
console.info('Import result: ', result);
|
||||
} catch (error) {
|
||||
bridge().showErrorMessageBox(error.message);
|
||||
}
|
||||
|
||||
this.dispatch({
|
||||
type: 'WINDOW_COMMAND',
|
||||
name: 'hideModalMessage',
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exportItems.push({
|
||||
label: 'PDF - ' + _('PDF File'),
|
||||
screens: ['Main'],
|
||||
click: async () => {
|
||||
this.dispatch({
|
||||
type: 'WINDOW_COMMAND',
|
||||
name: 'exportPdf',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const template = [
|
||||
{
|
||||
label: _('File'),
|
||||
@@ -192,6 +301,7 @@ class Application extends BaseApplication {
|
||||
}
|
||||
}, {
|
||||
label: _('New notebook'),
|
||||
accelerator: 'CommandOrControl+B',
|
||||
screens: ['Main'],
|
||||
click: () => {
|
||||
this.dispatch({
|
||||
@@ -202,46 +312,53 @@ class Application extends BaseApplication {
|
||||
}, {
|
||||
type: 'separator',
|
||||
}, {
|
||||
label: _('Import Evernote notes'),
|
||||
label: _('Import'),
|
||||
submenu: importItems,
|
||||
}, {
|
||||
label: _('Export'),
|
||||
submenu: exportItems,
|
||||
}, {
|
||||
type: 'separator',
|
||||
}, {
|
||||
label: _('Print'),
|
||||
accelerator: 'CommandOrControl+P',
|
||||
screens: ['Main'],
|
||||
click: () => {
|
||||
const filePaths = bridge().showOpenDialog({
|
||||
properties: ['openFile', 'createDirectory'],
|
||||
filters: [
|
||||
{ name: _('Evernote Export Files'), extensions: ['enex'] },
|
||||
]
|
||||
});
|
||||
if (!filePaths || !filePaths.length) return;
|
||||
|
||||
this.dispatch({
|
||||
type: 'NAV_GO',
|
||||
routeName: 'Import',
|
||||
props: {
|
||||
filePath: filePaths[0],
|
||||
},
|
||||
type: 'WINDOW_COMMAND',
|
||||
name: 'print',
|
||||
});
|
||||
}
|
||||
}, {
|
||||
type: 'separator',
|
||||
platforms: ['darwin'],
|
||||
}, {
|
||||
label: _('Hide %s', 'Joplin'),
|
||||
platforms: ['darwin'],
|
||||
accelerator: 'CommandOrControl+H',
|
||||
click: () => { bridge().electronApp().hide() }
|
||||
}, {
|
||||
type: 'separator',
|
||||
}, {
|
||||
label: _('Quit'),
|
||||
accelerator: 'CommandOrControl+Q',
|
||||
click: () => { bridge().electronApp().exit() }
|
||||
click: () => { bridge().electronApp().quit() }
|
||||
}]
|
||||
}, {
|
||||
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',
|
||||
}, {
|
||||
@@ -258,6 +375,42 @@ class Application extends BaseApplication {
|
||||
});
|
||||
},
|
||||
}],
|
||||
}, {
|
||||
label: _('View'),
|
||||
submenu: [{
|
||||
label: _('Toggle editor layout'),
|
||||
screens: ['Main'],
|
||||
accelerator: 'CommandOrControl+L',
|
||||
click: () => {
|
||||
this.dispatch({
|
||||
type: 'WINDOW_COMMAND',
|
||||
name: 'toggleVisiblePanes',
|
||||
});
|
||||
}
|
||||
}, {
|
||||
type: 'separator',
|
||||
screens: ['Main'],
|
||||
}, {
|
||||
label: Setting.settingMetadata('notes.sortOrder.field').label(),
|
||||
screens: ['Main'],
|
||||
submenu: sortNoteItems,
|
||||
}, {
|
||||
label: Setting.settingMetadata('notes.sortOrder.reverse').label(),
|
||||
type: 'checkbox',
|
||||
checked: Setting.value('notes.sortOrder.reverse'),
|
||||
screens: ['Main'],
|
||||
click: () => {
|
||||
Setting.setValue('notes.sortOrder.reverse', !Setting.value('notes.sortOrder.reverse'));
|
||||
},
|
||||
}, {
|
||||
label: Setting.settingMetadata('uncompletedTodosOnTop').label(),
|
||||
type: 'checkbox',
|
||||
checked: Setting.value('uncompletedTodosOnTop'),
|
||||
screens: ['Main'],
|
||||
click: () => {
|
||||
Setting.setValue('uncompletedTodosOnTop', !Setting.value('uncompletedTodosOnTop'));
|
||||
},
|
||||
}],
|
||||
}, {
|
||||
label: _('Tools'),
|
||||
submenu: [{
|
||||
@@ -281,6 +434,7 @@ class Application extends BaseApplication {
|
||||
}
|
||||
},{
|
||||
label: _('General Options'),
|
||||
accelerator: 'CommandOrControl+,',
|
||||
click: () => {
|
||||
this.dispatch({
|
||||
type: 'NAV_GO',
|
||||
@@ -294,6 +448,17 @@ class Application extends BaseApplication {
|
||||
label: _('Website and documentation'),
|
||||
accelerator: 'F1',
|
||||
click () { bridge().openExternal('http://joplin.cozic.net') }
|
||||
}, {
|
||||
label: _('Make a donation'),
|
||||
click () { bridge().openExternal('http://joplin.cozic.net/donate') }
|
||||
}, {
|
||||
label: _('Check for updates...'),
|
||||
click: () => {
|
||||
bridge().checkForUpdates(false, bridge().window(), this.checkForUpdateLoggerPath());
|
||||
}
|
||||
}, {
|
||||
type: 'separator',
|
||||
screens: ['Main'],
|
||||
}, {
|
||||
label: _('About Joplin'),
|
||||
click: () => {
|
||||
@@ -304,8 +469,8 @@ class Application extends BaseApplication {
|
||||
'Copyright © 2016-2018 Laurent Cozic',
|
||||
_('%s %s (%s, %s)', p.name, p.version, Setting.value('env'), process.platform),
|
||||
];
|
||||
bridge().showMessageBox({
|
||||
message: message.join('\n'),
|
||||
bridge().showInfoMessageBox(message.join('\n'), {
|
||||
icon: bridge().electronApp().buildDir() + '/icons/32x32.png',
|
||||
});
|
||||
}
|
||||
}]
|
||||
@@ -321,10 +486,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);
|
||||
@@ -340,6 +508,43 @@ class Application extends BaseApplication {
|
||||
this.lastMenuScreen_ = screen;
|
||||
}
|
||||
|
||||
updateTray() {
|
||||
// Tray icon (called AppIndicator) doesn't work in Ubuntu
|
||||
// http://www.webupd8.org/2017/04/fix-appindicator-not-working-for.html
|
||||
// Might be fixed in Electron 18.x but no non-beta release yet.
|
||||
if (!shim.isWindows() && !shim.isMac()) return;
|
||||
|
||||
const app = bridge().electronApp();
|
||||
|
||||
if (app.trayShown() === Setting.value('showTrayIcon')) return;
|
||||
|
||||
if (!Setting.value('showTrayIcon')) {
|
||||
app.destroyTray();
|
||||
} else {
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{ label: _('Open %s', app.electronApp().getName()), click: () => { app.window().show(); } },
|
||||
{ type: 'separator' },
|
||||
{ label: _('Exit'), click: () => { app.quit() } },
|
||||
])
|
||||
app.createTray(contextMenu);
|
||||
}
|
||||
}
|
||||
|
||||
updateEditorFont() {
|
||||
const fontFamilies = [];
|
||||
if (Setting.value('style.editor.fontFamily')) fontFamilies.push('"' + Setting.value('style.editor.fontFamily') + '"');
|
||||
fontFamilies.push('monospace');
|
||||
|
||||
// The '*' and '!important' parts are necessary to make sure Russian text is displayed properly
|
||||
// https://github.com/laurent22/joplin/issues/155
|
||||
|
||||
const css = '.ace_editor * { font-family: ' + fontFamilies.join(', ') + ' !important; }';
|
||||
const styleTag = document.createElement('style');
|
||||
styleTag.type = 'text/css';
|
||||
styleTag.appendChild(document.createTextNode(css));
|
||||
document.head.appendChild(styleTag);
|
||||
}
|
||||
|
||||
async start(argv) {
|
||||
argv = await super.start(argv);
|
||||
|
||||
@@ -385,17 +590,20 @@ class Application extends BaseApplication {
|
||||
// Note: Auto-update currently doesn't work in Linux: it downloads the update
|
||||
// but then doesn't install it on exit.
|
||||
if (shim.isWindows() || shim.isMac()) {
|
||||
const runAutoUpdateCheck = function() {
|
||||
const runAutoUpdateCheck = () => {
|
||||
if (Setting.value('autoUpdateEnabled')) {
|
||||
bridge().checkForUpdatesAndNotify(Setting.value('profileDir') + '/log-autoupdater.txt');
|
||||
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();
|
||||
|
||||
setTimeout(() => {
|
||||
AlarmService.garbageCollect();
|
||||
}, 1000 * 60 * 60);
|
||||
|
@@ -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,30 @@ 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) {
|
||||
// Don't use this directly - call one of the showXxxxxxxMessageBox() instead
|
||||
showMessageBox_(window, options) {
|
||||
const {dialog} = require('electron');
|
||||
return dialog.showMessageBox(options);
|
||||
const nativeImage = require('electron').nativeImage
|
||||
if (!window) window = this.window();
|
||||
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')],
|
||||
@@ -83,12 +86,12 @@ class Bridge {
|
||||
return result === 0;
|
||||
}
|
||||
|
||||
showInfoMessageBox(message) {
|
||||
const result = this.showMessageBox({
|
||||
showInfoMessageBox(message, options = {}) {
|
||||
const result = this.showMessageBox_(this.window(), Object.assign({}, {
|
||||
type: 'info',
|
||||
message: message,
|
||||
buttons: [_('OK')],
|
||||
});
|
||||
}, options));
|
||||
return result === 0;
|
||||
}
|
||||
|
||||
@@ -108,21 +111,9 @@ class Bridge {
|
||||
return require('electron').shell.openItem(fullPath)
|
||||
}
|
||||
|
||||
async checkForUpdatesAndNotify(logFilePath) {
|
||||
if (!this.autoUpdater_) {
|
||||
this.autoUpdateLogger_ = new Logger();
|
||||
this.autoUpdateLogger_.addTarget('file', { path: logFilePath });
|
||||
this.autoUpdateLogger_.setLevel(Logger.LEVEL_DEBUG);
|
||||
this.autoUpdateLogger_.info('checkForUpdatesAndNotify: Initializing...');
|
||||
this.autoUpdater_ = require("electron-updater").autoUpdater;
|
||||
this.autoUpdater_.logger = this.autoUpdateLogger_;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.autoUpdater_.checkForUpdatesAndNotify();
|
||||
} catch (error) {
|
||||
this.autoUpdateLogger_.error(error);
|
||||
}
|
||||
checkForUpdates(inBackground, window, logFilePath) {
|
||||
const { checkForUpdates } = require('./checkForUpdates.js');
|
||||
checkForUpdates(inBackground, window, logFilePath);
|
||||
}
|
||||
|
||||
}
|
||||
|
BIN
ElectronClient/app/build/icons/16x16-dev.png
Normal file
BIN
ElectronClient/app/build/icons/16x16-dev.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 611 B |
BIN
ElectronClient/app/build/icons/16x16@2x.png
Normal file
BIN
ElectronClient/app/build/icons/16x16@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
BIN
ElectronClient/app/build/icons/16x16@3x.png
Normal file
BIN
ElectronClient/app/build/icons/16x16@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
BIN
ElectronClient/app/build/icons/32x32@3.png
Normal file
BIN
ElectronClient/app/build/icons/32x32@3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
BIN
ElectronClient/app/build/icons/macos-16x16Template.png
Normal file
BIN
ElectronClient/app/build/icons/macos-16x16Template.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 348 B |
BIN
ElectronClient/app/build/icons/macos-16x16Template@2x.png
Normal file
BIN
ElectronClient/app/build/icons/macos-16x16Template@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 504 B |
110
ElectronClient/app/checkForUpdates.js
Normal file
110
ElectronClient/app/checkForUpdates.js
Normal file
@@ -0,0 +1,110 @@
|
||||
const { dialog } = require('electron')
|
||||
const { Logger } = require('lib/logger.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const fetch = require('node-fetch');
|
||||
const packageInfo = require('./packageInfo.js');
|
||||
const compareVersions = require('compare-versions');
|
||||
|
||||
let autoUpdateLogger_ = new Logger();
|
||||
let checkInBackground_ = false;
|
||||
let isCheckingForUpdate_ = false;
|
||||
let parentWindow_ = null;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
async function fetchLatestRelease() {
|
||||
const response = await fetch('https://api.github.com/repos/laurent22/joplin/releases/latest');
|
||||
|
||||
if (!response.ok) {
|
||||
const responseText = await response.text();
|
||||
throw new Error('Cannot get latest release info: ' + responseText.substr(0,500));
|
||||
}
|
||||
|
||||
const json = await response.json();
|
||||
|
||||
const version = json.tag_name.substr(1);
|
||||
let downloadUrl = null;
|
||||
const platform = process.platform;
|
||||
for (let i = 0; i < json.assets.length; i++) {
|
||||
const asset = json.assets[i];
|
||||
let found = false;
|
||||
if (platform === 'win32' && asset.name.indexOf('.exe') >= 0) {
|
||||
found = true;
|
||||
} else if (platform === 'darwin' && asset.name.indexOf('.dmg') >= 0) {
|
||||
found = true;
|
||||
} else if (platform === 'linux' && asset.name.indexOf('.AppImage') >= 0) {
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (found) {
|
||||
downloadUrl = asset.browser_download_url;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!downloadUrl) throw new Error('Cannot find download Url: ' + JSON.stringify(json).substr(0,500));
|
||||
|
||||
return {
|
||||
version: version,
|
||||
downloadUrl: downloadUrl,
|
||||
notes: json.body,
|
||||
};
|
||||
}
|
||||
|
||||
function checkForUpdates(inBackground, window, logFilePath) {
|
||||
if (isCheckingForUpdate_) {
|
||||
autoUpdateLogger_.info('checkForUpdates: Skipping check because it is already running');
|
||||
return;
|
||||
}
|
||||
|
||||
parentWindow_ = window;
|
||||
|
||||
onCheckStarted();
|
||||
|
||||
if (logFilePath && !autoUpdateLogger_.targets().length) {
|
||||
autoUpdateLogger_ = new Logger();
|
||||
autoUpdateLogger_.addTarget('file', { path: logFilePath });
|
||||
autoUpdateLogger_.setLevel(Logger.LEVEL_DEBUG);
|
||||
autoUpdateLogger_.info('checkForUpdates: Initializing...');
|
||||
}
|
||||
|
||||
checkInBackground_ = inBackground;
|
||||
|
||||
fetchLatestRelease().then(release => {
|
||||
if (compareVersions(release.version, packageInfo.version) <= 0) {
|
||||
if (!checkInBackground_) dialog.showMessageBox({ message: _('Current version is up-to-date.') })
|
||||
} else {
|
||||
const releaseNotes = release.notes.trim() ? "\n\n" + release.notes.trim() : '';
|
||||
|
||||
const buttonIndex = dialog.showMessageBox(parentWindow_, {
|
||||
type: 'info',
|
||||
message: _('An update is available, do you want to download it now?' + releaseNotes),
|
||||
buttons: [_('Yes'), _('No')]
|
||||
});
|
||||
|
||||
if (buttonIndex === 0) require('electron').shell.openExternal(release.downloadUrl);
|
||||
}
|
||||
}).catch(error => {
|
||||
autoUpdateLogger_.error(error);
|
||||
if (!checkInBackground_) showErrorMessageBox(error.message);
|
||||
}).then(() => {
|
||||
onCheckEnded();
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.checkForUpdates = checkForUpdates
|
3
ElectronClient/app/dev-app-update.yml-FORTESTING
Normal file
3
ElectronClient/app/dev-app-update.yml-FORTESTING
Normal file
@@ -0,0 +1,3 @@
|
||||
owner: laurent22
|
||||
repo: joplin
|
||||
provider: github
|
@@ -7,14 +7,22 @@ const { Header } = require('./Header.min.js');
|
||||
const { themeStyle } = require('../theme.js');
|
||||
const pathUtils = require('lib/path-utils.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const SyncTargetRegistry = require('lib/SyncTargetRegistry');
|
||||
const shared = require('lib/components/shared/config-shared.js');
|
||||
|
||||
class ConfigScreenComponent extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
settings: {},
|
||||
shared.init(this);
|
||||
|
||||
this.checkSyncConfig_ = async () => {
|
||||
await shared.checkSyncConfig(this, this.state.settings);
|
||||
}
|
||||
|
||||
this.rowStyle_ = {
|
||||
marginBottom: 10,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -32,10 +40,6 @@ class ConfigScreenComponent extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
output.sort((a, b) => {
|
||||
return a.label.toLowerCase() < b.label.toLowerCase() ? -1 : +1;
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -44,9 +48,7 @@ class ConfigScreenComponent extends React.Component {
|
||||
|
||||
let output = null;
|
||||
|
||||
const rowStyle = {
|
||||
marginBottom: 10,
|
||||
};
|
||||
const rowStyle = this.rowStyle_;
|
||||
|
||||
const labelStyle = Object.assign({}, theme.textStyle, {
|
||||
display: 'inline-block',
|
||||
@@ -57,16 +59,27 @@ class ConfigScreenComponent extends React.Component {
|
||||
display: 'inline-block',
|
||||
};
|
||||
|
||||
const descriptionStyle = Object.assign({}, theme.textStyle, {
|
||||
color: theme.colorFaded,
|
||||
marginTop: 5,
|
||||
fontStyle: 'italic',
|
||||
});
|
||||
|
||||
const updateSettingValue = (key, value) => {
|
||||
const settings = Object.assign({}, this.state.settings);
|
||||
settings[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.
|
||||
|
||||
const md = Setting.settingMetadata(key);
|
||||
|
||||
const descriptionText = Setting.keyDescription(key, 'desktop');
|
||||
const descriptionComp = descriptionText ? (
|
||||
<div style={descriptionStyle}>
|
||||
{descriptionText}
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
if (md.isEnum) {
|
||||
let items = [];
|
||||
const settingOptions = md.options();
|
||||
@@ -82,6 +95,7 @@ class ConfigScreenComponent extends React.Component {
|
||||
<select value={value} style={controlStyle} onChange={(event) => { updateSettingValue(key, event.target.value) }}>
|
||||
{items}
|
||||
</select>
|
||||
{ descriptionComp }
|
||||
</div>
|
||||
);
|
||||
} else if (md.type === Setting.TYPE_BOOL) {
|
||||
@@ -89,24 +103,42 @@ class ConfigScreenComponent extends React.Component {
|
||||
updateSettingValue(key, !value)
|
||||
}
|
||||
|
||||
// Hack: The {key+value.toString()} is needed as otherwise the checkbox doesn't update when the state changes.
|
||||
// There's probably a better way to do this but can't figure it out.
|
||||
|
||||
return (
|
||||
<div key={key} style={rowStyle}>
|
||||
<div key={key+value.toString()} style={rowStyle}>
|
||||
<div style={controlStyle}>
|
||||
<input id={'setting_checkbox_' + key} type="checkbox" checked={!!value} onChange={(event) => { onCheckboxClick(event) }}/><label onClick={(event) => { onCheckboxClick(event) }} style={labelStyle} htmlFor={'setting_checkbox_' + key}>{md.label()}</label>
|
||||
<input id={'setting_checkbox_' + key} type="checkbox" checked={!!value} onChange={(event) => { onCheckboxClick(event) }}/><label onClick={(event) => { onCheckboxClick(event) }} style={labelStyle} htmlFor={'setting_checkbox_' + key}>{md.label()}</label>
|
||||
{ descriptionComp }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (md.type === Setting.TYPE_STRING) {
|
||||
const onTextChange = (event) => {
|
||||
const settings = Object.assign({}, this.state.settings);
|
||||
settings[key] = event.target.value;
|
||||
this.setState({ settings: settings });
|
||||
updateSettingValue(key, event.target.value);
|
||||
}
|
||||
|
||||
const inputStyle = Object.assign({}, controlStyle, { width: '50%', minWidth: '20em' });
|
||||
const inputType = md.secure === true ? 'password' : 'text';
|
||||
|
||||
return (
|
||||
<div key={key} style={rowStyle}>
|
||||
<div style={labelStyle}><label>{md.label()}</label></div>
|
||||
<input type="text" style={controlStyle} value={this.state.settings[key]} onChange={(event) => {onTextChange(event)}} />
|
||||
<input type={inputType} style={inputStyle} value={this.state.settings[key]} onChange={(event) => {onTextChange(event)}} />
|
||||
{ descriptionComp }
|
||||
</div>
|
||||
);
|
||||
} else if (md.type === Setting.TYPE_INT) {
|
||||
const onNumChange = (event) => {
|
||||
updateSettingValue(key, event.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div key={key} style={rowStyle}>
|
||||
<div style={labelStyle}><label>{md.label()}</label></div>
|
||||
<input type="number" style={controlStyle} value={this.state.settings[key]} onChange={(event) => {onNumChange(event)}} min={md.minimum} max={md.maximum} step={md.step}/>
|
||||
{ descriptionComp }
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
@@ -117,10 +149,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' });
|
||||
}
|
||||
|
||||
@@ -130,7 +159,7 @@ class ConfigScreenComponent extends React.Component {
|
||||
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const style = this.props.style;
|
||||
const style = Object.assign({}, this.props.style, { overflow: 'auto' });
|
||||
const settings = this.state.settings;
|
||||
|
||||
const headerStyle = {
|
||||
@@ -142,23 +171,28 @@ 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']);
|
||||
|
||||
if (syncTargetMd.supportsConfigCheck) {
|
||||
const messages = shared.checkSyncConfigMessages(this);
|
||||
const statusStyle = Object.assign({}, theme.textStyle, { marginTop: 10 });
|
||||
const statusComp = !messages.length ? null : (
|
||||
<div style={statusStyle}>
|
||||
{messages[0]}
|
||||
{messages.length >= 1 ? (<p>{messages[1]}</p>) : null}
|
||||
</div>);
|
||||
|
||||
settingComps.push(
|
||||
<div key="check_sync_config_button" style={this.rowStyle_}>
|
||||
<button disabled={this.state.checkSyncConfigResult === 'checking'} onClick={this.checkSyncConfig_}>{_('Check synchronisation configuration')}</button>
|
||||
{ statusComp }
|
||||
</div>);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@@ -92,10 +92,14 @@ class EncryptionConfigScreenComponent extends React.Component {
|
||||
};
|
||||
|
||||
const mkComps = [];
|
||||
let nonExistingMasterKeyIds = this.props.notLoadedMasterKeys.slice();
|
||||
|
||||
for (let i = 0; i < masterKeys.length; i++) {
|
||||
const mk = masterKeys[i];
|
||||
mkComps.push(this.renderMasterKey(mk));
|
||||
|
||||
const idx = nonExistingMasterKeyIds.indexOf(mk.id);
|
||||
if (idx >= 0) nonExistingMasterKeyIds.splice(idx, 1);
|
||||
}
|
||||
|
||||
const onToggleButtonClick = async () => {
|
||||
@@ -121,7 +125,7 @@ class EncryptionConfigScreenComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
const decryptedItemsInfo = this.props.encryptionEnabled ? <p style={theme.textStyle}>{shared.decryptedStatText(this)}</p> : null;
|
||||
const decryptedItemsInfo = <p style={theme.textStyle}>{shared.decryptedStatText(this)}</p>;
|
||||
const toggleButton = <button onClick={() => { onToggleButtonClick() }}>{this.props.encryptionEnabled ? _('Disable encryption') : _('Enable encryption')}</button>
|
||||
|
||||
let masterKeySection = null;
|
||||
@@ -149,11 +153,36 @@ class EncryptionConfigScreenComponent extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
let nonExistingMasterKeySection = null;
|
||||
|
||||
if (nonExistingMasterKeyIds.length) {
|
||||
const rows = [];
|
||||
for (let i = 0; i < nonExistingMasterKeyIds.length; i++) {
|
||||
const id = nonExistingMasterKeyIds[i];
|
||||
rows.push(<tr key={id}><td style={theme.textStyle}>{id}</td></tr>);
|
||||
}
|
||||
|
||||
nonExistingMasterKeySection = (
|
||||
<div>
|
||||
<h1 style={theme.h1Style}>{_('Missing Master Keys')}</h1>
|
||||
<p style={theme.textStyle}>{_('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.')}</p>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th style={theme.textStyle}>{_('ID')}</th>
|
||||
</tr>
|
||||
{ rows }
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<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>
|
||||
@@ -163,12 +192,13 @@ 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}
|
||||
{toggleButton}
|
||||
{masterKeySection}
|
||||
{nonExistingMasterKeySection}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -183,6 +213,7 @@ const mapStateToProps = (state) => {
|
||||
passwords: state.settings['encryption.passwordCache'],
|
||||
encryptionEnabled: state.settings['encryption.enabled'],
|
||||
activeMasterKeyId: state.settings['encryption.activeMasterKeyId'],
|
||||
notLoadedMasterKeys: state.notLoadedMasterKeys,
|
||||
};
|
||||
};
|
||||
|
||||
|
@@ -25,9 +25,7 @@ class ImportScreenComponent extends React.Component {
|
||||
doImport: true,
|
||||
filePath: newProps.filePath,
|
||||
messages: [],
|
||||
});
|
||||
|
||||
this.doImport();
|
||||
}, () => { this.doImport() });
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -22,6 +22,10 @@ class MainScreenComponent extends React.Component {
|
||||
componentWillMount() {
|
||||
this.setState({
|
||||
promptOptions: null,
|
||||
modalLayer: {
|
||||
visible: false,
|
||||
message: '',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -163,6 +167,12 @@ class MainScreenComponent extends React.Component {
|
||||
}
|
||||
},
|
||||
});
|
||||
} else if (command.name === 'toggleVisiblePanes') {
|
||||
this.toggleVisiblePanes();
|
||||
} else if (command.name === 'showModalMessage') {
|
||||
this.setState({ modalLayer: { visible: true, message: command.message } });
|
||||
} else if (command.name === 'hideModalMessage') {
|
||||
this.setState({ modalLayer: { visible: false, message: '' } });
|
||||
} else if (command.name === 'editAlarm') {
|
||||
const note = await Note.load(command.noteId);
|
||||
|
||||
@@ -263,6 +273,17 @@ class MainScreenComponent extends React.Component {
|
||||
height: height,
|
||||
};
|
||||
|
||||
this.styles_.modalLayer = Object.assign({}, theme.textStyle, {
|
||||
zIndex: 10000,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
backgroundColor: theme.backgroundColorTransparent,
|
||||
width: width - 20,
|
||||
height: height - 20,
|
||||
padding: 10,
|
||||
});
|
||||
|
||||
return this.styles_;
|
||||
}
|
||||
|
||||
@@ -274,20 +295,22 @@ class MainScreenComponent extends React.Component {
|
||||
const messageBoxVisible = this.props.hasDisabledSyncItems || this.props.showMissingMasterKeyMessage;
|
||||
const styles = this.styles(this.props.theme, style.width, style.height, messageBoxVisible);
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const selectedFolderId = this.props.selectedFolderId;
|
||||
const onConflictFolder = this.props.selectedFolderId === Folder.conflictFolderId();
|
||||
|
||||
const headerButtons = [];
|
||||
|
||||
headerButtons.push({
|
||||
title: _('New note'),
|
||||
iconName: 'fa-file-o',
|
||||
enabled: !!folders.length,
|
||||
enabled: !!folders.length && !onConflictFolder,
|
||||
onClick: () => { this.doCommand({ name: 'newNote' }) },
|
||||
});
|
||||
|
||||
headerButtons.push({
|
||||
title: _('New to-do'),
|
||||
iconName: 'fa-check-square-o',
|
||||
enabled: !!folders.length,
|
||||
enabled: !!folders.length && !onConflictFolder,
|
||||
onClick: () => { this.doCommand({ name: 'newTodo' }) },
|
||||
});
|
||||
|
||||
@@ -307,9 +330,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_) {
|
||||
@@ -351,8 +372,12 @@ class MainScreenComponent extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
const modalLayerStyle = Object.assign({}, styles.modalLayer, { display: this.state.modalLayer.visible ? 'block' : 'none' });
|
||||
|
||||
return (
|
||||
<div style={style}>
|
||||
<div style={modalLayerStyle}>{this.state.modalLayer.message}</div>
|
||||
|
||||
<PromptDialog
|
||||
autocomplete={promptOptions && ('autocomplete' in promptOptions) ? promptOptions.autocomplete : null}
|
||||
defaultValue={promptOptions && promptOptions.value ? promptOptions.value : ''}
|
||||
@@ -384,6 +409,7 @@ const mapStateToProps = (state) => {
|
||||
notes: state.notes,
|
||||
hasDisabledSyncItems: state.hasDisabledSyncItems,
|
||||
showMissingMasterKeyMessage: state.notLoadedMasterKeys.length && state.masterKeys.length,
|
||||
selectedFolderId: state.selectedFolderId,
|
||||
};
|
||||
};
|
||||
|
||||
|
@@ -9,6 +9,8 @@ const { bridge } = require('electron').remote.require('./bridge');
|
||||
const Menu = bridge().Menu;
|
||||
const MenuItem = bridge().MenuItem;
|
||||
const eventManager = require('../eventManager');
|
||||
const InteropService = require('lib/services/InteropService');
|
||||
const InteropServiceHelper = require('../InteropServiceHelper.js');
|
||||
|
||||
class NoteListComponent extends React.Component {
|
||||
|
||||
@@ -91,6 +93,23 @@ class NoteListComponent extends React.Component {
|
||||
eventManager.emit('noteTypeToggle', { noteId: note.id });
|
||||
}
|
||||
}}));
|
||||
|
||||
const exportMenu = new Menu();
|
||||
|
||||
const ioService = new InteropService();
|
||||
const ioModules = ioService.modules();
|
||||
for (let i = 0; i < ioModules.length; i++) {
|
||||
const module = ioModules[i];
|
||||
if (module.type !== 'exporter') continue;
|
||||
|
||||
exportMenu.append(new MenuItem({ label: module.fullLabel() , click: async () => {
|
||||
await InteropServiceHelper.export(this.props.dispatch.bind(this), module, { sourceNoteIds: noteIds });
|
||||
}}));
|
||||
}
|
||||
|
||||
const exportMenuItem = new MenuItem({label: _('Export'), submenu: exportMenu});
|
||||
|
||||
menu.append(exportMenuItem);
|
||||
}
|
||||
|
||||
menu.append(new MenuItem({label: _('Delete'), click: async () => {
|
||||
|
@@ -16,6 +16,7 @@ const Menu = bridge().Menu;
|
||||
const MenuItem = bridge().MenuItem;
|
||||
const { shim } = require('lib/shim.js');
|
||||
const eventManager = require('../eventManager');
|
||||
const fs = require('fs-extra');
|
||||
|
||||
require('brace/mode/markdown');
|
||||
// https://ace.c9.io/build/kitchen-sink.html
|
||||
@@ -115,10 +116,14 @@ class NoteTextComponent extends React.Component {
|
||||
eventManager.removeListener('todoToggle', this.onTodoToggle_);
|
||||
}
|
||||
|
||||
async saveIfNeeded() {
|
||||
async saveIfNeeded(saveIfNewNote = false) {
|
||||
const forceSave = saveIfNewNote && (this.state.note && !this.state.note.id);
|
||||
|
||||
if (this.scheduleSaveTimeout_) clearTimeout(this.scheduleSaveTimeout_);
|
||||
this.scheduleSaveTimeout_ = null;
|
||||
if (!shared.isModified(this)) return;
|
||||
if (!forceSave) {
|
||||
if (!shared.isModified(this)) return;
|
||||
}
|
||||
await shared.saveNoteButton_press(this);
|
||||
}
|
||||
|
||||
@@ -190,13 +195,21 @@ class NoteTextComponent extends React.Component {
|
||||
this.editorSetScrollTop(1);
|
||||
this.restoreScrollTop_ = 0;
|
||||
|
||||
if (note) {
|
||||
const focusSettingName = !!note.is_todo ? 'newTodoFocus' : 'newNoteFocus';
|
||||
|
||||
if (Setting.value(focusSettingName) === 'title') {
|
||||
if (this.titleField_) this.titleField_.focus();
|
||||
} else {
|
||||
if (this.editor_) this.editor_.editor.focus();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.editor_) {
|
||||
const session = this.editor_.editor.getSession();
|
||||
const undoManager = session.getUndoManager();
|
||||
undoManager.reset();
|
||||
session.setUndoManager(undoManager);
|
||||
|
||||
this.editor_.editor.focus();
|
||||
this.editor_.editor.clearSelection();
|
||||
this.editor_.editor.moveCursorTo(0,0);
|
||||
}
|
||||
@@ -227,6 +240,10 @@ class NoteTextComponent extends React.Component {
|
||||
if ('syncStarted' in nextProps && !nextProps.syncStarted && !this.isModified()) {
|
||||
await this.reloadNote(nextProps, { noReloadIfLocalChanges: true });
|
||||
}
|
||||
|
||||
if (nextProps.windowCommand) {
|
||||
this.doCommand(nextProps.windowCommand);
|
||||
}
|
||||
}
|
||||
|
||||
isModified() {
|
||||
@@ -252,7 +269,7 @@ class NoteTextComponent extends React.Component {
|
||||
shared.showMetadata_onPress(this);
|
||||
}
|
||||
|
||||
webview_ipcMessage(event) {
|
||||
async webview_ipcMessage(event) {
|
||||
const msg = event.channel ? event.channel : '';
|
||||
const args = event.args;
|
||||
const arg0 = args && args.length >= 1 ? args[0] : null;
|
||||
@@ -274,6 +291,32 @@ class NoteTextComponent extends React.Component {
|
||||
} else if (msg === 'percentScroll') {
|
||||
this.ignoreNextEditorScroll_ = true;
|
||||
this.setEditorPercentScroll(arg0);
|
||||
} else if (msg === 'contextMenu') {
|
||||
const itemType = arg0 && arg0.type;
|
||||
|
||||
const menu = new Menu()
|
||||
|
||||
if (itemType === "image" || itemType === "link") {
|
||||
const resource = await Resource.load(arg0.resourceId);
|
||||
const resourcePath = Resource.fullPath(resource);
|
||||
|
||||
menu.append(new MenuItem({label: _('Open...'), click: async () => {
|
||||
bridge().openExternal(resourcePath);
|
||||
}}));
|
||||
|
||||
menu.append(new MenuItem({label: _('Save as...'), click: async () => {
|
||||
const filePath = bridge().showSaveDialog({
|
||||
defaultPath: resource.filename ? resource.filename : resource.title,
|
||||
});
|
||||
if (!filePath) return;
|
||||
await fs.copy(resourcePath, filePath);
|
||||
}}));
|
||||
} else {
|
||||
reg.logger().error('Unhandled item type: ' + itemType);
|
||||
return;
|
||||
}
|
||||
|
||||
menu.popup(bridge().window());
|
||||
} else if (msg.indexOf('joplin://') === 0) {
|
||||
const resourceId = msg.substr('joplin://'.length);
|
||||
Resource.load(resourceId).then((resource) => {
|
||||
@@ -281,10 +324,7 @@ class NoteTextComponent extends React.Component {
|
||||
bridge().openItem(filePath);
|
||||
});
|
||||
} else {
|
||||
bridge().showMessageBox({
|
||||
type: 'error',
|
||||
message: _('Unsupported link or message: %s', msg),
|
||||
});
|
||||
bridge().showErrorMessageBox(_('Unsupported link or message: %s', msg));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,18 +430,47 @@ class NoteTextComponent extends React.Component {
|
||||
this.scheduleSave();
|
||||
}
|
||||
|
||||
async doCommand(command) {
|
||||
if (!command) return;
|
||||
|
||||
let commandProcessed = true;
|
||||
|
||||
if (command.name === 'exportPdf' && this.webview_) {
|
||||
const path = bridge().showSaveDialog({
|
||||
filters: [{ name: _('PDF File'), extensions: ['pdf']}]
|
||||
});
|
||||
|
||||
if (path) {
|
||||
this.webview_.printToPDF({}, (error, data) => {
|
||||
if (error) {
|
||||
bridge().showErrorMessageBox(error.message);
|
||||
} else {
|
||||
shim.fsDriver().writeFile(path, data, 'buffer');
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (command.name === 'print' && this.webview_) {
|
||||
this.webview_.print();
|
||||
} else {
|
||||
commandProcessed = false;
|
||||
}
|
||||
|
||||
if (commandProcessed) {
|
||||
this.props.dispatch({
|
||||
type: 'WINDOW_COMMAND',
|
||||
name: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async commandAttachFile() {
|
||||
const noteId = this.props.noteId;
|
||||
if (!noteId) return;
|
||||
|
||||
const filePaths = bridge().showOpenDialog({
|
||||
properties: ['openFile', 'createDirectory', 'multiSelections'],
|
||||
});
|
||||
if (!filePaths || !filePaths.length) return;
|
||||
|
||||
await this.saveIfNeeded();
|
||||
let note = await Note.load(noteId);
|
||||
await this.saveIfNeeded(true);
|
||||
let note = await Note.load(this.state.note.id);
|
||||
|
||||
for (let i = 0; i < filePaths.length; i++) {
|
||||
const filePath = filePaths[i];
|
||||
@@ -419,20 +488,29 @@ class NoteTextComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
commandSetAlarm() {
|
||||
const noteId = this.props.noteId;
|
||||
if (!noteId) return;
|
||||
async commandSetAlarm() {
|
||||
await this.saveIfNeeded(true);
|
||||
|
||||
this.props.dispatch({
|
||||
type: 'WINDOW_COMMAND',
|
||||
name: 'editAlarm',
|
||||
noteId: noteId,
|
||||
noteId: this.state.note.id,
|
||||
});
|
||||
}
|
||||
|
||||
async commandSetTags() {
|
||||
await this.saveIfNeeded(true);
|
||||
|
||||
this.props.dispatch({
|
||||
type: 'WINDOW_COMMAND',
|
||||
name: 'setTags',
|
||||
noteId: this.state.note.id,
|
||||
});
|
||||
}
|
||||
|
||||
itemContextMenu(event) {
|
||||
const noteId = this.props.noteId;
|
||||
if (!noteId) return;
|
||||
const note = this.state.note;
|
||||
if (!note) return;
|
||||
|
||||
const menu = new Menu()
|
||||
|
||||
@@ -440,10 +518,16 @@ class NoteTextComponent extends React.Component {
|
||||
return this.commandAttachFile();
|
||||
}}));
|
||||
|
||||
menu.append(new MenuItem({label: _('Set alarm'), click: async () => {
|
||||
return this.commandSetAlarm();
|
||||
menu.append(new MenuItem({label: _('Tags'), click: async () => {
|
||||
return this.commandSetTags();
|
||||
}}));
|
||||
|
||||
if (!!note.is_todo) {
|
||||
menu.append(new MenuItem({label: _('Set alarm'), click: async () => {
|
||||
return this.commandSetAlarm();
|
||||
}}));
|
||||
}
|
||||
|
||||
menu.popup(bridge().window());
|
||||
}
|
||||
|
||||
@@ -453,6 +537,7 @@ class NoteTextComponent extends React.Component {
|
||||
const body = note && note.body ? note.body : '';
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const visiblePanes = this.props.visiblePanes || ['editor', 'viewer'];
|
||||
const isTodo = note && !!note.is_todo;
|
||||
|
||||
const borderWidth = 1;
|
||||
|
||||
@@ -548,7 +633,14 @@ class NoteTextComponent extends React.Component {
|
||||
},
|
||||
postMessageSyntax: 'ipcRenderer.sendToHost',
|
||||
};
|
||||
const html = this.mdToHtml().render(body, theme, mdOptions);
|
||||
|
||||
let bodyToRender = body;
|
||||
if (!bodyToRender.trim() && visiblePanes.indexOf('viewer') >= 0 && visiblePanes.indexOf('editor') < 0) {
|
||||
// Fixes https://github.com/laurent22/joplin/issues/217
|
||||
bodyToRender = '*' + _('This note has no content. Click on "%s" to toggle the editor and edit the note.', _('Layout')) + '*';
|
||||
}
|
||||
|
||||
const html = this.mdToHtml().render(bodyToRender, theme, mdOptions);
|
||||
this.webview_.send('setHtml', html);
|
||||
}
|
||||
|
||||
@@ -560,6 +652,12 @@ class NoteTextComponent extends React.Component {
|
||||
onClick: () => { return this.commandAttachFile(); },
|
||||
});
|
||||
|
||||
toolbarItems.push({
|
||||
title: _('Tags'),
|
||||
iconName: 'fa-tags',
|
||||
onClick: () => { return this.commandSetTags(); },
|
||||
});
|
||||
|
||||
if (note.is_todo) {
|
||||
toolbarItems.push({
|
||||
title: Note.needAlarm(note) ? time.formatMsToLocal(note.todo_due) : _('Set alarm'),
|
||||
@@ -576,9 +674,11 @@ class NoteTextComponent extends React.Component {
|
||||
|
||||
const titleEditor = <input
|
||||
type="text"
|
||||
ref={(elem) => { this.titleField_ = elem; } }
|
||||
style={titleEditorStyle}
|
||||
value={note && note.title ? note.title : ''}
|
||||
onChange={(event) => { this.title_changeText(event); }}
|
||||
placeholder={ this.props.newNote ? _('Creating new %s...', isTodo ? _('to-do') : _('note')) : '' }
|
||||
/>
|
||||
|
||||
const titleBarMenuButton = <IconButton style={{
|
||||
@@ -646,6 +746,7 @@ const mapStateToProps = (state) => {
|
||||
showAdvancedOptions: state.settings.showAdvancedOptions,
|
||||
syncStarted: state.syncStarted,
|
||||
newNote: state.newNote,
|
||||
windowCommand: state.windowCommand,
|
||||
};
|
||||
};
|
||||
|
||||
|
@@ -54,10 +54,7 @@ class OneDriveLoginScreenComponent extends React.Component {
|
||||
this.props.dispatch({ type: 'NAV_BACK' });
|
||||
reg.scheduleSync(0);
|
||||
} catch (error) {
|
||||
bridge().showMessageBox({
|
||||
type: 'error',
|
||||
message: 'Could not login to OneDrive. Please try again.\n\n' + error.message + "\n\n" + url.match(/.{1,64}/g).join('\n'),
|
||||
});
|
||||
bridge().showErrorMessageBox('Could not login to OneDrive. Please try again.\n\n' + error.message + "\n\n" + url.match(/.{1,64}/g).join('\n'));
|
||||
}
|
||||
|
||||
this.authCode_ = null;
|
||||
|
@@ -1,19 +1,19 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const shared = require('lib/components/shared/side-menu-shared.js');
|
||||
const { Synchronizer } = require('lib/synchronizer.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const Tag = require('lib/models/Tag.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { themeStyle } = require('../theme.js');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
const React = require("react");
|
||||
const { connect } = require("react-redux");
|
||||
const shared = require("lib/components/shared/side-menu-shared.js");
|
||||
const { Synchronizer } = require("lib/synchronizer.js");
|
||||
const BaseModel = require("lib/BaseModel.js");
|
||||
const Folder = require("lib/models/Folder.js");
|
||||
const Note = require("lib/models/Note.js");
|
||||
const Tag = require("lib/models/Tag.js");
|
||||
const { _ } = require("lib/locale.js");
|
||||
const { themeStyle } = require("../theme.js");
|
||||
const { bridge } = require("electron").remote.require("./bridge");
|
||||
const Menu = bridge().Menu;
|
||||
const MenuItem = bridge().MenuItem;
|
||||
const InteropServiceHelper = require("../InteropServiceHelper.js");
|
||||
|
||||
class SideBarComponent extends React.Component {
|
||||
|
||||
style() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
|
||||
@@ -27,63 +27,65 @@ class SideBarComponent extends React.Component {
|
||||
height: itemHeight,
|
||||
fontFamily: theme.fontFamily,
|
||||
fontSize: theme.fontSize,
|
||||
textDecoration: 'none',
|
||||
boxSizing: 'border-box',
|
||||
textDecoration: "none",
|
||||
boxSizing: "border-box",
|
||||
color: theme.color2,
|
||||
paddingLeft: 14,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
cursor: 'default',
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
cursor: "default",
|
||||
opacity: 0.8,
|
||||
whiteSpace: 'nowrap',
|
||||
whiteSpace: "nowrap",
|
||||
},
|
||||
listItemSelected: {
|
||||
backgroundColor: theme.selectedColor2,
|
||||
},
|
||||
conflictFolder: {
|
||||
color: theme.colorError2,
|
||||
fontWeight: 'bold',
|
||||
fontWeight: "bold",
|
||||
},
|
||||
header: {
|
||||
height: itemHeight * 1.8,
|
||||
fontFamily: theme.fontFamily,
|
||||
fontSize: theme.fontSize * 1.3,
|
||||
textDecoration: 'none',
|
||||
boxSizing: 'border-box',
|
||||
textDecoration: "none",
|
||||
boxSizing: "border-box",
|
||||
color: theme.color2,
|
||||
paddingLeft: 8,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
button: {
|
||||
padding: 6,
|
||||
fontFamily: theme.fontFamily,
|
||||
fontSize: theme.fontSize,
|
||||
textDecoration: 'none',
|
||||
boxSizing: 'border-box',
|
||||
textDecoration: "none",
|
||||
boxSizing: "border-box",
|
||||
color: theme.color2,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
border: "1px solid rgba(255,255,255,0.2)",
|
||||
marginTop: 10,
|
||||
marginLeft: 5,
|
||||
marginRight: 5,
|
||||
cursor: 'default',
|
||||
cursor: "default",
|
||||
},
|
||||
syncReport: {
|
||||
fontFamily: theme.fontFamily,
|
||||
fontSize: Math.round(theme.fontSize * .9),
|
||||
fontSize: Math.round(theme.fontSize * 0.9),
|
||||
color: theme.color2,
|
||||
opacity: .5,
|
||||
display: 'flex',
|
||||
alignItems: 'left',
|
||||
justifyContent: 'top',
|
||||
flexDirection: 'column',
|
||||
opacity: 0.5,
|
||||
display: "flex",
|
||||
alignItems: "left",
|
||||
justifyContent: "top",
|
||||
flexDirection: "column",
|
||||
marginTop: 10,
|
||||
marginLeft: 5,
|
||||
marginRight: 5,
|
||||
minHeight: 70,
|
||||
wordWrap: "break-word",
|
||||
width: "100%",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -91,19 +93,19 @@ class SideBarComponent extends React.Component {
|
||||
}
|
||||
|
||||
itemContextMenu(event) {
|
||||
const itemId = event.target.getAttribute('data-id');
|
||||
const itemId = event.target.getAttribute("data-id");
|
||||
if (itemId === Folder.conflictFolderId()) return;
|
||||
|
||||
const itemType = Number(event.target.getAttribute('data-type'));
|
||||
if (!itemId || !itemType) throw new Error('No data on element');
|
||||
|
||||
let deleteMessage = '';
|
||||
const itemType = Number(event.target.getAttribute("data-type"));
|
||||
if (!itemId || !itemType) throw new Error("No data on element");
|
||||
|
||||
let deleteMessage = "";
|
||||
if (itemType === BaseModel.TYPE_FOLDER) {
|
||||
deleteMessage = _('Delete notebook? All notes within this notebook will also be deleted.');
|
||||
deleteMessage = _("Delete notebook? All notes within this notebook will also be deleted.");
|
||||
} else if (itemType === BaseModel.TYPE_TAG) {
|
||||
deleteMessage = _('Remove this tag from all the notes?');
|
||||
deleteMessage = _("Remove this tag from all the notes?");
|
||||
} else if (itemType === BaseModel.TYPE_SEARCH) {
|
||||
deleteMessage = _('Remove this search from the sidebar?');
|
||||
deleteMessage = _("Remove this search from the sidebar?");
|
||||
}
|
||||
|
||||
const menu = new Menu();
|
||||
@@ -113,30 +115,55 @@ class SideBarComponent extends React.Component {
|
||||
item = BaseModel.byId(this.props.folders, itemId);
|
||||
}
|
||||
|
||||
menu.append(new MenuItem({label: _('Delete'), click: async () => {
|
||||
const ok = bridge().showConfirmMessageBox(deleteMessage);
|
||||
if (!ok) return;
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: _("Delete"),
|
||||
click: async () => {
|
||||
const ok = bridge().showConfirmMessageBox(deleteMessage);
|
||||
if (!ok) return;
|
||||
|
||||
if (itemType === BaseModel.TYPE_FOLDER) {
|
||||
await Folder.delete(itemId);
|
||||
} else if (itemType === BaseModel.TYPE_TAG) {
|
||||
await Tag.untagAll(itemId);
|
||||
} else if (itemType === BaseModel.TYPE_SEARCH) {
|
||||
this.props.dispatch({
|
||||
type: 'SEARCH_DELETE',
|
||||
id: itemId,
|
||||
});
|
||||
}
|
||||
}}))
|
||||
if (itemType === BaseModel.TYPE_FOLDER) {
|
||||
await Folder.delete(itemId);
|
||||
} else if (itemType === BaseModel.TYPE_TAG) {
|
||||
await Tag.untagAll(itemId);
|
||||
} else if (itemType === BaseModel.TYPE_SEARCH) {
|
||||
this.props.dispatch({
|
||||
type: "SEARCH_DELETE",
|
||||
id: itemId,
|
||||
});
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
if (itemType === BaseModel.TYPE_FOLDER && !item.encryption_applied) {
|
||||
menu.append(new MenuItem({label: _('Rename'), click: async () => {
|
||||
this.props.dispatch({
|
||||
type: 'WINDOW_COMMAND',
|
||||
name: 'renameFolder',
|
||||
id: itemId,
|
||||
});
|
||||
}}))
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: _("Rename"),
|
||||
click: async () => {
|
||||
this.props.dispatch({
|
||||
type: "WINDOW_COMMAND",
|
||||
name: "renameFolder",
|
||||
id: itemId,
|
||||
});
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
menu.append(new MenuItem({ type: "separator" }));
|
||||
|
||||
const InteropService = require("lib/services/InteropService.js");
|
||||
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: _("Export"),
|
||||
click: async () => {
|
||||
const ioService = new InteropService();
|
||||
const module = ioService.moduleByFormat_("exporter", "jex");
|
||||
await InteropServiceHelper.export(this.props.dispatch.bind(this), module, { sourceFolderIds: [itemId] });
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
menu.popup(bridge().window());
|
||||
@@ -144,21 +171,21 @@ class SideBarComponent extends React.Component {
|
||||
|
||||
folderItem_click(folder) {
|
||||
this.props.dispatch({
|
||||
type: 'FOLDER_SELECT',
|
||||
type: "FOLDER_SELECT",
|
||||
id: folder ? folder.id : null,
|
||||
});
|
||||
}
|
||||
|
||||
tagItem_click(tag) {
|
||||
this.props.dispatch({
|
||||
type: 'TAG_SELECT',
|
||||
type: "TAG_SELECT",
|
||||
id: tag ? tag.id : null,
|
||||
});
|
||||
}
|
||||
|
||||
searchItem_click(search) {
|
||||
this.props.dispatch({
|
||||
type: 'SEARCH_SELECT',
|
||||
type: "SEARCH_SELECT",
|
||||
id: search ? search.id : null,
|
||||
});
|
||||
}
|
||||
@@ -173,105 +200,180 @@ class SideBarComponent extends React.Component {
|
||||
if (folder.id === Folder.conflictFolderId()) style = Object.assign(style, this.style().conflictFolder);
|
||||
|
||||
const onDragOver = (event, folder) => {
|
||||
if (event.dataTransfer.types.indexOf('text/x-jop-note-ids') >= 0) event.preventDefault();
|
||||
}
|
||||
if (event.dataTransfer.types.indexOf("text/x-jop-note-ids") >= 0) event.preventDefault();
|
||||
};
|
||||
|
||||
const onDrop = async (event, folder) => {
|
||||
if (event.dataTransfer.types.indexOf('text/x-jop-note-ids') < 0) return;
|
||||
if (event.dataTransfer.types.indexOf("text/x-jop-note-ids") < 0) return;
|
||||
event.preventDefault();
|
||||
|
||||
const noteIds = JSON.parse(event.dataTransfer.getData('text/x-jop-note-ids'));
|
||||
const noteIds = JSON.parse(event.dataTransfer.getData("text/x-jop-note-ids"));
|
||||
for (let i = 0; i < noteIds.length; i++) {
|
||||
await Note.moveToFolder(noteIds[i], folder.id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const itemTitle = Folder.displayTitle(folder);
|
||||
|
||||
return <a
|
||||
className="list-item"
|
||||
onDragOver={(event) => { onDragOver(event, folder) } }
|
||||
onDrop={(event) => { onDrop(event, folder) } }
|
||||
href="#"
|
||||
data-id={folder.id}
|
||||
data-type={BaseModel.TYPE_FOLDER}
|
||||
onContextMenu={(event) => this.itemContextMenu(event)}
|
||||
key={folder.id}
|
||||
style={style} onClick={() => {this.folderItem_click(folder)}}>{itemTitle}
|
||||
</a>
|
||||
return (
|
||||
<a
|
||||
className="list-item"
|
||||
onDragOver={event => {
|
||||
onDragOver(event, folder);
|
||||
}}
|
||||
onDrop={event => {
|
||||
onDrop(event, folder);
|
||||
}}
|
||||
href="#"
|
||||
data-id={folder.id}
|
||||
data-type={BaseModel.TYPE_FOLDER}
|
||||
onContextMenu={event => this.itemContextMenu(event)}
|
||||
key={folder.id}
|
||||
style={style}
|
||||
onClick={() => {
|
||||
this.folderItem_click(folder);
|
||||
}}
|
||||
>
|
||||
{itemTitle}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
tagItem(tag, selected) {
|
||||
let style = Object.assign({}, this.style().listItem);
|
||||
if (selected) style = Object.assign(style, this.style().listItemSelected);
|
||||
return <a className="list-item" href="#" data-id={tag.id} data-type={BaseModel.TYPE_TAG} onContextMenu={(event) => this.itemContextMenu(event)} key={tag.id} style={style} onClick={() => {this.tagItem_click(tag)}}>{Tag.displayTitle(tag)}</a>
|
||||
return (
|
||||
<a
|
||||
className="list-item"
|
||||
href="#"
|
||||
data-id={tag.id}
|
||||
data-type={BaseModel.TYPE_TAG}
|
||||
onContextMenu={event => this.itemContextMenu(event)}
|
||||
key={tag.id}
|
||||
style={style}
|
||||
onClick={() => {
|
||||
this.tagItem_click(tag);
|
||||
}}
|
||||
>
|
||||
{Tag.displayTitle(tag)}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
searchItem(search, selected) {
|
||||
let style = Object.assign({}, this.style().listItem);
|
||||
if (selected) style = Object.assign(style, this.style().listItemSelected);
|
||||
return <a className="list-item" href="#" data-id={search.id} data-type={BaseModel.TYPE_SEARCH} onContextMenu={(event) => this.itemContextMenu(event)} key={search.id} style={style} onClick={() => {this.searchItem_click(search)}}>{search.title}</a>
|
||||
return (
|
||||
<a
|
||||
className="list-item"
|
||||
href="#"
|
||||
data-id={search.id}
|
||||
data-type={BaseModel.TYPE_SEARCH}
|
||||
onContextMenu={event => this.itemContextMenu(event)}
|
||||
key={search.id}
|
||||
style={style}
|
||||
onClick={() => {
|
||||
this.searchItem_click(search);
|
||||
}}
|
||||
>
|
||||
{search.title}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
makeDivider(key) {
|
||||
return <div style={{height:2, backgroundColor:'blue' }} key={key}></div>
|
||||
return <div style={{ height: 2, backgroundColor: "blue" }} key={key} />;
|
||||
}
|
||||
|
||||
makeHeader(key, label, iconName) {
|
||||
const style = this.style().header;
|
||||
const icon = <i style={{fontSize: style.fontSize * 1.2, marginRight: 5}} className={"fa " + iconName}></i>
|
||||
return <div style={style} key={key}>{icon}{label}</div>
|
||||
const icon = <i style={{ fontSize: style.fontSize * 1.2, marginRight: 5 }} className={"fa " + iconName} />;
|
||||
return (
|
||||
<div style={style} key={key}>
|
||||
{icon}
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
synchronizeButton(type) {
|
||||
const style = this.style().button;
|
||||
const iconName = type === 'sync' ? 'fa-refresh' : 'fa-times';
|
||||
const label = type === 'sync' ? _('Synchronise') : _('Cancel');
|
||||
const icon = <i style={{fontSize: style.fontSize, marginRight: 5}} className={"fa " + iconName}></i>
|
||||
return <a className="synchronize-button" style={style} href="#" key="sync_button" onClick={() => {this.sync_click()}}>{icon}{label}</a>
|
||||
const iconName = type === "sync" ? "fa-refresh" : "fa-times";
|
||||
const label = type === "sync" ? _("Synchronise") : _("Cancel");
|
||||
const icon = <i style={{ fontSize: style.fontSize, marginRight: 5 }} className={"fa " + iconName} />;
|
||||
return (
|
||||
<a
|
||||
className="synchronize-button"
|
||||
style={style}
|
||||
href="#"
|
||||
key="sync_button"
|
||||
onClick={() => {
|
||||
this.sync_click();
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
{label}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const style = Object.assign({}, this.style().root, this.props.style, {
|
||||
overflowX: 'hidden',
|
||||
overflowY: 'auto',
|
||||
overflowX: "hidden",
|
||||
overflowY: "auto",
|
||||
});
|
||||
|
||||
let items = [];
|
||||
|
||||
items.push(this.makeHeader('folderHeader', _('Notebooks'), 'fa-folder-o'));
|
||||
items.push(this.makeHeader("folderHeader", _("Notebooks"), "fa-folder-o"));
|
||||
|
||||
if (this.props.folders.length) {
|
||||
const folderItems = shared.renderFolders(this.props, this.folderItem.bind(this));
|
||||
items = items.concat(folderItems);
|
||||
}
|
||||
|
||||
items.push(this.makeHeader('tagHeader', _('Tags'), 'fa-tags'));
|
||||
items.push(this.makeHeader("tagHeader", _("Tags"), "fa-tags"));
|
||||
|
||||
if (this.props.tags.length) {
|
||||
const tagItems = shared.renderTags(this.props, this.tagItem.bind(this));
|
||||
|
||||
items.push(<div className="tags" key="tag_items">{tagItems}</div>);
|
||||
items.push(
|
||||
<div className="tags" key="tag_items">
|
||||
{tagItems}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.searches.length) {
|
||||
items.push(this.makeHeader('searchHeader', _('Searches'), 'fa-search'));
|
||||
items.push(this.makeHeader("searchHeader", _("Searches"), "fa-search"));
|
||||
|
||||
const searchItems = shared.renderSearches(this.props, this.searchItem.bind(this));
|
||||
|
||||
items.push(<div className="searches" key="search_items">{searchItems}</div>);
|
||||
items.push(
|
||||
<div className="searches" key="search_items">
|
||||
{searchItems}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let lines = Synchronizer.reportToLines(this.props.syncReport);
|
||||
const syncReportText = [];
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
syncReportText.push(<div key={i}>{lines[i]}</div>);
|
||||
syncReportText.push(
|
||||
<div key={i} style={{ wordWrap: "break-word", width: "100%" }}>
|
||||
{lines[i]}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
items.push(this.synchronizeButton(this.props.syncStarted ? 'cancel' : 'sync'));
|
||||
items.push(this.synchronizeButton(this.props.syncStarted ? "cancel" : "sync"));
|
||||
|
||||
items.push(<div style={this.style().syncReport} key='sync_report'>{syncReportText}</div>);
|
||||
items.push(
|
||||
<div style={this.style().syncReport} key="sync_report">
|
||||
{syncReportText}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="side-bar" style={style}>
|
||||
@@ -279,10 +381,9 @@ class SideBarComponent extends React.Component {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
folders: state.folders,
|
||||
tags: state.tags,
|
||||
@@ -300,4 +401,4 @@ const mapStateToProps = (state) => {
|
||||
|
||||
const SideBar = connect(mapStateToProps)(SideBarComponent);
|
||||
|
||||
module.exports = { SideBar };
|
||||
module.exports = { SideBar };
|
||||
|
@@ -1,179 +1,219 @@
|
||||
<style>
|
||||
body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#content {
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="hlScriptContainer"></div>
|
||||
<div id="content" ondragstart="return false;" ondrop="return false;"></div>
|
||||
|
||||
<script>
|
||||
const { ipcRenderer } = require('electron');
|
||||
const contentElement = document.getElementById('content');
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Handle dynamically loading HLJS when a code element is present
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
let hljsScriptAdded = false;
|
||||
let hljsLoaded = false;
|
||||
|
||||
function loadHljs(callback) {
|
||||
hljsScriptAdded = true;
|
||||
|
||||
const script = document.createElement('script');
|
||||
script.onload = function () {
|
||||
hljsLoaded = true;
|
||||
applyHljs();
|
||||
};
|
||||
script.src = 'highlight/highlight.pack.js';
|
||||
document.getElementById('hlScriptContainer').appendChild(script);
|
||||
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
// https://ace.c9.io/build/kitchen-sink.html
|
||||
// https://highlightjs.org/static/demo/
|
||||
link.href = 'highlight/styles/atom-one-light.css';
|
||||
document.getElementById('hlScriptContainer').appendChild(link);
|
||||
}
|
||||
|
||||
function loadAndApplyHljs() {
|
||||
var codeElements = document.getElementsByClassName('code');
|
||||
if (!codeElements.length) return;
|
||||
|
||||
if (!hljsScriptAdded) {
|
||||
this.loadHljs();
|
||||
return;
|
||||
}
|
||||
|
||||
// If HLJS is not loaded yet, no need to do anything. When it loads
|
||||
// it will automatically apply the style to all the code elements.
|
||||
if (hljsLoaded) applyHljs(codeElements);
|
||||
}
|
||||
|
||||
function applyHljs(codeElements) {
|
||||
if (typeof codeElements === 'undefined') codeElements = document.getElementsByClassName('code');
|
||||
|
||||
for (var i = 0; i < codeElements.length; i++) {
|
||||
hljs.highlightBlock(codeElements[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// / Handle dynamically loading HLJS when a code element is present
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
// Note: the scroll position source of truth is "percentScroll_". This is easier to manage than scrollTop because
|
||||
// the scrollTop value depends on the images being loaded or not. For example, if the scrollTop is saved while
|
||||
// images are being displayed then restored while images are being reloaded, the new scrollTop might be changed
|
||||
// so that it is not greater than contentHeight. On the other hand, with percentScroll it is possible to restore
|
||||
// it at any time knowing that it's not going to be changed because the content height has changed.
|
||||
// To restore percentScroll the "checkScrollIID" interval is used. It constantly resets the scroll position during
|
||||
// one second after the content has been updated.
|
||||
//
|
||||
// ignoreNextScroll is used to differentiate between scroll event from the users and those that are the result
|
||||
// of programmatically changing scrollTop. We only want to respond to events initiated by the user.
|
||||
|
||||
let percentScroll_ = 0;
|
||||
let checkScrollIID_ = null;
|
||||
|
||||
function setPercentScroll(percent) {
|
||||
percentScroll_ = percent;
|
||||
contentElement.scrollTop = percentScroll_ * maxScrollTop();
|
||||
}
|
||||
|
||||
function percentScroll() {
|
||||
return percentScroll_;
|
||||
}
|
||||
|
||||
function restorePercentScroll() {
|
||||
setPercentScroll(percentScroll_);
|
||||
}
|
||||
|
||||
ipcRenderer.on('setHtml', (event, html) => {
|
||||
contentElement.innerHTML = html;
|
||||
|
||||
loadAndApplyHljs();
|
||||
|
||||
// Remove the bullet from "ul" for checkbox lists and extra padding
|
||||
const checkboxes = document.getElementsByClassName('checkbox');
|
||||
for (let i = 0; i < checkboxes.length; i++) {
|
||||
const cb = checkboxes[i];
|
||||
const ul = cb.parentElement.parentElement;
|
||||
if (!ul) {
|
||||
console.warn('Unexpected layout for checkbox');
|
||||
continue;
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
overflow: hidden;
|
||||
}
|
||||
ul.style.listStyleType = 'none';
|
||||
ul.style.paddingLeft = 0;
|
||||
|
||||
#content {
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.katex { font-size: 1.3em; } /* This controls the global Katex font size*/
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body id="body">
|
||||
<div id="hlScriptContainer"></div>
|
||||
<div id="content" ondragstart="return false;" ondrop="return false;"></div>
|
||||
|
||||
<script>
|
||||
const { ipcRenderer } = require('electron');
|
||||
const contentElement = document.getElementById('content');
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Handle dynamically loading HLJS when a code element is present
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
let hljsScriptAdded = false;
|
||||
let hljsLoaded = false;
|
||||
|
||||
function loadHljs(callback) {
|
||||
hljsScriptAdded = true;
|
||||
|
||||
const script = document.createElement('script');
|
||||
script.onload = function () {
|
||||
hljsLoaded = true;
|
||||
applyHljs();
|
||||
};
|
||||
script.src = 'highlight/highlight.pack.js';
|
||||
document.getElementById('hlScriptContainer').appendChild(script);
|
||||
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
// https://ace.c9.io/build/kitchen-sink.html
|
||||
// https://highlightjs.org/static/demo/
|
||||
link.href = 'highlight/styles/atom-one-light.css';
|
||||
document.getElementById('hlScriptContainer').appendChild(link);
|
||||
}
|
||||
|
||||
let previousContentHeight = contentElement.scrollHeight;
|
||||
let startTime = Date.now();
|
||||
ignoreNextScrollEvent = true;
|
||||
restorePercentScroll();
|
||||
function loadAndApplyHljs() {
|
||||
var codeElements = document.getElementsByClassName('code');
|
||||
if (!codeElements.length) return;
|
||||
|
||||
if (!checkScrollIID_) {
|
||||
checkScrollIID_ = setInterval(() => {
|
||||
const h = contentElement.scrollHeight;
|
||||
if (h !== previousContentHeight) {
|
||||
previousContentHeight = h;
|
||||
ignoreNextScrollEvent = true;
|
||||
restorePercentScroll();
|
||||
if (!hljsScriptAdded) {
|
||||
this.loadHljs();
|
||||
return;
|
||||
}
|
||||
|
||||
// If HLJS is not loaded yet, no need to do anything. When it loads
|
||||
// it will automatically apply the style to all the code elements.
|
||||
if (hljsLoaded) applyHljs(codeElements);
|
||||
}
|
||||
|
||||
function applyHljs(codeElements) {
|
||||
if (typeof codeElements === 'undefined') codeElements = document.getElementsByClassName('code');
|
||||
|
||||
for (var i = 0; i < codeElements.length; i++) {
|
||||
try {
|
||||
hljs.highlightBlock(codeElements[i]);
|
||||
} catch (error) {
|
||||
console.warn('Cannot highlight code', error);
|
||||
}
|
||||
if (Date.now() - startTime >= 1000) {
|
||||
clearInterval(checkScrollIID_);
|
||||
checkScrollIID_ = null;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// / Handle dynamically loading HLJS when a code element is present
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
// Note: the scroll position source of truth is "percentScroll_". This is easier to manage than scrollTop because
|
||||
// the scrollTop value depends on the images being loaded or not. For example, if the scrollTop is saved while
|
||||
// images are being displayed then restored while images are being reloaded, the new scrollTop might be changed
|
||||
// so that it is not greater than contentHeight. On the other hand, with percentScroll it is possible to restore
|
||||
// it at any time knowing that it's not going to be changed because the content height has changed.
|
||||
// To restore percentScroll the "checkScrollIID" interval is used. It constantly resets the scroll position during
|
||||
// one second after the content has been updated.
|
||||
//
|
||||
// ignoreNextScroll is used to differentiate between scroll event from the users and those that are the result
|
||||
// of programmatically changing scrollTop. We only want to respond to events initiated by the user.
|
||||
|
||||
let percentScroll_ = 0;
|
||||
let checkScrollIID_ = null;
|
||||
|
||||
function setPercentScroll(percent) {
|
||||
percentScroll_ = percent;
|
||||
contentElement.scrollTop = percentScroll_ * maxScrollTop();
|
||||
}
|
||||
|
||||
function percentScroll() {
|
||||
return percentScroll_;
|
||||
}
|
||||
|
||||
function restorePercentScroll() {
|
||||
setPercentScroll(percentScroll_);
|
||||
}
|
||||
|
||||
ipcRenderer.on('setHtml', (event, html) => {
|
||||
updateBodyHeight();
|
||||
|
||||
contentElement.innerHTML = html;
|
||||
|
||||
loadAndApplyHljs();
|
||||
|
||||
// Remove the bullet from "ul" for checkbox lists and extra padding
|
||||
const checkboxes = document.getElementsByClassName('checkbox');
|
||||
for (let i = 0; i < checkboxes.length; i++) {
|
||||
const cb = checkboxes[i];
|
||||
const ul = cb.parentElement.parentElement;
|
||||
if (!ul) {
|
||||
console.warn('Unexpected layout for checkbox');
|
||||
continue;
|
||||
}
|
||||
}, 1);
|
||||
}
|
||||
});
|
||||
ul.style.listStyleType = 'none';
|
||||
ul.style.paddingLeft = 0;
|
||||
}
|
||||
|
||||
let ignoreNextScrollEvent = false;
|
||||
ipcRenderer.on('setPercentScroll', (event, percent) => {
|
||||
if (checkScrollIID_) {
|
||||
clearInterval(checkScrollIID_);
|
||||
checkScrollIID_ = null;
|
||||
let previousContentHeight = contentElement.scrollHeight;
|
||||
let startTime = Date.now();
|
||||
ignoreNextScrollEvent = true;
|
||||
restorePercentScroll();
|
||||
|
||||
if (!checkScrollIID_) {
|
||||
checkScrollIID_ = setInterval(() => {
|
||||
const h = contentElement.scrollHeight;
|
||||
if (h !== previousContentHeight) {
|
||||
previousContentHeight = h;
|
||||
ignoreNextScrollEvent = true;
|
||||
restorePercentScroll();
|
||||
}
|
||||
if (Date.now() - startTime >= 1000) {
|
||||
clearInterval(checkScrollIID_);
|
||||
checkScrollIID_ = null;
|
||||
}
|
||||
}, 1);
|
||||
}
|
||||
});
|
||||
|
||||
let ignoreNextScrollEvent = false;
|
||||
ipcRenderer.on('setPercentScroll', (event, percent) => {
|
||||
if (checkScrollIID_) {
|
||||
clearInterval(checkScrollIID_);
|
||||
checkScrollIID_ = null;
|
||||
}
|
||||
|
||||
ignoreNextScrollEvent = true;
|
||||
setPercentScroll(percent);
|
||||
});
|
||||
|
||||
function maxScrollTop() {
|
||||
return Math.max(0, contentElement.scrollHeight - contentElement.clientHeight);
|
||||
}
|
||||
|
||||
ignoreNextScrollEvent = true;
|
||||
setPercentScroll(percent);
|
||||
});
|
||||
|
||||
function maxScrollTop() {
|
||||
return Math.max(0, contentElement.scrollHeight - contentElement.clientHeight);
|
||||
}
|
||||
|
||||
contentElement.addEventListener('scroll', function(e) {
|
||||
if (ignoreNextScrollEvent) {
|
||||
ignoreNextScrollEvent = false;
|
||||
return;
|
||||
// The body element needs to have a fixed height for the content to be scrollable
|
||||
function updateBodyHeight() {
|
||||
document.getElementById('body').style.height = window.innerHeight + 'px';
|
||||
}
|
||||
const m = maxScrollTop();
|
||||
const percent = m ? contentElement.scrollTop / m : 0;
|
||||
setPercentScroll(percent);
|
||||
ipcRenderer.sendToHost('percentScroll', percent);
|
||||
});
|
||||
|
||||
// Disable drag and drop otherwise it's possible to drop a URL
|
||||
// on it and it will open in the view as a website.
|
||||
document.addEventListener('drop', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
document.addEventListener('dragover', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
document.addEventListener('dragover', function(e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
</script>
|
||||
contentElement.addEventListener('scroll', function(e) {
|
||||
if (ignoreNextScrollEvent) {
|
||||
ignoreNextScrollEvent = false;
|
||||
return;
|
||||
}
|
||||
const m = maxScrollTop();
|
||||
const percent = m ? contentElement.scrollTop / m : 0;
|
||||
setPercentScroll(percent);
|
||||
ipcRenderer.sendToHost('percentScroll', percent);
|
||||
});
|
||||
|
||||
document.addEventListener('contextmenu', function(event) {
|
||||
let element = event.target;
|
||||
|
||||
// To handle right clicks on resource icons
|
||||
if (element && !element.getAttribute('data-resource-id')) element = element.parentElement;
|
||||
|
||||
if (element && element.getAttribute('data-resource-id')) {
|
||||
ipcRenderer.sendToHost('contextMenu', {
|
||||
type: element.getAttribute('src') ? 'image' : 'link',
|
||||
resourceId: element.getAttribute('data-resource-id'),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Disable drag and drop otherwise it's possible to drop a URL
|
||||
// on it and it will open in the view as a website.
|
||||
document.addEventListener('drop', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
document.addEventListener('dragover', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
document.addEventListener('dragover', function(e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
window.addEventListener('resize', function() {
|
||||
updateBodyHeight();
|
||||
});
|
||||
|
||||
updateBodyHeight();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
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
1
ElectronClient/app/locales/eu.json
Normal file
1
ElectronClient/app/locales/eu.json
Normal file
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
@@ -1,12 +1,13 @@
|
||||
var locales = {};
|
||||
locales['en_GB'] = require('./en_GB.json');
|
||||
locales['de_DE'] = require('./de_DE.json');
|
||||
locales['es_CR'] = require('./es_CR.json');
|
||||
locales['es_ES'] = require('./es_ES.json');
|
||||
locales['eu'] = require('./eu.json');
|
||||
locales['fr_FR'] = require('./fr_FR.json');
|
||||
locales['hr_HR'] = require('./hr_HR.json');
|
||||
locales['it_IT'] = require('./it_IT.json');
|
||||
locales['ja_JP'] = require('./ja_JP.json');
|
||||
locales['nl_BE'] = require('./nl_BE.json');
|
||||
locales['pt_BR'] = require('./pt_BR.json');
|
||||
locales['ru_RU'] = require('./ru_RU.json');
|
||||
locales['zh_CN'] = require('./zh_CN.json');
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
ElectronClient/app/locales/nl_BE.json
Normal file
1
ElectronClient/app/locales/nl_BE.json
Normal file
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
@@ -3,6 +3,15 @@
|
||||
// Make it possible to require("/lib/...") without specifying full path
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
// Disable React message in console "Download the React DevTools for a better development experience"
|
||||
// https://stackoverflow.com/questions/42196819/disable-hide-download-the-react-devtools#42196820
|
||||
__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
|
||||
supportsFiber: true,
|
||||
inject: function() {},
|
||||
onCommitFiberRoot: function() {},
|
||||
onCommitFiberUnmount: function() {},
|
||||
};
|
||||
|
||||
const { app } = require('./app.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Resource = require('lib/models/Resource.js');
|
||||
@@ -17,11 +26,13 @@ const { FsDriverNode } = require('lib/fs-driver-node.js');
|
||||
const { shimInit } = require('lib/shim-init-node.js');
|
||||
const EncryptionService = require('lib/services/EncryptionService');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
const { FileApiDriverLocal } = require('lib/file-api-driver-local.js');
|
||||
|
||||
const fsDriver = new FsDriverNode();
|
||||
Logger.fsDriver_ = fsDriver;
|
||||
Resource.fsDriver_ = fsDriver;
|
||||
EncryptionService.fsDriver_ = fsDriver;
|
||||
FileApiDriverLocal.fsDriver_ = fsDriver;
|
||||
|
||||
// That's not good, but it's to avoid circular dependency issues
|
||||
// in the BaseItem class.
|
||||
@@ -53,11 +64,17 @@ document.addEventListener('click', (event) => event.preventDefault());
|
||||
app().start(bridge().processArgv()).then(() => {
|
||||
require('./gui/Root.min.js');
|
||||
}).catch((error) => {
|
||||
// If something goes wrong at this stage we don't have a console or a log file
|
||||
// so display the error in a message box.
|
||||
let msg = ['Fatal error:', error.message];
|
||||
if (error.fileName) msg.push(error.fileName);
|
||||
if (error.lineNumber) msg.push(error.lineNumber);
|
||||
if (error.stack) msg.push(error.stack);
|
||||
bridge().showErrorMessageBox(msg.join('\n'));
|
||||
if (error.code == 'flagError') {
|
||||
bridge().showErrorMessageBox(error.message);
|
||||
} else {
|
||||
// If something goes wrong at this stage we don't have a console or a log file
|
||||
// so display the error in a message box.
|
||||
let msg = ['Fatal error:', error.message];
|
||||
if (error.fileName) msg.push(error.fileName);
|
||||
if (error.lineNumber) msg.push(error.lineNumber);
|
||||
if (error.stack) msg.push(error.stack);
|
||||
bridge().showErrorMessageBox(msg.join('\n\n'));
|
||||
}
|
||||
|
||||
bridge().electronApp().exit(1);
|
||||
});
|
2235
ElectronClient/app/package-lock.json
generated
2235
ElectronClient/app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Joplin",
|
||||
"version": "0.10.47",
|
||||
"version": "1.0.73",
|
||||
"description": "Joplin for Desktop",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
@@ -8,8 +8,8 @@
|
||||
"pack": "node_modules/.bin/electron-builder --dir",
|
||||
"dist": "node_modules/.bin/electron-builder",
|
||||
"publish": "build -p always",
|
||||
"postinstall": "node compile-jsx.js && node compile-package-info.js",
|
||||
"compile": "node compile-jsx.js && node compile-package-info.js"
|
||||
"postinstall": "node compile-jsx.js && node compile-package-info.js && node ../../Tools/copycss.js --copy-fonts",
|
||||
"compile": "node compile-jsx.js && node compile-package-info.js && node ../../Tools/copycss.js --copy-fonts"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -22,6 +22,9 @@
|
||||
},
|
||||
"build": {
|
||||
"appId": "net.cozic.joplin-desktop",
|
||||
"extraResources": [
|
||||
"build/icons/*"
|
||||
],
|
||||
"win": {
|
||||
"icon": "../../Assets/Joplin.ico"
|
||||
},
|
||||
@@ -34,15 +37,18 @@
|
||||
"asar": false
|
||||
},
|
||||
"linux": {
|
||||
"asar": false
|
||||
"asar": false,
|
||||
"category": "Office"
|
||||
}
|
||||
},
|
||||
"homepage": "https://github.com/laurent22/joplin#readme",
|
||||
"devDependencies": {
|
||||
"ajv": "^6.2.0",
|
||||
"app-builder-bin": "^1.5.0",
|
||||
"babel-cli": "^6.26.0",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"electron": "^1.7.9",
|
||||
"electron-builder": "^19.45.4"
|
||||
"electron": "^1.7.11",
|
||||
"electron-builder": "^20.2.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"7zip-bin-mac": "^1.0.1",
|
||||
@@ -51,9 +57,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"app-module-path": "^2.2.0",
|
||||
"async-mutex": "^0.1.3",
|
||||
"base-64": "^0.1.0",
|
||||
"compare-versions": "^3.1.0",
|
||||
"electron-context-menu": "^0.9.1",
|
||||
"electron-log": "^2.2.11",
|
||||
"electron-updater": "^2.16.1",
|
||||
"electron-window-state": "^4.1.1",
|
||||
"follow-redirects": "^1.2.5",
|
||||
"form-data": "^2.3.1",
|
||||
@@ -61,9 +68,11 @@
|
||||
"highlight.js": "^9.12.0",
|
||||
"html-entities": "^1.2.1",
|
||||
"jssha": "^2.3.1",
|
||||
"katex": "^0.9.0-beta1",
|
||||
"levenshtein": "^1.0.5",
|
||||
"lodash": "^4.17.4",
|
||||
"markdown-it": "^8.4.0",
|
||||
"markdown-it-katex": "^2.0.3",
|
||||
"md5": "^2.2.1",
|
||||
"mime": "^2.0.3",
|
||||
"moment": "^2.19.1",
|
||||
@@ -77,13 +86,16 @@
|
||||
"react-dom": "^16.0.0",
|
||||
"react-redux": "^5.0.6",
|
||||
"redux": "^3.7.2",
|
||||
"sharp": "^0.18.4",
|
||||
"sharp": "^0.17.3",
|
||||
"smalltalk": "^2.5.1",
|
||||
"sprintf-js": "^1.1.1",
|
||||
"sqlite3": "^3.1.13",
|
||||
"string-padding": "^1.0.2",
|
||||
"string-to-stream": "^1.1.0",
|
||||
"tar": "^4.4.0",
|
||||
"tcp-port-used": "^0.1.2",
|
||||
"uuid": "^3.1.0"
|
||||
"url-parse": "^1.2.0",
|
||||
"uuid": "^3.1.0",
|
||||
"xml2js": "^0.4.19"
|
||||
}
|
||||
}
|
||||
|
@@ -1,12 +1,13 @@
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
|
||||
const globalStyle = {
|
||||
fontSize: 12,
|
||||
fontSize: 12 * Setting.value('style.zoom')/100,
|
||||
fontFamily: 'sans-serif',
|
||||
margin: 15, // No text and no interactive component should be within this margin
|
||||
itemMarginTop: 10,
|
||||
itemMarginBottom: 10,
|
||||
backgroundColor: "#ffffff",
|
||||
backgroundColorTransparent: 'rgba(255,255,255,0.9)',
|
||||
oddBackgroundColor: "#dddddd",
|
||||
color: "#222222", // For regular text
|
||||
colorError: "red",
|
||||
|
@@ -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);
|
||||
@@ -86,6 +90,8 @@ async function main(argv) {
|
||||
if (linuxUrl) content = content.replace(/(https:\/\/github.com\/laurent22\/joplin\/releases\/download\/.*?\.AppImage)/, linuxUrl);
|
||||
|
||||
setReadmeContent(content);
|
||||
|
||||
console.info("git pull && git add -A && git commit -m 'Update readme downloads' && git push")
|
||||
}
|
||||
|
||||
main(process.argv).catch((error) => {
|
||||
|
20
LICENSE
20
LICENSE
@@ -1,7 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016-2018 Laurent Cozic
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@@ -1,7 +0,0 @@
|
||||
Copyright (c) 2016-2018 Laurent Cozic
|
||||
|
||||
L'autorisation est accordée, gracieusement, à toute personne acquérant une copie de ce logiciel et des fichiers de documentation associés (le "Logiciel"), de commercialiser le Logiciel sans restriction, notamment les droits d'utiliser, de copier, de modifier, de fusionner, de publier, de distribuer, de sous-licencier et/ou de vendre des copies du Logiciel, ainsi que d'autoriser les personnes auxquelles le Logiciel est fourni à le faire, sous réserve des conditions suivantes :
|
||||
|
||||
La déclaration de copyright ci-dessus et la présente autorisation doivent être incluses dans toutes copies ou parties substantielles du Logiciel.
|
||||
|
||||
LE LOGICIEL EST FOURNI "TEL QUEL", SANS GARANTIE D'AUCUNE SORTE, EXPLICITE OU IMPLICITE, NOTAMMENT SANS GARANTIE DE QUALITÉ MARCHANDE, D’ADÉQUATION À UN USAGE PARTICULIER ET D'ABSENCE DE CONTREFAÇON. EN AUCUN CAS, LES AUTEURS OU TITULAIRES DU DROIT D'AUTEUR NE SERONT RESPONSABLES DE TOUT DOMMAGE, RÉCLAMATION OU AUTRE RESPONSABILITÉ, QUE CE SOIT DANS LE CADRE D'UN CONTRAT, D'UN DÉLIT OU AUTRE, EN PROVENANCE DE, CONSÉCUTIF À OU EN RELATION AVEC LE LOGICIEL OU SON UTILISATION, OU AVEC D'AUTRES ÉLÉMENTS DU LOGICIEL.
|
180
README.md
180
README.md
@@ -1,12 +1,12 @@
|
||||
# Joplin
|
||||
|
||||
Joplin is a free, open source note taking and to-do application, which can handle a large number of notes organised into notebooks. The notes are searchable, can be copied, tagged and modified either from the applications directly or from your own text editor. The notes are in [Markdown format](https://daringfireball.net/projects/markdown/basics).
|
||||
Joplin is a free, open source note taking and to-do application, which can handle a large number of notes organised into notebooks. The notes are searchable, can be copied, tagged and modified either from the applications directly or from your own text editor. The notes are in [Markdown format](#markdown).
|
||||
|
||||
Notes exported from Evernote via .enex files [can be imported](#importing-notes-from-evernote) into Joplin, including the formatted content (which is converted to Markdown), resources (images, attachments, etc.) and complete metadata (geolocation, updated time, created time, etc.).
|
||||
Notes exported from Evernote via .enex files [can be imported](#importing) into Joplin, including the formatted content (which is converted to Markdown), resources (images, attachments, etc.) and complete metadata (geolocation, updated time, created time, etc.). Plain Markdown files can also be imported.
|
||||
|
||||
The notes can be [synchronised](#synchronisation) with various targets including 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.
|
||||
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,16 +18,16 @@ 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.43/Joplin-Setup-0.10.43.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.43/Joplin-0.10.43.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.43/Joplin-0.10.43-x86_64.AppImage'><img alt='Get it on macOS' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeLinux.png'/></a>
|
||||
Windows | <a href='https://github.com/laurent22/joplin/releases/download/v1.0.70/Joplin-Setup-1.0.70.exe'><img alt='Get it on Windows' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeWindows.png'/></a>
|
||||
macOS | <a href='https://github.com/laurent22/joplin/releases/download/v1.0.70/Joplin-1.0.70.dmg'><img alt='Get it on macOS' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeMacOS.png'/></a>
|
||||
Linux | <a href='https://github.com/laurent22/joplin/releases/download/v1.0.70/Joplin-1.0.70-x86_64.AppImage'><img alt='Get it on Linux' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeLinux.png'/></a>
|
||||
|
||||
## Mobile applications
|
||||
|
||||
Operating System | 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>
|
||||
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>
|
||||
Operating System | Download | Alt. Download
|
||||
-----------------|----------|----------------
|
||||
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeAndroid.png'/></a> | or [Download APK File](https://github.com/laurent22/joplin-android/releases/download/android-v1.0.107/joplin-v1.0.107.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,18 +51,23 @@ For usage information, please refer to the full [Joplin Terminal Application Doc
|
||||
# Features
|
||||
|
||||
- Desktop, mobile and terminal applications.
|
||||
- End To End Encryption (E2EE)
|
||||
- Synchronisation with various services, including NextCloud, WebDAV and OneDrive. Dropbox is planned.
|
||||
- Import Enex files (Evernote export format) and Markdown files.
|
||||
- Export JEX files (Joplin Export format) and raw files.
|
||||
- Support notes, to-dos, tags and notebooks.
|
||||
- Sort notes by multiple criteria - title, updated time, etc.
|
||||
- Support for alarms (notifications) in mobile and desktop applications.
|
||||
- Offline first, so the entire data is always available on the device even without an internet connection.
|
||||
- Ability to synchronise with multiple targets, including the file system and OneDrive (NextCloud and Dropbox are planned).
|
||||
- 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
|
||||
|
||||
# Importing notes from Evernote
|
||||
# Importing
|
||||
|
||||
## Importing from Evernote
|
||||
|
||||
Joplin was designed as a replacement for Evernote and so can import complete Evernote notebooks, as well as notes, tags, resources (attached files) and note metadata (such as author, geo-location, etc.) via ENEX files. In terms of data, the only two things that might slightly differ are:
|
||||
|
||||
@@ -72,29 +77,77 @@ Joplin was designed as a replacement for Evernote and so can import complete Eve
|
||||
|
||||
To import Evernote data, first export your Evernote notebooks to ENEX files as described [here](https://help.evernote.com/hc/en-us/articles/209005557-How-to-back-up-export-and-restore-import-notes-and-notebooks). Then follow these steps:
|
||||
|
||||
On the **desktop application**, open the "File" menu, click "Import Evernote notes" and select your ENEX file. This will open a new screen which will display the import progress. The notes will be imported into a new separate notebook (so that, in case of a mistake, the notes are not mixed up with any existing notes). If needed then can then be moved to a different notebook, or the notebook can be renamed, etc.
|
||||
On the **desktop application**, open File > Import > ENEX and select your file. The notes will be imported into a new separate notebook. If needed they can then be moved to a different notebook, or the notebook can be renamed, etc.
|
||||
|
||||
On the **terminal application**, in [command-line mode](/terminal#command-line-mode), type `import-enex /path/to/file.enex`. This will import the notes into a new notebook named after the filename.
|
||||
On the **terminal application**, in [command-line mode](/terminal#command-line-mode), type `import /path/to/file.enex`. This will import the notes into a new notebook named after the filename.
|
||||
|
||||
# Importing notes from other applications
|
||||
## Importing from Markdown files
|
||||
|
||||
Joplin can import notes from plain Markdown file. You can either import a complete directory of Markdown files or individual files.
|
||||
|
||||
On the **desktop application**, open File > Import > MD and select your Markdown file or directory.
|
||||
|
||||
On the **terminal application**, in [command-line mode](/terminal#command-line-mode), type `import --format md /path/to/file.md` or `import --format md /path/to/directory/`.
|
||||
|
||||
## Importing from other applications
|
||||
|
||||
In general the way to import notes from any application into Joplin is to convert the notes to ENEX files (Evernote format) and to import these ENEX files into Joplin using the method above. Most note-taking applications support ENEX files so it should be relatively straightforward. For help about specific applications, see below:
|
||||
|
||||
* Standard Notes: Please see [this tutorial](https://programadorwebvalencia.com/migrate-notes-from-standard-notes-to-joplin/)
|
||||
* Tomboy Notes: Export the notes to ENEX files [as described here](https://askubuntu.com/questions/243691/how-can-i-export-my-tomboy-notes-into-evernote/608551) for example, and import these ENEX files into Joplin.
|
||||
|
||||
# Exporting
|
||||
|
||||
Joplin can export to the JEX format (Joplin Export file), which is a tar file that can contain multiple notes, notebooks, etc. This is a lossless format in that all the notes, but also metadata such as geo-location, updated time, tags, etc. are preserved. This format is convenient for backup purposes and can be re-imported into Joplin. A "raw" format is also available. This is the same as the JEX format except that the data is saved to a directory and each item represented by a single file.
|
||||
|
||||
# Synchronisation
|
||||
|
||||
One of the goals of Joplin was to avoid being tied to any particular company or service, whether it is Evernote, Google or Microsoft. As such the synchronisation is designed without any hard dependency to any particular service. Most of the synchronisation process is done at an abstract level and access to external services, such as OneDrive or Dropbox, 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.
|
||||
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.
|
||||
|
||||
Currently, synchronisation is possible with OneDrive (by default) or the local filesystem. A NextCloud driver, and a Dropbox one will also be available once [this React Native bug](https://github.com/facebook/react-native/issues/14445) is fixed. 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.
|
||||
Currently, synchronisation is possible with Nextcloud and OneDrive (by default) or the local filesystem. A Dropbox one will also be available once [this React Native bug](https://github.com/facebook/react-native/issues/14445) 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 "Synchronise" to start a synchronisation manually.
|
||||
|
||||
On the **desktop application**, to initiate the synchronisation process, click on the "Synchronise" 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). After that, the application will synchronise in the background whenever it is running, or you can click on "Synchronise" to start a synchronisation manually.
|
||||
## Nextcloud synchronisation
|
||||
|
||||
On the **terminal application**, to initiate the synchronisation process, type `:sync`. You will be asked to follow a link to authorise the application (simply input your Microsoft credentials - you do not need to register with OneDrive). After that, the application will synchronise in the background whenever it is running. It is possible to also synchronise outside of the user interface by typing `joplin sync` from the terminal. This can be used to setup a cron script to synchronise at regular interval. For example, this would do it every 30 minutes:
|
||||
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:
|
||||
|
||||
:config sync.5.path https://example.com/nextcloud/remote.php/dav/files/USERNAME/Joplin
|
||||
:config sync.5.username YOUR_USERNAME
|
||||
:config sync.5.password YOUR_PASSWORD
|
||||
:config sync.target 5
|
||||
|
||||
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.
|
||||
|
||||
## WebDAV synchronisation
|
||||
|
||||
Select the "WebDAV" synchronisation target and follow the same instructions as for Nextcloud above.
|
||||
|
||||
WebDAV-compatible services that are known to work with Joplin:
|
||||
|
||||
- [Box.com](https://www.box.com/)
|
||||
- [DriveHQ](https://www.drivehq.com)
|
||||
- [OwnCloud](https://owncloud.org/)
|
||||
- [Seafile](https://www.seafile.com/)
|
||||
- [Stack](https://www.transip.nl/stack/)
|
||||
- [Zimbra](https://www.zimbra.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.
|
||||
|
||||
On the **desktop application** or **mobile application**, select "OneDrive" as the synchronisation target in the config screen (it is selected by default). Then, to initiate the synchronisation process, click on the "Synchronise" 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).
|
||||
|
||||
On the **terminal application**, to initiate the synchronisation process, type `:sync`. You will be asked to follow a link to authorise the application (simply input your Microsoft credentials - you do not need to register with OneDrive). It is possible to also synchronise outside of the user interface by typing `joplin sync` from the terminal. This can be used to setup a cron script to synchronise at regular interval. For example, this would do it every 30 minutes:
|
||||
|
||||
*/30 * * * * /path/to/joplin sync
|
||||
|
||||
# Encryption
|
||||
|
||||
Joplin supports end-to-end encryption (E2EE) on all the applications. E2EE is a system where only the owner of the notes, notebooks, tags or resources can read them. It prevents potential eavesdroppers - including telecom providers, internet providers, and even the developers of Joplin from being able to access the data. Please see the [End-To-End Encryption Tutorial](http://joplin.cozic.net/help/e2ee) for more information about this feature and how to enable it.
|
||||
|
||||
For a more technical description, mostly relevant for development or to review the method being used, please see the [Encryption specification](http://joplin.cozic.net/help/spec).
|
||||
|
||||
# Attachments / Resources
|
||||
|
||||
Any kind of file can be attached to a note. In Markdown, links to these files are represented as a simple ID to the resource. In the note viewer, these files, if they are images, will be displayed or, if they are other files (PDF, text files, etc.) they will be displayed as links. Clicking on this link will open the file in the default application.
|
||||
@@ -107,15 +160,59 @@ On the desktop and mobile apps, an alarm can be associated with any to-do. It wi
|
||||
- **macOS**: >= 10.8 or Growl if earlier.
|
||||
- **Linux**: `notify-osd` or `libnotify-bin` installed (Ubuntu should have this by default). Growl otherwise
|
||||
|
||||
See [documentation and flow chart for reporter choice](./DECISION_FLOW.md)
|
||||
See [documentation and flow chart for reporter choice](https://github.com/mikaelbr/node-notifier/blob/master/DECISION_FLOW.md)
|
||||
|
||||
On mobile, the alarms will be displayed using the built-in notification system.
|
||||
|
||||
If for any reason the notifications do not work, please [open an issue](https://github.com/laurent22/joplin/issues).
|
||||
|
||||
# Markdown
|
||||
|
||||
Joplin uses and renders [Github-flavoured Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) with a few variations and additions. In particular:
|
||||
|
||||
## Math notation
|
||||
|
||||
Math expressions can be added using the [Katex notation](https://khan.github.io/KaTeX/). To add an inline equation, wrap the expression in `$EXPRESSION$`, eg. `$\sqrt{3x-1}+(1+x)^2$`. To create an expression block, wrap it as follow:
|
||||
|
||||
$$
|
||||
EXPRESSION
|
||||
$$
|
||||
|
||||
For example:
|
||||
|
||||
$$
|
||||
f(x) = \int_{-\infty}^\infty
|
||||
\hat f(\xi)\,e^{2 \pi i \xi x}
|
||||
\,d\xi
|
||||
$$
|
||||
|
||||
Here is an example with the Markdown and rendered result side by side:
|
||||
|
||||
<img src="https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/Katex.png" style="max-width: 100%; max-height: 35em;">
|
||||
|
||||
## Checkboxes
|
||||
|
||||
Checkboxes can be added like so:
|
||||
|
||||
- [ ] Milk
|
||||
- [ ] Rice
|
||||
- [ ] Eggs
|
||||
|
||||
The checkboxes can then be ticked in the mobile and desktop applications.
|
||||
|
||||
# Donations
|
||||
|
||||
Donations to Joplin support the development of the project. Developing quality applications mostly takes time, but there are also some expenses, such as digital certificates to sign the applications, app store fees, hosting, etc. Most of all, your donation will make it possible to keep up the current development standard.
|
||||
|
||||
Please see the [donation page](http://joplin.cozic.net/donate/) for information on how to support the development of Joplin.
|
||||
|
||||
# Contributing
|
||||
|
||||
Please see the guide for information on how to contribute to the development of Joplin: https://github.com/laurent22/joplin/blob/master/CONTRIBUTING.md
|
||||
|
||||
# Localisation
|
||||
|
||||
Joplin is currently available in English, French, Spanish, German, Portuguese, Chinese, Japanese, Russian, Croatian and Italian. If you would like to contribute a translation, it is quite straightforward, please follow these steps:
|
||||
Joplin is currently available in the languages below. If you would like to contribute a **new translation**, it is quite straightforward, please follow these steps:
|
||||
|
||||
- [Download Poedit](https://poedit.net/), the translation editor, and install it.
|
||||
- [Download the file to be translated](https://raw.githubusercontent.com/laurent22/joplin/master/CliClient/locales/joplin.pot).
|
||||
@@ -124,27 +221,36 @@ Joplin is currently available in English, French, Spanish, German, Portuguese, C
|
||||
|
||||
This translation will apply to the three applications - desktop, mobile and terminal.
|
||||
|
||||
# Contributing
|
||||
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.
|
||||
|
||||
Please see the guide for information on how to contribute to the development of Joplin: https://github.com/laurent22/joplin/blob/master/CONTRIBUTING.md
|
||||
Current translations:
|
||||
|
||||
# Coming features
|
||||
|
||||
- NextCloud support
|
||||
- All: End to end encryption
|
||||
- Windows: Tray icon
|
||||
- Desktop apps: Tag auto-complete
|
||||
- Desktop apps: Dark theme
|
||||
- Linux: Enable auto-update for desktop app
|
||||
<!-- LOCALE-TABLE-AUTO-GENERATED -->
|
||||
| Language | Po File | Last translator | Percent done
|
||||
---|---|---|---|---
|
||||
 | Basque | [eu](https://github.com/laurent22/joplin/blob/master/CliClient/locales/eu.po) | juan.abasolo@ehu.eus | 80%
|
||||
 | Croatian | [hr_HR](https://github.com/laurent22/joplin/blob/master/CliClient/locales/hr_HR.po) | Hrvoje Mandić <trbuhom@net.hr> | 65%
|
||||
 | Deutsch | [de_DE](https://github.com/laurent22/joplin/blob/master/CliClient/locales/de_DE.po) | Tobias Strobel <git@strobeltobias.de> | 97%
|
||||
 | English | [en_GB](https://github.com/laurent22/joplin/blob/master/CliClient/locales/en_GB.po) | | 100%
|
||||
 | Español | [es_ES](https://github.com/laurent22/joplin/blob/master/CliClient/locales/es_ES.po) | Fernando Martín <f@mrtn.es> | 100%
|
||||
 | Français | [fr_FR](https://github.com/laurent22/joplin/blob/master/CliClient/locales/fr_FR.po) | Laurent Cozic | 100%
|
||||
 | Italiano | [it_IT](https://github.com/laurent22/joplin/blob/master/CliClient/locales/it_IT.po) | | 66%
|
||||
 | Nederlands | [nl_BE](https://github.com/laurent22/joplin/blob/master/CliClient/locales/nl_BE.po) | | 80%
|
||||
 | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/master/CliClient/locales/pt_BR.po) | Renato Nunes Bastos <rnbastos@gmail.com> | 98%
|
||||
 | Русский | [ru_RU](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ru_RU.po) | Artyom Karlov <artyom.karlov@gmail.com> | 100%
|
||||
 | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/master/CliClient/locales/zh_CN.po) | RCJacH <RCJacH@outlook.com> | 66%
|
||||
 | 日本語 | [ja_JP](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ja_JP.po) | | 65%
|
||||
<!-- LOCALE-TABLE-AUTO-GENERATED -->
|
||||
|
||||
# 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
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016-2018 Laurent Cozic
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
@@ -1,368 +0,0 @@
|
||||
# Joplin
|
||||
|
||||
Joplin is a free, open source note taking and to-do application, which can handle a large number of notes organised into notebooks. The notes are searchable, can be copied, tagged and modified with your own text editor.
|
||||
|
||||
Notes exported from Evernote via .enex files [can be imported](#importing-notes-from-evernote) into Joplin, including the formatted content (which is converted to markdown), resources (images, attachments, etc.) and complete metadata (geolocation, updated time, created time, etc.).
|
||||
|
||||
The notes can be [synchronised](#synchronisation) with various targets including the file system (for example with a network directory) or with Microsoft OneDrive. When synchronising the notes, notebooks, tags and other metadata are saved to plain text files which can be easily inspected, backed up and moved around.
|
||||
|
||||
<img src="https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/ScreenshotTerminal.png" style="max-width: 60%">
|
||||
|
||||
# Installation
|
||||
|
||||
On macOS:
|
||||
|
||||
brew install joplin
|
||||
|
||||
On Linux or Windows (via [WSL](https://msdn.microsoft.com/en-us/commandline/wsl/faq?f=255&MSPPError=-2147217396)):
|
||||
|
||||
**Important:** First, [install Node 8+](https://nodejs.org/en/download/package-manager/). Node 8 is LTS but not yet available everywhere so you might need to manually install it.
|
||||
|
||||
NPM_CONFIG_PREFIX=~/.joplin-bin npm install -g joplin
|
||||
sudo ln -s ~/.joplin-bin/bin/joplin /usr/bin/joplin
|
||||
|
||||
By default, the application binary will be installed under `~/.joplin-bin`. You may change this directory if needed. Alternatively, if your npm permissions are setup as described [here](https://docs.npmjs.com/getting-started/fixing-npm-permissions#option-2-change-npms-default-directory-to-another-directory) (Option 2) then simply running `npm -g install joplin` would work.
|
||||
|
||||
To start it, type `joplin`.
|
||||
|
||||
# Demo
|
||||
|
||||
The demo application shows various Wikipedia articles converted to Markdown and organised into notebooks, as well as an example to-do list, in order to test and demonstrate the application. The demo application and its settings will be installed in a separate directory so as not to interfere with any existing Joplin application.
|
||||
|
||||
npm install -g demo-joplin
|
||||
|
||||
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).
|
||||
|
||||
<img src="https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/ScreenshotTerminalCaptions.png" height="450px">
|
||||
|
||||
## Input modes
|
||||
|
||||
Joplin user interface is partly based on the text editor Vim and offers two different modes to interact with the notes and notebooks:
|
||||
|
||||
### 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.
|
||||
|
||||
### 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).
|
||||
|
||||
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:
|
||||
|
||||
Shortcut | Description
|
||||
---------|------------
|
||||
`$n` | Refers to the currently selected note
|
||||
`$b` | Refers to the currently selected notebook
|
||||
`$c` | Refers to the currently selected item. For example, if the note list is current active, `$c` will refer to the currently selected note.
|
||||
|
||||
**Examples:**
|
||||
|
||||
Create a new note with title "Wednesday's meeting":
|
||||
|
||||
mknote "Wednesday's meeting"
|
||||
|
||||
Create a new to-do:
|
||||
|
||||
mktodo "Buy bread"
|
||||
|
||||
Move the currently selected note ($n) to the notebook with title "Personal"
|
||||
|
||||
mv $n "Personal"
|
||||
|
||||
Rename the currently selected notebook ($b) to "Something":
|
||||
|
||||
ren $b "Something"
|
||||
|
||||
Attach a local file to the currently selected note ($n):
|
||||
|
||||
attach $n /home/laurent/pictures/Vacation12.jpg
|
||||
|
||||
The configuration can also be changed from command-line mode. For example, to change the current editor to Sublime Text:
|
||||
|
||||
config editor "subl -w"
|
||||
|
||||
## Editing a note
|
||||
|
||||
To edit a note, select it and press `ENTER`. Or, in command-line mode, type `edit $n` to edit the currently selected note, or `edit "Note title"` to edit a particular note.
|
||||
|
||||
## Getting help
|
||||
|
||||
The complete usage information is available from command-line mode, by typing one of these commands:
|
||||
|
||||
Command | Description
|
||||
--------|-------------------
|
||||
`help` | General help information
|
||||
`help shortcuts` | 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.
|
||||
|
||||
# Importing notes from Evernote
|
||||
|
||||
To import Evernote data, follow these steps:
|
||||
|
||||
* First, export your Evernote notebooks to ENEX files as described [here](https://help.evernote.com/hc/en-us/articles/209005557-How-to-back-up-export-and-restore-import-notes-and-notebooks).
|
||||
* In Joplin, in [command-line mode](#command-line-mode), type `import-enex /path/to/file.enex`. This will import the notes into a new notebook named after the filename.
|
||||
* Then repeat the process for each notebook that needs to be imported.
|
||||
|
||||
# Synchronisation
|
||||
|
||||
One of the goals of Joplin was to avoid being tied to any particular company or service, whether it is Evernote, Google or Microsoft. As such the synchronisation is designed without any hard dependency to any particular service. Most of the synchronisation process is done at an abstract level and access to external services, such as OneDrive or Dropbox, 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.
|
||||
|
||||
Currently, synchronisation is possible with OneDrive (by default) or the local filesystem. A Dropbox driver will also be available once [this React Native bug](https://github.com/facebook/react-native/issues/14445) is fixed. 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.
|
||||
|
||||
To initiate the synchronisation process, type `:sync`. You will be asked to follow a link to authorise the application (simply input your Microsoft credentials - you do not need to register with OneDrive). After that, the application will synchronise in the background whenever it is running. It is possible to also synchronise outside of the user interface by typing `joplin sync` from the terminal. This can be used to setup a cron script to synchronise at regular interval. For example, this would do it every 30 minutes:
|
||||
|
||||
*/30 * * * * /path/to/joplin sync
|
||||
|
||||
# URLs
|
||||
|
||||
When Ctrl+Clicking a URL, most terminals will open that URL in the default browser. However, one issue, especially with long URLs, is that they can end up like this:
|
||||
|
||||
<img src="https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/UrlCut.png" width="300px">
|
||||
|
||||
Not only it makes the text hard to read, but the link, being cut in two, will also not be clickable.
|
||||
|
||||
As a solution Joplin tries to start a mini-server in the background and, if successful, all the links will be converted to a much shorter URL:
|
||||
|
||||
<img src="https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/UrlNoCut.png" width="300px">
|
||||
|
||||
Since this is still an actual URL, the terminal will still make it clickable. And with shorter URLs, the text is more readable and the links unlikely to be cut. Both resources (files that are attached to notes) and external links are handled in this way.
|
||||
|
||||
# Attachments / Resources
|
||||
|
||||
In Markdown, links to resources are represented as a simple ID to the resource. In order to give access to these resources, they will be, like links, converted to local URLs. Clicking this link will then open a browser, which will handle the file - i.e. display the image, open the PDF file, etc.
|
||||
|
||||
# Shell mode
|
||||
|
||||
Commands can also be used directly from a shell. To view the list of available commands, type `joplin help all`. To reference a note, notebook or tag you can either use the ID (type `joplin ls -l` to view the ID) or by title.
|
||||
|
||||
For example, this will create a new note "My note" in the notebook "My notebook":
|
||||
|
||||
$ joplin mkbook "My notebook"
|
||||
$ joplin use "My notebook"
|
||||
$ joplin mknote "My note"
|
||||
|
||||
To view the newly created note:
|
||||
|
||||
$ joplin ls -l
|
||||
fe889 07/12/2017 17:57 My note
|
||||
|
||||
Give a new title to the note:
|
||||
|
||||
$ joplin set fe889 title "New title"
|
||||
|
||||
# Available 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:
|
||||
|
||||
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.
|
||||
|
||||
# Available commands
|
||||
|
||||
The following commands are available in [command-line mode](#command-line-mode):
|
||||
|
||||
attach <note> <file>
|
||||
|
||||
Attaches the given file to the note.
|
||||
|
||||
config [name] [value]
|
||||
|
||||
Gets or sets a config value. If [value] is not provided, it will show the
|
||||
value of [name]. If neither [name] nor [value] is provided, it will list
|
||||
the current configuration.
|
||||
|
||||
-v, --verbose Also displays unset and hidden config variables.
|
||||
|
||||
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
|
||||
default editor.
|
||||
Type: string.
|
||||
|
||||
locale Language.
|
||||
Type: Enum.
|
||||
Possible values: en_GB (English), es_CR (Español),
|
||||
fr_FR (Français).
|
||||
Default: "en_GB"
|
||||
|
||||
dateFormat Date format.
|
||||
Type: Enum.
|
||||
Possible values: DD/MM/YYYY (30/01/2017), DD/MM/YY
|
||||
(30/01/17), MM/DD/YYYY (01/30/2017), MM/DD/YY
|
||||
(01/30/17), YYYY-MM-DD (2017-01-30).
|
||||
Default: "DD/MM/YYYY"
|
||||
|
||||
timeFormat Time format.
|
||||
Type: Enum.
|
||||
Possible values: HH:mm (20:30), h:mm A (8:30 PM).
|
||||
Default: "HH:mm"
|
||||
|
||||
uncompletedTodosOnTop Show uncompleted todos on top of the lists.
|
||||
Type: bool.
|
||||
Default: true
|
||||
|
||||
trackLocation Save geo-location with notes.
|
||||
Type: bool.
|
||||
Default: true
|
||||
|
||||
sync.interval Synchronisation interval.
|
||||
Type: Enum.
|
||||
Possible values: 0 (Disabled), 300 (5 minutes), 600
|
||||
(10 minutes), 1800 (30 minutes), 3600 (1 hour),
|
||||
43200 (12 hours), 86400 (24 hours).
|
||||
Default: 300
|
||||
|
||||
sync.target Synchronisation target.
|
||||
The target to synchonise to. If synchronising with
|
||||
the file system, set `sync.2.path` to specify the
|
||||
target directory.
|
||||
Type: Enum.
|
||||
Possible values: 2 (File system), 3 (OneDrive), 4
|
||||
(OneDrive Dev (For testing only)).
|
||||
Default: 3
|
||||
|
||||
cp <note> [notebook]
|
||||
|
||||
Duplicates the notes matching <note> to [notebook]. If no notebook is
|
||||
specified the note is duplicated in the current notebook.
|
||||
|
||||
done <note>
|
||||
|
||||
Marks a to-do as done.
|
||||
|
||||
edit <note>
|
||||
|
||||
Edit note.
|
||||
|
||||
exit
|
||||
|
||||
Exits the application.
|
||||
|
||||
export <directory>
|
||||
|
||||
Exports Joplin data to the given directory. By default, it will export the
|
||||
complete database including notebooks, notes, tags and resources.
|
||||
|
||||
--note <note> Exports only the given note.
|
||||
--notebook <notebook> Exports only the given notebook.
|
||||
|
||||
geoloc <note>
|
||||
|
||||
Displays a geolocation URL for the note.
|
||||
|
||||
help [command]
|
||||
|
||||
Displays usage information.
|
||||
|
||||
import-enex <file> [notebook]
|
||||
|
||||
Imports an Evernote notebook file (.enex file).
|
||||
|
||||
-f, --force Do not ask for confirmation.
|
||||
|
||||
mkbook <new-notebook>
|
||||
|
||||
Creates a new notebook.
|
||||
|
||||
mknote <new-note>
|
||||
|
||||
Creates a new note.
|
||||
|
||||
mktodo <new-todo>
|
||||
|
||||
Creates a new to-do.
|
||||
|
||||
mv <note> [notebook]
|
||||
|
||||
Moves the notes matching <note> to [notebook].
|
||||
|
||||
ren <item> <name>
|
||||
|
||||
Renames the given <item> (note or notebook) to <name>.
|
||||
|
||||
rmbook <notebook>
|
||||
|
||||
Deletes the given notebook.
|
||||
|
||||
-f, --force Deletes the notebook without asking for confirmation.
|
||||
|
||||
rmnote <note-pattern>
|
||||
|
||||
Deletes the notes matching <note-pattern>.
|
||||
|
||||
-f, --force Deletes the notes without asking for confirmation.
|
||||
|
||||
search <pattern> [notebook]
|
||||
|
||||
Searches for the given <pattern> in all the notes.
|
||||
|
||||
status
|
||||
|
||||
Displays summary about the notes and notebooks.
|
||||
|
||||
sync
|
||||
|
||||
Synchronises with remote storage.
|
||||
|
||||
--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]
|
||||
|
||||
<tag-command> can be "add", "remove" or "list" to assign or remove [tag]
|
||||
from [note], or to list the notes associated with [tag]. The command `tag
|
||||
list` can be used to list all the tags.
|
||||
|
||||
todo <todo-command> <note-pattern>
|
||||
|
||||
<todo-command> can either be "toggle" or "clear". Use "toggle" to toggle
|
||||
the given to-do between completed and uncompleted state (If the target is
|
||||
a regular note it will be converted to a to-do). Use "clear" to convert
|
||||
the to-do back to a regular note.
|
||||
|
||||
undone <note>
|
||||
|
||||
Marks a to-do as non-completed.
|
||||
|
||||
version
|
||||
|
||||
Displays version information
|
||||
|
||||
# License
|
||||
|
||||
Copyright (c) 2016-2018 Laurent Cozic
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -75,7 +75,7 @@ apply from: "../../node_modules/react-native/react.gradle"
|
||||
* Upload all the APKs to the Play Store and people will download
|
||||
* the correct one based on the CPU architecture of their device.
|
||||
*/
|
||||
def enableSeparateBuildPerCPUArchitecture = true
|
||||
def enableSeparateBuildPerCPUArchitecture = false
|
||||
|
||||
/**
|
||||
* Run Proguard to shrink the Java bytecode in release builds.
|
||||
@@ -90,8 +90,8 @@ android {
|
||||
applicationId "net.cozic.joplin"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 22
|
||||
versionCode 89
|
||||
versionName "0.10.74"
|
||||
versionCode 2097285
|
||||
versionName "1.0.107"
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", "x86"
|
||||
}
|
||||
@@ -137,11 +137,11 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':react-native-securerandom')
|
||||
compile project(':react-native-push-notification')
|
||||
compile project(':react-native-fs')
|
||||
compile project(':react-native-image-picker')
|
||||
compile project(':react-native-vector-icons')
|
||||
compile project(':react-native-securerandom')
|
||||
compile project(':react-native-push-notification')
|
||||
compile project(':react-native-fs')
|
||||
compile project(':react-native-image-picker')
|
||||
compile project(':react-native-vector-icons')
|
||||
compile project(':react-native-fs')
|
||||
compile fileTree(dir: "libs", include: ["*.jar"])
|
||||
compile "com.android.support:appcompat-v7:23.0.1"
|
||||
|
@@ -1,51 +1,3 @@
|
||||
const { main } = require('./main.js');
|
||||
|
||||
main();
|
||||
|
||||
// const React = require('react'); const Component = React.Component;
|
||||
// import {
|
||||
// AppRegistry,
|
||||
// StyleSheet,
|
||||
// Text,
|
||||
// View
|
||||
// } from 'react-native';
|
||||
|
||||
// module.exports = default class Joplin extends Component {;
|
||||
// render() {
|
||||
// return (
|
||||
// <View style={styles.container}>
|
||||
// <Text style={styles.welcome}>
|
||||
// Welcome to React Native!
|
||||
// </Text>
|
||||
// <Text style={styles.instructions}>
|
||||
// To get started, edit index.ios.js
|
||||
// </Text>
|
||||
// <Text style={styles.instructions}>
|
||||
// Press Cmd+R to reload,{'\n'}
|
||||
// Cmd+D or shake for dev menu
|
||||
// </Text>
|
||||
// </View>
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// const styles = StyleSheet.create({
|
||||
// container: {
|
||||
// flex: 1,
|
||||
// justifyContent: 'center',
|
||||
// alignItems: 'center',
|
||||
// backgroundColor: '#F5FCFF',
|
||||
// },
|
||||
// welcome: {
|
||||
// fontSize: 20,
|
||||
// textAlign: 'center',
|
||||
// margin: 10,
|
||||
// },
|
||||
// instructions: {
|
||||
// textAlign: 'center',
|
||||
// color: '#333333',
|
||||
// marginBottom: 5,
|
||||
// },
|
||||
// });
|
||||
|
||||
// AppRegistry.registerComponent('Joplin', () => Joplin);
|
||||
main();
|
@@ -1302,7 +1302,7 @@
|
||||
PRODUCT_NAME = Joplin;
|
||||
PROVISIONING_PROFILE = "";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -1342,7 +1342,7 @@
|
||||
PRODUCT_NAME = Joplin;
|
||||
PROVISIONING_PROFILE = "";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Release;
|
||||
|
@@ -17,11 +17,11 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.10.9</string>
|
||||
<string>1.0.13</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>9</string>
|
||||
<string>13</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
|
@@ -13,4 +13,36 @@ ArrayUtils.removeElement = function(array, element) {
|
||||
return array;
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/10264318/561309
|
||||
ArrayUtils.binarySearch = function(items, value) {
|
||||
var startIndex = 0,
|
||||
stopIndex = items.length - 1,
|
||||
middle = Math.floor((stopIndex + startIndex)/2);
|
||||
|
||||
while(items[middle] != value && startIndex < stopIndex){
|
||||
|
||||
//adjust search area
|
||||
if (value < items[middle]){
|
||||
stopIndex = middle - 1;
|
||||
} else if (value > items[middle]){
|
||||
startIndex = middle + 1;
|
||||
}
|
||||
|
||||
//recalculate middle
|
||||
middle = Math.floor((stopIndex + startIndex)/2);
|
||||
}
|
||||
|
||||
//make sure it's the right value
|
||||
return (items[middle] != value) ? -1 : middle;
|
||||
}
|
||||
|
||||
ArrayUtils.findByKey = function(array, key, value) {
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
const o = array[i];
|
||||
if (typeof o !== 'object') continue;
|
||||
if (o[key] === value) return o;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = ArrayUtils;
|
@@ -1,5 +1,5 @@
|
||||
const { createStore, applyMiddleware } = require('redux');
|
||||
const { reducer, defaultState } = require('lib/reducer.js');
|
||||
const { reducer, defaultState, stateUtils } = require('lib/reducer.js');
|
||||
const { JoplinDatabase } = require('lib/joplin-database.js');
|
||||
const { Database } = require('lib/database.js');
|
||||
const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
|
||||
@@ -21,17 +21,22 @@ const { shim } = require('lib/shim.js');
|
||||
const { _, setLocale, defaultLocale, closestSupportedLocale } = require('lib/locale.js');
|
||||
const os = require('os');
|
||||
const fs = require('fs-extra');
|
||||
const JoplinError = require('lib/JoplinError');
|
||||
const EventEmitter = require('events');
|
||||
const SyncTargetRegistry = require('lib/SyncTargetRegistry.js');
|
||||
const SyncTargetFilesystem = require('lib/SyncTargetFilesystem.js');
|
||||
const SyncTargetOneDrive = require('lib/SyncTargetOneDrive.js');
|
||||
const SyncTargetOneDriveDev = require('lib/SyncTargetOneDriveDev.js');
|
||||
const SyncTargetNextcloud = require('lib/SyncTargetNextcloud.js');
|
||||
const SyncTargetWebDAV = require('lib/SyncTargetWebDAV.js');
|
||||
const EncryptionService = require('lib/services/EncryptionService');
|
||||
const DecryptionWorker = require('lib/services/DecryptionWorker');
|
||||
|
||||
SyncTargetRegistry.addClass(SyncTargetFilesystem);
|
||||
SyncTargetRegistry.addClass(SyncTargetOneDrive);
|
||||
SyncTargetRegistry.addClass(SyncTargetOneDriveDev);
|
||||
SyncTargetRegistry.addClass(SyncTargetNextcloud);
|
||||
SyncTargetRegistry.addClass(SyncTargetWebDAV);
|
||||
|
||||
class BaseApplication {
|
||||
|
||||
@@ -91,14 +96,14 @@ class BaseApplication {
|
||||
let nextArg = argv.length >= 2 ? argv[1] : null;
|
||||
|
||||
if (arg == '--profile') {
|
||||
if (!nextArg) throw new Error(_('Usage: %s', '--profile <dir-path>'));
|
||||
if (!nextArg) throw new JoplinError(_('Usage: %s', '--profile <dir-path>'), 'flagError');
|
||||
matched.profileDir = nextArg;
|
||||
argv.splice(0, 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg == '--env') {
|
||||
if (!nextArg) throw new Error(_('Usage: %s', '--env <dev|prod>'));
|
||||
if (!nextArg) throw new JoplinError(_('Usage: %s', '--env <dev|prod>'), 'flagError');
|
||||
matched.env = nextArg;
|
||||
argv.splice(0, 2);
|
||||
continue;
|
||||
@@ -129,14 +134,14 @@ class BaseApplication {
|
||||
}
|
||||
|
||||
if (arg == '--log-level') {
|
||||
if (!nextArg) throw new Error(_('Usage: %s', '--log-level <none|error|warn|info|debug>'));
|
||||
if (!nextArg) throw new JoplinError(_('Usage: %s', '--log-level <none|error|warn|info|debug>'), 'flagError');
|
||||
matched.logLevel = Logger.levelStringToId(nextArg);
|
||||
argv.splice(0, 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.length && arg[0] == '-') {
|
||||
throw new Error(_('Unknown flag: %s', arg));
|
||||
throw new JoplinError(_('Unknown flag: %s', arg), 'flagError');
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -180,8 +185,9 @@ class BaseApplication {
|
||||
this.logger().debug('Refreshing notes:', parentType, parentId);
|
||||
|
||||
let options = {
|
||||
order: state.notesOrder,
|
||||
order: stateUtils.notesOrder(state.settings),
|
||||
uncompletedTodosOnTop: Setting.value('uncompletedTodosOnTop'),
|
||||
caseInsensitive: true,
|
||||
};
|
||||
|
||||
const source = JSON.stringify({
|
||||
@@ -251,14 +257,31 @@ class BaseApplication {
|
||||
|
||||
const result = next(action);
|
||||
const newState = store.getState();
|
||||
let refreshNotes = false;
|
||||
|
||||
if (action.type == 'FOLDER_SELECT' || action.type === 'FOLDER_DELETE') {
|
||||
Setting.setValue('activeFolderId', newState.selectedFolderId);
|
||||
this.currentFolder_ = newState.selectedFolderId ? await Folder.load(newState.selectedFolderId) : null;
|
||||
await this.refreshNotes(newState);
|
||||
refreshNotes = true;
|
||||
}
|
||||
|
||||
if (this.hasGui() && action.type == 'SETTING_UPDATE_ONE' && action.key == 'uncompletedTodosOnTop' || action.type == 'SETTING_UPDATE_ALL') {
|
||||
if (this.hasGui() && ((action.type == 'SETTING_UPDATE_ONE' && action.key == 'uncompletedTodosOnTop') || action.type == 'SETTING_UPDATE_ALL')) {
|
||||
refreshNotes = true;
|
||||
}
|
||||
|
||||
if (this.hasGui() && ((action.type == 'SETTING_UPDATE_ONE' && action.key.indexOf('notes.sortOrder') === 0) || action.type == 'SETTING_UPDATE_ALL')) {
|
||||
refreshNotes = true;
|
||||
}
|
||||
|
||||
if (action.type == 'TAG_SELECT' || action.type === 'TAG_DELETE') {
|
||||
refreshNotes = true;
|
||||
}
|
||||
|
||||
if (action.type == 'SEARCH_SELECT' || action.type === 'SEARCH_DELETE') {
|
||||
refreshNotes = true;
|
||||
}
|
||||
|
||||
if (refreshNotes) {
|
||||
await this.refreshNotes(newState);
|
||||
}
|
||||
|
||||
@@ -284,14 +307,6 @@ class BaseApplication {
|
||||
}
|
||||
}
|
||||
|
||||
if (action.type == 'TAG_SELECT' || action.type === 'TAG_DELETE') {
|
||||
await this.refreshNotes(newState);
|
||||
}
|
||||
|
||||
if (action.type == 'SEARCH_SELECT' || action.type === 'SEARCH_DELETE') {
|
||||
await this.refreshNotes(newState);
|
||||
}
|
||||
|
||||
if (action.type === 'NOTE_UPDATE_ONE') {
|
||||
// If there is a conflict, we refresh the folders so as to display "Conflicts" folder
|
||||
if (action.note && action.note.is_conflict) {
|
||||
@@ -299,11 +314,6 @@ class BaseApplication {
|
||||
}
|
||||
}
|
||||
|
||||
// if (action.type === 'NOTE_DELETE') {
|
||||
// // Update folders if a note is deleted in case the deleted note was a conflict
|
||||
// await FoldersScreenUtils.refreshFolders();
|
||||
// }
|
||||
|
||||
if (this.hasGui() && action.type == 'SETTING_UPDATE_ONE' && action.key == 'sync.interval' || action.type == 'SETTING_UPDATE_ALL') {
|
||||
reg.setupRecurrentSync();
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
const { Log } = require('lib/log.js');
|
||||
const { Database } = require('lib/database.js');
|
||||
const { uuid } = require('lib/uuid.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const Mutex = require('async-mutex').Mutex;
|
||||
|
||||
class BaseModel {
|
||||
|
||||
@@ -44,6 +44,14 @@ class BaseModel {
|
||||
return null;
|
||||
}
|
||||
|
||||
static modelTypeToName(type) {
|
||||
for (let i = 0; i < BaseModel.typeEnum_.length; i++) {
|
||||
const e = BaseModel.typeEnum_[i];
|
||||
if (e[1] === type) return e[0].substr(5).toLowerCase();
|
||||
}
|
||||
throw new Error('Unknown model type: ' + type);
|
||||
}
|
||||
|
||||
static hasField(name) {
|
||||
let fields = this.fieldNames();
|
||||
return fields.indexOf(name) >= 0;
|
||||
@@ -247,6 +255,40 @@ class BaseModel {
|
||||
return !Object.getOwnPropertyNames(diff).length;
|
||||
}
|
||||
|
||||
static saveMutex(modelOrId) {
|
||||
const noLockMutex = {
|
||||
acquire: function() { return null; }
|
||||
};
|
||||
|
||||
if (!modelOrId) return noLockMutex;
|
||||
|
||||
let modelId = typeof modelOrId === 'string' ? modelOrId : modelOrId.id;
|
||||
|
||||
if (!modelId) return noLockMutex;
|
||||
|
||||
let mutex = BaseModel.saveMutexes_[modelId];
|
||||
if (mutex) return mutex;
|
||||
|
||||
mutex = new Mutex();
|
||||
BaseModel.saveMutexes_[modelId] = mutex;
|
||||
return mutex;
|
||||
}
|
||||
|
||||
static releaseSaveMutex(modelOrId, release) {
|
||||
if (!release) return;
|
||||
if (!modelOrId) return release();
|
||||
|
||||
let modelId = typeof modelOrId === 'string' ? modelOrId : modelOrId.id;
|
||||
|
||||
if (!modelId) return release();
|
||||
|
||||
let mutex = BaseModel.saveMutexes_[modelId];
|
||||
if (!mutex) return release();
|
||||
|
||||
delete BaseModel.saveMutexes_[modelId];
|
||||
release();
|
||||
}
|
||||
|
||||
static saveQuery(o, options) {
|
||||
let temp = {}
|
||||
let fieldNames = this.fieldNames();
|
||||
@@ -320,7 +362,16 @@ class BaseModel {
|
||||
return query;
|
||||
}
|
||||
|
||||
static save(o, options = null) {
|
||||
static async save(o, options = null) {
|
||||
// When saving, there's a mutex per model ID. This is because the model returned from this function
|
||||
// is basically its input `o` (instead of being read from the database, for performance reasons).
|
||||
// This works well in general except if that model is saved simultaneously in two places. In that
|
||||
// case, the output won't be up-to-date and would cause for example display issues with out-dated
|
||||
// notes being displayed. This was an issue when notes were being synchronised while being decrypted
|
||||
// at the same time.
|
||||
|
||||
const mutexRelease = await this.saveMutex(o).acquire();
|
||||
|
||||
options = this.modOptions(options);
|
||||
options.isNew = this.isNew(o, options);
|
||||
|
||||
@@ -348,7 +399,11 @@ class BaseModel {
|
||||
queries = queries.concat(options.nextQueries);
|
||||
}
|
||||
|
||||
return this.db().transactionExecBatch(queries).then(() => {
|
||||
let output = null;
|
||||
|
||||
try {
|
||||
await this.db().transactionExecBatch(queries);
|
||||
|
||||
o = Object.assign({}, o);
|
||||
if (modelId) o.id = modelId;
|
||||
if ('updated_time' in saveQuery.modObject) o.updated_time = saveQuery.modObject.updated_time;
|
||||
@@ -365,10 +420,14 @@ class BaseModel {
|
||||
}
|
||||
}
|
||||
|
||||
return this.filter(o);
|
||||
}).catch((error) => {
|
||||
Log.error('Cannot save model', error);
|
||||
});
|
||||
output = this.filter(o);
|
||||
} catch (error) {
|
||||
this.logger().error('Cannot save model', error);
|
||||
}
|
||||
|
||||
this.releaseSaveMutex(o, mutexRelease);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
static isNew(object, options) {
|
||||
@@ -435,17 +494,35 @@ class BaseModel {
|
||||
|
||||
}
|
||||
|
||||
BaseModel.TYPE_NOTE = 1;
|
||||
BaseModel.TYPE_FOLDER = 2;
|
||||
BaseModel.TYPE_SETTING = 3;
|
||||
BaseModel.TYPE_RESOURCE = 4;
|
||||
BaseModel.TYPE_TAG = 5;
|
||||
BaseModel.TYPE_NOTE_TAG = 6;
|
||||
BaseModel.TYPE_SEARCH = 7;
|
||||
BaseModel.TYPE_ALARM = 8;
|
||||
BaseModel.TYPE_MASTER_KEY = 9;
|
||||
BaseModel.typeEnum_ = [
|
||||
['TYPE_NOTE', 1],
|
||||
['TYPE_FOLDER', 2],
|
||||
['TYPE_SETTING', 3],
|
||||
['TYPE_RESOURCE', 4],
|
||||
['TYPE_TAG', 5],
|
||||
['TYPE_NOTE_TAG', 6],
|
||||
['TYPE_SEARCH', 7],
|
||||
['TYPE_ALARM', 8],
|
||||
['TYPE_MASTER_KEY', 9],
|
||||
];
|
||||
|
||||
for (let i = 0; i < BaseModel.typeEnum_.length; i++) {
|
||||
const e = BaseModel.typeEnum_[i];
|
||||
BaseModel[e[0]] = e[1];
|
||||
}
|
||||
|
||||
// BaseModel.TYPE_NOTE = 1;
|
||||
// BaseModel.TYPE_FOLDER = 2;
|
||||
// BaseModel.TYPE_SETTING = 3;
|
||||
// BaseModel.TYPE_RESOURCE = 4;
|
||||
// BaseModel.TYPE_TAG = 5;
|
||||
// BaseModel.TYPE_NOTE_TAG = 6;
|
||||
// BaseModel.TYPE_SEARCH = 7;
|
||||
// BaseModel.TYPE_ALARM = 8;
|
||||
// BaseModel.TYPE_MASTER_KEY = 9;
|
||||
|
||||
BaseModel.db_ = null;
|
||||
BaseModel.dispatch = function(o) {};
|
||||
BaseModel.saveMutexes_ = {};
|
||||
|
||||
module.exports = BaseModel;
|
@@ -10,6 +10,10 @@ class BaseSyncTarget {
|
||||
this.options_ = options;
|
||||
}
|
||||
|
||||
static supportsConfigCheck() {
|
||||
return false;
|
||||
}
|
||||
|
||||
option(name, defaultValue = null) {
|
||||
return this.options_ && (name in this.options_) ? this.options_[name] : defaultValue;
|
||||
}
|
||||
@@ -30,6 +34,10 @@ class BaseSyncTarget {
|
||||
return false;
|
||||
}
|
||||
|
||||
authRouteName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
static id() {
|
||||
throw new Error('id() not implemented');
|
||||
}
|
||||
|
35
ReactNativeClient/lib/Cache.js
Normal file
35
ReactNativeClient/lib/Cache.js
Normal file
@@ -0,0 +1,35 @@
|
||||
class Cache {
|
||||
|
||||
async getItem(name) {
|
||||
let output = null;
|
||||
try {
|
||||
const storage = await Cache.storage();
|
||||
output = await storage.getItem(name);
|
||||
} catch (error) {
|
||||
console.info(error);
|
||||
// Defaults to returning null
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
async setItem(name, value, ttl = null) {
|
||||
try {
|
||||
const storage = await Cache.storage();
|
||||
const options = {};
|
||||
if (ttl !== null) options.ttl = ttl;
|
||||
await storage.setItem(name, value, options);
|
||||
} catch (error) {
|
||||
// Defaults to not saving to cache
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Cache.storage = async function() {
|
||||
if (Cache.storage_) return Cache.storage_;
|
||||
Cache.storage_ = require('node-persist');
|
||||
await Cache.storage_.init({ dir: require('os').tmpdir() + '/joplin-cache', ttl: 1000 * 60 });
|
||||
return Cache.storage_;
|
||||
}
|
||||
|
||||
module.exports = Cache;
|
@@ -2,11 +2,7 @@ class JoplinError extends Error {
|
||||
|
||||
constructor(message, code = null) {
|
||||
super(message);
|
||||
this.code_ = code;
|
||||
}
|
||||
|
||||
get code() {
|
||||
return this.code_;
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ const Resource = require('lib/models/Resource.js');
|
||||
const ModelCache = require('lib/ModelCache');
|
||||
const { shim } = require('lib/shim.js');
|
||||
const md5 = require('md5');
|
||||
const MdToHtml_Katex = require('lib/MdToHtml_Katex');
|
||||
|
||||
class MdToHtml {
|
||||
|
||||
@@ -28,7 +29,7 @@ class MdToHtml {
|
||||
const r = resources[n];
|
||||
k.push(r.id);
|
||||
}
|
||||
k.push(md5(body));
|
||||
k.push(md5(escape(body))); // https://github.com/pvorb/node-md5/issues/41
|
||||
k.push(md5(JSON.stringify(style)));
|
||||
k.push(md5(JSON.stringify(options)));
|
||||
return k.join('_');
|
||||
@@ -73,7 +74,7 @@ class MdToHtml {
|
||||
|
||||
renderImage_(attrs, options) {
|
||||
const loadResource = async (id) => {
|
||||
console.info('Loading resource: ' + id);
|
||||
// console.info('Loading resource: ' + id);
|
||||
|
||||
// Initially set to to an empty object to make
|
||||
// it clear that it is being loaded. Otherwise
|
||||
@@ -116,7 +117,7 @@ class MdToHtml {
|
||||
if (mime == 'image/png' || mime == 'image/jpg' || mime == 'image/jpeg' || mime == 'image/gif') {
|
||||
let src = './' + Resource.filename(resource);
|
||||
if (this.resourceBaseUrl_ !== null) src = this.resourceBaseUrl_ + src;
|
||||
let output = '<img title="' + htmlentities(title) + '" src="' + src + '"/>';
|
||||
let output = '<img data-resource-id="' + resource.id + '" title="' + htmlentities(title) + '" src="' + src + '"/>';
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -134,13 +135,17 @@ class MdToHtml {
|
||||
// Ideally they should be opened in the user's browser.
|
||||
return '<span style="opacity: 0.5">(Resource not yet supported: '; //+ htmlentities(text) + ']';
|
||||
} else {
|
||||
let resourceIdAttr = "";
|
||||
let icon = "";
|
||||
if (isResourceUrl) {
|
||||
const resourceId = Resource.pathToId(href);
|
||||
href = 'joplin://' + resourceId;
|
||||
href = "joplin://" + resourceId;
|
||||
resourceIdAttr = "data-resource-id='" + resourceId + "'";
|
||||
icon = '<span class="resource-icon"></span>';
|
||||
}
|
||||
|
||||
const js = options.postMessageSyntax + "(" + JSON.stringify(href) + "); return false;";
|
||||
let output = "<a title='" + htmlentities(title) + "' href='#' onclick='" + js + "'>";
|
||||
let output = "<a " + resourceIdAttr + " title='" + htmlentities(title) + "' href='#' onclick='" + js + "'>" + icon;
|
||||
return output;
|
||||
}
|
||||
}
|
||||
@@ -156,27 +161,59 @@ class MdToHtml {
|
||||
}
|
||||
}
|
||||
|
||||
renderTokens_(tokens, options) {
|
||||
rendererPlugin_(language) {
|
||||
if (!language) return null;
|
||||
|
||||
const handlers = {};
|
||||
handlers['katex'] = new MdToHtml_Katex();
|
||||
return language in handlers ? handlers[language] : null;
|
||||
}
|
||||
|
||||
parseInlineCodeLanguage_(content) {
|
||||
const m = content.match(/^\{\.([a-zA-Z0-9]+)\}/);
|
||||
if (m && m.length >= 2) {
|
||||
const language = m[1];
|
||||
return {
|
||||
language: language,
|
||||
newContent: content.substr(language.length + 3),
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
renderTokens_(markdownIt, tokens, options) {
|
||||
let output = [];
|
||||
let previousToken = null;
|
||||
let anchorAttrs = [];
|
||||
let extraCssBlocks = {};
|
||||
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
const t = tokens[i];
|
||||
let t = tokens[i];
|
||||
const nextToken = i < tokens.length ? tokens[i+1] : null;
|
||||
|
||||
let tag = t.tag;
|
||||
let openTag = null;
|
||||
let closeTag = null;
|
||||
let attrs = t.attrs ? t.attrs : [];
|
||||
let tokenContent = t.content ? t.content : '';
|
||||
const isCodeBlock = tag === 'code' && t.block;
|
||||
const isInlineCode = t.type === 'code_inline';
|
||||
const codeBlockLanguage = t && t.info ? t.info : null;
|
||||
let rendererPlugin = null;
|
||||
let rendererPluginOptions = { tagType: 'inline' };
|
||||
|
||||
// if (t.map) attrs.push(['data-map', t.map.join(':')]);
|
||||
if (isCodeBlock) rendererPlugin = this.rendererPlugin_(codeBlockLanguage);
|
||||
|
||||
if (previousToken && previousToken.tag === 'li' && tag === 'p') {
|
||||
// Markdown-it render list items as <li><p>Text<p></li> which makes it
|
||||
// complicated to style and layout the HTML, so we remove this extra
|
||||
// <p> here and below in closeTag.
|
||||
openTag = null;
|
||||
} else if (isInlineCode) {
|
||||
openTag = null;
|
||||
} else if (tag && t.type.indexOf('html_inline') >= 0) {
|
||||
openTag = null;
|
||||
} else if (tag && t.type.indexOf('_open') >= 0) {
|
||||
openTag = tag;
|
||||
} else if (tag && t.type.indexOf('_close') >= 0) {
|
||||
@@ -186,7 +223,11 @@ class MdToHtml {
|
||||
} else if (t.type === 'link_open') {
|
||||
openTag = 'a';
|
||||
} else if (isCodeBlock) {
|
||||
openTag = 'pre';
|
||||
if (rendererPlugin) {
|
||||
openTag = null;
|
||||
} else {
|
||||
openTag = 'pre';
|
||||
}
|
||||
}
|
||||
|
||||
if (openTag) {
|
||||
@@ -201,24 +242,55 @@ class MdToHtml {
|
||||
|
||||
if (isCodeBlock) {
|
||||
const codeAttrs = ['code'];
|
||||
if (t.info) codeAttrs.push(t.info); // t.info contains the language when the token is a codeblock
|
||||
output.push('<code class="' + codeAttrs.join(' ') + '">');
|
||||
if (!rendererPlugin) {
|
||||
if (codeBlockLanguage) codeAttrs.push(t.info); // t.info contains the language when the token is a codeblock
|
||||
output.push('<code class="' + codeAttrs.join(' ') + '">');
|
||||
}
|
||||
} else if (isInlineCode) {
|
||||
const result = this.parseInlineCodeLanguage_(tokenContent);
|
||||
if (result) {
|
||||
rendererPlugin = this.rendererPlugin_(result.language);
|
||||
tokenContent = result.newContent;
|
||||
}
|
||||
|
||||
if (!rendererPlugin) {
|
||||
output.push('<code>');
|
||||
}
|
||||
}
|
||||
|
||||
if (t.type === 'math_inline' || t.type === 'math_block') {
|
||||
rendererPlugin = this.rendererPlugin_('katex');
|
||||
rendererPluginOptions = { tagType: t.type === 'math_block' ? 'block' : 'inline' };
|
||||
}
|
||||
|
||||
if (rendererPlugin) {
|
||||
rendererPlugin.loadAssets().catch((error) => {
|
||||
console.warn('MdToHtml: Error loading assets for ' + rendererPlugin.name() + ': ', error.message);
|
||||
});
|
||||
}
|
||||
|
||||
if (t.type === 'image') {
|
||||
if (t.content) attrs.push(['title', t.content]);
|
||||
if (tokenContent) attrs.push(['title', tokenContent]);
|
||||
output.push(this.renderImage_(attrs, options));
|
||||
} else if (t.type === 'html_inline') {
|
||||
output.push(t.content);
|
||||
} else if (t.type === 'softbreak') {
|
||||
output.push('<br/>');
|
||||
} else if (t.type === 'hr') {
|
||||
output.push('<hr/>');
|
||||
} else {
|
||||
if (t.children) {
|
||||
const parsedChildren = this.renderTokens_(t.children, options);
|
||||
const parsedChildren = this.renderTokens_(markdownIt, t.children, options);
|
||||
output = output.concat(parsedChildren);
|
||||
} else {
|
||||
if (t.content) {
|
||||
output.push(htmlentities(t.content));
|
||||
if (tokenContent) {
|
||||
if ((isCodeBlock || isInlineCode) && rendererPlugin) {
|
||||
output = rendererPlugin.processContent(output, tokenContent, isCodeBlock ? 'block' : 'inline');
|
||||
} else if (rendererPlugin) {
|
||||
output = rendererPlugin.processContent(output, tokenContent, rendererPluginOptions.tagType);
|
||||
} else {
|
||||
output.push(htmlentities(tokenContent));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -230,10 +302,18 @@ class MdToHtml {
|
||||
} else if (tag && t.type.indexOf('inline') >= 0) {
|
||||
closeTag = openTag;
|
||||
} else if (isCodeBlock) {
|
||||
closeTag = openTag;
|
||||
if (!rendererPlugin) closeTag = openTag;
|
||||
}
|
||||
|
||||
if (isCodeBlock) output.push('</code>');
|
||||
if (isCodeBlock) {
|
||||
if (!rendererPlugin) {
|
||||
output.push('</code>');
|
||||
}
|
||||
} else if (isInlineCode) {
|
||||
if (!rendererPlugin) {
|
||||
output.push('</code>');
|
||||
}
|
||||
}
|
||||
|
||||
if (closeTag) {
|
||||
if (closeTag === 'a') {
|
||||
@@ -243,8 +323,28 @@ class MdToHtml {
|
||||
}
|
||||
}
|
||||
|
||||
if (rendererPlugin) {
|
||||
const extraCss = rendererPlugin.extraCss();
|
||||
const name = rendererPlugin.name();
|
||||
if (extraCss && !(name in extraCssBlocks)) {
|
||||
extraCssBlocks[name] = extraCss;
|
||||
}
|
||||
}
|
||||
|
||||
previousToken = t;
|
||||
}
|
||||
|
||||
// Insert the extra CSS at the top of the HTML
|
||||
|
||||
const temp = ['<style>'];
|
||||
for (let n in extraCssBlocks) {
|
||||
if (!extraCssBlocks.hasOwnProperty(n)) continue;
|
||||
temp.push(extraCssBlocks[n]);
|
||||
}
|
||||
temp.push('</style>');
|
||||
|
||||
output = temp.concat(output);
|
||||
|
||||
return output.join('');
|
||||
}
|
||||
|
||||
@@ -259,8 +359,15 @@ class MdToHtml {
|
||||
const md = new MarkdownIt({
|
||||
breaks: true,
|
||||
linkify: true,
|
||||
html: true,
|
||||
});
|
||||
const env = {};
|
||||
|
||||
// This is currently used only so that the $expression$ and $$\nexpression\n$$ blocks are translated
|
||||
// to math_inline and math_block blocks. These blocks are then processed directly with the Katex
|
||||
// library. It is better this way as then it is possible to conditionally load the CSS required by
|
||||
// Katex and use an up-to-date version of Katex (as of 2018, the plugin is still using 0.6, which is
|
||||
// buggy instead of 0.9).
|
||||
md.use(require('markdown-it-katex'));
|
||||
|
||||
// Hack to make checkboxes clickable. Ideally, checkboxes should be parsed properly in
|
||||
// renderTokens_(), but for now this hack works. Marking it with HORRIBLE_HACK so
|
||||
@@ -278,18 +385,19 @@ class MdToHtml {
|
||||
}
|
||||
}
|
||||
|
||||
const env = {};
|
||||
const tokens = md.parse(body, env);
|
||||
|
||||
let renderedBody = this.renderTokens_(md, tokens, options);
|
||||
|
||||
// console.info(body);
|
||||
// console.info(tokens);
|
||||
|
||||
let renderedBody = this.renderTokens_(tokens, options);
|
||||
// console.info(renderedBody);
|
||||
|
||||
if (HORRIBLE_HACK) {
|
||||
let loopCount = 0;
|
||||
while (renderedBody.indexOf('mJOPm') >= 0) {
|
||||
renderedBody = renderedBody.replace(/mJOPmCHECKBOXm([A-Z]+)m(\d+)m/, function(v, type, index) {
|
||||
|
||||
const js = options.postMessageSyntax + "('checkboxclick:" + type + ':' + index + "'); this.classList.contains('tick') ? this.classList.remove('tick') : this.classList.add('tick'); return false;";
|
||||
return '<a href="#" onclick="' + js + '" class="checkbox ' + (type == 'NOTICK' ? '' : 'tick') + '"><span>' + '' + '</span></a>';
|
||||
});
|
||||
@@ -338,6 +446,18 @@ class MdToHtml {
|
||||
ul {
|
||||
padding-left: 1.3em;
|
||||
}
|
||||
.resource-icon {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: .5em;
|
||||
text-decoration: none;
|
||||
width: 1.15em;
|
||||
height: 1.5em;
|
||||
margin-right: 0.4em;
|
||||
background-color: ` + style.htmlColor + `;
|
||||
/* Awesome Font file */
|
||||
-webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 384 512'><path d='M369.9 97.9L286 14C277 5 264.8-.1 252.1-.1H48C21.5 0 0 21.5 0 48v416c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48V131.9c0-12.7-5.1-25-14.1-34zM332.1 128H256V51.9l76.1 76.1zM48 464V48h160v104c0 13.3 10.7 24 24 24h104v288H48z'/></svg>");
|
||||
}
|
||||
a.checkbox {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
@@ -374,6 +494,32 @@ class MdToHtml {
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@media print {
|
||||
body {
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
a.checkbox {
|
||||
border: 1pt solid ` + style.htmlColor + `;
|
||||
border-radius: 2pt;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
line-height: 1em;
|
||||
text-align: center;
|
||||
top: .4em;
|
||||
}
|
||||
|
||||
a.checkbox.tick:after {
|
||||
content: "X";
|
||||
}
|
||||
|
||||
a.checkbox.tick {
|
||||
top: 0;
|
||||
left: -0.02em;
|
||||
color: ` + style.htmlColor + `;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const styleHtml = '<style>' + normalizeCss + "\n" + css + '</style>';
|
||||
|
48
ReactNativeClient/lib/MdToHtml_Katex.js
Normal file
48
ReactNativeClient/lib/MdToHtml_Katex.js
Normal file
@@ -0,0 +1,48 @@
|
||||
const { shim } = require('lib/shim');
|
||||
const katex = require('katex');
|
||||
const katexCss = require('lib/csstojs/katex.css.js');
|
||||
const Setting = require('lib/models/Setting');
|
||||
|
||||
class MdToHtml_Katex {
|
||||
|
||||
name() {
|
||||
return 'katex';
|
||||
}
|
||||
|
||||
processContent(renderedTokens, content, tagType) {
|
||||
try {
|
||||
let renderered = katex.renderToString(content);
|
||||
|
||||
if (tagType === 'block') renderered = '<p>' + renderered + '</p>';
|
||||
|
||||
renderedTokens.push(renderered);
|
||||
} catch (error) {
|
||||
renderedTokens.push('Cannot render Katex content: ' + error.message);
|
||||
}
|
||||
return renderedTokens;
|
||||
}
|
||||
|
||||
extraCss() {
|
||||
return katexCss;
|
||||
}
|
||||
|
||||
async loadAssets() {
|
||||
// In node, the fonts are simply copied using copycss to where Katex expects to find them, which is under app/gui/note-viewer/fonts
|
||||
|
||||
// In React Native, it's more complicated and we need to download and copy them to the right directory. Ideally, we should embed
|
||||
// them as an asset and copy them from there (or load them from there by modifying Katex CSS), but for now that will do.
|
||||
|
||||
if (shim.isReactNative()) {
|
||||
// Fonts must go under the resourceDir directory because this is the baseUrl of NoteBodyViewer
|
||||
const baseDir = Setting.value('resourceDir');
|
||||
await shim.fsDriver().mkdir(baseDir + '/fonts');
|
||||
|
||||
await shim.fetchBlob('https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.9.0-beta1/fonts/KaTeX_Main-Regular.woff2', { overwrite: false, path: baseDir + '/fonts/KaTeX_Main-Regular.woff2' });
|
||||
await shim.fetchBlob('https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.9.0-beta1/fonts/KaTeX_Math-Italic.woff2', { overwrite: false, path: baseDir + '/fonts/KaTeX_Math-Italic.woff2' });
|
||||
await shim.fetchBlob('https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.9.0-beta1/fonts/KaTeX_Size1-Regular.woff2', { overwrite: false, path: baseDir + '/fonts/KaTeX_Size1-Regular.woff2' });
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = MdToHtml_Katex;
|
56
ReactNativeClient/lib/ObjectUtils.js
Normal file
56
ReactNativeClient/lib/ObjectUtils.js
Normal file
@@ -0,0 +1,56 @@
|
||||
const ObjectUtils = {};
|
||||
|
||||
ObjectUtils.sortByValue = function(object) {
|
||||
const temp = [];
|
||||
for (let k in object) {
|
||||
if (!object.hasOwnProperty(k)) continue;
|
||||
temp.push({
|
||||
key: k,
|
||||
value: object[k],
|
||||
});
|
||||
}
|
||||
|
||||
temp.sort(function(a, b) {
|
||||
let v1 = a.value;
|
||||
let v2 = b.value;
|
||||
if (typeof v1 === 'string') v1 = v1.toLowerCase();
|
||||
if (typeof v2 === 'string') v2 = v2.toLowerCase();
|
||||
if (v1 === v2) return 0;
|
||||
return v1 < v2 ? -1 : +1;
|
||||
});
|
||||
|
||||
const output = {};
|
||||
for (let i = 0; i < temp.length; i++) {
|
||||
const item = temp[i];
|
||||
output[item.key] = item.value;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
ObjectUtils.fieldsEqual = function(o1, o2) {
|
||||
if ((!o1 || !o2) && (o1 !== o2)) return false;
|
||||
|
||||
for (let k in o1) {
|
||||
if (!o1.hasOwnProperty(k)) continue;
|
||||
if (o1[k] !== o2[k]) return false;
|
||||
}
|
||||
|
||||
const c1 = Object.getOwnPropertyNames(o1);
|
||||
const c2 = Object.getOwnPropertyNames(o2);
|
||||
|
||||
if (c1.length !== c2.length) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ObjectUtils.convertValuesToFunctions = function(o) {
|
||||
const output = {};
|
||||
for (let n in o) {
|
||||
if (!o.hasOwnProperty(n)) continue;
|
||||
output[n] = () => { return typeof o[n] === 'function' ? o[n]() : o[n]; }
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
module.exports = ObjectUtils;
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user