Compare commits
310 Commits
cli-v1.0.1
...
v1.0.207
Author | SHA1 | Date | |
---|---|---|---|
|
22b5888671 | ||
|
41edae9719 | ||
|
6446a3c4de | ||
|
9c4939e051 | ||
|
9f9f760ede | ||
|
488cb99d4c | ||
|
4167ecafd2 | ||
|
6ca41ddf80 | ||
|
734f83470c | ||
|
0f8f7aeb14 | ||
|
3b653a95a0 | ||
|
06eb389b21 | ||
|
17d2d6f959 | ||
|
20e065a371 | ||
|
57cf826e2c | ||
|
dbe8d3a68e | ||
|
5a174b2236 | ||
|
b8158f3b53 | ||
|
e768b8cfea | ||
|
c593546575 | ||
|
578f93fb90 | ||
|
24a90036b7 | ||
|
488c0f1828 | ||
|
f4717d0a3d | ||
|
913c78f45d | ||
|
4e90c05472 | ||
|
d31d9f2638 | ||
|
0cf56d5a9a | ||
|
80112e7b35 | ||
|
b2f1fac133 | ||
|
cd57877175 | ||
|
30f515d473 | ||
|
199113341f | ||
|
64aef69d74 | ||
|
2073755498 | ||
|
f72b5c1028 | ||
|
561b24f6fd | ||
|
d41221bd90 | ||
|
132c6543b2 | ||
|
7152344fe1 | ||
|
a04092c99c | ||
|
c273fd4d63 | ||
|
e06dd9c69f | ||
|
463b20013d | ||
|
204fbec648 | ||
|
d65706aa9c | ||
|
f2a1597ecc | ||
|
9a2f0bee29 | ||
|
3f8833eaf1 | ||
|
c3a1e7c6e9 | ||
|
f5aec137bd | ||
|
20960dbd47 | ||
|
e45fd6b4d3 | ||
|
8eacba972e | ||
|
51732a5adb | ||
|
f51873d877 | ||
|
d738fa54ad | ||
|
ec8ccc94aa | ||
|
cb8dca747b | ||
|
b16ebbbf7a | ||
|
90ccb34916 | ||
|
724af8f757 | ||
|
125fa8647a | ||
|
bf47237d8f | ||
|
294b9fc941 | ||
|
699c1ab58c | ||
|
150ee14de6 | ||
|
b207f7a53f | ||
|
096698912e | ||
|
a5ec1be7d1 | ||
|
f9f884a7f3 | ||
|
31d59c1e21 | ||
|
7663044e9f | ||
|
7ceb68d835 | ||
|
ac2635a363 | ||
|
2496e6e738 | ||
|
b4b36c6f22 | ||
|
0171ea837f | ||
|
79a343c2d9 | ||
|
dd5ed48c7e | ||
|
e7169cc70d | ||
|
c65ee4a424 | ||
|
feee162578 | ||
|
d1cab4b7f5 | ||
|
d2acf314f5 | ||
|
b803984773 | ||
|
d2582f4fdf | ||
|
dfd18ebe24 | ||
|
94082bc6a5 | ||
|
5c3c72c37e | ||
|
d730e068b0 | ||
|
b2f8c38842 | ||
|
71b2e36429 | ||
|
e598c64241 | ||
|
2b2ec2c655 | ||
|
381d757525 | ||
|
d54c59cf74 | ||
|
1f481fba4e | ||
|
69cd88e4a5 | ||
|
b140a9d7de | ||
|
b1b530edf2 | ||
|
ab63316f83 | ||
|
35df8e5d9e | ||
|
ea3c82733c | ||
|
60f75fe2db | ||
|
9214095a64 | ||
|
676c43ebab | ||
|
e65af8c1e4 | ||
|
2321455723 | ||
|
9260235224 | ||
|
56801c0a08 | ||
|
aa738cc086 | ||
|
900b0f3eb2 | ||
|
b5a64e2c3e | ||
|
4163d9e474 | ||
|
d769dc2b94 | ||
|
c9fc52ec74 | ||
|
fe72568d3b | ||
|
c5c379f38a | ||
|
2050889590 | ||
|
6164e2d8eb | ||
|
30c175ef29 | ||
|
f6fed72b64 | ||
|
7596ff2eda | ||
|
142976f012 | ||
|
ab5c97f75a | ||
|
aea3de982a | ||
|
83aff6f478 | ||
|
6cb7154bf4 | ||
|
3aa38d35d7 | ||
|
870a76c570 | ||
|
206a64eff6 | ||
|
6b97839617 | ||
|
456d979aa6 | ||
|
ea5e0d337c | ||
|
9651317695 | ||
|
a4cc2076d7 | ||
|
db820d500d | ||
|
722192127c | ||
|
36e23d6432 | ||
|
8cfb014f60 | ||
|
b4a572c8ae | ||
|
531e92e2da | ||
|
53a857b706 | ||
|
4f5473f8a2 | ||
|
1b617a4adc | ||
|
7741a43d42 | ||
|
776411f882 | ||
|
ae4cecc621 | ||
|
5be98a46e3 | ||
|
1907ef0c99 | ||
|
bd99b25848 | ||
|
12a7b3c73c | ||
|
75b28c46af | ||
|
93dccf62df | ||
|
ec499eecd5 | ||
|
7ccd19e21d | ||
|
a6459d3641 | ||
|
9b26378fdd | ||
|
6b8e84332d | ||
|
6d56bb8afd | ||
|
be9e873277 | ||
|
10feeeeb6b | ||
|
518af9dc0a | ||
|
093fc811eb | ||
|
2a8b27033f | ||
|
62bddd7c69 | ||
|
113f91b3c5 | ||
|
d40ad2de89 | ||
|
3b7733018c | ||
|
e319864669 | ||
|
7251813634 | ||
|
e024015d5e | ||
|
3f5e6d72d6 | ||
|
9fd43a30f9 | ||
|
0707a0e05f | ||
|
f550d847c4 | ||
|
1e9a0036e7 | ||
|
16cf0a3058 | ||
|
0fa19a281c | ||
|
9c53f485e6 | ||
|
dc131f77d4 | ||
|
712601b8c0 | ||
|
ff7775b344 | ||
|
0512fa6208 | ||
|
27ab2b8e30 | ||
|
9fa7c9c20a | ||
|
6bd0250ef8 | ||
|
0d736bcb58 | ||
|
c1129604ba | ||
|
693f6cbfe7 | ||
|
7f397a4da8 | ||
|
52ba5c5bb7 | ||
|
ce24942a03 | ||
|
719afba05a | ||
|
c99dd2ba87 | ||
|
a59cc94afc | ||
|
336cadbc12 | ||
|
a946dc69c1 | ||
|
e2e95df057 | ||
|
e095369e1a | ||
|
1c938a5998 | ||
|
a45128807e | ||
|
d54e52b1a8 | ||
|
5743729d11 | ||
|
11d8466db1 | ||
|
c3360d6c48 | ||
|
37158fdb89 | ||
|
ae73051797 | ||
|
62db3d09ea | ||
|
f82aa0adde | ||
|
5e5b6cdc42 | ||
|
351306eb03 | ||
|
e4fffa52d4 | ||
|
d622ff4a78 | ||
|
b6d4fd16c9 | ||
|
a548d695f2 | ||
|
499f318192 | ||
|
42ecf98344 | ||
|
6f08e1e4ff | ||
|
9524eb7e37 | ||
|
84e478f8fe | ||
|
0cc77f99ac | ||
|
622049dfad | ||
|
65bcc58261 | ||
|
9749a2b9b7 | ||
|
7b365194ba | ||
|
6e5c2730f1 | ||
|
8d045f0c96 | ||
|
c370358bd1 | ||
|
03e8d921d3 | ||
|
6cf624c18d | ||
|
41acdce165 | ||
|
c607444c23 | ||
|
940198b9a0 | ||
|
9c3cf705c6 | ||
|
6027725fae | ||
|
054aa52bc8 | ||
|
f4a562bc3c | ||
|
de8bf33ad9 | ||
|
159eaf7899 | ||
|
1bc045eb18 | ||
|
0f934e48bc | ||
|
3ecd29d0b5 | ||
|
42498842b5 | ||
|
ed4fbf093d | ||
|
96b19027ec | ||
|
cdb8a1e98c | ||
|
8cd26c9380 | ||
|
0863f0d564 | ||
|
63a2f7b7a5 | ||
|
9b562276f3 | ||
|
a17e01793e | ||
|
7fb061ea76 | ||
|
91d864bded | ||
|
339d7d16c7 | ||
|
8e5762c3a3 | ||
|
50a811720f | ||
|
c2e1c4c1e1 | ||
|
e7b11a2d82 | ||
|
61d3582357 | ||
|
9e66219690 | ||
|
d049b8846c | ||
|
bdd760f343 | ||
|
1ee88618e8 | ||
|
e399474b4e | ||
|
e2e55b6e08 | ||
|
d0d2bad7f4 | ||
|
92bee549a1 | ||
|
02121f66de | ||
|
115eb6f511 | ||
|
d208da577f | ||
|
62665899c6 | ||
|
7640839f92 | ||
|
cda837247a | ||
|
8e2ba0d963 | ||
|
f4958de885 | ||
|
3917e3469d | ||
|
5ce79b1761 | ||
|
7a621e0cd7 | ||
|
34a1c965be | ||
|
17b42ae051 | ||
|
55ae00af27 | ||
|
7e200b1ec7 | ||
|
f65a3be231 | ||
|
20bec7e26c | ||
|
b367955e56 | ||
|
84c3ef144d | ||
|
ab2c8e3826 | ||
|
f2c6ff7904 | ||
|
06ee4d08d6 | ||
|
e96679c820 | ||
|
105e4652c6 | ||
|
e5b8f149bf | ||
|
334cb23691 | ||
|
52d9807648 | ||
|
b44a2075a8 | ||
|
b60952d684 | ||
|
5db362e812 | ||
|
0b74168343 | ||
|
58e2d7be61 | ||
|
728c167660 | ||
|
982f274425 | ||
|
557c7a2877 | ||
|
dbba7af4b5 | ||
|
82eefca110 | ||
|
778b30b1cf | ||
|
2d4616da01 | ||
|
37a12326dc | ||
|
5516b3284c |
@@ -21,41 +21,76 @@ Clipper/content_scripts/Readability.js
|
|||||||
Clipper/dist
|
Clipper/dist
|
||||||
Clipper/icons
|
Clipper/icons
|
||||||
Clipper/popup/build
|
Clipper/popup/build
|
||||||
|
Clipper/popup/config/webpack.config.js
|
||||||
|
Clipper/popup/config/webpack_config_at_eject_time.js
|
||||||
Clipper/popup/node_modules
|
Clipper/popup/node_modules
|
||||||
|
Clipper/popup/scripts/build.js
|
||||||
docs/
|
docs/
|
||||||
ElectronClient/dist
|
ElectronClient/dist
|
||||||
ElectronClient/lib
|
ElectronClient/lib
|
||||||
|
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/plugins/lists.js
|
||||||
ElectronClient/lib/vendor/sjcl-rn.js
|
ElectronClient/lib/vendor/sjcl-rn.js
|
||||||
ElectronClient/lib/vendor/sjcl.js
|
ElectronClient/lib/vendor/sjcl.js
|
||||||
ElectronClient/locales
|
ElectronClient/locales
|
||||||
ElectronClient/node_modules
|
ElectronClient/node_modules
|
||||||
|
ElectronClient/packageInfo.js
|
||||||
highlight.pack.js
|
highlight.pack.js
|
||||||
|
Modules/TinyMCE/JoplinLists/
|
||||||
node_modules/
|
node_modules/
|
||||||
ReactNativeClient/android
|
ReactNativeClient/android
|
||||||
ReactNativeClient/ios
|
ReactNativeClient/ios
|
||||||
|
ReactNativeClient/lib/joplin-renderer/assets/
|
||||||
|
ReactNativeClient/lib/joplin-renderer/vendor/fountain.min.js
|
||||||
|
ReactNativeClient/lib/rnInjectedJs/
|
||||||
ReactNativeClient/lib/vendor/
|
ReactNativeClient/lib/vendor/
|
||||||
ReactNativeClient/lib/welcomeAssets.js
|
ReactNativeClient/lib/welcomeAssets.js
|
||||||
ReactNativeClient/locales
|
ReactNativeClient/locales
|
||||||
ReactNativeClient/node_modules
|
ReactNativeClient/node_modules
|
||||||
|
ReactNativeClient/pluginAssets/
|
||||||
readme/
|
readme/
|
||||||
Tools/node_modules
|
|
||||||
Tools/PortableAppsLauncher
|
|
||||||
Server/.git/
|
Server/.git/
|
||||||
Server/.github/
|
Server/.github/
|
||||||
Server/docs/
|
|
||||||
Server/dist/
|
|
||||||
Server/bin/
|
Server/bin/
|
||||||
|
Server/dist/
|
||||||
|
Server/docs/
|
||||||
Server/node_modules/
|
Server/node_modules/
|
||||||
ElectronClient/packageInfo.js
|
Tools/node_modules
|
||||||
ReactNativeClient/pluginAssets/
|
Tools/PortableAppsLauncher
|
||||||
ReactNativeClient/lib/joplin-renderer/vendor/fountain.min.js
|
Modules/TinyMCE/IconPack/postinstall.js
|
||||||
ReactNativeClient/lib/joplin-renderer/assets/
|
Modules/TinyMCE/langs/
|
||||||
ReactNativeClient/lib/rnInjectedJs/
|
|
||||||
|
|
||||||
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
|
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
|
||||||
|
ElectronClient/gui/MultiNoteActions.js
|
||||||
ElectronClient/gui/NoteContentPropertiesDialog.js
|
ElectronClient/gui/NoteContentPropertiesDialog.js
|
||||||
|
ElectronClient/gui/NoteEditor/NoteBody/AceEditor/AceEditor.js
|
||||||
|
ElectronClient/gui/NoteEditor/NoteBody/AceEditor/styles/index.js
|
||||||
|
ElectronClient/gui/NoteEditor/NoteBody/AceEditor/Toolbar.js
|
||||||
|
ElectronClient/gui/NoteEditor/NoteBody/AceEditor/utils/index.js
|
||||||
|
ElectronClient/gui/NoteEditor/NoteBody/AceEditor/utils/types.js
|
||||||
|
ElectronClient/gui/NoteEditor/NoteBody/AceEditor/utils/useListIdent.js
|
||||||
|
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js
|
||||||
|
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/utils/useScroll.js
|
||||||
|
ElectronClient/gui/NoteEditor/NoteEditor.js
|
||||||
|
ElectronClient/gui/NoteEditor/styles/index.js
|
||||||
|
ElectronClient/gui/NoteEditor/utils/contextMenu.js
|
||||||
|
ElectronClient/gui/NoteEditor/utils/index.js
|
||||||
|
ElectronClient/gui/NoteEditor/utils/resourceHandling.js
|
||||||
|
ElectronClient/gui/NoteEditor/utils/types.js
|
||||||
|
ElectronClient/gui/NoteEditor/utils/useDropHandler.js
|
||||||
|
ElectronClient/gui/NoteEditor/utils/useFormNote.js
|
||||||
|
ElectronClient/gui/NoteEditor/utils/useMarkupToHtml.js
|
||||||
|
ElectronClient/gui/NoteEditor/utils/useMessageHandler.js
|
||||||
|
ElectronClient/gui/NoteEditor/utils/useNoteSearchBar.js
|
||||||
|
ElectronClient/gui/NoteEditor/utils/useSearchMarkers.js
|
||||||
|
ElectronClient/gui/NoteEditor/utils/useWindowCommandHandler.js
|
||||||
|
ElectronClient/gui/NoteToolbar/NoteToolbar.js
|
||||||
ElectronClient/gui/ResourceScreen.js
|
ElectronClient/gui/ResourceScreen.js
|
||||||
ElectronClient/gui/ShareNoteDialog.js
|
ElectronClient/gui/ShareNoteDialog.js
|
||||||
|
ReactNativeClient/lib/AsyncActionQueue.js
|
||||||
|
ReactNativeClient/lib/hooks/useImperativeHandlerDebugger.js
|
||||||
|
ReactNativeClient/lib/hooks/usePrevious.js
|
||||||
|
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/checkbox.js
|
||||||
|
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js
|
||||||
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
|
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
|
||||||
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js
|
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js
|
||||||
ReactNativeClient/lib/JoplinServerApi.js
|
ReactNativeClient/lib/JoplinServerApi.js
|
||||||
|
12
.eslintrc.js
@@ -32,6 +32,8 @@ module.exports = {
|
|||||||
'browserSupportsPromises_': true,
|
'browserSupportsPromises_': true,
|
||||||
'chrome': 'readonly',
|
'chrome': 'readonly',
|
||||||
'browser': 'readonly',
|
'browser': 'readonly',
|
||||||
|
|
||||||
|
'tinymce': 'readonly',
|
||||||
},
|
},
|
||||||
'parserOptions': {
|
'parserOptions': {
|
||||||
'ecmaVersion': 2018,
|
'ecmaVersion': 2018,
|
||||||
@@ -47,21 +49,27 @@ module.exports = {
|
|||||||
"react/jsx-uses-react": "error",
|
"react/jsx-uses-react": "error",
|
||||||
"react/jsx-uses-vars": "error",
|
"react/jsx-uses-vars": "error",
|
||||||
"no-unused-vars": "error",
|
"no-unused-vars": "error",
|
||||||
|
"@typescript-eslint/no-unused-vars": "error",
|
||||||
"no-constant-condition": 0,
|
"no-constant-condition": 0,
|
||||||
"no-prototype-builtins": 0,
|
"no-prototype-builtins": 0,
|
||||||
// This error is always a false positive so far since it detects
|
// This error is always a false positive so far since it detects
|
||||||
// possible race conditions in contexts where we know it cannot happen.
|
// possible race conditions in contexts where we know it cannot happen.
|
||||||
"require-atomic-updates": 0,
|
"require-atomic-updates": 0,
|
||||||
|
"prefer-const": ["error"],
|
||||||
|
"no-var": ["error"],
|
||||||
|
|
||||||
// Checks rules of Hooks
|
// Checks rules of Hooks
|
||||||
"react-hooks/rules-of-hooks": "error",
|
"react-hooks/rules-of-hooks": "error",
|
||||||
// Checks effect dependencies
|
// Checks effect dependencies
|
||||||
"react-hooks/exhaustive-deps": "error",
|
// Disable because of this: https://github.com/facebook/react/issues/16265
|
||||||
|
// "react-hooks/exhaustive-deps": "warn",
|
||||||
|
|
||||||
// -------------------------------
|
// -------------------------------
|
||||||
// Formatting
|
// Formatting
|
||||||
// -------------------------------
|
// -------------------------------
|
||||||
"space-in-parens": ["error", "never"],
|
"space-in-parens": ["error", "never"],
|
||||||
|
"space-infix-ops": ["error"],
|
||||||
|
"curly": ["error", "multi-line", "consistent"],
|
||||||
"semi": ["error", "always"],
|
"semi": ["error", "always"],
|
||||||
"eol-last": ["error", "always"],
|
"eol-last": ["error", "always"],
|
||||||
"quotes": ["error", "single"],
|
"quotes": ["error", "single"],
|
||||||
@@ -90,7 +98,7 @@ module.exports = {
|
|||||||
"multiline-comment-style": ["error", "separate-lines"],
|
"multiline-comment-style": ["error", "separate-lines"],
|
||||||
"space-before-blocks": "error",
|
"space-before-blocks": "error",
|
||||||
"spaced-comment": ["error", "always"],
|
"spaced-comment": ["error", "always"],
|
||||||
"keyword-spacing": ["error", { "before": true, "after": true }]
|
"keyword-spacing": ["error", { "before": true, "after": true }],
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"react",
|
"react",
|
||||||
|
15
.github/ISSUE_TEMPLATE/---feature-requests-and-support.md
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
name: "\U0001F914 Feature requests and support"
|
||||||
|
about: 'For non-bug issues we recommend using the forum, where you''ll be more likely
|
||||||
|
to get an answer: https://discourse.joplinapp.org/'
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
If this is a feature request or a support query, please note that you'll not get an answer here.
|
||||||
|
|
||||||
|
Instead we recommend using the forum where you'll are a lot more likely to get an answer: https://discourse.joplinapp.org/
|
||||||
|
|
||||||
|
The forum is also the right place to submit a feature request so that it can be discussed by other users.
|
5
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,8 +1,9 @@
|
|||||||
---
|
---
|
||||||
name: "🐛 Bug Report"
|
name: "\U0001F41B Bug Report"
|
||||||
about: Report a reproducible bug or regression in Joplin.
|
about: Report a reproducible bug or regression in Joplin.
|
||||||
title: ''
|
title: ''
|
||||||
labels: 'bug'
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
29
.github/ISSUE_TEMPLATE/question.md
vendored
@@ -1,29 +0,0 @@
|
|||||||
---
|
|
||||||
name: "🤔 Questions and Help"
|
|
||||||
about: The issue tracker is not for questions. Please ask questions on https://discourse.joplinapp.org/.
|
|
||||||
title: ''
|
|
||||||
labels: 'invalid'
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
⚠🚨⛔ 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.
|
|
||||||
|
|
||||||
## Questions and Help
|
|
||||||
|
|
||||||
Please read the [documentation](https://joplinapp.org/) and [FAQ](https://joplinapp.org/faq/) first.
|
|
||||||
|
|
||||||
### https://discourse.joplinapp.org/
|
|
||||||
|
|
||||||
If you have still questions related to Joplin, please open a topic in the [forum](https://discourse.joplinapp.org/).
|
|
||||||
You can use your GitHub credentials to login to the forum.
|
|
||||||
|
|
||||||
## Links
|
|
||||||
|
|
||||||
- Documentation: https://joplinapp.org
|
|
||||||
- FAQ: https://joplinapp.org/faq/
|
|
||||||
- Forum: https://discourse.joplinapp.org
|
|
||||||
- How to enable end-to-end encryption: https://joplinapp.org/e2ee/
|
|
||||||
- API documentation: https://joplinapp.org/api/
|
|
||||||
- How to enable debug mode: https://joplinapp.org/debugging/
|
|
6
.github/stale.yml
vendored
@@ -1,15 +1,11 @@
|
|||||||
# Configuration for probot-stale - https://github.com/probot/stale
|
# Configuration for probot-stale - https://github.com/probot/stale
|
||||||
# Number of days of inactivity before an issue becomes stale
|
# Number of days of inactivity before an issue becomes stale
|
||||||
daysUntilStale: 90
|
daysUntilStale: 45
|
||||||
# Number of days of inactivity before a stale issue is closed
|
# Number of days of inactivity before a stale issue is closed
|
||||||
daysUntilClose: 7
|
daysUntilClose: 7
|
||||||
# Issues with these labels will never be considered stale
|
# Issues with these labels will never be considered stale
|
||||||
exemptLabels:
|
exemptLabels:
|
||||||
- "good first issue"
|
- "good first issue"
|
||||||
- "essential"
|
|
||||||
- "essential-reviewed"
|
|
||||||
- "help wanted"
|
|
||||||
- "nice to have"
|
|
||||||
- "upstream"
|
- "upstream"
|
||||||
- "backlog"
|
- "backlog"
|
||||||
- "high"
|
- "high"
|
||||||
|
28
.gitignore
vendored
@@ -50,9 +50,37 @@ Tools/commit_hook.txt
|
|||||||
*.map
|
*.map
|
||||||
|
|
||||||
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
|
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
|
||||||
|
ElectronClient/gui/MultiNoteActions.js
|
||||||
ElectronClient/gui/NoteContentPropertiesDialog.js
|
ElectronClient/gui/NoteContentPropertiesDialog.js
|
||||||
|
ElectronClient/gui/NoteEditor/NoteBody/AceEditor/AceEditor.js
|
||||||
|
ElectronClient/gui/NoteEditor/NoteBody/AceEditor/styles/index.js
|
||||||
|
ElectronClient/gui/NoteEditor/NoteBody/AceEditor/Toolbar.js
|
||||||
|
ElectronClient/gui/NoteEditor/NoteBody/AceEditor/utils/index.js
|
||||||
|
ElectronClient/gui/NoteEditor/NoteBody/AceEditor/utils/types.js
|
||||||
|
ElectronClient/gui/NoteEditor/NoteBody/AceEditor/utils/useListIdent.js
|
||||||
|
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js
|
||||||
|
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/utils/useScroll.js
|
||||||
|
ElectronClient/gui/NoteEditor/NoteEditor.js
|
||||||
|
ElectronClient/gui/NoteEditor/styles/index.js
|
||||||
|
ElectronClient/gui/NoteEditor/utils/contextMenu.js
|
||||||
|
ElectronClient/gui/NoteEditor/utils/index.js
|
||||||
|
ElectronClient/gui/NoteEditor/utils/resourceHandling.js
|
||||||
|
ElectronClient/gui/NoteEditor/utils/types.js
|
||||||
|
ElectronClient/gui/NoteEditor/utils/useDropHandler.js
|
||||||
|
ElectronClient/gui/NoteEditor/utils/useFormNote.js
|
||||||
|
ElectronClient/gui/NoteEditor/utils/useMarkupToHtml.js
|
||||||
|
ElectronClient/gui/NoteEditor/utils/useMessageHandler.js
|
||||||
|
ElectronClient/gui/NoteEditor/utils/useNoteSearchBar.js
|
||||||
|
ElectronClient/gui/NoteEditor/utils/useSearchMarkers.js
|
||||||
|
ElectronClient/gui/NoteEditor/utils/useWindowCommandHandler.js
|
||||||
|
ElectronClient/gui/NoteToolbar/NoteToolbar.js
|
||||||
ElectronClient/gui/ResourceScreen.js
|
ElectronClient/gui/ResourceScreen.js
|
||||||
ElectronClient/gui/ShareNoteDialog.js
|
ElectronClient/gui/ShareNoteDialog.js
|
||||||
|
ReactNativeClient/lib/AsyncActionQueue.js
|
||||||
|
ReactNativeClient/lib/hooks/useImperativeHandlerDebugger.js
|
||||||
|
ReactNativeClient/lib/hooks/usePrevious.js
|
||||||
|
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/checkbox.js
|
||||||
|
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js
|
||||||
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
|
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
|
||||||
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js
|
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js
|
||||||
ReactNativeClient/lib/JoplinServerApi.js
|
ReactNativeClient/lib/JoplinServerApi.js
|
||||||
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
BIN
Assets/ImageSources/RoundedCorners_1024x1024.png
Normal file
After Width: | Height: | Size: 70 KiB |
BIN
Assets/ImageSources/RoundedCorners_16x16.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
Assets/ImageSources/RoundedCorners_64x64.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
Assets/ImageSources/Square_1024x1024.png
Normal file
After Width: | Height: | Size: 33 KiB |
109
Assets/SmallTile.svg
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<?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"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
sodipodi:docname="disegno.svg"
|
||||||
|
inkscape:version="1.0rc1 (09960d6, 2020-04-09)"
|
||||||
|
id="svg8"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 70 70"
|
||||||
|
height="70"
|
||||||
|
width="70">
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:window-y="25"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-height="775"
|
||||||
|
inkscape:window-width="1280"
|
||||||
|
inkscape:guide-bbox="true"
|
||||||
|
showguides="true"
|
||||||
|
inkscape:pagecheckerboard="true"
|
||||||
|
inkscape:showpageshadow="false"
|
||||||
|
showborder="true"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:document-rotation="0"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:cy="39.253064"
|
||||||
|
inkscape:cx="25.246811"
|
||||||
|
inkscape:zoom="5.8562241"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
borderopacity="1.0"
|
||||||
|
bordercolor="#666666"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
id="base"
|
||||||
|
units="px">
|
||||||
|
<sodipodi:guide
|
||||||
|
inkscape:color="rgb(0,0,255)"
|
||||||
|
inkscape:locked="false"
|
||||||
|
inkscape:label=""
|
||||||
|
id="guide1932"
|
||||||
|
orientation="-1,0"
|
||||||
|
position="12,70" />
|
||||||
|
<sodipodi:guide
|
||||||
|
inkscape:color="rgb(0,0,255)"
|
||||||
|
inkscape:locked="false"
|
||||||
|
inkscape:label=""
|
||||||
|
id="guide1934"
|
||||||
|
orientation="-1,0"
|
||||||
|
position="58,70" />
|
||||||
|
<sodipodi:guide
|
||||||
|
inkscape:color="rgb(0,0,255)"
|
||||||
|
inkscape:locked="false"
|
||||||
|
inkscape:label=""
|
||||||
|
id="guide1936"
|
||||||
|
orientation="0,1"
|
||||||
|
position="0,58" />
|
||||||
|
<sodipodi:guide
|
||||||
|
inkscape:color="rgb(0,0,255)"
|
||||||
|
inkscape:locked="false"
|
||||||
|
inkscape:label=""
|
||||||
|
id="guide1938"
|
||||||
|
orientation="0,1"
|
||||||
|
position="0,12" />
|
||||||
|
<sodipodi:guide
|
||||||
|
inkscape:color="rgb(0,0,255)"
|
||||||
|
inkscape:locked="false"
|
||||||
|
inkscape:label=""
|
||||||
|
id="guide2021"
|
||||||
|
orientation="-1,0"
|
||||||
|
position="35,70" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<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
|
||||||
|
id="layer1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
inkscape:label="Livello 1">
|
||||||
|
<path
|
||||||
|
inkscape:transform-center-y="23.027731"
|
||||||
|
inkscape:transform-center-x="-17.510929"
|
||||||
|
id="path30"
|
||||||
|
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133334;stop-opacity:1"
|
||||||
|
d="M 52.1731,12 H 35.107236 c -0.161294,0 -0.291666,0.130494 -0.291666,0.291543 v 5.419239 c 0,0.182381 0.147678,0.330176 0.330178,0.330176 h 2.339824 c 0.94638,0 1.713811,0.727936 1.795988,1.652976 v 3.715485 2.069989 19.715998 0.139708 h -6.44e-4 c 0.0058,0.206545 -0.0023,0.409658 -0.02244,0.609702 -0.0023,0.02918 -0.0062,0.05801 -0.0097,0.08697 -0.01349,0.116897 -0.02846,0.233161 -0.05114,0.346981 -0.01243,0.06489 -0.02993,0.128044 -0.0455,0.191949 -0.02097,0.08499 -0.0379,0.171342 -0.06401,0.254505 -0.1543,0.493916 -0.39825,0.95202 -0.735666,1.355297 -0.01243,0.01438 -0.02771,0.02761 -0.03998,0.04182 -0.102908,0.119222 -0.210962,0.235367 -0.330548,0.34416 -0.120695,0.109778 -0.248492,0.211819 -0.382795,0.306628 -0.954719,0.676302 -2.23974,0.973854 -3.6755,0.836485 -1.830697,-0.172088 -3.646921,-1.002309 -5.114939,-2.337984 -1.467772,-1.335554 -2.380422,-2.988039 -2.569305,-4.653525 -0.169868,-1.489971 0.276578,-2.801116 1.256197,-3.692426 0.0027,-0.0019 0.005,-0.0037 0.0069,-0.0058 0.0379,-0.03398 0.0796,-0.06379 0.1186,-0.09654 0.701076,-0.58738 1.614951,-0.943313 2.657858,-1.041435 0.01172,-0.0011 0.02306,-0.003 0.03423,-0.0039 0.109777,-0.0097 0.221631,-0.01402 0.334471,-0.01827 0.05899,-0.0019 0.117251,-0.0062 0.177111,-0.0065 0.01986,-2.76e-4 0.03875,-0.0023 0.05864,-0.0023 0.03557,0 0.07248,0.0046 0.108055,0.0051 0.164347,0.0027 0.329933,0.0094 0.498825,0.02379 0.02134,0.0017 0.0417,9.78e-4 0.06365,0.003 0.01562,0.0016 0.03103,0.005 0.04662,0.0065 0.956682,0.09592 1.90392,0.381568 2.802834,0.814406 0.01876,8.39e-4 0.04047,0.0065 0.06759,0.02159 0.274004,0.151353 0.326498,-0.01101 0.334593,-0.118351 v -4.688237 -3.46883 c 0,-0.233653 -0.162519,-0.440321 -0.3904,-0.490484 -4.841795,-1.065597 -9.592336,0.04355 -12.944402,3.09499 -2.928307,2.663747 -4.344811,6.507647 -3.885849,10.545461 0.409535,3.596512 2.255685,7.05173 5.198956,9.730318 2.868698,2.609903 6.521262,4.242028 10.288996,4.596993 0.519184,0.04805 1.040822,0.07283 1.549826,0.07283 3.598351,0 6.937296,-1.233851 9.401735,-3.475688 2.33455,-2.125552 3.732902,-5.044294 3.938097,-8.219619 l 0.01901,-20.825138 h 0.0021 V 19.91155 h 7.7e-4 v -0.09714 c 0.02109,-0.982682 0.822378,-1.773296 1.81046,-1.773296 h 2.339824 c 0.182259,0 0.330177,-0.147802 0.330177,-0.330177 v -5.419299 c -2.1e-5,-0.161046 -0.130406,-0.291543 -0.291686,-0.291543"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</g>
|
||||||
|
<inkscape:templateinfo>
|
||||||
|
<inkscape:name>SmallTile</inkscape:name>
|
||||||
|
<inkscape:shortdesc>Small tile</inkscape:shortdesc>
|
||||||
|
<inkscape:date>2020-04-15</inkscape:date>
|
||||||
|
</inkscape:templateinfo>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 5.4 KiB |
109
Assets/Square150x150Logo.svg
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<?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"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
sodipodi:docname="disegno-1.svg"
|
||||||
|
inkscape:version="1.0rc1 (09960d6, 2020-04-09)"
|
||||||
|
id="svg8"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 150 150"
|
||||||
|
height="150"
|
||||||
|
width="150">
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:window-y="25"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-height="775"
|
||||||
|
inkscape:window-width="1280"
|
||||||
|
inkscape:guide-bbox="true"
|
||||||
|
showguides="true"
|
||||||
|
inkscape:pagecheckerboard="true"
|
||||||
|
inkscape:showpageshadow="false"
|
||||||
|
showborder="true"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:document-rotation="0"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:cy="79.075594"
|
||||||
|
inkscape:cx="91.835957"
|
||||||
|
inkscape:zoom="3.1892627"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
borderopacity="1.0"
|
||||||
|
bordercolor="#666666"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
id="base"
|
||||||
|
units="px">
|
||||||
|
<sodipodi:guide
|
||||||
|
inkscape:color="rgb(0,0,255)"
|
||||||
|
inkscape:locked="false"
|
||||||
|
inkscape:label=""
|
||||||
|
id="guide1919"
|
||||||
|
orientation="0,1"
|
||||||
|
position="0,115" />
|
||||||
|
<sodipodi:guide
|
||||||
|
inkscape:color="rgb(0,0,255)"
|
||||||
|
inkscape:locked="false"
|
||||||
|
inkscape:label=""
|
||||||
|
id="guide1921"
|
||||||
|
orientation="-1,0"
|
||||||
|
position="25,150" />
|
||||||
|
<sodipodi:guide
|
||||||
|
inkscape:color="rgb(0,0,255)"
|
||||||
|
inkscape:locked="false"
|
||||||
|
inkscape:label=""
|
||||||
|
id="guide1923"
|
||||||
|
orientation="-1,0"
|
||||||
|
position="125,150" />
|
||||||
|
<sodipodi:guide
|
||||||
|
inkscape:color="rgb(0,0,255)"
|
||||||
|
inkscape:locked="false"
|
||||||
|
inkscape:label=""
|
||||||
|
id="guide1925"
|
||||||
|
orientation="0,1"
|
||||||
|
position="0,35" />
|
||||||
|
<sodipodi:guide
|
||||||
|
inkscape:color="rgb(0,0,255)"
|
||||||
|
inkscape:locked="false"
|
||||||
|
inkscape:label=""
|
||||||
|
id="guide2879"
|
||||||
|
orientation="-1,0"
|
||||||
|
position="75,150" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<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
|
||||||
|
id="layer1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
inkscape:label="Livello 1">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
d="M 105.30102,35.000001 H 75.621261 c -0.280511,0 -0.507245,0.226946 -0.507245,0.507031 v 9.424762 c 0,0.317185 0.256831,0.574219 0.574223,0.574219 h 4.069258 c 1.645878,0 2.980541,1.265976 3.123457,2.874741 v 6.461712 3.599981 34.288687 0.24297 h -0.0011 c 0.01009,0.359209 -0.004,0.712449 -0.03903,1.060352 -0.004,0.05075 -0.01078,0.100887 -0.01687,0.151252 -0.02346,0.203299 -0.0495,0.405497 -0.08894,0.603445 -0.02162,0.112852 -0.05205,0.222685 -0.07913,0.333824 -0.03647,0.147809 -0.06591,0.297986 -0.111322,0.442618 -0.268348,0.858984 -0.692608,1.655686 -1.279419,2.357038 -0.02162,0.02501 -0.04819,0.04802 -0.06953,0.07273 -0.178971,0.207343 -0.366891,0.409334 -0.574866,0.598539 -0.209904,0.190918 -0.43216,0.368381 -0.66573,0.533266 -1.660381,1.176182 -3.8952,1.693662 -6.392174,1.454752 -3.18382,-0.29928 -6.34247,-1.743142 -8.895544,-4.066054 -2.552647,-2.322703 -4.139864,-5.196589 -4.468356,-8.093086 -0.295423,-2.591254 0.481005,-4.871506 2.18469,-6.42161 0.0047,-0.0033 0.0087,-0.0064 0.012,-0.01009 0.06591,-0.05909 0.138435,-0.110939 0.206261,-0.167895 1.219262,-1.021531 2.80861,-1.640544 4.622361,-1.811191 0.02038,-0.0019 0.0401,-0.0052 0.05953,-0.0068 0.190917,-0.01687 0.385445,-0.02438 0.581689,-0.03177 0.102591,-0.0033 0.203915,-0.01078 0.308019,-0.0113 0.03454,-4.8e-4 0.06739,-0.004 0.101982,-0.004 0.06186,0 0.126053,0.008 0.187922,0.0089 0.285821,0.0047 0.573797,0.01635 0.867522,0.04137 0.03711,0.003 0.07252,0.0017 0.110695,0.0052 0.02717,0.0028 0.05397,0.0087 0.08108,0.0113 1.663794,0.166817 3.311164,0.663596 4.874493,1.416358 0.03263,0.0015 0.07038,0.0113 0.117548,0.03755 0.476528,0.263222 0.567822,-0.01915 0.581901,-0.205828 V 73.11952 67.086773 c 0,-0.406353 -0.282642,-0.765776 -0.678957,-0.853016 -8.420512,-1.853212 -16.682323,0.07574 -22.512002,5.382591 -5.092707,4.632602 -7.556192,11.317645 -6.757997,18.339929 0.712234,6.254803 3.92293,12.263883 9.041661,16.922293 4.989039,4.53896 11.341325,7.37744 17.893905,7.99477 0.902929,0.0836 1.810125,0.12666 2.69535,0.12666 6.258001,0 12.064861,-2.14583 16.350841,-6.04467 4.060086,-3.69661 6.492006,-8.77269 6.848866,-14.294994 l 0.0331,-36.217627 h 0.004 v -9.683497 h 10e-4 v -0.16894 c 0.0367,-1.709012 1.43023,-3.083992 3.148627,-3.083992 h 4.06926 c 0.31697,0 0.57422,-0.257047 0.57422,-0.574221 v -9.424867 c -4e-5,-0.28008 -0.22679,-0.507031 -0.50728,-0.507031"
|
||||||
|
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133334;stop-opacity:1"
|
||||||
|
id="path30"
|
||||||
|
inkscape:transform-center-x="-30.453831"
|
||||||
|
inkscape:transform-center-y="40.048222" />
|
||||||
|
</g>
|
||||||
|
<inkscape:templateinfo>
|
||||||
|
<inkscape:name>Square150x150Logo</inkscape:name>
|
||||||
|
<inkscape:shortdesc>Medium tile</inkscape:shortdesc>
|
||||||
|
<inkscape:date>2020-04-15</inkscape:date>
|
||||||
|
</inkscape:templateinfo>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 820 B |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 63 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 63 KiB |
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 64 KiB |
59
BUILD.md
@@ -22,19 +22,6 @@ Then you can test the various applications:
|
|||||||
cd ElectronClient
|
cd ElectronClient
|
||||||
npm start
|
npm start
|
||||||
|
|
||||||
If you'd like to auto-reload the app on changes rather than having to quit and restart it manually each time, you can use [watchman-make](https://facebook.github.io/watchman/docs/watchman-make.html):
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd ElectronClient
|
|
||||||
watchman-make -p '**/*.js' '**/*.jsx' --run "npm start"
|
|
||||||
```
|
|
||||||
|
|
||||||
It still requires you to quit the application each time you want it to rebuild, but at least you don't have to re-run `"npm start"` each time. Here's what the workflow loop looks like in practice:
|
|
||||||
|
|
||||||
1. Edit and save files in your text editor.
|
|
||||||
2. Switch to the Electron app and <kbd>cmd</kbd>+<kbd>Q</kbd> to quit it.
|
|
||||||
3. `watchman` immediately restarts the app for you (whereas usually you'd have to switch back to the terminal, type `"npm start"`, and hit enter).
|
|
||||||
|
|
||||||
## Testing the Terminal application
|
## Testing the Terminal application
|
||||||
|
|
||||||
cd CliClient
|
cd CliClient
|
||||||
@@ -47,12 +34,12 @@ First you need to setup React Native to build projects with native code. For thi
|
|||||||
Then:
|
Then:
|
||||||
|
|
||||||
cd ReactNativeClient
|
cd ReactNativeClient
|
||||||
npm start-android
|
npm run start-android
|
||||||
# Or: npm start-ios
|
# Or: npm run start-ios
|
||||||
|
|
||||||
To run the iOS application, it might be easier to open the file `ios/Joplin.xcworkspace` on XCode and run the app from there.
|
To run the iOS application, it might be easier to open the file `ios/Joplin.xcworkspace` on XCode and run the app from there.
|
||||||
|
|
||||||
Normally the bundler should start automatically with the application. If it doesn't run `npm start`.
|
Normally the bundler should start automatically with the application. If it doesn't, run `npm start`.
|
||||||
|
|
||||||
## Building the clipper
|
## Building the clipper
|
||||||
|
|
||||||
@@ -80,37 +67,21 @@ You can specify additional parameters when running the desktop or CLI applicatio
|
|||||||
|
|
||||||
Most of the application is written in JavaScript, however new classes and files should generally be written in [TypeScript](https://www.typescriptlang.org/). All TypeScript files are generated next to the .ts or .tsx file. So for example, if there's a file "lib/MyClass.ts", there will be a generated "lib/MyClass.js" next to it. It is implemented that way as it requires minimal changes to integrate TypeScript in the existing JavaScript code base.
|
Most of the application is written in JavaScript, however new classes and files should generally be written in [TypeScript](https://www.typescriptlang.org/). All TypeScript files are generated next to the .ts or .tsx file. So for example, if there's a file "lib/MyClass.ts", there will be a generated "lib/MyClass.js" next to it. It is implemented that way as it requires minimal changes to integrate TypeScript in the existing JavaScript code base.
|
||||||
|
|
||||||
# Troubleshooting desktop application
|
## Hot reload
|
||||||
|
|
||||||
## On Linux and macOS
|
If you'd like to auto-reload the desktop app on changes rather than having to quit and restart it manually each time, you can use [watchman-make](https://facebook.github.io/watchman/docs/watchman-make.html):
|
||||||
|
|
||||||
If there's an error `while loading shared libraries: libgconf-2.so.4: cannot open shared object file: No such file or directory`, run `sudo apt-get install libgconf-2-4`
|
```sh
|
||||||
|
cd ElectronClient
|
||||||
|
watchman-make -p '**/*.js' '**/*.jsx' --run "npm start"
|
||||||
|
```
|
||||||
|
|
||||||
If you get a node-gyp related error, you might need to manually install it: `npm install -g node-gyp`.
|
It still requires you to quit the application each time you want it to rebuild, but at least you don't have to re-run `"npm start"` each time. Here's what the workflow loop looks like in practice:
|
||||||
|
|
||||||
If you get the error `libtool: unrecognized option '-static'`, follow the instructions [in this post](https://stackoverflow.com/a/38552393/561309) to use the correct libtool version.
|
1. Edit and save files in your text editor.
|
||||||
|
2. Switch to the Electron app and <kbd>cmd</kbd>+<kbd>Q</kbd> to quit it.
|
||||||
|
3. `watchman` immediately restarts the app for you (whereas usually you'd have to switch back to the terminal, type `"npm start"`, and hit enter).
|
||||||
|
|
||||||
## On Windows
|
# Troubleshooting
|
||||||
|
|
||||||
If node-gyp does not work (MSBUILD: error MSB3428: Could not load the Visual C++ component "VCBuild.exe"), you might need to install `windows-build-tools` using `npm install --global windows-build-tools`.
|
Please read for the [Build Troubleshooting Document](https://github.com/laurent22/joplin/blob/master/readme/build_troubleshooting.md) for various tips on how to get the build working.
|
||||||
|
|
||||||
If `yarn dist` fails, it may need administrative rights.
|
|
||||||
|
|
||||||
If you get an `error MSB8020: The build tools for v140 cannot be found.` try to run with a different toolset version, eg `npm install --toolset=v141` (See [here](https://github.com/mapbox/node-sqlite3/issues/1124) for more info).
|
|
||||||
|
|
||||||
## Other issues
|
|
||||||
|
|
||||||
> The application window doesn't open or is white
|
|
||||||
|
|
||||||
This is an indication that there's an early initialisation error. Try this:
|
|
||||||
|
|
||||||
- In ElectronAppWrapper, set `debugEarlyBugs` to `true`. This will force the window to show up and should open the console next to it, which should display any error.
|
|
||||||
- In more rare cases, an already open instance of Joplin can create strange low-level bugs that will display no error but will result in this white window. A non-dev instance of Joplin, or a dev instance that wasn't properly closed might cause this. So make sure you close everything and try again. Perhaps even other Electron apps running (Skype, Slack, etc.) could cause this?
|
|
||||||
- Also try to delete node_modules and rebuild.
|
|
||||||
- If all else fails, switch your computer off and on again, to make sure you start clean.
|
|
||||||
|
|
||||||
> How to work on the app from Windows?
|
|
||||||
|
|
||||||
**You should not use WSL at all** because this is a GUI app that lives outside of WSL, and the WSL layer can cause all kind of very hard to debug issues. It can also lock files in node_modules that cannot be unlocked when the app crashes. (You need to restart your computer.) Likewise, don't run the TypeScript watch command from WSL.
|
|
||||||
|
|
||||||
So everything should be done from a Windows Command prompt or Windows PowerShell running as Administrator. All build and start commands are designed to work cross-platform, including on Windows.
|
|
@@ -23,13 +23,11 @@ Avoid listing multiple requests in one topic. One topic per request makes it eas
|
|||||||
|
|
||||||
Finally, when submitting a pull request, don't forget to [test your code](#unit-tests).
|
Finally, when submitting a pull request, don't forget to [test your code](#unit-tests).
|
||||||
|
|
||||||
# Contribute to the project
|
# Contributing to Joplin's translation
|
||||||
|
|
||||||
## Contributing to Joplin's translation
|
|
||||||
|
|
||||||
Joplin is available in multiple languages thanks to the help of its users. You can help translate Joplin to your language or keep it up to date. Please read the documentation about [Localisation](https://joplinapp.org/#localisation).
|
Joplin is available in multiple languages thanks to the help of its users. You can help translate Joplin to your language or keep it up to date. Please read the documentation about [Localisation](https://joplinapp.org/#localisation).
|
||||||
|
|
||||||
## Contributing to Joplin's code
|
# Contributing to Joplin's code
|
||||||
|
|
||||||
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:
|
||||||
|
|
||||||
@@ -41,7 +39,7 @@ If you want to start contributing to the project's code, please follow these gui
|
|||||||
|
|
||||||
Building the apps is relatively easy - please [see the build instructions](https://github.com/laurent22/joplin/blob/master/BUILD.md) for more details.
|
Building the apps is relatively easy - please [see the build instructions](https://github.com/laurent22/joplin/blob/master/BUILD.md) for more details.
|
||||||
|
|
||||||
### Coding style
|
## Coding style
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
@@ -49,44 +47,45 @@ For new React components, please use [React Hooks](https://reactjs.org/docs/hook
|
|||||||
|
|
||||||
For changes made to the Desktop client that affect the user interface, refer to `ElectronClient/app/theme.js` for all styling information. The goal is to create a consistent user interface to allow for easy navigation of Joplin's various features and improve the overall user experience.
|
For changes made to the Desktop client that affect the user interface, refer to `ElectronClient/app/theme.js` for all styling information. The goal is to create a consistent user interface to allow for easy navigation of Joplin's various features and improve the overall user experience.
|
||||||
|
|
||||||
### Unit tests
|
## Automated tests
|
||||||
|
|
||||||
When submitting a pull request for a new feature or bug fix, please add unit tests for your code. Unit testing GUI changes is not always possible so it is not required, but any change in a file under /lib for example should be unit tested.
|
When submitting a pull request for a new feature or bug fixes, please add automated tests for your code whenever possible. Tests in Joplin are divided in **unit tests** and **feature tests**.
|
||||||
|
|
||||||
|
* **Unit tests** are used to tests models, services or utility classes - they are relatively low level. Unit tests should be prefixed with the type of class that is being tested - for example "models_Folder" or "services_SearchEngine".
|
||||||
|
|
||||||
|
* **Feature tests** on the other hand are to test higher level functionalities such as interactions with the GUI and how they affect the underlying model. Often these tests would dispatch Redux actions, and inspect how the application state has been changed. The feature tests should be prefixed with "feature_", for example "feature_TagList". There's a good explanation on what qualifies as a feature test in [this post](https://github.com/laurent22/joplin/pull/2819#issuecomment-603502230).
|
||||||
|
|
||||||
The tests are under CliClient/tests. To get them running, you first need to build the CLI app:
|
The tests are under CliClient/tests. To get them running, you first need to build the CLI app:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm run tsc # Build the .ts and .tsx files
|
|
||||||
cd CliClient
|
|
||||||
npm install
|
npm install
|
||||||
|
cd CliClient
|
||||||
```
|
```
|
||||||
|
|
||||||
To run all the test units:
|
To run all the test units:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm run test
|
npm test
|
||||||
```
|
```
|
||||||
|
|
||||||
To run just one particular file:
|
To run just one particular file:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm run test -- --filter=markdownUtils # Don't add the .js extension
|
npm test -- --filter=markdownUtils # Don't add the .js extension
|
||||||
```
|
```
|
||||||
|
|
||||||
To filter tests. For example, to run all the test units that contain "should handle conflict" in their description:
|
To filter tests. For example, to run all the test units that contain "should handle conflict" in their description:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm run test -- --filter="should handle conflict"
|
npm test -- --filter="should handle conflict"
|
||||||
```
|
```
|
||||||
|
|
||||||
If you get the error `Cannot find module '/joplin/CliClient/node_modules/sqlite3/lib/binding/node-v79-darwin-x64/node_sqlite3.node'`, you may need to run `npm rebuild`.
|
|
||||||
|
|
||||||
## About abandoned pull requests
|
## About abandoned pull requests
|
||||||
|
|
||||||
It happens that a pull request is started but not finished and despite our attempts to contact the contributor, we don’t hear from them again.
|
It happens that a pull request is started but not finished and despite our attempts to contact the contributor, we don't hear from them again.
|
||||||
|
|
||||||
In that case we will not merge the pull request, even if only small changes are missing. Our policy is simply to close the pull request. Why? Because an unfinished pull request essentially means giving us work and moving on. We would rather not encourage this behaviour.
|
In that case we will not merge the pull request, even if only small changes are missing. Our policy is simply to close the pull request. Why? Because an unfinished pull request essentially means giving us work and moving on. We would rather not encourage this behaviour.
|
||||||
|
|
||||||
Also, please note that since we have spent time reviewing the pull request and proposing solutions, we reserve the right to re-use that knowledge to create a new pull request, potentially based on your changes.
|
Also, please note that since we have spent time reviewing the pull request and proposing solutions, we reserve the right to re-use that knowledge to create a new pull request, potentially based on your changes.
|
||||||
|
|
||||||
We’d much prefer that you complete the pull request though, so we’ll be sure to ping you a few times before that!
|
We'd much prefer that you complete the pull request though, so we'll be sure to ping you a few times before that!
|
||||||
|
@@ -5,6 +5,7 @@ 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 Setting = require('lib/models/Setting.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');
|
||||||
@@ -134,7 +135,7 @@ class AppGui {
|
|||||||
const item = folderList.currentItem;
|
const item = folderList.currentItem;
|
||||||
|
|
||||||
if (item === '-') {
|
if (item === '-') {
|
||||||
let newIndex = event.currentIndex + (event.previousIndex < event.currentIndex ? +1 : -1);
|
const newIndex = event.currentIndex + (event.previousIndex < event.currentIndex ? +1 : -1);
|
||||||
let nextItem = folderList.itemAt(newIndex);
|
let nextItem = folderList.itemAt(newIndex);
|
||||||
if (!nextItem) nextItem = folderList.itemAt(event.previousIndex);
|
if (!nextItem) nextItem = folderList.itemAt(event.previousIndex);
|
||||||
|
|
||||||
@@ -186,7 +187,7 @@ class AppGui {
|
|||||||
borderRightWidth: 1,
|
borderRightWidth: 1,
|
||||||
};
|
};
|
||||||
noteList.on('currentItemChange', async () => {
|
noteList.on('currentItemChange', async () => {
|
||||||
let note = noteList.currentItem;
|
const note = noteList.currentItem;
|
||||||
this.store_.dispatch({
|
this.store_.dispatch({
|
||||||
type: 'NOTE_SELECT',
|
type: 'NOTE_SELECT',
|
||||||
id: note ? note.id : null,
|
id: note ? note.id : null,
|
||||||
@@ -243,9 +244,9 @@ class AppGui {
|
|||||||
|
|
||||||
const hLayout = new HLayoutWidget();
|
const hLayout = new HLayoutWidget();
|
||||||
hLayout.name = 'hLayout';
|
hLayout.name = 'hLayout';
|
||||||
hLayout.addChild(folderList, { type: 'stretch', factor: 1 });
|
hLayout.addChild(folderList, { type: 'stretch', factor: Setting.value('layout.folderList.factor') });
|
||||||
hLayout.addChild(noteList, { type: 'stretch', factor: 1 });
|
hLayout.addChild(noteList, { type: 'stretch', factor: Setting.value('layout.noteList.factor') });
|
||||||
hLayout.addChild(noteLayout, { type: 'stretch', factor: 2 });
|
hLayout.addChild(noteLayout, { type: 'stretch', factor: Setting.value('layout.note.factor') });
|
||||||
|
|
||||||
const vLayout = new VLayoutWidget();
|
const vLayout = new VLayoutWidget();
|
||||||
vLayout.name = 'vLayout';
|
vLayout.name = 'vLayout';
|
||||||
@@ -338,7 +339,7 @@ class AppGui {
|
|||||||
|
|
||||||
if (consoleWidget.isMaximized__ === doMaximize) return;
|
if (consoleWidget.isMaximized__ === doMaximize) return;
|
||||||
|
|
||||||
let constraints = {
|
const constraints = {
|
||||||
type: 'stretch',
|
type: 'stretch',
|
||||||
factor: !doMaximize ? 1 : 4,
|
factor: !doMaximize ? 1 : 4,
|
||||||
};
|
};
|
||||||
@@ -415,10 +416,10 @@ class AppGui {
|
|||||||
async handleModelAction(action) {
|
async handleModelAction(action) {
|
||||||
this.logger().info('Action:', action);
|
this.logger().info('Action:', action);
|
||||||
|
|
||||||
let state = Object.assign({}, defaultState);
|
const state = Object.assign({}, defaultState);
|
||||||
state.notes = this.widget('noteList').items;
|
state.notes = this.widget('noteList').items;
|
||||||
|
|
||||||
let newState = reducer(state, action);
|
const newState = reducer(state, action);
|
||||||
|
|
||||||
if (newState !== state) {
|
if (newState !== state) {
|
||||||
this.widget('noteList').items = newState.notes;
|
this.widget('noteList').items = newState.notes;
|
||||||
@@ -485,9 +486,9 @@ class AppGui {
|
|||||||
// this.logger().debug('Got command: ' + cmd);
|
// this.logger().debug('Got command: ' + cmd);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let note = this.widget('noteList').currentItem;
|
const note = this.widget('noteList').currentItem;
|
||||||
let folder = this.widget('folderList').currentItem;
|
const folder = this.widget('folderList').currentItem;
|
||||||
let args = splitCommandString(cmd);
|
const args = splitCommandString(cmd);
|
||||||
|
|
||||||
for (let i = 0; i < args.length; i++) {
|
for (let i = 0; i < args.length; i++) {
|
||||||
if (args[i] == '$n') {
|
if (args[i] == '$n') {
|
||||||
@@ -548,7 +549,7 @@ class AppGui {
|
|||||||
stdout(text) {
|
stdout(text) {
|
||||||
if (text === null || text === undefined) return;
|
if (text === null || text === undefined) return;
|
||||||
|
|
||||||
let lines = text.split('\n');
|
const lines = text.split('\n');
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
const v = typeof lines[i] === 'object' ? JSON.stringify(lines[i]) : lines[i];
|
const v = typeof lines[i] === 'object' ? JSON.stringify(lines[i]) : lines[i];
|
||||||
this.widget('console').addLine(v);
|
this.widget('console').addLine(v);
|
||||||
@@ -626,7 +627,7 @@ class AppGui {
|
|||||||
|
|
||||||
if (link.type === 'item') {
|
if (link.type === 'item') {
|
||||||
const itemId = link.id;
|
const itemId = link.id;
|
||||||
let item = await BaseItem.loadItemById(itemId);
|
const 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) {
|
||||||
@@ -750,7 +751,7 @@ class AppGui {
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
const shortcutKey = this.currentShortcutKeys_.join('');
|
const shortcutKey = this.currentShortcutKeys_.join('');
|
||||||
let keymapItem = this.keymapItemByKey(shortcutKey);
|
const keymapItem = this.keymapItemByKey(shortcutKey);
|
||||||
|
|
||||||
// If this command is an alias to another command, resolve to the actual command
|
// If this command is an alias to another command, resolve to the actual command
|
||||||
|
|
||||||
@@ -766,7 +767,7 @@ class AppGui {
|
|||||||
if (keymapItem.type === 'function') {
|
if (keymapItem.type === 'function') {
|
||||||
this.processFunctionCommand(keymapItem.command);
|
this.processFunctionCommand(keymapItem.command);
|
||||||
} else if (keymapItem.type === 'prompt') {
|
} else if (keymapItem.type === 'prompt') {
|
||||||
let promptOptions = {};
|
const promptOptions = {};
|
||||||
if ('cursorPosition' in keymapItem) promptOptions.cursorPosition = keymapItem.cursorPosition;
|
if ('cursorPosition' in keymapItem) promptOptions.cursorPosition = keymapItem.cursorPosition;
|
||||||
const commandString = await statusBar.prompt(keymapItem.command ? keymapItem.command : '', null, promptOptions);
|
const commandString = await statusBar.prompt(keymapItem.command ? keymapItem.command : '', null, promptOptions);
|
||||||
this.addCommandToConsole(commandString);
|
this.addCommandToConsole(commandString);
|
||||||
|
@@ -47,7 +47,7 @@ class Application extends BaseApplication {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async loadItem(type, pattern, options = null) {
|
async loadItem(type, pattern, options = null) {
|
||||||
let output = await this.loadItems(type, pattern, options);
|
const output = await this.loadItems(type, pattern, options);
|
||||||
|
|
||||||
if (output.length > 1) {
|
if (output.length > 1) {
|
||||||
// output.sort((a, b) => { return a.user_updated_time < b.user_updated_time ? +1 : -1; });
|
// output.sort((a, b) => { return a.user_updated_time < b.user_updated_time ? +1 : -1; });
|
||||||
@@ -144,7 +144,7 @@ class Application extends BaseApplication {
|
|||||||
if (options.type === 'boolean') {
|
if (options.type === 'boolean') {
|
||||||
if (answer === null) return false; // Pressed ESCAPE
|
if (answer === null) return false; // Pressed ESCAPE
|
||||||
if (!answer) answer = options.answers[0];
|
if (!answer) answer = options.answers[0];
|
||||||
let positiveIndex = options.booleanAnswerDefault == 'y' ? 0 : 1;
|
const positiveIndex = options.booleanAnswerDefault == 'y' ? 0 : 1;
|
||||||
return answer.toLowerCase() === options.answers[positiveIndex].toLowerCase();
|
return answer.toLowerCase() === options.answers[positiveIndex].toLowerCase();
|
||||||
} else {
|
} else {
|
||||||
return answer;
|
return answer;
|
||||||
@@ -181,7 +181,7 @@ class Application extends BaseApplication {
|
|||||||
const ext = fileExtension(path);
|
const ext = fileExtension(path);
|
||||||
if (ext != 'js') return;
|
if (ext != 'js') return;
|
||||||
|
|
||||||
let CommandClass = require(`./${path}`);
|
const 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);
|
||||||
@@ -192,8 +192,8 @@ class Application extends BaseApplication {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (uiType !== null) {
|
if (uiType !== null) {
|
||||||
let temp = [];
|
const temp = [];
|
||||||
for (let n in this.commands_) {
|
for (const n in this.commands_) {
|
||||||
if (!this.commands_.hasOwnProperty(n)) continue;
|
if (!this.commands_.hasOwnProperty(n)) continue;
|
||||||
const c = this.commands_[n];
|
const c = this.commands_[n];
|
||||||
if (!c.supportsUi(uiType)) continue;
|
if (!c.supportsUi(uiType)) continue;
|
||||||
@@ -207,8 +207,8 @@ class Application extends BaseApplication {
|
|||||||
|
|
||||||
async commandNames() {
|
async commandNames() {
|
||||||
const metadata = await this.commandMetadata();
|
const metadata = await this.commandMetadata();
|
||||||
let output = [];
|
const output = [];
|
||||||
for (let n in metadata) {
|
for (const n in metadata) {
|
||||||
if (!metadata.hasOwnProperty(n)) continue;
|
if (!metadata.hasOwnProperty(n)) continue;
|
||||||
output.push(n);
|
output.push(n);
|
||||||
}
|
}
|
||||||
@@ -227,7 +227,7 @@ class Application extends BaseApplication {
|
|||||||
const commands = this.commands();
|
const commands = this.commands();
|
||||||
|
|
||||||
output = {};
|
output = {};
|
||||||
for (let n in commands) {
|
for (const n in commands) {
|
||||||
if (!commands.hasOwnProperty(n)) continue;
|
if (!commands.hasOwnProperty(n)) continue;
|
||||||
const cmd = commands[n];
|
const cmd = commands[n];
|
||||||
output[n] = cmd.metadata();
|
output[n] = cmd.metadata();
|
||||||
@@ -251,7 +251,7 @@ class Application extends BaseApplication {
|
|||||||
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));
|
const e = new Error(_('No such command: %s', name));
|
||||||
e.type = 'notFound';
|
e.type = 'notFound';
|
||||||
throw e;
|
throw e;
|
||||||
} else {
|
} else {
|
||||||
@@ -362,7 +362,7 @@ class Application extends BaseApplication {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const output = [];
|
const output = [];
|
||||||
for (let n in itemsByCommand) {
|
for (const n in itemsByCommand) {
|
||||||
if (!itemsByCommand.hasOwnProperty(n)) continue;
|
if (!itemsByCommand.hasOwnProperty(n)) continue;
|
||||||
output.push(itemsByCommand[n]);
|
output.push(itemsByCommand[n]);
|
||||||
}
|
}
|
||||||
|
@@ -1,20 +1,20 @@
|
|||||||
var { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
var Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
var Folder = require('lib/models/Folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
var Tag = require('lib/models/Tag.js');
|
const Tag = require('lib/models/Tag.js');
|
||||||
var { cliUtils } = require('./cli-utils.js');
|
const { cliUtils } = require('./cli-utils.js');
|
||||||
var yargParser = require('yargs-parser');
|
const yargParser = require('yargs-parser');
|
||||||
var fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
|
|
||||||
async function handleAutocompletionPromise(line) {
|
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);
|
const 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 commands 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);
|
const x = names.filter(n => n.indexOf(words[0]) === 0);
|
||||||
if (x.length === 1) {
|
if (x.length === 1) {
|
||||||
return `${x[0]} `;
|
return `${x[0]} `;
|
||||||
}
|
}
|
||||||
@@ -36,8 +36,8 @@ async function handleAutocompletionPromise(line) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// complete an option
|
// complete an option
|
||||||
let next = words.length > 1 ? words[words.length - 1] : '';
|
const next = words.length > 1 ? words[words.length - 1] : '';
|
||||||
let l = [];
|
const 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(' ');
|
||||||
@@ -60,7 +60,7 @@ async function handleAutocompletionPromise(line) {
|
|||||||
if (l.length === 0) {
|
if (l.length === 0) {
|
||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
let ret = l.map(a => toCommandLine(a));
|
const ret = l.map(a => toCommandLine(a));
|
||||||
ret.prefix = `${toCommandLine(words.slice(0, -1))} `;
|
ret.prefix = `${toCommandLine(words.slice(0, -1))} `;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -69,7 +69,7 @@ async function handleAutocompletionPromise(line) {
|
|||||||
// 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)['_'];
|
const cmdUsage = yargParser(metadata.usage)['_'];
|
||||||
cmdUsage.splice(0, 1);
|
cmdUsage.splice(0, 1);
|
||||||
|
|
||||||
if (cmdUsage.length >= positionalArgs) {
|
if (cmdUsage.length >= positionalArgs) {
|
||||||
@@ -95,29 +95,29 @@ async function handleAutocompletionPromise(line) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (argName == 'tag') {
|
if (argName == 'tag') {
|
||||||
let tags = await Tag.search({ titlePattern: `${next}*` });
|
const 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') {
|
||||||
let files = await fs.readdir('.');
|
const files = await fs.readdir('.');
|
||||||
l.push(...files);
|
l.push(...files);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argName == 'tag-command') {
|
if (argName == 'tag-command') {
|
||||||
let c = filterList(['add', 'remove', 'list', 'notetags'], next);
|
const c = filterList(['add', 'remove', 'list', 'notetags'], next);
|
||||||
l.push(...c);
|
l.push(...c);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argName == 'todo-command') {
|
if (argName == 'todo-command') {
|
||||||
let c = filterList(['toggle', 'clear'], next);
|
const c = filterList(['toggle', 'clear'], next);
|
||||||
l.push(...c);
|
l.push(...c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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));
|
const ret = l.map(a => toCommandLine(a));
|
||||||
ret.prefix = `${toCommandLine(words.slice(0, -1))} `;
|
ret.prefix = `${toCommandLine(words.slice(0, -1))} `;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -155,7 +155,7 @@ function getArguments(line) {
|
|||||||
let inSingleQuotes = false;
|
let inSingleQuotes = false;
|
||||||
let inDoubleQuotes = false;
|
let inDoubleQuotes = false;
|
||||||
let currentWord = '';
|
let currentWord = '';
|
||||||
let parsed = [];
|
const 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) {
|
||||||
@@ -192,7 +192,7 @@ function getArguments(line) {
|
|||||||
return parsed;
|
return parsed;
|
||||||
}
|
}
|
||||||
function filterList(list, next) {
|
function filterList(list, next) {
|
||||||
let output = [];
|
const output = [];
|
||||||
for (let i = 0; i < list.length; i++) {
|
for (let i = 0; i < list.length; i++) {
|
||||||
if (list[i].indexOf(next) !== 0) continue;
|
if (list[i].indexOf(next) !== 0) continue;
|
||||||
output.push(list[i]);
|
output.push(list[i]);
|
||||||
|
@@ -50,7 +50,7 @@ class BaseCommand {
|
|||||||
async cancel() {}
|
async cancel() {}
|
||||||
|
|
||||||
name() {
|
name() {
|
||||||
let r = this.usage().split(' ');
|
const r = this.usage().split(' ');
|
||||||
return r[0];
|
return r[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -15,11 +15,11 @@ function wrap(text, indent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderOptions(options) {
|
function renderOptions(options) {
|
||||||
let output = [];
|
const output = [];
|
||||||
const optionColWidth = getOptionColWidth(options);
|
const optionColWidth = getOptionColWidth(options);
|
||||||
|
|
||||||
for (let i = 0; i < options.length; i++) {
|
for (let i = 0; i < options.length; i++) {
|
||||||
let option = options[i];
|
const 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);
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ function renderOptions(options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderCommand(cmd) {
|
function renderCommand(cmd) {
|
||||||
let output = [];
|
const output = [];
|
||||||
output.push(INDENT + cmd.usage());
|
output.push(INDENT + cmd.usage());
|
||||||
output.push('');
|
output.push('');
|
||||||
output.push(wrap(cmd.description(), INDENT + INDENT));
|
output.push(wrap(cmd.description(), INDENT + INDENT));
|
||||||
@@ -48,14 +48,14 @@ function renderCommand(cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getCommands() {
|
function getCommands() {
|
||||||
let output = [];
|
const 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}`);
|
const CommandClass = require(`./${path}`);
|
||||||
let cmd = new CommandClass();
|
const cmd = new CommandClass();
|
||||||
if (!cmd.enabled()) return;
|
if (!cmd.enabled()) return;
|
||||||
if (cmd.hidden()) return;
|
if (cmd.hidden()) return;
|
||||||
output.push(cmd);
|
output.push(cmd);
|
||||||
@@ -73,7 +73,7 @@ function getOptionColWidth(options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getHeader() {
|
function getHeader() {
|
||||||
let output = [];
|
const output = [];
|
||||||
|
|
||||||
output.push('NAME');
|
output.push('NAME');
|
||||||
output.push('');
|
output.push('');
|
||||||
@@ -84,7 +84,7 @@ function getHeader() {
|
|||||||
output.push('DESCRIPTION');
|
output.push('DESCRIPTION');
|
||||||
output.push('');
|
output.push('');
|
||||||
|
|
||||||
let description = [];
|
const 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');
|
||||||
@@ -98,7 +98,7 @@ function getHeader() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getFooter() {
|
function getFooter() {
|
||||||
let output = [];
|
const output = [];
|
||||||
|
|
||||||
output.push('WEBSITE');
|
output.push('WEBSITE');
|
||||||
output.push('');
|
output.push('');
|
||||||
@@ -120,10 +120,10 @@ async function main() {
|
|||||||
// setLocale('fr_FR');
|
// setLocale('fr_FR');
|
||||||
|
|
||||||
const commands = getCommands();
|
const commands = getCommands();
|
||||||
let commandBlocks = [];
|
const commandBlocks = [];
|
||||||
|
|
||||||
for (let i = 0; i < commands.length; i++) {
|
for (let i = 0; i < commands.length; i++) {
|
||||||
let cmd = commands[i];
|
const cmd = commands[i];
|
||||||
commandBlocks.push(renderCommand(cmd));
|
commandBlocks.push(renderCommand(cmd));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -40,8 +40,8 @@ function createClient(id) {
|
|||||||
const client = createClient(1);
|
const client = createClient(1);
|
||||||
|
|
||||||
function execCommand(client, command) {
|
function execCommand(client, command) {
|
||||||
let exePath = `node ${joplinAppPath}`;
|
const exePath = `node ${joplinAppPath}`;
|
||||||
let cmd = `${exePath} --update-geolocation-disabled --env dev --profile ${client.profileDir} ${command}`;
|
const 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) => {
|
||||||
@@ -129,8 +129,8 @@ testUnits.testCat = async () => {
|
|||||||
await execCommand(client, 'mkbook nb1');
|
await execCommand(client, 'mkbook nb1');
|
||||||
await execCommand(client, 'mknote mynote');
|
await execCommand(client, 'mknote mynote');
|
||||||
|
|
||||||
let folder = await Folder.loadByTitle('nb1');
|
const folder = await Folder.loadByTitle('nb1');
|
||||||
let note = await Note.loadFolderNoteByField(folder.id, 'title', 'mynote');
|
const note = await Note.loadFolderNoteByField(folder.id, 'title', 'mynote');
|
||||||
|
|
||||||
let r = await execCommand(client, 'cat mynote');
|
let r = await execCommand(client, 'cat mynote');
|
||||||
assertTrue(r.indexOf('mynote') >= 0);
|
assertTrue(r.indexOf('mynote') >= 0);
|
||||||
@@ -149,7 +149,7 @@ testUnits.testConfig = async () => {
|
|||||||
await Setting.load();
|
await Setting.load();
|
||||||
assertEquals('subl', Setting.value('editor'));
|
assertEquals('subl', Setting.value('editor'));
|
||||||
|
|
||||||
let r = await execCommand(client, 'config');
|
const 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);
|
||||||
};
|
};
|
||||||
@@ -161,14 +161,14 @@ testUnits.testCp = async () => {
|
|||||||
|
|
||||||
await execCommand(client, 'cp n1');
|
await execCommand(client, 'cp n1');
|
||||||
|
|
||||||
let f1 = await Folder.loadByTitle('nb1');
|
const f1 = await Folder.loadByTitle('nb1');
|
||||||
let f2 = await Folder.loadByTitle('nb2');
|
const f2 = await Folder.loadByTitle('nb2');
|
||||||
let notes = await Note.previews(f1.id);
|
let notes = await Note.previews(f1.id);
|
||||||
|
|
||||||
assertEquals(2, notes.length);
|
assertEquals(2, notes.length);
|
||||||
|
|
||||||
await execCommand(client, 'cp n1 nb2');
|
await execCommand(client, 'cp n1 nb2');
|
||||||
let notesF1 = await Note.previews(f1.id);
|
const notesF1 = await Note.previews(f1.id);
|
||||||
assertEquals(2, notesF1.length);
|
assertEquals(2, notesF1.length);
|
||||||
notes = await Note.previews(f2.id);
|
notes = await Note.previews(f2.id);
|
||||||
assertEquals(1, notes.length);
|
assertEquals(1, notes.length);
|
||||||
@@ -179,7 +179,7 @@ testUnits.testLs = async () => {
|
|||||||
await execCommand(client, 'mkbook nb1');
|
await execCommand(client, 'mkbook nb1');
|
||||||
await execCommand(client, 'mknote note1');
|
await execCommand(client, 'mknote note1');
|
||||||
await execCommand(client, 'mknote note2');
|
await execCommand(client, 'mknote note2');
|
||||||
let r = await execCommand(client, 'ls');
|
const r = await execCommand(client, 'ls');
|
||||||
|
|
||||||
assertTrue(r.indexOf('note1') >= 0);
|
assertTrue(r.indexOf('note1') >= 0);
|
||||||
assertTrue(r.indexOf('note2') >= 0);
|
assertTrue(r.indexOf('note2') >= 0);
|
||||||
@@ -191,8 +191,8 @@ testUnits.testMv = async () => {
|
|||||||
await execCommand(client, 'mknote n1');
|
await execCommand(client, 'mknote n1');
|
||||||
await execCommand(client, 'mv n1 nb2');
|
await execCommand(client, 'mv n1 nb2');
|
||||||
|
|
||||||
let f1 = await Folder.loadByTitle('nb1');
|
const f1 = await Folder.loadByTitle('nb1');
|
||||||
let f2 = await Folder.loadByTitle('nb2');
|
const f2 = await Folder.loadByTitle('nb2');
|
||||||
let notes1 = await Note.previews(f1.id);
|
let notes1 = await Note.previews(f1.id);
|
||||||
let notes2 = await Note.previews(f2.id);
|
let notes2 = await Note.previews(f2.id);
|
||||||
|
|
||||||
@@ -218,18 +218,18 @@ async function main() {
|
|||||||
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.setDb(db);
|
||||||
await Setting.load();
|
await Setting.load();
|
||||||
|
|
||||||
let onlyThisTest = 'testMv';
|
let onlyThisTest = 'testMv';
|
||||||
onlyThisTest = '';
|
onlyThisTest = '';
|
||||||
|
|
||||||
for (let n in testUnits) {
|
for (const n in testUnits) {
|
||||||
if (!testUnits.hasOwnProperty(n)) continue;
|
if (!testUnits.hasOwnProperty(n)) continue;
|
||||||
if (onlyThisTest && n != onlyThisTest) continue;
|
if (onlyThisTest && n != onlyThisTest) continue;
|
||||||
|
|
||||||
await clearDatabase();
|
await clearDatabase();
|
||||||
let testName = n.substr(4).toLowerCase();
|
const testName = n.substr(4).toLowerCase();
|
||||||
process.stdout.write(`${testName}: `);
|
process.stdout.write(`${testName}: `);
|
||||||
await testUnits[n]();
|
await testUnits[n]();
|
||||||
console.info('');
|
console.info('');
|
||||||
|
@@ -11,27 +11,27 @@ cliUtils.printArray = function(logFunction, rows) {
|
|||||||
const ALIGN_LEFT = 0;
|
const ALIGN_LEFT = 0;
|
||||||
const ALIGN_RIGHT = 1;
|
const ALIGN_RIGHT = 1;
|
||||||
|
|
||||||
let colWidths = [];
|
const colWidths = [];
|
||||||
let colAligns = [];
|
const colAligns = [];
|
||||||
|
|
||||||
for (let i = 0; i < rows.length; i++) {
|
for (let i = 0; i < rows.length; i++) {
|
||||||
let row = rows[i];
|
const row = rows[i];
|
||||||
|
|
||||||
for (let j = 0; j < row.length; j++) {
|
for (let j = 0; j < row.length; j++) {
|
||||||
let item = row[j];
|
const item = row[j];
|
||||||
let width = item ? item.toString().length : 0;
|
const width = item ? item.toString().length : 0;
|
||||||
let align = typeof item == 'number' ? ALIGN_RIGHT : ALIGN_LEFT;
|
const align = typeof item == 'number' ? ALIGN_RIGHT : ALIGN_LEFT;
|
||||||
if (!colWidths[j] || colWidths[j] < width) colWidths[j] = width;
|
if (!colWidths[j] || colWidths[j] < width) colWidths[j] = width;
|
||||||
if (colAligns.length <= j) colAligns[j] = align;
|
if (colAligns.length <= j) colAligns[j] = align;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let row = 0; row < rows.length; row++) {
|
for (let row = 0; row < rows.length; row++) {
|
||||||
let line = [];
|
const line = [];
|
||||||
for (let col = 0; col < colWidths.length; col++) {
|
for (let col = 0; col < colWidths.length; col++) {
|
||||||
let item = rows[row][col];
|
const item = rows[row][col];
|
||||||
let width = colWidths[col];
|
const width = colWidths[col];
|
||||||
let dir = colAligns[col] == ALIGN_LEFT ? stringPadding.RIGHT : stringPadding.LEFT;
|
const dir = colAligns[col] == ALIGN_LEFT ? stringPadding.RIGHT : stringPadding.LEFT;
|
||||||
line.push(stringPadding(item, width, ' ', dir));
|
line.push(stringPadding(item, width, ' ', dir));
|
||||||
}
|
}
|
||||||
logFunction(line.join(' '));
|
logFunction(line.join(' '));
|
||||||
@@ -39,7 +39,7 @@ cliUtils.printArray = function(logFunction, rows) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
cliUtils.parseFlags = function(flags) {
|
cliUtils.parseFlags = function(flags) {
|
||||||
let output = {};
|
const output = {};
|
||||||
flags = flags.split(',');
|
flags = flags.split(',');
|
||||||
for (let i = 0; i < flags.length; i++) {
|
for (let i = 0; i < flags.length; i++) {
|
||||||
let f = flags[i].trim();
|
let f = flags[i].trim();
|
||||||
@@ -76,11 +76,11 @@ cliUtils.parseCommandArg = function(arg) {
|
|||||||
cliUtils.makeCommandArgs = function(cmd, argv) {
|
cliUtils.makeCommandArgs = function(cmd, argv) {
|
||||||
let cmdUsage = cmd.usage();
|
let cmdUsage = cmd.usage();
|
||||||
cmdUsage = yargParser(cmdUsage);
|
cmdUsage = yargParser(cmdUsage);
|
||||||
let output = {};
|
const output = {};
|
||||||
|
|
||||||
let options = cmd.options();
|
const options = cmd.options();
|
||||||
let booleanFlags = [];
|
const booleanFlags = [];
|
||||||
let aliases = {};
|
const 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];
|
||||||
@@ -97,7 +97,7 @@ cliUtils.makeCommandArgs = function(cmd, argv) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let args = yargParser(argv, {
|
const args = yargParser(argv, {
|
||||||
boolean: booleanFlags,
|
boolean: booleanFlags,
|
||||||
alias: aliases,
|
alias: aliases,
|
||||||
string: ['_'],
|
string: ['_'],
|
||||||
@@ -113,8 +113,8 @@ cliUtils.makeCommandArgs = function(cmd, argv) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let argOptions = {};
|
const argOptions = {};
|
||||||
for (let key in args) {
|
for (const key in args) {
|
||||||
if (!args.hasOwnProperty(key)) continue;
|
if (!args.hasOwnProperty(key)) continue;
|
||||||
if (key == '_') continue;
|
if (key == '_') continue;
|
||||||
argOptions[key] = args[key];
|
argOptions[key] = args[key];
|
||||||
@@ -134,7 +134,7 @@ cliUtils.promptMcq = function(message, answers) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
message += '\n\n';
|
message += '\n\n';
|
||||||
for (let n in answers) {
|
for (const 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`;
|
||||||
}
|
}
|
||||||
|
@@ -15,6 +15,10 @@ class Command extends BaseCommand {
|
|||||||
return 'Build the API doc';
|
return 'Build the API doc';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enabled() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
createPropertiesTable(tableFields) {
|
createPropertiesTable(tableFields) {
|
||||||
const headers = [
|
const headers = [
|
||||||
{ name: 'name', label: 'Name' },
|
{ name: 'name', label: 'Name' },
|
||||||
@@ -52,7 +56,6 @@ class Command extends BaseCommand {
|
|||||||
lines.push('# Joplin API');
|
lines.push('# Joplin API');
|
||||||
lines.push('');
|
lines.push('');
|
||||||
|
|
||||||
lines.push('When the Web Clipper service is enabled, Joplin exposes a [REST API](https://en.wikipedia.org/wiki/Representational_state_transfer) which allows third-party applications to access Joplin\'s data and to create, modify or delete notes, notebooks, resources or tags.');
|
|
||||||
lines.push('');
|
lines.push('');
|
||||||
lines.push('In order to use it, you\'ll first need to find on which port the service is running. To do so, open the Web Clipper Options in Joplin and if the service is running it should tell you on which port. Normally it runs on port **41184**. If you want to find it programmatically, you may follow this kind of algorithm:');
|
lines.push('In order to use it, you\'ll first need to find on which port the service is running. To do so, open the Web Clipper Options in Joplin and if the service is running it should tell you on which port. Normally it runs on port **41184**. If you want to find it programmatically, you may follow this kind of algorithm:');
|
||||||
lines.push('');
|
lines.push('');
|
||||||
|
@@ -14,9 +14,9 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
let title = args['note'];
|
const title = args['note'];
|
||||||
|
|
||||||
let note = await app().loadItem(BaseModel.TYPE_NOTE, title, { parent: app().currentFolder() });
|
const note = await app().loadItem(BaseModel.TYPE_NOTE, title, { parent: app().currentFolder() });
|
||||||
this.encryptionCheck(note);
|
this.encryptionCheck(note);
|
||||||
if (!note) throw new Error(_('Cannot find "%s".', title));
|
if (!note) throw new Error(_('Cannot find "%s".', title));
|
||||||
|
|
||||||
|
@@ -18,9 +18,9 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
let title = args['note'];
|
const title = args['note'];
|
||||||
|
|
||||||
let item = await app().loadItem(BaseModel.TYPE_NOTE, title, { parent: app().currentFolder() });
|
const item = await app().loadItem(BaseModel.TYPE_NOTE, title, { parent: app().currentFolder() });
|
||||||
if (!item) throw new Error(_('Cannot find "%s".', title));
|
if (!item) throw new Error(_('Cannot find "%s".', title));
|
||||||
|
|
||||||
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);
|
||||||
|
@@ -35,7 +35,7 @@ class Command extends BaseCommand {
|
|||||||
});
|
});
|
||||||
|
|
||||||
inputStream.on('end', () => {
|
inputStream.on('end', () => {
|
||||||
let json = chunks.join('');
|
const json = chunks.join('');
|
||||||
let settingsObj;
|
let settingsObj;
|
||||||
try {
|
try {
|
||||||
settingsObj = JSON.parse(json);
|
settingsObj = JSON.parse(json);
|
||||||
@@ -83,7 +83,7 @@ class Command extends BaseCommand {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (isExport || (!isImport && !args.value)) {
|
if (isExport || (!isImport && !args.value)) {
|
||||||
let keys = Setting.keys(!verbose, 'cli');
|
const keys = Setting.keys(!verbose, 'cli');
|
||||||
keys.sort();
|
keys.sort();
|
||||||
|
|
||||||
if (isExport) {
|
if (isExport) {
|
||||||
|
@@ -18,15 +18,15 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
async action() {
|
async action() {
|
||||||
let items = [];
|
let items = [];
|
||||||
let folders = await Folder.all();
|
const folders = await Folder.all();
|
||||||
for (let i = 0; i < folders.length; i++) {
|
for (let i = 0; i < folders.length; i++) {
|
||||||
let folder = folders[i];
|
const folder = folders[i];
|
||||||
let notes = await Note.previews(folder.id);
|
const notes = await Note.previews(folder.id);
|
||||||
items.push(folder);
|
items.push(folder);
|
||||||
items = items.concat(notes);
|
items = items.concat(notes);
|
||||||
}
|
}
|
||||||
|
|
||||||
let tags = await Tag.all();
|
const tags = await Tag.all();
|
||||||
for (let i = 0; i < tags.length; i++) {
|
for (let i = 0; i < tags.length; i++) {
|
||||||
tags[i].notes_ = await Tag.noteIds(tags[i].id);
|
tags[i].notes_ = await Tag.noteIds(tags[i].id);
|
||||||
}
|
}
|
||||||
|
@@ -24,12 +24,11 @@ class Command extends BaseCommand {
|
|||||||
['-p, --password <password>', 'Use this password as master password (For security reasons, it is not recommended to use this option).'],
|
['-p, --password <password>', 'Use this password as master password (For security reasons, it is not recommended to use this option).'],
|
||||||
['-v, --verbose', 'More verbose output for the `target-status` command'],
|
['-v, --verbose', 'More verbose output for the `target-status` command'],
|
||||||
['-o, --output <directory>', 'Output directory'],
|
['-o, --output <directory>', 'Output directory'],
|
||||||
|
['--retry-failed-items', 'Applies to `decrypt` command - retries decrypting items that previously could not be decrypted.'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
// change-password
|
|
||||||
|
|
||||||
const options = args.options;
|
const options = args.options;
|
||||||
|
|
||||||
const askForMasterKey = async error => {
|
const askForMasterKey = async error => {
|
||||||
@@ -44,6 +43,27 @@ class Command extends BaseCommand {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const startDecryption = async () => {
|
||||||
|
this.stdout(_('Starting decryption... Please wait as it may take several minutes depending on how much there is to decrypt.'));
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
await DecryptionWorker.instance().start();
|
||||||
|
break;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code === 'masterKeyNotLoaded') {
|
||||||
|
const ok = await askForMasterKey(error);
|
||||||
|
if (!ok) return;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stdout(_('Completed decryption.'));
|
||||||
|
};
|
||||||
|
|
||||||
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 });
|
||||||
if (!password) {
|
if (!password) {
|
||||||
@@ -73,24 +93,8 @@ class Command extends BaseCommand {
|
|||||||
const plainText = await EncryptionService.instance().decryptString(args.path);
|
const plainText = await EncryptionService.instance().decryptString(args.path);
|
||||||
this.stdout(plainText);
|
this.stdout(plainText);
|
||||||
} else {
|
} else {
|
||||||
this.stdout(_('Starting decryption... Please wait as it may take several minutes depending on how much there is to decrypt.'));
|
if (args.options['retry-failed-items']) await DecryptionWorker.instance().clearDisabledItems();
|
||||||
|
await startDecryption();
|
||||||
while (true) {
|
|
||||||
try {
|
|
||||||
await DecryptionWorker.instance().start();
|
|
||||||
break;
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code === 'masterKeyNotLoaded') {
|
|
||||||
const ok = await askForMasterKey(error);
|
|
||||||
if (!ok) return;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.stdout(_('Completed decryption.'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -138,7 +142,7 @@ class Command extends BaseCommand {
|
|||||||
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 = [];
|
const paths = [];
|
||||||
fs.readdirSync(targetPath).forEach(path => {
|
fs.readdirSync(targetPath).forEach(path => {
|
||||||
paths.push(path);
|
paths.push(path);
|
||||||
});
|
});
|
||||||
@@ -151,10 +155,10 @@ class Command extends BaseCommand {
|
|||||||
let encryptedResourceCount = 0;
|
let encryptedResourceCount = 0;
|
||||||
let otherItemCount = 0;
|
let otherItemCount = 0;
|
||||||
|
|
||||||
let encryptedPaths = [];
|
const encryptedPaths = [];
|
||||||
let decryptedPaths = [];
|
const decryptedPaths = [];
|
||||||
|
|
||||||
let paths = dirPaths(targetPath);
|
const paths = dirPaths(targetPath);
|
||||||
|
|
||||||
for (let i = 0; i < paths.length; i++) {
|
for (let i = 0; i < paths.length; i++) {
|
||||||
const path = paths[i];
|
const path = paths[i];
|
||||||
@@ -164,7 +168,7 @@ class Command extends BaseCommand {
|
|||||||
// this.stdout(fullPath);
|
// this.stdout(fullPath);
|
||||||
|
|
||||||
if (path === '.resource') {
|
if (path === '.resource') {
|
||||||
let resourcePaths = dirPaths(fullPath);
|
const resourcePaths = dirPaths(fullPath);
|
||||||
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++;
|
||||||
|
@@ -35,7 +35,7 @@ class Command extends BaseCommand {
|
|||||||
// Load note or create it if it doesn't exist
|
// Load note or create it if it doesn't exist
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
let title = args['note'];
|
const title = args['note'];
|
||||||
|
|
||||||
if (!app().currentFolder()) throw new Error(_('No active notebook.'));
|
if (!app().currentFolder()) throw new Error(_('No active notebook.'));
|
||||||
let note = await app().loadItem(BaseModel.TYPE_NOTE, title);
|
let note = await app().loadItem(BaseModel.TYPE_NOTE, title);
|
||||||
@@ -91,7 +91,7 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
const updatedContent = await fs.readFile(tempFilePath, 'utf8');
|
const updatedContent = await fs.readFile(tempFilePath, 'utf8');
|
||||||
if (updatedContent !== originalContent) {
|
if (updatedContent !== originalContent) {
|
||||||
let updatedNote = await Note.unserializeForEdit(updatedContent);
|
const updatedNote = await Note.unserializeForEdit(updatedContent);
|
||||||
updatedNote.id = note.id;
|
updatedNote.id = note.id;
|
||||||
await Note.save(updatedNote);
|
await Note.save(updatedNote);
|
||||||
this.stdout(_('Note has been saved.'));
|
this.stdout(_('Note has been saved.'));
|
||||||
|
@@ -24,7 +24,7 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
let exportOptions = {};
|
const exportOptions = {};
|
||||||
exportOptions.path = args.path;
|
exportOptions.path = args.path;
|
||||||
|
|
||||||
exportOptions.format = args.options.format ? args.options.format : 'jex';
|
exportOptions.format = args.options.format ? args.options.format : 'jex';
|
||||||
|
@@ -14,9 +14,9 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
let title = args['note'];
|
const title = args['note'];
|
||||||
|
|
||||||
let item = await app().loadItem(BaseModel.TYPE_NOTE, title, { parent: app().currentFolder() });
|
const item = await app().loadItem(BaseModel.TYPE_NOTE, title, { parent: app().currentFolder() });
|
||||||
if (!item) throw new Error(_('Cannot find "%s".', title));
|
if (!item) throw new Error(_('Cannot find "%s".', title));
|
||||||
const url = Note.geolocationUrl(item);
|
const url = Note.geolocationUrl(item);
|
||||||
this.stdout(url);
|
this.stdout(url);
|
||||||
|
@@ -15,8 +15,8 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
allCommands() {
|
allCommands() {
|
||||||
const commands = app().commands(app().uiType());
|
const commands = app().commands(app().uiType());
|
||||||
let output = [];
|
const output = [];
|
||||||
for (let n in commands) {
|
for (const n in commands) {
|
||||||
if (!commands.hasOwnProperty(n)) continue;
|
if (!commands.hasOwnProperty(n)) continue;
|
||||||
const command = commands[n];
|
const command = commands[n];
|
||||||
if (command.hidden()) continue;
|
if (command.hidden()) continue;
|
||||||
@@ -48,7 +48,7 @@ class Command extends BaseCommand {
|
|||||||
.gui()
|
.gui()
|
||||||
.keymap();
|
.keymap();
|
||||||
|
|
||||||
let rows = [];
|
const 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];
|
||||||
|
@@ -25,7 +25,7 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
let folder = await app().loadItem(BaseModel.TYPE_FOLDER, args.notebook);
|
const folder = await app().loadItem(BaseModel.TYPE_FOLDER, args.notebook);
|
||||||
|
|
||||||
if (args.notebook && !folder) throw new Error(_('Cannot find "%s".', args.notebook));
|
if (args.notebook && !folder) throw new Error(_('Cannot find "%s".', args.notebook));
|
||||||
|
|
||||||
@@ -39,7 +39,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 = [];
|
const 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));
|
||||||
if (progressState.updated) line.push(_('Updated: %d.', progressState.updated));
|
if (progressState.updated) line.push(_('Updated: %d.', progressState.updated));
|
||||||
@@ -51,7 +51,7 @@ class Command extends BaseCommand {
|
|||||||
};
|
};
|
||||||
|
|
||||||
importOptions.onError = error => {
|
importOptions.onError = error => {
|
||||||
let s = error.trace ? error.trace : error.toString();
|
const s = error.trace ? error.trace : error.toString();
|
||||||
this.stdout(s);
|
this.stdout(s);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -19,19 +19,26 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enabled() {
|
enabled() {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
options() {
|
options() {
|
||||||
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')]];
|
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')],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
let pattern = args['note-pattern'];
|
const pattern = args['note-pattern'];
|
||||||
let items = [];
|
let items = [];
|
||||||
let options = args.options;
|
const options = args.options;
|
||||||
|
|
||||||
let queryOptions = {};
|
const queryOptions = {};
|
||||||
if (options.limit) queryOptions.limit = options.limit;
|
if (options.limit) queryOptions.limit = options.limit;
|
||||||
if (options.sort) {
|
if (options.sort) {
|
||||||
queryOptions.orderBy = options.sort;
|
queryOptions.orderBy = options.sort;
|
||||||
@@ -63,19 +70,19 @@ class Command extends BaseCommand {
|
|||||||
} else {
|
} else {
|
||||||
let hasTodos = false;
|
let hasTodos = false;
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
let item = items[i];
|
const item = items[i];
|
||||||
if (item.is_todo) {
|
if (item.is_todo) {
|
||||||
hasTodos = true;
|
hasTodos = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let seenTitles = [];
|
const seenTitles = [];
|
||||||
let rows = [];
|
const rows = [];
|
||||||
let shortIdShown = false;
|
let shortIdShown = false;
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
let item = items[i];
|
const item = items[i];
|
||||||
let row = [];
|
const row = [];
|
||||||
|
|
||||||
if (options.long) {
|
if (options.long) {
|
||||||
row.push(BaseModel.shortId(item.id));
|
row.push(BaseModel.shortId(item.id));
|
||||||
|
@@ -13,7 +13,7 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
let folder = await Folder.save({ title: args['new-notebook'] }, { userSideValidation: true });
|
const folder = await Folder.save({ title: args['new-notebook'] }, { userSideValidation: true });
|
||||||
app().switchCurrentFolder(folder);
|
app().switchCurrentFolder(folder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -26,7 +26,7 @@ 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);
|
const ids = notes.map(n => n.id);
|
||||||
await Note.batchDelete(ids);
|
await Note.batchDelete(ids);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,8 +18,8 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
let pattern = args['pattern'];
|
const pattern = args['pattern'];
|
||||||
let folderTitle = args['notebook'];
|
const folderTitle = args['notebook'];
|
||||||
|
|
||||||
let folder = null;
|
let folder = null;
|
||||||
if (folderTitle) {
|
if (folderTitle) {
|
||||||
|
@@ -23,18 +23,18 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
let title = args['note'];
|
const title = args['note'];
|
||||||
let propName = args['name'];
|
const propName = args['name'];
|
||||||
let propValue = args['value'];
|
let propValue = args['value'];
|
||||||
if (!propValue) propValue = '';
|
if (!propValue) propValue = '';
|
||||||
|
|
||||||
let notes = await app().loadItems(BaseModel.TYPE_NOTE, title);
|
const notes = await app().loadItems(BaseModel.TYPE_NOTE, title);
|
||||||
if (!notes.length) throw new Error(_('Cannot find "%s".', title));
|
if (!notes.length) throw new Error(_('Cannot find "%s".', title));
|
||||||
|
|
||||||
for (let i = 0; i < notes.length; i++) {
|
for (let i = 0; i < notes.length; i++) {
|
||||||
this.encryptionCheck(notes[i]);
|
this.encryptionCheck(notes[i]);
|
||||||
|
|
||||||
let newNote = {
|
const newNote = {
|
||||||
id: notes[i].id,
|
id: notes[i].id,
|
||||||
type_: notes[i].type_,
|
type_: notes[i].type_,
|
||||||
};
|
};
|
||||||
|
@@ -14,30 +14,39 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async action() {
|
async action() {
|
||||||
let service = new ReportService();
|
const service = new ReportService();
|
||||||
let report = await service.status(Setting.value('sync.target'));
|
const report = await service.status(Setting.value('sync.target'));
|
||||||
|
|
||||||
for (let i = 0; i < report.length; i++) {
|
for (let i = 0; i < report.length; i++) {
|
||||||
let section = report[i];
|
const section = report[i];
|
||||||
|
|
||||||
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) {
|
let canRetryType = '';
|
||||||
|
|
||||||
|
for (const n in section.body) {
|
||||||
if (!section.body.hasOwnProperty(n)) continue;
|
if (!section.body.hasOwnProperty(n)) continue;
|
||||||
let line = section.body[n];
|
const item = section.body[n];
|
||||||
this.stdout(line);
|
|
||||||
|
if (typeof item === 'object') {
|
||||||
|
canRetryType = item.canRetryType;
|
||||||
|
this.stdout(item.text);
|
||||||
|
} else {
|
||||||
|
this.stdout(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canRetryType === 'e2ee') {
|
||||||
|
this.stdout('');
|
||||||
|
this.stdout(_('To retry decryption of these items. Run `e2ee decrypt --retry-failed-items`'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app()
|
app().gui().showConsole();
|
||||||
.gui()
|
app().gui().maximizeConsole();
|
||||||
.showConsole();
|
|
||||||
app()
|
|
||||||
.gui()
|
|
||||||
.maximizeConsole();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -161,9 +161,9 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
const sync = await syncTarget.synchronizer();
|
const sync = await syncTarget.synchronizer();
|
||||||
|
|
||||||
let options = {
|
const options = {
|
||||||
onProgress: report => {
|
onProgress: report => {
|
||||||
let lines = Synchronizer.reportToLines(report);
|
const lines = Synchronizer.reportToLines(report);
|
||||||
if (lines.length) cliUtils.redraw(lines.join(' '));
|
if (lines.length) cliUtils.redraw(lines.join(' '));
|
||||||
},
|
},
|
||||||
onMessage: msg => {
|
onMessage: msg => {
|
||||||
@@ -185,7 +185,7 @@ class Command extends BaseCommand {
|
|||||||
options.context = context;
|
options.context = context;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let newContext = await sync.start(options);
|
const newContext = await sync.start(options);
|
||||||
Setting.setValue(contextKey, JSON.stringify(newContext));
|
Setting.setValue(contextKey, JSON.stringify(newContext));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code == 'alreadyStarted') {
|
if (error.code == 'alreadyStarted') {
|
||||||
|
@@ -20,7 +20,7 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
let tag = null;
|
let tag = null;
|
||||||
let options = args.options;
|
const options = args.options;
|
||||||
|
|
||||||
if (args.tag) tag = await app().loadItem(BaseModel.TYPE_TAG, args.tag);
|
if (args.tag) tag = await app().loadItem(BaseModel.TYPE_TAG, args.tag);
|
||||||
let notes = [];
|
let notes = [];
|
||||||
@@ -46,7 +46,7 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
} else if (command == 'list') {
|
} else if (command == 'list') {
|
||||||
if (tag) {
|
if (tag) {
|
||||||
let notes = await Tag.notes(tag.id);
|
const notes = await Tag.notes(tag.id);
|
||||||
notes.map(note => {
|
notes.map(note => {
|
||||||
let line = '';
|
let line = '';
|
||||||
if (options.long) {
|
if (options.long) {
|
||||||
@@ -70,7 +70,7 @@ class Command extends BaseCommand {
|
|||||||
this.stdout(line);
|
this.stdout(line);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
let tags = await Tag.all();
|
const tags = await Tag.all();
|
||||||
tags.map(tag => {
|
tags.map(tag => {
|
||||||
this.stdout(tag.title);
|
this.stdout(tag.title);
|
||||||
});
|
});
|
||||||
|
@@ -17,7 +17,7 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
let folder = await app().loadItem(BaseModel.TYPE_FOLDER, args['notebook']);
|
const folder = await app().loadItem(BaseModel.TYPE_FOLDER, args['notebook']);
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
@@ -12,7 +12,7 @@ const fs = require('fs-extra');
|
|||||||
const baseDir = `${dirname(__dirname)}/tests/fuzzing`;
|
const baseDir = `${dirname(__dirname)}/tests/fuzzing`;
|
||||||
const syncDir = `${baseDir}/sync`;
|
const syncDir = `${baseDir}/sync`;
|
||||||
const joplinAppPath = `${__dirname}/main.js`;
|
const joplinAppPath = `${__dirname}/main.js`;
|
||||||
let syncDurations = [];
|
const syncDurations = [];
|
||||||
|
|
||||||
const fsDriver = new FsDriverNode();
|
const fsDriver = new FsDriverNode();
|
||||||
Logger.fsDriver_ = fsDriver;
|
Logger.fsDriver_ = fsDriver;
|
||||||
@@ -34,10 +34,10 @@ function createClient(id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function createClients() {
|
async function createClients() {
|
||||||
let output = [];
|
const output = [];
|
||||||
let promises = [];
|
const promises = [];
|
||||||
for (let clientId = 0; clientId < 2; clientId++) {
|
for (let clientId = 0; clientId < 2; clientId++) {
|
||||||
let client = createClient(clientId);
|
const client = createClient(clientId);
|
||||||
promises.push(fs.remove(client.profileDir));
|
promises.push(fs.remove(client.profileDir));
|
||||||
promises.push(
|
promises.push(
|
||||||
execCommand(client, 'config sync.target 2').then(() => {
|
execCommand(client, 'config sync.target 2').then(() => {
|
||||||
@@ -2064,8 +2064,8 @@ function randomWord() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function execCommand(client, command, options = {}) {
|
function execCommand(client, command, options = {}) {
|
||||||
let exePath = `node ${joplinAppPath}`;
|
const exePath = `node ${joplinAppPath}`;
|
||||||
let cmd = `${exePath} --update-geolocation-disabled --env dev --log-level debug --profile ${client.profileDir} ${command}`;
|
const cmd = `${exePath} --update-geolocation-disabled --env dev --log-level debug --profile ${client.profileDir} ${command}`;
|
||||||
logger.info(`${client.id}: ${command}`);
|
logger.info(`${client.id}: ${command}`);
|
||||||
|
|
||||||
if (options.killAfter) {
|
if (options.killAfter) {
|
||||||
@@ -2073,7 +2073,7 @@ function execCommand(client, command, options = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let childProcess = exec(cmd, (error, stdout, stderr) => {
|
const childProcess = exec(cmd, (error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
if (error.signal == 'SIGTERM') {
|
if (error.signal == 'SIGTERM') {
|
||||||
resolve('Process was killed');
|
resolve('Process was killed');
|
||||||
@@ -2096,7 +2096,7 @@ function execCommand(client, command, options = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function clientItems(client) {
|
async function clientItems(client) {
|
||||||
let itemsJson = await execCommand(client, 'dump');
|
const itemsJson = await execCommand(client, 'dump');
|
||||||
try {
|
try {
|
||||||
return JSON.parse(itemsJson);
|
return JSON.parse(itemsJson);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -2105,7 +2105,7 @@ async function clientItems(client) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function randomTag(items) {
|
function randomTag(items) {
|
||||||
let tags = [];
|
const tags = [];
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
if (items[i].type_ != 5) continue;
|
if (items[i].type_ != 5) continue;
|
||||||
tags.push(items[i]);
|
tags.push(items[i]);
|
||||||
@@ -2115,7 +2115,7 @@ function randomTag(items) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function randomNote(items) {
|
function randomNote(items) {
|
||||||
let notes = [];
|
const notes = [];
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
if (items[i].type_ != 1) continue;
|
if (items[i].type_ != 1) continue;
|
||||||
notes.push(items[i]);
|
notes.push(items[i]);
|
||||||
@@ -2125,14 +2125,14 @@ function randomNote(items) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function execRandomCommand(client) {
|
async function execRandomCommand(client) {
|
||||||
let possibleCommands = [
|
const possibleCommands = [
|
||||||
['mkbook {word}', 40], // CREATE FOLDER
|
['mkbook {word}', 40], // CREATE FOLDER
|
||||||
['mknote {word}', 70], // CREATE NOTE
|
['mknote {word}', 70], // CREATE NOTE
|
||||||
[
|
[
|
||||||
async () => {
|
async () => {
|
||||||
// DELETE RANDOM ITEM
|
// DELETE RANDOM ITEM
|
||||||
let items = await clientItems(client);
|
const items = await clientItems(client);
|
||||||
let item = randomElement(items);
|
const item = randomElement(items);
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
|
|
||||||
if (item.type_ == 1) {
|
if (item.type_ == 1) {
|
||||||
@@ -2150,8 +2150,8 @@ async function execRandomCommand(client) {
|
|||||||
[
|
[
|
||||||
async () => {
|
async () => {
|
||||||
// SYNC
|
// SYNC
|
||||||
let avgSyncDuration = averageSyncDuration();
|
const avgSyncDuration = averageSyncDuration();
|
||||||
let options = {};
|
const options = {};
|
||||||
if (!isNaN(avgSyncDuration)) {
|
if (!isNaN(avgSyncDuration)) {
|
||||||
if (Math.random() >= 0.5) {
|
if (Math.random() >= 0.5) {
|
||||||
options.killAfter = avgSyncDuration * Math.random();
|
options.killAfter = avgSyncDuration * Math.random();
|
||||||
@@ -2164,8 +2164,8 @@ async function execRandomCommand(client) {
|
|||||||
[
|
[
|
||||||
async () => {
|
async () => {
|
||||||
// UPDATE RANDOM ITEM
|
// UPDATE RANDOM ITEM
|
||||||
let items = await clientItems(client);
|
const items = await clientItems(client);
|
||||||
let item = randomNote(items);
|
const item = randomNote(items);
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
|
|
||||||
return execCommand(client, `set ${item.id} title "${randomWord()}"`);
|
return execCommand(client, `set ${item.id} title "${randomWord()}"`);
|
||||||
@@ -2175,12 +2175,12 @@ async function execRandomCommand(client) {
|
|||||||
[
|
[
|
||||||
async () => {
|
async () => {
|
||||||
// ADD TAG
|
// ADD TAG
|
||||||
let items = await clientItems(client);
|
const items = await clientItems(client);
|
||||||
let note = randomNote(items);
|
const note = randomNote(items);
|
||||||
if (!note) return;
|
if (!note) return;
|
||||||
|
|
||||||
let tag = randomTag(items);
|
const tag = randomTag(items);
|
||||||
let tagTitle = !tag || Math.random() >= 0.9 ? `tag-${randomWord()}` : tag.title;
|
const tagTitle = !tag || Math.random() >= 0.9 ? `tag-${randomWord()}` : tag.title;
|
||||||
|
|
||||||
return execCommand(client, `tag add ${tagTitle} ${note.id}`);
|
return execCommand(client, `tag add ${tagTitle} ${note.id}`);
|
||||||
},
|
},
|
||||||
@@ -2191,7 +2191,7 @@ async function execRandomCommand(client) {
|
|||||||
let cmd = null;
|
let cmd = null;
|
||||||
while (true) {
|
while (true) {
|
||||||
cmd = randomElement(possibleCommands);
|
cmd = randomElement(possibleCommands);
|
||||||
let r = 1 + Math.floor(Math.random() * 100);
|
const r = 1 + Math.floor(Math.random() * 100);
|
||||||
if (r <= cmd[1]) break;
|
if (r <= cmd[1]) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2210,7 +2210,7 @@ function averageSyncDuration() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function randomNextCheckTime() {
|
function randomNextCheckTime() {
|
||||||
let output = time.unixMs() + 1000 + Math.random() * 1000 * 120;
|
const output = time.unixMs() + 1000 + Math.random() * 1000 * 120;
|
||||||
logger.info(`Next sync check: ${time.unixMsToIso(output)} (${Math.round((output - time.unixMs()) / 1000)} sec.)`);
|
logger.info(`Next sync check: ${time.unixMsToIso(output)} (${Math.round((output - time.unixMs()) / 1000)} sec.)`);
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
@@ -2223,11 +2223,11 @@ function findItem(items, itemId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function compareItems(item1, item2) {
|
function compareItems(item1, item2) {
|
||||||
let output = [];
|
const output = [];
|
||||||
for (let n in item1) {
|
for (const n in item1) {
|
||||||
if (!item1.hasOwnProperty(n)) continue;
|
if (!item1.hasOwnProperty(n)) continue;
|
||||||
let p1 = item1[n];
|
const p1 = item1[n];
|
||||||
let p2 = item2[n];
|
const p2 = item2[n];
|
||||||
|
|
||||||
if (n == 'notes_') {
|
if (n == 'notes_') {
|
||||||
p1.sort();
|
p1.sort();
|
||||||
@@ -2243,13 +2243,13 @@ function compareItems(item1, item2) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function findMissingItems_(items1, items2) {
|
function findMissingItems_(items1, items2) {
|
||||||
let output = [];
|
const output = [];
|
||||||
|
|
||||||
for (let i = 0; i < items1.length; i++) {
|
for (let i = 0; i < items1.length; i++) {
|
||||||
let item1 = items1[i];
|
const item1 = items1[i];
|
||||||
let found = false;
|
let found = false;
|
||||||
for (let j = 0; j < items2.length; j++) {
|
for (let j = 0; j < items2.length; j++) {
|
||||||
let item2 = items2[j];
|
const item2 = items2[j];
|
||||||
if (item1.id == item2.id) {
|
if (item1.id == item2.id) {
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
@@ -2269,33 +2269,33 @@ function findMissingItems(items1, items2) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function compareClientItems(clientItems) {
|
async function compareClientItems(clientItems) {
|
||||||
let itemCounts = [];
|
const itemCounts = [];
|
||||||
for (let i = 0; i < clientItems.length; i++) {
|
for (let i = 0; i < clientItems.length; i++) {
|
||||||
let items = clientItems[i];
|
const items = clientItems[i];
|
||||||
itemCounts.push(items.length);
|
itemCounts.push(items.length);
|
||||||
}
|
}
|
||||||
logger.info(`Item count: ${itemCounts.join(', ')}`);
|
logger.info(`Item count: ${itemCounts.join(', ')}`);
|
||||||
|
|
||||||
let missingItems = findMissingItems(clientItems[0], clientItems[1]);
|
const missingItems = findMissingItems(clientItems[0], clientItems[1]);
|
||||||
if (missingItems[0].length || missingItems[1].length) {
|
if (missingItems[0].length || missingItems[1].length) {
|
||||||
logger.error('Items are different');
|
logger.error('Items are different');
|
||||||
logger.error(missingItems);
|
logger.error(missingItems);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let differences = [];
|
const differences = [];
|
||||||
let items = clientItems[0];
|
const items = clientItems[0];
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
let item1 = items[i];
|
const item1 = items[i];
|
||||||
for (let clientId = 1; clientId < clientItems.length; clientId++) {
|
for (let clientId = 1; clientId < clientItems.length; clientId++) {
|
||||||
let item2 = findItem(clientItems[clientId], item1.id);
|
const item2 = findItem(clientItems[clientId], item1.id);
|
||||||
if (!item2) {
|
if (!item2) {
|
||||||
logger.error(`Item not found on client ${clientId}:`);
|
logger.error(`Item not found on client ${clientId}:`);
|
||||||
logger.error(item1);
|
logger.error(item1);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let diff = compareItems(item1, item2);
|
const diff = compareItems(item1, item2);
|
||||||
if (diff.length) {
|
if (diff.length) {
|
||||||
differences.push({
|
differences.push({
|
||||||
item1: JSON.stringify(item1),
|
item1: JSON.stringify(item1),
|
||||||
@@ -2315,7 +2315,7 @@ async function compareClientItems(clientItems) {
|
|||||||
async function main() {
|
async function main() {
|
||||||
await fs.remove(syncDir);
|
await fs.remove(syncDir);
|
||||||
|
|
||||||
let clients = await createClients();
|
const clients = await createClients();
|
||||||
let clientId = 0;
|
let clientId = 0;
|
||||||
|
|
||||||
for (let i = 0; i < clients.length; i++) {
|
for (let i = 0; i < clients.length; i++) {
|
||||||
@@ -2348,7 +2348,7 @@ async function main() {
|
|||||||
|
|
||||||
if (state == 'syncCheck') {
|
if (state == 'syncCheck') {
|
||||||
state = 'waitForSyncCheck';
|
state = 'waitForSyncCheck';
|
||||||
let clientItems = [];
|
const clientItems = [];
|
||||||
// Up to 3 sync operations must be performed by each clients in order for them
|
// Up to 3 sync operations must be performed by each clients in order for them
|
||||||
// to be perfectly in sync - in order for each items to send their changes
|
// to be perfectly in sync - in order for each items to send their changes
|
||||||
// and get those from the other clients, and to also get changes that are
|
// and get those from the other clients, and to also get changes that are
|
||||||
@@ -2356,12 +2356,12 @@ async function main() {
|
|||||||
// with another one).
|
// with another one).
|
||||||
for (let loopCount = 0; loopCount < 3; loopCount++) {
|
for (let loopCount = 0; loopCount < 3; loopCount++) {
|
||||||
for (let i = 0; i < clients.length; i++) {
|
for (let i = 0; i < clients.length; i++) {
|
||||||
let beforeTime = time.unixMs();
|
const beforeTime = time.unixMs();
|
||||||
await execCommand(clients[i], 'sync');
|
await execCommand(clients[i], 'sync');
|
||||||
syncDurations.push(time.unixMs() - beforeTime);
|
syncDurations.push(time.unixMs() - beforeTime);
|
||||||
if (syncDurations.length > 20) syncDurations.splice(0, 1);
|
if (syncDurations.length > 20) syncDurations.splice(0, 1);
|
||||||
if (loopCount === 2) {
|
if (loopCount === 2) {
|
||||||
let dump = await execCommand(clients[i], 'dump');
|
const dump = await execCommand(clients[i], 'dump');
|
||||||
clientItems[i] = JSON.parse(dump);
|
clientItems[i] = JSON.parse(dump);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -20,7 +20,7 @@ class FolderListWidget extends ListWidget {
|
|||||||
this.trimItemTitle = false;
|
this.trimItemTitle = false;
|
||||||
|
|
||||||
this.itemRenderer = item => {
|
this.itemRenderer = item => {
|
||||||
let output = [];
|
const output = [];
|
||||||
if (item === '-') {
|
if (item === '-') {
|
||||||
output.push('-'.repeat(this.innerWidth));
|
output.push('-'.repeat(this.innerWidth));
|
||||||
} else if (item.type_ === Folder.modelType()) {
|
} else if (item.type_ === Folder.modelType()) {
|
||||||
@@ -121,7 +121,7 @@ class FolderListWidget extends ListWidget {
|
|||||||
|
|
||||||
folderHasChildren_(folders, folderId) {
|
folderHasChildren_(folders, folderId) {
|
||||||
for (let i = 0; i < folders.length; i++) {
|
for (let i = 0; i < folders.length; i++) {
|
||||||
let folder = folders[i];
|
const folder = folders[i];
|
||||||
if (folder.parent_id === folderId) return true;
|
if (folder.parent_id === folderId) return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@@ -46,6 +46,8 @@ class NoteWidget extends TextWidget {
|
|||||||
|
|
||||||
if (this.note_ && this.note_.encryption_applied) {
|
if (this.note_ && this.note_.encryption_applied) {
|
||||||
this.text = _('One or more items are currently encrypted and you may need to supply a master password. To do so please type `e2ee decrypt`. If you have already supplied the password, the encrypted items are being decrypted in the background and will be available soon.');
|
this.text = _('One or more items are currently encrypted and you may need to supply a master password. To do so please type `e2ee decrypt`. If you have already supplied the password, the encrypted items are being decrypted in the background and will be available soon.');
|
||||||
|
this.text += '\n\n';
|
||||||
|
this.text += _('You may also type `status` for more information.');
|
||||||
} else {
|
} else {
|
||||||
this.text = this.note_ ? `${this.note_.title}\n\n${this.note_.body}` : '';
|
this.text = this.note_ ? `${this.note_.title}\n\n${this.note_.body}` : '';
|
||||||
}
|
}
|
||||||
|
@@ -106,7 +106,7 @@ class StatusBarWidget extends BaseWidget {
|
|||||||
|
|
||||||
const isSecurePrompt = !!this.promptState_.secure;
|
const isSecurePrompt = !!this.promptState_.secure;
|
||||||
|
|
||||||
let options = {
|
const options = {
|
||||||
cancelable: true,
|
cancelable: true,
|
||||||
history: this.history,
|
history: this.history,
|
||||||
default: this.promptState_.initialText,
|
default: this.promptState_.initialText,
|
||||||
|
@@ -6,11 +6,11 @@ const MAX_WIDTH = 78;
|
|||||||
const INDENT = ' ';
|
const INDENT = ' ';
|
||||||
|
|
||||||
function renderTwoColumnData(options, baseIndent, width) {
|
function renderTwoColumnData(options, baseIndent, width) {
|
||||||
let output = [];
|
const output = [];
|
||||||
const optionColWidth = getOptionColWidth(options);
|
const optionColWidth = getOptionColWidth(options);
|
||||||
|
|
||||||
for (let i = 0; i < options.length; i++) {
|
for (let i = 0; i < options.length; i++) {
|
||||||
let option = options[i];
|
const option = options[i];
|
||||||
const flag = option[0];
|
const flag = option[0];
|
||||||
const indent = baseIndent + INDENT + ' '.repeat(optionColWidth + 2);
|
const indent = baseIndent + INDENT + ' '.repeat(optionColWidth + 2);
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ function renderCommandHelp(cmd, width = null) {
|
|||||||
|
|
||||||
const baseIndent = '';
|
const baseIndent = '';
|
||||||
|
|
||||||
let output = [];
|
const output = [];
|
||||||
output.push(baseIndent + cmd.usage());
|
output.push(baseIndent + cmd.usage());
|
||||||
output.push('');
|
output.push('');
|
||||||
output.push(wrap(cmd.description(), baseIndent + INDENT, width));
|
output.push(wrap(cmd.description(), baseIndent + INDENT, width));
|
||||||
@@ -42,7 +42,7 @@ function renderCommandHelp(cmd, width = null) {
|
|||||||
|
|
||||||
if (cmd.name() === 'config') {
|
if (cmd.name() === 'config') {
|
||||||
const renderMetadata = md => {
|
const renderMetadata = md => {
|
||||||
let desc = [];
|
const desc = [];
|
||||||
|
|
||||||
if (md.label) {
|
if (md.label) {
|
||||||
let label = md.label();
|
let label = md.label();
|
||||||
@@ -77,7 +77,7 @@ function renderCommandHelp(cmd, width = null) {
|
|||||||
output.push(_('Possible keys/values:'));
|
output.push(_('Possible keys/values:'));
|
||||||
output.push('');
|
output.push('');
|
||||||
|
|
||||||
let keysValues = [];
|
const keysValues = [];
|
||||||
const keys = Setting.keys(true, 'cli');
|
const keys = Setting.keys(true, 'cli');
|
||||||
for (let i = 0; i < keys.length; i++) {
|
for (let i = 0; i < keys.length; i++) {
|
||||||
if (keysValues.length) keysValues.push(['', '']);
|
if (keysValues.length) keysValues.push(['', '']);
|
||||||
|
@@ -54,7 +54,7 @@ shimInit();
|
|||||||
const application = app();
|
const application = app();
|
||||||
|
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
var rl = require('readline').createInterface({
|
const rl = require('readline').createInterface({
|
||||||
input: process.stdin,
|
input: process.stdin,
|
||||||
output: process.stdout,
|
output: process.stdout,
|
||||||
});
|
});
|
||||||
|
@@ -15,10 +15,12 @@ tasks.build = {
|
|||||||
await utils.copyDir(`${__dirname}/../patches`, `${buildDir}/patches`);
|
await utils.copyDir(`${__dirname}/../patches`, `${buildDir}/patches`);
|
||||||
await tasks.copyLib.fn();
|
await tasks.copyLib.fn();
|
||||||
await utils.copyFile(`${__dirname}/package.json`, `${buildDir}/package.json`);
|
await utils.copyFile(`${__dirname}/package.json`, `${buildDir}/package.json`);
|
||||||
|
await utils.copyFile(`${__dirname}/package-lock.json`, `${buildDir}/package-lock.json`);
|
||||||
|
await utils.copyFile(`${__dirname}/gulpfile.js`, `${buildDir}/gulpfile.js`);
|
||||||
|
|
||||||
const packageRaw = await fs.readFile(`${buildDir}/package.json`);
|
const packageRaw = await fs.readFile(`${buildDir}/package.json`);
|
||||||
const package = JSON.parse(packageRaw.toString());
|
const package = JSON.parse(packageRaw.toString());
|
||||||
package.scripts.postinstall = package.scripts.postinstall.replace(/\.\.\/patches/, './patches');
|
package.scripts.postinstall = 'patch-package';
|
||||||
await fs.writeFile(`${buildDir}/package.json`, JSON.stringify(package, null, 2), 'utf8');
|
await fs.writeFile(`${buildDir}/package.json`, JSON.stringify(package, null, 2), 'utf8');
|
||||||
|
|
||||||
fs.chmodSync(`${buildDir}/main.js`, 0o755);
|
fs.chmodSync(`${buildDir}/main.js`, 0o755);
|
||||||
|
@@ -32,42 +32,46 @@ locales['ru_RU'] = require('./ru_RU.json');
|
|||||||
locales['sl_SI'] = require('./sl_SI.json');
|
locales['sl_SI'] = require('./sl_SI.json');
|
||||||
locales['sr_RS'] = require('./sr_RS.json');
|
locales['sr_RS'] = require('./sr_RS.json');
|
||||||
locales['sv'] = require('./sv.json');
|
locales['sv'] = require('./sv.json');
|
||||||
|
locales['th_TH'] = require('./th_TH.json');
|
||||||
locales['tr_TR'] = require('./tr_TR.json');
|
locales['tr_TR'] = require('./tr_TR.json');
|
||||||
|
locales['vi'] = require('./vi.json');
|
||||||
locales['zh_CN'] = require('./zh_CN.json');
|
locales['zh_CN'] = require('./zh_CN.json');
|
||||||
locales['zh_TW'] = require('./zh_TW.json');
|
locales['zh_TW'] = require('./zh_TW.json');
|
||||||
stats['ar'] = {"percentDone":92};
|
stats['ar'] = {"percentDone":87};
|
||||||
stats['eu'] = {"percentDone":39};
|
stats['eu'] = {"percentDone":37};
|
||||||
stats['bs_BA'] = {"percentDone":85};
|
stats['bs_BA'] = {"percentDone":81};
|
||||||
stats['bg_BG'] = {"percentDone":77};
|
stats['bg_BG'] = {"percentDone":73};
|
||||||
stats['ca'] = {"percentDone":61};
|
stats['ca'] = {"percentDone":58};
|
||||||
stats['hr_HR'] = {"percentDone":32};
|
stats['hr_HR'] = {"percentDone":31};
|
||||||
stats['cs_CZ'] = {"percentDone":94};
|
stats['cs_CZ'] = {"percentDone":90};
|
||||||
stats['da_DK'] = {"percentDone":85};
|
stats['da_DK'] = {"percentDone":81};
|
||||||
stats['de_DE'] = {"percentDone":100};
|
stats['de_DE'] = {"percentDone":97};
|
||||||
stats['et_EE'] = {"percentDone":76};
|
stats['et_EE'] = {"percentDone":72};
|
||||||
stats['en_GB'] = {"percentDone":100};
|
stats['en_GB'] = {"percentDone":100};
|
||||||
stats['en_US'] = {"percentDone":100};
|
stats['en_US'] = {"percentDone":100};
|
||||||
stats['es_ES'] = {"percentDone":95};
|
stats['es_ES'] = {"percentDone":90};
|
||||||
stats['eo'] = {"percentDone":44};
|
stats['eo'] = {"percentDone":41};
|
||||||
stats['fr_FR'] = {"percentDone":95};
|
stats['fr_FR'] = {"percentDone":91};
|
||||||
stats['gl_ES'] = {"percentDone":50};
|
stats['gl_ES'] = {"percentDone":47};
|
||||||
stats['it_IT'] = {"percentDone":97};
|
stats['it_IT'] = {"percentDone":96};
|
||||||
stats['nl_NL'] = {"percentDone":97};
|
stats['nl_NL'] = {"percentDone":92};
|
||||||
stats['nl_BE'] = {"percentDone":39};
|
stats['nl_BE'] = {"percentDone":37};
|
||||||
stats['nb_NO'] = {"percentDone":89};
|
stats['nb_NO'] = {"percentDone":97};
|
||||||
stats['fa'] = {"percentDone":38};
|
stats['fa'] = {"percentDone":36};
|
||||||
stats['pl_PL'] = {"percentDone":75};
|
stats['pl_PL'] = {"percentDone":84};
|
||||||
stats['pt_PT'] = {"percentDone":91};
|
stats['pt_PT'] = {"percentDone":98};
|
||||||
stats['pt_BR'] = {"percentDone":88};
|
stats['pt_BR'] = {"percentDone":98};
|
||||||
stats['ro'] = {"percentDone":39};
|
stats['ro'] = {"percentDone":37};
|
||||||
stats['sl_SI'] = {"percentDone":49};
|
stats['sl_SI'] = {"percentDone":47};
|
||||||
stats['sv'] = {"percentDone":68};
|
stats['sv'] = {"percentDone":78};
|
||||||
stats['tr_TR'] = {"percentDone":92};
|
stats['th_TH'] = {"percentDone":58};
|
||||||
stats['el_GR'] = {"percentDone":93};
|
stats['vi'] = {"percentDone":94};
|
||||||
stats['ru_RU'] = {"percentDone":95};
|
stats['tr_TR'] = {"percentDone":100};
|
||||||
stats['sr_RS'] = {"percentDone":75};
|
stats['el_GR'] = {"percentDone":89};
|
||||||
|
stats['ru_RU'] = {"percentDone":97};
|
||||||
|
stats['sr_RS'] = {"percentDone":78};
|
||||||
stats['zh_CN'] = {"percentDone":97};
|
stats['zh_CN'] = {"percentDone":97};
|
||||||
stats['zh_TW'] = {"percentDone":91};
|
stats['zh_TW'] = {"percentDone":98};
|
||||||
stats['ja_JP'] = {"percentDone":97};
|
stats['ja_JP'] = {"percentDone":100};
|
||||||
stats['ko'] = {"percentDone":97};
|
stats['ko'] = {"percentDone":95};
|
||||||
module.exports = { locales: locales, stats: stats };
|
module.exports = { locales: locales, stats: stats };
|