Compare commits
438 Commits
android-v1
...
android-v1
Author | SHA1 | Date | |
---|---|---|---|
|
8c1d13b364 | ||
|
7ad407a8c8 | ||
|
bd02704384 | ||
|
03831babcd | ||
|
cfe61bbb9b | ||
|
a3bfb19059 | ||
|
e5f116fd2d | ||
|
a7f3a70ba4 | ||
|
1607a57fb7 | ||
|
ecd7690743 | ||
|
47a2087a91 | ||
|
7653764403 | ||
|
b7c8d5b9b1 | ||
|
fdac132905 | ||
|
60b993412e | ||
|
697c914c77 | ||
|
8c17ecb3d9 | ||
|
4c93121102 | ||
|
5efe9b99c2 | ||
|
3249763091 | ||
|
60964a4224 | ||
|
805a5399b5 | ||
|
0a39e4ccb0 | ||
|
f8cb908e0f | ||
|
d2aceed34e | ||
|
e6d9d83d92 | ||
|
1a1c87b5c4 | ||
|
5bcec583bb | ||
|
692606292f | ||
|
1782e55c66 | ||
|
707828ca76 | ||
|
5b0a817d69 | ||
|
23e77efc83 | ||
|
03c7368d71 | ||
|
fdc80f0663 | ||
|
4e708ea021 | ||
|
49898d01b5 | ||
|
8036923aca | ||
|
76c8f777ca | ||
|
e84bafd034 | ||
|
37fc42eb73 | ||
|
a0a89112bd | ||
|
4842f83aae | ||
|
69b185ff5e | ||
|
d8c2f0cbba | ||
|
5b05f22476 | ||
|
8012d1fcfc | ||
|
40753296c4 | ||
|
7baeb223f3 | ||
|
affacf472f | ||
|
896925ca49 | ||
|
78628c0f42 | ||
|
ee650c1ef4 | ||
|
5b65fae49d | ||
|
2d5d8ceb1d | ||
|
de214c8695 | ||
|
ca2446aef0 | ||
|
0ca96ee8be | ||
|
0c82c108ec | ||
|
15f09ef169 | ||
|
45cca9e002 | ||
|
76ecfd0234 | ||
|
927d46b2e4 | ||
|
3353598041 | ||
|
e0b7cbd70b | ||
|
21d06ad9a6 | ||
|
9e227200af | ||
|
bac546c090 | ||
|
058b6265a6 | ||
|
d4234b8921 | ||
|
18762cd4d5 | ||
|
9a2a223650 | ||
|
a0ad2b24b1 | ||
|
211c02c7de | ||
|
a3279ab164 | ||
|
d0ce79d81b | ||
|
e896be48f0 | ||
|
a806d753c4 | ||
|
ab4fd9ed53 | ||
|
9c98fb5312 | ||
|
c0dd8d0332 | ||
|
60a1f96b4f | ||
|
40dfb730dc | ||
|
4e72a8f3a5 | ||
|
3da32f007e | ||
|
4af8bb8e8d | ||
|
a6f190766c | ||
|
fa3f0d2071 | ||
|
8d6cfdc292 | ||
|
48f0c1c37b | ||
|
17584b52af | ||
|
c05bc899eb | ||
|
c9f6ce7496 | ||
|
67d2c454ee | ||
|
83c3c027d0 | ||
|
3cac187023 | ||
|
3800b0bf47 | ||
|
41b2eb8871 | ||
|
bc84cdda7d | ||
|
6d77394196 | ||
|
968bba4d8e | ||
|
cdd8090f33 | ||
|
1be26e4497 | ||
|
cc51ba4f90 | ||
|
85984109b5 | ||
|
689a12cdfb | ||
|
abd74db112 | ||
|
cb06dab16f | ||
|
d2a7e46c1a | ||
|
2997b0f2c4 | ||
|
f450ef09cc | ||
|
316746605e | ||
|
5522d11eed | ||
|
67266af1ae | ||
|
273c0f432c | ||
|
d346cdb897 | ||
|
57a4b48c9b | ||
|
c3b1018929 | ||
|
a2ba64ccf5 | ||
|
26d91e355a | ||
|
7bf394d8d5 | ||
|
cdf6f9c436 | ||
|
dece5d8de7 | ||
|
33b8da98af | ||
|
0386534b3a | ||
|
885858dcb4 | ||
|
8c5d82fab2 | ||
|
c683c1b8ec | ||
|
cde5aa9b06 | ||
|
d3ba6798d7 | ||
|
1f42c10c2a | ||
|
14f6108e57 | ||
|
9512a08810 | ||
|
78b8648945 | ||
|
71e01e5566 | ||
|
01614b5a13 | ||
|
7153c06e88 | ||
|
30969f8ab6 | ||
|
19f9c4f540 | ||
|
5b5e07d4dc | ||
|
4ab1fb3ec5 | ||
|
9dedb832fc | ||
|
b8b487991d | ||
|
f10ba28e0c | ||
|
22398e69c5 | ||
|
0eb51e6bb0 | ||
|
9436075fdf | ||
|
409494afbb | ||
|
a544ebd451 | ||
|
8350a6cc12 | ||
|
b5e7dc5304 | ||
|
a11171d73a | ||
|
4706abbc06 | ||
|
58b307ba02 | ||
|
3281ab05b1 | ||
|
3a32e79001 | ||
|
d91b3624e9 | ||
|
d02ced59f8 | ||
|
ba3abf7c4c | ||
|
cc24398e71 | ||
|
27d0a8f6e7 | ||
|
4788c46e2c | ||
|
0370c7b627 | ||
|
5cbae74086 | ||
|
008e30bdb7 | ||
|
a5f17fad58 | ||
|
22c3646fc6 | ||
|
a3e7f0b5ef | ||
|
9f5da92ab4 | ||
|
fdafe3b947 | ||
|
6dec711a0a | ||
|
ec67bc7f1a | ||
|
47aaf639b3 | ||
|
51233c2745 | ||
|
90bc84c010 | ||
|
7f1e684dab | ||
|
5f28d0ec24 | ||
|
348c4ad3a3 | ||
|
f90cc8d67d | ||
|
d3e9ffcaea | ||
|
b0a4a10dcc | ||
|
8ef5c96cb6 | ||
|
6be36ffe17 | ||
|
7d7975daf4 | ||
|
c98644b72f | ||
|
8a097fb79c | ||
|
f71e7f4fd3 | ||
|
9c8add97e7 | ||
|
b099c811cc | ||
|
a8ae0f8078 | ||
|
d355169b60 | ||
|
08295525de | ||
|
fdcf27fc65 | ||
|
f2c82b05d9 | ||
|
add9dda759 | ||
|
fbba4a1ec4 | ||
|
154d303463 | ||
|
fdef2db232 | ||
|
d49fa8e42b | ||
|
871d3cb87d | ||
|
f1d751b356 | ||
|
60c1939d26 | ||
|
d2aaac22e5 | ||
|
c6842a8591 | ||
|
60e9abdd61 | ||
|
9796630aa2 | ||
|
7230cdea33 | ||
|
6460907976 | ||
|
d7a50a1b48 | ||
|
4a88343372 | ||
|
74c2bbc2f0 | ||
|
5ed5d16716 | ||
|
43083b0b7a | ||
|
a4e5054008 | ||
|
02eb2f2e45 | ||
|
5d015bf746 | ||
|
ce5db5a5c1 | ||
|
cdcc3902c5 | ||
|
309835f2fe | ||
|
eca0ab0ef6 | ||
|
4ce35ee1ed | ||
|
8856456afd | ||
|
4259d900f4 | ||
|
438c448ef7 | ||
|
0fb5b35212 | ||
|
b69efb4970 | ||
|
44ea237538 | ||
|
a58316b24c | ||
|
df95d01f6e | ||
|
d6924893e5 | ||
|
0f04ea4f70 | ||
|
4b8026cf83 | ||
|
a98a586295 | ||
|
29ec7ba03a | ||
|
a35cc23d28 | ||
|
20a8ddd841 | ||
|
774fc9ce53 | ||
|
84ab395fae | ||
|
edc4dc5801 | ||
|
6e128a7285 | ||
|
ff977cebaf | ||
|
5bcd5f050a | ||
|
fe57f163f3 | ||
|
cb24db4e39 | ||
|
e93d96193c | ||
|
637a4dc1f9 | ||
|
2c522637ef | ||
|
8fbb1fd246 | ||
|
75b7e7d999 | ||
|
7e2e901035 | ||
|
a73a1f896c | ||
|
ae7f0e8ffb | ||
|
08343e6be9 | ||
|
9cb3496159 | ||
|
37b035d0d0 | ||
|
f37ac8b5de | ||
|
343d04b05d | ||
|
e92741edd6 | ||
|
8565cd7d40 | ||
|
58e299383d | ||
|
bcb44aa532 | ||
|
0c552feb57 | ||
|
cd1aa57243 | ||
|
c9098b0489 | ||
|
117ce52597 | ||
|
1836b9a0b0 | ||
|
563a4b7bd8 | ||
|
2a5648d1a7 | ||
|
5b425f9178 | ||
|
820e32ddca | ||
|
b873e706ca | ||
|
f0f6e7c856 | ||
|
1ff3c7d074 | ||
|
393bb8993e | ||
|
00ddb1eb64 | ||
|
cd518776a9 | ||
|
391f7d22a3 | ||
|
effbf10571 | ||
|
4405e94e0c | ||
|
69f1b72127 | ||
|
383fa2e278 | ||
|
573100c203 | ||
|
348efdd7b6 | ||
|
cb9cc95e6a | ||
|
1994b334fa | ||
|
3e5a9cdb97 | ||
|
ae863c95c7 | ||
|
e89e5efb62 | ||
|
9f2ce06829 | ||
|
ec89ebc6b0 | ||
|
f3e9668eb7 | ||
|
cc7e2fc456 | ||
|
172d925f0f | ||
|
8a8ecaade3 | ||
|
d21a3f0bca | ||
|
9322212601 | ||
|
691eefec2f | ||
|
50b66cceca | ||
|
c7c57ab2a5 | ||
|
558b6443bc | ||
|
335b43ead4 | ||
|
e648392330 | ||
|
ab29d7e872 | ||
|
d7fae6b5b8 | ||
|
4ba4910a9c | ||
|
fe9a037cf9 | ||
|
2f14832c34 | ||
|
52ace55db0 | ||
|
224a4d786b | ||
|
3ea97ad9ff | ||
|
e7a56bb2b1 | ||
|
e4aed469d7 | ||
|
15a42a3729 | ||
|
0b9e007b46 | ||
|
88561a6c3c | ||
|
e6b77c3381 | ||
|
f7e1589476 | ||
|
0379523eaf | ||
|
5db7502fe4 | ||
|
1578188fde | ||
|
e8e1a0fe4d | ||
|
8059009ff3 | ||
|
32865f065c | ||
|
e03ef78049 | ||
|
45a820bb35 | ||
|
3c26159b79 | ||
|
cf67e0d4af | ||
|
1b2767167d | ||
|
f07bb5c275 | ||
|
0e2cc418e2 | ||
|
0340456d55 | ||
|
7aea2cec69 | ||
|
fa83107840 | ||
|
bb0bf46f81 | ||
|
694c3fed2d | ||
|
772e39b710 | ||
|
05e0a2c29d | ||
|
78e0efb95f | ||
|
a5f749cfd2 | ||
|
3d6c932e1b | ||
|
4488a1b95f | ||
|
e2808a90c6 | ||
|
755a972e02 | ||
|
8b1de22049 | ||
|
a9735123b7 | ||
|
5ccafa2838 | ||
|
e2926a4f82 | ||
|
09df315639 | ||
|
5a9b3b6c7c | ||
|
6da6f35ddd | ||
|
dcb5590842 | ||
|
5135c8a782 | ||
|
1b2f4fb036 | ||
|
76a4a445f0 | ||
|
20abb125a5 | ||
|
be9e50b4a1 | ||
|
02bfcf577d | ||
|
038efa10f2 | ||
|
dfa692569b | ||
|
9abc6a2e44 | ||
|
11f23f4e00 | ||
|
6a7d40d171 | ||
|
bf5601429e | ||
|
73ae8aaf2f | ||
|
7eb7bd98f3 | ||
|
10e22654ea | ||
|
ccfc80ad04 | ||
|
5e95278084 | ||
|
d69ba6bc75 | ||
|
d28fbe2d3b | ||
|
415e7b84da | ||
|
ac4986b620 | ||
|
9a4f4cbb65 | ||
|
8e32957111 | ||
|
91aa3703d4 | ||
|
a889762056 | ||
|
6478d6c9c9 | ||
|
7a681d0a4a | ||
|
83b6eba8bd | ||
|
2766ded5f6 | ||
|
ca0d966ed9 | ||
|
386c583b0e | ||
|
b3d34ad7e9 | ||
|
ba5c636dda | ||
|
ea16f6e0b1 | ||
|
0dd0dc5489 | ||
|
f3ab21ff43 | ||
|
5ac6b46efd | ||
|
6548f30a4b | ||
|
849d7983f6 | ||
|
e32e4423db | ||
|
7f5bf131a8 | ||
|
87a639df2b | ||
|
bdd8eab87e | ||
|
b9e5c8a387 | ||
|
d646a2dd01 | ||
|
71a3a0176e | ||
|
a363d119cf | ||
|
ff08bdbc0b | ||
|
71efff6827 | ||
|
7595fe4a8c | ||
|
7697e75466 | ||
|
b8fbaa2029 | ||
|
38bc750ecf | ||
|
6cfacb1a48 | ||
|
0b9078d034 | ||
|
86dc72b204 | ||
|
64b7bc3d62 | ||
|
086f9e1123 | ||
|
4fe70fe8ee | ||
|
95a1f40404 | ||
|
88f04509ee | ||
|
7eebd544d6 | ||
|
e369a8decf | ||
|
ad8054ba4b | ||
|
b47cb4e29a | ||
|
8c42ddf6c3 | ||
|
f7fcabbf41 | ||
|
38a51070fc | ||
|
44fa099a77 | ||
|
af6f3999df | ||
|
6fbeb35951 | ||
|
b2eadffde0 | ||
|
200ba2775f | ||
|
39ba021a79 | ||
|
2c6b291b9b | ||
|
770846be2e | ||
|
af4aa01b75 | ||
|
1bf2bec805 | ||
|
3a41ac9be0 | ||
|
f2c9cdd7f1 | ||
|
058f418cc7 | ||
|
5fa84b0dfb | ||
|
ec8ec3e38d | ||
|
0e6190b42b | ||
|
fcfee36c8c | ||
|
1d3d3b99bb | ||
|
2f80bf9647 |
48
.eslintignore
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
*.min.js
|
||||||
|
.git/
|
||||||
|
.github/
|
||||||
|
_mydocs/
|
||||||
|
_releases/
|
||||||
|
Assets/
|
||||||
|
CliClient/build
|
||||||
|
CliClient/locales
|
||||||
|
CliClient/node_modules
|
||||||
|
CliClient/tests-build
|
||||||
|
CliClient/tests/enex_to_md
|
||||||
|
CliClient/tests/html_to_md
|
||||||
|
CliClient/tests/logs
|
||||||
|
CliClient/tests/support
|
||||||
|
CliClient/tests/sync
|
||||||
|
CliClient/tests/tmp
|
||||||
|
Clipper/joplin-webclipper/content_scripts/JSDOMParser.js
|
||||||
|
Clipper/joplin-webclipper/content_scripts/Readability-readerable.js
|
||||||
|
Clipper/joplin-webclipper/content_scripts/Readability.js
|
||||||
|
Clipper/joplin-webclipper/dist
|
||||||
|
Clipper/joplin-webclipper/icons
|
||||||
|
Clipper/joplin-webclipper/popup/build
|
||||||
|
Clipper/joplin-webclipper/popup/node_modules
|
||||||
|
docs/
|
||||||
|
ElectronClient/app/dist
|
||||||
|
ElectronClient/app/lib
|
||||||
|
ElectronClient/app/lib/vendor/sjcl-rn.js
|
||||||
|
ElectronClient/app/lib/vendor/sjcl.js
|
||||||
|
ElectronClient/app/locales
|
||||||
|
ElectronClient/app/node_modules
|
||||||
|
highlight.pack.js
|
||||||
|
node_modules/
|
||||||
|
ReactNativeClient/android
|
||||||
|
ReactNativeClient/ios
|
||||||
|
ReactNativeClient/lib/vendor/
|
||||||
|
ReactNativeClient/lib/welcomeAssets.js
|
||||||
|
ReactNativeClient/locales
|
||||||
|
ReactNativeClient/node_modules
|
||||||
|
readme/
|
||||||
|
Tools/node_modules
|
||||||
|
Tools/PortableAppsLauncher
|
||||||
|
Server/.git/
|
||||||
|
Server/.github/
|
||||||
|
Server/docs/
|
||||||
|
Server/dist/
|
||||||
|
Server/bin/
|
||||||
|
Server/node_modules/
|
||||||
|
ElectronClient/app/packageInfo.js
|
85
.eslintrc.js
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
module.exports = {
|
||||||
|
'env': {
|
||||||
|
'browser': true,
|
||||||
|
'es6': true,
|
||||||
|
'node': true,
|
||||||
|
},
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
'extends': ['eslint:recommended'],
|
||||||
|
'globals': {
|
||||||
|
'Atomics': 'readonly',
|
||||||
|
'SharedArrayBuffer': 'readonly',
|
||||||
|
|
||||||
|
// Jasmine variables
|
||||||
|
'expect': 'readonly',
|
||||||
|
'describe': 'readonly',
|
||||||
|
'it': 'readonly',
|
||||||
|
'beforeEach': 'readonly',
|
||||||
|
'jasmine': 'readonly',
|
||||||
|
|
||||||
|
// React Native variables
|
||||||
|
'__DEV__': 'readonly',
|
||||||
|
|
||||||
|
// Clipper variables
|
||||||
|
'browserSupportsPromises_': true,
|
||||||
|
'chrome': 'readonly',
|
||||||
|
'browser': 'readonly',
|
||||||
|
},
|
||||||
|
'parserOptions': {
|
||||||
|
'ecmaVersion': 2018,
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"jsx": true,
|
||||||
|
},
|
||||||
|
"sourceType": "module",
|
||||||
|
},
|
||||||
|
'rules': {
|
||||||
|
// -------------------------------
|
||||||
|
// Code correctness
|
||||||
|
// -------------------------------
|
||||||
|
"react/jsx-uses-react": "error",
|
||||||
|
"react/jsx-uses-vars": "error",
|
||||||
|
"no-unused-vars": "error",
|
||||||
|
"no-constant-condition": 0,
|
||||||
|
"no-prototype-builtins": 0,
|
||||||
|
// This error is always a false positive so far since it detects
|
||||||
|
// possible race conditions in contexts where we know it cannot happen.
|
||||||
|
"require-atomic-updates": 0,
|
||||||
|
// "no-lonely-if": "error",
|
||||||
|
|
||||||
|
// -------------------------------
|
||||||
|
// Formatting
|
||||||
|
// -------------------------------
|
||||||
|
"space-in-parens": ["error", "never"],
|
||||||
|
"semi": ["error", "always"],
|
||||||
|
"eol-last": ["error", "always"],
|
||||||
|
"quotes": ["error", "single"],
|
||||||
|
"indent": ["error", "tab"],
|
||||||
|
"comma-dangle": ["error", "always-multiline"],
|
||||||
|
"no-trailing-spaces": "error",
|
||||||
|
"linebreak-style": ["error", "unix"],
|
||||||
|
"prefer-template": ["error"],
|
||||||
|
"template-curly-spacing": ["error", "never"],
|
||||||
|
"key-spacing": ["error", {
|
||||||
|
"beforeColon": false,
|
||||||
|
"afterColon": true,
|
||||||
|
"mode": "strict"
|
||||||
|
}],
|
||||||
|
"block-spacing": ["error"],
|
||||||
|
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
|
||||||
|
"no-spaced-func": ["error"],
|
||||||
|
"func-call-spacing": ["error"],
|
||||||
|
"space-before-function-paren": ["error", {
|
||||||
|
"anonymous": "never",
|
||||||
|
"named": "never",
|
||||||
|
"asyncArrow": "always"
|
||||||
|
}],
|
||||||
|
"multiline-comment-style": ["error", "separate-lines"],
|
||||||
|
"space-before-blocks": "error",
|
||||||
|
"spaced-comment": ["error", "always"],
|
||||||
|
"keyword-spacing": ["error", { "before": true, "after": true }]
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"react",
|
||||||
|
"@typescript-eslint",
|
||||||
|
],
|
||||||
|
};
|
1
.github/FUNDING.yml
vendored
@@ -1,4 +1,5 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
patreon: joplin
|
patreon: joplin
|
||||||
|
github: laurent22
|
||||||
custom: https://joplinapp.org/donate/
|
custom: https://joplinapp.org/donate/
|
||||||
|
5
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,4 +1,9 @@
|
|||||||
👉 Please follow one of these issue templates:
|
👉 Please follow one of these issue templates:
|
||||||
- https://github.com/laurent22/joplin/issues/new/choose
|
- https://github.com/laurent22/joplin/issues/new/choose
|
||||||
|
|
||||||
|
⚠️
|
||||||
|
The GitHub issue tracker is for **bugs** and **security issues** ONLY. For feature requests and support, please use the forum:
|
||||||
|
https://discourse.joplinapp.org/
|
||||||
|
⚠️
|
||||||
|
|
||||||
Note: to keep the backlog clean and actionable, issues may be immediately closed if they do not follow one of the above issue templates.
|
Note: to keep the backlog clean and actionable, issues may be immediately closed if they do not follow one of the above issue templates.
|
||||||
|
5
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -12,6 +12,11 @@ labels: 'bug'
|
|||||||
Please test using the latest Joplin release to make sure your issue has not already been fixed.
|
Please test using the latest Joplin release to make sure your issue has not already been fixed.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
IMPORTANT: If you are reporting a clipper bug, please include an example URL that shows the issue.
|
||||||
|
Without the URL the issue is likely to be closed.
|
||||||
|
-->
|
||||||
|
|
||||||
## Environment
|
## Environment
|
||||||
|
|
||||||
Joplin version:
|
Joplin version:
|
||||||
|
33
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,33 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest a feature for Joplin.
|
|
||||||
title: '[Feature request] '
|
|
||||||
labels: 'feature request'
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Please search open issues first - many features have already been requested!
|
|
||||||
-->
|
|
||||||
|
|
||||||
## Has it been discussed in the forum? Link to topic.
|
|
||||||
<!--
|
|
||||||
Feature requests should be discussed in the forum first. https://discourse.joplinapp.org
|
|
||||||
Please provide a link to the topic.
|
|
||||||
-->
|
|
||||||
|
|
||||||
|
|
||||||
## Is your feature request related to a problem? Please describe.
|
|
||||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
|
||||||
|
|
||||||
|
|
||||||
## Describe the solution you'd like
|
|
||||||
<!-- A clear and concise description of what you want to happen. -->
|
|
||||||
|
|
||||||
|
|
||||||
## Describe alternatives you've considered
|
|
||||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
|
||||||
|
|
||||||
|
|
||||||
## Additional context
|
|
||||||
<!-- Add any other context or screenshots about the feature request here. -->
|
|
6
.github/ISSUE_TEMPLATE/question.md
vendored
@@ -1,12 +1,12 @@
|
|||||||
---
|
---
|
||||||
name: "🤔 Questions and Help"
|
name: "🤔 Questions and Help"
|
||||||
about: The issue tracker is not for questions. Please ask questions on https://discourse.joplinapp.org/.
|
about: The issue tracker is not for questions. Please ask questions on https://discourse.joplinapp.org/.
|
||||||
title: 'Question: '
|
title: ''
|
||||||
labels: 'question'
|
labels: 'invalid'
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
🚨 The issue tracker is not for questions. 🚨
|
⚠🚨⛔ The issue tracker is not for questions. ⛔🚨⚠
|
||||||
|
|
||||||
As it happens, support requests that are created as issues are likely to be closed. We want to make sure you are able to find the help you seek.
|
As it happens, support requests that are created as issues are likely to be closed. We want to make sure you are able to find the help you seek.
|
||||||
|
|
||||||
|
2
.github/PULL_REQUEST_TEMPLATE
vendored
@@ -6,6 +6,8 @@ Please prefix the title with the platform you are targetting:
|
|||||||
- "Mobile" for the mobile app (or "Android" / "iOS" if the pull request only applies to one of the mobile platforms)
|
- "Mobile" for the mobile app (or "Android" / "iOS" if the pull request only applies to one of the mobile platforms)
|
||||||
- "CLI" for the CLI app
|
- "CLI" for the CLI app
|
||||||
|
|
||||||
|
If it's not related to any platform (such as a translation, change to the documentation, etc.), simply don't add a platform.
|
||||||
|
|
||||||
For example: "Desktop: Added new setting to change font", or "Mobile: Fixed config screen error"
|
For example: "Desktop: Added new setting to change font", or "Mobile: Fixed config screen error"
|
||||||
|
|
||||||
PLEASE READ THE GUIDE FIRST: https://github.com/laurent22/joplin/blob/master/CONTRIBUTING.md
|
PLEASE READ THE GUIDE FIRST: https://github.com/laurent22/joplin/blob/master/CONTRIBUTING.md
|
||||||
|
25
.github/lock.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Configuration for Lock Threads - https://github.com/dessant/lock-threads
|
||||||
|
# Number of days of inactivity before a closed issue or pull request is locked
|
||||||
|
daysUntilLock: 7
|
||||||
|
# Skip issues and pull requests created before a given timestamp. Timestamp must
|
||||||
|
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
|
||||||
|
skipCreatedBefore: false
|
||||||
|
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
|
||||||
|
exemptLabels: []
|
||||||
|
# Label to add before locking, such as `outdated`. Set to `false` to disable
|
||||||
|
lockLabel: false
|
||||||
|
# Comment to post before locking. Set to `false` to disable
|
||||||
|
lockComment: false
|
||||||
|
# Assign `resolved` as the reason for locking. Set to `false` to disable
|
||||||
|
setLockReason: false
|
||||||
|
# Limit to only `issues` or `pulls`
|
||||||
|
only: issues
|
||||||
|
# Optionally, specify configuration settings just for `issues` or `pulls`
|
||||||
|
# issues:
|
||||||
|
# exemptLabels:
|
||||||
|
# - help-wanted
|
||||||
|
# lockLabel: outdated
|
||||||
|
# pulls:
|
||||||
|
# daysUntilLock: 30
|
||||||
|
# Repository to extend settings from
|
||||||
|
# _extends: repo
|
2
.github/stale.yml
vendored
@@ -17,7 +17,7 @@ staleLabel: stale
|
|||||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||||
markComment: >
|
markComment: >
|
||||||
Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs.
|
Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs.
|
||||||
You may also label this issue as "backlog" and I will leave it open.
|
You may comment on the issue and I will leave it open.
|
||||||
Thank you for your contributions.
|
Thank you for your contributions.
|
||||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||||
closeComment: >
|
closeComment: >
|
||||||
|
3
.gitignore
vendored
@@ -42,4 +42,5 @@ ReactNativeClient/lib/csstojs/
|
|||||||
ReactNativeClient/lib/rnInjectedJs/
|
ReactNativeClient/lib/rnInjectedJs/
|
||||||
ElectronClient/app/gui/note-viewer/fonts/
|
ElectronClient/app/gui/note-viewer/fonts/
|
||||||
ElectronClient/app/gui/note-viewer/lib.js
|
ElectronClient/app/gui/note-viewer/lib.js
|
||||||
Tools/commit_hook.txt
|
Tools/commit_hook.txt
|
||||||
|
.vscode/*
|
||||||
|
45
.travis.yml
@@ -1,5 +1,5 @@
|
|||||||
# Only build tags (Doesn't work - doesn't build anything)
|
# Only build tags (Doesn't work - doesn't build anything)
|
||||||
if: tag IS present
|
if: tag IS present OR type = pull_request
|
||||||
|
|
||||||
rvm: 2.3.3
|
rvm: 2.3.3
|
||||||
|
|
||||||
@@ -54,8 +54,47 @@ before_install:
|
|||||||
|
|
||||||
script:
|
script:
|
||||||
- |
|
- |
|
||||||
|
# Install tools
|
||||||
|
npm install
|
||||||
cd Tools
|
cd Tools
|
||||||
npm install
|
npm install
|
||||||
cd ../ElectronClient/app
|
cd ..
|
||||||
|
|
||||||
|
# Run test units.
|
||||||
|
# Only do it for pull requests because Travis randomly fails to run them
|
||||||
|
# and that would break the desktop release.
|
||||||
|
if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
|
||||||
|
cd CliClient
|
||||||
|
npm install
|
||||||
|
./run_test.sh
|
||||||
|
testResult=$?
|
||||||
|
if [ $testResult -ne 0 ]; then
|
||||||
|
exit $testResult
|
||||||
|
fi
|
||||||
|
cd ..
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run linter for pull requests only - this is so that
|
||||||
|
# bypassing eslint is allowed for urgent fixes.
|
||||||
|
if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
|
||||||
|
npm run linter-ci ./
|
||||||
|
testResult=$?
|
||||||
|
if [ $testResult -ne 0 ]; then
|
||||||
|
exit $testResult
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Find out if we should run the build or not. Electron-builder gets stuck when
|
||||||
|
# builing PRs so we disable it in this case. The Linux build should provide
|
||||||
|
# enough info if the app builds or not.
|
||||||
|
# https://github.com/electron-userland/electron-builder/issues/4263
|
||||||
|
if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
|
||||||
|
if [ "$TRAVIS_OS_NAME" == "osx" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Prepare the Electron app and build it
|
||||||
|
cd ElectronClient/app
|
||||||
rsync -aP --delete ../../ReactNativeClient/lib/ lib/
|
rsync -aP --delete ../../ReactNativeClient/lib/ lib/
|
||||||
npm install && yarn dist
|
npm install && USE_HARD_LINKS=false yarn dist
|
||||||
|
BIN
Assets/GooglePlayStoreIcon.psd
Normal file
BIN
Assets/IconBackgroundSquare.png
Normal file
After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 36 KiB |
19
Assets/JoplinIcon.svg
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 682.66669 682.66669" height="682.66669" width="682.66669" xml:space="preserve" id="svg2" version="1.1">
|
||||||
|
<defs id="defs6">
|
||||||
|
<linearGradient id="linearGradient26" spreadMethod="pad" gradientTransform="matrix(-4387.91,4387.91,4387.91,4387.91,4753.95,366.05)" gradientUnits="userSpaceOnUse" y2="0" x2="1" y1="0" x1="0">
|
||||||
|
<stop id="stop22" offset="0" style="stop-opacity:1;stop-color:#004caf"/>
|
||||||
|
<stop id="stop24" offset="1" style="stop-opacity:1;stop-color:#1f95f8"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<g transform="matrix(1.3333333,0,0,-1.3333333,0,682.66667)" id="g10">
|
||||||
|
<g transform="scale(0.1)" id="g12">
|
||||||
|
<g id="g14">
|
||||||
|
<g clip-path="url(#clipPath20)" id="g16">
|
||||||
|
<path id="path28" style="fill:url(#linearGradient26);fill-opacity:1;fill-rule:nonzero;stroke:none" d="M 3873.89,0 H 1246.11 C 560.754,0 0,560.75 0,1246.11 V 3873.88 C 0,4559.25 560.754,5120 1246.11,5120 H 3873.89 C 4559.25,5120 5120,4559.25 5120,3873.88 V 1246.11 C 5120,560.75 4559.25,0 3873.89,0"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<path id="path30" style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M 3961.59,4435.23 H 2570.18 c -13.15,0 -23.78,-10.64 -23.78,-23.77 v -441.84 c 0,-14.87 12.04,-26.92 26.92,-26.92 h 190.77 c 77.16,0 139.73,-59.35 146.43,-134.77 V 3505 3336.23 1728.75 1717.36 h -0.05 c 0.48,-16.84 -0.19,-33.4 -1.83,-49.71 -0.18,-2.38 -0.5,-4.73 -0.79,-7.09 -1.1,-9.53 -2.32,-19.01 -4.17,-28.29 -1.01,-5.29 -2.44,-10.44 -3.71,-15.65 -1.71,-6.93 -3.09,-13.97 -5.22,-20.75 -12.58,-40.27 -32.47,-77.62 -59.98,-110.5 -1.01,-1.17 -2.26,-2.25 -3.26,-3.41 -8.39,-9.72 -17.2,-19.19 -26.95,-28.06 -9.84,-8.95 -20.26,-17.27 -31.21,-25 -77.84,-55.14 -182.61,-79.4 -299.67,-68.2 -149.26,14.03 -297.34,81.72 -417.03,190.62 -119.67,108.89 -194.08,243.62 -209.48,379.41 -13.85,121.48 22.55,228.38 102.42,301.05 0.21,0.16 0.4,0.31 0.56,0.48 3.09,2.77 6.49,5.2 9.67,7.87 57.16,47.89 131.67,76.91 216.7,84.91 0.96,0.09 1.88,0.24 2.79,0.32 8.95,0.79 18.07,1.15 27.27,1.49 4.81,0.16 9.56,0.5 14.44,0.54 1.62,0.02 3.16,0.19 4.78,0.19 2.9,0 5.91,-0.38 8.81,-0.42 13.4,-0.21 26.9,-0.76 40.67,-1.94 1.74,-0.14 3.4,-0.08 5.19,-0.24 1.27,-0.13 2.53,-0.41 3.8,-0.54 78,-7.82 155.23,-31.11 228.52,-66.4 1.53,-0.07 3.3,-0.54 5.51,-1.76 22.34,-12.34 26.62,0.9 27.28,9.65 v 382.24 282.82 c 0,19.05 -13.25,35.9 -31.83,39.99 -394.76,86.88 -782.08,-3.55 -1055.38,-252.34 -238.75,-217.18 -354.24,-530.58 -316.82,-859.79 33.39,-293.23 183.91,-574.94 423.88,-793.33 233.89,-212.79 531.69,-345.86 838.88,-374.801 42.33,-3.918 84.86,-5.938 126.36,-5.938 293.38,0 565.61,100.598 766.54,283.379 190.34,173.3 304.35,411.27 321.08,670.16 l 1.55,1697.91 h 0.17 v 453.97 h 0.06 v 7.92 c 1.72,80.12 67.05,144.58 147.61,144.58 h 190.77 c 14.86,0 26.92,12.05 26.92,26.92 v 441.84 c 0,13.13 -10.63,23.77 -23.78,23.77"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.0 KiB |
96
Assets/JoplinLetter.eps
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
%!PS-Adobe-3.0 EPSF-3.0
|
||||||
|
%%Creator: cairo 1.15.6 (http://cairographics.org)
|
||||||
|
%%CreationDate: Wed Dec 04 10:22:47 2019
|
||||||
|
%%Pages: 1
|
||||||
|
%%DocumentData: Clean7Bit
|
||||||
|
%%LanguageLevel: 2
|
||||||
|
%%BoundingBox: 0 0 331 372
|
||||||
|
%%EndComments
|
||||||
|
%%BeginProlog
|
||||||
|
save
|
||||||
|
50 dict begin
|
||||||
|
/q { gsave } bind def
|
||||||
|
/Q { grestore } bind def
|
||||||
|
/cm { 6 array astore concat } bind def
|
||||||
|
/w { setlinewidth } bind def
|
||||||
|
/J { setlinecap } bind def
|
||||||
|
/j { setlinejoin } bind def
|
||||||
|
/M { setmiterlimit } bind def
|
||||||
|
/d { setdash } bind def
|
||||||
|
/m { moveto } bind def
|
||||||
|
/l { lineto } bind def
|
||||||
|
/c { curveto } bind def
|
||||||
|
/h { closepath } bind def
|
||||||
|
/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
|
||||||
|
0 exch rlineto 0 rlineto closepath } bind def
|
||||||
|
/S { stroke } bind def
|
||||||
|
/f { fill } bind def
|
||||||
|
/f* { eofill } bind def
|
||||||
|
/n { newpath } bind def
|
||||||
|
/W { clip } bind def
|
||||||
|
/W* { eoclip } bind def
|
||||||
|
/BT { } bind def
|
||||||
|
/ET { } bind def
|
||||||
|
/pdfmark where { pop globaldict /?pdfmark /exec load put }
|
||||||
|
{ globaldict begin /?pdfmark /pop load def /pdfmark
|
||||||
|
/cleartomark load def end } ifelse
|
||||||
|
/BDC { mark 3 1 roll /BDC pdfmark } bind def
|
||||||
|
/EMC { mark /EMC pdfmark } bind def
|
||||||
|
/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
|
||||||
|
/Tj { show currentpoint cairo_store_point } bind def
|
||||||
|
/TJ {
|
||||||
|
{
|
||||||
|
dup
|
||||||
|
type /stringtype eq
|
||||||
|
{ show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
|
||||||
|
} forall
|
||||||
|
currentpoint cairo_store_point
|
||||||
|
} bind def
|
||||||
|
/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
|
||||||
|
cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
|
||||||
|
/Tf { pop /cairo_font exch def /cairo_font_matrix where
|
||||||
|
{ pop cairo_selectfont } if } bind def
|
||||||
|
/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
|
||||||
|
/cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
|
||||||
|
/cairo_font where { pop cairo_selectfont } if } bind def
|
||||||
|
/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
|
||||||
|
cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
|
||||||
|
/g { setgray } bind def
|
||||||
|
/rg { setrgbcolor } bind def
|
||||||
|
/d1 { setcachedevice } bind def
|
||||||
|
/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def
|
||||||
|
/cairo_image { image cairo_flush_ascii85_file } def
|
||||||
|
/cairo_imagemask { imagemask cairo_flush_ascii85_file } def
|
||||||
|
%%EndProlog
|
||||||
|
%%BeginSetup
|
||||||
|
%%EndSetup
|
||||||
|
%%Page: 1 1
|
||||||
|
%%BeginPageSetup
|
||||||
|
%%PageBoundingBox: 0 0 331 372
|
||||||
|
%%EndPageSetup
|
||||||
|
q 0 0 331 372 rectclip
|
||||||
|
1 0 0 -1 0 372 cm q
|
||||||
|
0 g
|
||||||
|
116.223 371.219 m 92.629 368.047 79.551 364.387 63.867 356.57 c 34.902
|
||||||
|
342.121 12.555 317.016 3.848 289.133 c 0.383 278.039 -0.012 275.32 0 262.508
|
||||||
|
c 0.008 251.938 0.258 249.27 1.699 244.234 c 4.723 233.676 8.77 226.742
|
||||||
|
16.426 219.008 c 25.66 209.676 35.293 205.477 48.723 204.93 c 61.566 204.41
|
||||||
|
70.113 206.629 78.211 212.594 c 83.371 216.391 88.637 223.828 91.578 231.484
|
||||||
|
c 93.445 236.34 93.59 237.676 94.133 255.008 c 94.699 273.145 95.164 276.641
|
||||||
|
98.078 284.684 c 102.059 295.672 108.82 303.285 118.855 308.086 c 127.93
|
||||||
|
312.426 134.84 313.762 146.094 313.348 c 153.41 313.078 156.309 312.625
|
||||||
|
160.699 311.07 c 173.777 306.434 184.691 296.008 189.934 283.133 c 195.355
|
||||||
|
269.828 195.344 270.066 195.328 160.883 c 195.313 65.117 195.297 64.098
|
||||||
|
193.781 60.758 c 190.078 52.586 186.18 51.125 166.371 50.488 c 151.848
|
||||||
|
50.02 l 151.848 0 l 329.973 0.383 l 330.168 25.199 l 330.367 50.02 l 315.918
|
||||||
|
50.527 l 307.902 50.809 300.027 51.465 298.23 52.004 c 293.328 53.461 290.211
|
||||||
|
56.363 288.234 61.305 c 286.504 65.633 l 286.027 164.258 l 285.547 264.215
|
||||||
|
285.48 266.555 282.668 280.234 c 277.531 305.25 259.137 330.953 234.594
|
||||||
|
347.406 c 214.891 360.621 193.523 367.852 164.223 371.227 c 156.445 372.121
|
||||||
|
122.898 372.117 116.223 371.219 c h
|
||||||
|
116.223 371.219 m f
|
||||||
|
Q Q
|
||||||
|
showpage
|
||||||
|
%%Trailer
|
||||||
|
end restore
|
||||||
|
%%EOF
|
BIN
Assets/JoplinLetter.png
Normal file
After Width: | Height: | Size: 11 KiB |
@@ -1,6 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
<svg
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
@@ -9,54 +7,63 @@
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
width="116.54575mm"
|
viewBox="0 0 682.66669 682.66669"
|
||||||
height="131.19589mm"
|
height="682.66669"
|
||||||
viewBox="0 0 116.54575 131.19589"
|
width="682.66669"
|
||||||
|
xml:space="preserve"
|
||||||
|
id="svg2"
|
||||||
version="1.1"
|
version="1.1"
|
||||||
id="svg8"
|
sodipodi:docname="JoplinLetter.svg"
|
||||||
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
|
inkscape:version="1.0beta1 (32d4812, 2019-09-19)"><metadata
|
||||||
sodipodi:docname="JoplinLetter.svg">
|
id="metadata23">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
|
||||||
|
<dc:title></dc:title>
|
||||||
|
|
||||||
|
</cc:Work>
|
||||||
|
|
||||||
|
</rdf:RDF>
|
||||||
|
|
||||||
|
</metadata>
|
||||||
|
|
||||||
|
<sodipodi:namedview
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
inkscape:document-rotation="0"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="1600"
|
||||||
|
inkscape:window-height="907"
|
||||||
|
id="namedview21"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="0.35"
|
||||||
|
inkscape:cx="400"
|
||||||
|
inkscape:cy="560"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="23"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="svg2" />
|
||||||
|
|
||||||
|
|
||||||
<defs
|
<defs
|
||||||
id="defs2" />
|
id="defs6" />
|
||||||
<sodipodi:namedview
|
|
||||||
id="base"
|
|
||||||
pagecolor="#ffffff"
|
<path
|
||||||
bordercolor="#666666"
|
id="path30"
|
||||||
borderopacity="1.0"
|
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333;stop-opacity:1"
|
||||||
inkscape:pageopacity="0.0"
|
d="M 528.21199,91.302685 H 342.69066 c -1.75334,0 -3.17067,1.418666 -3.17067,3.169333 v 58.912002 c 0,1.98266 1.60533,3.58933 3.58933,3.58933 h 25.436 c 10.288,0 18.63067,7.91333 19.524,17.96933 v 40.39067 22.50266 214.33067 1.51866 h -0.007 c 0.064,2.24534 -0.0253,4.45334 -0.244,6.628 -0.024,0.31734 -0.0667,0.63067 -0.10534,0.94534 -0.14666,1.27066 -0.30933,2.53466 -0.556,3.772 -0.13466,0.70533 -0.32533,1.392 -0.49466,2.08666 -0.228,0.924 -0.412,1.86267 -0.696,2.76667 -1.67734,5.36933 -4.32934,10.34933 -7.99734,14.73333 -0.13466,0.156 -0.30133,0.3 -0.43466,0.45467 -1.11867,1.296 -2.29334,2.55866 -3.59334,3.74133 -1.312,1.19333 -2.70133,2.30267 -4.16133,3.33333 -10.37867,7.352 -24.348,10.58667 -39.956,9.09334 -19.90133,-1.87067 -39.64533,-10.896 -55.604,-25.416 -15.956,-14.51867 -25.87733,-32.48267 -27.93066,-50.588 -1.84667,-16.19733 3.00666,-30.45067 13.656,-40.14 0.028,-0.0213 0.0533,-0.0413 0.0747,-0.064 0.412,-0.36933 0.86534,-0.69333 1.28934,-1.04933 7.62133,-6.38534 17.556,-10.25467 28.89333,-11.32134 0.128,-0.012 0.25067,-0.032 0.372,-0.0427 1.19333,-0.10534 2.40933,-0.15334 3.636,-0.19867 0.64133,-0.0213 1.27467,-0.0667 1.92533,-0.072 0.216,-0.003 0.42134,-0.0253 0.63734,-0.0253 0.38666,0 0.788,0.0507 1.17466,0.056 1.78667,0.028 3.58667,0.10133 5.42267,0.25866 0.232,0.0187 0.45333,0.0107 0.692,0.032 0.16933,0.0173 0.33733,0.0547 0.50667,0.072 10.4,1.04267 20.69733,4.148 30.46933,8.85334 0.204,0.009 0.44,0.072 0.73466,0.23466 2.97867,1.64534 3.54934,-0.12 3.63734,-1.28666 V 329.57734 291.868 c 0,-2.54 -1.76667,-4.78666 -4.244,-5.332 -52.63467,-11.584 -104.27733,0.47334 -140.71733,33.64534 -31.83333,28.95733 -47.232,70.74399 -42.24267,114.63866 4.452,39.09733 24.52134,76.65866 56.51733,105.77733 31.18534,28.372 70.892,46.11467 111.85067,49.97347 5.644,0.5224 11.31467,0.79173 16.848,0.79173 39.11733,0 75.41466,-13.41307 102.20533,-37.78387 25.37867,-23.10666 40.58,-54.836 42.81066,-89.35466 l 0.20667,-226.388 h 0.0227 v -60.52933 h 0.008 v -1.056 c 0.22933,-10.68266 8.94,-19.27733 19.68133,-19.27733 h 25.436 c 1.98133,0 3.58933,-1.60667 3.58933,-3.58933 V 94.472018 c 0,-1.750667 -1.41733,-3.169333 -3.17066,-3.169333"
|
||||||
inkscape:pageshadow="2"
|
inkscape:connector-curvature="0" />
|
||||||
inkscape:zoom="0.49497475"
|
|
||||||
inkscape:cx="152.11122"
|
|
||||||
inkscape:cy="-26.090631"
|
|
||||||
inkscape:document-units="mm"
|
|
||||||
inkscape:current-layer="layer1"
|
|
||||||
showgrid="false"
|
|
||||||
inkscape:window-width="1920"
|
|
||||||
inkscape:window-height="1017"
|
|
||||||
inkscape:window-x="-8"
|
|
||||||
inkscape:window-y="-8"
|
|
||||||
inkscape:window-maximized="1" />
|
|
||||||
<metadata
|
|
||||||
id="metadata5">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:format>image/svg+xml</dc:format>
|
|
||||||
<dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
||||||
<dc:title></dc:title>
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<g
|
|
||||||
inkscape:label="Layer 1"
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer1"
|
|
||||||
transform="translate(-2.7903623,-2.175533)">
|
|
||||||
<path
|
|
||||||
style="fill:#000000;stroke-width:0.26458332"
|
|
||||||
d="m 43.790458,133.13317 c -8.32317,-1.11843 -12.937,-2.40956 -18.46857,-5.16822 -10.21924,-5.09644 -18.1023498,-13.95338 -21.1745998,-23.79038 -1.22214,-3.91319 -1.3607,-4.872332 -1.35685,-9.392712 0.003,-3.72804 0.0907,-4.66941 0.59927,-6.44569 1.0664,-3.7246 2.49409,-6.1704 5.19529,-8.90014 3.2574198,-3.29184 6.6565798,-4.77332 11.3929598,-4.96548 4.53189,-0.18388 7.54661,0.59927 10.40386,2.70266 1.82035,1.34007 3.67693,3.96421 4.71565,6.66525 0.65839,1.71204 0.70959,2.1839 0.90042,8.29756 0.19973,6.39855 0.36372,7.6318 1.39223,10.469902 1.40468,3.87611 3.78939,6.56189 7.33039,8.25588 3.20047,1.53108 5.63801,2.00183 9.60817,1.8556 2.58182,-0.0951 3.60332,-0.25442 5.15337,-0.80371 4.61358,-1.63493 8.46322,-5.31381 10.31326,-9.85579 1.91154,-4.693002 1.90785,-4.609372 1.90213,-43.127082 -0.005,-33.78395 -0.0106,-34.14337 -0.54484,-35.32188 -1.30698,-2.882895 -2.68223,-3.398165 -9.66971,-3.622945 l -5.12472,-0.16486 V 10.998334 2.175533 l 31.41927,0.06723 31.419272,0.06723 0.0697,8.755726 0.0697,8.755724 -5.09675,0.1793 c -2.82759,0.0995 -5.60596,0.33101 -6.24051,0.52006 -1.72896,0.5151 -2.82899,1.538795 -3.52569,3.281045 l -0.61059,1.5269 -0.16762,34.7927 c -0.16988,35.26321 -0.19381,36.08914 -1.18496,40.914372 -1.81292,8.82581 -8.301582,17.89221 -16.959672,23.69719 -6.95182,4.66099 -14.48972,7.21214 -24.82645,8.40235 -2.7431,0.31585 -14.57797,0.31433 -16.93333,-0.002 z"
|
|
||||||
id="path21"
|
|
||||||
inkscape:connector-curvature="0" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 679 B After Width: | Height: | Size: 697 B |
Before Width: | Height: | Size: 968 B After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.9 KiB |
BIN
Assets/SquareIcon1024.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
Assets/SquareIcon512.png
Normal file
After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 79 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 9.0 KiB |
BIN
Assets/macOs.iconset/icon_128x128@2x.png
Normal file
After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 679 B After Width: | Height: | Size: 1.1 KiB |
BIN
Assets/macOs.iconset/icon_16x16@2x.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 27 KiB |
BIN
Assets/macOs.iconset/icon_256x256@2x.png
Normal file
After Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 2.0 KiB |
BIN
Assets/macOs.iconset/icon_32x32@2x.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 71 KiB |
BIN
Assets/macOs.iconset/icon_512x512@2x.png
Normal file
After Width: | Height: | Size: 71 KiB |
BIN
Assets/macOsTemplateIcon16.psd
Normal file
BIN
Assets/macOsTemplateIcon32.psd
Normal file
10
BUILD.md
@@ -3,6 +3,7 @@
|
|||||||
# General information
|
# 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.
|
- 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.
|
||||||
|
- In general, most of the backend (anything to do with the database, synchronisation, data import or export, etc.) is shared across all the apps, so when making a change please consider how it will affect all the apps.
|
||||||
|
|
||||||
## macOS dependencies
|
## macOS dependencies
|
||||||
|
|
||||||
@@ -18,11 +19,10 @@
|
|||||||
|
|
||||||
# Building the tools
|
# Building the tools
|
||||||
|
|
||||||
Before building any of the applications, you need to build the tools:
|
Before building any of the applications, you need to build the tools and pre-commit hooks:
|
||||||
|
|
||||||
```
|
```
|
||||||
cd Tools
|
npm install && cd Tools && npm install
|
||||||
npm install
|
|
||||||
```
|
```
|
||||||
|
|
||||||
# Building the Electron application
|
# Building the Electron application
|
||||||
@@ -65,7 +65,7 @@ The [building\_win32\_tips on this page](./readme/building_win32_tips.md) might
|
|||||||
|
|
||||||
# Building the Mobile application
|
# 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.
|
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 "React Native CLI Quickstart" tab.
|
||||||
|
|
||||||
Then, from `/ReactNativeClient`, run `npm install`, then `react-native run-ios` or `react-native run-android`.
|
Then, from `/ReactNativeClient`, run `npm install`, then `react-native run-ios` or `react-native run-android`.
|
||||||
|
|
||||||
@@ -78,4 +78,4 @@ npm install
|
|||||||
rsync --delete -aP ../ReactNativeClient/locales/ build/locales/
|
rsync --delete -aP ../ReactNativeClient/locales/ build/locales/
|
||||||
```
|
```
|
||||||
|
|
||||||
Run `run.sh` to start the application for testing.
|
Run `run.sh` to start the application for testing.
|
||||||
|
@@ -32,7 +32,8 @@ Joplin is available in multiple languages thanks to the help of its users. You c
|
|||||||
|
|
||||||
If you want to start contributing to the project's code, please follow these guidelines before creating a pull request:
|
If you want to start contributing to the project's code, please follow these guidelines before creating a pull request:
|
||||||
|
|
||||||
- Bug fixes are always welcome. Start by reviewing the list of [essential issues](https://github.com/laurent22/joplin/issues?q=is%3Aissue+is%3Aopen+label%3Aessential)
|
- Bug fixes are always welcome. Start by reviewing the [list of bugs](https://github.com/laurent22/joplin/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
|
||||||
|
- A good way to easily start contributing is to pick and work on a [good first issue](https://github.com/laurent22/joplin/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). We try to make these issues as clear as possible and provide basic info on how the code should be changed, and if something is unclear feel free to ask for more information on the issue.
|
||||||
- Before adding a new feature, ask about it in the [Github Issue Tracker](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is%3Aissue) or the [Joplin Forum](https://discourse.joplinapp.org/), or check if existing discussions exist to make sure the new functionality is desired.
|
- Before adding a new feature, ask about it in the [Github Issue Tracker](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is%3Aissue) or the [Joplin Forum](https://discourse.joplinapp.org/), or check if existing discussions exist to make sure the new functionality is desired.
|
||||||
- **Changes that will consist in more than 50 lines of code should be discussed the [Joplin Forum](https://discourse.joplinapp.org/)**, so that you don't spend too much time implementing something that might not be accepted.
|
- **Changes that will consist in more than 50 lines of code should be discussed the [Joplin Forum](https://discourse.joplinapp.org/)**, so that you don't spend too much time implementing something that might not be accepted.
|
||||||
|
|
||||||
@@ -40,10 +41,7 @@ Building the apps is relatively easy - please [see the build instructions](https
|
|||||||
|
|
||||||
## Coding style
|
## Coding style
|
||||||
|
|
||||||
There are only two rules, but not following them means the pull request will not be accepted (it can be accepted once the issues are fixed):
|
Coding style is enforced by a pre-commit hook that runs eslint. This hook is installed whenever running `npm install` on any of the application directory. If for some reason the pre-commit hook didn't get installed, you can manually install it by running `npm install` at the root of the repository.
|
||||||
|
|
||||||
- **Please use tabs, NOT spaces.**
|
|
||||||
- **Please do not add or remove optional characters, such as spaces or colons.** Please setup your editor so that it only changes what you are working on and is not making automated changes elsewhere. The reason for this is that small white space changes make diff hard to read and can cause needless conflicts.
|
|
||||||
|
|
||||||
## Unit tests
|
## Unit tests
|
||||||
|
|
||||||
@@ -54,10 +52,23 @@ The tests are under CliClient/tests. To get them running, you first need to buil
|
|||||||
cd CliClient
|
cd CliClient
|
||||||
npm i
|
npm i
|
||||||
|
|
||||||
Then to run all the test units:
|
To run the test units, you must have an instance of the cli app running. In a first window navigate into `CliClient` and run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./run.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
> If you get an error like `Error: Cannot find module '../locales/index.js'`, this means you must (a) rebuild translations or (b) take > them from one of the other apps. To do option b, you can run the following command to copy them from the `ReactNativeClient` directory:>
|
||||||
|
>
|
||||||
|
> ```sh
|
||||||
|
> cd .. # Return to the root of the project
|
||||||
|
> rsync -aP ./ReactNativeClient/locales/ ./CliClient/build/locales/
|
||||||
|
> ```
|
||||||
|
|
||||||
|
Then run the tests in a second window. To run all the test units:
|
||||||
|
|
||||||
./run_test.sh
|
./run_test.sh
|
||||||
|
|
||||||
To run just one particular file:
|
To run just one particular file:
|
||||||
|
|
||||||
./run_test.sh markdownUtils # Don't add the .js extension
|
./run_test.sh markdownUtils # Don't add the .js extension
|
||||||
|
13
CliClient/.eslintrc.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
module.exports = {
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["tests/**/*.js"],
|
||||||
|
'rules': {
|
||||||
|
// Ignore all unused function arguments, because in some
|
||||||
|
// case they are kept to indicate the function signature.
|
||||||
|
"no-unused-vars": ["error", { "argsIgnorePattern": ".*" }],
|
||||||
|
"@typescript-eslint/no-unused-vars": 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
@@ -1,14 +1,11 @@
|
|||||||
const { _ } = require('lib/locale.js');
|
|
||||||
const { Logger } = require('lib/logger.js');
|
const { Logger } = require('lib/logger.js');
|
||||||
const Resource = require('lib/models/Resource.js');
|
|
||||||
const { netUtils } = require('lib/net-utils.js');
|
const { netUtils } = require('lib/net-utils.js');
|
||||||
|
|
||||||
const http = require("http");
|
const http = require('http');
|
||||||
const urlParser = require("url");
|
const urlParser = require('url');
|
||||||
const enableServerDestroy = require('server-destroy');
|
const enableServerDestroy = require('server-destroy');
|
||||||
|
|
||||||
class ResourceServer {
|
class ResourceServer {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.server_ = null;
|
this.server_ = null;
|
||||||
this.logger_ = new Logger();
|
this.logger_ = new Logger();
|
||||||
@@ -31,7 +28,7 @@ class ResourceServer {
|
|||||||
|
|
||||||
baseUrl() {
|
baseUrl() {
|
||||||
if (!this.port_) return '';
|
if (!this.port_) return '';
|
||||||
return 'http://127.0.0.1:' + this.port_;
|
return `http://127.0.0.1:${this.port_}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
setLinkHandler(handler) {
|
setLinkHandler(handler) {
|
||||||
@@ -40,7 +37,7 @@ class ResourceServer {
|
|||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
this.port_ = await netUtils.findAvailablePort([9167, 9267, 8167, 8267]);
|
this.port_ = await netUtils.findAvailablePort([9167, 9267, 8167, 8267]);
|
||||||
if (!this.port_) {
|
if (!this.port_) {
|
||||||
this.logger().error('Could not find available port to start resource server. Please report the error at https://github.com/laurent22/joplin');
|
this.logger().error('Could not find available port to start resource server. Please report the error at https://github.com/laurent22/joplin');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -48,16 +45,15 @@ class ResourceServer {
|
|||||||
this.server_ = http.createServer();
|
this.server_ = http.createServer();
|
||||||
|
|
||||||
this.server_.on('request', async (request, response) => {
|
this.server_.on('request', async (request, response) => {
|
||||||
|
const writeResponse = message => {
|
||||||
const writeResponse = (message) => {
|
|
||||||
response.write(message);
|
response.write(message);
|
||||||
response.end();
|
response.end();
|
||||||
}
|
};
|
||||||
|
|
||||||
const url = urlParser.parse(request.url, true);
|
const url = urlParser.parse(request.url, true);
|
||||||
let resourceId = url.pathname.split('/');
|
let resourceId = url.pathname.split('/');
|
||||||
if (resourceId.length < 2) {
|
if (resourceId.length < 2) {
|
||||||
writeResponse('Error: could not get resource ID from path name: ' + url.pathname);
|
writeResponse(`Error: could not get resource ID from path name: ${url.pathname}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
resourceId = resourceId[1];
|
resourceId = resourceId[1];
|
||||||
@@ -66,9 +62,10 @@ class ResourceServer {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const done = await this.linkHandler_(resourceId, response);
|
const done = await this.linkHandler_(resourceId, response);
|
||||||
if (!done) throw new Error('Unhandled resource: ' + resourceId);
|
if (!done) throw new Error(`Unhandled resource: ${resourceId}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
response.setHeader('Content-Type', 'text/plain');
|
response.setHeader('Content-Type', 'text/plain');
|
||||||
|
// eslint-disable-next-line require-atomic-updates
|
||||||
response.statusCode = 400;
|
response.statusCode = 400;
|
||||||
response.write(error.message);
|
response.write(error.message);
|
||||||
}
|
}
|
||||||
@@ -76,7 +73,7 @@ class ResourceServer {
|
|||||||
response.end();
|
response.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.server_.on('error', (error) => {
|
this.server_.on('error', error => {
|
||||||
this.logger().error('Resource server:', error);
|
this.logger().error('Resource server:', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -91,7 +88,6 @@ class ResourceServer {
|
|||||||
if (this.server_) this.server_.destroy();
|
if (this.server_) this.server_.destroy();
|
||||||
this.server_ = null;
|
this.server_ = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ResourceServer;
|
module.exports = ResourceServer;
|
||||||
|
@@ -5,13 +5,12 @@ const Tag = require('lib/models/Tag.js');
|
|||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const Resource = require('lib/models/Resource.js');
|
const Resource = require('lib/models/Resource.js');
|
||||||
const { cliUtils } = require('./cli-utils.js');
|
|
||||||
const { reducer, defaultState } = require('lib/reducer.js');
|
const { reducer, defaultState } = require('lib/reducer.js');
|
||||||
const { splitCommandString } = require('lib/string-utils.js');
|
const { splitCommandString } = require('lib/string-utils.js');
|
||||||
const { reg } = require('lib/registry.js');
|
const { reg } = require('lib/registry.js');
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const Entities = require('html-entities').AllHtmlEntities;
|
const Entities = require('html-entities').AllHtmlEntities;
|
||||||
const htmlentities = (new Entities()).encode;
|
const htmlentities = new Entities().encode;
|
||||||
|
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
const tk = require('terminal-kit');
|
const tk = require('terminal-kit');
|
||||||
@@ -20,12 +19,10 @@ const Renderer = require('tkwidgets/framework/Renderer.js');
|
|||||||
const DecryptionWorker = require('lib/services/DecryptionWorker');
|
const DecryptionWorker = require('lib/services/DecryptionWorker');
|
||||||
|
|
||||||
const BaseWidget = require('tkwidgets/BaseWidget.js');
|
const BaseWidget = require('tkwidgets/BaseWidget.js');
|
||||||
const ListWidget = require('tkwidgets/ListWidget.js');
|
|
||||||
const TextWidget = require('tkwidgets/TextWidget.js');
|
const TextWidget = require('tkwidgets/TextWidget.js');
|
||||||
const HLayoutWidget = require('tkwidgets/HLayoutWidget.js');
|
const HLayoutWidget = require('tkwidgets/HLayoutWidget.js');
|
||||||
const VLayoutWidget = require('tkwidgets/VLayoutWidget.js');
|
const VLayoutWidget = require('tkwidgets/VLayoutWidget.js');
|
||||||
const ReduxRootWidget = require('tkwidgets/ReduxRootWidget.js');
|
const ReduxRootWidget = require('tkwidgets/ReduxRootWidget.js');
|
||||||
const RootWidget = require('tkwidgets/RootWidget.js');
|
|
||||||
const WindowWidget = require('tkwidgets/WindowWidget.js');
|
const WindowWidget = require('tkwidgets/WindowWidget.js');
|
||||||
|
|
||||||
const NoteWidget = require('./gui/NoteWidget.js');
|
const NoteWidget = require('./gui/NoteWidget.js');
|
||||||
@@ -37,7 +34,6 @@ const StatusBarWidget = require('./gui/StatusBarWidget.js');
|
|||||||
const ConsoleWidget = require('./gui/ConsoleWidget.js');
|
const ConsoleWidget = require('./gui/ConsoleWidget.js');
|
||||||
|
|
||||||
class AppGui {
|
class AppGui {
|
||||||
|
|
||||||
constructor(app, store, keymap) {
|
constructor(app, store, keymap) {
|
||||||
try {
|
try {
|
||||||
this.app_ = app;
|
this.app_ = app;
|
||||||
@@ -50,12 +46,12 @@ class AppGui {
|
|||||||
// Some keys are directly handled by the tkwidget framework
|
// Some keys are directly handled by the tkwidget framework
|
||||||
// so they need to be remapped in a different way.
|
// so they need to be remapped in a different way.
|
||||||
this.tkWidgetKeys_ = {
|
this.tkWidgetKeys_ = {
|
||||||
'focus_next': 'TAB',
|
focus_next: 'TAB',
|
||||||
'focus_previous': 'SHIFT_TAB',
|
focus_previous: 'SHIFT_TAB',
|
||||||
'move_up': 'UP',
|
move_up: 'UP',
|
||||||
'move_down': 'DOWN',
|
move_down: 'DOWN',
|
||||||
'page_down': 'PAGE_DOWN',
|
page_down: 'PAGE_DOWN',
|
||||||
'page_up': 'PAGE_UP',
|
page_up: 'PAGE_UP',
|
||||||
};
|
};
|
||||||
|
|
||||||
this.renderer_ = null;
|
this.renderer_ = null;
|
||||||
@@ -64,7 +60,7 @@ class AppGui {
|
|||||||
|
|
||||||
this.renderer_ = new Renderer(this.term(), this.rootWidget_);
|
this.renderer_ = new Renderer(this.term(), this.rootWidget_);
|
||||||
|
|
||||||
this.app_.on('modelAction', async (event) => {
|
this.app_.on('modelAction', async event => {
|
||||||
await this.handleModelAction(event.action);
|
await this.handleModelAction(event.action);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -83,7 +79,7 @@ class AppGui {
|
|||||||
reg.setupRecurrentSync();
|
reg.setupRecurrentSync();
|
||||||
DecryptionWorker.instance().scheduleStart();
|
DecryptionWorker.instance().scheduleStart();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.fullScreen(false);
|
if (this.term_) { this.fullScreen(false); }
|
||||||
console.error(error);
|
console.error(error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -134,7 +130,7 @@ class AppGui {
|
|||||||
};
|
};
|
||||||
folderList.name = 'folderList';
|
folderList.name = 'folderList';
|
||||||
folderList.vStretch = true;
|
folderList.vStretch = true;
|
||||||
folderList.on('currentItemChange', async (event) => {
|
folderList.on('currentItemChange', async event => {
|
||||||
const item = folderList.currentItem;
|
const item = folderList.currentItem;
|
||||||
|
|
||||||
if (item === '-') {
|
if (item === '-') {
|
||||||
@@ -169,7 +165,7 @@ class AppGui {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.rootWidget_.connect(folderList, (state) => {
|
this.rootWidget_.connect(folderList, state => {
|
||||||
return {
|
return {
|
||||||
selectedFolderId: state.selectedFolderId,
|
selectedFolderId: state.selectedFolderId,
|
||||||
selectedTagId: state.selectedTagId,
|
selectedTagId: state.selectedTagId,
|
||||||
@@ -196,7 +192,7 @@ class AppGui {
|
|||||||
id: note ? note.id : null,
|
id: note ? note.id : null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.rootWidget_.connect(noteList, (state) => {
|
this.rootWidget_.connect(noteList, state => {
|
||||||
return {
|
return {
|
||||||
selectedNoteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null,
|
selectedNoteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null,
|
||||||
items: state.notes,
|
items: state.notes,
|
||||||
@@ -210,7 +206,7 @@ class AppGui {
|
|||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
borderLeftWidth: 1,
|
borderLeftWidth: 1,
|
||||||
};
|
};
|
||||||
this.rootWidget_.connect(noteText, (state) => {
|
this.rootWidget_.connect(noteText, state => {
|
||||||
return {
|
return {
|
||||||
noteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null,
|
noteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null,
|
||||||
notes: state.notes,
|
notes: state.notes,
|
||||||
@@ -225,7 +221,7 @@ class AppGui {
|
|||||||
borderLeftWidth: 1,
|
borderLeftWidth: 1,
|
||||||
borderRightWidth: 1,
|
borderRightWidth: 1,
|
||||||
};
|
};
|
||||||
this.rootWidget_.connect(noteMetadata, (state) => {
|
this.rootWidget_.connect(noteMetadata, state => {
|
||||||
return { noteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null };
|
return { noteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null };
|
||||||
});
|
});
|
||||||
noteMetadata.hide();
|
noteMetadata.hide();
|
||||||
@@ -292,7 +288,7 @@ class AppGui {
|
|||||||
if (!cmd) return;
|
if (!cmd) return;
|
||||||
const isConfigPassword = cmd.indexOf('config ') >= 0 && cmd.indexOf('password') >= 0;
|
const isConfigPassword = cmd.indexOf('config ') >= 0 && cmd.indexOf('password') >= 0;
|
||||||
if (isConfigPassword) return;
|
if (isConfigPassword) return;
|
||||||
this.stdout(chalk.cyan.bold('> ' + cmd));
|
this.stdout(chalk.cyan.bold(`> ${cmd}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
setupKeymap(keymap) {
|
setupKeymap(keymap) {
|
||||||
@@ -301,7 +297,7 @@ class AppGui {
|
|||||||
for (let i = 0; i < keymap.length; i++) {
|
for (let i = 0; i < keymap.length; i++) {
|
||||||
const item = Object.assign({}, keymap[i]);
|
const item = Object.assign({}, keymap[i]);
|
||||||
|
|
||||||
if (!item.command) throw new Error('Missing command for keymap item: ' + JSON.stringify(item));
|
if (!item.command) throw new Error(`Missing command for keymap item: ${JSON.stringify(item)}`);
|
||||||
|
|
||||||
if (!('type' in item)) item.type = 'exec';
|
if (!('type' in item)) item.type = 'exec';
|
||||||
|
|
||||||
@@ -408,7 +404,7 @@ class AppGui {
|
|||||||
activeListItem() {
|
activeListItem() {
|
||||||
const widget = this.widget('mainWindow').focusedWidget;
|
const widget = this.widget('mainWindow').focusedWidget;
|
||||||
if (!widget) return null;
|
if (!widget) return null;
|
||||||
|
|
||||||
if (widget.name == 'noteList' || widget.name == 'folderList') {
|
if (widget.name == 'noteList' || widget.name == 'folderList') {
|
||||||
return widget.currentItem;
|
return widget.currentItem;
|
||||||
}
|
}
|
||||||
@@ -430,25 +426,21 @@ class AppGui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async processFunctionCommand(cmd) {
|
async processFunctionCommand(cmd) {
|
||||||
|
|
||||||
if (cmd === 'activate') {
|
if (cmd === 'activate') {
|
||||||
|
|
||||||
const w = this.widget('mainWindow').focusedWidget;
|
const w = this.widget('mainWindow').focusedWidget;
|
||||||
if (w.name === 'folderList') {
|
if (w.name === 'folderList') {
|
||||||
this.widget('noteList').focus();
|
this.widget('noteList').focus();
|
||||||
} else if (w.name === 'noteList' || w.name === 'noteText') {
|
} else if (w.name === 'noteList' || w.name === 'noteText') {
|
||||||
this.processPromptCommand('edit $n');
|
this.processPromptCommand('edit $n');
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (cmd === 'delete') {
|
} else if (cmd === 'delete') {
|
||||||
|
|
||||||
if (this.widget('folderList').hasFocus) {
|
if (this.widget('folderList').hasFocus) {
|
||||||
const item = this.widget('folderList').selectedJoplinItem;
|
const item = this.widget('folderList').selectedJoplinItem;
|
||||||
|
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
|
|
||||||
if (item.type_ === BaseModel.TYPE_FOLDER) {
|
if (item.type_ === BaseModel.TYPE_FOLDER) {
|
||||||
await this.processPromptCommand('rmbook ' + item.id);
|
await this.processPromptCommand(`rmbook ${item.id}`);
|
||||||
} else if (item.type_ === BaseModel.TYPE_TAG) {
|
} else if (item.type_ === BaseModel.TYPE_TAG) {
|
||||||
this.stdout(_('To delete a tag, untag the associated notes.'));
|
this.stdout(_('To delete a tag, untag the associated notes.'));
|
||||||
} else if (item.type_ === BaseModel.TYPE_SEARCH) {
|
} else if (item.type_ === BaseModel.TYPE_SEARCH) {
|
||||||
@@ -462,9 +454,7 @@ class AppGui {
|
|||||||
} else {
|
} else {
|
||||||
this.stdout(_('Please select the note or notebook to be deleted first.'));
|
this.stdout(_('Please select the note or notebook to be deleted first.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (cmd === 'toggle_console') {
|
} else if (cmd === 'toggle_console') {
|
||||||
|
|
||||||
if (!this.consoleIsShown()) {
|
if (!this.consoleIsShown()) {
|
||||||
this.showConsole();
|
this.showConsole();
|
||||||
this.minimizeConsole();
|
this.minimizeConsole();
|
||||||
@@ -475,22 +465,15 @@ class AppGui {
|
|||||||
this.maximizeConsole();
|
this.maximizeConsole();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (cmd === 'toggle_metadata') {
|
} else if (cmd === 'toggle_metadata') {
|
||||||
|
|
||||||
this.toggleNoteMetadata();
|
this.toggleNoteMetadata();
|
||||||
|
|
||||||
} else if (cmd === 'enter_command_line_mode') {
|
} else if (cmd === 'enter_command_line_mode') {
|
||||||
|
|
||||||
const cmd = await this.widget('statusBar').prompt();
|
const cmd = await this.widget('statusBar').prompt();
|
||||||
if (!cmd) return;
|
if (!cmd) return;
|
||||||
this.addCommandToConsole(cmd);
|
this.addCommandToConsole(cmd);
|
||||||
await this.processPromptCommand(cmd);
|
await this.processPromptCommand(cmd);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
throw new Error(`Unknown command: ${cmd}`);
|
||||||
throw new Error('Unknown command: ' + cmd);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -501,7 +484,7 @@ class AppGui {
|
|||||||
|
|
||||||
// this.logger().debug('Got command: ' + cmd);
|
// this.logger().debug('Got command: ' + cmd);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let note = this.widget('noteList').currentItem;
|
let note = this.widget('noteList').currentItem;
|
||||||
let folder = this.widget('folderList').currentItem;
|
let folder = this.widget('folderList').currentItem;
|
||||||
let args = splitCommandString(cmd);
|
let args = splitCommandString(cmd);
|
||||||
@@ -511,7 +494,7 @@ class AppGui {
|
|||||||
args[i] = note ? note.id : '';
|
args[i] = note ? note.id : '';
|
||||||
} else if (args[i] == '$b') {
|
} else if (args[i] == '$b') {
|
||||||
args[i] = folder ? folder.id : '';
|
args[i] = folder ? folder.id : '';
|
||||||
} else if (args[i] == '$c') {
|
} else if (args[i] == '$c') {
|
||||||
const item = this.activeListItem();
|
const item = this.activeListItem();
|
||||||
args[i] = item ? item.id : '';
|
args[i] = item ? item.id : '';
|
||||||
}
|
}
|
||||||
@@ -523,7 +506,7 @@ class AppGui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.widget('console').scrollBottom();
|
this.widget('console').scrollBottom();
|
||||||
|
|
||||||
// Invalidate so that the screen is redrawn in case inputting a command has moved
|
// 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.
|
// the GUI up (in particular due to autocompletion), it's moved back to the right position.
|
||||||
this.widget('root').invalidate();
|
this.widget('root').invalidate();
|
||||||
@@ -603,17 +586,17 @@ class AppGui {
|
|||||||
async setupResourceServer() {
|
async setupResourceServer() {
|
||||||
const linkStyle = chalk.blue.underline;
|
const linkStyle = chalk.blue.underline;
|
||||||
const noteTextWidget = this.widget('noteText');
|
const noteTextWidget = this.widget('noteText');
|
||||||
const resourceIdRegex = /^:\/[a-f0-9]+$/i
|
const resourceIdRegex = /^:\/[a-f0-9]+$/i;
|
||||||
const noteLinks = {};
|
const noteLinks = {};
|
||||||
|
|
||||||
const hasProtocol = function(s, protocols) {
|
const hasProtocol = function(s, protocols) {
|
||||||
if (!s) return false;
|
if (!s) return false;
|
||||||
s = s.trim().toLowerCase();
|
s = s.trim().toLowerCase();
|
||||||
for (let i = 0; i < protocols.length; i++) {
|
for (let i = 0; i < protocols.length; i++) {
|
||||||
if (s.indexOf(protocols[i] + '://') === 0) return true;
|
if (s.indexOf(`${protocols[i]}://`) === 0) return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
};
|
||||||
|
|
||||||
// By default, before the server is started, only the regular
|
// By default, before the server is started, only the regular
|
||||||
// URLs appear in blue.
|
// URLs appear in blue.
|
||||||
@@ -637,29 +620,31 @@ class AppGui {
|
|||||||
const link = noteLinks[path];
|
const link = noteLinks[path];
|
||||||
|
|
||||||
if (link.type === 'url') {
|
if (link.type === 'url') {
|
||||||
response.writeHead(302, { 'Location': link.url });
|
response.writeHead(302, { Location: link.url });
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (link.type === 'item') {
|
if (link.type === 'item') {
|
||||||
const itemId = link.id;
|
const itemId = link.id;
|
||||||
let item = await BaseItem.loadItemById(itemId);
|
let item = await BaseItem.loadItemById(itemId);
|
||||||
if (!item) throw new Error('No item with ID ' + itemId); // Should be nearly impossible
|
if (!item) throw new Error(`No item with ID ${itemId}`); // Should be nearly impossible
|
||||||
|
|
||||||
if (item.type_ === BaseModel.TYPE_RESOURCE) {
|
if (item.type_ === BaseModel.TYPE_RESOURCE) {
|
||||||
if (item.mime) response.setHeader('Content-Type', item.mime);
|
if (item.mime) response.setHeader('Content-Type', item.mime);
|
||||||
response.write(await Resource.content(item));
|
response.write(await Resource.content(item));
|
||||||
} else if (item.type_ === BaseModel.TYPE_NOTE) {
|
} else if (item.type_ === BaseModel.TYPE_NOTE) {
|
||||||
const html = [`
|
const html = [
|
||||||
|
`
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html class="client-nojs" lang="en" dir="ltr">
|
<html class="client-nojs" lang="en" dir="ltr">
|
||||||
<head><meta charset="UTF-8"/></head><body>
|
<head><meta charset="UTF-8"/></head><body>
|
||||||
`];
|
`,
|
||||||
html.push('<pre>' + htmlentities(item.title) + '\n\n' + htmlentities(item.body) + '</pre>');
|
];
|
||||||
|
html.push(`<pre>${htmlentities(item.title)}\n\n${htmlentities(item.body)}</pre>`);
|
||||||
html.push('</body></html>');
|
html.push('</body></html>');
|
||||||
response.write(html.join(''));
|
response.write(html.join(''));
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Unsupported item type: ' + item.type_);
|
throw new Error(`Unsupported item type: ${item.type_}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -679,7 +664,7 @@ class AppGui {
|
|||||||
noteLinks[index] = {
|
noteLinks[index] = {
|
||||||
type: 'item',
|
type: 'item',
|
||||||
id: url.substr(2),
|
id: url.substr(2),
|
||||||
};
|
};
|
||||||
} else if (hasProtocol(url, ['http', 'https', 'file', 'ftp'])) {
|
} else if (hasProtocol(url, ['http', 'https', 'file', 'ftp'])) {
|
||||||
noteLinks[index] = {
|
noteLinks[index] = {
|
||||||
type: 'url',
|
type: 'url',
|
||||||
@@ -691,7 +676,7 @@ class AppGui {
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
return linkStyle(this.resourceServer_.baseUrl() + '/' + index);
|
return linkStyle(`${this.resourceServer_.baseUrl()}/${index}`);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -710,8 +695,7 @@ class AppGui {
|
|||||||
|
|
||||||
term.grabInput();
|
term.grabInput();
|
||||||
|
|
||||||
term.on('key', async (name, matches, data) => {
|
term.on('key', async (name) => {
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Handle special shortcuts
|
// Handle special shortcuts
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@@ -729,13 +713,13 @@ class AppGui {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name === 'CTRL_C' ) {
|
if (name === 'CTRL_C') {
|
||||||
const cmd = this.app().currentCommand();
|
const cmd = this.app().currentCommand();
|
||||||
if (!cmd || !cmd.cancellable() || this.commandCancelCalled_) {
|
if (!cmd || !cmd.cancellable() || this.commandCancelCalled_) {
|
||||||
this.stdout(_('Press Ctrl+D or type "exit" to exit the application'));
|
this.stdout(_('Press Ctrl+D or type "exit" to exit the application'));
|
||||||
} else {
|
} else {
|
||||||
this.commandCancelCalled_ = true;
|
this.commandCancelCalled_ = true;
|
||||||
await cmd.cancel()
|
await cmd.cancel();
|
||||||
this.commandCancelCalled_ = false;
|
this.commandCancelCalled_ = false;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -744,8 +728,8 @@ class AppGui {
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Build up current shortcut
|
// Build up current shortcut
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
const now = (new Date()).getTime();
|
const now = new Date().getTime();
|
||||||
|
|
||||||
if (now - this.lastShortcutKeyTime_ > 800 || this.isSpecialKey(name)) {
|
if (now - this.lastShortcutKeyTime_ > 800 || this.isSpecialKey(name)) {
|
||||||
this.currentShortcutKeys_ = [name];
|
this.currentShortcutKeys_ = [name];
|
||||||
@@ -793,7 +777,7 @@ class AppGui {
|
|||||||
} else if (keymapItem.type === 'tkwidgets') {
|
} else if (keymapItem.type === 'tkwidgets') {
|
||||||
this.widget('root').handleKey(this.tkWidgetKeys_[keymapItem.command]);
|
this.widget('root').handleKey(this.tkWidgetKeys_[keymapItem.command]);
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Unknown command type: ' + JSON.stringify(keymapItem));
|
throw new Error(`Unknown command type: ${JSON.stringify(keymapItem)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -813,7 +797,6 @@ class AppGui {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AppGui.INPUT_MODE_NORMAL = 1;
|
AppGui.INPUT_MODE_NORMAL = 1;
|
||||||
|
@@ -1,10 +1,5 @@
|
|||||||
const { BaseApplication } = require('lib/BaseApplication');
|
const { BaseApplication } = require('lib/BaseApplication');
|
||||||
const { createStore, applyMiddleware } = require('redux');
|
|
||||||
const { reducer, defaultState } = 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');
|
const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
|
||||||
const { DatabaseDriverNode } = require('lib/database-driver-node.js');
|
|
||||||
const ResourceService = require('lib/services/ResourceService');
|
const ResourceService = require('lib/services/ResourceService');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const Folder = require('lib/models/Folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
@@ -12,21 +7,15 @@ const BaseItem = require('lib/models/BaseItem.js');
|
|||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const Tag = require('lib/models/Tag.js');
|
const Tag = require('lib/models/Tag.js');
|
||||||
const Setting = require('lib/models/Setting.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');
|
const { reg } = require('lib/registry.js');
|
||||||
const { fileExtension } = require('lib/path-utils.js');
|
const { fileExtension } = require('lib/path-utils.js');
|
||||||
const { shim } = require('lib/shim.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const { _, setLocale, defaultLocale, closestSupportedLocale } = require('lib/locale.js');
|
|
||||||
const os = require('os');
|
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const { cliUtils } = require('./cli-utils.js');
|
const { cliUtils } = require('./cli-utils.js');
|
||||||
const Cache = require('lib/Cache');
|
const Cache = require('lib/Cache');
|
||||||
const WelcomeUtils = require('lib/WelcomeUtils');
|
|
||||||
const RevisionService = require('lib/services/RevisionService');
|
const RevisionService = require('lib/services/RevisionService');
|
||||||
|
|
||||||
class Application extends BaseApplication {
|
class Application extends BaseApplication {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@@ -75,7 +64,7 @@ class Application extends BaseApplication {
|
|||||||
// const response = await cliUtils.promptMcq(msg, answers);
|
// const response = await cliUtils.promptMcq(msg, answers);
|
||||||
// if (!response) return null;
|
// if (!response) return null;
|
||||||
|
|
||||||
return output[response - 1];
|
// return output[response - 1];
|
||||||
} else {
|
} else {
|
||||||
return output.length ? output[0] : null;
|
return output.length ? output[0] : null;
|
||||||
}
|
}
|
||||||
@@ -97,10 +86,12 @@ class Application extends BaseApplication {
|
|||||||
const parent = options.parent ? options.parent : app().currentFolder();
|
const parent = options.parent ? options.parent : app().currentFolder();
|
||||||
const ItemClass = BaseItem.itemClass(type);
|
const ItemClass = BaseItem.itemClass(type);
|
||||||
|
|
||||||
if (type == BaseModel.TYPE_NOTE && pattern.indexOf('*') >= 0) { // Handle it as pattern
|
if (type == BaseModel.TYPE_NOTE && pattern.indexOf('*') >= 0) {
|
||||||
|
// Handle it as pattern
|
||||||
if (!parent) throw new Error(_('No notebook selected.'));
|
if (!parent) throw new Error(_('No notebook selected.'));
|
||||||
return await Note.previews(parent.id, { titlePattern: pattern });
|
return await Note.previews(parent.id, { titlePattern: pattern });
|
||||||
} else { // Single item
|
} else {
|
||||||
|
// Single item
|
||||||
let item = null;
|
let item = null;
|
||||||
if (type == BaseModel.TYPE_NOTE) {
|
if (type == BaseModel.TYPE_NOTE) {
|
||||||
if (!parent) throw new Error(_('No notebook has been specified.'));
|
if (!parent) throw new Error(_('No notebook has been specified.'));
|
||||||
@@ -126,15 +117,15 @@ class Application extends BaseApplication {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setupCommand(cmd) {
|
setupCommand(cmd) {
|
||||||
cmd.setStdout((text) => {
|
cmd.setStdout(text => {
|
||||||
return this.stdout(text);
|
return this.stdout(text);
|
||||||
});
|
});
|
||||||
|
|
||||||
cmd.setDispatcher((action) => {
|
cmd.setDispatcher(action => {
|
||||||
if (this.store()) {
|
if (this.store()) {
|
||||||
return this.store().dispatch(action);
|
return this.store().dispatch(action);
|
||||||
} else {
|
} else {
|
||||||
return (action) => {};
|
return () => {};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -145,10 +136,10 @@ class Application extends BaseApplication {
|
|||||||
if (!options.answers) options.answers = options.booleanAnswerDefault === 'y' ? [_('Y'), _('n')] : [_('N'), _('y')];
|
if (!options.answers) options.answers = options.booleanAnswerDefault === 'y' ? [_('Y'), _('n')] : [_('N'), _('y')];
|
||||||
|
|
||||||
if (options.type == 'boolean') {
|
if (options.type == 'boolean') {
|
||||||
message += ' (' + options.answers.join('/') + ')';
|
message += ` (${options.answers.join('/')})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
let answer = await this.gui().prompt('', message + ' ', options);
|
let answer = await this.gui().prompt('', `${message} `, options);
|
||||||
|
|
||||||
if (options.type === 'boolean') {
|
if (options.type === 'boolean') {
|
||||||
if (answer === null) return false; // Pressed ESCAPE
|
if (answer === null) return false; // Pressed ESCAPE
|
||||||
@@ -185,12 +176,12 @@ class Application extends BaseApplication {
|
|||||||
|
|
||||||
commands(uiType = null) {
|
commands(uiType = null) {
|
||||||
if (!this.allCommandsLoaded_) {
|
if (!this.allCommandsLoaded_) {
|
||||||
fs.readdirSync(__dirname).forEach((path) => {
|
fs.readdirSync(__dirname).forEach(path => {
|
||||||
if (path.indexOf('command-') !== 0) return;
|
if (path.indexOf('command-') !== 0) return;
|
||||||
const ext = fileExtension(path)
|
const ext = fileExtension(path);
|
||||||
if (ext != 'js') return;
|
if (ext != 'js') return;
|
||||||
|
|
||||||
let CommandClass = require('./' + path);
|
let CommandClass = require(`./${path}`);
|
||||||
let cmd = new CommandClass();
|
let cmd = new CommandClass();
|
||||||
if (!cmd.enabled()) return;
|
if (!cmd.enabled()) return;
|
||||||
cmd = this.setupCommand(cmd);
|
cmd = this.setupCommand(cmd);
|
||||||
@@ -257,7 +248,7 @@ class Application extends BaseApplication {
|
|||||||
|
|
||||||
let CommandClass = null;
|
let CommandClass = null;
|
||||||
try {
|
try {
|
||||||
CommandClass = require(__dirname + '/command-' + name + '.js');
|
CommandClass = require(`${__dirname}/command-${name}.js`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.message && error.message.indexOf('Cannot find module') >= 0) {
|
if (error.message && error.message.indexOf('Cannot find module') >= 0) {
|
||||||
let e = new Error(_('No such command: %s', name));
|
let e = new Error(_('No such command: %s', name));
|
||||||
@@ -276,19 +267,27 @@ class Application extends BaseApplication {
|
|||||||
|
|
||||||
dummyGui() {
|
dummyGui() {
|
||||||
return {
|
return {
|
||||||
isDummy: () => { return true; },
|
isDummy: () => {
|
||||||
prompt: (initialText = '', promptString = '', options = null) => { return cliUtils.prompt(initialText, promptString, options); },
|
return true;
|
||||||
|
},
|
||||||
|
prompt: (initialText = '', promptString = '', options = null) => {
|
||||||
|
return cliUtils.prompt(initialText, promptString, options);
|
||||||
|
},
|
||||||
showConsole: () => {},
|
showConsole: () => {},
|
||||||
maximizeConsole: () => {},
|
maximizeConsole: () => {},
|
||||||
stdout: (text) => { console.info(text); },
|
stdout: text => {
|
||||||
fullScreen: (b=true) => {},
|
console.info(text);
|
||||||
|
},
|
||||||
|
fullScreen: () => {},
|
||||||
exit: () => {},
|
exit: () => {},
|
||||||
showModalOverlay: (text) => {},
|
showModalOverlay: () => {},
|
||||||
hideModalOverlay: () => {},
|
hideModalOverlay: () => {},
|
||||||
stdoutMaxWidth: () => { return 100; },
|
stdoutMaxWidth: () => {
|
||||||
|
return 100;
|
||||||
|
},
|
||||||
forceRender: () => {},
|
forceRender: () => {},
|
||||||
termSaveState: () => {},
|
termSaveState: () => {},
|
||||||
termRestoreState: (state) => {},
|
termRestoreState: () => {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,7 +299,7 @@ class Application extends BaseApplication {
|
|||||||
|
|
||||||
let outException = null;
|
let outException = null;
|
||||||
try {
|
try {
|
||||||
if (this.gui().isDummy() && !this.activeCommand_.supportsUi('cli')) throw new Error(_('The command "%s" is only available in GUI mode', this.activeCommand_.name()));
|
if (this.gui().isDummy() && !this.activeCommand_.supportsUi('cli')) throw new Error(_('The command "%s" is only available in GUI mode', this.activeCommand_.name()));
|
||||||
const cmdArgs = cliUtils.makeCommandArgs(this.activeCommand_, argv);
|
const cmdArgs = cliUtils.makeCommandArgs(this.activeCommand_, argv);
|
||||||
await this.activeCommand_.action(cmdArgs);
|
await this.activeCommand_.action(cmdArgs);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -316,24 +315,24 @@ class Application extends BaseApplication {
|
|||||||
|
|
||||||
async loadKeymaps() {
|
async loadKeymaps() {
|
||||||
const defaultKeyMap = [
|
const defaultKeyMap = [
|
||||||
{ "keys": [":"], "type": "function", "command": "enter_command_line_mode" },
|
{ keys: [':'], type: 'function', command: 'enter_command_line_mode' },
|
||||||
{ "keys": ["TAB"], "type": "function", "command": "focus_next" },
|
{ keys: ['TAB'], type: 'function', command: 'focus_next' },
|
||||||
{ "keys": ["SHIFT_TAB"], "type": "function", "command": "focus_previous" },
|
{ keys: ['SHIFT_TAB'], type: 'function', command: 'focus_previous' },
|
||||||
{ "keys": ["UP"], "type": "function", "command": "move_up" },
|
{ keys: ['UP'], type: 'function', command: 'move_up' },
|
||||||
{ "keys": ["DOWN"], "type": "function", "command": "move_down" },
|
{ keys: ['DOWN'], type: 'function', command: 'move_down' },
|
||||||
{ "keys": ["PAGE_UP"], "type": "function", "command": "page_up" },
|
{ keys: ['PAGE_UP'], type: 'function', command: 'page_up' },
|
||||||
{ "keys": ["PAGE_DOWN"], "type": "function", "command": "page_down" },
|
{ keys: ['PAGE_DOWN'], type: 'function', command: 'page_down' },
|
||||||
{ "keys": ["ENTER"], "type": "function", "command": "activate" },
|
{ keys: ['ENTER'], type: 'function', command: 'activate' },
|
||||||
{ "keys": ["DELETE", "BACKSPACE"], "type": "function", "command": "delete" },
|
{ keys: ['DELETE', 'BACKSPACE'], type: 'function', command: 'delete' },
|
||||||
{ "keys": [" "], "command": "todo toggle $n" },
|
{ keys: [' '], command: 'todo toggle $n' },
|
||||||
{ "keys": ["tc"], "type": "function", "command": "toggle_console" },
|
{ keys: ['tc'], type: 'function', command: 'toggle_console' },
|
||||||
{ "keys": ["tm"], "type": "function", "command": "toggle_metadata" },
|
{ keys: ['tm'], type: 'function', command: 'toggle_metadata' },
|
||||||
{ "keys": ["/"], "type": "prompt", "command": "search \"\"", "cursorPosition": -2 },
|
{ keys: ['/'], type: 'prompt', command: 'search ""', cursorPosition: -2 },
|
||||||
{ "keys": ["mn"], "type": "prompt", "command": "mknote \"\"", "cursorPosition": -2 },
|
{ keys: ['mn'], type: 'prompt', command: 'mknote ""', cursorPosition: -2 },
|
||||||
{ "keys": ["mt"], "type": "prompt", "command": "mktodo \"\"", "cursorPosition": -2 },
|
{ keys: ['mt'], type: 'prompt', command: 'mktodo ""', cursorPosition: -2 },
|
||||||
{ "keys": ["mb"], "type": "prompt", "command": "mkbook \"\"", "cursorPosition": -2 },
|
{ keys: ['mb'], type: 'prompt', command: 'mkbook ""', cursorPosition: -2 },
|
||||||
{ "keys": ["yn"], "type": "prompt", "command": "cp $n \"\"", "cursorPosition": -2 },
|
{ keys: ['yn'], type: 'prompt', command: 'cp $n ""', cursorPosition: -2 },
|
||||||
{ "keys": ["dn"], "type": "prompt", "command": "mv $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
|
// Filter the keymap item by command so that items in keymap.json can override
|
||||||
@@ -341,10 +340,10 @@ class Application extends BaseApplication {
|
|||||||
const itemsByCommand = {};
|
const itemsByCommand = {};
|
||||||
|
|
||||||
for (let i = 0; i < defaultKeyMap.length; i++) {
|
for (let i = 0; i < defaultKeyMap.length; i++) {
|
||||||
itemsByCommand[defaultKeyMap[i].command] = defaultKeyMap[i]
|
itemsByCommand[defaultKeyMap[i].command] = defaultKeyMap[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
const filePath = Setting.value('profileDir') + '/keymap.json';
|
const filePath = `${Setting.value('profileDir')}/keymap.json`;
|
||||||
if (await fs.pathExists(filePath)) {
|
if (await fs.pathExists(filePath)) {
|
||||||
try {
|
try {
|
||||||
let configString = await fs.readFile(filePath, 'utf-8');
|
let configString = await fs.readFile(filePath, 'utf-8');
|
||||||
@@ -356,7 +355,7 @@ class Application extends BaseApplication {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let msg = error.message ? error.message : '';
|
let msg = error.message ? error.message : '';
|
||||||
msg = 'Could not load keymap ' + filePath + '\n' + msg;
|
msg = `Could not load keymap ${filePath}\n${msg}`;
|
||||||
error.message = msg;
|
error.message = msg;
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -368,13 +367,25 @@ class Application extends BaseApplication {
|
|||||||
output.push(itemsByCommand[n]);
|
output.push(itemsByCommand[n]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Map reserved shortcuts to their equivalent key
|
||||||
|
// https://github.com/cronvel/terminal-kit/issues/101
|
||||||
|
for (let i = 0; i < output.length; i++) {
|
||||||
|
const newKeys = output[i].keys.map(k => {
|
||||||
|
k = k.replace(/CTRL_H/g, 'BACKSPACE');
|
||||||
|
k = k.replace(/CTRL_I/g, 'TAB');
|
||||||
|
k = k.replace(/CTRL_M/g, 'ENTER');
|
||||||
|
return k;
|
||||||
|
});
|
||||||
|
output[i].keys = newKeys;
|
||||||
|
}
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
async start(argv) {
|
async start(argv) {
|
||||||
argv = await super.start(argv);
|
argv = await super.start(argv);
|
||||||
|
|
||||||
cliUtils.setStdout((object) => {
|
cliUtils.setStdout(object => {
|
||||||
return this.stdout(object);
|
return this.stdout(object);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -385,6 +396,8 @@ class Application extends BaseApplication {
|
|||||||
|
|
||||||
this.currentFolder_ = await Folder.load(Setting.value('activeFolderId'));
|
this.currentFolder_ = await Folder.load(Setting.value('activeFolderId'));
|
||||||
|
|
||||||
|
await this.applySettingsSideEffects();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.execCommand(argv);
|
await this.execCommand(argv);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -398,10 +411,11 @@ class Application extends BaseApplication {
|
|||||||
|
|
||||||
await Setting.saveAll();
|
await Setting.saveAll();
|
||||||
|
|
||||||
// Need to call exit() explicitely, otherwise Node wait for any timeout to complete
|
// Need to call exit() explicitly, otherwise Node wait for any timeout to complete
|
||||||
// https://stackoverflow.com/questions/18050095
|
// https://stackoverflow.com/questions/18050095
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
} else { // Otherwise open the GUI
|
} else {
|
||||||
|
// Otherwise open the GUI
|
||||||
this.initRedux();
|
this.initRedux();
|
||||||
|
|
||||||
const keymap = await this.loadKeymaps();
|
const keymap = await this.loadKeymaps();
|
||||||
@@ -421,7 +435,7 @@ class Application extends BaseApplication {
|
|||||||
const tags = await Tag.allWithNotes();
|
const tags = await Tag.allWithNotes();
|
||||||
|
|
||||||
ResourceService.runInBackground();
|
ResourceService.runInBackground();
|
||||||
|
|
||||||
RevisionService.instance().runInBackground();
|
RevisionService.instance().runInBackground();
|
||||||
|
|
||||||
this.dispatch({
|
this.dispatch({
|
||||||
@@ -435,7 +449,6 @@ class Application extends BaseApplication {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let application_ = null;
|
let application_ = null;
|
||||||
@@ -446,4 +459,4 @@ function app() {
|
|||||||
return application_;
|
return application_;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { app };
|
module.exports = { app };
|
||||||
|
@@ -10,42 +10,47 @@ async function handleAutocompletionPromise(line) {
|
|||||||
// Auto-complete the command name
|
// Auto-complete the command name
|
||||||
const names = await app().commandNames();
|
const names = await app().commandNames();
|
||||||
let words = getArguments(line);
|
let words = getArguments(line);
|
||||||
//If there is only one word and it is not already a command name then you
|
// If there is only one word and it is not already a command name then you
|
||||||
//should look for commmands it could be
|
// should look for commands it could be
|
||||||
if (words.length == 1) {
|
if (words.length == 1) {
|
||||||
if (names.indexOf(words[0]) === -1) {
|
if (names.indexOf(words[0]) === -1) {
|
||||||
let x = names.filter((n) => n.indexOf(words[0]) === 0);
|
let x = names.filter(n => n.indexOf(words[0]) === 0);
|
||||||
if (x.length === 1) {
|
if (x.length === 1) {
|
||||||
return x[0] + ' ';
|
return `${x[0]} `;
|
||||||
}
|
}
|
||||||
return x.length > 0 ? x.map((a) => a + ' ') : line;
|
return x.length > 0 ? x.map(a => `${a} `) : line;
|
||||||
} else {
|
} else {
|
||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//There is more than one word and it is a command
|
// There is more than one word and it is a command
|
||||||
const metadata = (await app().commandMetadata())[words[0]];
|
const metadata = (await app().commandMetadata())[words[0]];
|
||||||
//If for some reason this command does not have any associated metadata
|
// If for some reason this command does not have any associated metadata
|
||||||
//just don't autocomplete. However, this should not happen.
|
// just don't autocomplete. However, this should not happen.
|
||||||
if (metadata === undefined) {
|
if (metadata === undefined) {
|
||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
//complete an option
|
|
||||||
|
if (words[0] === 'tag' && words[1] === 'notetags') {
|
||||||
|
metadata.usage = 'tag <tag-command> <note>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// complete an option
|
||||||
let next = words.length > 1 ? words[words.length - 1] : '';
|
let next = words.length > 1 ? words[words.length - 1] : '';
|
||||||
let l = [];
|
let l = [];
|
||||||
if (next[0] === '-') {
|
if (next[0] === '-') {
|
||||||
for (let i = 0; i<metadata.options.length; i++) {
|
for (let i = 0; i < metadata.options.length; i++) {
|
||||||
const options = metadata.options[i][0].split(' ');
|
const options = metadata.options[i][0].split(' ');
|
||||||
//if there are multiple options then they will be separated by comma and
|
// if there are multiple options then they will be separated by comma and
|
||||||
//space. The comma should be removed
|
// space. The comma should be removed
|
||||||
if (options[0][options[0].length - 1] === ',') {
|
if (options[0][options[0].length - 1] === ',') {
|
||||||
options[0] = options[0].slice(0, -1);
|
options[0] = options[0].slice(0, -1);
|
||||||
}
|
}
|
||||||
if (words.includes(options[0]) || words.includes(options[1])) {
|
if (words.includes(options[0]) || words.includes(options[1])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
//First two elements are the flag and the third is the description
|
// First two elements are the flag and the third is the description
|
||||||
//Only autocomplete long
|
// Only autocomplete long
|
||||||
if (options.length > 1 && options[1].indexOf(next) === 0) {
|
if (options.length > 1 && options[1].indexOf(next) === 0) {
|
||||||
l.push(options[1]);
|
l.push(options[1]);
|
||||||
} else if (options[0].indexOf(next) === 0) {
|
} else if (options[0].indexOf(next) === 0) {
|
||||||
@@ -55,44 +60,43 @@ async function handleAutocompletionPromise(line) {
|
|||||||
if (l.length === 0) {
|
if (l.length === 0) {
|
||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
let ret = l.map(a=>toCommandLine(a));
|
let ret = l.map(a => toCommandLine(a));
|
||||||
ret.prefix = toCommandLine(words.slice(0, -1)) + ' ';
|
ret.prefix = `${toCommandLine(words.slice(0, -1))} `;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
//Complete an argument
|
// Complete an argument
|
||||||
//Determine the number of positional arguments by counting the number of
|
// Determine the number of positional arguments by counting the number of
|
||||||
//words that don't start with a - less one for the command name
|
// words that don't start with a - less one for the command name
|
||||||
const positionalArgs = words.filter((a)=>a.indexOf('-') !== 0).length - 1;
|
const positionalArgs = words.filter(a => a.indexOf('-') !== 0).length - 1;
|
||||||
|
|
||||||
let cmdUsage = yargParser(metadata.usage)['_'];
|
let cmdUsage = yargParser(metadata.usage)['_'];
|
||||||
cmdUsage.splice(0, 1);
|
cmdUsage.splice(0, 1);
|
||||||
|
|
||||||
if (cmdUsage.length >= positionalArgs) {
|
if (cmdUsage.length >= positionalArgs) {
|
||||||
|
|
||||||
let argName = cmdUsage[positionalArgs - 1];
|
let argName = cmdUsage[positionalArgs - 1];
|
||||||
argName = cliUtils.parseCommandArg(argName).name;
|
argName = cliUtils.parseCommandArg(argName).name;
|
||||||
|
|
||||||
const currentFolder = app().currentFolder();
|
const currentFolder = app().currentFolder();
|
||||||
|
|
||||||
if (argName == 'note' || argName == 'note-pattern') {
|
if (argName == 'note' || argName == 'note-pattern') {
|
||||||
const notes = currentFolder ? await Note.previews(currentFolder.id, { titlePattern: next + '*' }) : [];
|
const notes = currentFolder ? await Note.previews(currentFolder.id, { titlePattern: `${next}*` }) : [];
|
||||||
l.push(...notes.map((n) => n.title));
|
l.push(...notes.map(n => n.title));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argName == 'notebook') {
|
if (argName == 'notebook') {
|
||||||
const folders = await Folder.search({ titlePattern: next + '*' });
|
const folders = await Folder.search({ titlePattern: `${next}*` });
|
||||||
l.push(...folders.map((n) => n.title));
|
l.push(...folders.map(n => n.title));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argName == 'item') {
|
if (argName == 'item') {
|
||||||
const notes = currentFolder ? await Note.previews(currentFolder.id, { titlePattern: next + '*' }) : [];
|
const notes = currentFolder ? await Note.previews(currentFolder.id, { titlePattern: `${next}*` }) : [];
|
||||||
const folders = await Folder.search({ titlePattern: next + '*' });
|
const folders = await Folder.search({ titlePattern: `${next}*` });
|
||||||
l.push(...notes.map((n) => n.title), folders.map((n) => n.title));
|
l.push(...notes.map(n => n.title), folders.map(n => n.title));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argName == 'tag') {
|
if (argName == 'tag') {
|
||||||
let tags = await Tag.search({ titlePattern: next + '*' });
|
let tags = await Tag.search({ titlePattern: `${next}*` });
|
||||||
l.push(...tags.map((n) => n.title));
|
l.push(...tags.map(n => n.title));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argName == 'file') {
|
if (argName == 'file') {
|
||||||
@@ -101,7 +105,7 @@ async function handleAutocompletionPromise(line) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (argName == 'tag-command') {
|
if (argName == 'tag-command') {
|
||||||
let c = filterList(['add', 'remove', 'list'], next);
|
let c = filterList(['add', 'remove', 'list', 'notetags'], next);
|
||||||
l.push(...c);
|
l.push(...c);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,12 +117,11 @@ async function handleAutocompletionPromise(line) {
|
|||||||
if (l.length === 1) {
|
if (l.length === 1) {
|
||||||
return toCommandLine([...words.slice(0, -1), l[0]]);
|
return toCommandLine([...words.slice(0, -1), l[0]]);
|
||||||
} else if (l.length > 1) {
|
} else if (l.length > 1) {
|
||||||
let ret = l.map(a=>toCommandLine(a));
|
let ret = l.map(a => toCommandLine(a));
|
||||||
ret.prefix = toCommandLine(words.slice(0, -1)) + ' ';
|
ret.prefix = `${toCommandLine(words.slice(0, -1))} `;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
return line;
|
return line;
|
||||||
|
|
||||||
}
|
}
|
||||||
function handleAutocompletion(str, callback) {
|
function handleAutocompletion(str, callback) {
|
||||||
handleAutocompletionPromise(str).then(function(res) {
|
handleAutocompletionPromise(str).then(function(res) {
|
||||||
@@ -127,22 +130,24 @@ function handleAutocompletion(str, callback) {
|
|||||||
}
|
}
|
||||||
function toCommandLine(args) {
|
function toCommandLine(args) {
|
||||||
if (Array.isArray(args)) {
|
if (Array.isArray(args)) {
|
||||||
return args.map(function(a) {
|
return args
|
||||||
if(a.indexOf('"') !== -1 || a.indexOf(' ') !== -1) {
|
.map(function(a) {
|
||||||
return "'" + a + "'";
|
if (a.indexOf('"') !== -1 || a.indexOf(' ') !== -1) {
|
||||||
} else if (a.indexOf("'") !== -1) {
|
return `'${a}'`;
|
||||||
return '"' + a + '"';
|
} else if (a.indexOf('\'') !== -1) {
|
||||||
} else {
|
return `"${a}"`;
|
||||||
return a;
|
} else {
|
||||||
}
|
return a;
|
||||||
}).join(' ');
|
}
|
||||||
|
})
|
||||||
|
.join(' ');
|
||||||
} else {
|
} else {
|
||||||
if(args.indexOf('"') !== -1 || args.indexOf(' ') !== -1) {
|
if (args.indexOf('"') !== -1 || args.indexOf(' ') !== -1) {
|
||||||
return "'" + args + "' ";
|
return `'${args}' `;
|
||||||
} else if (args.indexOf("'") !== -1) {
|
} else if (args.indexOf('\'') !== -1) {
|
||||||
return '"' + args + '" ';
|
return `"${args}" `;
|
||||||
} else {
|
} else {
|
||||||
return args + ' ';
|
return `${args} `;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,27 +156,26 @@ function getArguments(line) {
|
|||||||
let inDoubleQuotes = false;
|
let inDoubleQuotes = false;
|
||||||
let currentWord = '';
|
let currentWord = '';
|
||||||
let parsed = [];
|
let parsed = [];
|
||||||
for(let i = 0; i<line.length; i++) {
|
for (let i = 0; i < line.length; i++) {
|
||||||
if(line[i] === '"') {
|
if (line[i] === '"') {
|
||||||
if(inDoubleQuotes) {
|
if (inDoubleQuotes) {
|
||||||
inDoubleQuotes = false;
|
inDoubleQuotes = false;
|
||||||
//maybe push word to parsed?
|
// maybe push word to parsed?
|
||||||
//currentWord += '"';
|
// currentWord += '"';
|
||||||
} else {
|
} else {
|
||||||
inDoubleQuotes = true;
|
inDoubleQuotes = true;
|
||||||
//currentWord += '"';
|
// currentWord += '"';
|
||||||
}
|
}
|
||||||
} else if(line[i] === "'") {
|
} else if (line[i] === '\'') {
|
||||||
if(inSingleQuotes) {
|
if (inSingleQuotes) {
|
||||||
inSingleQuotes = false;
|
inSingleQuotes = false;
|
||||||
//maybe push word to parsed?
|
// maybe push word to parsed?
|
||||||
//currentWord += "'";
|
// currentWord += "'";
|
||||||
} else {
|
} else {
|
||||||
inSingleQuotes = true;
|
inSingleQuotes = true;
|
||||||
//currentWord += "'";
|
// currentWord += "'";
|
||||||
}
|
}
|
||||||
} else if (/\s/.test(line[i]) &&
|
} else if (/\s/.test(line[i]) && !(inDoubleQuotes || inSingleQuotes)) {
|
||||||
!(inDoubleQuotes || inSingleQuotes)) {
|
|
||||||
if (currentWord !== '') {
|
if (currentWord !== '') {
|
||||||
parsed.push(currentWord);
|
parsed.push(currentWord);
|
||||||
currentWord = '';
|
currentWord = '';
|
||||||
|
@@ -2,7 +2,6 @@ const { _ } = require('lib/locale.js');
|
|||||||
const { reg } = require('lib/registry.js');
|
const { reg } = require('lib/registry.js');
|
||||||
|
|
||||||
class BaseCommand {
|
class BaseCommand {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.stdout_ = null;
|
this.stdout_ = null;
|
||||||
this.prompt_ = null;
|
this.prompt_ = null;
|
||||||
@@ -20,7 +19,7 @@ class BaseCommand {
|
|||||||
throw new Error('Description not defined');
|
throw new Error('Description not defined');
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action() {
|
||||||
throw new Error('Action not defined');
|
throw new Error('Action not defined');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +92,6 @@ class BaseCommand {
|
|||||||
logger() {
|
logger() {
|
||||||
return reg.logger();
|
return reg.logger();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { BaseCommand };
|
module.exports = { BaseCommand };
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const { fileExtension, basename, dirname } = require('lib/path-utils.js');
|
const { fileExtension, dirname } = require('lib/path-utils.js');
|
||||||
const wrap_ = require('word-wrap');
|
const wrap_ = require('word-wrap');
|
||||||
const { _, setLocale, languageCode } = require('lib/locale.js');
|
const { languageCode } = require('lib/locale.js');
|
||||||
|
|
||||||
const rootDir = dirname(dirname(__dirname));
|
const rootDir = dirname(dirname(__dirname));
|
||||||
const MAX_WIDTH = 78;
|
const MAX_WIDTH = 78;
|
||||||
@@ -22,14 +22,14 @@ function renderOptions(options) {
|
|||||||
let option = options[i];
|
let option = options[i];
|
||||||
const flag = option[0];
|
const flag = option[0];
|
||||||
const indent = INDENT + INDENT + ' '.repeat(optionColWidth + 2);
|
const indent = INDENT + INDENT + ' '.repeat(optionColWidth + 2);
|
||||||
|
|
||||||
let r = wrap(option[1], indent);
|
let r = wrap(option[1], indent);
|
||||||
r = r.substr(flag.length + (INDENT + INDENT).length);
|
r = r.substr(flag.length + (INDENT + INDENT).length);
|
||||||
r = INDENT + INDENT + flag + r;
|
r = INDENT + INDENT + flag + r;
|
||||||
output.push(r);
|
output.push(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
return output.join("\n");
|
return output.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderCommand(cmd) {
|
function renderCommand(cmd) {
|
||||||
@@ -44,17 +44,17 @@ function renderCommand(cmd) {
|
|||||||
output.push('');
|
output.push('');
|
||||||
output.push(optionString);
|
output.push(optionString);
|
||||||
}
|
}
|
||||||
return output.join("\n");
|
return output.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCommands() {
|
function getCommands() {
|
||||||
let output = [];
|
let output = [];
|
||||||
fs.readdirSync(__dirname).forEach((path) => {
|
fs.readdirSync(__dirname).forEach(path => {
|
||||||
if (path.indexOf('command-') !== 0) return;
|
if (path.indexOf('command-') !== 0) return;
|
||||||
const ext = fileExtension(path)
|
const ext = fileExtension(path);
|
||||||
if (ext != 'js') return;
|
if (ext != 'js') return;
|
||||||
|
|
||||||
let CommandClass = require('./' + path);
|
let CommandClass = require(`./${path}`);
|
||||||
let cmd = new CommandClass();
|
let cmd = new CommandClass();
|
||||||
if (!cmd.enabled()) return;
|
if (!cmd.enabled()) return;
|
||||||
if (cmd.hidden()) return;
|
if (cmd.hidden()) return;
|
||||||
@@ -87,14 +87,14 @@ function getHeader() {
|
|||||||
let description = [];
|
let description = [];
|
||||||
description.push('Joplin is a note taking and to-do application, which can handle a large number of notes organised into notebooks.');
|
description.push('Joplin is a note taking and to-do application, which can handle a large number of notes organised into notebooks.');
|
||||||
description.push('The notes are searchable, can be copied, tagged and modified with your own text editor.');
|
description.push('The notes are searchable, can be copied, tagged and modified with your own text editor.');
|
||||||
description.push("\n\n");
|
description.push('\n\n');
|
||||||
description.push('The notes can be synchronised with various target including the file system (for example with a network directory) or with Microsoft OneDrive.');
|
description.push('The notes can be synchronised with various target including the file system (for example with a network directory) or with Microsoft OneDrive.');
|
||||||
description.push("\n\n");
|
description.push('\n\n');
|
||||||
description.push('Notes exported from Evenotes via .enex files can be imported into Joplin, including the formatted content, resources (images, attachments, etc.) and complete metadata (geolocation, updated time, created time, etc.).');
|
description.push('Notes exported from Evenotes via .enex files can be imported into Joplin, including the formatted content, resources (images, attachments, etc.) and complete metadata (geolocation, updated time, created time, etc.).');
|
||||||
|
|
||||||
output.push(wrap(description.join(''), INDENT));
|
output.push(wrap(description.join(''), INDENT));
|
||||||
|
|
||||||
return output.join("\n");
|
return output.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFooter() {
|
function getFooter() {
|
||||||
@@ -102,18 +102,18 @@ function getFooter() {
|
|||||||
|
|
||||||
output.push('WEBSITE');
|
output.push('WEBSITE');
|
||||||
output.push('');
|
output.push('');
|
||||||
output.push(INDENT + 'https://joplinapp.org');
|
output.push(`${INDENT}https://joplinapp.org`);
|
||||||
|
|
||||||
output.push('');
|
output.push('');
|
||||||
|
|
||||||
output.push('LICENSE');
|
output.push('LICENSE');
|
||||||
output.push('');
|
output.push('');
|
||||||
let filePath = rootDir + '/LICENSE_' + languageCode();
|
let filePath = `${rootDir}/LICENSE_${languageCode()}`;
|
||||||
if (!fs.existsSync(filePath)) filePath = rootDir + '/LICENSE';
|
if (!fs.existsSync(filePath)) filePath = `${rootDir}/LICENSE`;
|
||||||
const licenseText = fs.readFileSync(filePath, 'utf8');
|
const licenseText = fs.readFileSync(filePath, 'utf8');
|
||||||
output.push(wrap(licenseText, INDENT));
|
output.push(wrap(licenseText, INDENT));
|
||||||
|
|
||||||
return output.join("\n");
|
return output.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
@@ -128,12 +128,12 @@ async function main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const headerText = getHeader();
|
const headerText = getHeader();
|
||||||
const commandsText = commandBlocks.join("\n\n");
|
const commandsText = commandBlocks.join('\n\n');
|
||||||
const footerText = getFooter();
|
const footerText = getFooter();
|
||||||
|
|
||||||
console.info(headerText + "\n\n" + 'USAGE' + "\n\n" + commandsText + "\n\n" + footerText);
|
console.info(`${headerText}\n\n` + 'USAGE' + `\n\n${commandsText}\n\n${footerText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch((error) => {
|
main().catch(error => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
});
|
});
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
"use strict"
|
'use strict';
|
||||||
|
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const { Logger } = require('lib/logger.js');
|
const { Logger } = require('lib/logger.js');
|
||||||
@@ -10,14 +10,14 @@ const Folder = require('lib/models/Folder.js');
|
|||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const Setting = require('lib/models/Setting.js');
|
const Setting = require('lib/models/Setting.js');
|
||||||
const { sprintf } = require('sprintf-js');
|
const { sprintf } = require('sprintf-js');
|
||||||
const exec = require('child_process').exec
|
const exec = require('child_process').exec;
|
||||||
|
|
||||||
process.on('unhandledRejection', (reason, p) => {
|
process.on('unhandledRejection', (reason, p) => {
|
||||||
console.error('Unhandled promise rejection', p, 'reason:', reason);
|
console.error('Unhandled promise rejection', p, 'reason:', reason);
|
||||||
});
|
});
|
||||||
|
|
||||||
const baseDir = dirname(__dirname) + '/tests/cli-integration';
|
const baseDir = `${dirname(__dirname)}/tests/cli-integration`;
|
||||||
const joplinAppPath = __dirname + '/main.js';
|
const joplinAppPath = `${__dirname}/main.js`;
|
||||||
|
|
||||||
const logger = new Logger();
|
const logger = new Logger();
|
||||||
logger.addTarget('console');
|
logger.addTarget('console');
|
||||||
@@ -32,17 +32,17 @@ db.setLogger(dbLogger);
|
|||||||
|
|
||||||
function createClient(id) {
|
function createClient(id) {
|
||||||
return {
|
return {
|
||||||
'id': id,
|
id: id,
|
||||||
'profileDir': baseDir + '/client' + id,
|
profileDir: `${baseDir}/client${id}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = createClient(1);
|
const client = createClient(1);
|
||||||
|
|
||||||
function execCommand(client, command, options = {}) {
|
function execCommand(client, command) {
|
||||||
let exePath = 'node ' + joplinAppPath;
|
let exePath = `node ${joplinAppPath}`;
|
||||||
let cmd = exePath + ' --update-geolocation-disabled --env dev --profile ' + client.profileDir + ' ' + command;
|
let cmd = `${exePath} --update-geolocation-disabled --env dev --profile ${client.profileDir} ${command}`;
|
||||||
logger.info(client.id + ': ' + command);
|
logger.info(`${client.id}: ${command}`);
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
exec(cmd, (error, stdout, stderr) => {
|
exec(cmd, (error, stdout, stderr) => {
|
||||||
@@ -72,14 +72,7 @@ function assertEquals(expected, real) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function clearDatabase() {
|
async function clearDatabase() {
|
||||||
await db.transactionExecBatch([
|
await db.transactionExecBatch(['DELETE FROM folders', 'DELETE FROM notes', 'DELETE FROM tags', 'DELETE FROM note_tags', 'DELETE FROM resources', 'DELETE FROM deleted_items']);
|
||||||
'DELETE FROM folders',
|
|
||||||
'DELETE FROM notes',
|
|
||||||
'DELETE FROM tags',
|
|
||||||
'DELETE FROM note_tags',
|
|
||||||
'DELETE FROM resources',
|
|
||||||
'DELETE FROM deleted_items',
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const testUnits = {};
|
const testUnits = {};
|
||||||
@@ -101,7 +94,7 @@ testUnits.testFolders = async () => {
|
|||||||
|
|
||||||
folders = await Folder.all();
|
folders = await Folder.all();
|
||||||
assertEquals(0, folders.length);
|
assertEquals(0, folders.length);
|
||||||
}
|
};
|
||||||
|
|
||||||
testUnits.testNotes = async () => {
|
testUnits.testNotes = async () => {
|
||||||
await execCommand(client, 'mkbook nb1');
|
await execCommand(client, 'mkbook nb1');
|
||||||
@@ -121,16 +114,16 @@ testUnits.testNotes = async () => {
|
|||||||
notes = await Note.all();
|
notes = await Note.all();
|
||||||
assertEquals(2, notes.length);
|
assertEquals(2, notes.length);
|
||||||
|
|
||||||
await execCommand(client, "rm -f 'blabla*'");
|
await execCommand(client, 'rm -f \'blabla*\'');
|
||||||
|
|
||||||
notes = await Note.all();
|
notes = await Note.all();
|
||||||
assertEquals(2, notes.length);
|
assertEquals(2, notes.length);
|
||||||
|
|
||||||
await execCommand(client, "rm -f 'n*'");
|
await execCommand(client, 'rm -f \'n*\'');
|
||||||
|
|
||||||
notes = await Note.all();
|
notes = await Note.all();
|
||||||
assertEquals(0, notes.length);
|
assertEquals(0, notes.length);
|
||||||
}
|
};
|
||||||
|
|
||||||
testUnits.testCat = async () => {
|
testUnits.testCat = async () => {
|
||||||
await execCommand(client, 'mkbook nb1');
|
await execCommand(client, 'mkbook nb1');
|
||||||
@@ -145,7 +138,7 @@ testUnits.testCat = async () => {
|
|||||||
|
|
||||||
r = await execCommand(client, 'cat -v mynote');
|
r = await execCommand(client, 'cat -v mynote');
|
||||||
assertTrue(r.indexOf(note.id) >= 0);
|
assertTrue(r.indexOf(note.id) >= 0);
|
||||||
}
|
};
|
||||||
|
|
||||||
testUnits.testConfig = async () => {
|
testUnits.testConfig = async () => {
|
||||||
await execCommand(client, 'config editor vim');
|
await execCommand(client, 'config editor vim');
|
||||||
@@ -159,7 +152,7 @@ testUnits.testConfig = async () => {
|
|||||||
let r = await execCommand(client, 'config');
|
let r = await execCommand(client, 'config');
|
||||||
assertTrue(r.indexOf('editor') >= 0);
|
assertTrue(r.indexOf('editor') >= 0);
|
||||||
assertTrue(r.indexOf('subl') >= 0);
|
assertTrue(r.indexOf('subl') >= 0);
|
||||||
}
|
};
|
||||||
|
|
||||||
testUnits.testCp = async () => {
|
testUnits.testCp = async () => {
|
||||||
await execCommand(client, 'mkbook nb2');
|
await execCommand(client, 'mkbook nb2');
|
||||||
@@ -180,7 +173,7 @@ testUnits.testCp = async () => {
|
|||||||
notes = await Note.previews(f2.id);
|
notes = await Note.previews(f2.id);
|
||||||
assertEquals(1, notes.length);
|
assertEquals(1, notes.length);
|
||||||
assertEquals(notesF1[0].title, notes[0].title);
|
assertEquals(notesF1[0].title, notes[0].title);
|
||||||
}
|
};
|
||||||
|
|
||||||
testUnits.testLs = async () => {
|
testUnits.testLs = async () => {
|
||||||
await execCommand(client, 'mkbook nb1');
|
await execCommand(client, 'mkbook nb1');
|
||||||
@@ -190,7 +183,7 @@ testUnits.testLs = async () => {
|
|||||||
|
|
||||||
assertTrue(r.indexOf('note1') >= 0);
|
assertTrue(r.indexOf('note1') >= 0);
|
||||||
assertTrue(r.indexOf('note2') >= 0);
|
assertTrue(r.indexOf('note2') >= 0);
|
||||||
}
|
};
|
||||||
|
|
||||||
testUnits.testMv = async () => {
|
testUnits.testMv = async () => {
|
||||||
await execCommand(client, 'mkbook nb2');
|
await execCommand(client, 'mkbook nb2');
|
||||||
@@ -210,21 +203,21 @@ testUnits.testMv = async () => {
|
|||||||
await execCommand(client, 'mknote note2');
|
await execCommand(client, 'mknote note2');
|
||||||
await execCommand(client, 'mknote note3');
|
await execCommand(client, 'mknote note3');
|
||||||
await execCommand(client, 'mknote blabla');
|
await execCommand(client, 'mknote blabla');
|
||||||
await execCommand(client, "mv 'note*' nb2");
|
await execCommand(client, 'mv \'note*\' nb2');
|
||||||
|
|
||||||
notes1 = await Note.previews(f1.id);
|
notes1 = await Note.previews(f1.id);
|
||||||
notes2 = await Note.previews(f2.id);
|
notes2 = await Note.previews(f2.id);
|
||||||
|
|
||||||
assertEquals(1, notes1.length);
|
assertEquals(1, notes1.length);
|
||||||
assertEquals(4, notes2.length);
|
assertEquals(4, notes2.length);
|
||||||
}
|
};
|
||||||
|
|
||||||
async function main(argv) {
|
async function main() {
|
||||||
await fs.remove(baseDir);
|
await fs.remove(baseDir);
|
||||||
|
|
||||||
logger.info(await execCommand(client, 'version'));
|
logger.info(await execCommand(client, 'version'));
|
||||||
|
|
||||||
await db.open({ name: client.profileDir + '/database.sqlite' });
|
await db.open({ name: `${client.profileDir}/database.sqlite` });
|
||||||
BaseModel.db_ = db;
|
BaseModel.db_ = db;
|
||||||
await Setting.load();
|
await Setting.load();
|
||||||
|
|
||||||
@@ -237,13 +230,13 @@ async function main(argv) {
|
|||||||
|
|
||||||
await clearDatabase();
|
await clearDatabase();
|
||||||
let testName = n.substr(4).toLowerCase();
|
let testName = n.substr(4).toLowerCase();
|
||||||
process.stdout.write(testName + ': ');
|
process.stdout.write(`${testName}: `);
|
||||||
await testUnits[n]();
|
await testUnits[n]();
|
||||||
console.info('');
|
console.info('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
main(process.argv).catch((error) => {
|
main(process.argv).catch(error => {
|
||||||
console.info('');
|
console.info('');
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
});
|
});
|
||||||
|
@@ -5,7 +5,7 @@ const stringPadding = require('string-padding');
|
|||||||
|
|
||||||
const cliUtils = {};
|
const cliUtils = {};
|
||||||
|
|
||||||
cliUtils.printArray = function(logFunction, rows, headers = null) {
|
cliUtils.printArray = function(logFunction, rows) {
|
||||||
if (!rows.length) return '';
|
if (!rows.length) return '';
|
||||||
|
|
||||||
const ALIGN_LEFT = 0;
|
const ALIGN_LEFT = 0;
|
||||||
@@ -16,7 +16,7 @@ cliUtils.printArray = function(logFunction, rows, headers = null) {
|
|||||||
|
|
||||||
for (let i = 0; i < rows.length; i++) {
|
for (let i = 0; i < rows.length; i++) {
|
||||||
let row = rows[i];
|
let row = rows[i];
|
||||||
|
|
||||||
for (let j = 0; j < row.length; j++) {
|
for (let j = 0; j < row.length; j++) {
|
||||||
let item = row[j];
|
let item = row[j];
|
||||||
let width = item ? item.toString().length : 0;
|
let width = item ? item.toString().length : 0;
|
||||||
@@ -26,7 +26,6 @@ cliUtils.printArray = function(logFunction, rows, headers = null) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let lines = [];
|
|
||||||
for (let row = 0; row < rows.length; row++) {
|
for (let row = 0; row < rows.length; row++) {
|
||||||
let line = [];
|
let line = [];
|
||||||
for (let col = 0; col < colWidths.length; col++) {
|
for (let col = 0; col < colWidths.length; col++) {
|
||||||
@@ -37,7 +36,7 @@ cliUtils.printArray = function(logFunction, rows, headers = null) {
|
|||||||
}
|
}
|
||||||
logFunction(line.join(' '));
|
logFunction(line.join(' '));
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
cliUtils.parseFlags = function(flags) {
|
cliUtils.parseFlags = function(flags) {
|
||||||
let output = {};
|
let output = {};
|
||||||
@@ -56,10 +55,10 @@ cliUtils.parseFlags = function(flags) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
}
|
};
|
||||||
|
|
||||||
cliUtils.parseCommandArg = function(arg) {
|
cliUtils.parseCommandArg = function(arg) {
|
||||||
if (arg.length <= 2) throw new Error('Invalid command arg: ' + arg);
|
if (arg.length <= 2) throw new Error(`Invalid command arg: ${arg}`);
|
||||||
|
|
||||||
const c1 = arg[0];
|
const c1 = arg[0];
|
||||||
const c2 = arg[arg.length - 1];
|
const c2 = arg[arg.length - 1];
|
||||||
@@ -70,9 +69,9 @@ cliUtils.parseCommandArg = function(arg) {
|
|||||||
} else if (c1 == '[' && c2 == ']') {
|
} else if (c1 == '[' && c2 == ']') {
|
||||||
return { required: false, name: name };
|
return { required: false, name: name };
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Invalid command arg: ' + arg);
|
throw new Error(`Invalid command arg: ${arg}`);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
cliUtils.makeCommandArgs = function(cmd, argv) {
|
cliUtils.makeCommandArgs = function(cmd, argv) {
|
||||||
let cmdUsage = cmd.usage();
|
let cmdUsage = cmd.usage();
|
||||||
@@ -83,9 +82,8 @@ cliUtils.makeCommandArgs = function(cmd, argv) {
|
|||||||
let booleanFlags = [];
|
let booleanFlags = [];
|
||||||
let aliases = {};
|
let aliases = {};
|
||||||
for (let i = 0; i < options.length; i++) {
|
for (let i = 0; i < options.length; i++) {
|
||||||
if (options[i].length != 2) throw new Error('Invalid options: ' + options[i]);
|
if (options[i].length != 2) throw new Error(`Invalid options: ${options[i]}`);
|
||||||
let flags = options[i][0];
|
let flags = options[i][0];
|
||||||
let text = options[i][1];
|
|
||||||
|
|
||||||
flags = cliUtils.parseFlags(flags);
|
flags = cliUtils.parseFlags(flags);
|
||||||
|
|
||||||
@@ -125,27 +123,27 @@ cliUtils.makeCommandArgs = function(cmd, argv) {
|
|||||||
output.options = argOptions;
|
output.options = argOptions;
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
};
|
||||||
|
|
||||||
cliUtils.promptMcq = function(message, answers) {
|
cliUtils.promptMcq = function(message, answers) {
|
||||||
const readline = require('readline');
|
const readline = require('readline');
|
||||||
|
|
||||||
const rl = readline.createInterface({
|
const rl = readline.createInterface({
|
||||||
input: process.stdin,
|
input: process.stdin,
|
||||||
output: process.stdout
|
output: process.stdout,
|
||||||
});
|
});
|
||||||
|
|
||||||
message += "\n\n";
|
message += '\n\n';
|
||||||
for (let n in answers) {
|
for (let n in answers) {
|
||||||
if (!answers.hasOwnProperty(n)) continue;
|
if (!answers.hasOwnProperty(n)) continue;
|
||||||
message += _('%s: %s', n, answers[n]) + "\n";
|
message += `${_('%s: %s', n, answers[n])}\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
message += "\n";
|
message += '\n';
|
||||||
message += _('Your choice: ');
|
message += _('Your choice: ');
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
rl.question(message, (answer) => {
|
rl.question(message, answer => {
|
||||||
rl.close();
|
rl.close();
|
||||||
|
|
||||||
if (!(answer in answers)) {
|
if (!(answer in answers)) {
|
||||||
@@ -156,7 +154,7 @@ cliUtils.promptMcq = function(message, answers) {
|
|||||||
resolve(answer);
|
resolve(answer);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
cliUtils.promptConfirm = function(message, answers = null) {
|
cliUtils.promptConfirm = function(message, answers = null) {
|
||||||
if (!answers) answers = [_('Y'), _('n')];
|
if (!answers) answers = [_('Y'), _('n')];
|
||||||
@@ -164,23 +162,24 @@ cliUtils.promptConfirm = function(message, answers = null) {
|
|||||||
|
|
||||||
const rl = readline.createInterface({
|
const rl = readline.createInterface({
|
||||||
input: process.stdin,
|
input: process.stdin,
|
||||||
output: process.stdout
|
output: process.stdout,
|
||||||
});
|
});
|
||||||
|
|
||||||
message += ' (' + answers.join('/') + ')';
|
message += ` (${answers.join('/')})`;
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve) => {
|
||||||
rl.question(message + ' ', (answer) => {
|
rl.question(`${message} `, answer => {
|
||||||
const ok = !answer || answer.toLowerCase() == answers[0].toLowerCase();
|
const ok = !answer || answer.toLowerCase() == answers[0].toLowerCase();
|
||||||
rl.close();
|
rl.close();
|
||||||
resolve(ok);
|
resolve(ok);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
// Note: initialText is there to have the same signature as statusBar.prompt() so that
|
// 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
|
// it can be a drop-in replacement, however initialText is not used (and cannot be
|
||||||
// with readline.question?).
|
// with readline.question?).
|
||||||
|
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
||||||
cliUtils.prompt = function(initialText = '', promptString = ':', options = null) {
|
cliUtils.prompt = function(initialText = '', promptString = ':', options = null) {
|
||||||
if (!options) options = {};
|
if (!options) options = {};
|
||||||
|
|
||||||
@@ -189,10 +188,9 @@ cliUtils.prompt = function(initialText = '', promptString = ':', options = null)
|
|||||||
|
|
||||||
const mutableStdout = new Writable({
|
const mutableStdout = new Writable({
|
||||||
write: function(chunk, encoding, callback) {
|
write: function(chunk, encoding, callback) {
|
||||||
if (!this.muted)
|
if (!this.muted) process.stdout.write(chunk, encoding);
|
||||||
process.stdout.write(chunk, encoding);
|
|
||||||
callback();
|
callback();
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const rl = readline.createInterface({
|
const rl = readline.createInterface({
|
||||||
@@ -201,18 +199,18 @@ cliUtils.prompt = function(initialText = '', promptString = ':', options = null)
|
|||||||
terminal: true,
|
terminal: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve) => {
|
||||||
mutableStdout.muted = false;
|
mutableStdout.muted = false;
|
||||||
|
|
||||||
rl.question(promptString, (answer) => {
|
rl.question(promptString, answer => {
|
||||||
rl.close();
|
rl.close();
|
||||||
if (!!options.secure) this.stdout_('');
|
if (options.secure) this.stdout_('');
|
||||||
resolve(answer);
|
resolve(answer);
|
||||||
});
|
});
|
||||||
|
|
||||||
mutableStdout.muted = !!options.secure;
|
mutableStdout.muted = !!options.secure;
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
let redrawStarted_ = false;
|
let redrawStarted_ = false;
|
||||||
let redrawLastLog_ = null;
|
let redrawLastLog_ = null;
|
||||||
@@ -220,7 +218,7 @@ let redrawLastUpdateTime_ = 0;
|
|||||||
|
|
||||||
cliUtils.setStdout = function(v) {
|
cliUtils.setStdout = function(v) {
|
||||||
this.stdout_ = v;
|
this.stdout_ = v;
|
||||||
}
|
};
|
||||||
|
|
||||||
cliUtils.redraw = function(s) {
|
cliUtils.redraw = function(s) {
|
||||||
const now = time.unixMs();
|
const now = time.unixMs();
|
||||||
@@ -233,8 +231,8 @@ cliUtils.redraw = function(s) {
|
|||||||
redrawLastLog_ = s;
|
redrawLastLog_ = s;
|
||||||
}
|
}
|
||||||
|
|
||||||
redrawStarted_ = true;
|
redrawStarted_ = true;
|
||||||
}
|
};
|
||||||
|
|
||||||
cliUtils.redrawDone = function() {
|
cliUtils.redrawDone = function() {
|
||||||
if (!redrawStarted_) return;
|
if (!redrawStarted_) return;
|
||||||
@@ -245,6 +243,6 @@ cliUtils.redrawDone = function() {
|
|||||||
|
|
||||||
redrawLastLog_ = null;
|
redrawLastLog_ = null;
|
||||||
redrawStarted_ = false;
|
redrawStarted_ = false;
|
||||||
}
|
};
|
||||||
|
|
||||||
module.exports = { cliUtils };
|
module.exports = { cliUtils };
|
||||||
|
@@ -1,19 +1,12 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
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 BaseItem = require('lib/models/BaseItem');
|
||||||
const BaseModel = require('lib/BaseModel');
|
const BaseModel = require('lib/BaseModel');
|
||||||
const Setting = require('lib/models/Setting.js');
|
|
||||||
const { toTitleCase } = require('lib/string-utils.js');
|
const { toTitleCase } = require('lib/string-utils.js');
|
||||||
const { reg } = require('lib/registry.js');
|
const { reg } = require('lib/registry.js');
|
||||||
const markdownUtils = require('lib/markdownUtils');
|
const markdownUtils = require('lib/markdownUtils');
|
||||||
const { Database } = require('lib/database.js');
|
const { Database } = require('lib/database.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'apidoc';
|
return 'apidoc';
|
||||||
}
|
}
|
||||||
@@ -23,18 +16,22 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createPropertiesTable(tableFields) {
|
createPropertiesTable(tableFields) {
|
||||||
const headers = [
|
const headers = [
|
||||||
{ name: 'name', label: 'Name' },
|
{ name: 'name', label: 'Name' },
|
||||||
{ name: 'type', label: 'Type', filter: (value) => {
|
{
|
||||||
return Database.enumName('fieldType', value);
|
name: 'type',
|
||||||
}},
|
label: 'Type',
|
||||||
{ name: 'description', label: 'Description' },
|
filter: value => {
|
||||||
];
|
return Database.enumName('fieldType', value);
|
||||||
|
},
|
||||||
return markdownUtils.createMarkdownTable(headers, tableFields);
|
},
|
||||||
|
{ name: 'description', label: 'Description' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return markdownUtils.createMarkdownTable(headers, tableFields);
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action() {
|
||||||
const models = [
|
const models = [
|
||||||
{
|
{
|
||||||
type: BaseModel.TYPE_NOTE,
|
type: BaseModel.TYPE_NOTE,
|
||||||
@@ -70,8 +67,8 @@ class Command extends BaseCommand {
|
|||||||
lines.push('}');
|
lines.push('}');
|
||||||
lines.push('```');
|
lines.push('```');
|
||||||
lines.push('');
|
lines.push('');
|
||||||
|
|
||||||
lines.push('# Authorisation')
|
lines.push('# Authorisation');
|
||||||
lines.push('');
|
lines.push('');
|
||||||
lines.push('To prevent unauthorised applications from accessing the API, the calls must be authentified. To do so, you must provide a token as a query parameter for each API call. You can get this token from the Joplin desktop application, on the Web Clipper Options screen.');
|
lines.push('To prevent unauthorised applications from accessing the API, the calls must be authentified. To do so, you must provide a token as a query parameter for each API call. You can get this token from the Joplin desktop application, on the Web Clipper Options screen.');
|
||||||
lines.push('');
|
lines.push('');
|
||||||
@@ -171,7 +168,7 @@ class Command extends BaseCommand {
|
|||||||
// });
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
lines.push('# ' + toTitleCase(tableName));
|
lines.push(`# ${toTitleCase(tableName)}`);
|
||||||
lines.push('');
|
lines.push('');
|
||||||
|
|
||||||
if (model.type === BaseModel.TYPE_FOLDER) {
|
if (model.type === BaseModel.TYPE_FOLDER) {
|
||||||
@@ -184,9 +181,9 @@ class Command extends BaseCommand {
|
|||||||
lines.push(this.createPropertiesTable(tableFields));
|
lines.push(this.createPropertiesTable(tableFields));
|
||||||
lines.push('');
|
lines.push('');
|
||||||
|
|
||||||
lines.push('## GET /' + tableName);
|
lines.push(`## GET /${tableName}`);
|
||||||
lines.push('');
|
lines.push('');
|
||||||
lines.push('Gets all ' + tableName);
|
lines.push(`Gets all ${tableName}`);
|
||||||
lines.push('');
|
lines.push('');
|
||||||
|
|
||||||
if (model.type === BaseModel.TYPE_FOLDER) {
|
if (model.type === BaseModel.TYPE_FOLDER) {
|
||||||
@@ -194,9 +191,9 @@ class Command extends BaseCommand {
|
|||||||
lines.push('');
|
lines.push('');
|
||||||
}
|
}
|
||||||
|
|
||||||
lines.push('## GET /' + tableName + '/:id');
|
lines.push(`## GET /${tableName}/:id`);
|
||||||
lines.push('');
|
lines.push('');
|
||||||
lines.push('Gets ' + singular + ' with ID :id');
|
lines.push(`Gets ${singular} with ID :id`);
|
||||||
lines.push('');
|
lines.push('');
|
||||||
|
|
||||||
if (model.type === BaseModel.TYPE_TAG) {
|
if (model.type === BaseModel.TYPE_TAG) {
|
||||||
@@ -211,6 +208,11 @@ class Command extends BaseCommand {
|
|||||||
lines.push('');
|
lines.push('');
|
||||||
lines.push('Gets all the tags attached to this note.');
|
lines.push('Gets all the tags attached to this note.');
|
||||||
lines.push('');
|
lines.push('');
|
||||||
|
|
||||||
|
lines.push('## GET /notes/:id/resources');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('Gets all the resources attached to this note.');
|
||||||
|
lines.push('');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (model.type === BaseModel.TYPE_FOLDER) {
|
if (model.type === BaseModel.TYPE_FOLDER) {
|
||||||
@@ -227,9 +229,9 @@ class Command extends BaseCommand {
|
|||||||
lines.push('');
|
lines.push('');
|
||||||
}
|
}
|
||||||
|
|
||||||
lines.push('## POST /' + tableName);
|
lines.push(`## POST /${tableName}`);
|
||||||
lines.push('');
|
lines.push('');
|
||||||
lines.push('Creates a new ' + singular);
|
lines.push(`Creates a new ${singular}`);
|
||||||
lines.push('');
|
lines.push('');
|
||||||
|
|
||||||
if (model.type === BaseModel.TYPE_RESOURCE) {
|
if (model.type === BaseModel.TYPE_RESOURCE) {
|
||||||
@@ -273,14 +275,14 @@ class Command extends BaseCommand {
|
|||||||
lines.push('');
|
lines.push('');
|
||||||
}
|
}
|
||||||
|
|
||||||
lines.push('## PUT /' + tableName + '/:id');
|
lines.push(`## PUT /${tableName}/:id`);
|
||||||
lines.push('');
|
lines.push('');
|
||||||
lines.push('Sets the properties of the ' + singular + ' with ID :id');
|
lines.push(`Sets the properties of the ${singular} with ID :id`);
|
||||||
lines.push('');
|
lines.push('');
|
||||||
|
|
||||||
lines.push('## DELETE /' + tableName + '/:id');
|
lines.push(`## DELETE /${tableName}/:id`);
|
||||||
lines.push('');
|
lines.push('');
|
||||||
lines.push('Deletes the ' + singular + ' with ID :id');
|
lines.push(`Deletes the ${singular} with ID :id`);
|
||||||
lines.push('');
|
lines.push('');
|
||||||
|
|
||||||
if (model.type === BaseModel.TYPE_TAG) {
|
if (model.type === BaseModel.TYPE_TAG) {
|
||||||
@@ -293,7 +295,6 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
this.stdout(lines.join('\n'));
|
this.stdout(lines.join('\n'));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -3,10 +3,8 @@ const { app } = require('./app.js');
|
|||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const { shim } = require('lib/shim.js');
|
const { shim } = require('lib/shim.js');
|
||||||
const fs = require('fs-extra');
|
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'attach <note> <file>';
|
return 'attach <note> <file>';
|
||||||
}
|
}
|
||||||
@@ -26,7 +24,6 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
await shim.attachFileToNote(note, localFilePath);
|
await shim.attachFileToNote(note, localFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -2,11 +2,9 @@ const { BaseCommand } = require('./base-command.js');
|
|||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const Folder = require('lib/models/Folder.js');
|
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'cat <note>';
|
return 'cat <note>';
|
||||||
}
|
}
|
||||||
@@ -16,9 +14,7 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
options() {
|
options() {
|
||||||
return [
|
return [['-v, --verbose', _('Displays the complete information about note.')]];
|
||||||
['-v, --verbose', _('Displays the complete information about note.')],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
@@ -30,10 +26,13 @@ class Command extends BaseCommand {
|
|||||||
const content = args.options.verbose ? await Note.serialize(item) : await Note.serializeForEdit(item);
|
const content = args.options.verbose ? await Note.serialize(item) : await Note.serializeForEdit(item);
|
||||||
this.stdout(content);
|
this.stdout(content);
|
||||||
|
|
||||||
app().gui().showConsole();
|
app()
|
||||||
app().gui().maximizeConsole();
|
.gui()
|
||||||
|
.showConsole();
|
||||||
|
app()
|
||||||
|
.gui()
|
||||||
|
.maximizeConsole();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -4,25 +4,22 @@ const { app } = require('./app.js');
|
|||||||
const Setting = require('lib/models/Setting.js');
|
const Setting = require('lib/models/Setting.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'config [name] [value]';
|
return 'config [name] [value]';
|
||||||
}
|
}
|
||||||
|
|
||||||
description() {
|
description() {
|
||||||
return _("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.");
|
return _('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.');
|
||||||
}
|
}
|
||||||
|
|
||||||
options() {
|
options() {
|
||||||
return [
|
return [['-v, --verbose', _('Also displays unset and hidden config variables.')]];
|
||||||
['-v, --verbose', _('Also displays unset and hidden config variables.')],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
const verbose = args.options.verbose;
|
const verbose = args.options.verbose;
|
||||||
|
|
||||||
const renderKeyValue = (name) => {
|
const renderKeyValue = name => {
|
||||||
const md = Setting.settingMetadata(name);
|
const md = Setting.settingMetadata(name);
|
||||||
let value = Setting.value(name);
|
let value = Setting.value(name);
|
||||||
if (typeof value === 'object' || Array.isArray(value)) value = JSON.stringify(value);
|
if (typeof value === 'object' || Array.isArray(value)) value = JSON.stringify(value);
|
||||||
@@ -33,7 +30,7 @@ class Command extends BaseCommand {
|
|||||||
} else {
|
} else {
|
||||||
return _('%s = %s', name, value);
|
return _('%s = %s', name, value);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (!args.name && !args.value) {
|
if (!args.name && !args.value) {
|
||||||
let keys = Setting.keys(!verbose, 'cli');
|
let keys = Setting.keys(!verbose, 'cli');
|
||||||
@@ -43,15 +40,23 @@ class Command extends BaseCommand {
|
|||||||
if (!verbose && !value) continue;
|
if (!verbose && !value) continue;
|
||||||
this.stdout(renderKeyValue(keys[i]));
|
this.stdout(renderKeyValue(keys[i]));
|
||||||
}
|
}
|
||||||
app().gui().showConsole();
|
app()
|
||||||
app().gui().maximizeConsole();
|
.gui()
|
||||||
|
.showConsole();
|
||||||
|
app()
|
||||||
|
.gui()
|
||||||
|
.maximizeConsole();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.name && !args.value) {
|
if (args.name && !args.value) {
|
||||||
this.stdout(renderKeyValue(args.name));
|
this.stdout(renderKeyValue(args.name));
|
||||||
app().gui().showConsole();
|
app()
|
||||||
app().gui().maximizeConsole();
|
.gui()
|
||||||
|
.showConsole();
|
||||||
|
app()
|
||||||
|
.gui()
|
||||||
|
.maximizeConsole();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +69,6 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
await Setting.saveAll();
|
await Setting.saveAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -2,11 +2,9 @@ const { BaseCommand } = require('./base-command.js');
|
|||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const Folder = require('lib/models/Folder.js');
|
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'cp <note> [notebook]';
|
return 'cp <note> [notebook]';
|
||||||
}
|
}
|
||||||
@@ -33,7 +31,6 @@ class Command extends BaseCommand {
|
|||||||
Note.updateGeolocation(newNote.id);
|
Note.updateGeolocation(newNote.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -2,12 +2,10 @@ const { BaseCommand } = require('./base-command.js');
|
|||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const Folder = require('lib/models/Folder.js');
|
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const { time } = require('lib/time-utils.js');
|
const { time } = require('lib/time-utils.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'done <note>';
|
return 'done <note>';
|
||||||
}
|
}
|
||||||
@@ -35,7 +33,6 @@ class Command extends BaseCommand {
|
|||||||
async action(args) {
|
async action(args) {
|
||||||
await Command.handleAction(this, args, true);
|
await Command.handleAction(this, args, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -1,12 +1,9 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
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 Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const Tag = require('lib/models/Tag.js');
|
const Tag = require('lib/models/Tag.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'dump';
|
return 'dump';
|
||||||
}
|
}
|
||||||
@@ -19,7 +16,7 @@ class Command extends BaseCommand {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action() {
|
||||||
let items = [];
|
let items = [];
|
||||||
let folders = await Folder.all();
|
let folders = await Folder.all();
|
||||||
for (let i = 0; i < folders.length; i++) {
|
for (let i = 0; i < folders.length; i++) {
|
||||||
@@ -35,10 +32,9 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
items = items.concat(tags);
|
items = items.concat(tags);
|
||||||
|
|
||||||
this.stdout(JSON.stringify(items));
|
this.stdout(JSON.stringify(items));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -1,9 +1,7 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const { cliUtils } = require('./cli-utils.js');
|
|
||||||
const EncryptionService = require('lib/services/EncryptionService');
|
const EncryptionService = require('lib/services/EncryptionService');
|
||||||
const DecryptionWorker = require('lib/services/DecryptionWorker');
|
const DecryptionWorker = require('lib/services/DecryptionWorker');
|
||||||
const MasterKey = require('lib/models/MasterKey');
|
|
||||||
const BaseItem = require('lib/models/BaseItem');
|
const BaseItem = require('lib/models/BaseItem');
|
||||||
const Setting = require('lib/models/Setting.js');
|
const Setting = require('lib/models/Setting.js');
|
||||||
const { shim } = require('lib/shim');
|
const { shim } = require('lib/shim');
|
||||||
@@ -12,7 +10,6 @@ const imageType = require('image-type');
|
|||||||
const readChunk = require('read-chunk');
|
const readChunk = require('read-chunk');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'e2ee <command> [path]';
|
return 'e2ee <command> [path]';
|
||||||
}
|
}
|
||||||
@@ -35,7 +32,7 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
const options = args.options;
|
const options = args.options;
|
||||||
|
|
||||||
const askForMasterKey = async (error) => {
|
const askForMasterKey = async error => {
|
||||||
const masterKeyId = error.masterKeyId;
|
const masterKeyId = error.masterKeyId;
|
||||||
const password = await this.prompt(_('Enter master password:'), { type: 'string', secure: true });
|
const password = await this.prompt(_('Enter master password:'), { type: 'string', secure: true });
|
||||||
if (!password) {
|
if (!password) {
|
||||||
@@ -45,7 +42,7 @@ class Command extends BaseCommand {
|
|||||||
Setting.setObjectKey('encryption.passwordCache', masterKeyId, password);
|
Setting.setObjectKey('encryption.passwordCache', masterKeyId, password);
|
||||||
await EncryptionService.instance().loadMasterKeysFromSettings();
|
await EncryptionService.instance().loadMasterKeysFromSettings();
|
||||||
return true;
|
return true;
|
||||||
}
|
};
|
||||||
|
|
||||||
if (args.command === 'enable') {
|
if (args.command === 'enable') {
|
||||||
const password = options.password ? options.password.toString() : await this.prompt(_('Enter master password:'), { type: 'string', secure: true });
|
const password = options.password ? options.password.toString() : await this.prompt(_('Enter master password:'), { type: 'string', secure: true });
|
||||||
@@ -53,7 +50,15 @@ class Command extends BaseCommand {
|
|||||||
this.stdout(_('Operation cancelled'));
|
this.stdout(_('Operation cancelled'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const password2 = await this.prompt(_('Confirm password:'), { type: 'string', secure: true });
|
||||||
|
if (!password2) {
|
||||||
|
this.stdout(_('Operation cancelled'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (password !== password2) {
|
||||||
|
this.stdout(_('Passwords do not match!'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
await EncryptionService.instance().generateMasterKeyAndEnableEncryption(password);
|
await EncryptionService.instance().generateMasterKeyAndEnableEncryption(password);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -100,13 +105,13 @@ class Command extends BaseCommand {
|
|||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
const outputDir = options.output ? options.output : require('os').tmpdir();
|
const outputDir = options.output ? options.output : require('os').tmpdir();
|
||||||
let outFile = outputDir + '/' + pathUtils.filename(args.path) + '.' + Date.now() + '.bin';
|
let outFile = `${outputDir}/${pathUtils.filename(args.path)}.${Date.now()}.bin`;
|
||||||
await EncryptionService.instance().decryptFile(args.path, outFile);
|
await EncryptionService.instance().decryptFile(args.path, outFile);
|
||||||
const buffer = await readChunk(outFile, 0, 64);
|
const buffer = await readChunk(outFile, 0, 64);
|
||||||
const detectedType = imageType(buffer);
|
const detectedType = imageType(buffer);
|
||||||
|
|
||||||
if (detectedType) {
|
if (detectedType) {
|
||||||
const newOutFile = outFile + '.' + detectedType.ext;
|
const newOutFile = `${outFile}.${detectedType.ext}`;
|
||||||
await shim.fsDriver().move(outFile, newOutFile);
|
await shim.fsDriver().move(outFile, newOutFile);
|
||||||
outFile = newOutFile;
|
outFile = newOutFile;
|
||||||
}
|
}
|
||||||
@@ -128,19 +133,17 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
if (args.command === 'target-status') {
|
if (args.command === 'target-status') {
|
||||||
const fs = require('fs-extra');
|
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;
|
const targetPath = args.path;
|
||||||
if (!targetPath) throw new Error('Please specify the sync target path.');
|
if (!targetPath) throw new Error('Please specify the sync target path.');
|
||||||
|
|
||||||
const dirPaths = function(targetPath) {
|
const dirPaths = function(targetPath) {
|
||||||
let paths = [];
|
let paths = [];
|
||||||
fs.readdirSync(targetPath).forEach((path) => {
|
fs.readdirSync(targetPath).forEach(path => {
|
||||||
paths.push(path);
|
paths.push(path);
|
||||||
});
|
});
|
||||||
return paths;
|
return paths;
|
||||||
}
|
};
|
||||||
|
|
||||||
let itemCount = 0;
|
let itemCount = 0;
|
||||||
let resourceCount = 0;
|
let resourceCount = 0;
|
||||||
@@ -155,7 +158,7 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
for (let i = 0; i < paths.length; i++) {
|
for (let i = 0; i < paths.length; i++) {
|
||||||
const path = paths[i];
|
const path = paths[i];
|
||||||
const fullPath = targetPath + '/' + path;
|
const fullPath = `${targetPath}/${path}`;
|
||||||
const stat = await fs.stat(fullPath);
|
const stat = await fs.stat(fullPath);
|
||||||
|
|
||||||
// this.stdout(fullPath);
|
// this.stdout(fullPath);
|
||||||
@@ -165,7 +168,7 @@ class Command extends BaseCommand {
|
|||||||
for (let j = 0; j < resourcePaths.length; j++) {
|
for (let j = 0; j < resourcePaths.length; j++) {
|
||||||
const resourcePath = resourcePaths[j];
|
const resourcePath = resourcePaths[j];
|
||||||
resourceCount++;
|
resourceCount++;
|
||||||
const fullResourcePath = fullPath + '/' + resourcePath;
|
const fullResourcePath = `${fullPath}/${resourcePath}`;
|
||||||
const isEncrypted = await EncryptionService.instance().fileIsEncrypted(fullResourcePath);
|
const isEncrypted = await EncryptionService.instance().fileIsEncrypted(fullResourcePath);
|
||||||
if (isEncrypted) {
|
if (isEncrypted) {
|
||||||
encryptedResourceCount++;
|
encryptedResourceCount++;
|
||||||
@@ -199,9 +202,9 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stdout('Encrypted items: ' + encryptedItemCount + '/' + itemCount);
|
this.stdout(`Encrypted items: ${encryptedItemCount}/${itemCount}`);
|
||||||
this.stdout('Encrypted resources: ' + encryptedResourceCount + '/' + resourceCount);
|
this.stdout(`Encrypted resources: ${encryptedResourceCount}/${resourceCount}`);
|
||||||
this.stdout('Other items (never encrypted): ' + otherItemCount);
|
this.stdout(`Other items (never encrypted): ${otherItemCount}`);
|
||||||
|
|
||||||
if (options.verbose) {
|
if (options.verbose) {
|
||||||
this.stdout('');
|
this.stdout('');
|
||||||
@@ -224,7 +227,6 @@ class Command extends BaseCommand {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -1,17 +1,14 @@
|
|||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
|
const { splitCommandString } = require('lib/string-utils.js');
|
||||||
const { uuid } = require('lib/uuid.js');
|
const { uuid } = require('lib/uuid.js');
|
||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const Folder = require('lib/models/Folder.js');
|
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const Setting = require('lib/models/Setting.js');
|
const Setting = require('lib/models/Setting.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const { cliUtils } = require('./cli-utils.js');
|
|
||||||
const { time } = require('lib/time-utils.js');
|
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'edit <note>';
|
return 'edit <note>';
|
||||||
}
|
}
|
||||||
@@ -21,20 +18,19 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
let watcher = null;
|
|
||||||
let tempFilePath = null;
|
let tempFilePath = null;
|
||||||
|
|
||||||
const onFinishedEditing = async () => {
|
const onFinishedEditing = async () => {
|
||||||
if (tempFilePath) fs.removeSync(tempFilePath);
|
if (tempFilePath) fs.removeSync(tempFilePath);
|
||||||
}
|
};
|
||||||
|
|
||||||
const textEditorPath = () => {
|
const textEditorPath = () => {
|
||||||
if (Setting.value('editor')) return Setting.value('editor');
|
if (Setting.value('editor')) return Setting.value('editor');
|
||||||
if (process.env.EDITOR) return process.env.EDITOR;
|
if (process.env.EDITOR) return process.env.EDITOR;
|
||||||
throw new Error(_('No text editor is defined. Please set it using `config editor <editor-path>`'));
|
throw new Error(_('No text editor is defined. Please set it using `config editor <editor-path>`'));
|
||||||
}
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Load note or create it if it doesn't exist
|
// Load note or create it if it doesn't exist
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@@ -58,14 +54,14 @@ class Command extends BaseCommand {
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
let editorPath = textEditorPath();
|
let editorPath = textEditorPath();
|
||||||
let editorArgs = editorPath.split(' ');
|
let editorArgs = splitCommandString(editorPath);
|
||||||
|
|
||||||
editorPath = editorArgs[0];
|
editorPath = editorArgs[0];
|
||||||
editorArgs = editorArgs.splice(1);
|
editorArgs = editorArgs.splice(1);
|
||||||
|
|
||||||
const originalContent = await Note.serializeForEdit(note);
|
const originalContent = await Note.serializeForEdit(note);
|
||||||
|
|
||||||
tempFilePath = Setting.value('tempDir') + '/' + uuid.create() + '.md';
|
tempFilePath = `${Setting.value('tempDir')}/${uuid.create()}.md`;
|
||||||
editorArgs.push(tempFilePath);
|
editorArgs.push(tempFilePath);
|
||||||
|
|
||||||
await fs.writeFile(tempFilePath, originalContent);
|
await fs.writeFile(tempFilePath, originalContent);
|
||||||
@@ -80,7 +76,7 @@ class Command extends BaseCommand {
|
|||||||
await app().gui().forceRender();
|
await app().gui().forceRender();
|
||||||
const termState = app().gui().termSaveState();
|
const termState = app().gui().termSaveState();
|
||||||
|
|
||||||
const spawnSync = require('child_process').spawnSync;
|
const spawnSync = require('child_process').spawnSync;
|
||||||
const result = 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));
|
if (result.error) this.stdout(_('Error opening note in editor: %s', result.error.message));
|
||||||
@@ -107,13 +103,11 @@ class Command extends BaseCommand {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await onFinishedEditing();
|
await onFinishedEditing();
|
||||||
|
} catch (error) {
|
||||||
} catch(error) {
|
|
||||||
await onFinishedEditing();
|
await onFinishedEditing();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -3,7 +3,6 @@ const { app } = require('./app.js');
|
|||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'exit';
|
return 'exit';
|
||||||
}
|
}
|
||||||
@@ -16,10 +15,9 @@ class Command extends BaseCommand {
|
|||||||
return ['gui'];
|
return ['gui'];
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action() {
|
||||||
await app().exit();
|
await app().exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -1,13 +1,10 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { Database } = require('lib/database.js');
|
|
||||||
const { app } = require('./app.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');
|
const { ReportService } = require('lib/services/report.js');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'export-sync-status';
|
return 'export-sync-status';
|
||||||
}
|
}
|
||||||
@@ -20,17 +17,20 @@ class Command extends BaseCommand {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action() {
|
||||||
const service = new ReportService();
|
const service = new ReportService();
|
||||||
const csv = await service.basicItemList({ format: 'csv' });
|
const csv = await service.basicItemList({ format: 'csv' });
|
||||||
const filePath = Setting.value('profileDir') + '/syncReport-' + (new Date()).getTime() + '.csv';
|
const filePath = `${Setting.value('profileDir')}/syncReport-${new Date().getTime()}.csv`;
|
||||||
await fs.writeFileSync(filePath, csv);
|
await fs.writeFileSync(filePath, csv);
|
||||||
this.stdout('Sync status exported to ' + filePath);
|
this.stdout(`Sync status exported to ${filePath}`);
|
||||||
|
|
||||||
app().gui().showConsole();
|
app()
|
||||||
app().gui().maximizeConsole();
|
.gui()
|
||||||
|
.showConsole();
|
||||||
|
app()
|
||||||
|
.gui()
|
||||||
|
.maximizeConsole();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -1,14 +1,10 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const InteropService = require('lib/services/InteropService.js');
|
const InteropService = require('lib/services/InteropService.js');
|
||||||
const BaseModel = require('lib/BaseModel.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 { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const fs = require('fs-extra');
|
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'export <path>';
|
return 'export <path>';
|
||||||
}
|
}
|
||||||
@@ -19,17 +15,14 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
options() {
|
options() {
|
||||||
const service = new InteropService();
|
const service = new InteropService();
|
||||||
const formats = service.modules()
|
const formats = service
|
||||||
|
.modules()
|
||||||
.filter(m => m.type === 'exporter')
|
.filter(m => m.type === 'exporter')
|
||||||
.map(m => m.format + (m.description ? ' (' + m.description + ')' : ''));
|
.map(m => m.format + (m.description ? ` (${m.description})` : ''));
|
||||||
|
|
||||||
return [
|
return [['--format <format>', _('Destination format: %s', formats.join(', '))], ['--note <note>', _('Exports only the given note.')], ['--notebook <notebook>', _('Exports only the given notebook.')]];
|
||||||
['--format <format>', _('Destination format: %s', formats.join(', '))],
|
|
||||||
['--note <note>', _('Exports only the given note.')],
|
|
||||||
['--notebook <notebook>', _('Exports only the given notebook.')],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
let exportOptions = {};
|
let exportOptions = {};
|
||||||
exportOptions.path = args.path;
|
exportOptions.path = args.path;
|
||||||
@@ -37,25 +30,20 @@ class Command extends BaseCommand {
|
|||||||
exportOptions.format = args.options.format ? args.options.format : 'jex';
|
exportOptions.format = args.options.format ? args.options.format : 'jex';
|
||||||
|
|
||||||
if (args.options.note) {
|
if (args.options.note) {
|
||||||
|
|
||||||
const notes = await app().loadItems(BaseModel.TYPE_NOTE, args.options.note, { parent: app().currentFolder() });
|
const notes = await app().loadItems(BaseModel.TYPE_NOTE, args.options.note, { parent: app().currentFolder() });
|
||||||
if (!notes.length) throw new Error(_('Cannot find "%s".', args.options.note));
|
if (!notes.length) throw new Error(_('Cannot find "%s".', args.options.note));
|
||||||
exportOptions.sourceNoteIds = notes.map((n) => n.id);
|
exportOptions.sourceNoteIds = notes.map(n => n.id);
|
||||||
|
|
||||||
} else if (args.options.notebook) {
|
} else if (args.options.notebook) {
|
||||||
|
|
||||||
const folders = await app().loadItems(BaseModel.TYPE_FOLDER, args.options.notebook);
|
const folders = await app().loadItems(BaseModel.TYPE_FOLDER, args.options.notebook);
|
||||||
if (!folders.length) throw new Error(_('Cannot find "%s".', args.options.notebook));
|
if (!folders.length) throw new Error(_('Cannot find "%s".', args.options.notebook));
|
||||||
exportOptions.sourceFolderIds = folders.map((n) => n.id);
|
exportOptions.sourceFolderIds = folders.map(n => n.id);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const service = new InteropService();
|
const service = new InteropService();
|
||||||
const result = await service.export(exportOptions);
|
const result = await service.export(exportOptions);
|
||||||
|
|
||||||
result.warnings.map((w) => this.stdout(w));
|
result.warnings.map(w => this.stdout(w));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -2,11 +2,9 @@ const { BaseCommand } = require('./base-command.js');
|
|||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const Folder = require('lib/models/Folder.js');
|
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'geoloc <note>';
|
return 'geoloc <note>';
|
||||||
}
|
}
|
||||||
@@ -23,9 +21,10 @@ class Command extends BaseCommand {
|
|||||||
const url = Note.geolocationUrl(item);
|
const url = Note.geolocationUrl(item);
|
||||||
this.stdout(url);
|
this.stdout(url);
|
||||||
|
|
||||||
app().gui().showConsole();
|
app()
|
||||||
|
.gui()
|
||||||
|
.showConsole();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -1,14 +1,10 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { renderCommandHelp } = require('./help-utils.js');
|
const { renderCommandHelp } = require('./help-utils.js');
|
||||||
const { Database } = require('lib/database.js');
|
|
||||||
const Setting = require('lib/models/Setting.js');
|
|
||||||
const { wrap } = require('lib/string-utils.js');
|
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const { cliUtils } = require('./cli-utils.js');
|
const { cliUtils } = require('./cli-utils.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'help [command]';
|
return 'help [command]';
|
||||||
}
|
}
|
||||||
@@ -28,7 +24,7 @@ class Command extends BaseCommand {
|
|||||||
output.push(command);
|
output.push(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
output.sort((a, b) => a.name() < b.name() ? -1 : +1);
|
output.sort((a, b) => (a.name() < b.name() ? -1 : +1));
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
@@ -40,31 +36,37 @@ class Command extends BaseCommand {
|
|||||||
this.stdout(_('For information on how to customise the shortcuts please visit %s', 'https://joplinapp.org/terminal/#shortcuts'));
|
this.stdout(_('For information on how to customise the shortcuts please visit %s', 'https://joplinapp.org/terminal/#shortcuts'));
|
||||||
this.stdout('');
|
this.stdout('');
|
||||||
|
|
||||||
if (app().gui().isDummy()) {
|
if (
|
||||||
|
app()
|
||||||
|
.gui()
|
||||||
|
.isDummy()
|
||||||
|
) {
|
||||||
throw new Error(_('Shortcuts are not available in CLI mode.'));
|
throw new Error(_('Shortcuts are not available in CLI mode.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const keymap = app().gui().keymap();
|
const keymap = app()
|
||||||
|
.gui()
|
||||||
|
.keymap();
|
||||||
|
|
||||||
let rows = [];
|
let rows = [];
|
||||||
|
|
||||||
for (let i = 0; i < keymap.length; i++) {
|
for (let i = 0; i < keymap.length; i++) {
|
||||||
const item = keymap[i];
|
const item = keymap[i];
|
||||||
const keys = item.keys.map((k) => k === ' ' ? '(SPACE)' : k);
|
const keys = item.keys.map(k => (k === ' ' ? '(SPACE)' : k));
|
||||||
rows.push([keys.join(', '), item.command]);
|
rows.push([keys.join(', '), item.command]);
|
||||||
}
|
}
|
||||||
|
|
||||||
cliUtils.printArray(this.stdout.bind(this), rows);
|
cliUtils.printArray(this.stdout.bind(this), rows);
|
||||||
} else if (args.command === 'all') {
|
} else if (args.command === 'all') {
|
||||||
const commands = this.allCommands();
|
const commands = this.allCommands();
|
||||||
const output = commands.map((c) => renderCommandHelp(c));
|
const output = commands.map(c => renderCommandHelp(c));
|
||||||
this.stdout(output.join('\n\n'));
|
this.stdout(output.join('\n\n'));
|
||||||
} else if (args.command) {
|
} else if (args.command) {
|
||||||
const command = app().findCommandByName(args['command']);
|
const command = app().findCommandByName(args['command']);
|
||||||
if (!command) throw new Error(_('Cannot find "%s".', args.command));
|
if (!command) throw new Error(_('Cannot find "%s".', args.command));
|
||||||
this.stdout(renderCommandHelp(command, stdoutWidth));
|
this.stdout(renderCommandHelp(command, stdoutWidth));
|
||||||
} else {
|
} else {
|
||||||
const commandNames = this.allCommands().map((a) => a.name());
|
const commandNames = this.allCommands().map(a => a.name());
|
||||||
|
|
||||||
this.stdout(_('Type `help [command]` for more information about a command; or type `help all` for the complete usage information.'));
|
this.stdout(_('Type `help [command]` for more information about a command; or type `help all` for the complete usage information.'));
|
||||||
this.stdout('');
|
this.stdout('');
|
||||||
@@ -82,10 +84,13 @@ class Command extends BaseCommand {
|
|||||||
this.stdout(_('For the list of keyboard shortcuts and config options, type `help keymap`'));
|
this.stdout(_('For the list of keyboard shortcuts and config options, type `help keymap`'));
|
||||||
}
|
}
|
||||||
|
|
||||||
app().gui().showConsole();
|
app()
|
||||||
app().gui().maximizeConsole();
|
.gui()
|
||||||
|
.showConsole();
|
||||||
|
app()
|
||||||
|
.gui()
|
||||||
|
.maximizeConsole();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -1,17 +1,11 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const InteropService = require('lib/services/InteropService.js');
|
const InteropService = require('lib/services/InteropService.js');
|
||||||
const BaseModel = require('lib/BaseModel.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 { cliUtils } = require('./cli-utils.js');
|
||||||
const { reg } = require('lib/registry.js');
|
|
||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const fs = require('fs-extra');
|
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'import <path> [notebook]';
|
return 'import <path> [notebook]';
|
||||||
}
|
}
|
||||||
@@ -22,14 +16,14 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
options() {
|
options() {
|
||||||
const service = new InteropService();
|
const service = new InteropService();
|
||||||
const formats = service.modules().filter(m => m.type === 'importer').map(m => m.format);
|
const formats = service
|
||||||
|
.modules()
|
||||||
|
.filter(m => m.type === 'importer')
|
||||||
|
.map(m => m.format);
|
||||||
|
|
||||||
return [
|
return [['--format <format>', _('Source format: %s', ['auto'].concat(formats).join(', '))], ['-f, --force', _('Do not ask for confirmation.')]];
|
||||||
['--format <format>', _('Source format: %s', (['auto'].concat(formats)).join(', '))],
|
|
||||||
['-f, --force', _('Do not ask for confirmation.')],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
let folder = await app().loadItem(BaseModel.TYPE_FOLDER, args.notebook);
|
let folder = await app().loadItem(BaseModel.TYPE_FOLDER, args.notebook);
|
||||||
|
|
||||||
@@ -44,7 +38,7 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
// onProgress/onError supported by Enex import only
|
// onProgress/onError supported by Enex import only
|
||||||
|
|
||||||
importOptions.onProgress = (progressState) => {
|
importOptions.onProgress = progressState => {
|
||||||
let line = [];
|
let line = [];
|
||||||
line.push(_('Found: %d.', progressState.loaded));
|
line.push(_('Found: %d.', progressState.loaded));
|
||||||
line.push(_('Created: %d.', progressState.created));
|
line.push(_('Created: %d.', progressState.created));
|
||||||
@@ -56,20 +50,21 @@ class Command extends BaseCommand {
|
|||||||
cliUtils.redraw(lastProgress);
|
cliUtils.redraw(lastProgress);
|
||||||
};
|
};
|
||||||
|
|
||||||
importOptions.onError = (error) => {
|
importOptions.onError = error => {
|
||||||
let s = error.trace ? error.trace : error.toString();
|
let s = error.trace ? error.trace : error.toString();
|
||||||
this.stdout(s);
|
this.stdout(s);
|
||||||
};
|
};
|
||||||
|
|
||||||
app().gui().showConsole();
|
app()
|
||||||
|
.gui()
|
||||||
|
.showConsole();
|
||||||
this.stdout(_('Importing notes...'));
|
this.stdout(_('Importing notes...'));
|
||||||
const service = new InteropService();
|
const service = new InteropService();
|
||||||
const result = await service.import(importOptions);
|
const result = await service.import(importOptions);
|
||||||
result.warnings.map((w) => this.stdout(w));
|
result.warnings.map(w => this.stdout(w));
|
||||||
cliUtils.redrawDone();
|
cliUtils.redrawDone();
|
||||||
if (lastProgress) this.stdout(_('The notes have been imported: %s', lastProgress));
|
if (lastProgress) this.stdout(_('The notes have been imported: %s', lastProgress));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -10,7 +10,6 @@ const { time } = require('lib/time-utils.js');
|
|||||||
const { cliUtils } = require('./cli-utils.js');
|
const { cliUtils } = require('./cli-utils.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'ls [note-pattern]';
|
return 'ls [note-pattern]';
|
||||||
}
|
}
|
||||||
@@ -24,14 +23,7 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
options() {
|
options() {
|
||||||
return [
|
return [['-n, --limit <num>', _('Displays only the first top <num> notes.')], ['-s, --sort <field>', _('Sorts the item by <field> (eg. title, updated_time, created_time).')], ['-r, --reverse', _('Reverses the sorting order.')], ['-t, --type <type>', _('Displays only the items of the specific type(s). Can be `n` for notes, `t` for to-dos, or `nt` for notes and to-dos (eg. `-tt` would display only the to-dos, while `-ttd` would display notes and to-dos.')], ['-f, --format <format>', _('Either "text" or "json"')], ['-l, --long', _('Use long list format. Format is ID, NOTE_COUNT (for notebook), DATE, TODO_CHECKED (for to-dos), TITLE')]];
|
||||||
['-n, --limit <num>', _('Displays only the first top <num> notes.')],
|
|
||||||
['-s, --sort <field>', _('Sorts the item by <field> (eg. title, updated_time, created_time).')],
|
|
||||||
['-r, --reverse', _('Reverses the sorting order.')],
|
|
||||||
['-t, --type <type>', _('Displays only the items of the specific type(s). Can be `n` for notes, `t` for to-dos, or `nt` for notes and to-dos (eg. `-tt` would display only the to-dos, while `-ttd` would display notes and to-dos.')],
|
|
||||||
['-f, --format <format>', _('Either "text" or "json"')],
|
|
||||||
['-l, --long', _('Use long list format. Format is ID, NOTE_COUNT (for notebook), DATE, TODO_CHECKED (for to-dos), TITLE')],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
@@ -98,14 +90,14 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
let title = item.title;
|
let title = item.title;
|
||||||
if (!shortIdShown && (seenTitles.indexOf(item.title) >= 0 || !item.title)) {
|
if (!shortIdShown && (seenTitles.indexOf(item.title) >= 0 || !item.title)) {
|
||||||
title += ' (' + BaseModel.shortId(item.id) + ')';
|
title += ` (${BaseModel.shortId(item.id)})`;
|
||||||
} else {
|
} else {
|
||||||
seenTitles.push(item.title);
|
seenTitles.push(item.title);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasTodos) {
|
if (hasTodos) {
|
||||||
if (item.is_todo) {
|
if (item.is_todo) {
|
||||||
row.push(sprintf('[%s]', !!item.todo_completed ? 'X' : ' '));
|
row.push(sprintf('[%s]', item.todo_completed ? 'X' : ' '));
|
||||||
} else {
|
} else {
|
||||||
row.push(' ');
|
row.push(' ');
|
||||||
}
|
}
|
||||||
@@ -118,9 +110,7 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
cliUtils.printArray(this.stdout.bind(this), rows);
|
cliUtils.printArray(this.stdout.bind(this), rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -2,10 +2,8 @@ const { BaseCommand } = require('./base-command.js');
|
|||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale.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 {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'mkbook <new-notebook>';
|
return 'mkbook <new-notebook>';
|
||||||
}
|
}
|
||||||
@@ -15,10 +13,9 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
let folder = await Folder.save({ title: args['new-notebook'] }, { userSideValidation: true });
|
let folder = await Folder.save({ title: args['new-notebook'] }, { userSideValidation: true });
|
||||||
app().switchCurrentFolder(folder);
|
app().switchCurrentFolder(folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -4,7 +4,6 @@ const { _ } = require('lib/locale.js');
|
|||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'mknote <new-note>';
|
return 'mknote <new-note>';
|
||||||
}
|
}
|
||||||
@@ -26,7 +25,6 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
app().switchCurrentFolder(app().currentFolder());
|
app().switchCurrentFolder(app().currentFolder());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -4,7 +4,6 @@ const { _ } = require('lib/locale.js');
|
|||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'mktodo <new-todo>';
|
return 'mktodo <new-todo>';
|
||||||
}
|
}
|
||||||
@@ -27,7 +26,6 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
app().switchCurrentFolder(app().currentFolder());
|
app().switchCurrentFolder(app().currentFolder());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -6,7 +6,6 @@ const Folder = require('lib/models/Folder.js');
|
|||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'mv <note> [notebook]';
|
return 'mv <note> [notebook]';
|
||||||
}
|
}
|
||||||
@@ -18,7 +17,7 @@ class Command extends BaseCommand {
|
|||||||
async action(args) {
|
async action(args) {
|
||||||
const pattern = args['note'];
|
const pattern = args['note'];
|
||||||
const destination = args['notebook'];
|
const destination = args['notebook'];
|
||||||
|
|
||||||
const folder = await Folder.loadByField('title', destination);
|
const folder = await Folder.loadByField('title', destination);
|
||||||
if (!folder) throw new Error(_('Cannot find "%s".', destination));
|
if (!folder) throw new Error(_('Cannot find "%s".', destination));
|
||||||
|
|
||||||
@@ -29,7 +28,6 @@ class Command extends BaseCommand {
|
|||||||
await Note.moveToFolder(notes[i].id, folder.id);
|
await Note.moveToFolder(notes[i].id, folder.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -6,7 +6,6 @@ const Folder = require('lib/models/Folder.js');
|
|||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'ren <item> <name>';
|
return 'ren <item> <name>';
|
||||||
}
|
}
|
||||||
@@ -35,7 +34,6 @@ class Command extends BaseCommand {
|
|||||||
await Note.save(newItem);
|
await Note.save(newItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -1,14 +1,10 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const BaseItem = require('lib/models/BaseItem.js');
|
|
||||||
const Folder = require('lib/models/Folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
const Note = require('lib/models/Note.js');
|
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const { cliUtils } = require('./cli-utils.js');
|
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'rmbook <notebook>';
|
return 'rmbook <notebook>';
|
||||||
}
|
}
|
||||||
@@ -18,9 +14,7 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
options() {
|
options() {
|
||||||
return [
|
return [['-f, --force', _('Deletes the notebook without asking for confirmation.')]];
|
||||||
['-f, --force', _('Deletes the notebook without asking for confirmation.')],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
@@ -34,7 +28,6 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
await Folder.delete(folder.id);
|
await Folder.delete(folder.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -1,14 +1,10 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const BaseItem = require('lib/models/BaseItem.js');
|
|
||||||
const Folder = require('lib/models/Folder.js');
|
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const { cliUtils } = require('./cli-utils.js');
|
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'rmnote <note-pattern>';
|
return 'rmnote <note-pattern>';
|
||||||
}
|
}
|
||||||
@@ -18,9 +14,7 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
options() {
|
options() {
|
||||||
return [
|
return [['-f, --force', _('Deletes the notes without asking for confirmation.')]];
|
||||||
['-f, --force', _('Deletes the notes without asking for confirmation.')],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
@@ -32,10 +26,9 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
const ok = force ? true : await this.prompt(notes.length > 1 ? _('%d notes match this pattern. Delete them?', notes.length) : _('Delete note?'), { booleanAnswerDefault: 'n' });
|
const ok = force ? true : await this.prompt(notes.length > 1 ? _('%d notes match this pattern. Delete them?', notes.length) : _('Delete note?'), { booleanAnswerDefault: 'n' });
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
let ids = notes.map((n) => n.id);
|
let ids = notes.map(n => n.id);
|
||||||
await Note.batchDelete(ids);
|
await Note.batchDelete(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -1,15 +1,10 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { app } = require('./app.js');
|
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const Folder = require('lib/models/Folder.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');
|
const { uuid } = require('lib/uuid.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'search <pattern> [notebook]';
|
return 'search <pattern> [notebook]';
|
||||||
}
|
}
|
||||||
@@ -50,7 +45,6 @@ class Command extends BaseCommand {
|
|||||||
id: searchId,
|
id: searchId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
57
CliClient/app/command-server.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
const { BaseCommand } = require('./base-command.js');
|
||||||
|
const { _ } = require('lib/locale.js');
|
||||||
|
const Setting = require('lib/models/Setting.js');
|
||||||
|
const { Logger } = require('lib/logger.js');
|
||||||
|
const { shim } = require('lib/shim');
|
||||||
|
|
||||||
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
return 'server <command>';
|
||||||
|
}
|
||||||
|
|
||||||
|
description() {
|
||||||
|
return `${_('Start, stop or check the API server. To specify on which port it should run, set the api.port config variable. Commands are (%s).', ['start', 'stop', 'status'].join('|'))} This is an experimental feature - use at your own risks! It is recommended that the server runs off its own separate profile so that no two CLI instances access that profile at the same time. Use --profile to specify the profile path.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async action(args) {
|
||||||
|
const command = args.command;
|
||||||
|
|
||||||
|
const ClipperServer = require('lib/ClipperServer');
|
||||||
|
const stdoutFn = (s) => this.stdout(s);
|
||||||
|
const clipperLogger = new Logger();
|
||||||
|
clipperLogger.addTarget('file', { path: `${Setting.value('profileDir')}/log-clipper.txt` });
|
||||||
|
clipperLogger.addTarget('console', { console: {
|
||||||
|
info: stdoutFn,
|
||||||
|
warn: stdoutFn,
|
||||||
|
error: stdoutFn,
|
||||||
|
}});
|
||||||
|
ClipperServer.instance().setDispatch(() => {});
|
||||||
|
ClipperServer.instance().setLogger(clipperLogger);
|
||||||
|
|
||||||
|
const pidPath = `${Setting.value('profileDir')}/clipper-pid.txt`;
|
||||||
|
const runningOnPort = await ClipperServer.instance().isRunning();
|
||||||
|
|
||||||
|
if (command === 'start') {
|
||||||
|
if (runningOnPort) {
|
||||||
|
this.stdout(_('Server is already running on port %d', runningOnPort));
|
||||||
|
} else {
|
||||||
|
await shim.fsDriver().writeFile(pidPath, process.pid.toString(), 'utf-8');
|
||||||
|
await ClipperServer.instance().start(); // Never exit
|
||||||
|
}
|
||||||
|
} else if (command === 'status') {
|
||||||
|
this.stdout(runningOnPort ? _('Server is running on port %d', runningOnPort) : _('Server is not running.'));
|
||||||
|
} else if (command === 'stop') {
|
||||||
|
if (!runningOnPort) {
|
||||||
|
this.stdout(_('Server is not running.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pid = await shim.fsDriver().readFile(pidPath);
|
||||||
|
if (!pid) return;
|
||||||
|
process.kill(pid, 'SIGTERM');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Command;
|
@@ -3,12 +3,9 @@ const { app } = require('./app.js');
|
|||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const { Database } = require('lib/database.js');
|
const { Database } = require('lib/database.js');
|
||||||
const Folder = require('lib/models/Folder.js');
|
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const BaseItem = require('lib/models/BaseItem.js');
|
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'set <note> <name> [value]';
|
return 'set <note> <name> [value]';
|
||||||
}
|
}
|
||||||
@@ -19,7 +16,7 @@ class Command extends BaseCommand {
|
|||||||
for (let i = 0; i < fields.length; i++) {
|
for (let i = 0; i < fields.length; i++) {
|
||||||
const f = fields[i];
|
const f = fields[i];
|
||||||
if (f.name === 'id') continue;
|
if (f.name === 'id') continue;
|
||||||
s.push(f.name + ' (' + Database.enumName('fieldType', f.type) + ')');
|
s.push(`${f.name} (${Database.enumName('fieldType', f.type)})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _('Sets the property <name> of the given <note> to the given [value]. Possible properties are:\n\n%s', s.join(', '));
|
return _('Sets the property <name> of the given <note> to the given [value]. Possible properties are:\n\n%s', s.join(', '));
|
||||||
@@ -42,10 +39,16 @@ class Command extends BaseCommand {
|
|||||||
type_: notes[i].type_,
|
type_: notes[i].type_,
|
||||||
};
|
};
|
||||||
newNote[propName] = propValue;
|
newNote[propName] = propValue;
|
||||||
await Note.save(newNote);
|
|
||||||
|
const timestamp = Date.now();
|
||||||
|
|
||||||
|
await Note.save(newNote, {
|
||||||
|
autoTimestamp: false, // No auto-timestamp because user may have provided them
|
||||||
|
updated_time: timestamp,
|
||||||
|
created_time: timestamp,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -1,12 +1,10 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { Database } = require('lib/database.js');
|
|
||||||
const { app } = require('./app.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 { _ } = require('lib/locale.js');
|
||||||
const { ReportService } = require('lib/services/report.js');
|
const { ReportService } = require('lib/services/report.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'status';
|
return 'status';
|
||||||
}
|
}
|
||||||
@@ -15,7 +13,7 @@ class Command extends BaseCommand {
|
|||||||
return _('Displays summary about the notes and notebooks.');
|
return _('Displays summary about the notes and notebooks.');
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action() {
|
||||||
let service = new ReportService();
|
let service = new ReportService();
|
||||||
let report = await service.status(Setting.value('sync.target'));
|
let report = await service.status(Setting.value('sync.target'));
|
||||||
|
|
||||||
@@ -24,7 +22,7 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
if (i > 0) this.stdout('');
|
if (i > 0) this.stdout('');
|
||||||
|
|
||||||
this.stdout('# ' + section.title);
|
this.stdout(`# ${section.title}`);
|
||||||
this.stdout('');
|
this.stdout('');
|
||||||
|
|
||||||
for (let n in section.body) {
|
for (let n in section.body) {
|
||||||
@@ -34,10 +32,13 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app().gui().showConsole();
|
app()
|
||||||
app().gui().maximizeConsole();
|
.gui()
|
||||||
|
.showConsole();
|
||||||
|
app()
|
||||||
|
.gui()
|
||||||
|
.maximizeConsole();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -3,7 +3,6 @@ const { app } = require('./app.js');
|
|||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const { OneDriveApiNodeUtils } = require('./onedrive-api-node-utils.js');
|
const { OneDriveApiNodeUtils } = require('./onedrive-api-node-utils.js');
|
||||||
const Setting = require('lib/models/Setting.js');
|
const Setting = require('lib/models/Setting.js');
|
||||||
const BaseItem = require('lib/models/BaseItem.js');
|
|
||||||
const ResourceFetcher = require('lib/services/ResourceFetcher');
|
const ResourceFetcher = require('lib/services/ResourceFetcher');
|
||||||
const { Synchronizer } = require('lib/synchronizer.js');
|
const { Synchronizer } = require('lib/synchronizer.js');
|
||||||
const { reg } = require('lib/registry.js');
|
const { reg } = require('lib/registry.js');
|
||||||
@@ -14,7 +13,6 @@ const fs = require('fs-extra');
|
|||||||
const SyncTargetRegistry = require('lib/SyncTargetRegistry');
|
const SyncTargetRegistry = require('lib/SyncTargetRegistry');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.syncTargetId_ = null;
|
this.syncTargetId_ = null;
|
||||||
@@ -31,9 +29,7 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
options() {
|
options() {
|
||||||
return [
|
return [['--target <target>', _('Sync to provided target (defaults to sync.target config value)')]];
|
||||||
['--target <target>', _('Sync to provided target (defaults to sync.target config value)')],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static lockFile(filePath) {
|
static lockFile(filePath) {
|
||||||
@@ -66,21 +62,25 @@ class Command extends BaseCommand {
|
|||||||
const syncTarget = reg.syncTarget(this.syncTargetId_);
|
const syncTarget = reg.syncTarget(this.syncTargetId_);
|
||||||
const syncTargetMd = SyncTargetRegistry.idToMetadata(this.syncTargetId_);
|
const syncTargetMd = SyncTargetRegistry.idToMetadata(this.syncTargetId_);
|
||||||
|
|
||||||
if (this.syncTargetId_ === 3 || this.syncTargetId_ === 4) { // OneDrive
|
if (this.syncTargetId_ === 3 || this.syncTargetId_ === 4) {
|
||||||
|
// OneDrive
|
||||||
this.oneDriveApiUtils_ = new OneDriveApiNodeUtils(syncTarget.api());
|
this.oneDriveApiUtils_ = new OneDriveApiNodeUtils(syncTarget.api());
|
||||||
const auth = await this.oneDriveApiUtils_.oauthDance({
|
const auth = await this.oneDriveApiUtils_.oauthDance({
|
||||||
log: (...s) => { return this.stdout(...s); }
|
log: (...s) => {
|
||||||
|
return this.stdout(...s);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
this.oneDriveApiUtils_ = null;
|
this.oneDriveApiUtils_ = null;
|
||||||
|
|
||||||
Setting.setValue('sync.' + this.syncTargetId_ + '.auth', auth ? JSON.stringify(auth) : null);
|
Setting.setValue(`sync.${this.syncTargetId_}.auth`, auth ? JSON.stringify(auth) : null);
|
||||||
if (!auth) {
|
if (!auth) {
|
||||||
this.stdout(_('Authentication was not completed (did not receive an authentication token).'));
|
this.stdout(_('Authentication was not completed (did not receive an authentication token).'));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else if (syncTargetMd.name === 'dropbox') { // Dropbox
|
} else if (syncTargetMd.name === 'dropbox') {
|
||||||
|
// Dropbox
|
||||||
const api = await syncTarget.api();
|
const api = await syncTarget.api();
|
||||||
const loginUrl = api.loginUrl();
|
const loginUrl = api.loginUrl();
|
||||||
this.stdout(_('To allow Joplin to synchronise with Dropbox, please follow the steps below:'));
|
this.stdout(_('To allow Joplin to synchronise with Dropbox, please follow the steps below:'));
|
||||||
@@ -93,7 +93,7 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const response = await api.execAuthToken(authCode);
|
const response = await api.execAuthToken(authCode);
|
||||||
Setting.setValue('sync.' + this.syncTargetId_ + '.auth', response.access_token);
|
Setting.setValue(`sync.${this.syncTargetId_}.auth`, response.access_token);
|
||||||
api.setAuthToken(response.access_token);
|
api.setAuthToken(response.access_token);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -117,8 +117,8 @@ class Command extends BaseCommand {
|
|||||||
this.releaseLockFn_ = null;
|
this.releaseLockFn_ = null;
|
||||||
|
|
||||||
// Lock is unique per profile/database
|
// Lock is unique per profile/database
|
||||||
const lockFilePath = require('os').tmpdir() + '/synclock_' + md5(escape(Setting.value('profileDir'))); // https://github.com/pvorb/node-md5/issues/41
|
const lockFilePath = `${require('os').tmpdir()}/synclock_${md5(escape(Setting.value('profileDir')))}`; // https://github.com/pvorb/node-md5/issues/41
|
||||||
if (!await fs.pathExists(lockFilePath)) await fs.writeFile(lockFilePath, 'synclock');
|
if (!(await fs.pathExists(lockFilePath))) await fs.writeFile(lockFilePath, 'synclock');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (await Command.isLocked(lockFilePath)) throw new Error(_('Synchronisation is already in progress.'));
|
if (await Command.isLocked(lockFilePath)) throw new Error(_('Synchronisation is already in progress.'));
|
||||||
@@ -147,22 +147,26 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
const syncTarget = reg.syncTarget(this.syncTargetId_);
|
const syncTarget = reg.syncTarget(this.syncTargetId_);
|
||||||
|
|
||||||
if (!await syncTarget.isAuthenticated()) {
|
if (!(await syncTarget.isAuthenticated())) {
|
||||||
app().gui().showConsole();
|
app()
|
||||||
app().gui().maximizeConsole();
|
.gui()
|
||||||
|
.showConsole();
|
||||||
|
app()
|
||||||
|
.gui()
|
||||||
|
.maximizeConsole();
|
||||||
|
|
||||||
const authDone = await this.doAuth();
|
const authDone = await this.doAuth();
|
||||||
if (!authDone) return cleanUp();
|
if (!authDone) return cleanUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
const sync = await syncTarget.synchronizer();
|
const sync = await syncTarget.synchronizer();
|
||||||
|
|
||||||
let options = {
|
let options = {
|
||||||
onProgress: (report) => {
|
onProgress: report => {
|
||||||
let lines = Synchronizer.reportToLines(report);
|
let lines = Synchronizer.reportToLines(report);
|
||||||
if (lines.length) cliUtils.redraw(lines.join(' '));
|
if (lines.length) cliUtils.redraw(lines.join(' '));
|
||||||
},
|
},
|
||||||
onMessage: (msg) => {
|
onMessage: msg => {
|
||||||
cliUtils.redrawDone();
|
cliUtils.redrawDone();
|
||||||
this.stdout(msg);
|
this.stdout(msg);
|
||||||
},
|
},
|
||||||
@@ -174,7 +178,7 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
this.stdout(_('Starting synchronisation...'));
|
this.stdout(_('Starting synchronisation...'));
|
||||||
|
|
||||||
const contextKey = 'sync.' + this.syncTargetId_ + '.context';
|
const contextKey = `sync.${this.syncTargetId_}.context`;
|
||||||
let context = Setting.value(contextKey);
|
let context = Setting.value(contextKey);
|
||||||
|
|
||||||
context = context ? JSON.parse(context) : {};
|
context = context ? JSON.parse(context) : {};
|
||||||
@@ -193,7 +197,7 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
// When using the tool in command line mode, the ResourceFetcher service is
|
// When using the tool in command line mode, the ResourceFetcher service is
|
||||||
// not going to be running in the background, so the resources need to be
|
// not going to be running in the background, so the resources need to be
|
||||||
// explicitely downloaded below.
|
// explicitly downloaded below.
|
||||||
if (!app().hasGui()) {
|
if (!app().hasGui()) {
|
||||||
this.stdout(_('Downloading resources...'));
|
this.stdout(_('Downloading resources...'));
|
||||||
await ResourceFetcher.instance().fetchAll();
|
await ResourceFetcher.instance().fetchAll();
|
||||||
@@ -237,7 +241,6 @@ class Command extends BaseCommand {
|
|||||||
cancellable() {
|
cancellable() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -6,19 +6,16 @@ const BaseModel = require('lib/BaseModel.js');
|
|||||||
const { time } = require('lib/time-utils.js');
|
const { time } = require('lib/time-utils.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'tag <tag-command> [tag] [note]';
|
return 'tag <tag-command> [tag] [note]';
|
||||||
}
|
}
|
||||||
|
|
||||||
description() {
|
description() {
|
||||||
return _('<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 (use -l for long option).');
|
return _('<tag-command> can be "add", "remove", "list", or "notetags" to assign or remove [tag] from [note], to list notes associated with [tag], or to list tags associated with [note]. The command `tag list` can be used to list all the tags (use -l for long option).');
|
||||||
}
|
}
|
||||||
|
|
||||||
options() {
|
options() {
|
||||||
return [
|
return [['-l, --long', _('Use long list format. Format is ID, NOTE_COUNT (for notebook), DATE, TODO_CHECKED (for to-dos), TITLE')]];
|
||||||
['-l, --long', _('Use long list format. Format is ID, NOTE_COUNT (for notebook), DATE, TODO_CHECKED (for to-dos), TITLE')],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
@@ -50,7 +47,7 @@ class Command extends BaseCommand {
|
|||||||
} else if (command == 'list') {
|
} else if (command == 'list') {
|
||||||
if (tag) {
|
if (tag) {
|
||||||
let notes = await Tag.notes(tag.id);
|
let notes = await Tag.notes(tag.id);
|
||||||
notes.map((note) => {
|
notes.map(note => {
|
||||||
let line = '';
|
let line = '';
|
||||||
if (options.long) {
|
if (options.long) {
|
||||||
line += BaseModel.shortId(note.id);
|
line += BaseModel.shortId(note.id);
|
||||||
@@ -61,7 +58,7 @@ class Command extends BaseCommand {
|
|||||||
if (note.is_todo) {
|
if (note.is_todo) {
|
||||||
line += '[';
|
line += '[';
|
||||||
if (note.todo_completed) {
|
if (note.todo_completed) {
|
||||||
line += 'X';
|
line += 'X';
|
||||||
} else {
|
} else {
|
||||||
line += ' ';
|
line += ' ';
|
||||||
}
|
}
|
||||||
@@ -74,13 +71,25 @@ class Command extends BaseCommand {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
let tags = await Tag.all();
|
let tags = await Tag.all();
|
||||||
tags.map((tag) => { this.stdout(tag.title); });
|
tags.map(tag => {
|
||||||
|
this.stdout(tag.title);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (command == 'notetags') {
|
||||||
|
if (args.tag) {
|
||||||
|
const note = await app().loadItem(BaseModel.TYPE_NOTE, args.tag);
|
||||||
|
if (!note) throw new Error(_('Cannot find "%s".', args.tag));
|
||||||
|
const tags = await Tag.tagsByNoteId(note.id);
|
||||||
|
tags.map(tag => {
|
||||||
|
this.stdout(tag.title);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error(_('Cannot find "%s".', ''));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error(_('Invalid command: "%s"', command));
|
throw new Error(_('Invalid command: "%s"', command));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -2,12 +2,10 @@ const { BaseCommand } = require('./base-command.js');
|
|||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const Folder = require('lib/models/Folder.js');
|
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const { time } = require('lib/time-utils.js');
|
const { time } = require('lib/time-utils.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'todo <todo-command> <note-pattern>';
|
return 'todo <todo-command> <note-pattern>';
|
||||||
}
|
}
|
||||||
@@ -39,12 +37,11 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
} else if (action == 'clear') {
|
} else if (action == 'clear') {
|
||||||
toSave.is_todo = 0;
|
toSave.is_todo = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Note.save(toSave);
|
await Note.save(toSave);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -1,15 +1,9 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { app } = require('./app.js');
|
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.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');
|
const CommandDone = require('./command-done.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'undone <note>';
|
return 'undone <note>';
|
||||||
}
|
}
|
||||||
@@ -21,7 +15,6 @@ class Command extends BaseCommand {
|
|||||||
async action(args) {
|
async action(args) {
|
||||||
await CommandDone.handleAction(this, args, false);
|
await CommandDone.handleAction(this, args, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -2,10 +2,8 @@ const { BaseCommand } = require('./base-command.js');
|
|||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const Folder = require('lib/models/Folder.js');
|
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'use <notebook>';
|
return 'use <notebook>';
|
||||||
}
|
}
|
||||||
@@ -14,10 +12,6 @@ class Command extends BaseCommand {
|
|||||||
return _('Switches to [notebook] - all further operations will happen within this notebook.');
|
return _('Switches to [notebook] - all further operations will happen within this notebook.');
|
||||||
}
|
}
|
||||||
|
|
||||||
autocomplete() {
|
|
||||||
return { data: autocompleteFolders };
|
|
||||||
}
|
|
||||||
|
|
||||||
compatibleUis() {
|
compatibleUis() {
|
||||||
return ['cli'];
|
return ['cli'];
|
||||||
}
|
}
|
||||||
@@ -27,7 +21,6 @@ class Command extends BaseCommand {
|
|||||||
if (!folder) throw new Error(_('Cannot find "%s".', args['notebook']));
|
if (!folder) throw new Error(_('Cannot find "%s".', args['notebook']));
|
||||||
app().switchCurrentFolder(folder);
|
app().switchCurrentFolder(folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|