Compare commits
418 Commits
v0.10.14
...
android-v0
Author | SHA1 | Date | |
---|---|---|---|
|
c984c19fee | ||
|
ac8e91e82e | ||
|
738ef2b0fa | ||
|
9746a3964b | ||
|
9efbf74b6f | ||
|
c16ea6b237 | ||
|
b06a3b588f | ||
|
6ff67e0995 | ||
|
1a5c8d126d | ||
|
f632580eed | ||
|
1d73f0cdee | ||
|
99c7111f8c | ||
|
ae9806561a | ||
|
fffdf5b5b7 | ||
|
3de19c3db7 | ||
|
56e074b4ef | ||
|
1a79253780 | ||
|
b67908df11 | ||
|
6a5089f71d | ||
|
f710463b67 | ||
|
6ae0c3aba0 | ||
|
07c6347014 | ||
|
b10999e83e | ||
|
961b5bfd25 | ||
|
d1f1d1068a | ||
|
faade0afe2 | ||
|
a442a49e2f | ||
|
7d3fbbcaba | ||
|
d9bb7c3271 | ||
|
4d1dd17fa2 | ||
|
c5c6c777be | ||
|
1fd1a73fda | ||
|
feeb498a79 | ||
|
1d7f30d441 | ||
|
424443a2d8 | ||
|
08b58f0e4c | ||
|
c2a0d8600f | ||
|
ede3c2ce2f | ||
|
0b93515711 | ||
|
2f13e689b9 | ||
|
ea135a0d28 | ||
|
f67e4a03e4 | ||
|
e9268edeff | ||
|
a8576a55d6 | ||
|
eb500cdf9e | ||
|
7b9dc66121 | ||
|
bba2c68c6f | ||
|
c70d8bea78 | ||
|
176bda66ad | ||
|
78ce10ddf0 | ||
|
29f14681a8 | ||
|
aaf617e41c | ||
|
b99146ed7f | ||
|
d136161650 | ||
|
39051a27a1 | ||
|
30bc9dd551 | ||
|
cc2f665313 | ||
|
37c989ed28 | ||
|
adc5885980 | ||
|
8de5b4219d | ||
|
e096ddebd4 | ||
|
83398dd0bc | ||
|
ddc78ebb41 | ||
|
70b69eb31e | ||
|
3fa891e136 | ||
|
6f7a9f3295 | ||
|
8e8793943b | ||
|
83d9faf2fe | ||
|
f45a4fff8b | ||
|
1e02aa3120 | ||
|
77fec75f23 | ||
|
2c9feae20b | ||
|
a81788b3fa | ||
|
88cfba53a3 | ||
|
d659d975cd | ||
|
277ad90f72 | ||
|
f2e3bedde6 | ||
|
98c0f2315a | ||
|
0926755635 | ||
|
75f8234db5 | ||
|
b8e85e1587 | ||
|
b6add857f9 | ||
|
4e41731c08 | ||
|
6ac3b8a8f9 | ||
|
7ab2b11c9d | ||
|
1eb1c5914c | ||
|
5780951f7d | ||
|
67e790e393 | ||
|
0d472486ca | ||
|
2d218904d0 | ||
|
c3fbcb8feb | ||
|
64031ac6cf | ||
|
caae7f7cf8 | ||
|
d56b247adf | ||
|
11bdfbde61 | ||
|
a59bf55c16 | ||
|
043be1916c | ||
|
42e34b5c3b | ||
|
931083b2e2 | ||
|
09b9df4228 | ||
|
37663bd110 | ||
|
f01c6aa8d1 | ||
|
8838017830 | ||
|
1d6fb8058f | ||
|
bb51729bea | ||
|
507e7e6014 | ||
|
f42908b11c | ||
|
03ec406627 | ||
|
c703521b6c | ||
|
cf97bf9a77 | ||
|
304b9a582f | ||
|
4d5c4b1743 | ||
|
4abe5d07c4 | ||
|
f6633e23f5 | ||
|
a6d6201ecb | ||
|
0115e74163 | ||
|
4314c392f6 | ||
|
685f541bb4 | ||
|
f9634ea283 | ||
|
6252a4d8c8 | ||
|
d13d7bec45 | ||
|
74e284d795 | ||
|
7b82dacbf2 | ||
|
f82162f94a | ||
|
e2d5128624 | ||
|
9074412472 | ||
|
0db9c66317 | ||
|
b22211fada | ||
|
7efeaa3a22 | ||
|
79efac2f71 | ||
|
9d7b7092f5 | ||
|
71e877d369 | ||
|
7e086a7730 | ||
|
3f810c71b0 | ||
|
500fbc5294 | ||
|
6d0f60d9a1 | ||
|
e19c26fdd1 | ||
|
86b86513d9 | ||
|
6ff19063ef | ||
|
6a75451539 | ||
|
70a33f8533 | ||
|
23722719f0 | ||
|
d499251206 | ||
|
d180e7b5e1 | ||
|
9e869a5b1f | ||
|
ab959623aa | ||
|
08d2655f13 | ||
|
20632ae1c1 | ||
|
bef7c38724 | ||
|
d1abf4971d | ||
|
d13c2cf8d7 | ||
|
3146273409 | ||
|
1891eb4998 | ||
|
70b03971f6 | ||
|
38c050b47e | ||
|
0bf5c9ebdd | ||
|
6683da804b | ||
|
7750b954fc | ||
|
edbff5a26a | ||
|
18846c11ed | ||
|
cc02c1d585 | ||
|
26bf7c4d46 | ||
|
2959fa1080 | ||
|
3f4f154949 | ||
|
4c0b472f67 | ||
|
4756238821 | ||
|
f5d26e0d81 | ||
|
e9bb5bee9d | ||
|
2c608bca3c | ||
|
d9c1e30e9b | ||
|
5bc72e2b44 | ||
|
df05d04dad | ||
|
888ac8f4c2 | ||
|
55266e5694 | ||
|
39c73e1649 | ||
|
3bf9d01f0a | ||
|
89ef33f7ca | ||
|
f71fe9a1a6 | ||
|
1008b1835b | ||
|
2ffa5419e2 | ||
|
bd20ecff78 | ||
|
a073514c46 | ||
|
c6ff14226f | ||
|
ee02f8255e | ||
|
5951ed3f55 | ||
|
f6fbf3ba0f | ||
|
9bce52a92a | ||
|
e44975622a | ||
|
92b857d83b | ||
|
671e538740 | ||
|
8a282fd2e1 | ||
|
cda623a95c | ||
|
0f343bccda | ||
|
a513f6f3f0 | ||
|
3b4809714e | ||
|
238b5ab9b9 | ||
|
08d9e9b6aa | ||
|
b22900eb3a | ||
|
89fc2c4779 | ||
|
353b79f5e5 | ||
|
b929b46281 | ||
|
5c4a536dad | ||
|
91e337307c | ||
|
2855b68ed4 | ||
|
45ca6284f9 | ||
|
145ee13356 | ||
|
6f97747199 | ||
|
f3751e4ba6 | ||
|
5cd55cada6 | ||
|
bad4b2ecb8 | ||
|
7aafd63ff3 | ||
|
3227a13035 | ||
|
027f96d100 | ||
|
f242a3c215 | ||
|
ad6c347180 | ||
|
4f0431da55 | ||
|
b873fdd029 | ||
|
c1ff820913 | ||
|
7008daf92a | ||
|
ed914c6907 | ||
|
73e81a54b4 | ||
|
ab8c66a361 | ||
|
4b55fefcb1 | ||
|
0eac8b25e1 | ||
|
aec556ff7d | ||
|
110dc29bd4 | ||
|
b1efea1bd9 | ||
|
8671467ed3 | ||
|
7851b6b429 | ||
|
8eb5f8b74e | ||
|
1830ee9fd2 | ||
|
dd615d6a8f | ||
|
08a518db70 | ||
|
581372de0b | ||
|
fe2c1c197e | ||
|
9854fddeb2 | ||
|
6c3918ebd2 | ||
|
4af496632f | ||
|
485ef1f2c2 | ||
|
cd1e7a1083 | ||
|
4dce9e9e47 | ||
|
dbeff4fd7d | ||
|
aef2e4845d | ||
|
739c6be476 | ||
|
ff502670bf | ||
|
3894dd1191 | ||
|
818537933a | ||
|
ec50808ba1 | ||
|
33fd8325be | ||
|
c72e0a14c0 | ||
|
58bc708014 | ||
|
ede1ed8b22 | ||
|
22e39b4434 | ||
|
9d984596cc | ||
|
fe909f659d | ||
|
ea120eae91 | ||
|
fa819d25b0 | ||
|
65739a5077 | ||
|
228d06e27f | ||
|
d386b83c53 | ||
|
e1b1f31cf1 | ||
|
f80b403dfe | ||
|
5af55d7da3 | ||
|
5e6a389f97 | ||
|
6f26910243 | ||
|
b2056f9e4d | ||
|
dca4bb204b | ||
|
ade39837de | ||
|
2ef2296566 | ||
|
04ed914894 | ||
|
332dd0d859 | ||
|
d9a1f7855d | ||
|
3ec59a835c | ||
|
620225bb2d | ||
|
9d7d469908 | ||
|
16bf0cf646 | ||
|
8079106c3e | ||
|
28143db968 | ||
|
7f1a14fa22 | ||
|
4de1edda05 | ||
|
52f09d2638 | ||
|
97b8cad755 | ||
|
52cb10dd4e | ||
|
135a8a9273 | ||
|
a346116d5f | ||
|
934c3c8001 | ||
|
565c17df37 | ||
|
5ee9a35f7d | ||
|
bd73107853 | ||
|
2e8fe88f53 | ||
|
444c96d5e7 | ||
|
f5ff68b236 | ||
|
d1a83d065a | ||
|
ddb73c8642 | ||
|
4df73cd82c | ||
|
c446e4471d | ||
|
fa22d5bae3 | ||
|
1a610054d3 | ||
|
11517fa037 | ||
|
67a457b9c5 | ||
|
18dc6c826a | ||
|
033d356b56 | ||
|
f9f5974267 | ||
|
6e23fead59 | ||
|
7df6541902 | ||
|
9a40851c77 | ||
|
748acdf03f | ||
|
13d27357a0 | ||
|
c72caad764 | ||
|
2933d09366 | ||
|
f51ad26db7 | ||
|
50ca686727 | ||
|
3612529e52 | ||
|
c053146885 | ||
|
8ccf2ec521 | ||
|
0428917ea2 | ||
|
914a2554ab | ||
|
26bb7dc33b | ||
|
8e5b0eadd9 | ||
|
a96b91cfef | ||
|
03251d4c40 | ||
|
112609c5f1 | ||
|
cc7cbc2ecf | ||
|
946ad7c71a | ||
|
60d2b0c763 | ||
|
d7f3cfd778 | ||
|
a2ae2c766a | ||
|
f52ff730b1 | ||
|
9327a61a36 | ||
|
05997908e5 | ||
|
2a93dea378 | ||
|
fbf7b2cc43 | ||
|
d8b19f7d08 | ||
|
acc4eb5d28 | ||
|
bcd5cd9110 | ||
|
45a4034816 | ||
|
978a08fb06 | ||
|
72dc5a6c99 | ||
|
5340fb8af9 | ||
|
3ce1172c36 | ||
|
e4d48f43d6 | ||
|
3e1ea0eb0a | ||
|
d10d6ba7de | ||
|
cf832354a2 | ||
|
b27519b85a | ||
|
27af0e69ef | ||
|
6283bf6190 | ||
|
48b648e656 | ||
|
367a18db93 | ||
|
f5feb595f6 | ||
|
c6ec0279fc | ||
|
b0b5488c2e | ||
|
3722012da5 | ||
|
c5214b6c44 | ||
|
585ccc2b8b | ||
|
ab8115b89d | ||
|
8d8395c226 | ||
|
8dbec0e8f4 | ||
|
1c1228bb88 | ||
|
7d9eec262a | ||
|
a057fbf3bd | ||
|
518feadc3e | ||
|
b1e351ce77 | ||
|
c6cb2800d7 | ||
|
c31c7a8a67 | ||
|
3d6fe4c2cd | ||
|
da7034ae08 | ||
|
37de5fd4b3 | ||
|
e2cbef1538 | ||
|
4de044dc90 | ||
|
d5dc27d788 | ||
|
e80dd59da2 | ||
|
cbd2075156 | ||
|
4b5c1491d0 | ||
|
ea077852a1 | ||
|
ca20a2a1c2 | ||
|
37c0b6d24a | ||
|
0c14a42b28 | ||
|
f126e0a944 | ||
|
9519bb1218 | ||
|
716b1315a9 | ||
|
6f88c025f5 | ||
|
2f3458e207 | ||
|
bd265e5a9d | ||
|
9d5d197747 | ||
|
ba1a005fcd | ||
|
f09cec794b | ||
|
9619d451f9 | ||
|
240fbf1720 | ||
|
286722fda7 | ||
|
86b5f3582f | ||
|
69ae32a9af | ||
|
504c3d4c0d | ||
|
9e77f01088 | ||
|
409f2ca98d | ||
|
2bfaa0e02c | ||
|
209f2cae55 | ||
|
ce9da90846 | ||
|
43bc7b5619 | ||
|
056741e2c2 | ||
|
01ba0c3078 | ||
|
7d58763fe9 | ||
|
4bb9be8f78 | ||
|
092c09065f | ||
|
fcc2eb6354 | ||
|
94a800aa3c | ||
|
add69aa210 | ||
|
5098c03264 | ||
|
2724167028 | ||
|
eacd0e5cfd | ||
|
e799062059 | ||
|
96a2aa6ad8 | ||
|
82674e8bdb | ||
|
6404d3520d | ||
|
667f5c0cfe | ||
|
fca504d552 | ||
|
b9194e94aa | ||
|
7ac6f39658 |
7
.gitignore
vendored
@@ -32,4 +32,9 @@ INFO.md
|
||||
sync_staging.sh
|
||||
*.swp
|
||||
_vieux/
|
||||
_mydocs
|
||||
_mydocs
|
||||
.DS_Store
|
||||
Assets/DownloadBadges*.psd
|
||||
node_modules
|
||||
Tools/github_oauth_token.txt
|
||||
_releases
|
10
.travis.yml
@@ -28,20 +28,22 @@ matrix:
|
||||
before_install:
|
||||
# HOMEBREW_NO_AUTO_UPDATE needed so that Homebrew doesn't upgrade to the next
|
||||
# version, which requires Ruby 2.3, which is not available on the Travis VM.
|
||||
|
||||
# Silence apt-get update errors (for example when a module doesn't exist) since
|
||||
# otherwise it will make the whole build fails, even though all we need is yarn.
|
||||
- |
|
||||
if [ "$TRAVIS_OS_NAME" == "osx" ]; then
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install yarn
|
||||
else
|
||||
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
|
||||
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
|
||||
sudo apt-get update && sudo apt-get install yarn
|
||||
sudo apt-get update || true
|
||||
sudo apt-get install -y yarn
|
||||
fi
|
||||
|
||||
script:
|
||||
- |
|
||||
cd ElectronClient/app
|
||||
rsync -aP ../../ReactNativeClient/lib lib/
|
||||
rsync -aP --delete ../../ReactNativeClient/lib/ lib/
|
||||
npm install
|
||||
ls -la .
|
||||
ls -la lib
|
||||
yarn dist
|
||||
|
Before Width: | Height: | Size: 79 KiB |
BIN
Assets/Icon.psd
BIN
Assets/LinuxIcons/1024x1024.png
Normal file
After Width: | Height: | Size: 79 KiB |
BIN
Assets/LinuxIcons/128x128.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
BIN
Assets/LinuxIcons/16x16.png
Normal file
After Width: | Height: | Size: 679 B |
BIN
Assets/LinuxIcons/24x24.png
Normal file
After Width: | Height: | Size: 968 B |
BIN
Assets/LinuxIcons/256x256.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
Assets/LinuxIcons/32x32.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 151 KiB |
Before Width: | Height: | Size: 170 KiB After Width: | Height: | Size: 170 KiB |
BIN
Assets/Screenshots/Screenshot_1511192771.png
Normal file
After Width: | Height: | Size: 205 KiB |
BIN
Assets/Screenshots/Screenshot_1511192805.png
Normal file
After Width: | Height: | Size: 300 KiB |
BIN
Assets/Screenshots/Screenshot_1511193130.png
Normal file
After Width: | Height: | Size: 117 KiB |
BIN
Assets/Screenshots/Screenshot_1511193142.png
Normal file
After Width: | Height: | Size: 277 KiB |
BIN
Assets/Screenshots/Screenshot_1511193169.png
Normal file
After Width: | Height: | Size: 133 KiB |
BIN
Assets/Screenshots/Screenshot_1511193181.png
Normal file
After Width: | Height: | Size: 214 KiB |
BIN
Assets/Screenshots/Screenshot_1511193188.png
Normal file
After Width: | Height: | Size: 181 KiB |
BIN
Assets/Screenshots/Screenshot_1511193192.png
Normal file
After Width: | Height: | Size: 133 KiB |
BIN
Assets/Screenshots/iOS/Screenshot_iPad_Paysage.jpg
Normal file
After Width: | Height: | Size: 288 KiB |
BIN
Assets/Screenshots/iOS/Screenshot_iPad_Paysage.png
Normal file
After Width: | Height: | Size: 335 KiB |
BIN
Assets/Screenshots/iOS/Screenshot_iPad_Paysage.psd
Normal file
BIN
Assets/Screenshots/iOS/Screenshot_iPhone_Portrait.jpg
Normal file
After Width: | Height: | Size: 226 KiB |
BIN
Assets/Screenshots/iOS/Screenshot_iPhone_Portrait.png
Normal file
After Width: | Height: | Size: 254 KiB |
BIN
Assets/Screenshots/iOS/Screenshot_iPhone_Portrait.psd
Normal file
BIN
Assets/iOSIcons/29.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
Assets/iOSIcons/29x2.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
Assets/iOSIcons/29x3.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
Assets/iOSIcons/40.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
Assets/iOSIcons/40x2.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
Assets/iOSIcons/40x3.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
Assets/iOSIcons/57.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
Assets/iOSIcons/57x2.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
Assets/iOSIcons/60x2.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
Assets/iOSIcons/60x3.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
Assets/iOSIcons/AppStore.png
Normal file
After Width: | Height: | Size: 74 KiB |
BIN
Assets/macOs.icns
Normal file
BIN
Assets/macOs.iconset/icon_1024x1024.png
Normal file
After Width: | Height: | Size: 79 KiB |
BIN
Assets/macOs.iconset/icon_128x128.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
Assets/macOs.iconset/icon_16x16.png
Normal file
After Width: | Height: | Size: 679 B |
BIN
Assets/macOs.iconset/icon_256x256.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
Assets/macOs.iconset/icon_32x32.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
Assets/macOs.iconset/icon_512x512.png
Normal file
After Width: | Height: | Size: 28 KiB |
52
BUILD.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# General information
|
||||
|
||||
- All the applications share the same library, which, for historical reasons, is in ReactNativeClient/lib. This library is copied to the relevant directories when building each app.
|
||||
- The translations are built by running CliClient/build-translation.sh. You normally don't need to run this if you haven't updated the translation since the compiled files are on the repository.
|
||||
|
||||
## macOS dependencies
|
||||
|
||||
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
|
||||
|
||||
- Install yarn - https://yarnpkg.com/lang/en/docs/install/
|
||||
- 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 Electron application
|
||||
|
||||
```
|
||||
cd ElectronClient/app
|
||||
rsync --delete -a ../../ReactNativeClient/lib/ lib/
|
||||
npm install
|
||||
yarn dist
|
||||
```
|
||||
|
||||
If there's an error `while loading shared libraries: libgconf-2.so.4: cannot open shared object file: No such file or directory`, run `sudo apt-get install libgconf-2-4`
|
||||
|
||||
For node-gyp to work, you might need to install the `windows-build-tools` using `npm install --global windows-build-tools`.
|
||||
|
||||
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 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.
|
||||
|
||||
Then, from `/ReactNativeClient`, run `npm install`, then `react-native run-ios` or `react-native run-android`.
|
||||
|
||||
# Building the Terminal application
|
||||
|
||||
```
|
||||
cd CliClient
|
||||
npm install
|
||||
./build.sh
|
||||
rsync --delete -aP ../ReactNativeClient/locales/ build/locales/
|
||||
```
|
||||
|
||||
Run `run.sh` to start the application for testing.
|
6
CONTRIBUTING.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# 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.
|
@@ -1,6 +1,6 @@
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { Logger } = require('lib/logger.js');
|
||||
const { Resource } = require('lib/models/resource.js');
|
||||
const Resource = require('lib/models/Resource.js');
|
||||
const { netUtils } = require('lib/net-utils.js');
|
||||
|
||||
const http = require("http");
|
||||
|
@@ -1,11 +1,12 @@
|
||||
const { Logger } = require('lib/logger.js');
|
||||
const { Folder } = require('lib/models/folder.js');
|
||||
const { Tag } = require('lib/models/tag.js');
|
||||
const { BaseModel } = require('lib/base-model.js');
|
||||
const { Note } = require('lib/models/note.js');
|
||||
const { Resource } = require('lib/models/resource.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Tag = require('lib/models/Tag.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const Resource = require('lib/models/Resource.js');
|
||||
const { cliUtils } = require('./cli-utils.js');
|
||||
const { reducer, defaultState } = require('lib/reducer.js');
|
||||
const { splitCommandString } = require('lib/string-utils.js');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
|
||||
@@ -13,6 +14,7 @@ const chalk = require('chalk');
|
||||
const tk = require('terminal-kit');
|
||||
const TermWrapper = require('tkwidgets/framework/TermWrapper.js');
|
||||
const Renderer = require('tkwidgets/framework/Renderer.js');
|
||||
const DecryptionWorker = require('lib/services/DecryptionWorker');
|
||||
|
||||
const BaseWidget = require('tkwidgets/BaseWidget.js');
|
||||
const ListWidget = require('tkwidgets/ListWidget.js');
|
||||
@@ -64,6 +66,7 @@ class AppGui {
|
||||
// a regular command it's not necessary since the process
|
||||
// exits right away.
|
||||
reg.setupRecurrentSync();
|
||||
DecryptionWorker.instance().scheduleStart();
|
||||
}
|
||||
|
||||
store() {
|
||||
@@ -79,8 +82,16 @@ class AppGui {
|
||||
await this.renderer_.renderRoot();
|
||||
}
|
||||
|
||||
prompt(initialText = '', promptString = ':') {
|
||||
return this.widget('statusBar').prompt(initialText, promptString);
|
||||
termSaveState() {
|
||||
return this.term().saveState();
|
||||
}
|
||||
|
||||
termRestoreState(state) {
|
||||
return this.term().restoreState(state);
|
||||
}
|
||||
|
||||
prompt(initialText = '', promptString = ':', options = null) {
|
||||
return this.widget('statusBar').prompt(initialText, promptString, options);
|
||||
}
|
||||
|
||||
stdoutMaxWidth() {
|
||||
@@ -166,7 +177,7 @@ class AppGui {
|
||||
});
|
||||
this.rootWidget_.connect(noteList, (state) => {
|
||||
return {
|
||||
selectedNoteId: state.selectedNoteId,
|
||||
selectedNoteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null,
|
||||
items: state.notes,
|
||||
};
|
||||
});
|
||||
@@ -180,7 +191,7 @@ class AppGui {
|
||||
};
|
||||
this.rootWidget_.connect(noteText, (state) => {
|
||||
return {
|
||||
noteId: state.selectedNoteId,
|
||||
noteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null,
|
||||
notes: state.notes,
|
||||
};
|
||||
});
|
||||
@@ -194,7 +205,7 @@ class AppGui {
|
||||
borderRightWidth: 1,
|
||||
};
|
||||
this.rootWidget_.connect(noteMetadata, (state) => {
|
||||
return { noteId: state.selectedNoteId };
|
||||
return { noteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null };
|
||||
});
|
||||
noteMetadata.hide();
|
||||
|
||||
@@ -320,13 +331,16 @@ class AppGui {
|
||||
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_REMOVE',
|
||||
type: 'SEARCH_DELETE',
|
||||
id: item.id,
|
||||
});
|
||||
}
|
||||
@@ -338,6 +352,10 @@ class AppGui {
|
||||
}
|
||||
};
|
||||
|
||||
shortcuts['BACKSPACE'] = {
|
||||
alias: 'DELETE',
|
||||
};
|
||||
|
||||
shortcuts[' '] = {
|
||||
friendlyName: 'SPACE',
|
||||
description: () => _('Set a to-do as completed / not completed'),
|
||||
@@ -518,28 +536,32 @@ class AppGui {
|
||||
return;
|
||||
}
|
||||
|
||||
let note = this.widget('noteList').currentItem;
|
||||
let folder = this.widget('folderList').currentItem;
|
||||
let args = cliUtils.splitCommandString(cmd);
|
||||
try {
|
||||
let note = this.widget('noteList').currentItem;
|
||||
let folder = this.widget('folderList').currentItem;
|
||||
let args = splitCommandString(cmd);
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] == '$n') {
|
||||
args[i] = note ? note.id : '';
|
||||
} else if (args[i] == '$b') {
|
||||
args[i] = folder ? folder.id : '';
|
||||
} else if (args[i] == '$c') {
|
||||
const item = this.activeListItem();
|
||||
args[i] = item ? item.id : '';
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] == '$n') {
|
||||
args[i] = note ? note.id : '';
|
||||
} else if (args[i] == '$b') {
|
||||
args[i] = folder ? folder.id : '';
|
||||
} else if (args[i] == '$c') {
|
||||
const item = this.activeListItem();
|
||||
args[i] = item ? item.id : '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await this.app().execCommand(args);
|
||||
} catch (error) {
|
||||
this.stdout(error.message);
|
||||
}
|
||||
|
||||
this.widget('console').scrollBottom();
|
||||
|
||||
// Invalidate so that the screen is redrawn in case inputting a command has moved
|
||||
// the GUI up (in particular due to autocompletion), it's moved back to the right position.
|
||||
this.widget('root').invalidate();
|
||||
}
|
||||
|
||||
async updateFolderList() {
|
||||
@@ -764,7 +786,10 @@ class AppGui {
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
const shortcutKey = this.currentShortcutKeys_.join('');
|
||||
const cmd = shortcutKey in this.shortcuts_ ? this.shortcuts_[shortcutKey] : null;
|
||||
let cmd = shortcutKey in this.shortcuts_ ? this.shortcuts_[shortcutKey] : null;
|
||||
|
||||
// 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;
|
||||
@@ -815,4 +840,4 @@ class AppGui {
|
||||
AppGui.INPUT_MODE_NORMAL = 1;
|
||||
AppGui.INPUT_MODE_META = 2;
|
||||
|
||||
module.exports = AppGui;
|
||||
module.exports = AppGui;
|
||||
|
@@ -5,12 +5,12 @@ const { JoplinDatabase } = require('lib/joplin-database.js');
|
||||
const { Database } = require('lib/database.js');
|
||||
const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
|
||||
const { DatabaseDriverNode } = require('lib/database-driver-node.js');
|
||||
const { BaseModel } = require('lib/base-model.js');
|
||||
const { Folder } = require('lib/models/folder.js');
|
||||
const { BaseItem } = require('lib/models/base-item.js');
|
||||
const { Note } = require('lib/models/note.js');
|
||||
const { Tag } = require('lib/models/tag.js');
|
||||
const { Setting } = require('lib/models/setting.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const BaseItem = require('lib/models/BaseItem.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const Tag = require('lib/models/Tag.js');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const { Logger } = require('lib/logger.js');
|
||||
const { sprintf } = require('sprintf-js');
|
||||
const { reg } = require('lib/registry.js');
|
||||
@@ -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() {
|
||||
@@ -144,13 +146,15 @@ class Application extends BaseApplication {
|
||||
message += ' (' + options.answers.join('/') + ')';
|
||||
}
|
||||
|
||||
let answer = await this.gui().prompt('', message + ' ');
|
||||
let answer = await this.gui().prompt('', message + ' ', options);
|
||||
|
||||
if (options.type === 'boolean') {
|
||||
if (answer === null) return false; // Pressed ESCAPE
|
||||
if (!answer) answer = options.answers[0];
|
||||
let positiveIndex = options.booleanAnswerDefault == 'y' ? 0 : 1;
|
||||
return answer.toLowerCase() === options.answers[positiveIndex].toLowerCase();
|
||||
} else {
|
||||
return answer;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -168,31 +172,42 @@ class Application extends BaseApplication {
|
||||
await doExit();
|
||||
}, 5000);
|
||||
|
||||
if (await reg.syncStarted()) {
|
||||
if (await reg.syncTarget().syncStarted()) {
|
||||
this.stdout(_('Cancelling background synchronisation... Please wait.'));
|
||||
const sync = await reg.synchronizer(Setting.value('sync.target'));
|
||||
const sync = await reg.syncTarget().synchronizer();
|
||||
await sync.cancel();
|
||||
}
|
||||
|
||||
await doExit();
|
||||
}
|
||||
|
||||
commands() {
|
||||
if (this.allCommandsLoaded_) return this.commands_;
|
||||
commands(uiType = null) {
|
||||
if (!this.allCommandsLoaded_) {
|
||||
fs.readdirSync(__dirname).forEach((path) => {
|
||||
if (path.indexOf('command-') !== 0) return;
|
||||
const ext = fileExtension(path)
|
||||
if (ext != 'js') return;
|
||||
|
||||
fs.readdirSync(__dirname).forEach((path) => {
|
||||
if (path.indexOf('command-') !== 0) return;
|
||||
const ext = fileExtension(path)
|
||||
if (ext != 'js') return;
|
||||
let CommandClass = require('./' + path);
|
||||
let cmd = new CommandClass();
|
||||
if (!cmd.enabled()) return;
|
||||
cmd = this.setupCommand(cmd);
|
||||
this.commands_[cmd.name()] = cmd;
|
||||
});
|
||||
|
||||
let CommandClass = require('./' + path);
|
||||
let cmd = new CommandClass();
|
||||
if (!cmd.enabled()) return;
|
||||
cmd = this.setupCommand(cmd);
|
||||
this.commands_[cmd.name()] = cmd;
|
||||
});
|
||||
this.allCommandsLoaded_ = true;
|
||||
}
|
||||
|
||||
this.allCommandsLoaded_ = true;
|
||||
if (uiType !== null) {
|
||||
let temp = [];
|
||||
for (let n in this.commands_) {
|
||||
if (!this.commands_.hasOwnProperty(n)) continue;
|
||||
const c = this.commands_[n];
|
||||
if (!c.supportsUi(uiType)) continue;
|
||||
temp[n] = c;
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
|
||||
return this.commands_;
|
||||
}
|
||||
@@ -210,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_);
|
||||
}
|
||||
@@ -229,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_);
|
||||
@@ -246,9 +257,13 @@ class Application extends BaseApplication {
|
||||
try {
|
||||
CommandClass = require(__dirname + '/command-' + name + '.js');
|
||||
} catch (error) {
|
||||
let e = new Error('No such command: ' + name);
|
||||
e.type = 'notFound';
|
||||
throw e;
|
||||
if (error.message && error.message.indexOf('Cannot find module') >= 0) {
|
||||
let e = new Error(_('No such command: %s', name));
|
||||
e.type = 'notFound';
|
||||
throw e;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
let cmd = new CommandClass();
|
||||
@@ -260,7 +275,7 @@ class Application extends BaseApplication {
|
||||
dummyGui() {
|
||||
return {
|
||||
isDummy: () => { return true; },
|
||||
prompt: (initialText = '', promptString = '') => { return cliUtils.prompt(initialText, promptString); },
|
||||
prompt: (initialText = '', promptString = '', options = null) => { return cliUtils.prompt(initialText, promptString, options); },
|
||||
showConsole: () => {},
|
||||
maximizeConsole: () => {},
|
||||
stdout: (text) => { console.info(text); },
|
||||
@@ -268,7 +283,10 @@ class Application extends BaseApplication {
|
||||
exit: () => {},
|
||||
showModalOverlay: (text) => {},
|
||||
hideModalOverlay: () => {},
|
||||
stdoutMaxWidth: () => { return 78; }
|
||||
stdoutMaxWidth: () => { return 78; },
|
||||
forceRender: () => {},
|
||||
termSaveState: () => {},
|
||||
termRestoreState: (state) => {},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -306,6 +324,8 @@ class Application extends BaseApplication {
|
||||
if (argv.length) {
|
||||
this.gui_ = this.dummyGui();
|
||||
|
||||
this.currentFolder_ = await Folder.load(Setting.value('activeFolderId'));
|
||||
|
||||
try {
|
||||
await this.execCommand(argv);
|
||||
} catch (error) {
|
||||
@@ -334,7 +354,7 @@ class Application extends BaseApplication {
|
||||
|
||||
this.dispatch({
|
||||
type: 'TAG_UPDATE_ALL',
|
||||
tags: tags,
|
||||
items: tags,
|
||||
});
|
||||
|
||||
this.store().dispatch({
|
||||
|
185
CliClient/app/autocompletion.js
Normal file
@@ -0,0 +1,185 @@
|
||||
var { app } = require('./app.js');
|
||||
var Note = require('lib/models/Note.js');
|
||||
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');
|
||||
|
||||
async function handleAutocompletionPromise(line) {
|
||||
// Auto-complete the command name
|
||||
const names = await app().commandNames();
|
||||
let words = getArguments(line);
|
||||
//If there is only one word and it is not already a command name then you
|
||||
//should look for commmands it could be
|
||||
if (words.length == 1) {
|
||||
if (names.indexOf(words[0]) === -1) {
|
||||
let x = names.filter((n) => n.indexOf(words[0]) === 0);
|
||||
if (x.length === 1) {
|
||||
return x[0] + ' ';
|
||||
}
|
||||
return x.length > 0 ? x.map((a) => a + ' ') : line;
|
||||
} else {
|
||||
return line;
|
||||
}
|
||||
}
|
||||
//There is more than one word and it is a command
|
||||
const metadata = (await app().commandMetadata())[words[0]];
|
||||
//If for some reason this command does not have any associated metadata
|
||||
//just don't autocomplete. However, this should not happen.
|
||||
if (metadata === undefined) {
|
||||
return line;
|
||||
}
|
||||
//complete an option
|
||||
let next = words.length > 1 ? words[words.length - 1] : '';
|
||||
let l = [];
|
||||
if (next[0] === '-') {
|
||||
for (let i = 0; i<metadata.options.length; i++) {
|
||||
const options = metadata.options[i][0].split(' ');
|
||||
//if there are multiple options then they will be seperated by comma and
|
||||
//space. The comma should be removed
|
||||
if (options[0][options[0].length - 1] === ',') {
|
||||
options[0] = options[0].slice(0, -1);
|
||||
}
|
||||
if (words.includes(options[0]) || words.includes(options[1])) {
|
||||
continue;
|
||||
}
|
||||
//First two elements are the flag and the third is the description
|
||||
//Only autocomplete long
|
||||
if (options.length > 1 && options[1].indexOf(next) === 0) {
|
||||
l.push(options[1]);
|
||||
} else if (options[0].indexOf(next) === 0) {
|
||||
l.push(options[2]);
|
||||
}
|
||||
}
|
||||
if (l.length === 0) {
|
||||
return line;
|
||||
}
|
||||
let ret = l.map(a=>toCommandLine(a));
|
||||
ret.prefix = toCommandLine(words.slice(0, -1)) + ' ';
|
||||
return ret;
|
||||
}
|
||||
//Complete an argument
|
||||
//Determine the number of positional arguments by counting the number of
|
||||
//words that don't start with a - less one for the command name
|
||||
const positionalArgs = words.filter((a)=>a.indexOf('-') !== 0).length - 1;
|
||||
|
||||
let cmdUsage = yargParser(metadata.usage)['_'];
|
||||
cmdUsage.splice(0, 1);
|
||||
|
||||
if (cmdUsage.length >= positionalArgs) {
|
||||
|
||||
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 + '*' });
|
||||
l.push(...notes.map((n) => n.title));
|
||||
}
|
||||
|
||||
if (argName == 'notebook') {
|
||||
const folders = await Folder.search({ titlePattern: next + '*' });
|
||||
l.push(...folders.map((n) => n.title));
|
||||
}
|
||||
|
||||
if (argName == 'tag') {
|
||||
let tags = await Tag.search({ titlePattern: next + '*' });
|
||||
l.push(...tags.map((n) => n.title));
|
||||
}
|
||||
|
||||
if (argName == 'tag-command') {
|
||||
let c = filterList(['add', 'remove', 'list'], next);
|
||||
l.push(...c);
|
||||
}
|
||||
|
||||
if (argName == 'todo-command') {
|
||||
let c = filterList(['toggle', 'clear'], next);
|
||||
l.push(...c);
|
||||
}
|
||||
}
|
||||
if (l.length === 1) {
|
||||
return toCommandLine([...words.slice(0, -1), l[0]]);
|
||||
} else if (l.length > 1) {
|
||||
let ret = l.map(a=>toCommandLine(a));
|
||||
ret.prefix = toCommandLine(words.slice(0, -1)) + ' ';
|
||||
return ret;
|
||||
}
|
||||
return line;
|
||||
|
||||
}
|
||||
function handleAutocompletion(str, callback) {
|
||||
handleAutocompletionPromise(str).then(function(res) {
|
||||
callback(undefined, res);
|
||||
});
|
||||
}
|
||||
function toCommandLine(args) {
|
||||
if (Array.isArray(args)) {
|
||||
return args.map(function(a) {
|
||||
if(a.indexOf('"') !== -1 || a.indexOf(' ') !== -1) {
|
||||
return "'" + a + "'";
|
||||
} else if (a.indexOf("'") !== -1) {
|
||||
return '"' + a + '"';
|
||||
} else {
|
||||
return a;
|
||||
}
|
||||
}).join(' ');
|
||||
} else {
|
||||
if(args.indexOf('"') !== -1 || args.indexOf(' ') !== -1) {
|
||||
return "'" + args + "' ";
|
||||
} else if (args.indexOf("'") !== -1) {
|
||||
return '"' + args + '" ';
|
||||
} else {
|
||||
return args + ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
function getArguments(line) {
|
||||
let inSingleQuotes = false;
|
||||
let inDoubleQuotes = false;
|
||||
let currentWord = '';
|
||||
let parsed = [];
|
||||
for(let i = 0; i<line.length; i++) {
|
||||
if(line[i] === '"') {
|
||||
if(inDoubleQuotes) {
|
||||
inDoubleQuotes = false;
|
||||
//maybe push word to parsed?
|
||||
//currentWord += '"';
|
||||
} else {
|
||||
inDoubleQuotes = true;
|
||||
//currentWord += '"';
|
||||
}
|
||||
} else if(line[i] === "'") {
|
||||
if(inSingleQuotes) {
|
||||
inSingleQuotes = false;
|
||||
//maybe push word to parsed?
|
||||
//currentWord += "'";
|
||||
} else {
|
||||
inSingleQuotes = true;
|
||||
//currentWord += "'";
|
||||
}
|
||||
} else if (/\s/.test(line[i]) &&
|
||||
!(inDoubleQuotes || inSingleQuotes)) {
|
||||
if (currentWord !== '') {
|
||||
parsed.push(currentWord);
|
||||
currentWord = '';
|
||||
}
|
||||
} else {
|
||||
currentWord += line[i];
|
||||
}
|
||||
}
|
||||
if (!(inSingleQuotes || inDoubleQuotes) && /\s/.test(line[line.length - 1])) {
|
||||
parsed.push('');
|
||||
} else {
|
||||
parsed.push(currentWord);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
function filterList(list, next) {
|
||||
let output = [];
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
if (list[i].indexOf(next) !== 0) continue;
|
||||
output.push(list[i]);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
module.exports = { handleAutocompletion };
|
@@ -12,6 +12,10 @@ class BaseCommand {
|
||||
throw new Error('Usage not defined');
|
||||
}
|
||||
|
||||
encryptionCheck(item) {
|
||||
if (item && item.encryption_applied) throw new Error(_('Cannot change encrypted item'));
|
||||
}
|
||||
|
||||
description() {
|
||||
throw new Error('Description not defined');
|
||||
}
|
||||
|
@@ -1,208 +0,0 @@
|
||||
const fs = require('fs-extra');
|
||||
const { fileExtension, basename, dirname } = require('lib/path-utils.js');
|
||||
const { _, setLocale, languageCode } = require('lib/locale.js');
|
||||
const marked = require('lib/marked.js');
|
||||
|
||||
const headerHtml = `
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Joplin - a free, open source, te taking and to-do application with synchronisation capabilities</title>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
|
||||
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
|
||||
<style>
|
||||
body {
|
||||
background-color: #F1F1F1;
|
||||
color: #333333;
|
||||
}
|
||||
table {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
td, th {
|
||||
padding: .5em 1em .5em 0;
|
||||
}
|
||||
h1, h2 {
|
||||
border-bottom: 1px solid #eaecef;
|
||||
padding-bottom: 0.3em;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
font-weight: 600;
|
||||
font-size: 2em;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.6em;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
code {
|
||||
color: black;
|
||||
background-color: #eee;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
pre code {
|
||||
border: none;
|
||||
}
|
||||
.title-icon {
|
||||
height: 2em;
|
||||
}
|
||||
.sub-title {
|
||||
font-weight: bold;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
.container {
|
||||
background-color: white;
|
||||
padding: 0;
|
||||
box-shadow: 0 10px 20px #888888;
|
||||
}
|
||||
table.screenshots {
|
||||
margin-top: 2em;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
table.screenshots th {
|
||||
height: 3em;
|
||||
text-align: center;
|
||||
}
|
||||
table.screenshots th,
|
||||
table.screenshots td {
|
||||
border: 1px solid #C2C2C2;
|
||||
}
|
||||
.mobile-screenshot {
|
||||
height: 40em;
|
||||
padding: 1em;
|
||||
}
|
||||
.cli-screenshot-wrapper {
|
||||
background-color: black;
|
||||
vertical-align: top;
|
||||
padding: 1em 2em 1em 1em;
|
||||
}
|
||||
.cli-screenshot {
|
||||
font-family: "Monaco", "Inconsolata", "CONSOLAS", "Deja Vu Sans Mono", "Droid Sans Mono", "Andale Mono", monospace;
|
||||
background-color: black;
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
.cli-screenshot .prompt {
|
||||
color: #48C2F0;
|
||||
}
|
||||
.header {
|
||||
position: relative;
|
||||
padding-left: 2em;
|
||||
padding-right: 2em;
|
||||
padding-top: 1em;
|
||||
padding-bottom: 1em;
|
||||
color: white;
|
||||
background-color: #2B2B3D;
|
||||
}
|
||||
.content {
|
||||
padding-left: 2em;
|
||||
padding-right: 2em;
|
||||
padding-bottom: 2em;
|
||||
padding-top: 2em;
|
||||
}
|
||||
.forkme {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top:0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<div class="header">
|
||||
<a class="forkme" href="https://github.com/laurent22/joplin"><img src="docs/images/ForkMe.png"/></a>
|
||||
<h1 id="joplin"><img class="title-icon" src="docs/images/Icon512.png">oplin</h1>
|
||||
<p class="sub-title">A free, open source, note taking and to-do application with synchronisation capabilities.</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
`;
|
||||
|
||||
const footerHtml = `
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
// const screenshotHtml = `
|
||||
// <table class="screenshots">
|
||||
// <tr>
|
||||
// <th>
|
||||
// Mobile
|
||||
// </th>
|
||||
// <th>
|
||||
// Command line
|
||||
// </th>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td>
|
||||
// <img class="mobile-screenshot" src="docs/images/Mobile.png"/>
|
||||
// </td>
|
||||
// <td class="cli-screenshot-wrapper">
|
||||
// <pre class="cli-screenshot">
|
||||
// <span class="prompt">joplin:/My notebook$</span> ls -n 12
|
||||
// [ ] 8am conference call ☎
|
||||
// [ ] Make vet appointment
|
||||
// [ ] Go pick up parcel
|
||||
// [ ] Pay flat rent 💸
|
||||
// [X] Book ferry 🚢
|
||||
// [X] Deploy Joplin app
|
||||
// Open source stuff
|
||||
// Swimming pool time table 🏊
|
||||
// Grocery shopping list 📝
|
||||
// Work itinerary
|
||||
// Tuesday random note
|
||||
// Vacation plans ☀️
|
||||
// </pre>
|
||||
// </td>
|
||||
// </tr>
|
||||
// </table>
|
||||
// `;
|
||||
|
||||
const gaHtml = `
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-103586105-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
|
||||
</script>
|
||||
`;
|
||||
|
||||
const rootDir = dirname(dirname(__dirname));
|
||||
|
||||
function markdownToHtml(md) {
|
||||
const renderer = new marked.Renderer();
|
||||
|
||||
// Remove the header because it's going to be added back as HTML
|
||||
md = md.replace(/# Joplin/, '');
|
||||
|
||||
let output = marked(md, {
|
||||
gfm: true,
|
||||
break: true,
|
||||
renderer: renderer,
|
||||
});
|
||||
|
||||
//output = output.replace(/<!-- \[SCREENSHOTS\] -->/, screenshotHtml);
|
||||
|
||||
return headerHtml + output + gaHtml + footerHtml;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const md = fs.readFileSync(rootDir + '/README.md', 'utf8');
|
||||
const html = markdownToHtml(md);
|
||||
|
||||
fs.writeFileSync(rootDir + '/index.html', html);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
});
|
@@ -5,10 +5,10 @@ const { Logger } = require('lib/logger.js');
|
||||
const { dirname } = require('lib/path-utils.js');
|
||||
const { DatabaseDriverNode } = require('lib/database-driver-node.js');
|
||||
const { JoplinDatabase } = require('lib/joplin-database.js');
|
||||
const { BaseModel } = require('lib/base-model.js');
|
||||
const { Folder } = require('lib/models/folder.js');
|
||||
const { Note } = require('lib/models/note.js');
|
||||
const { Setting } = require('lib/models/setting.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const { sprintf } = require('sprintf-js');
|
||||
const exec = require('child_process').exec
|
||||
|
||||
|
@@ -5,75 +5,6 @@ const stringPadding = require('string-padding');
|
||||
|
||||
const cliUtils = {};
|
||||
|
||||
cliUtils.splitCommandString = function(command) {
|
||||
let args = [];
|
||||
let state = "start"
|
||||
let current = ""
|
||||
let quote = "\""
|
||||
let escapeNext = false;
|
||||
for (let i = 0; i < command.length; i++) {
|
||||
let c = command[i]
|
||||
|
||||
if (state == "quotes") {
|
||||
if (c != quote) {
|
||||
current += c
|
||||
} else {
|
||||
args.push(current)
|
||||
current = ""
|
||||
state = "start"
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (escapeNext) {
|
||||
current += c;
|
||||
escapeNext = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == "\\") {
|
||||
escapeNext = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == '"' || c == '\'') {
|
||||
state = "quotes"
|
||||
quote = c
|
||||
continue
|
||||
}
|
||||
|
||||
if (state == "arg") {
|
||||
if (c == ' ' || c == '\t') {
|
||||
args.push(current)
|
||||
current = ""
|
||||
state = "start"
|
||||
} else {
|
||||
current += c
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (c != ' ' && c != "\t") {
|
||||
state = "arg"
|
||||
current += c
|
||||
}
|
||||
}
|
||||
|
||||
if (state == "quotes") {
|
||||
throw new Error("Unclosed quote in command line: " + command)
|
||||
}
|
||||
|
||||
if (current != "") {
|
||||
args.push(current)
|
||||
}
|
||||
|
||||
if (args.length <= 0) {
|
||||
throw new Error("Empty command line")
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
cliUtils.printArray = function(logFunction, rows, headers = null) {
|
||||
if (!rows.length) return '';
|
||||
|
||||
@@ -247,38 +178,39 @@ cliUtils.promptConfirm = function(message, answers = null) {
|
||||
});
|
||||
}
|
||||
|
||||
cliUtils.promptInput = function(message) {
|
||||
const readline = require('readline');
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
rl.question(message + ' ', (answer) => {
|
||||
rl.close();
|
||||
resolve(answer);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Note: initialText is there to have the same signature as statusBar.prompt() so that
|
||||
// it can be a drop-in replacement, however initialText is not used (and cannot be
|
||||
// with readline.question?).
|
||||
cliUtils.prompt = function(initialText = '', promptString = ':') {
|
||||
cliUtils.prompt = function(initialText = '', promptString = ':', options = null) {
|
||||
if (!options) options = {};
|
||||
|
||||
const readline = require('readline');
|
||||
const Writable = require('stream').Writable;
|
||||
|
||||
const mutableStdout = new Writable({
|
||||
write: function(chunk, encoding, callback) {
|
||||
if (!this.muted)
|
||||
process.stdout.write(chunk, encoding);
|
||||
callback();
|
||||
}
|
||||
});
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
output: mutableStdout,
|
||||
terminal: true,
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
mutableStdout.muted = false;
|
||||
|
||||
rl.question(promptString, (answer) => {
|
||||
rl.close();
|
||||
if (!!options.secure) this.stdout_('');
|
||||
resolve(answer);
|
||||
});
|
||||
|
||||
mutableStdout.muted = !!options.secure;
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { BaseModel } = require('lib/base-model.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const { shim } = require('lib/shim.js');
|
||||
const fs = require('fs-extra');
|
||||
|
||||
@@ -19,6 +19,7 @@ class Command extends BaseCommand {
|
||||
let title = args['note'];
|
||||
|
||||
let note = await app().loadItem(BaseModel.TYPE_NOTE, title, { parent: app().currentFolder() });
|
||||
this.encryptionCheck(note);
|
||||
if (!note) throw new Error(_('Cannot find "%s".', title));
|
||||
|
||||
const localFilePath = args['file'];
|
||||
|
@@ -1,9 +1,9 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { BaseModel } = require('lib/base-model.js');
|
||||
const { Folder } = require('lib/models/folder.js');
|
||||
const { Note } = require('lib/models/note.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
|
||||
@@ -21,10 +21,6 @@ class Command extends BaseCommand {
|
||||
];
|
||||
}
|
||||
|
||||
enabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
let title = args['note'];
|
||||
|
||||
@@ -33,6 +29,9 @@ class Command extends BaseCommand {
|
||||
|
||||
const content = args.options.verbose ? await Note.serialize(item) : await Note.serializeForEdit(item);
|
||||
this.stdout(content);
|
||||
|
||||
app().gui().showConsole();
|
||||
app().gui().maximizeConsole();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const { _, setLocale } = require('lib/locale.js');
|
||||
const { app } = require('./app.js');
|
||||
const { Setting } = require('lib/models/setting.js');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
|
||||
@@ -23,7 +23,8 @@ class Command extends BaseCommand {
|
||||
const verbose = args.options.verbose;
|
||||
|
||||
const renderKeyValue = (name) => {
|
||||
const value = Setting.value(name);
|
||||
let value = Setting.value(name);
|
||||
if (typeof value === 'object' || Array.isArray(value)) value = JSON.stringify(value);
|
||||
if (Setting.isEnum(name)) {
|
||||
return _('%s = %s (%s)', name, value, Setting.enumOptionsDoc(name));
|
||||
} else {
|
||||
|
@@ -1,9 +1,9 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { BaseModel } = require('lib/base-model.js');
|
||||
const { Folder } = require('lib/models/folder.js');
|
||||
const { Note } = require('lib/models/note.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
|
||||
|
@@ -1,9 +1,9 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { BaseModel } = require('lib/base-model.js');
|
||||
const { Folder } = require('lib/models/folder.js');
|
||||
const { Note } = require('lib/models/note.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
@@ -16,8 +16,9 @@ class Command extends BaseCommand {
|
||||
return _('Marks a to-do as done.');
|
||||
}
|
||||
|
||||
static async handleAction(args, isCompleted) {
|
||||
static async handleAction(commandInstance, args, isCompleted) {
|
||||
const note = await app().loadItem(BaseModel.TYPE_NOTE, args.note);
|
||||
commandInstance.encryptionCheck(note);
|
||||
if (!note) throw new Error(_('Cannot find "%s".', args.note));
|
||||
if (!note.is_todo) throw new Error(_('Note is not a to-do: "%s"', args.note));
|
||||
|
||||
@@ -32,7 +33,7 @@ class Command extends BaseCommand {
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
await Command.handleAction(args, true);
|
||||
await Command.handleAction(this, args, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,9 +1,9 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { Folder } = require('lib/models/folder.js');
|
||||
const { Note } = require('lib/models/note.js');
|
||||
const { Tag } = require('lib/models/tag.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const Tag = require('lib/models/Tag.js');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
|
||||
|
183
CliClient/app/command-e2ee.js
Normal file
@@ -0,0 +1,183 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { cliUtils } = require('./cli-utils.js');
|
||||
const EncryptionService = require('lib/services/EncryptionService');
|
||||
const DecryptionWorker = require('lib/services/DecryptionWorker');
|
||||
const MasterKey = require('lib/models/MasterKey');
|
||||
const BaseItem = require('lib/models/BaseItem');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
|
||||
usage() {
|
||||
return 'e2ee <command> [path]';
|
||||
}
|
||||
|
||||
description() {
|
||||
return _('Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, `status` and `target-status`.');
|
||||
}
|
||||
|
||||
options() {
|
||||
return [
|
||||
// This is here mostly for testing - shouldn't be used
|
||||
['-p, --password <password>', 'Use this password as master password (For security reasons, it is not recommended to use this option).'],
|
||||
['-v, --verbose', 'More verbose output for the `target-status` command'],
|
||||
];
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
// change-password
|
||||
|
||||
const options = args.options;
|
||||
|
||||
if (args.command === 'enable') {
|
||||
const password = options.password ? options.password.toString() : await this.prompt(_('Enter master password:'), { type: 'string', secure: true });
|
||||
if (!password) {
|
||||
this.stdout(_('Operation cancelled'));
|
||||
return;
|
||||
}
|
||||
|
||||
await EncryptionService.instance().generateMasterKeyAndEnableEncryption(password);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.command === 'disable') {
|
||||
await EncryptionService.instance().disableEncryption();
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.command === 'decrypt') {
|
||||
this.stdout(_('Starting decryption... Please wait as it may take several minutes depending on how much there is to decrypt.'));
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
await DecryptionWorker.instance().start();
|
||||
break;
|
||||
} catch (error) {
|
||||
if (error.code === 'masterKeyNotLoaded') {
|
||||
const masterKeyId = error.masterKeyId;
|
||||
const password = await this.prompt(_('Enter master password:'), { type: 'string', secure: true });
|
||||
if (!password) {
|
||||
this.stdout(_('Operation cancelled'));
|
||||
return;
|
||||
}
|
||||
Setting.setObjectKey('encryption.passwordCache', masterKeyId, password);
|
||||
await EncryptionService.instance().loadMasterKeysFromSettings();
|
||||
continue;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
this.stdout(_('Completed decryption.'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.command === 'status') {
|
||||
this.stdout(_('Encryption is: %s', Setting.value('encryption.enabled') ? _('Enabled') : _('Disabled')));
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.command === 'target-status') {
|
||||
const fs = require('fs-extra');
|
||||
const pathUtils = require('lib/path-utils.js');
|
||||
const fsDriver = new (require('lib/fs-driver-node.js').FsDriverNode)();
|
||||
|
||||
const targetPath = args.path;
|
||||
if (!targetPath) throw new Error('Please specify the sync target path.');
|
||||
|
||||
const dirPaths = function(targetPath) {
|
||||
let paths = [];
|
||||
fs.readdirSync(targetPath).forEach((path) => {
|
||||
paths.push(path);
|
||||
});
|
||||
return paths;
|
||||
}
|
||||
|
||||
let itemCount = 0;
|
||||
let resourceCount = 0;
|
||||
let encryptedItemCount = 0;
|
||||
let encryptedResourceCount = 0;
|
||||
let otherItemCount = 0;
|
||||
|
||||
let encryptedPaths = [];
|
||||
let decryptedPaths = [];
|
||||
|
||||
let paths = dirPaths(targetPath);
|
||||
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
const path = paths[i];
|
||||
const fullPath = targetPath + '/' + path;
|
||||
const stat = await fs.stat(fullPath);
|
||||
|
||||
// this.stdout(fullPath);
|
||||
|
||||
if (path === '.resource') {
|
||||
let resourcePaths = dirPaths(fullPath);
|
||||
for (let j = 0; j < resourcePaths.length; j++) {
|
||||
const resourcePath = resourcePaths[j];
|
||||
resourceCount++;
|
||||
const fullResourcePath = fullPath + '/' + resourcePath;
|
||||
const isEncrypted = await EncryptionService.instance().fileIsEncrypted(fullResourcePath);
|
||||
if (isEncrypted) {
|
||||
encryptedResourceCount++;
|
||||
encryptedPaths.push(fullResourcePath);
|
||||
} else {
|
||||
decryptedPaths.push(fullResourcePath);
|
||||
}
|
||||
}
|
||||
} 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);
|
||||
|
||||
if (!ItemClass.encryptionSupported()) {
|
||||
otherItemCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const isEncrypted = await EncryptionService.instance().itemIsEncrypted(item);
|
||||
|
||||
if (isEncrypted) {
|
||||
encryptedItemCount++;
|
||||
encryptedPaths.push(fullPath);
|
||||
} else {
|
||||
decryptedPaths.push(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.stdout('Encrypted items: ' + encryptedItemCount + '/' + itemCount);
|
||||
this.stdout('Encrypted resources: ' + encryptedResourceCount + '/' + resourceCount);
|
||||
this.stdout('Other items (never encrypted): ' + otherItemCount);
|
||||
|
||||
if (options.verbose) {
|
||||
this.stdout('');
|
||||
this.stdout('# Encrypted paths');
|
||||
this.stdout('');
|
||||
for (let i = 0; i < encryptedPaths.length; i++) {
|
||||
const path = encryptedPaths[i];
|
||||
this.stdout(path);
|
||||
}
|
||||
|
||||
this.stdout('');
|
||||
this.stdout('# Decrypted paths');
|
||||
this.stdout('');
|
||||
for (let i = 0; i < decryptedPaths.length; i++) {
|
||||
const path = decryptedPaths[i];
|
||||
this.stdout(path);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Command;
|
@@ -3,10 +3,10 @@ const { BaseCommand } = require('./base-command.js');
|
||||
const { uuid } = require('lib/uuid.js');
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { Folder } = require('lib/models/folder.js');
|
||||
const { Note } = require('lib/models/note.js');
|
||||
const { Setting } = require('lib/models/setting.js');
|
||||
const { BaseModel } = require('lib/base-model.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const { cliUtils } = require('./cli-utils.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
|
||||
@@ -44,6 +44,8 @@ class Command extends BaseCommand {
|
||||
if (!app().currentFolder()) throw new Error(_('No active notebook.'));
|
||||
let note = await app().loadItem(BaseModel.TYPE_NOTE, title);
|
||||
|
||||
this.encryptionCheck(note);
|
||||
|
||||
if (!note) {
|
||||
const ok = await this.prompt(_('Note does not exist: "%s". Create it?', title));
|
||||
if (!ok) return;
|
||||
@@ -76,12 +78,14 @@ class Command extends BaseCommand {
|
||||
|
||||
app().gui().showModalOverlay(_('Starting to edit note. Close the editor to get back to the prompt.'));
|
||||
await app().gui().forceRender();
|
||||
const termState = app().gui().term().saveState();
|
||||
const termState = app().gui().termSaveState();
|
||||
|
||||
const spawnSync = require('child_process').spawnSync;
|
||||
spawnSync(editorPath, editorArgs, { stdio: 'inherit' });
|
||||
const result = spawnSync(editorPath, editorArgs, { stdio: 'inherit' });
|
||||
|
||||
app().gui().term().restoreState(termState);
|
||||
if (result.error) this.stdout(_('Error opening note in editor: %s', result.error.message));
|
||||
|
||||
app().gui().termRestoreState(termState);
|
||||
app().gui().hideModalOverlay();
|
||||
app().gui().forceRender();
|
||||
|
||||
|
@@ -12,6 +12,10 @@ class Command extends BaseCommand {
|
||||
return _('Exits the application.');
|
||||
}
|
||||
|
||||
compatibleUis() {
|
||||
return ['gui'];
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
await app().exit();
|
||||
}
|
||||
|
36
CliClient/app/command-export-sync-status.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const { Database } = require('lib/database.js');
|
||||
const { app } = require('./app.js');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { ReportService } = require('lib/services/report.js');
|
||||
const fs = require('fs-extra');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
|
||||
usage() {
|
||||
return 'export-sync-status';
|
||||
}
|
||||
|
||||
description() {
|
||||
return 'Export sync status';
|
||||
}
|
||||
|
||||
hidden() {
|
||||
return true;
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
const service = new ReportService();
|
||||
const csv = await service.basicItemList({ format: 'csv' });
|
||||
const filePath = Setting.value('profileDir') + '/syncReport-' + (new Date()).getTime() + '.csv';
|
||||
await fs.writeFileSync(filePath, csv);
|
||||
this.stdout('Sync status exported to ' + filePath);
|
||||
|
||||
app().gui().showConsole();
|
||||
app().gui().maximizeConsole();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Command;
|
@@ -1,7 +1,7 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const { Exporter } = require('lib/services/exporter.js');
|
||||
const { BaseModel } = require('lib/base-model.js');
|
||||
const { Note } = require('lib/models/note.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
|
@@ -1,9 +1,9 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { BaseModel } = require('lib/base-model.js');
|
||||
const { Folder } = require('lib/models/folder.js');
|
||||
const { Note } = require('lib/models/note.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
|
||||
|
@@ -2,7 +2,7 @@ const { BaseCommand } = require('./base-command.js');
|
||||
const { app } = require('./app.js');
|
||||
const { renderCommandHelp } = require('./help-utils.js');
|
||||
const { Database } = require('lib/database.js');
|
||||
const { Setting } = require('lib/models/setting.js');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const { wrap } = require('lib/string-utils.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { cliUtils } = require('./cli-utils.js');
|
||||
@@ -18,7 +18,7 @@ class Command extends BaseCommand {
|
||||
}
|
||||
|
||||
allCommands() {
|
||||
const commands = app().commands();
|
||||
const commands = app().commands(app().uiType());
|
||||
let output = [];
|
||||
for (let n in commands) {
|
||||
if (!commands.hasOwnProperty(n)) continue;
|
||||
@@ -65,7 +65,7 @@ class Command extends BaseCommand {
|
||||
} else {
|
||||
const commandNames = this.allCommands().map((a) => a.name());
|
||||
|
||||
this.stdout(_('Type `help [command]` for more information about a command.'));
|
||||
this.stdout(_('Type `help [command]` for more information about a command; or type `help all` for the complete usage information.'));
|
||||
this.stdout('');
|
||||
this.stdout(_('The possible commands are:'));
|
||||
this.stdout('');
|
||||
|
@@ -1,8 +1,8 @@
|
||||
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('import-enex');
|
||||
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');
|
||||
|
||||
|
@@ -1,10 +1,10 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { BaseModel } = require('lib/base-model.js');
|
||||
const { Folder } = require('lib/models/folder.js');
|
||||
const { Setting } = require('lib/models/setting.js');
|
||||
const { Note } = require('lib/models/note.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const { sprintf } = require('sprintf-js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const { cliUtils } = require('./cli-utils.js');
|
||||
|
@@ -1,7 +1,7 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { Folder } = require('lib/models/folder.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const { reg } = require('lib/registry.js');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { Note } = require('lib/models/note.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { Note } = require('lib/models/note.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
|
||||
|
@@ -1,9 +1,9 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { BaseModel } = require('lib/base-model.js');
|
||||
const { Folder } = require('lib/models/folder.js');
|
||||
const { Note } = require('lib/models/note.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
|
||||
|
@@ -1,9 +1,9 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { BaseModel } = require('lib/base-model.js');
|
||||
const { Folder } = require('lib/models/folder.js');
|
||||
const { Note } = require('lib/models/note.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
|
||||
@@ -20,6 +20,7 @@ class Command extends BaseCommand {
|
||||
const name = args['name'];
|
||||
|
||||
const item = await app().loadItem('folderOrNote', pattern);
|
||||
this.encryptionCheck(item);
|
||||
if (!item) throw new Error(_('Cannot find "%s".', pattern));
|
||||
|
||||
const newItem = {
|
||||
|
@@ -1,10 +1,10 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { BaseItem } = require('lib/models/base-item.js');
|
||||
const { Folder } = require('lib/models/folder.js');
|
||||
const { Note } = require('lib/models/note.js');
|
||||
const { BaseModel } = require('lib/base-model.js');
|
||||
const BaseItem = require('lib/models/BaseItem.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const { cliUtils } = require('./cli-utils.js');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
@@ -29,7 +29,7 @@ class Command extends BaseCommand {
|
||||
|
||||
const folder = await app().loadItem(BaseModel.TYPE_FOLDER, pattern);
|
||||
if (!folder) throw new Error(_('Cannot find "%s".', pattern));
|
||||
const ok = force ? true : await this.prompt(_('Delete notebook "%s"?', folder.title), { booleanAnswerDefault: 'n' });
|
||||
const ok = force ? true : await this.prompt(_('Delete notebook? All notes within this notebook will also be deleted.'), { booleanAnswerDefault: 'n' });
|
||||
if (!ok) return;
|
||||
|
||||
await Folder.delete(folder.id);
|
||||
|
@@ -1,10 +1,10 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { BaseItem } = require('lib/models/base-item.js');
|
||||
const { Folder } = require('lib/models/folder.js');
|
||||
const { Note } = require('lib/models/note.js');
|
||||
const { BaseModel } = require('lib/base-model.js');
|
||||
const BaseItem = require('lib/models/BaseItem.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const { cliUtils } = require('./cli-utils.js');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
|
@@ -1,9 +1,9 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { BaseModel } = require('lib/base-model.js');
|
||||
const { Folder } = require('lib/models/folder.js');
|
||||
const { Note } = require('lib/models/note.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const { sprintf } = require('sprintf-js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const { uuid } = require('lib/uuid.js');
|
||||
|
@@ -1,10 +1,11 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { BaseModel } = require('lib/base-model.js');
|
||||
const { Folder } = require('lib/models/folder.js');
|
||||
const { Note } = require('lib/models/note.js');
|
||||
const { BaseItem } = require('lib/models/base-item.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const { Database } = require('lib/database.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const BaseItem = require('lib/models/BaseItem.js');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
|
||||
@@ -12,16 +13,16 @@ class Command extends BaseCommand {
|
||||
return 'set <note> <name> [value]';
|
||||
}
|
||||
|
||||
enabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
description() {
|
||||
return _('Sets the property <name> of the given <note> to the given [value].');
|
||||
}
|
||||
const fields = Note.fields();
|
||||
const s = [];
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
const f = fields[i];
|
||||
if (f.name === 'id') continue;
|
||||
s.push(f.name + ' (' + Database.enumName('fieldType', f.type) + ')');
|
||||
}
|
||||
|
||||
hidden() {
|
||||
return true;
|
||||
return _('Sets the property <name> of the given <note> to the given [value]. Possible properties are:\n\n%s', s.join(', '));
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
@@ -34,6 +35,8 @@ class Command extends BaseCommand {
|
||||
if (!notes.length) throw new Error(_('Cannot find "%s".', title));
|
||||
|
||||
for (let i = 0; i < notes.length; i++) {
|
||||
this.encryptionCheck(notes[i]);
|
||||
|
||||
let newNote = {
|
||||
id: notes[i].id,
|
||||
type_: notes[i].type_,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const { Database } = require('lib/database.js');
|
||||
const { app } = require('./app.js');
|
||||
const { Setting } = require('lib/models/setting.js');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { ReportService } = require('lib/services/report.js');
|
||||
|
||||
|
@@ -2,8 +2,8 @@ const { BaseCommand } = require('./base-command.js');
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { OneDriveApiNodeUtils } = require('./onedrive-api-node-utils.js');
|
||||
const { Setting } = require('lib/models/setting.js');
|
||||
const { BaseItem } = require('lib/models/base-item.js');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const BaseItem = require('lib/models/BaseItem.js');
|
||||
const { Synchronizer } = require('lib/synchronizer.js');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const { cliUtils } = require('./cli-utils.js');
|
||||
@@ -16,7 +16,7 @@ class Command extends BaseCommand {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.syncTarget_ = null;
|
||||
this.syncTargetId_ = null;
|
||||
this.releaseLockFn_ = null;
|
||||
this.oneDriveApiUtils_ = null;
|
||||
}
|
||||
@@ -32,7 +32,6 @@ class Command extends BaseCommand {
|
||||
options() {
|
||||
return [
|
||||
['--target <target>', _('Sync to provided target (defaults to sync.target config value)')],
|
||||
['--random-failures', 'For debugging purposes. Do not use.'],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -62,6 +61,27 @@ class Command extends BaseCommand {
|
||||
});
|
||||
}
|
||||
|
||||
async doAuth(syncTargetId) {
|
||||
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;
|
||||
}
|
||||
|
||||
cancelAuth() {
|
||||
if (this.oneDriveApiUtils_) {
|
||||
this.oneDriveApiUtils_.cancelOAuthDance();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
doingAuth() {
|
||||
return !!this.oneDriveApiUtils_;
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
this.releaseLockFn_ = null;
|
||||
|
||||
@@ -91,25 +111,24 @@ class Command extends BaseCommand {
|
||||
};
|
||||
|
||||
try {
|
||||
this.syncTarget_ = Setting.value('sync.target');
|
||||
if (args.options.target) this.syncTarget_ = args.options.target;
|
||||
this.syncTargetId_ = Setting.value('sync.target');
|
||||
if (args.options.target) this.syncTargetId_ = args.options.target;
|
||||
|
||||
if (this.syncTarget_ == Setting.SYNC_TARGET_ONEDRIVE && !reg.syncHasAuth(this.syncTarget_)) {
|
||||
const syncTarget = reg.syncTarget(this.syncTargetId_);
|
||||
|
||||
if (!syncTarget.isAuthenticated()) {
|
||||
app().gui().showConsole();
|
||||
app().gui().maximizeConsole();
|
||||
this.oneDriveApiUtils_ = new OneDriveApiNodeUtils(reg.oneDriveApi());
|
||||
const auth = await this.oneDriveApiUtils_.oauthDance({
|
||||
log: (...s) => { return this.stdout(...s); }
|
||||
});
|
||||
this.oneDriveApiUtils_ = null;
|
||||
Setting.setValue('sync.3.auth', auth ? JSON.stringify(auth) : null);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
let sync = await reg.synchronizer(this.syncTarget_);
|
||||
const sync = await syncTarget.synchronizer();
|
||||
|
||||
let options = {
|
||||
onProgress: (report) => {
|
||||
@@ -120,16 +139,15 @@ class Command extends BaseCommand {
|
||||
cliUtils.redrawDone();
|
||||
this.stdout(msg);
|
||||
},
|
||||
randomFailures: args.options['random-failures'] === true,
|
||||
};
|
||||
|
||||
this.stdout(_('Synchronisation target: %s (%s)', Setting.enumOptionLabel('sync.target', this.syncTarget_), this.syncTarget_));
|
||||
this.stdout(_('Synchronisation target: %s (%s)', Setting.enumOptionLabel('sync.target', this.syncTargetId_), this.syncTargetId_));
|
||||
|
||||
if (!sync) throw new Error(_('Cannot initialize synchroniser.'));
|
||||
|
||||
this.stdout(_('Starting synchronisation...'));
|
||||
|
||||
const contextKey = 'sync.' + this.syncTarget_ + '.context';
|
||||
const contextKey = 'sync.' + this.syncTargetId_ + '.context';
|
||||
let context = Setting.value(contextKey);
|
||||
|
||||
context = context ? JSON.parse(context) : {};
|
||||
@@ -156,26 +174,28 @@ class Command extends BaseCommand {
|
||||
}
|
||||
|
||||
async cancel() {
|
||||
if (this.oneDriveApiUtils_) {
|
||||
this.oneDriveApiUtils_.cancelOAuthDance();
|
||||
if (this.doingAuth()) {
|
||||
this.cancelAuth();
|
||||
return;
|
||||
}
|
||||
|
||||
const target = this.syncTarget_ ? this.syncTarget_ : Setting.value('sync.target');
|
||||
const syncTargetId = this.syncTargetId_ ? this.syncTargetId_ : Setting.value('sync.target');
|
||||
|
||||
cliUtils.redrawDone();
|
||||
|
||||
this.stdout(_('Cancelling... Please wait.'));
|
||||
|
||||
if (reg.syncHasAuth(target)) {
|
||||
let sync = await reg.synchronizer(target);
|
||||
const syncTarget = reg.syncTarget(syncTargetId);
|
||||
|
||||
if (syncTarget.isAuthenticated()) {
|
||||
const sync = await syncTarget.synchronizer();
|
||||
if (sync) await sync.cancel();
|
||||
} else {
|
||||
if (this.releaseLockFn_) this.releaseLockFn_();
|
||||
this.releaseLockFn_ = null;
|
||||
}
|
||||
|
||||
this.syncTarget_ = null;
|
||||
this.syncTargetId_ = null;
|
||||
}
|
||||
|
||||
cancellable() {
|
||||
|
@@ -1,8 +1,8 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { Tag } = require('lib/models/tag.js');
|
||||
const { BaseModel } = require('lib/base-model.js');
|
||||
const Tag = require('lib/models/Tag.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
|
||||
|
@@ -1,9 +1,9 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { BaseModel } = require('lib/base-model.js');
|
||||
const { Folder } = require('lib/models/folder.js');
|
||||
const { Note } = require('lib/models/note.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
@@ -25,6 +25,8 @@ class Command extends BaseCommand {
|
||||
for (let i = 0; i < notes.length; i++) {
|
||||
const note = notes[i];
|
||||
|
||||
this.encryptionCheck(note);
|
||||
|
||||
let toSave = {
|
||||
id: note.id,
|
||||
};
|
||||
|
@@ -1,9 +1,9 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { BaseModel } = require('lib/base-model.js');
|
||||
const { Folder } = require('lib/models/folder.js');
|
||||
const { Note } = require('lib/models/note.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
|
||||
const CommandDone = require('./command-done.js');
|
||||
@@ -19,7 +19,7 @@ class Command extends BaseCommand {
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
await CommandDone.handleAction(args, false);
|
||||
await CommandDone.handleAction(this, args, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { BaseModel } = require('lib/base-model.js');
|
||||
const { Folder } = require('lib/models/folder.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
|
||||
@@ -18,8 +18,8 @@ class Command extends BaseCommand {
|
||||
return { data: autocompleteFolders };
|
||||
}
|
||||
|
||||
enabled() {
|
||||
return false;
|
||||
compatibleUis() {
|
||||
return ['cli'];
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const { Setting } = require('lib/models/setting.js');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const { Logger } = require('lib/logger.js');
|
||||
const { Resource } = require('lib/models/resource.js');
|
||||
const Resource = require('lib/models/Resource.js');
|
||||
const { dirname } = require('lib/path-utils.js');
|
||||
const { FsDriverNode } = require('./fs-driver-node.js');
|
||||
const lodash = require('lodash');
|
||||
|
@@ -1,6 +1,6 @@
|
||||
const Folder = require('lib/models/folder.js').Folder;
|
||||
const Tag = require('lib/models/tag.js').Tag;
|
||||
const BaseModel = require('lib/base-model.js').BaseModel;
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Tag = require('lib/models/Tag.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const ListWidget = require('tkwidgets/ListWidget.js');
|
||||
const _ = require('lib/locale.js')._;
|
||||
|
||||
@@ -24,9 +24,9 @@ class FolderListWidget extends ListWidget {
|
||||
if (item === '-') {
|
||||
output.push('-'.repeat(this.innerWidth));
|
||||
} else if (item.type_ === Folder.modelType()) {
|
||||
output.push(item.title);
|
||||
output.push(Folder.displayTitle(item));
|
||||
} else if (item.type_ === Tag.modelType()) {
|
||||
output.push('[' + item.title + ']');
|
||||
output.push('[' + Folder.displayTitle(item) + ']');
|
||||
} else if (item.type_ === BaseModel.TYPE_SEARCH) {
|
||||
output.push(_('Search:'));
|
||||
output.push(item.title);
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const Note = require('lib/models/note.js').Note;
|
||||
const Note = require('lib/models/Note.js');
|
||||
const ListWidget = require('tkwidgets/ListWidget.js');
|
||||
|
||||
class NoteListWidget extends ListWidget {
|
||||
@@ -10,7 +10,7 @@ class NoteListWidget extends ListWidget {
|
||||
this.updateIndexFromSelectedNoteId_ = false;
|
||||
|
||||
this.itemRenderer = (note) => {
|
||||
let label = note.title; // + ' ' + note.id;
|
||||
let label = Note.displayTitle(note); // + ' ' + note.id;
|
||||
if (note.is_todo) {
|
||||
label = '[' + (note.todo_completed ? 'X' : ' ') + '] ' + label;
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const Note = require('lib/models/note.js').Note;
|
||||
const Note = require('lib/models/Note.js');
|
||||
const TextWidget = require('tkwidgets/TextWidget.js');
|
||||
|
||||
class NoteMetadataWidget extends TextWidget {
|
||||
|
@@ -1,5 +1,6 @@
|
||||
const Note = require('lib/models/note.js').Note;
|
||||
const Note = require('lib/models/Note.js');
|
||||
const TextWidget = require('tkwidgets/TextWidget.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
|
||||
class NoteWidget extends TextWidget {
|
||||
|
||||
@@ -32,11 +33,24 @@ class NoteWidget extends TextWidget {
|
||||
this.reloadNote();
|
||||
}
|
||||
|
||||
welcomeText() {
|
||||
return _('Welcome to Joplin!\n\nType `:help shortcuts` for the list of keyboard shortcuts, or just `:help` for usage information.\n\nFor example, to create a notebook press `mb`; to create a note press `mn`.');
|
||||
}
|
||||
|
||||
reloadNote() {
|
||||
if (this.noteId_) {
|
||||
if (!this.noteId_ && !this.notes.length) {
|
||||
this.text = this.welcomeText();
|
||||
this.scrollTop = 0;
|
||||
} else if (this.noteId_) {
|
||||
this.doAsync('loadNote', async () => {
|
||||
this.note_ = await Note.load(this.noteId_);
|
||||
this.text = this.note_ ? this.note_.title + "\n\n" + this.note_.body : '';
|
||||
|
||||
if (this.note_ && this.note_.encryption_applied) {
|
||||
this.text = _('One or more items are currently encrypted and you may need to supply a master password. To do so please type `e2ee decrypt`. If you have already supplied the password, the encrypted items are being decrypted in the background and will be available soon.');
|
||||
} else {
|
||||
this.text = this.note_ ? this.note_.title + "\n\n" + this.note_.body : '';
|
||||
}
|
||||
|
||||
if (this.lastLoadedNoteId_ !== this.noteId_) this.scrollTop = 0;
|
||||
this.lastLoadedNoteId_ = this.noteId_;
|
||||
});
|
||||
|
@@ -2,6 +2,7 @@ const BaseWidget = require('tkwidgets/BaseWidget.js');
|
||||
const chalk = require('chalk');
|
||||
const termutils = require('tkwidgets/framework/termutils.js');
|
||||
const stripAnsi = require('strip-ansi');
|
||||
const { handleAutocompletion } = require('../autocompletion.js');
|
||||
|
||||
class StatusBarWidget extends BaseWidget {
|
||||
|
||||
@@ -41,6 +42,7 @@ class StatusBarWidget extends BaseWidget {
|
||||
};
|
||||
|
||||
if ('cursorPosition' in options) this.promptState_.cursorPosition = options.cursorPosition;
|
||||
if ('secure' in options) this.promptState_.secure = options.secure;
|
||||
|
||||
this.promptState_.promise = new Promise((resolve, reject) => {
|
||||
this.promptState_.resolve = resolve;
|
||||
@@ -104,13 +106,19 @@ class StatusBarWidget extends BaseWidget {
|
||||
|
||||
this.term.showCursor(true);
|
||||
|
||||
const isSecurePrompt = !!this.promptState_.secure;
|
||||
|
||||
let options = {
|
||||
cancelable: true,
|
||||
history: this.history,
|
||||
default: this.promptState_.initialText,
|
||||
autoComplete: handleAutocompletion,
|
||||
autoCompleteHint : true,
|
||||
autoCompleteMenu : true,
|
||||
};
|
||||
|
||||
if ('cursorPosition' in this.promptState_) options.cursorPosition = this.promptState_.cursorPosition;
|
||||
if (isSecurePrompt) options.echoChar = true;
|
||||
|
||||
this.inputEventEmitter_ = this.term.inputField(options, (error, input) => {
|
||||
let resolveResult = null;
|
||||
@@ -125,7 +133,7 @@ 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 (input && input.length > 1) this.history_.push(input);
|
||||
if (!isSecurePrompt && input && input.length > 1) this.history_.push(input);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,4 +167,4 @@ class StatusBarWidget extends BaseWidget {
|
||||
|
||||
}
|
||||
|
||||
module.exports = StatusBarWidget;
|
||||
module.exports = StatusBarWidget;
|
||||
|
@@ -1,6 +1,6 @@
|
||||
const fs = require('fs-extra');
|
||||
const { wrap } = require('lib/string-utils.js');
|
||||
const { Setting } = require('lib/models/setting.js');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const { fileExtension, basename, dirname } = require('lib/path-utils.js');
|
||||
const { _, setLocale, languageCode } = require('lib/locale.js');
|
||||
|
||||
|
@@ -1,26 +1,36 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Loading time: 20170803: 1.5s with no commands
|
||||
|
||||
// Make it possible to require("/lib/...") without specifying full path
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const compareVersion = require('compare-version');
|
||||
const nodeVersion = process && process.versions && process.versions.node ? process.versions.node : '0.0.0';
|
||||
if (compareVersion(nodeVersion, '8.0.0') < 0) {
|
||||
console.error('Joplin requires Node 8+. Detected version ' + nodeVersion);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const { app } = require('./app.js');
|
||||
const { BaseModel } = require('lib/base-model.js');
|
||||
const { Folder } = require('lib/models/folder.js');
|
||||
const { Resource } = require('lib/models/resource.js');
|
||||
const { BaseItem } = require('lib/models/base-item.js');
|
||||
const { Note } = require('lib/models/note.js');
|
||||
const { Tag } = require('lib/models/tag.js');
|
||||
const { NoteTag } = require('lib/models/note-tag.js');
|
||||
const { Setting } = require('lib/models/setting.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Resource = require('lib/models/Resource.js');
|
||||
const BaseItem = require('lib/models/BaseItem.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const Tag = require('lib/models/Tag.js');
|
||||
const NoteTag = require('lib/models/NoteTag.js');
|
||||
const MasterKey = require('lib/models/MasterKey');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
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.
|
||||
@@ -29,6 +39,7 @@ BaseItem.loadClass('Folder', Folder);
|
||||
BaseItem.loadClass('Resource', Resource);
|
||||
BaseItem.loadClass('Tag', Tag);
|
||||
BaseItem.loadClass('NoteTag', NoteTag);
|
||||
BaseItem.loadClass('MasterKey', MasterKey);
|
||||
|
||||
Setting.setConstant('appId', 'net.cozic.joplin-cli');
|
||||
Setting.setConstant('appType', 'cli');
|
||||
|
@@ -1,3 +0,0 @@
|
||||
#/bin/bash
|
||||
CLIENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
NODE_PATH="$CLIENT_DIR/build" node "$CLIENT_DIR/build/build-translation.js" --silent
|