Compare commits
429 Commits
android-v2
...
android-v2
Author | SHA1 | Date | |
---|---|---|---|
|
4c50706d54 | ||
|
0f9d6a12b0 | ||
|
13da286b55 | ||
|
da4d57c1db | ||
|
7b06090255 | ||
|
2cc4ac087b | ||
|
c4ff7857b1 | ||
|
938e639a01 | ||
|
fe3ad3e9b4 | ||
|
681d413b36 | ||
|
8b452106be | ||
|
72a8724a5b | ||
|
04112b025c | ||
|
bea9edf64b | ||
|
fd432f928d | ||
|
a8f6676fb3 | ||
|
6720fd1f0e | ||
|
f3518cddbe | ||
|
d4d400217b | ||
|
a863f92490 | ||
|
aaf4dcc50c | ||
|
cfd34da03f | ||
|
c24467c15e | ||
|
3ed6ad5948 | ||
|
de0ae7653f | ||
|
a9691f6b1c | ||
|
1a7d463f09 | ||
|
47a49fd5de | ||
|
be949390f8 | ||
|
b26a9f1b22 | ||
|
b7b0f770ee | ||
|
1ae5d4ea9b | ||
|
aa267f0dc6 | ||
|
691618b50c | ||
|
33ed7545a9 | ||
|
d8d0e705f2 | ||
|
bdc970d718 | ||
|
4636d1539c | ||
|
bc1165be46 | ||
|
f5e1e45f6f | ||
|
352ee6496e | ||
|
fdbfa78e53 | ||
|
dfe5383d4b | ||
|
eb1e9e0436 | ||
|
3d7f864982 | ||
|
1ffe30b359 | ||
|
5d5bd3abd6 | ||
|
747264bd89 | ||
|
7bd1bdb0e3 | ||
|
d5ec92e540 | ||
|
2b50e99bb1 | ||
|
a4c5164bea | ||
|
88d6c94e6b | ||
|
4f45c977e2 | ||
|
cbf7e24c47 | ||
|
7ebc98633a | ||
|
1d4e3fe3ea | ||
|
94e0ea403a | ||
|
d217478852 | ||
|
4c9cbd61a4 | ||
|
d07a540618 | ||
|
720b6f0190 | ||
|
33fff4c8f0 | ||
|
6f83ccd044 | ||
|
7d68dafaed | ||
|
4f066b06d2 | ||
|
a11bed1bb2 | ||
|
96c9e29025 | ||
|
2519b73117 | ||
|
639bfc4533 | ||
|
97a04ea9e3 | ||
|
150ee7631e | ||
|
9a68157b86 | ||
|
12878672e4 | ||
|
15f019789b | ||
|
311905f6d2 | ||
|
a74035c22f | ||
|
361e56e9b4 | ||
|
04fa761258 | ||
|
b2109dab99 | ||
|
61a3962eda | ||
|
97af8cb7f5 | ||
|
17bdec22ca | ||
|
1d176b31ff | ||
|
32e76d7988 | ||
|
94e3582bb8 | ||
|
b9b2d37fdd | ||
|
a465d0ee8d | ||
|
a29666d442 | ||
|
d3ccb9786b | ||
|
2625f0d89e | ||
|
1cd0c3742e | ||
|
2e94b01700 | ||
|
20b1c2e7cb | ||
|
4e8863d81f | ||
|
a34a911b1e | ||
|
2bf2395f1b | ||
|
a6b150e63a | ||
|
50a7b21f8d | ||
|
76e8186858 | ||
|
763716397b | ||
|
68e04f944f | ||
|
91b51adc48 | ||
|
4c12c25bd8 | ||
|
c0dcf1d3e3 | ||
|
efa35d9317 | ||
|
3c210a012a | ||
|
92667bafc1 | ||
|
fc7d053521 | ||
|
47072b3813 | ||
|
7a13003af5 | ||
|
723b708d89 | ||
|
36a24a5ed8 | ||
|
091bf4a3d7 | ||
|
ce8e5e61be | ||
|
b8462c4754 | ||
|
73ad7e0572 | ||
|
2bf4f39ca1 | ||
|
29b8fcd5bb | ||
|
2a96012577 | ||
|
474e8c95a0 | ||
|
906b7c3a33 | ||
|
0a2afdb010 | ||
|
a281163353 | ||
|
9aa7240d5e | ||
|
9cf63168da | ||
|
430f73c47a | ||
|
7e8c87e908 | ||
|
4e09b6f2a4 | ||
|
d02058d337 | ||
|
f95ee689fd | ||
|
164e53ee7d | ||
|
cb1322f01e | ||
|
cc22d71408 | ||
|
ff7c13ac85 | ||
|
302bc4dcc7 | ||
|
ebfc5da0fe | ||
|
00eee19077 | ||
|
4048d83dd7 | ||
|
48b8fae625 | ||
|
60fbc6e022 | ||
|
ae73b5b87c | ||
|
6bc2ef9869 | ||
|
206d4ab56d | ||
|
96456aa851 | ||
|
adf0ccee2f | ||
|
01394e43d9 | ||
|
39c19db69c | ||
|
105e408dca | ||
|
37dbea1613 | ||
|
2512adebd2 | ||
|
28538de028 | ||
|
a0b4f5acac | ||
|
30a5bda5b9 | ||
|
c5865b3bf5 | ||
|
a9f6b38120 | ||
|
75447f7267 | ||
|
ca703adf6a | ||
|
f38abc5718 | ||
|
d733a7f5ae | ||
|
0f388b13c7 | ||
|
754ca39926 | ||
|
8a05baa97f | ||
|
1df00a1e5a | ||
|
5e63b623ca | ||
|
f8a25cb275 | ||
|
1e866ead3e | ||
|
3970b0876d | ||
|
6b711f655d | ||
|
65d3ed0c67 | ||
|
512e37b46a | ||
|
ed15b5e5da | ||
|
f9d1157266 | ||
|
cda0538681 | ||
|
3342b138b4 | ||
|
9285bb7a88 | ||
|
590769b1ae | ||
|
0873b1900b | ||
|
5df499e6c7 | ||
|
d89c686555 | ||
|
d81497f7b8 | ||
|
8cd377013e | ||
|
c623d98bda | ||
|
02232c0ca3 | ||
|
8ae3210378 | ||
|
bf907f21c4 | ||
|
cec5f1f3ba | ||
|
b4928eb7e5 | ||
|
3dcbb2ed51 | ||
|
e744cf0171 | ||
|
1ffafdcefc | ||
|
e48303aeb7 | ||
|
fb13e0ca80 | ||
|
7345cd4678 | ||
|
5341501d53 | ||
|
1fcfa9c591 | ||
|
1cd2d9254f | ||
|
ee18271f9b | ||
|
bf59b23efe | ||
|
973193623a | ||
|
495f088320 | ||
|
d264bdd14d | ||
|
144ec1eea2 | ||
|
7d60bfc192 | ||
|
b5428ddc2a | ||
|
2075626d42 | ||
|
4b9307015e | ||
|
9c88cb2151 | ||
|
45dcca865d | ||
|
c421a5a732 | ||
|
f7158f5e27 | ||
|
780c269e14 | ||
|
5eb0cda8b9 | ||
|
3b4eb16110 | ||
|
674bbf9667 | ||
|
f7e64e9681 | ||
|
4ec2fb216e | ||
|
ddebeb68b2 | ||
|
8b09d71d5b | ||
|
3df499714b | ||
|
6e9ff89dda | ||
|
880215acc4 | ||
|
d4157e14fe | ||
|
b237a92e02 | ||
|
0bd164642e | ||
|
872fadf454 | ||
|
404e86cef4 | ||
|
5f6b2f1a63 | ||
|
858205a72b | ||
|
dc8ea620a6 | ||
|
28527541f5 | ||
|
b1051a3144 | ||
|
06639eb399 | ||
|
5e3c12aeb8 | ||
|
7e50b117ed | ||
|
bb47c59414 | ||
|
26459ea9dc | ||
|
67a68709e3 | ||
|
d13a3fc5c8 | ||
|
27b5f59d7c | ||
|
6b7c11d694 | ||
|
1bf38eff32 | ||
|
c93f22d84b | ||
|
17c25b72f0 | ||
|
a2ded180cb | ||
|
d6c7902390 | ||
|
167892e383 | ||
|
62dd4b9926 | ||
|
b19a0761fd | ||
|
e47ac1de6a | ||
|
d2fe33183b | ||
|
02658370a6 | ||
|
46ed6411c1 | ||
|
c333e9c802 | ||
|
73617bf019 | ||
|
f3038c9cf9 | ||
|
0f81b08f89 | ||
|
0c34497c78 | ||
|
f244f44c7b | ||
|
b11006c3a7 | ||
|
d63b84ee1f | ||
|
e761b97944 | ||
|
49cf4d9965 | ||
|
3732d3e360 | ||
|
62a64ed575 | ||
|
20cb03ab69 | ||
|
995423cadf | ||
|
a730d349e4 | ||
|
6d6f9b3c3b | ||
|
09b5ef6713 | ||
|
e0cc8da884 | ||
|
d17e36d038 | ||
|
815a0a5db4 | ||
|
19b68102b2 | ||
|
04e0274055 | ||
|
bce94f1775 | ||
|
0e847685ff | ||
|
c008b58712 | ||
|
de67ec4684 | ||
|
06bf6b85fe | ||
|
0d1529f1ad | ||
|
4fc786cf0b | ||
|
6306a0f371 | ||
|
45c483263e | ||
|
d50e347f35 | ||
|
913e650bb3 | ||
|
5e537db538 | ||
|
706e7e07d1 | ||
|
dc9f732ded | ||
|
cedf84698e | ||
|
9915a59bce | ||
|
1bbec445d5 | ||
|
27a0959f30 | ||
|
b70589ef56 | ||
|
b6039e739a | ||
|
7558c2e9fe | ||
|
d63fc524bf | ||
|
48ea014ee0 | ||
|
fd5a4dcbbf | ||
|
92a0964a8d | ||
|
c0c32a7ac1 | ||
|
5cfe945c4e | ||
|
51f96eb9dd | ||
|
f6121bae2b | ||
|
3c40854a8c | ||
|
b271e54828 | ||
|
2c0f8d5c6b | ||
|
474365188d | ||
|
80c2a87da2 | ||
|
a8f0f01826 | ||
|
4bb06cb4a0 | ||
|
9d0a862798 | ||
|
583c6da970 | ||
|
9a7c737f15 | ||
|
08805f01e7 | ||
|
01cc550adf | ||
|
1a250639ca | ||
|
d610036692 | ||
|
f05307d69f | ||
|
cc99ec3edf | ||
|
604dcbc35b | ||
|
b459ba7224 | ||
|
aaa1dc853d | ||
|
cadc974999 | ||
|
d812cf273f | ||
|
0d0398312f | ||
|
39c8fc812d | ||
|
9e89357e64 | ||
|
0638d711d7 | ||
|
dad91594c8 | ||
|
fb77c75520 | ||
|
00adf2aced | ||
|
aaddc58416 | ||
|
5092e21067 | ||
|
388000ded3 | ||
|
567c513d83 | ||
|
1e14dffab0 | ||
|
ed02a0a900 | ||
|
f989698998 | ||
|
0a75480a32 | ||
|
86b470359d | ||
|
9bad668cc5 | ||
|
c18c31ab7f | ||
|
7c24a2f4be | ||
|
56438ea644 | ||
|
6ddae5b654 | ||
|
3ee8da0cae | ||
|
2fd985b9ac | ||
|
3cc87e6acd | ||
|
ac6c6e9996 | ||
|
7c6db44cc7 | ||
|
fb7f524415 | ||
|
fd4f70f62b | ||
|
afd653ba4d | ||
|
9827788176 | ||
|
2924bb5437 | ||
|
7ce954da30 | ||
|
84e8f8b0dc | ||
|
0a2bcf7342 | ||
|
ddd8b99637 | ||
|
705565e4bf | ||
|
92d446e0a3 | ||
|
345c602e07 | ||
|
852991f429 | ||
|
e2f9e87cdf | ||
|
7f0d86a1e5 | ||
|
a97cb65bbc | ||
|
61d32926e7 | ||
|
6f902201a9 | ||
|
e5ffe3af0f | ||
|
54cc4898a4 | ||
|
414e60aa48 | ||
|
8869c7b6c2 | ||
|
74f7801f25 | ||
|
3c8a8619f9 | ||
|
35849b1d34 | ||
|
306b043b30 | ||
|
0117f23ee4 | ||
|
8c5eaaf65c | ||
|
529140cf87 | ||
|
d11bdf586a | ||
|
c103bbce14 | ||
|
7936e789c0 | ||
|
f267d8853b | ||
|
46ce792ef2 | ||
|
074261862d | ||
|
069c7a6b34 | ||
|
7272ad771b | ||
|
74d66a9f1a | ||
|
6244999899 | ||
|
9930359210 | ||
|
84cc1576a8 | ||
|
29b42b3b37 | ||
|
3b15834e8d | ||
|
2b9bf6deba | ||
|
0abaeef05e | ||
|
b2e86d2f8a | ||
|
52a3a59623 | ||
|
c78afc46c9 | ||
|
9b89f4ecfb | ||
|
99be29707f | ||
|
2a62bbdbd3 | ||
|
c5e15e93f4 | ||
|
719e0e6104 | ||
|
1eea2400cb | ||
|
83efd67ff8 | ||
|
8ef682dcf9 | ||
|
401d551daa | ||
|
8a533b2003 | ||
|
27f888d0c4 | ||
|
4aeb2fafb2 | ||
|
dc20402f5b | ||
|
9b078de9d8 | ||
|
214f9916d9 | ||
|
87aeffa160 | ||
|
14a2d2d795 | ||
|
3560bc62a2 | ||
|
0729d1db27 | ||
|
5f27d425bf | ||
|
e2956c391d | ||
|
bdc8f30705 | ||
|
2c9bf9f03a | ||
|
60c2964acd | ||
|
97248035b1 | ||
|
35c79a2cfb | ||
|
9b9762f940 | ||
|
e186fe8936 | ||
|
213cd419f0 | ||
|
b89b5fef65 |
101
.eslintignore
@@ -59,6 +59,8 @@ packages/app-mobile/locales
|
||||
packages/app-mobile/node_modules
|
||||
packages/app-mobile/pluginAssets/
|
||||
packages/fork-*
|
||||
packages/default-plugins/plugin-base-repo/
|
||||
packages/default-plugins/plugin-sources/
|
||||
packages/htmlpack/dist/
|
||||
packages/lib/assets/
|
||||
packages/lib/lib/lib.js
|
||||
@@ -84,14 +86,37 @@ packages/lib/countable/Countable.js
|
||||
|
||||
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
|
||||
packages/app-cli/app/LinkSelector.js
|
||||
packages/app-cli/app/app.js
|
||||
packages/app-cli/app/base-command.js
|
||||
packages/app-cli/app/command-apidoc.js
|
||||
packages/app-cli/app/command-attach.js
|
||||
packages/app-cli/app/command-cat.js
|
||||
packages/app-cli/app/command-config.js
|
||||
packages/app-cli/app/command-cp.js
|
||||
packages/app-cli/app/command-done.test.js
|
||||
packages/app-cli/app/command-done.js
|
||||
packages/app-cli/app/command-dump.js
|
||||
packages/app-cli/app/command-e2ee.js
|
||||
packages/app-cli/app/command-edit.js
|
||||
packages/app-cli/app/command-exit.js
|
||||
packages/app-cli/app/command-export-sync-status.js
|
||||
packages/app-cli/app/command-export.js
|
||||
packages/app-cli/app/command-geoloc.js
|
||||
packages/app-cli/app/command-help.js
|
||||
packages/app-cli/app/command-import.js
|
||||
packages/app-cli/app/command-ls.js
|
||||
packages/app-cli/app/command-mkbook.test.js
|
||||
packages/app-cli/app/command-mkbook.js
|
||||
packages/app-cli/app/command-mv.js
|
||||
packages/app-cli/app/command-ren.js
|
||||
packages/app-cli/app/command-rmbook.js
|
||||
packages/app-cli/app/command-rmnote.js
|
||||
packages/app-cli/app/command-set.js
|
||||
packages/app-cli/app/command-settingschema.js
|
||||
packages/app-cli/app/command-sync.js
|
||||
packages/app-cli/app/command-testing.js
|
||||
packages/app-cli/app/command-use.js
|
||||
packages/app-cli/app/command-version.js
|
||||
packages/app-cli/app/gui/StatusBarWidget.js
|
||||
packages/app-cli/app/services/plugins/PluginRunner.js
|
||||
packages/app-cli/app/setupCommand.js
|
||||
@@ -222,7 +247,6 @@ packages/app-desktop/gui/Navigator.js
|
||||
packages/app-desktop/gui/NoteContentPropertiesDialog.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/Toolbar.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/index.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/setupVim.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/types.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useContextMenu.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useCursorUtils.test.js
|
||||
@@ -249,6 +273,8 @@ packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/styles/index.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/joplinCommandToTinyMceCommands.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/openEditDialog.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/shouldPasteResources.test.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/shouldPasteResources.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/types.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useContextMenu.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useScroll.js
|
||||
@@ -378,13 +404,16 @@ packages/app-desktop/gui/style/StyledTextInput.js
|
||||
packages/app-desktop/gui/utils/NoteListUtils.js
|
||||
packages/app-desktop/gui/utils/convertToScreenCoordinates.js
|
||||
packages/app-desktop/gui/utils/loadScript.js
|
||||
packages/app-desktop/gulpfile.js
|
||||
packages/app-desktop/integration-tests/main.spec.js
|
||||
packages/app-desktop/integration-tests/models/MainScreen.js
|
||||
packages/app-desktop/integration-tests/models/NoteEditorScreen.js
|
||||
packages/app-desktop/integration-tests/models/SettingsScreen.js
|
||||
packages/app-desktop/integration-tests/simpleBackup.spec.js
|
||||
packages/app-desktop/integration-tests/util/activateMainMenuItem.js
|
||||
packages/app-desktop/integration-tests/util/createStartupArgs.js
|
||||
packages/app-desktop/integration-tests/util/firstNonDevToolsWindow.js
|
||||
packages/app-desktop/integration-tests/util/setFilePickerResponse.js
|
||||
packages/app-desktop/integration-tests/util/test.js
|
||||
packages/app-desktop/playwright.config.js
|
||||
packages/app-desktop/plugins/GotoAnything.js
|
||||
@@ -412,8 +441,11 @@ packages/app-desktop/services/sortOrder/PerFolderSortOrderService.js
|
||||
packages/app-desktop/services/sortOrder/notesSortOrderUtils.test.js
|
||||
packages/app-desktop/services/sortOrder/notesSortOrderUtils.js
|
||||
packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js
|
||||
packages/app-desktop/tools/copy7Zip.js
|
||||
packages/app-desktop/tools/notarizeMacApp.js
|
||||
packages/app-desktop/tools/renameReleaseAssets.js
|
||||
packages/app-desktop/utils/7zip/getPathToExecutable7Zip.js
|
||||
packages/app-desktop/utils/7zip/pathToBundled7Zip.js
|
||||
packages/app-desktop/utils/checkForUpdatesUtils.test.js
|
||||
packages/app-desktop/utils/checkForUpdatesUtils.js
|
||||
packages/app-desktop/utils/checkForUpdatesUtilsTestData.js
|
||||
@@ -456,10 +488,15 @@ packages/app-mobile/components/NoteEditor/MarkdownToolbar/ToggleSpaceButton.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/Toolbar.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/ToolbarButton.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/ToolbarOverflowRows.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/buttons/useActionButtons.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/buttons/useHeaderButtons.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/buttons/useInlineFormattingButtons.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/buttons/useListButtons.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/types.js
|
||||
packages/app-mobile/components/NoteEditor/NoteEditor.test.js
|
||||
packages/app-mobile/components/NoteEditor/NoteEditor.js
|
||||
packages/app-mobile/components/NoteEditor/SearchPanel.js
|
||||
packages/app-mobile/components/NoteEditor/hooks/useKeyboardVisible.js
|
||||
packages/app-mobile/components/NoteEditor/types.js
|
||||
packages/app-mobile/components/NoteList.js
|
||||
packages/app-mobile/components/ProfileSwitcher/ProfileEditor.js
|
||||
@@ -527,21 +564,30 @@ packages/app-mobile/utils/fs-driver/runOnDeviceTests.js
|
||||
packages/app-mobile/utils/setupNotifications.js
|
||||
packages/app-mobile/utils/shareHandler.js
|
||||
packages/app-mobile/utils/types.js
|
||||
packages/default-plugins/build.js
|
||||
packages/default-plugins/buildDefaultPlugins.js
|
||||
packages/default-plugins/commands/buildAll.js
|
||||
packages/default-plugins/commands/editPatch.js
|
||||
packages/default-plugins/utils/getPathToPatchFileFor.js
|
||||
packages/default-plugins/utils/readRepositoryJson.js
|
||||
packages/default-plugins/utils/waitForCliInput.js
|
||||
packages/editor/CodeMirror/CodeMirror5Emulation/CodeMirror5Emulation.test.js
|
||||
packages/editor/CodeMirror/CodeMirror5Emulation/CodeMirror5Emulation.js
|
||||
packages/editor/CodeMirror/CodeMirror5Emulation/Decorator.js
|
||||
packages/editor/CodeMirror/CodeMirrorControl.test.js
|
||||
packages/editor/CodeMirror/CodeMirrorControl.js
|
||||
packages/editor/CodeMirror/PluginLoader.js
|
||||
packages/editor/CodeMirror/configFromSettings.js
|
||||
packages/editor/CodeMirror/createEditor.test.js
|
||||
packages/editor/CodeMirror/createEditor.js
|
||||
packages/editor/CodeMirror/editorCommands/editorCommands.js
|
||||
packages/editor/CodeMirror/editorCommands/insertLineAfter.test.js
|
||||
packages/editor/CodeMirror/editorCommands/insertLineAfter.js
|
||||
packages/editor/CodeMirror/editorCommands/supportsCommand.js
|
||||
packages/editor/CodeMirror/editorCommands/swapLine.js
|
||||
packages/editor/CodeMirror/getScrollFraction.js
|
||||
packages/editor/CodeMirror/markdown/computeSelectionFormatting.test.js
|
||||
packages/editor/CodeMirror/markdown/computeSelectionFormatting.js
|
||||
packages/editor/CodeMirror/markdown/decoratorExtension.test.js
|
||||
packages/editor/CodeMirror/markdown/decoratorExtension.js
|
||||
packages/editor/CodeMirror/markdown/markdownCommands.bulletedVsChecklist.test.js
|
||||
packages/editor/CodeMirror/markdown/markdownCommands.test.js
|
||||
@@ -552,12 +598,15 @@ packages/editor/CodeMirror/markdown/markdownMathParser.js
|
||||
packages/editor/CodeMirror/markdown/markdownReformatter.test.js
|
||||
packages/editor/CodeMirror/markdown/markdownReformatter.js
|
||||
packages/editor/CodeMirror/markdown/syntaxHighlightingLanguages.js
|
||||
packages/editor/CodeMirror/pluginApi/PluginLoader.js
|
||||
packages/editor/CodeMirror/pluginApi/codeMirrorRequire.js
|
||||
packages/editor/CodeMirror/testUtil/createEditorSettings.js
|
||||
packages/editor/CodeMirror/testUtil/createTestEditor.js
|
||||
packages/editor/CodeMirror/testUtil/forceFullParse.js
|
||||
packages/editor/CodeMirror/testUtil/loadLanguages.js
|
||||
packages/editor/CodeMirror/theme.js
|
||||
packages/editor/CodeMirror/util/isInSyntaxNode.js
|
||||
packages/editor/CodeMirror/util/setupVim.js
|
||||
packages/editor/SelectionFormatting.js
|
||||
packages/editor/events.js
|
||||
packages/editor/types.js
|
||||
@@ -623,6 +672,7 @@ packages/lib/components/shared/config/shouldShowMissingPasswordWarning.test.js
|
||||
packages/lib/components/shared/config/shouldShowMissingPasswordWarning.js
|
||||
packages/lib/components/shared/note-screen-shared.js
|
||||
packages/lib/components/shared/reduxSharedMiddleware.js
|
||||
packages/lib/components/shared/side-menu-shared.js
|
||||
packages/lib/database-driver-better-sqlite.js
|
||||
packages/lib/database.js
|
||||
packages/lib/debug/DebugService.js
|
||||
@@ -633,7 +683,9 @@ packages/lib/eventManager.js
|
||||
packages/lib/file-api-driver-joplinServer.js
|
||||
packages/lib/file-api-driver-memory.js
|
||||
packages/lib/file-api-driver.test.js
|
||||
packages/lib/file-api.test.js
|
||||
packages/lib/file-api.js
|
||||
packages/lib/folders-screen-utils.js
|
||||
packages/lib/fs-driver-base.js
|
||||
packages/lib/fs-driver-node.js
|
||||
packages/lib/fsDriver.test.js
|
||||
@@ -650,6 +702,7 @@ packages/lib/import-enex.js
|
||||
packages/lib/initLib.js
|
||||
packages/lib/locale.test.js
|
||||
packages/lib/locale.js
|
||||
packages/lib/makeDiscourseDebugUrl.test.js
|
||||
packages/lib/makeDiscourseDebugUrl.js
|
||||
packages/lib/markdownUtils.test.js
|
||||
packages/lib/markdownUtils.js
|
||||
@@ -680,6 +733,7 @@ packages/lib/models/Search.js
|
||||
packages/lib/models/Setting.test.js
|
||||
packages/lib/models/Setting.js
|
||||
packages/lib/models/SmartFilter.js
|
||||
packages/lib/models/Tag.test.js
|
||||
packages/lib/models/Tag.js
|
||||
packages/lib/models/dateTimeFormats.test.js
|
||||
packages/lib/models/settings/FileHandler.js
|
||||
@@ -688,11 +742,13 @@ packages/lib/models/utils/itemCanBeEncrypted.js
|
||||
packages/lib/models/utils/paginatedFeed.js
|
||||
packages/lib/models/utils/paginationToSql.js
|
||||
packages/lib/models/utils/readOnly.js
|
||||
packages/lib/models/utils/resourceUtils.js
|
||||
packages/lib/models/utils/types.js
|
||||
packages/lib/models/utils/userData.test.js
|
||||
packages/lib/models/utils/userData.js
|
||||
packages/lib/net-utils.js
|
||||
packages/lib/ntp.js
|
||||
packages/lib/onedrive-api.test.js
|
||||
packages/lib/onedrive-api.js
|
||||
packages/lib/path-utils.js
|
||||
packages/lib/reducer.js
|
||||
@@ -735,9 +791,13 @@ packages/lib/services/commands/propsHaveChanged.js
|
||||
packages/lib/services/commands/stateToWhenClauseContext.js
|
||||
packages/lib/services/contextkey/contextkey.js
|
||||
packages/lib/services/database/addMigrationFile.js
|
||||
packages/lib/services/database/isSqliteSyntaxError.js
|
||||
packages/lib/services/database/migrations/42.js
|
||||
packages/lib/services/database/migrations/43.js
|
||||
packages/lib/services/database/migrations/44.js
|
||||
packages/lib/services/database/migrations/45.js
|
||||
packages/lib/services/database/migrations/index.js
|
||||
packages/lib/services/database/sqlStringToLines.js
|
||||
packages/lib/services/database/types.js
|
||||
packages/lib/services/debug/populateDatabase.js
|
||||
packages/lib/services/e2ee/EncryptionService.test.js
|
||||
@@ -784,6 +844,13 @@ packages/lib/services/keychain/KeychainServiceDriverBase.js
|
||||
packages/lib/services/noteList/defaultLeftToRightListRenderer.js
|
||||
packages/lib/services/noteList/defaultListRenderer.js
|
||||
packages/lib/services/noteList/renderers.js
|
||||
packages/lib/services/ocr/OcrDriverBase.js
|
||||
packages/lib/services/ocr/OcrService.test.js
|
||||
packages/lib/services/ocr/OcrService.js
|
||||
packages/lib/services/ocr/drivers/OcrDriverTesseract.js
|
||||
packages/lib/services/ocr/utils/filterOcrText.test.js
|
||||
packages/lib/services/ocr/utils/filterOcrText.js
|
||||
packages/lib/services/ocr/utils/types.js
|
||||
packages/lib/services/plugins/BasePlatformImplementation.js
|
||||
packages/lib/services/plugins/BasePluginRunner.js
|
||||
packages/lib/services/plugins/MenuController.js
|
||||
@@ -861,15 +928,17 @@ packages/lib/services/rest/utils/paginatedResults.js
|
||||
packages/lib/services/rest/utils/readonlyProperties.js
|
||||
packages/lib/services/rest/utils/requestFields.js
|
||||
packages/lib/services/rest/utils/requestPaginationOptions.js
|
||||
packages/lib/services/searchengine/SearchEngine.js
|
||||
packages/lib/services/searchengine/SearchEngineUtils.test.js
|
||||
packages/lib/services/searchengine/SearchEngineUtils.js
|
||||
packages/lib/services/searchengine/SearchFilter.test.js
|
||||
packages/lib/services/searchengine/filterParser.test.js
|
||||
packages/lib/services/searchengine/filterParser.js
|
||||
packages/lib/services/searchengine/gotoAnythingStyleQuery.test.js
|
||||
packages/lib/services/searchengine/gotoAnythingStyleQuery.js
|
||||
packages/lib/services/searchengine/queryBuilder.js
|
||||
packages/lib/services/search/SearchEngine.resources.test.js
|
||||
packages/lib/services/search/SearchEngine.test.js
|
||||
packages/lib/services/search/SearchEngine.js
|
||||
packages/lib/services/search/SearchEngineUtils.test.js
|
||||
packages/lib/services/search/SearchEngineUtils.js
|
||||
packages/lib/services/search/SearchFilter.test.js
|
||||
packages/lib/services/search/filterParser.test.js
|
||||
packages/lib/services/search/filterParser.js
|
||||
packages/lib/services/search/gotoAnythingStyleQuery.test.js
|
||||
packages/lib/services/search/gotoAnythingStyleQuery.js
|
||||
packages/lib/services/search/queryBuilder.js
|
||||
packages/lib/services/share/ShareService.test.js
|
||||
packages/lib/services/share/ShareService.js
|
||||
packages/lib/services/share/reducer.js
|
||||
@@ -908,6 +977,7 @@ packages/lib/services/synchronizer/utils/handleSyncStartupOperation.js
|
||||
packages/lib/services/synchronizer/utils/resourceRemotePath.js
|
||||
packages/lib/services/synchronizer/utils/syncDeleteStep.js
|
||||
packages/lib/services/synchronizer/utils/types.js
|
||||
packages/lib/shim-init-node.js
|
||||
packages/lib/shim.js
|
||||
packages/lib/testing/syncTargetUtils.js
|
||||
packages/lib/testing/test-utils-synchronizer.js
|
||||
@@ -923,6 +993,7 @@ packages/lib/themes/solarizedDark.js
|
||||
packages/lib/themes/solarizedLight.js
|
||||
packages/lib/themes/type.js
|
||||
packages/lib/time.js
|
||||
packages/lib/types.js
|
||||
packages/lib/utils/credentialFiles.js
|
||||
packages/lib/utils/joplinCloud.js
|
||||
packages/lib/utils/processStartFlags.js
|
||||
@@ -964,6 +1035,8 @@ packages/plugin-repo-cli/lib/types.js
|
||||
packages/plugin-repo-cli/lib/updateReadme.test.js
|
||||
packages/plugin-repo-cli/lib/updateReadme.js
|
||||
packages/plugin-repo-cli/lib/utils.js
|
||||
packages/plugin-repo-cli/lib/validateUntrustedManifest.test.js
|
||||
packages/plugin-repo-cli/lib/validateUntrustedManifest.js
|
||||
packages/plugins/ToggleSidebars/api/index.js
|
||||
packages/plugins/ToggleSidebars/api/types.js
|
||||
packages/plugins/ToggleSidebars/src/index.js
|
||||
@@ -993,13 +1066,15 @@ packages/renderer/MdToHtml/rules/sanitize_html.js
|
||||
packages/renderer/MdToHtml/rules/source_map.js
|
||||
packages/renderer/MdToHtml/setupLinkify.js
|
||||
packages/renderer/MdToHtml/validateLinks.js
|
||||
packages/renderer/assetsToHeaders.js
|
||||
packages/renderer/defaultResourceModel.js
|
||||
packages/renderer/headerAnchor.js
|
||||
packages/renderer/highlight.js
|
||||
packages/renderer/htmlUtils.test.js
|
||||
packages/renderer/htmlUtils.js
|
||||
packages/renderer/index.js
|
||||
packages/renderer/noteStyle.js
|
||||
packages/renderer/pathUtils.js
|
||||
packages/renderer/types.js
|
||||
packages/renderer/utils.js
|
||||
packages/tools/build-release-stats.test.js
|
||||
packages/tools/build-release-stats.js
|
||||
@@ -1007,8 +1082,6 @@ packages/tools/build-translation.js
|
||||
packages/tools/build-welcome.js
|
||||
packages/tools/buildServerDocker.test.js
|
||||
packages/tools/buildServerDocker.js
|
||||
packages/tools/bundleDefaultPlugins.test.js
|
||||
packages/tools/bundleDefaultPlugins.js
|
||||
packages/tools/checkIgnoredFiles.js
|
||||
packages/tools/checkLibPaths.test.js
|
||||
packages/tools/checkLibPaths.js
|
||||
|
4
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
@@ -26,8 +26,8 @@ body:
|
||||
id: version
|
||||
attributes:
|
||||
label: "Joplin version"
|
||||
placeholder: "For example 2.3.6"
|
||||
description:
|
||||
placeholder: "For example 1.0.5"
|
||||
description: Please note that we will close the issue if the exact version is not provided, as we cannot help without this.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
52
.github/scripts/run_ci.sh
vendored
@@ -8,16 +8,23 @@ SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||
ROOT_DIR="$SCRIPT_DIR/../.."
|
||||
|
||||
IS_PULL_REQUEST=0
|
||||
IS_DEV_BRANCH=0
|
||||
IS_DESKTOP_RELEASE=0
|
||||
IS_SERVER_RELEASE=0
|
||||
IS_LINUX=0
|
||||
IS_MACOS=0
|
||||
|
||||
# If pull requests are coming from a branch of the main repository,
|
||||
# IS_PULL_REQUEST will be zero.
|
||||
if [ "$GITHUB_EVENT_NAME" == "pull_request" ]; then
|
||||
IS_PULL_REQUEST=1
|
||||
fi
|
||||
|
||||
if [ "$GITHUB_REF" == "refs/heads/dev" ]; then
|
||||
IS_DEV_BRANCH=1
|
||||
if [[ $GIT_TAG_NAME = $SERVER_TAG_PREFIX-* ]]; then
|
||||
IS_SERVER_RELEASE=1
|
||||
fi
|
||||
|
||||
if [[ $GIT_TAG_NAME = v* ]]; then
|
||||
IS_DESKTOP_RELEASE=1
|
||||
fi
|
||||
|
||||
if [ "$RUNNER_OS" == "Linux" ]; then
|
||||
@@ -28,6 +35,14 @@ else
|
||||
IS_MACOS=1
|
||||
fi
|
||||
|
||||
# Tests can randomly fail in some cases, so only run them when not publishing
|
||||
# a release
|
||||
RUN_TESTS=0
|
||||
|
||||
if [ "$IS_SERVER_RELEASE" = 0 ] && [ "$IS_DESKTOP_RELEASE" = 0 ]; then
|
||||
RUN_TESTS=1
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# Print environment
|
||||
# =============================================================================
|
||||
@@ -43,7 +58,9 @@ echo "SERVER_TAG_PREFIX=$SERVER_TAG_PREFIX"
|
||||
|
||||
echo "IS_CONTINUOUS_INTEGRATION=$IS_CONTINUOUS_INTEGRATION"
|
||||
echo "IS_PULL_REQUEST=$IS_PULL_REQUEST"
|
||||
echo "IS_DEV_BRANCH=$IS_DEV_BRANCH"
|
||||
echo "IS_DESKTOP_RELEASE=$IS_DESKTOP_RELEASE"
|
||||
echo "IS_SERVER_RELEASE=$IS_SERVER_RELEASE"
|
||||
echo "RUN_TESTS=$RUN_TESTS"
|
||||
echo "IS_LINUX=$IS_LINUX"
|
||||
echo "IS_MACOS=$IS_MACOS"
|
||||
|
||||
@@ -64,11 +81,10 @@ if [ $testResult -ne 0 ]; then
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# Run test units. Only do it for pull requests and dev branch because we don't
|
||||
# want it to randomly fail when trying to create a desktop release.
|
||||
# Run test units
|
||||
# =============================================================================
|
||||
|
||||
if [ "$IS_PULL_REQUEST" == "1" ] || [ "$IS_DEV_BRANCH" = "1" ]; then
|
||||
if [ "$RUN_TESTS" == "1" ]; then
|
||||
echo "Step: Running tests..."
|
||||
|
||||
# On Linux, we run the Joplin Server tests using PostgreSQL
|
||||
@@ -98,23 +114,12 @@ if [ "$IS_PULL_REQUEST" == "1" ] || [ "$IS_DEV_BRANCH" = "1" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# Check that the website builder can run without errors
|
||||
# =============================================================================
|
||||
|
||||
if [ "$IS_PULL_REQUEST" == "1" ] || [ "$IS_DEV_BRANCH" = "1" ]; then
|
||||
if [ "$IS_LINUX" == "1" ]; then
|
||||
echo "Step: Running website builder..."
|
||||
node packages/tools/website/processDocs.js --env dev
|
||||
fi
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# Run linter for pull requests only. We also don't want this to make the desktop
|
||||
# release randomly fail.
|
||||
# =============================================================================
|
||||
|
||||
if [ "$IS_PULL_REQUEST" == "1" ] || [ "$IS_DEV_BRANCH" = "1" ]; then
|
||||
if [ "$RUN_TESTS" == "1" ]; then
|
||||
echo "Step: Running linter..."
|
||||
|
||||
yarn run linter-ci ./
|
||||
@@ -154,7 +159,7 @@ fi
|
||||
# what commit may have broken translation building.
|
||||
# =============================================================================
|
||||
|
||||
if [ "$IS_PULL_REQUEST" == "1" ] || [ "$IS_DEV_BRANCH" = "1" ]; then
|
||||
if [ "$RUN_TESTS" == "1" ]; then
|
||||
if [ "$IS_LINUX" == "1" ]; then
|
||||
echo "Step: Checking for lost translation strings..."
|
||||
|
||||
@@ -190,10 +195,11 @@ fi
|
||||
# Check that the website still builds
|
||||
# =============================================================================
|
||||
|
||||
if [ "$IS_PULL_REQUEST" == "1" ] || [ "$IS_DEV_BRANCH" = "1" ]; then
|
||||
if [ "$RUN_TESTS" == "1" ]; then
|
||||
echo "Step: Check that the website still builds..."
|
||||
|
||||
mkdir -p ../joplin-website/docs
|
||||
ll ../joplin-website/docs/api/references/plugin_api
|
||||
SKIP_SPONSOR_PROCESSING=1 yarn run buildWebsite
|
||||
testResult=$?
|
||||
if [ $testResult -ne 0 ]; then
|
||||
@@ -226,7 +232,7 @@ fi
|
||||
|
||||
cd "$ROOT_DIR/packages/app-desktop"
|
||||
|
||||
if [[ $GIT_TAG_NAME = v* ]]; then
|
||||
if [ "$IS_DESKTOP_RELEASE" == "1" ]; then
|
||||
echo "Step: Building and publishing desktop application..."
|
||||
# cd "$ROOT_DIR/packages/tools"
|
||||
# node bundleDefaultPlugins.js
|
||||
@@ -251,7 +257,7 @@ if [[ $GIT_TAG_NAME = v* ]]; then
|
||||
else
|
||||
USE_HARD_LINKS=false yarn run dist
|
||||
fi
|
||||
elif [[ $IS_LINUX = 1 ]] && [[ $GIT_TAG_NAME = $SERVER_TAG_PREFIX-* ]]; then
|
||||
elif [[ $IS_LINUX = 1 ]] && [ "$IS_SERVER_RELEASE" == "1" ]; then
|
||||
echo "Step: Building Docker Image..."
|
||||
cd "$ROOT_DIR"
|
||||
yarn run buildServerDocker --tag-name $GIT_TAG_NAME --push-images --repository $SERVER_REPOSITORY
|
||||
|
2
.github/workflows/build-android.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
sudo apt-get update || true
|
||||
sudo apt-get install -y libsecret-1-dev
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
|
8
.github/workflows/build-macos-m1.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- uses: olegtarasov/get-tag@v2.1
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
@@ -33,6 +33,12 @@ jobs:
|
||||
# https://yarnpkg.com/getting-started/install
|
||||
corepack enable
|
||||
|
||||
- name: Install macOs dependencies
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
# Required for building the canvas package
|
||||
brew install pango
|
||||
|
||||
# See github-action-main.yml for explanation
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
|
9
.github/workflows/check-pr-title.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
name: Check pull request title
|
||||
on: [pull_request]
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: Slashgear/action-check-pr-title@v4.3.0
|
||||
with:
|
||||
regexp: "(Desktop|Mobile|All|Cli|Tools|Chore|Clipper|Server|Android|iOS|Plugins|CI|Plugin Repo|Doc): (Fixes|Resolves) #[0-9]+: .+"
|
14
.github/workflows/comment-on-failure.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
name: comment-on-failure
|
||||
on:
|
||||
workflow_run:
|
||||
workflows:
|
||||
- Joplin Continuous Integration
|
||||
- react-native-android-build-apk
|
||||
- Build macOS M1
|
||||
types: [ completed ]
|
||||
|
||||
jobs:
|
||||
comment-failure:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: quipper/comment-failure-action@v0.1.1
|
12
.github/workflows/github-actions-main.yml
vendored
@@ -59,6 +59,12 @@ jobs:
|
||||
# testing.
|
||||
sudo apt-get install -y xvfb
|
||||
|
||||
- name: Install macOs dependencies
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
# Required for building the canvas package
|
||||
brew install pango
|
||||
|
||||
- name: Install Docker Engine
|
||||
# if: runner.os == 'Linux' && startsWith(github.ref, 'refs/tags/server-v')
|
||||
if: runner.os == 'Linux'
|
||||
@@ -75,7 +81,7 @@ jobs:
|
||||
sudo apt-get update || true
|
||||
sudo apt-get install -y docker-ce docker-ce-cli containerd.io
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- uses: olegtarasov/get-tag@v2.1
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
@@ -92,7 +98,7 @@ jobs:
|
||||
# Login to Docker only if we're on a server release tag. If we run this on
|
||||
# a pull request it will fail because the PR doesn't have access to
|
||||
# secrets
|
||||
- uses: docker/login-action@v2
|
||||
- uses: docker/login-action@v3
|
||||
if: runner.os == 'Linux' && startsWith(github.ref, 'refs/tags/server-v')
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
@@ -174,7 +180,7 @@ jobs:
|
||||
sudo apt-get update || true
|
||||
sudo apt-get install -y docker-ce docker-ce-cli containerd.io
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
|
99
.gitignore
vendored
@@ -66,14 +66,37 @@ docs/**/*.mustache
|
||||
|
||||
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
|
||||
packages/app-cli/app/LinkSelector.js
|
||||
packages/app-cli/app/app.js
|
||||
packages/app-cli/app/base-command.js
|
||||
packages/app-cli/app/command-apidoc.js
|
||||
packages/app-cli/app/command-attach.js
|
||||
packages/app-cli/app/command-cat.js
|
||||
packages/app-cli/app/command-config.js
|
||||
packages/app-cli/app/command-cp.js
|
||||
packages/app-cli/app/command-done.test.js
|
||||
packages/app-cli/app/command-done.js
|
||||
packages/app-cli/app/command-dump.js
|
||||
packages/app-cli/app/command-e2ee.js
|
||||
packages/app-cli/app/command-edit.js
|
||||
packages/app-cli/app/command-exit.js
|
||||
packages/app-cli/app/command-export-sync-status.js
|
||||
packages/app-cli/app/command-export.js
|
||||
packages/app-cli/app/command-geoloc.js
|
||||
packages/app-cli/app/command-help.js
|
||||
packages/app-cli/app/command-import.js
|
||||
packages/app-cli/app/command-ls.js
|
||||
packages/app-cli/app/command-mkbook.test.js
|
||||
packages/app-cli/app/command-mkbook.js
|
||||
packages/app-cli/app/command-mv.js
|
||||
packages/app-cli/app/command-ren.js
|
||||
packages/app-cli/app/command-rmbook.js
|
||||
packages/app-cli/app/command-rmnote.js
|
||||
packages/app-cli/app/command-set.js
|
||||
packages/app-cli/app/command-settingschema.js
|
||||
packages/app-cli/app/command-sync.js
|
||||
packages/app-cli/app/command-testing.js
|
||||
packages/app-cli/app/command-use.js
|
||||
packages/app-cli/app/command-version.js
|
||||
packages/app-cli/app/gui/StatusBarWidget.js
|
||||
packages/app-cli/app/services/plugins/PluginRunner.js
|
||||
packages/app-cli/app/setupCommand.js
|
||||
@@ -204,7 +227,6 @@ packages/app-desktop/gui/Navigator.js
|
||||
packages/app-desktop/gui/NoteContentPropertiesDialog.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/Toolbar.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/index.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/setupVim.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/types.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useContextMenu.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useCursorUtils.test.js
|
||||
@@ -231,6 +253,8 @@ packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/styles/index.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/joplinCommandToTinyMceCommands.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/openEditDialog.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/shouldPasteResources.test.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/shouldPasteResources.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/types.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useContextMenu.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useScroll.js
|
||||
@@ -360,13 +384,16 @@ packages/app-desktop/gui/style/StyledTextInput.js
|
||||
packages/app-desktop/gui/utils/NoteListUtils.js
|
||||
packages/app-desktop/gui/utils/convertToScreenCoordinates.js
|
||||
packages/app-desktop/gui/utils/loadScript.js
|
||||
packages/app-desktop/gulpfile.js
|
||||
packages/app-desktop/integration-tests/main.spec.js
|
||||
packages/app-desktop/integration-tests/models/MainScreen.js
|
||||
packages/app-desktop/integration-tests/models/NoteEditorScreen.js
|
||||
packages/app-desktop/integration-tests/models/SettingsScreen.js
|
||||
packages/app-desktop/integration-tests/simpleBackup.spec.js
|
||||
packages/app-desktop/integration-tests/util/activateMainMenuItem.js
|
||||
packages/app-desktop/integration-tests/util/createStartupArgs.js
|
||||
packages/app-desktop/integration-tests/util/firstNonDevToolsWindow.js
|
||||
packages/app-desktop/integration-tests/util/setFilePickerResponse.js
|
||||
packages/app-desktop/integration-tests/util/test.js
|
||||
packages/app-desktop/playwright.config.js
|
||||
packages/app-desktop/plugins/GotoAnything.js
|
||||
@@ -394,8 +421,11 @@ packages/app-desktop/services/sortOrder/PerFolderSortOrderService.js
|
||||
packages/app-desktop/services/sortOrder/notesSortOrderUtils.test.js
|
||||
packages/app-desktop/services/sortOrder/notesSortOrderUtils.js
|
||||
packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js
|
||||
packages/app-desktop/tools/copy7Zip.js
|
||||
packages/app-desktop/tools/notarizeMacApp.js
|
||||
packages/app-desktop/tools/renameReleaseAssets.js
|
||||
packages/app-desktop/utils/7zip/getPathToExecutable7Zip.js
|
||||
packages/app-desktop/utils/7zip/pathToBundled7Zip.js
|
||||
packages/app-desktop/utils/checkForUpdatesUtils.test.js
|
||||
packages/app-desktop/utils/checkForUpdatesUtils.js
|
||||
packages/app-desktop/utils/checkForUpdatesUtilsTestData.js
|
||||
@@ -438,10 +468,15 @@ packages/app-mobile/components/NoteEditor/MarkdownToolbar/ToggleSpaceButton.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/Toolbar.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/ToolbarButton.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/ToolbarOverflowRows.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/buttons/useActionButtons.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/buttons/useHeaderButtons.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/buttons/useInlineFormattingButtons.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/buttons/useListButtons.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/types.js
|
||||
packages/app-mobile/components/NoteEditor/NoteEditor.test.js
|
||||
packages/app-mobile/components/NoteEditor/NoteEditor.js
|
||||
packages/app-mobile/components/NoteEditor/SearchPanel.js
|
||||
packages/app-mobile/components/NoteEditor/hooks/useKeyboardVisible.js
|
||||
packages/app-mobile/components/NoteEditor/types.js
|
||||
packages/app-mobile/components/NoteList.js
|
||||
packages/app-mobile/components/ProfileSwitcher/ProfileEditor.js
|
||||
@@ -509,21 +544,30 @@ packages/app-mobile/utils/fs-driver/runOnDeviceTests.js
|
||||
packages/app-mobile/utils/setupNotifications.js
|
||||
packages/app-mobile/utils/shareHandler.js
|
||||
packages/app-mobile/utils/types.js
|
||||
packages/default-plugins/build.js
|
||||
packages/default-plugins/buildDefaultPlugins.js
|
||||
packages/default-plugins/commands/buildAll.js
|
||||
packages/default-plugins/commands/editPatch.js
|
||||
packages/default-plugins/utils/getPathToPatchFileFor.js
|
||||
packages/default-plugins/utils/readRepositoryJson.js
|
||||
packages/default-plugins/utils/waitForCliInput.js
|
||||
packages/editor/CodeMirror/CodeMirror5Emulation/CodeMirror5Emulation.test.js
|
||||
packages/editor/CodeMirror/CodeMirror5Emulation/CodeMirror5Emulation.js
|
||||
packages/editor/CodeMirror/CodeMirror5Emulation/Decorator.js
|
||||
packages/editor/CodeMirror/CodeMirrorControl.test.js
|
||||
packages/editor/CodeMirror/CodeMirrorControl.js
|
||||
packages/editor/CodeMirror/PluginLoader.js
|
||||
packages/editor/CodeMirror/configFromSettings.js
|
||||
packages/editor/CodeMirror/createEditor.test.js
|
||||
packages/editor/CodeMirror/createEditor.js
|
||||
packages/editor/CodeMirror/editorCommands/editorCommands.js
|
||||
packages/editor/CodeMirror/editorCommands/insertLineAfter.test.js
|
||||
packages/editor/CodeMirror/editorCommands/insertLineAfter.js
|
||||
packages/editor/CodeMirror/editorCommands/supportsCommand.js
|
||||
packages/editor/CodeMirror/editorCommands/swapLine.js
|
||||
packages/editor/CodeMirror/getScrollFraction.js
|
||||
packages/editor/CodeMirror/markdown/computeSelectionFormatting.test.js
|
||||
packages/editor/CodeMirror/markdown/computeSelectionFormatting.js
|
||||
packages/editor/CodeMirror/markdown/decoratorExtension.test.js
|
||||
packages/editor/CodeMirror/markdown/decoratorExtension.js
|
||||
packages/editor/CodeMirror/markdown/markdownCommands.bulletedVsChecklist.test.js
|
||||
packages/editor/CodeMirror/markdown/markdownCommands.test.js
|
||||
@@ -534,12 +578,15 @@ packages/editor/CodeMirror/markdown/markdownMathParser.js
|
||||
packages/editor/CodeMirror/markdown/markdownReformatter.test.js
|
||||
packages/editor/CodeMirror/markdown/markdownReformatter.js
|
||||
packages/editor/CodeMirror/markdown/syntaxHighlightingLanguages.js
|
||||
packages/editor/CodeMirror/pluginApi/PluginLoader.js
|
||||
packages/editor/CodeMirror/pluginApi/codeMirrorRequire.js
|
||||
packages/editor/CodeMirror/testUtil/createEditorSettings.js
|
||||
packages/editor/CodeMirror/testUtil/createTestEditor.js
|
||||
packages/editor/CodeMirror/testUtil/forceFullParse.js
|
||||
packages/editor/CodeMirror/testUtil/loadLanguages.js
|
||||
packages/editor/CodeMirror/theme.js
|
||||
packages/editor/CodeMirror/util/isInSyntaxNode.js
|
||||
packages/editor/CodeMirror/util/setupVim.js
|
||||
packages/editor/SelectionFormatting.js
|
||||
packages/editor/events.js
|
||||
packages/editor/types.js
|
||||
@@ -605,6 +652,7 @@ packages/lib/components/shared/config/shouldShowMissingPasswordWarning.test.js
|
||||
packages/lib/components/shared/config/shouldShowMissingPasswordWarning.js
|
||||
packages/lib/components/shared/note-screen-shared.js
|
||||
packages/lib/components/shared/reduxSharedMiddleware.js
|
||||
packages/lib/components/shared/side-menu-shared.js
|
||||
packages/lib/database-driver-better-sqlite.js
|
||||
packages/lib/database.js
|
||||
packages/lib/debug/DebugService.js
|
||||
@@ -615,7 +663,9 @@ packages/lib/eventManager.js
|
||||
packages/lib/file-api-driver-joplinServer.js
|
||||
packages/lib/file-api-driver-memory.js
|
||||
packages/lib/file-api-driver.test.js
|
||||
packages/lib/file-api.test.js
|
||||
packages/lib/file-api.js
|
||||
packages/lib/folders-screen-utils.js
|
||||
packages/lib/fs-driver-base.js
|
||||
packages/lib/fs-driver-node.js
|
||||
packages/lib/fsDriver.test.js
|
||||
@@ -632,6 +682,7 @@ packages/lib/import-enex.js
|
||||
packages/lib/initLib.js
|
||||
packages/lib/locale.test.js
|
||||
packages/lib/locale.js
|
||||
packages/lib/makeDiscourseDebugUrl.test.js
|
||||
packages/lib/makeDiscourseDebugUrl.js
|
||||
packages/lib/markdownUtils.test.js
|
||||
packages/lib/markdownUtils.js
|
||||
@@ -662,6 +713,7 @@ packages/lib/models/Search.js
|
||||
packages/lib/models/Setting.test.js
|
||||
packages/lib/models/Setting.js
|
||||
packages/lib/models/SmartFilter.js
|
||||
packages/lib/models/Tag.test.js
|
||||
packages/lib/models/Tag.js
|
||||
packages/lib/models/dateTimeFormats.test.js
|
||||
packages/lib/models/settings/FileHandler.js
|
||||
@@ -670,11 +722,13 @@ packages/lib/models/utils/itemCanBeEncrypted.js
|
||||
packages/lib/models/utils/paginatedFeed.js
|
||||
packages/lib/models/utils/paginationToSql.js
|
||||
packages/lib/models/utils/readOnly.js
|
||||
packages/lib/models/utils/resourceUtils.js
|
||||
packages/lib/models/utils/types.js
|
||||
packages/lib/models/utils/userData.test.js
|
||||
packages/lib/models/utils/userData.js
|
||||
packages/lib/net-utils.js
|
||||
packages/lib/ntp.js
|
||||
packages/lib/onedrive-api.test.js
|
||||
packages/lib/onedrive-api.js
|
||||
packages/lib/path-utils.js
|
||||
packages/lib/reducer.js
|
||||
@@ -717,9 +771,13 @@ packages/lib/services/commands/propsHaveChanged.js
|
||||
packages/lib/services/commands/stateToWhenClauseContext.js
|
||||
packages/lib/services/contextkey/contextkey.js
|
||||
packages/lib/services/database/addMigrationFile.js
|
||||
packages/lib/services/database/isSqliteSyntaxError.js
|
||||
packages/lib/services/database/migrations/42.js
|
||||
packages/lib/services/database/migrations/43.js
|
||||
packages/lib/services/database/migrations/44.js
|
||||
packages/lib/services/database/migrations/45.js
|
||||
packages/lib/services/database/migrations/index.js
|
||||
packages/lib/services/database/sqlStringToLines.js
|
||||
packages/lib/services/database/types.js
|
||||
packages/lib/services/debug/populateDatabase.js
|
||||
packages/lib/services/e2ee/EncryptionService.test.js
|
||||
@@ -766,6 +824,13 @@ packages/lib/services/keychain/KeychainServiceDriverBase.js
|
||||
packages/lib/services/noteList/defaultLeftToRightListRenderer.js
|
||||
packages/lib/services/noteList/defaultListRenderer.js
|
||||
packages/lib/services/noteList/renderers.js
|
||||
packages/lib/services/ocr/OcrDriverBase.js
|
||||
packages/lib/services/ocr/OcrService.test.js
|
||||
packages/lib/services/ocr/OcrService.js
|
||||
packages/lib/services/ocr/drivers/OcrDriverTesseract.js
|
||||
packages/lib/services/ocr/utils/filterOcrText.test.js
|
||||
packages/lib/services/ocr/utils/filterOcrText.js
|
||||
packages/lib/services/ocr/utils/types.js
|
||||
packages/lib/services/plugins/BasePlatformImplementation.js
|
||||
packages/lib/services/plugins/BasePluginRunner.js
|
||||
packages/lib/services/plugins/MenuController.js
|
||||
@@ -843,15 +908,17 @@ packages/lib/services/rest/utils/paginatedResults.js
|
||||
packages/lib/services/rest/utils/readonlyProperties.js
|
||||
packages/lib/services/rest/utils/requestFields.js
|
||||
packages/lib/services/rest/utils/requestPaginationOptions.js
|
||||
packages/lib/services/searchengine/SearchEngine.js
|
||||
packages/lib/services/searchengine/SearchEngineUtils.test.js
|
||||
packages/lib/services/searchengine/SearchEngineUtils.js
|
||||
packages/lib/services/searchengine/SearchFilter.test.js
|
||||
packages/lib/services/searchengine/filterParser.test.js
|
||||
packages/lib/services/searchengine/filterParser.js
|
||||
packages/lib/services/searchengine/gotoAnythingStyleQuery.test.js
|
||||
packages/lib/services/searchengine/gotoAnythingStyleQuery.js
|
||||
packages/lib/services/searchengine/queryBuilder.js
|
||||
packages/lib/services/search/SearchEngine.resources.test.js
|
||||
packages/lib/services/search/SearchEngine.test.js
|
||||
packages/lib/services/search/SearchEngine.js
|
||||
packages/lib/services/search/SearchEngineUtils.test.js
|
||||
packages/lib/services/search/SearchEngineUtils.js
|
||||
packages/lib/services/search/SearchFilter.test.js
|
||||
packages/lib/services/search/filterParser.test.js
|
||||
packages/lib/services/search/filterParser.js
|
||||
packages/lib/services/search/gotoAnythingStyleQuery.test.js
|
||||
packages/lib/services/search/gotoAnythingStyleQuery.js
|
||||
packages/lib/services/search/queryBuilder.js
|
||||
packages/lib/services/share/ShareService.test.js
|
||||
packages/lib/services/share/ShareService.js
|
||||
packages/lib/services/share/reducer.js
|
||||
@@ -890,6 +957,7 @@ packages/lib/services/synchronizer/utils/handleSyncStartupOperation.js
|
||||
packages/lib/services/synchronizer/utils/resourceRemotePath.js
|
||||
packages/lib/services/synchronizer/utils/syncDeleteStep.js
|
||||
packages/lib/services/synchronizer/utils/types.js
|
||||
packages/lib/shim-init-node.js
|
||||
packages/lib/shim.js
|
||||
packages/lib/testing/syncTargetUtils.js
|
||||
packages/lib/testing/test-utils-synchronizer.js
|
||||
@@ -905,6 +973,7 @@ packages/lib/themes/solarizedDark.js
|
||||
packages/lib/themes/solarizedLight.js
|
||||
packages/lib/themes/type.js
|
||||
packages/lib/time.js
|
||||
packages/lib/types.js
|
||||
packages/lib/utils/credentialFiles.js
|
||||
packages/lib/utils/joplinCloud.js
|
||||
packages/lib/utils/processStartFlags.js
|
||||
@@ -946,6 +1015,8 @@ packages/plugin-repo-cli/lib/types.js
|
||||
packages/plugin-repo-cli/lib/updateReadme.test.js
|
||||
packages/plugin-repo-cli/lib/updateReadme.js
|
||||
packages/plugin-repo-cli/lib/utils.js
|
||||
packages/plugin-repo-cli/lib/validateUntrustedManifest.test.js
|
||||
packages/plugin-repo-cli/lib/validateUntrustedManifest.js
|
||||
packages/plugins/ToggleSidebars/api/index.js
|
||||
packages/plugins/ToggleSidebars/api/types.js
|
||||
packages/plugins/ToggleSidebars/src/index.js
|
||||
@@ -975,13 +1046,15 @@ packages/renderer/MdToHtml/rules/sanitize_html.js
|
||||
packages/renderer/MdToHtml/rules/source_map.js
|
||||
packages/renderer/MdToHtml/setupLinkify.js
|
||||
packages/renderer/MdToHtml/validateLinks.js
|
||||
packages/renderer/assetsToHeaders.js
|
||||
packages/renderer/defaultResourceModel.js
|
||||
packages/renderer/headerAnchor.js
|
||||
packages/renderer/highlight.js
|
||||
packages/renderer/htmlUtils.test.js
|
||||
packages/renderer/htmlUtils.js
|
||||
packages/renderer/index.js
|
||||
packages/renderer/noteStyle.js
|
||||
packages/renderer/pathUtils.js
|
||||
packages/renderer/types.js
|
||||
packages/renderer/utils.js
|
||||
packages/tools/build-release-stats.test.js
|
||||
packages/tools/build-release-stats.js
|
||||
@@ -989,8 +1062,6 @@ packages/tools/build-translation.js
|
||||
packages/tools/build-welcome.js
|
||||
packages/tools/buildServerDocker.test.js
|
||||
packages/tools/buildServerDocker.js
|
||||
packages/tools/bundleDefaultPlugins.test.js
|
||||
packages/tools/bundleDefaultPlugins.js
|
||||
packages/tools/checkIgnoredFiles.js
|
||||
packages/tools/checkLibPaths.test.js
|
||||
packages/tools/checkLibPaths.js
|
||||
|
@@ -1,3 +1,5 @@
|
||||
packages/app-clipper/popup/
|
||||
packages/app-cli/tests/support/plugins/
|
||||
packages/doc-builder/
|
||||
packages/doc-builder/
|
||||
packages/default-plugins/plugin-base-repo/
|
||||
packages/default-plugins/plugin-sources/
|
12
.yarn/patches/nanoid-npm-3.3.7-98824ba130.patch
Normal file
@@ -0,0 +1,12 @@
|
||||
diff --git a/package.json b/package.json
|
||||
index 4f24d9658ca167733dbe9c3fb3bcfc3f4e6d20c8..15062cc4eb3bc8e14b54b07a9c873e5a5c4b6ab8 100644
|
||||
--- a/package.json
|
||||
+++ b/package.json
|
||||
@@ -35,7 +35,6 @@
|
||||
"module": "index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
- "browser": "./index.browser.js",
|
||||
"require": {
|
||||
"types": "./index.d.cts",
|
||||
"default": "./index.cjs"
|
15
.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
# We remove the `canvas` optional dependency because electron-rebuild fails to build it, and
|
||||
# the `canvas` API is already part of Electron
|
||||
diff --git a/package.json b/package.json
|
||||
index 105811f53d508486e08a60dc1b6e437cd24d7427..dea6a4e6612c4a4006cc482e46ff5270dcfda1e5 100644
|
||||
--- a/package.json
|
||||
+++ b/package.json
|
||||
@@ -13,7 +13,6 @@
|
||||
"bugs": "https://github.com/mozilla/pdf.js/issues",
|
||||
"license": "Apache-2.0",
|
||||
"optionalDependencies": {
|
||||
- "canvas": "^2.11.2",
|
||||
"path2d-polyfill": "^2.0.1"
|
||||
},
|
||||
"browser": {
|
@@ -23,6 +23,7 @@ module.exports = {
|
||||
return `joplin.${camelCaseToDots(p)
|
||||
.replace(/menu\.items/, 'menuItems')
|
||||
.replace(/toolbar\.buttons/, 'toolbarButtons')
|
||||
.replace(/note\.list/, 'noteList')
|
||||
.replace(/content\.scripts/, 'contentScripts')}`;
|
||||
},
|
||||
|
||||
|
BIN
Assets/WebsiteAssets/images/news/20231203-drawing.png
Normal file
After Width: | Height: | Size: 973 KiB |
BIN
Assets/WebsiteAssets/images/news/20231203-rte-table.png
Normal file
After Width: | Height: | Size: 54 KiB |
BIN
Assets/WebsiteAssets/images/news/20231203-settings.png
Normal file
After Width: | Height: | Size: 143 KiB |
BIN
Assets/WebsiteAssets/images/ocr/search_results.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
Assets/WebsiteAssets/images/publish_note/Banner.png
Normal file
After Width: | Height: | Size: 99 KiB |
BIN
Assets/WebsiteAssets/images/sponsors/Celebian.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
Assets/WebsiteAssets/images/sponsors/SortedTravel.png
Normal file
After Width: | Height: | Size: 21 KiB |
@@ -1,4 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Joplin]]></title><description><![CDATA[Joplin, the open source note-taking application]]></description><link>https://joplinapp.org</link><generator>RSS for Node</generator><lastBuildDate>Mon, 23 Oct 2023 00:00:00 GMT</lastBuildDate><atom:link href="https://joplinapp.org/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Mon, 23 Oct 2023 00:00:00 GMT</pubDate><item><title><![CDATA[Working in the shadows with white-hat hackers]]></title><description><![CDATA[<p>The majority of Joplin's development is carried out in the public domain. This includes the discussion of issues on GitHub, as well as the submission of pull requests and related discussions. The transparency of these processes allows for collaborative problem-solving and shared insights.</p>
|
||||
<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Joplin]]></title><description><![CDATA[Joplin, the open source note-taking application]]></description><link>https://joplinapp.org</link><generator>RSS for Node</generator><lastBuildDate>Sat, 23 Dec 2023 00:00:00 GMT</lastBuildDate><atom:link href="https://joplinapp.org/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Sat, 23 Dec 2023 00:00:00 GMT</pubDate><item><title><![CDATA[Faster synchronisation in upcoming Joplin version!]]></title><description><![CDATA[<p>The next version of Joplin Cloud (and Joplin Server) will include a change that will make synchronisation, in particular when synchronising a new device for the first time, significantly faster. This is achieved by bundling more data with the calls that retrieve notes and other data, thus reducing the number of unnecessary requests.</p>
|
||||
<p>This change will be applied soon to Joplin Cloud (and Server), and the Joplin mobile, desktop and CLI applications will be able to use it from version 2.14.</p>
|
||||
<p>In my tests with about 26,000 items, synchronisation was more than twice as fast on Joplin Cloud (22.5 minutes vs 9.5 minutes):</p>
|
||||
<h2>Before<a name="before" href="#before" class="heading-anchor">🔗</a></h2>
|
||||
<p>Created local items: 21814. Fetched items: 26591/26591. Completed: 23/12/2023 10:38 (<strong>1346s</strong>)</p>
|
||||
<p><strong>real 22m35.810s</strong><br/><br>
|
||||
user 3m19.182s<br/><br>
|
||||
sys 1m24.207s</p>
|
||||
<h2>Optimised<a name="optimised" href="#optimised" class="heading-anchor">🔗</a></h2>
|
||||
<p>Created local items: 21822. Fetched items: 26600/26600. Completed: 23/12/2023 11:48 (<strong>571s</strong>)</p>
|
||||
<p><strong>real 9m38.932s</strong><br/><br>
|
||||
user 1m10.119s<br/><br>
|
||||
sys 0m38.013s</p>
|
||||
]]></description><link>https://joplinapp.org/news/20231223-faster-sync</link><guid isPermaLink="false">20231223-faster-sync</guid><pubDate>Sat, 23 Dec 2023 00:00:00 GMT</pubDate><twitter-text>Faster synchronisation in upcoming Joplin version!</twitter-text></item><item><title><![CDATA[What's new in Joplin 2.13]]></title><description><![CDATA[<h2>Desktop<a name="desktop" href="#desktop" class="heading-anchor">🔗</a></h2>
|
||||
<h3>Rich Text Editor improvements<a name="rich-text-editor-improvements" href="#rich-text-editor-improvements" class="heading-anchor">🔗</a></h3>
|
||||
<p>In this update, significant enhancements have been implemented in the Rich Text Editor to enhance user experience. One notable improvement is the ability to seamlessly embed various content within tables, including lists and other tables, which was a frequently requested feature:</p>
|
||||
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20231203-rte-table.png" alt=""></p>
|
||||
<p>The interoperability with other text editors such as Word and Excel has also been improved, in particular when copying and pasting content between these tools and Joplin.</p>
|
||||
<h3>Improved ENEX import<a name="improved-enex-import" href="#improved-enex-import" class="heading-anchor">🔗</a></h3>
|
||||
<p>The Evernote Export format, known as ENEX, has always been challenging to support. This is because Evernote frequently changes it, breaking importers, and may also occasionally introduce bugs. At times, their own application struggles to import back files that it has exported, highlighting the volatility of the ENEX format.</p>
|
||||
<p>After undergoing continuous improvements for seven years, the importer has achieved a relatively stable state. The majority of issues encountered now stem from invalid files generated by Evernote, a result of bugs introduced in recent versions. Despite these challenges, we strive to enhance the import process by employing various heuristics to process the invalid data as effectively as possible.</p>
|
||||
<p>The list of bug fixes is below:</p>
|
||||
<ul>
|
||||
<li>Apply correct size to images imported from ENEX files</li>
|
||||
<li>ENEX files that contain resources with invalid mime types are imported correctly</li>
|
||||
<li>Ensure that ENEX resources with invalid filenames are imported correctly</li>
|
||||
<li>Prevent application from being stuck when importing an invalid ENEX file</li>
|
||||
</ul>
|
||||
<h3>New plugins APIs<a name="new-plugins-apis" href="#new-plugins-apis" class="heading-anchor">🔗</a></h3>
|
||||
<h4>Note list<a name="note-list" href="#note-list" class="heading-anchor">🔗</a></h4>
|
||||
<p>We are continuously improving the plugin API to make it as easy as possible to customize the application.</p>
|
||||
<p>One of the main addition is support note list plugins - it allows any plugins to customise what the list should display each note.</p>
|
||||
<p>See the <a href="https://joplinapp.org/api/references/plugin_api/classes/joplinviewsnotelist.html">Joplin Plugin Note List API</a> and the <a href="https://github.com/laurent22/joplin/blob/dev/packages/app-cli/tests/support/plugins/note_list_renderer/src/index.ts">Note List Demo</a> for more information.</p>
|
||||
<p>The demo includes three examples of note lists: a fundamental "top to bottom" version, a sophisticated "left to right" variant that additionally displays a note thumbnail, and a demonstration of an interactive note list, allowing direct modification of the note title from the list.</p>
|
||||
<p>This API was developed based on the feedback we got and we hope it will be useful. We remain open to further input from users and plugin developers regarding any potential missing features and areas for improvement!</p>
|
||||
<h4>Imaging API<a name="imaging-api" href="#imaging-api" class="heading-anchor">🔗</a></h4>
|
||||
<p>The imaging API was created to allow plugin developers to easily load and manipulate images. For now it is possible to load PNG and JPG files, and to resize and crop them.</p>
|
||||
<p><a href="https://joplinapp.org/api/references/plugin_api/classes/joplinimaging.html">https://joplinapp.org/api/references/plugin_api/classes/joplinimaging.html</a></p>
|
||||
<h4>And more<a name="and-more" href="#and-more" class="heading-anchor">🔗</a></h4>
|
||||
<p>A few more APIs have been added such as one to <a href="https://joplinapp.org/api/references/plugin_api/classes/joplinviewsdialogs.html#showopendialog">open dialog to select a file or folder</a> and a way to <a href="https://joplinapp.org/api/references/plugin_api/enums/contentscripttype.html#getting-the-settings-from-the-renderer">access the plugin settings from a renderer script</a>.</p>
|
||||
<h3>New beta Markdown editor<a name="new-beta-markdown-editor" href="#new-beta-markdown-editor" class="heading-anchor">🔗</a></h3>
|
||||
<p>This version also features a new Markdown editor based on <a href="https://codemirror.net/">CodeMirror 6</a>. This change means that we'll be able to use the same editor on both the desktop and mobile application (which already uses CodeMirror 6), which will allow a more consistent user experience across devices.</p>
|
||||
<p>Another benefit is that, in a future version, it will allow us to support plugins on the mobile application. There are several other advantages that Henry <a href="https://discourse.joplinapp.org/t/pre-release-v2-13-is-now-available-updated-18-11-2023/32697/12?u=laurent">listed in this forum post</a>.</p>
|
||||
<h2>Mobile<a name="mobile" href="#mobile" class="heading-anchor">🔗</a></h2>
|
||||
<h3>Drawing option<a name="drawing-option" href="#drawing-option" class="heading-anchor">🔗</a></h3>
|
||||
<p>In this new version, Henry integrated the js-draw editor to the mobile app. Js-draw is a powerful freehand drawing library which allows you to draw using a pen, touch screen or mouse.</p>
|
||||
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20231203-drawing.png" alt=""></p>
|
||||
<p>The editor is integrated to the mobile application. On desktop application, you can use the same library and edit your drawings using the <a href="https://github.com/personalizedrefrigerator/joplin-plugin-freehand-drawing">Freehand Drawing plugin</a>. We will make this plugin available by default in a future desktop release.</p>
|
||||
<h3>Setting screen<a name="setting-screen" href="#setting-screen" class="heading-anchor">🔗</a></h3>
|
||||
<p>The mobile version also features a redesigned configuration screen. It is now divided into multiple sections with clear titles, and you can also search for settings using the built-in search option.</p>
|
||||
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20231203-settings.png" alt=""></p>
|
||||
<h1>Full changelog<a name="full-changelog" href="#full-changelog" class="heading-anchor">🔗</a></h1>
|
||||
<p>This is just an overview of the main features. The full changelog is available there:</p>
|
||||
<ul>
|
||||
<li>Desktop: <a href="https://joplinapp.org/help/about/changelog/desktop">https://joplinapp.org/help/about/changelog/desktop</a></li>
|
||||
<li>Android: <a href="https://joplinapp.org/help/about/changelog/android/">https://joplinapp.org/help/about/changelog/android/</a></li>
|
||||
<li>iOS: <a href="https://joplinapp.org/help/about/changelog/ios/">https://joplinapp.org/help/about/changelog/ios/</a></li>
|
||||
</ul>
|
||||
]]></description><link>https://joplinapp.org/news/20231204-release-2-13</link><guid isPermaLink="false">20231204-release-2-13</guid><pubDate>Mon, 04 Dec 2023 00:00:00 GMT</pubDate><twitter-text>What's new in Joplin 2.13</twitter-text></item><item><title><![CDATA[Working in the shadows with white-hat hackers]]></title><description><![CDATA[<p>The majority of Joplin's development is carried out in the public domain. This includes the discussion of issues on GitHub, as well as the submission of pull requests and related discussions. The transparency of these processes allows for collaborative problem-solving and shared insights.</p>
|
||||
<p>However, there is one aspect that operates behind closed doors, and for good reason: addressing cybersecurity vulnerabilities. It is imperative that these issues remain undisclosed until they have been resolved. Once a solution is implemented, it is usually accompanied by discreet commits and a message in the changelog to signify the progress made.</p>
|
||||
<p>Typically, the process begins with an email from a security researcher. They provide valuable insights, such as a specially crafted note that triggers a bug, or an API call, along with an explanation of how the application's security can be circumvented. We examine the vulnerability, create a fix, and create automated test units to prevent any accidental reintroduction of the vulnerability in future code updates. An example of such a commit is: <a href="https://github.com/laurent22/joplin/commit/9e90d9016daf79b5414646a93fd369aedb035071">9e90d9016daf79b5414646a93fd369aedb035071</a></p>
|
||||
<p>We then share our fix with the researcher for validation. Additionally, we often apply the fix to previous versions of Joplin, depending on the severity of the vulnerability.</p>
|
||||
@@ -26,7 +84,7 @@
|
||||
<p>In this release, we've undertaken numerous enhancements and addressed various bugs in the Rich Text editor. Notably, we've introduced support for plugin toolbar icons. Additionally, we've refined the editor's ability to manage text that's copied from applications like Word, Office, and LibreOffice, thereby enhancing cross-application compatibility. Among the minor yet impactful improvements, we've fine-tuned the handling of newlines and paragraphs—a highly requested feature. Another notable update is the improved automatic switching between light and dark modes.</p>
|
||||
<p>Altogether, this release encompasses around 12 significant changes for the Rich Text editor, and rest assured, there's more to come in the future!</p>
|
||||
<h3>Share permissions<a name="share-permissions" href="#share-permissions" class="heading-anchor">🔗</a></h3>
|
||||
<p>Using Joplin Cloud Pro and Teams, you now have the ability to customize the read and write permissions for the notebooks you share. You have the option to grant other users permission to edit the notes or share them as read-only. This ensures that you can confidently share a notebook without worrying about unintentional modifications by your friends or colleagues.</p>
|
||||
<p>Using Joplin Cloud Teams, you now have the ability to customize the read and write permissions for the notebooks you share. You have the option to grant other users permission to edit the notes or share them as read-only. This ensures that you can confidently share a notebook without worrying about unintentional modifications by your friends or colleagues.</p>
|
||||
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20230825-share-permissions.png" alt=""></p>
|
||||
<h3>Email to Note<a name="email-to-note" href="#email-to-note" class="heading-anchor">🔗</a></h3>
|
||||
<p>Joplin Cloud Pro and Teams also now include the Email to Note feature, allowing you to conveniently store your emails within Joplin Cloud. By simply forwarding your emails to your Joplin Cloud address, you can transform them into notes. The email's subject will serve as the note title, while the body of the email will be the note's content. These notes will be organized within a notebook named "Inbox."</p>
|
||||
@@ -306,38 +364,4 @@
|
||||
<h2>Plugin API improvements<a name="plugin-api-improvements" href="#plugin-api-improvements" class="heading-anchor">🔗</a></h2>
|
||||
<p>This version also includes a number of improvements to the plugin API, in particular it is now easier to customise the editor context menu from a plugin and dynamically add items to it depending on the context. For example, with the Rich Markdown plugin it will be possible to right-click on an image and open it, or copy it to the clipboard.</p>
|
||||
<p>A few additional functions have also been added to make plugin development simpler - in particular a command to open any item, whether it's a notebook, note, tag or attachement; and functions to work with attachements, in particular to reveal an attachement in the system file explorer, and to track changes to an attachement.</p>
|
||||
]]></description><link>https://joplinapp.org/news/20220224-release-2-7</link><guid isPermaLink="false">20220224-release-2-7</guid><pubDate>Thu, 24 Feb 2022 00:00:00 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[Automatic deletion of disabled accounts on Joplin Cloud]]></title><description><![CDATA[<p>As of 15 Feb 2022, disabled accounts on Joplin Cloud will be automatically deleted after 90 days. A disabled account is one where the Stripe subscription has been cancelled either by the user or automatically (eg for unpaid invoices).</p>
|
||||
<p>Although it is an automated system, I will manually verify each account that's queued for deletion over the next few days for additional safety (for now everything's working as expected).</p>
|
||||
<p>When an account is queued for deletion, all notes, notebooks, tags, etc are removed from the system within 2 days, and permanently deleted within 7 days. User information, in particular email and full name will be removed from the system within 2 days, but archived for an additional 90 days for legal reasons, after which they will be deleted too.</p>
|
||||
]]></description><link>https://joplinapp.org/news/20220215-142000</link><guid isPermaLink="false">20220215-142000</guid><pubDate>Tue, 15 Feb 2022 00:00:00 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[Joplin 2.6 is available!]]></title><description><![CDATA[<p>Many changes in this new release, available on mobile, desktop and CLI:</p>
|
||||
<p><strong>Per-notebook sort order and sort buttons</strong></p>
|
||||
<p>This new feature adds a number of changes to the way notes are sorted. The most visible one is the addition of a sort button above the note list - it allows sorting by modification date, creation date, title or by custom order, in either ascending or descending order:</p>
|
||||
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20211217-120324_0.png" alt=""></p>
|
||||
<p>By default, this sort order is going to apply to all notebooks, however you can now also assign a per-notebook sort order. In this case, any sort order will be apply to that notebook only. To enable this behaviour, simply right-click on a notebook and select "Toggle own sort order":</p>
|
||||
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20211217-120324_1.png" alt=""></p>
|
||||
<p>Thanks to Kenichi Kobayashi for developing this feature!</p>
|
||||
<p><strong>Support for notebook icons</strong></p>
|
||||
<p>It is now possible to associate icons with notebooks no both the desktop and mobile applications. To do so, right-click on a notebook and selected "Edit".</p>
|
||||
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20211217-120324_2.png" alt=""></p>
|
||||
<p>This will open the new notebook dialog from which can change the title and assign an icon. For now the icons are emojis but perhaps custom icons could be supported later on.</p>
|
||||
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20211217-120324_3.png" alt=""></p>
|
||||
<p><strong>Allow collaborating on encrypted notebooks using Joplin Cloud</strong></p>
|
||||
<p>Thanks to the encryption improvements in the previous Joplin versions it is now possible to share and collaborated on encrypted notebooks, when synchronising with Joplin Cloud (or Joplin Server).</p>
|
||||
<p>To get this working, you and the recipient will need to have Joplin 2.6 and the person who shares will need to have encryption enabled. After that most of the process is handled automatically by the apps - in particular it will automatically generate and share the required encryption keys for each users.</p>
|
||||
<p><strong>Improved synchronisation startup speed</strong></p>
|
||||
<p>Synchronisation is also a bit faster in this release due to an optimisation on the startup process. When syncing, the app needs to acquire a lock, which may be time consuming since it requires making multiple requests. This has now been optimised so that less requests are necessary and also each request consumes less resources. This will have a postive impact on Joplin Cloud in particular, but you should also see improvements with Joplin Server and smaller improvements with the other sync targets.</p>
|
||||
<p><strong>Improved Markdown editor split view scrolling</strong></p>
|
||||
<p>Kenichi Kobayashi made some great improvements to the Markdown editor scrolling in this release. The issue before was that the editor on the left and the viewer on the right would often not be in sync, in particular if the note contains several images and other media.</p>
|
||||
<p>With Kenichi's change the editor and viewer stay nicely in sync, regardless of the note content. In fact it looks a bit like magic when you scroll through large notes - notice in particular how each side appear to wait for the other or speed up in order to make sure both sides are aligned as well possible. Kenichi provides a nice technical documentation about the feature <a href="https://github.com/laurent22/joplin/pull/5512#issuecomment-931277022">here</a>.</p>
|
||||
<p><a href="https://www.youtube.com/watch?v=Wbs5XZR0oeU">https://www.youtube.com/watch?v=Wbs5XZR0oeU</a></p>
|
||||
<p><strong>Improved and optimised S3 synchronisation</strong></p>
|
||||
<p>Thanks to the efforts of Lee Matos, synchronisation with S3 is now more reliable and errors are also better handled. The underlying S3 SDK has also been upgraded from v2 to v3 which results in a smaller executable size (about 3-5 MB depending on the operating system)</p>
|
||||
<p><strong>Export notes as self-contained HTML files</strong></p>
|
||||
<p>Exporting a single note as HTML is now more user friendly as all images, scripts, styles and other attachments are all packed into a single HTML file (Previously it would create multiples files and directories). This makes it easier to share the complete note with someone who doesn't have Joplin.</p>
|
||||
<p><strong>Other changes and bug fixes</strong></p>
|
||||
<p>This release includes a total of 19 new features and improvements and 16 bug fixes. See the 2.6.x changelogs for more details:</p>
|
||||
<p><a href="https://joplinapp.org/help/about/changelog/desktop">https://joplinapp.org/help/about/changelog/desktop</a></p>
|
||||
<p><a href="https://joplinapp.org/help/about/changelog/android/">https://joplinapp.org/help/about/changelog/android/</a></p>
|
||||
<p><a href="https://joplinapp.org/help/about/changelog/ios/">https://joplinapp.org/help/about/changelog/ios/</a></p>
|
||||
<p><a href="https://joplinapp.org/help/about/changelog/cli/">https://joplinapp.org/help/about/changelog/cli/</a></p>
|
||||
]]></description><link>https://joplinapp.org/news/20211217-120324</link><guid isPermaLink="false">20211217-120324</guid><pubDate>Fri, 17 Dec 2021 12:03:24 GMT</pubDate><twitter-text></twitter-text></item></channel></rss>
|
||||
]]></description><link>https://joplinapp.org/news/20220224-release-2-7</link><guid isPermaLink="false">20220224-release-2-7</guid><pubDate>Thu, 24 Feb 2022 00:00:00 GMT</pubDate><twitter-text></twitter-text></item></channel></rss>
|
@@ -5,62 +5,64 @@ set -e
|
||||
trap 'handleError' ERR
|
||||
|
||||
handleError() {
|
||||
echo ""
|
||||
echo "If you encountered an error, please consider fixing"
|
||||
echo "the script for your environment and creating a pull"
|
||||
echo "request instead of asking for support on GitHub or"
|
||||
echo "the forum. The error message above should tell you"
|
||||
echo "where and why the error happened."
|
||||
echo ""
|
||||
echo "If you encountered an error, please consider fixing"
|
||||
echo "the script for your environment and creating a pull"
|
||||
echo "request instead of asking for support on GitHub or"
|
||||
echo "the forum. The error message above should tell you"
|
||||
echo "where and why the error happened."
|
||||
}
|
||||
|
||||
#-----------------------------------------------------
|
||||
# Variables
|
||||
#-----------------------------------------------------
|
||||
COLOR_RED=`tput setaf 1`
|
||||
COLOR_GREEN=`tput setaf 2`
|
||||
COLOR_YELLOW=`tput setaf 3`
|
||||
COLOR_BLUE=`tput setaf 4`
|
||||
COLOR_RESET=`tput sgr0`
|
||||
# Only set colors, if tput available and TERM is recognized
|
||||
if [[ `command -v tput` && `tput setaf 1 2>/dev/null` ]]; then
|
||||
COLOR_RED=`tput setaf 1`
|
||||
COLOR_GREEN=`tput setaf 2`
|
||||
COLOR_YELLOW=`tput setaf 3`
|
||||
COLOR_BLUE=`tput setaf 4`
|
||||
COLOR_RESET=`tput sgr0`
|
||||
fi
|
||||
SILENT=false
|
||||
ALLOW_ROOT=false
|
||||
SHOW_CHANGELOG=false
|
||||
INCLUDE_PRE_RELEASE=false
|
||||
|
||||
print() {
|
||||
if [[ "${SILENT}" == false ]] ; then
|
||||
echo -e "$@"
|
||||
fi
|
||||
if [[ "${SILENT}" == false ]]; then
|
||||
echo -e "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
showLogo() {
|
||||
print "${COLOR_BLUE}"
|
||||
print " _ _ _ "
|
||||
print " | | ___ _ __ | (_)_ __ "
|
||||
print " _ | |/ _ \| '_ \| | | '_ \ "
|
||||
print "| |_| | (_) | |_) | | | | | |"
|
||||
print " \___/ \___/| .__/|_|_|_| |_|"
|
||||
print " |_|"
|
||||
print ""
|
||||
print "Linux Installer and Updater"
|
||||
print "${COLOR_RESET}"
|
||||
print "${COLOR_BLUE}"
|
||||
print " _ _ _ "
|
||||
print " | | ___ _ __ | (_)_ __ "
|
||||
print " _ | |/ _ \| '_ \| | | '_ \ "
|
||||
print "| |_| | (_) | |_) | | | | | |"
|
||||
print " \___/ \___/| .__/|_|_|_| |_|"
|
||||
print " |_|"
|
||||
print ""
|
||||
print "Linux Installer and Updater"
|
||||
print "${COLOR_RESET}"
|
||||
}
|
||||
|
||||
showHelp() {
|
||||
showLogo
|
||||
print "Available Arguments:"
|
||||
print "\t" "--help" "\t" "Show this help information" "\n"
|
||||
print "\t" "--allow-root" "\t" "Allow the install to be run as root"
|
||||
print "\t" "--changelog" "\t" "Show the changelog after installation"
|
||||
print "\t" "--force" "\t" "Always download the latest version"
|
||||
print "\t" "--silent" "\t" "Don't print any output"
|
||||
print "\t" "--prerelease" "\t" "Check for new Versions including Pre-Releases"
|
||||
|
||||
if [[ ! -z $1 ]]; then
|
||||
print "\n" "${COLOR_RED}ERROR: " "$*" "${COLOR_RESET}" "\n"
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
showLogo
|
||||
print "Available Arguments:"
|
||||
print "\t" "--help" "\t" "Show this help information" "\n"
|
||||
print "\t" "--allow-root" "\t" "Allow the install to be run as root"
|
||||
print "\t" "--changelog" "\t" "Show the changelog after installation"
|
||||
print "\t" "--force" "\t" "Always download the latest version"
|
||||
print "\t" "--silent" "\t" "Don't print any output"
|
||||
print "\t" "--prerelease" "\t" "Check for new Versions including Pre-Releases"
|
||||
|
||||
if [[ ! -z $1 ]]; then
|
||||
print "\n" "${COLOR_RED}ERROR: " "$*" "${COLOR_RESET}" "\n"
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
#-----------------------------------------------------
|
||||
@@ -71,9 +73,9 @@ optspec=":h-:"
|
||||
while getopts "${optspec}" OPT; do
|
||||
[ "${OPT}" = " " ] && continue
|
||||
if [ "${OPT}" = "-" ]; then # long option: reformulate OPT and OPTARG
|
||||
OPT="${OPTARG%%=*}" # extract long option name
|
||||
OPTARG="${OPTARG#$OPT}" # extract long option argument (may be empty)
|
||||
OPTARG="${OPTARG#=}" # if long option argument, remove assigning `=`
|
||||
OPT="${OPTARG%%=*}" # extract long option name
|
||||
OPTARG="${OPTARG#$OPT}" # extract long option argument (may be empty)
|
||||
OPTARG="${OPTARG#=}" # if long option argument, remove assigning `=`
|
||||
fi
|
||||
case "${OPT}" in
|
||||
h | help ) showHelp ;;
|
||||
@@ -90,8 +92,8 @@ shift $((OPTIND-1)) # remove parsed options and args from $@ list
|
||||
|
||||
## Check and warn if running as root.
|
||||
if [[ $EUID = 0 ]] && [[ "${ALLOW_ROOT}" != true ]]; then
|
||||
showHelp "It is not recommended (nor necessary) to run this script as root. To do so anyway, please use '--allow-root'"
|
||||
exit 1
|
||||
showHelp "It is not recommended (nor necessary) to run this script as root. To do so anyway, please use '--allow-root'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
#-----------------------------------------------------
|
||||
@@ -104,12 +106,12 @@ print "Checking architecture..."
|
||||
## uname actually gives more information than needed, but it contains all architectures (hardware and software)
|
||||
ARCHITECTURE=$(uname -m -p -i || echo "NO CHECK")
|
||||
|
||||
if [[ $ARCHITECTURE = "NO CHECK" ]] ; then
|
||||
if [[ $ARCHITECTURE = "NO CHECK" ]]; then
|
||||
print "${COLOR_YELLOW}WARNING: Can't get system architecture, skipping check${COLOR_RESET}"
|
||||
elif [[ $ARCHITECTURE =~ .*aarch.*|.*arm.* ]] ; then
|
||||
elif [[ $ARCHITECTURE =~ .*aarch.*|.*arm.* ]]; then
|
||||
showHelp "Arm systems are not officially supported by Joplin, please search the forum (https://discourse.joplinapp.org/) for more information"
|
||||
exit 1
|
||||
elif [[ $ARCHITECTURE =~ .*i386.*|.*i686.* ]] ; then
|
||||
elif [[ $ARCHITECTURE =~ .*i386.*|.*i686.* ]]; then
|
||||
showHelp "32-bit systems are not supported by Joplin, please search the forum (https://discourse.joplinapp.org/) for more information"
|
||||
exit 1
|
||||
fi
|
||||
@@ -122,7 +124,7 @@ if [[ $(command -v ldconfig) ]]; then
|
||||
else
|
||||
LIBFUSE=$(find /lib /usr/lib /lib64 /usr/lib64 /usr/local/lib -name "libfuse.so.2" 2>/dev/null | grep "libfuse.so.2" || echo '')
|
||||
fi
|
||||
if [[ $LIBFUSE == "" ]] ; then
|
||||
if [[ $LIBFUSE == "" ]]; then
|
||||
print "${COLOR_RED}Error: Can't get libfuse2 on system, please install libfuse2${COLOR_RESET}"
|
||||
print "See https://joplinapp.org/help/faq/#desktop-application-will-not-launch-on-linux and https://github.com/AppImage/AppImageKit/wiki/FUSE for more information"
|
||||
exit 1
|
||||
@@ -141,17 +143,17 @@ fi
|
||||
|
||||
# Check if it's in the latest version
|
||||
if [[ -e ~/.joplin/VERSION ]] && [[ $(< ~/.joplin/VERSION) == "${RELEASE_VERSION}" ]]; then
|
||||
print "${COLOR_GREEN}You already have the latest version${COLOR_RESET} ${RELEASE_VERSION} ${COLOR_GREEN}installed.${COLOR_RESET}"
|
||||
([[ "$FORCE" == true ]] && print "Forcing installation...") || exit 0
|
||||
print "${COLOR_GREEN}You already have the latest version${COLOR_RESET} ${RELEASE_VERSION} ${COLOR_GREEN}installed.${COLOR_RESET}"
|
||||
([[ "$FORCE" == true ]] && print "Forcing installation...") || exit 0
|
||||
else
|
||||
[[ -e ~/.joplin/VERSION ]] && CURRENT_VERSION=$(< ~/.joplin/VERSION)
|
||||
print "The latest version is ${RELEASE_VERSION}, but you have ${CURRENT_VERSION:-no version} installed."
|
||||
[[ -e ~/.joplin/VERSION ]] && CURRENT_VERSION=$(< ~/.joplin/VERSION)
|
||||
print "The latest version is ${RELEASE_VERSION}, but you have ${CURRENT_VERSION:-no version} installed."
|
||||
fi
|
||||
|
||||
# Check if it's an update or a new install
|
||||
DOWNLOAD_TYPE="New"
|
||||
if [[ -f ~/.joplin/Joplin.AppImage ]]; then
|
||||
DOWNLOAD_TYPE="Update"
|
||||
DOWNLOAD_TYPE="Update"
|
||||
fi
|
||||
|
||||
#-----------------------------------------------------
|
||||
@@ -183,8 +185,7 @@ mv "${TEMP_DIR}/joplin.png" ~/.local/share/icons/hicolor/512x512/apps/joplin.png
|
||||
print "${COLOR_GREEN}OK${COLOR_RESET}"
|
||||
|
||||
# Detect desktop environment
|
||||
if [ "$XDG_CURRENT_DESKTOP" = "" ]
|
||||
then
|
||||
if [ "$XDG_CURRENT_DESKTOP" = "" ]; then
|
||||
DESKTOP=$(echo "${XDG_DATA_DIRS}" | sed 's/.*\(xfce\|kde\|gnome\).*/\1/')
|
||||
else
|
||||
DESKTOP=$XDG_CURRENT_DESKTOP
|
||||
@@ -210,8 +211,7 @@ if command -v lsb_release &> /dev/null; then
|
||||
# uses these to sandbox processes. Unfortunately, it doesn't look like we can get around this
|
||||
# without writing the AppImage to a non-user-writable location (without invalidating other security
|
||||
# controls). See https://discourse.joplinapp.org/t/possible-future-requirement-for-no-sandbox-flag-for-ubuntu-23-10/.
|
||||
if [[ $DISTVER = "Ubuntu23.10" || $DISTVER =~ Debian1. || ( "$DISTVER" = "Linuxmint4" && "$DISTCODENAME" = "debbie" ) || ( "$DISTVER" = "CentOS" && "$DISTMAJOR" =~ 6|7 ) ]]
|
||||
then
|
||||
if [[ $DISTVER = "Ubuntu23.10" || $DISTVER =~ Debian1. || ( "$DISTVER" = "Linuxmint4" && "$DISTCODENAME" = "debbie" ) || ( "$DISTVER" = "CentOS" && "$DISTMAJOR" =~ 6|7 ) ]]; then
|
||||
SANDBOXPARAM="--no-sandbox"
|
||||
print "${COLOR_YELLOW}WARNING${COLOR_RESET} Electron sandboxing disabled."
|
||||
print " See https://discourse.joplinapp.org/t/32160/5 for details."
|
||||
@@ -222,41 +222,40 @@ fi
|
||||
# `.local/share/desktop` had a desktop file created.
|
||||
# However some environments don't return a desktop BUT still support these desktop files
|
||||
# the command check was added to support all Desktops that have support for the
|
||||
# freedesktop standard
|
||||
# freedesktop standard
|
||||
# The old checks are left in place for historical reasons, but
|
||||
# NO MORE DESKTOP ENVIRONMENTS SHOULD BE ADDED
|
||||
# If a new environment needs to be supported, then the command check section should be re-thought
|
||||
if [[ $DESKTOP =~ .*gnome.*|.*kde.*|.*xfce.*|.*mate.*|.*lxqt.*|.*unity.*|.*x-cinnamon.*|.*deepin.*|.*pantheon.*|.*lxde.*|.*i3.*|.*sway.* ]] || [[ `command -v update-desktop-database` ]]
|
||||
then
|
||||
DATA_HOME=${XDG_DATA_HOME:-~/.local/share}
|
||||
DESKTOP_FILE_LOCATION="$DATA_HOME/applications"
|
||||
# Only delete the desktop file if it will be replaced
|
||||
rm -f "$DESKTOP_FILE_LOCATION/appimagekit-joplin.desktop"
|
||||
if [[ $DESKTOP =~ .*gnome.*|.*kde.*|.*xfce.*|.*mate.*|.*lxqt.*|.*unity.*|.*x-cinnamon.*|.*deepin.*|.*pantheon.*|.*lxde.*|.*i3.*|.*sway.* ]] || [[ `command -v update-desktop-database` ]]; then
|
||||
DATA_HOME=${XDG_DATA_HOME:-~/.local/share}
|
||||
DESKTOP_FILE_LOCATION="$DATA_HOME/applications"
|
||||
# Only delete the desktop file if it will be replaced
|
||||
rm -f "$DESKTOP_FILE_LOCATION/appimagekit-joplin.desktop"
|
||||
|
||||
# On some systems this directory doesn't exist by default
|
||||
mkdir -p "$DESKTOP_FILE_LOCATION"
|
||||
|
||||
# Tabs specifically, and not spaces, are needed for indentation with Bash heredocs
|
||||
cat >> "$DESKTOP_FILE_LOCATION/appimagekit-joplin.desktop" <<-EOF
|
||||
[Desktop Entry]
|
||||
Encoding=UTF-8
|
||||
Name=Joplin
|
||||
Comment=Joplin for Desktop
|
||||
Exec=${HOME}/.joplin/Joplin.AppImage ${SANDBOXPARAM} %u
|
||||
Icon=joplin
|
||||
StartupWMClass=Joplin
|
||||
Type=Application
|
||||
Categories=Office;
|
||||
MimeType=x-scheme-handler/joplin;
|
||||
X-GNOME-SingleWindow=true // should be removed eventually as it was upstream to be an XDG specification
|
||||
SingleMainWindow=true
|
||||
EOF
|
||||
|
||||
# Update application icons
|
||||
[[ `command -v update-desktop-database` ]] && update-desktop-database "$DESKTOP_FILE_LOCATION" && update-desktop-database "$DATA_HOME/icons"
|
||||
print "${COLOR_GREEN}OK${COLOR_RESET}"
|
||||
# On some systems this directory doesn't exist by default
|
||||
mkdir -p "$DESKTOP_FILE_LOCATION"
|
||||
|
||||
# No spaces or tabs should be used for indentation with Bash heredocs
|
||||
cat >> "$DESKTOP_FILE_LOCATION/appimagekit-joplin.desktop" <<-EOF
|
||||
[Desktop Entry]
|
||||
Encoding=UTF-8
|
||||
Name=Joplin
|
||||
Comment=Joplin for Desktop
|
||||
Exec=${HOME}/.joplin/Joplin.AppImage ${SANDBOXPARAM} %u
|
||||
Icon=joplin
|
||||
StartupWMClass=Joplin
|
||||
Type=Application
|
||||
Categories=Office;
|
||||
MimeType=x-scheme-handler/joplin;
|
||||
X-GNOME-SingleWindow=true // should be removed eventually as it was upstream to be an XDG specification
|
||||
SingleMainWindow=true
|
||||
EOF
|
||||
|
||||
# Update application icons
|
||||
[[ `command -v update-desktop-database` ]] && update-desktop-database "$DESKTOP_FILE_LOCATION" && update-desktop-database "$DATA_HOME/icons"
|
||||
print "${COLOR_GREEN}OK${COLOR_RESET}"
|
||||
else
|
||||
print "${COLOR_RED}NOT DONE, unknown desktop '${DESKTOP}'${COLOR_RESET}"
|
||||
print "${COLOR_RED}NOT DONE, unknown desktop '${DESKTOP}'${COLOR_RESET}"
|
||||
fi
|
||||
|
||||
#-----------------------------------------------------
|
||||
@@ -271,8 +270,8 @@ echo "$RELEASE_VERSION" > ~/.joplin/VERSION
|
||||
|
||||
#-----------------------------------------------------
|
||||
if [[ "$SHOW_CHANGELOG" == true ]]; then
|
||||
NOTES=$(wget -qO - https://api.github.com/repos/laurent22/joplin/releases/latest | grep -Po '"body": "\K.*(?=")')
|
||||
print "${COLOR_BLUE}Changelog:${COLOR_RESET}\n${NOTES}"
|
||||
NOTES=$(wget -qO - https://api.github.com/repos/laurent22/joplin/releases/latest | grep -Po '"body": "\K.*(?=")')
|
||||
print "${COLOR_BLUE}Changelog:${COLOR_RESET}\n${NOTES}"
|
||||
fi
|
||||
|
||||
#-----------------------------------------------------
|
||||
|
@@ -26,12 +26,12 @@ For more information about the applications, see the [full Joplin documentation]
|
||||
|
||||
Donations to Joplin support the development of the project. Developing quality applications mostly takes time, but there are also some expenses, such as digital certificates to sign the applications, app store fees, hosting, etc. Most of all, your donation will make it possible to keep up the current development standard.
|
||||
|
||||
Please see the [donation page](https://github.com/laurent22/joplin/blob/dev/readme/about/donate.md) for information on how to support the development of Joplin.
|
||||
Please see the [donation page](https://github.com/laurent22/joplin/blob/dev/readme/donate.md) for information on how to support the development of Joplin.
|
||||
|
||||
# Sponsors
|
||||
|
||||
<!-- SPONSORS-ORG -->
|
||||
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://www.hosting.de/nextcloud/?mtm_campaign=managed-nextcloud&mtm_kwd=joplinapp&mtm_source=joplinapp-webseite&mtm_medium=banner"><img title="Hosting.de" width="256" src="https://joplinapp.org/images/sponsors/HostingDe.png"/></a> <a href="https://residence-greece.com/"><img title="Greece Golden Visa" width="256" src="https://joplinapp.org/images/sponsors/ResidenceGreece.jpg"/></a> <a href="https://grundstueckspreise.info/"><img title="SP Software GmbH" width="256" src="https://joplinapp.org/images/sponsors/Grundstueckspreise.png"/></a> <a href="https://tranio.com/spain/"><img title="Property for sale in Spain" width="256" src="https://joplinapp.org/images/sponsors/TranioOverseasProperty.jpg"/></a> <a href="https://citricsheep.com"><img title="Citric Sheep" width="256" src="https://joplinapp.org/images/sponsors/CitricSheep.png"/></a>
|
||||
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://www.hosting.de/nextcloud/?mtm_campaign=managed-nextcloud&mtm_kwd=joplinapp&mtm_source=joplinapp-webseite&mtm_medium=banner"><img title="Hosting.de" width="256" src="https://joplinapp.org/images/sponsors/HostingDe.png"/></a> <a href="https://grundstueckspreise.info/"><img title="SP Software GmbH" width="256" src="https://joplinapp.org/images/sponsors/Grundstueckspreise.png"/></a> <a href="https://citricsheep.com"><img title="Citric Sheep" width="256" src="https://joplinapp.org/images/sponsors/CitricSheep.png"/></a> <a href="https://sorted.travel/?utm_source=joplinapp "><img title="Sorted Travel" width="256" src="https://joplinapp.org/images/sponsors/SortedTravel.png"/></a> <a href="https://celebian.com "><img title="Celebian" width="256" src="https://joplinapp.org/images/sponsors/Celebian.png"/></a>
|
||||
<!-- SPONSORS-ORG -->
|
||||
|
||||
* * *
|
||||
|
16
jest.base-setup.js
Normal file
@@ -0,0 +1,16 @@
|
||||
/* eslint-disable jest/require-top-level-describe */
|
||||
|
||||
module.exports = () => {
|
||||
// Disable the additional information that Jest adds to each console
|
||||
// statement. It's rarely needed and if it is it can be commented out here.
|
||||
|
||||
const jestConsole = console;
|
||||
|
||||
beforeEach(() => {
|
||||
global.console = require('console');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
global.console = jestConsole;
|
||||
});
|
||||
};
|
32
package.json
@@ -13,7 +13,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"buildApiDoc": "yarn workspace joplin start apidoc ../../readme/api/references/rest_api.md",
|
||||
"buildCommandIndex": "node packages/tools/gulp/tasks/buildCommandIndexRun.js",
|
||||
"buildScriptIndexes": "node packages/tools/gulp/tasks/buildScriptIndexesRun.js",
|
||||
"buildParallel": "yarn workspaces foreach --verbose --interlaced --parallel --jobs 2 --topological run build && yarn run tsc",
|
||||
"buildPluginDoc": "cd packages/generate-plugin-doc && yarn run buildPluginDoc_",
|
||||
"buildSequential": "yarn workspaces foreach --verbose --interlaced --topological run build && yarn run tsc",
|
||||
@@ -31,7 +31,7 @@
|
||||
"crowdinUpload": "crowdin upload",
|
||||
"cspell": "cspell",
|
||||
"dependencyTree": "madge",
|
||||
"generateDatabaseTypes": "node packages/tools/generate-database-types",
|
||||
"generateTypes": "node packages/tools/generate-database-types",
|
||||
"linkChecker": "linkchecker https://joplinapp.org/ && linkchecker --check-extern https://joplinapp.org/api/references/plugin_api/classes/joplin.html",
|
||||
"linter-ci": "eslint --resolve-plugins-relative-to . --quiet --ext .js --ext .jsx --ext .ts --ext .tsx",
|
||||
"linter-interactive": "eslint-interactive --resolve-plugins-relative-to . --fix --quiet --ext .js --ext .jsx --ext .ts --ext .tsx",
|
||||
@@ -72,39 +72,41 @@
|
||||
"@crowdin/cli": "3",
|
||||
"@joplin/utils": "~2.12",
|
||||
"@seiyab/eslint-plugin-react-hooks": "4.5.1-beta.0",
|
||||
"@typescript-eslint/eslint-plugin": "6.7.2",
|
||||
"@typescript-eslint/parser": "6.7.2",
|
||||
"@typescript-eslint/eslint-plugin": "6.8.0",
|
||||
"@typescript-eslint/parser": "6.8.0",
|
||||
"cspell": "5.21.2",
|
||||
"eslint": "8.49.0",
|
||||
"eslint": "8.52.0",
|
||||
"eslint-interactive": "10.8.0",
|
||||
"eslint-plugin-import": "2.28.1",
|
||||
"eslint-plugin-jest": "27.4.0",
|
||||
"eslint-plugin-jest": "27.4.3",
|
||||
"eslint-plugin-promise": "6.1.1",
|
||||
"eslint-plugin-react": "7.33.2",
|
||||
"execa": "5.1.1",
|
||||
"fs-extra": "11.1.1",
|
||||
"fs-extra": "11.2.0",
|
||||
"glob": "10.3.10",
|
||||
"gulp": "4.0.2",
|
||||
"husky": "3.1.0",
|
||||
"lerna": "3.22.1",
|
||||
"lint-staged": "14.0.1",
|
||||
"lint-staged": "15.2.0",
|
||||
"madge": "6.1.0",
|
||||
"npm-package-json-lint": "7.0.0",
|
||||
"npm-package-json-lint": "7.1.0",
|
||||
"typescript": "5.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/fs-extra": "11.0.3",
|
||||
"eslint-plugin-github": "4.10.0",
|
||||
"@types/fs-extra": "11.0.4",
|
||||
"eslint-plugin-github": "4.10.1",
|
||||
"http-server": "14.1.1",
|
||||
"node-gyp": "9.4.0",
|
||||
"nodemon": "3.0.1"
|
||||
"node-gyp": "9.4.1",
|
||||
"nodemon": "3.0.2"
|
||||
},
|
||||
"packageManager": "yarn@3.6.4",
|
||||
"resolutions": {
|
||||
"react-native-camera@4.2.1": "patch:react-native-camera@npm%3A4.2.1#./.yarn/patches/react-native-camera-npm-4.2.1-24b2600a7e.patch",
|
||||
"react-native-vosk@0.1.12": "patch:react-native-vosk@npm%3A0.1.12#./.yarn/patches/react-native-vosk-npm-0.1.12-76b1caaae8.patch",
|
||||
"eslint": "patch:eslint@8.49.0#./.yarn/patches/eslint-npm-8.39.0-d92bace04d.patch",
|
||||
"eslint": "patch:eslint@8.52.0#./.yarn/patches/eslint-npm-8.39.0-d92bace04d.patch",
|
||||
"app-builder-lib@24.4.0": "patch:app-builder-lib@npm%3A24.4.0#./.yarn/patches/app-builder-lib-npm-24.4.0-05322ff057.patch",
|
||||
"react-native@0.71.10": "patch:react-native@npm%3A0.71.10#./.yarn/patches/react-native-animation-fix/react-native-npm-0.71.10-f9c32562d8.patch"
|
||||
"react-native@0.71.10": "patch:react-native@npm%3A0.71.10#./.yarn/patches/react-native-animation-fix/react-native-npm-0.71.10-f9c32562d8.patch",
|
||||
"nanoid": "patch:nanoid@npm%3A3.3.7#./.yarn/patches/nanoid-npm-3.3.7-98824ba130.patch",
|
||||
"pdfjs-dist": "patch:pdfjs-dist@npm%3A3.11.174#./.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch"
|
||||
}
|
||||
}
|
||||
|
@@ -1,469 +1,461 @@
|
||||
const BaseApplication = require('@joplin/lib/BaseApplication').default;
|
||||
const { FoldersScreenUtils } = require('@joplin/lib/folders-screen-utils.js');
|
||||
const ResourceService = require('@joplin/lib/services/ResourceService').default;
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
const Folder = require('@joplin/lib/models/Folder').default;
|
||||
const BaseItem = require('@joplin/lib/models/BaseItem').default;
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
const Tag = require('@joplin/lib/models/Tag').default;
|
||||
const Setting = require('@joplin/lib/models/Setting').default;
|
||||
const { reg } = require('@joplin/lib/registry.js');
|
||||
const { fileExtension } = require('@joplin/lib/path-utils');
|
||||
const { splitCommandString } = require('@joplin/utils');
|
||||
const { splitCommandBatch } = require('@joplin/lib/string-utils');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const fs = require('fs-extra');
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const BaseApplication_1 = require("@joplin/lib/BaseApplication");
|
||||
const folders_screen_utils_js_1 = require("@joplin/lib/folders-screen-utils.js");
|
||||
const ResourceService_1 = require("@joplin/lib/services/ResourceService");
|
||||
const BaseModel_1 = require("@joplin/lib/BaseModel");
|
||||
const Folder_1 = require("@joplin/lib/models/Folder");
|
||||
const BaseItem_1 = require("@joplin/lib/models/BaseItem");
|
||||
const Note_1 = require("@joplin/lib/models/Note");
|
||||
const Tag_1 = require("@joplin/lib/models/Tag");
|
||||
const Setting_1 = require("@joplin/lib/models/Setting");
|
||||
const registry_js_1 = require("@joplin/lib/registry.js");
|
||||
const path_utils_1 = require("@joplin/lib/path-utils");
|
||||
const utils_1 = require("@joplin/utils");
|
||||
const locale_1 = require("@joplin/lib/locale");
|
||||
const fs_extra_1 = require("fs-extra");
|
||||
const RevisionService_1 = require("@joplin/lib/services/RevisionService");
|
||||
const shim_1 = require("@joplin/lib/shim");
|
||||
const setupCommand_1 = require("./setupCommand");
|
||||
const { cliUtils } = require('./cli-utils.js');
|
||||
const Cache = require('@joplin/lib/Cache');
|
||||
const RevisionService = require('@joplin/lib/services/RevisionService').default;
|
||||
const shim = require('@joplin/lib/shim').default;
|
||||
const setupCommand = require('./setupCommand').default;
|
||||
|
||||
class Application extends BaseApplication {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.showPromptString_ = true;
|
||||
this.commands_ = {};
|
||||
this.commandMetadata_ = null;
|
||||
this.activeCommand_ = null;
|
||||
this.allCommandsLoaded_ = false;
|
||||
this.showStackTraces_ = false;
|
||||
this.gui_ = null;
|
||||
this.cache_ = new Cache();
|
||||
}
|
||||
|
||||
gui() {
|
||||
return this.gui_;
|
||||
}
|
||||
|
||||
commandStdoutMaxWidth() {
|
||||
return this.gui().stdoutMaxWidth();
|
||||
}
|
||||
|
||||
async guessTypeAndLoadItem(pattern, options = null) {
|
||||
let type = BaseModel.TYPE_NOTE;
|
||||
if (pattern.indexOf('/') === 0) {
|
||||
type = BaseModel.TYPE_FOLDER;
|
||||
pattern = pattern.substr(1);
|
||||
}
|
||||
return this.loadItem(type, pattern, options);
|
||||
}
|
||||
|
||||
async loadItem(type, pattern, options = null) {
|
||||
const output = await this.loadItems(type, pattern, options);
|
||||
|
||||
if (output.length > 1) {
|
||||
// output.sort((a, b) => { return a.user_updated_time < b.user_updated_time ? +1 : -1; });
|
||||
|
||||
// let answers = { 0: _('[Cancel]') };
|
||||
// for (let i = 0; i < output.length; i++) {
|
||||
// answers[i + 1] = output[i].title;
|
||||
// }
|
||||
|
||||
// Not really useful with new UI?
|
||||
throw new Error(_('More than one item match "%s". Please narrow down your query.', pattern));
|
||||
|
||||
// let msg = _('More than one item match "%s". Please select one:', pattern);
|
||||
// const response = await cliUtils.promptMcq(msg, answers);
|
||||
// if (!response) return null;
|
||||
|
||||
// return output[response - 1];
|
||||
} else {
|
||||
return output.length ? output[0] : null;
|
||||
}
|
||||
}
|
||||
|
||||
async loadItems(type, pattern, options = null) {
|
||||
if (type === 'folderOrNote') {
|
||||
const folders = await this.loadItems(BaseModel.TYPE_FOLDER, pattern, options);
|
||||
if (folders.length) return folders;
|
||||
return await this.loadItems(BaseModel.TYPE_NOTE, pattern, options);
|
||||
}
|
||||
|
||||
pattern = pattern ? pattern.toString() : '';
|
||||
|
||||
if (type === BaseModel.TYPE_FOLDER && (pattern === Folder.conflictFolderTitle() || pattern === Folder.conflictFolderId())) return [Folder.conflictFolder()];
|
||||
|
||||
if (!options) options = {};
|
||||
|
||||
const parent = options.parent ? options.parent : app().currentFolder();
|
||||
const ItemClass = BaseItem.itemClass(type);
|
||||
|
||||
if (type === BaseModel.TYPE_NOTE && pattern.indexOf('*') >= 0) {
|
||||
// Handle it as pattern
|
||||
if (!parent) throw new Error(_('No notebook selected.'));
|
||||
return await Note.previews(parent.id, { titlePattern: pattern });
|
||||
} else {
|
||||
// Single item
|
||||
let item = null;
|
||||
if (type === BaseModel.TYPE_NOTE) {
|
||||
if (!parent) throw new Error(_('No notebook has been specified.'));
|
||||
item = await ItemClass.loadFolderNoteByField(parent.id, 'title', pattern);
|
||||
} else {
|
||||
item = await ItemClass.loadByTitle(pattern);
|
||||
}
|
||||
if (item) return [item];
|
||||
|
||||
item = await ItemClass.load(pattern); // Load by id
|
||||
if (item) return [item];
|
||||
|
||||
if (pattern.length >= 2) {
|
||||
return await ItemClass.loadByPartialId(pattern);
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
setupCommand(cmd) {
|
||||
return setupCommand(cmd, t => this.stdout(t), () => this.store(), () => this.gui());
|
||||
}
|
||||
|
||||
stdout(text) {
|
||||
return this.gui().stdout(text);
|
||||
}
|
||||
|
||||
async exit(code = 0) {
|
||||
const doExit = async () => {
|
||||
this.gui().exit();
|
||||
await super.exit(code);
|
||||
};
|
||||
|
||||
// Give it a few seconds to cancel otherwise exit anyway
|
||||
shim.setTimeout(async () => {
|
||||
await doExit();
|
||||
}, 5000);
|
||||
|
||||
if (await reg.syncTarget().syncStarted()) {
|
||||
this.stdout(_('Cancelling background synchronisation... Please wait.'));
|
||||
const sync = await reg.syncTarget().synchronizer();
|
||||
await sync.cancel();
|
||||
}
|
||||
|
||||
await doExit();
|
||||
}
|
||||
|
||||
commands(uiType = null) {
|
||||
if (!this.allCommandsLoaded_) {
|
||||
// eslint-disable-next-line github/array-foreach -- Old code before rule was applied
|
||||
fs.readdirSync(__dirname).forEach(path => {
|
||||
if (path.indexOf('command-') !== 0) return;
|
||||
if (path.endsWith('.test.js')) return;
|
||||
const ext = fileExtension(path);
|
||||
if (ext !== 'js') return;
|
||||
|
||||
const CommandClass = require(`./${path}`);
|
||||
let cmd = new CommandClass();
|
||||
if (!cmd.enabled()) return;
|
||||
cmd = this.setupCommand(cmd);
|
||||
this.commands_[cmd.name()] = cmd;
|
||||
});
|
||||
|
||||
this.allCommandsLoaded_ = true;
|
||||
}
|
||||
|
||||
if (uiType !== null) {
|
||||
const temp = [];
|
||||
for (const n in this.commands_) {
|
||||
if (!this.commands_.hasOwnProperty(n)) continue;
|
||||
const c = this.commands_[n];
|
||||
if (!c.supportsUi(uiType)) continue;
|
||||
temp[n] = c;
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
|
||||
return this.commands_;
|
||||
}
|
||||
|
||||
async commandNames() {
|
||||
const metadata = await this.commandMetadata();
|
||||
const output = [];
|
||||
for (const n in metadata) {
|
||||
if (!metadata.hasOwnProperty(n)) continue;
|
||||
output.push(n);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
async commandMetadata() {
|
||||
if (this.commandMetadata_) return this.commandMetadata_;
|
||||
|
||||
let output = await this.cache_.getItem('metadata');
|
||||
if (output) {
|
||||
this.commandMetadata_ = output;
|
||||
return { ...this.commandMetadata_ };
|
||||
}
|
||||
|
||||
const commands = this.commands();
|
||||
|
||||
output = {};
|
||||
for (const n in commands) {
|
||||
if (!commands.hasOwnProperty(n)) continue;
|
||||
const cmd = commands[n];
|
||||
output[n] = cmd.metadata();
|
||||
}
|
||||
|
||||
await this.cache_.setItem('metadata', output, 1000 * 60 * 60 * 24);
|
||||
|
||||
this.commandMetadata_ = output;
|
||||
return { ...this.commandMetadata_ };
|
||||
}
|
||||
|
||||
hasGui() {
|
||||
return this.gui() && !this.gui().isDummy();
|
||||
}
|
||||
|
||||
findCommandByName(name) {
|
||||
if (this.commands_[name]) return this.commands_[name];
|
||||
|
||||
let CommandClass = null;
|
||||
try {
|
||||
CommandClass = require(`${__dirname}/command-${name}.js`);
|
||||
} catch (error) {
|
||||
if (error.message && error.message.indexOf('Cannot find module') >= 0) {
|
||||
const e = new Error(_('No such command: %s', name));
|
||||
e.type = 'notFound';
|
||||
throw e;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
let cmd = new CommandClass();
|
||||
cmd = this.setupCommand(cmd);
|
||||
this.commands_[name] = cmd;
|
||||
return this.commands_[name];
|
||||
}
|
||||
|
||||
dummyGui() {
|
||||
return {
|
||||
isDummy: () => {
|
||||
return true;
|
||||
},
|
||||
prompt: (initialText = '', promptString = '', options = null) => {
|
||||
return cliUtils.prompt(initialText, promptString, options);
|
||||
},
|
||||
showConsole: () => {},
|
||||
maximizeConsole: () => {},
|
||||
stdout: text => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(text);
|
||||
},
|
||||
fullScreen: () => {},
|
||||
exit: () => {},
|
||||
showModalOverlay: () => {},
|
||||
hideModalOverlay: () => {},
|
||||
stdoutMaxWidth: () => {
|
||||
return 100;
|
||||
},
|
||||
forceRender: () => {},
|
||||
termSaveState: () => {},
|
||||
termRestoreState: () => {},
|
||||
};
|
||||
}
|
||||
|
||||
async execCommand(argv) {
|
||||
if (!argv.length) return this.execCommand(['help']);
|
||||
// reg.logger().debug('execCommand()', argv);
|
||||
const commandName = argv[0];
|
||||
this.activeCommand_ = this.findCommandByName(commandName);
|
||||
|
||||
let outException = null;
|
||||
try {
|
||||
if (this.gui().isDummy() && !this.activeCommand_.supportsUi('cli')) throw new Error(_('The command "%s" is only available in GUI mode', this.activeCommand_.name()));
|
||||
const cmdArgs = cliUtils.makeCommandArgs(this.activeCommand_, argv);
|
||||
await this.activeCommand_.action(cmdArgs);
|
||||
} catch (error) {
|
||||
outException = error;
|
||||
}
|
||||
this.activeCommand_ = null;
|
||||
if (outException) throw outException;
|
||||
}
|
||||
|
||||
currentCommand() {
|
||||
return this.activeCommand_;
|
||||
}
|
||||
|
||||
async loadKeymaps() {
|
||||
const defaultKeyMap = [
|
||||
{ keys: [':'], type: 'function', command: 'enter_command_line_mode' },
|
||||
{ keys: ['TAB'], type: 'function', command: 'focus_next' },
|
||||
{ keys: ['SHIFT_TAB'], type: 'function', command: 'focus_previous' },
|
||||
{ keys: ['UP'], type: 'function', command: 'move_up' },
|
||||
{ keys: ['DOWN'], type: 'function', command: 'move_down' },
|
||||
{ keys: ['PAGE_UP'], type: 'function', command: 'page_up' },
|
||||
{ keys: ['PAGE_DOWN'], type: 'function', command: 'page_down' },
|
||||
{ keys: ['ENTER'], type: 'function', command: 'activate' },
|
||||
{ keys: ['DELETE', 'BACKSPACE'], type: 'function', command: 'delete' },
|
||||
{ keys: ['n'], type: 'function', command: 'next_link' },
|
||||
{ keys: ['b'], type: 'function', command: 'previous_link' },
|
||||
{ keys: ['o'], type: 'function', command: 'open_link' },
|
||||
{ keys: [' '], command: 'todo toggle $n' },
|
||||
{ keys: ['tc'], type: 'function', command: 'toggle_console' },
|
||||
{ keys: ['tm'], type: 'function', command: 'toggle_metadata' },
|
||||
{ keys: ['ti'], type: 'function', command: 'toggle_ids' },
|
||||
{ keys: ['/'], type: 'prompt', command: 'search ""', cursorPosition: -2 },
|
||||
{ keys: ['mn'], type: 'prompt', command: 'mknote ""', cursorPosition: -2 },
|
||||
{ keys: ['mt'], type: 'prompt', command: 'mktodo ""', cursorPosition: -2 },
|
||||
{ keys: ['mb'], type: 'prompt', command: 'mkbook ""', cursorPosition: -2 },
|
||||
{ keys: ['yn'], type: 'prompt', command: 'cp $n ""', cursorPosition: -2 },
|
||||
{ keys: ['dn'], type: 'prompt', command: 'mv $n ""', cursorPosition: -2 },
|
||||
];
|
||||
|
||||
// Filter the keymap item by command so that items in keymap.json can override
|
||||
// the default ones.
|
||||
const itemsByCommand = {};
|
||||
|
||||
for (let i = 0; i < defaultKeyMap.length; i++) {
|
||||
itemsByCommand[defaultKeyMap[i].command] = defaultKeyMap[i];
|
||||
}
|
||||
|
||||
const filePath = `${Setting.value('profileDir')}/keymap.json`;
|
||||
if (await fs.pathExists(filePath)) {
|
||||
try {
|
||||
let configString = await fs.readFile(filePath, 'utf-8');
|
||||
configString = configString.replace(/^\s*\/\/.*/, ''); // Strip off comments
|
||||
const keymap = JSON.parse(configString);
|
||||
for (let keymapIndex = 0; keymapIndex < keymap.length; keymapIndex++) {
|
||||
const item = keymap[keymapIndex];
|
||||
itemsByCommand[item.command] = item;
|
||||
}
|
||||
} catch (error) {
|
||||
let msg = error.message ? error.message : '';
|
||||
msg = `Could not load keymap ${filePath}\n${msg}`;
|
||||
error.message = msg;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const output = [];
|
||||
for (const n in itemsByCommand) {
|
||||
if (!itemsByCommand.hasOwnProperty(n)) continue;
|
||||
output.push(itemsByCommand[n]);
|
||||
}
|
||||
|
||||
// Map reserved shortcuts to their equivalent key
|
||||
// https://github.com/cronvel/terminal-kit/issues/101
|
||||
for (let i = 0; i < output.length; i++) {
|
||||
const newKeys = output[i].keys.map(k => {
|
||||
k = k.replace(/CTRL_H/g, 'BACKSPACE');
|
||||
k = k.replace(/CTRL_I/g, 'TAB');
|
||||
k = k.replace(/CTRL_M/g, 'ENTER');
|
||||
return k;
|
||||
});
|
||||
output[i].keys = newKeys;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
async commandList(argv) {
|
||||
if (argv.length && argv[0] === 'batch') {
|
||||
const commands = [];
|
||||
const commandLines = splitCommandBatch(await fs.readFile(argv[1], 'utf-8'));
|
||||
|
||||
for (const commandLine of commandLines) {
|
||||
if (!commandLine.trim()) continue;
|
||||
const splitted = splitCommandString(commandLine.trim());
|
||||
commands.push(splitted);
|
||||
}
|
||||
return commands;
|
||||
} else {
|
||||
return [argv];
|
||||
}
|
||||
}
|
||||
|
||||
// We need this special case here because by the time the `version` command
|
||||
// runs, the keychain has already been setup.
|
||||
checkIfKeychainEnabled(argv) {
|
||||
return argv.indexOf('version') < 0;
|
||||
}
|
||||
|
||||
async start(argv) {
|
||||
const keychainEnabled = this.checkIfKeychainEnabled(argv);
|
||||
|
||||
argv = await super.start(argv, { keychainEnabled });
|
||||
|
||||
cliUtils.setStdout(object => {
|
||||
return this.stdout(object);
|
||||
});
|
||||
|
||||
this.initRedux();
|
||||
|
||||
// If we have some arguments left at this point, it's a command
|
||||
// so execute it.
|
||||
if (argv.length) {
|
||||
this.gui_ = this.dummyGui();
|
||||
|
||||
this.currentFolder_ = await Folder.load(Setting.value('activeFolderId'));
|
||||
|
||||
await this.applySettingsSideEffects();
|
||||
|
||||
try {
|
||||
const commands = await this.commandList(argv);
|
||||
for (const command of commands) {
|
||||
await this.execCommand(command);
|
||||
}
|
||||
} catch (error) {
|
||||
if (this.showStackTraces_) {
|
||||
console.error(error);
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(error.message);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await Setting.saveAll();
|
||||
|
||||
// Need to call exit() explicitly, otherwise Node wait for any timeout to complete
|
||||
// https://stackoverflow.com/questions/18050095
|
||||
process.exit(0);
|
||||
} else {
|
||||
// Otherwise open the GUI
|
||||
const keymap = await this.loadKeymaps();
|
||||
|
||||
const AppGui = require('./app-gui.js');
|
||||
this.gui_ = new AppGui(this, this.store(), keymap);
|
||||
this.gui_.setLogger(this.logger());
|
||||
await this.gui_.start();
|
||||
|
||||
// Since the settings need to be loaded before the store is created, it will never
|
||||
// receive the SETTING_UPDATE_ALL even, which mean state.settings will not be
|
||||
// initialised. So we manually call dispatchUpdateAll() to force an update.
|
||||
Setting.dispatchUpdateAll();
|
||||
|
||||
await FoldersScreenUtils.refreshFolders();
|
||||
|
||||
const tags = await Tag.allWithNotes();
|
||||
|
||||
ResourceService.runInBackground();
|
||||
|
||||
RevisionService.instance().runInBackground();
|
||||
|
||||
this.dispatch({
|
||||
type: 'TAG_UPDATE_ALL',
|
||||
items: tags,
|
||||
});
|
||||
|
||||
this.store().dispatch({
|
||||
type: 'FOLDER_SELECT',
|
||||
id: Setting.value('activeFolderId'),
|
||||
});
|
||||
|
||||
this.startRotatingLogMaintenance(Setting.value('profileDir'));
|
||||
}
|
||||
}
|
||||
const { splitCommandBatch } = require('@joplin/lib/string-utils');
|
||||
class Application extends BaseApplication_1.default {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.commands_ = {};
|
||||
this.commandMetadata_ = null;
|
||||
this.activeCommand_ = null;
|
||||
this.allCommandsLoaded_ = false;
|
||||
this.gui_ = null;
|
||||
this.cache_ = new Cache();
|
||||
}
|
||||
gui() {
|
||||
return this.gui_;
|
||||
}
|
||||
commandStdoutMaxWidth() {
|
||||
return this.gui().stdoutMaxWidth();
|
||||
}
|
||||
guessTypeAndLoadItem(pattern, options = null) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
let type = BaseModel_1.default.TYPE_NOTE;
|
||||
if (pattern.indexOf('/') === 0) {
|
||||
type = BaseModel_1.default.TYPE_FOLDER;
|
||||
pattern = pattern.substr(1);
|
||||
}
|
||||
return this.loadItem(type, pattern, options);
|
||||
});
|
||||
}
|
||||
loadItem(type, pattern, options = null) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const output = yield this.loadItems(type, pattern, options);
|
||||
if (output.length > 1) {
|
||||
// output.sort((a, b) => { return a.user_updated_time < b.user_updated_time ? +1 : -1; });
|
||||
// let answers = { 0: _('[Cancel]') };
|
||||
// for (let i = 0; i < output.length; i++) {
|
||||
// answers[i + 1] = output[i].title;
|
||||
// }
|
||||
// Not really useful with new UI?
|
||||
throw new Error((0, locale_1._)('More than one item match "%s". Please narrow down your query.', pattern));
|
||||
// let msg = _('More than one item match "%s". Please select one:', pattern);
|
||||
// const response = await cliUtils.promptMcq(msg, answers);
|
||||
// if (!response) return null;
|
||||
// return output[response - 1];
|
||||
}
|
||||
else {
|
||||
return output.length ? output[0] : null;
|
||||
}
|
||||
});
|
||||
}
|
||||
loadItems(type, pattern, options = null) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (type === 'folderOrNote') {
|
||||
const folders = yield this.loadItems(BaseModel_1.default.TYPE_FOLDER, pattern, options);
|
||||
if (folders.length)
|
||||
return folders;
|
||||
return yield this.loadItems(BaseModel_1.default.TYPE_NOTE, pattern, options);
|
||||
}
|
||||
pattern = pattern ? pattern.toString() : '';
|
||||
if (type === BaseModel_1.default.TYPE_FOLDER && (pattern === Folder_1.default.conflictFolderTitle() || pattern === Folder_1.default.conflictFolderId()))
|
||||
return [Folder_1.default.conflictFolder()];
|
||||
if (!options)
|
||||
options = {};
|
||||
const parent = options.parent ? options.parent : app().currentFolder();
|
||||
const ItemClass = BaseItem_1.default.itemClass(type);
|
||||
if (type === BaseModel_1.default.TYPE_NOTE && pattern.indexOf('*') >= 0) {
|
||||
// Handle it as pattern
|
||||
if (!parent)
|
||||
throw new Error((0, locale_1._)('No notebook selected.'));
|
||||
return yield Note_1.default.previews(parent.id, { titlePattern: pattern });
|
||||
}
|
||||
else {
|
||||
// Single item
|
||||
let item = null;
|
||||
if (type === BaseModel_1.default.TYPE_NOTE) {
|
||||
if (!parent)
|
||||
throw new Error((0, locale_1._)('No notebook has been specified.'));
|
||||
item = yield ItemClass.loadFolderNoteByField(parent.id, 'title', pattern);
|
||||
}
|
||||
else {
|
||||
item = yield ItemClass.loadByTitle(pattern);
|
||||
}
|
||||
if (item)
|
||||
return [item];
|
||||
item = yield ItemClass.load(pattern); // Load by id
|
||||
if (item)
|
||||
return [item];
|
||||
if (pattern.length >= 2) {
|
||||
return yield ItemClass.loadByPartialId(pattern);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
});
|
||||
}
|
||||
setupCommand(cmd) {
|
||||
return (0, setupCommand_1.default)(cmd, (t) => this.stdout(t), () => this.store(), () => this.gui());
|
||||
}
|
||||
stdout(text) {
|
||||
return this.gui().stdout(text);
|
||||
}
|
||||
exit(code = 0) {
|
||||
const _super = Object.create(null, {
|
||||
exit: { get: () => super.exit }
|
||||
});
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const doExit = () => __awaiter(this, void 0, void 0, function* () {
|
||||
this.gui().exit();
|
||||
yield _super.exit.call(this, code);
|
||||
});
|
||||
// Give it a few seconds to cancel otherwise exit anyway
|
||||
shim_1.default.setTimeout(() => __awaiter(this, void 0, void 0, function* () {
|
||||
yield doExit();
|
||||
}), 5000);
|
||||
if (yield registry_js_1.reg.syncTarget().syncStarted()) {
|
||||
this.stdout((0, locale_1._)('Cancelling background synchronisation... Please wait.'));
|
||||
const sync = yield registry_js_1.reg.syncTarget().synchronizer();
|
||||
yield sync.cancel();
|
||||
}
|
||||
yield doExit();
|
||||
});
|
||||
}
|
||||
commands(uiType = null) {
|
||||
if (!this.allCommandsLoaded_) {
|
||||
// eslint-disable-next-line github/array-foreach -- Old code before rule was applied
|
||||
(0, fs_extra_1.readdirSync)(__dirname).forEach(path => {
|
||||
if (path.indexOf('command-') !== 0)
|
||||
return;
|
||||
if (path.endsWith('.test.js'))
|
||||
return;
|
||||
const ext = (0, path_utils_1.fileExtension)(path);
|
||||
if (ext !== 'js')
|
||||
return;
|
||||
const CommandClass = require(`./${path}`);
|
||||
let cmd = new CommandClass();
|
||||
if (!cmd.enabled())
|
||||
return;
|
||||
cmd = this.setupCommand(cmd);
|
||||
this.commands_[cmd.name()] = cmd;
|
||||
});
|
||||
this.allCommandsLoaded_ = true;
|
||||
}
|
||||
if (uiType !== null) {
|
||||
const temp = {};
|
||||
for (const n in this.commands_) {
|
||||
if (!this.commands_.hasOwnProperty(n))
|
||||
continue;
|
||||
const c = this.commands_[n];
|
||||
if (!c.supportsUi(uiType))
|
||||
continue;
|
||||
temp[n] = c;
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
return this.commands_;
|
||||
}
|
||||
commandNames() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const metadata = yield this.commandMetadata();
|
||||
const output = [];
|
||||
for (const n in metadata) {
|
||||
if (!metadata.hasOwnProperty(n))
|
||||
continue;
|
||||
output.push(n);
|
||||
}
|
||||
return output;
|
||||
});
|
||||
}
|
||||
commandMetadata() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (this.commandMetadata_)
|
||||
return this.commandMetadata_;
|
||||
let output = yield this.cache_.getItem('metadata');
|
||||
if (output) {
|
||||
this.commandMetadata_ = output;
|
||||
return Object.assign({}, this.commandMetadata_);
|
||||
}
|
||||
const commands = this.commands();
|
||||
output = {};
|
||||
for (const n in commands) {
|
||||
if (!commands.hasOwnProperty(n))
|
||||
continue;
|
||||
const cmd = commands[n];
|
||||
output[n] = cmd.metadata();
|
||||
}
|
||||
yield this.cache_.setItem('metadata', output, 1000 * 60 * 60 * 24);
|
||||
this.commandMetadata_ = output;
|
||||
return Object.assign({}, this.commandMetadata_);
|
||||
});
|
||||
}
|
||||
hasGui() {
|
||||
return this.gui() && !this.gui().isDummy();
|
||||
}
|
||||
findCommandByName(name) {
|
||||
if (this.commands_[name])
|
||||
return this.commands_[name];
|
||||
let CommandClass = null;
|
||||
try {
|
||||
CommandClass = require(`${__dirname}/command-${name}.js`);
|
||||
}
|
||||
catch (error) {
|
||||
if (error.message && error.message.indexOf('Cannot find module') >= 0) {
|
||||
const e = new Error((0, locale_1._)('No such command: %s', name));
|
||||
e.type = 'notFound';
|
||||
throw e;
|
||||
}
|
||||
else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
let cmd = new CommandClass();
|
||||
cmd = this.setupCommand(cmd);
|
||||
this.commands_[name] = cmd;
|
||||
return this.commands_[name];
|
||||
}
|
||||
dummyGui() {
|
||||
return {
|
||||
isDummy: () => {
|
||||
return true;
|
||||
},
|
||||
prompt: (initialText = '', promptString = '', options = null) => {
|
||||
return cliUtils.prompt(initialText, promptString, options);
|
||||
},
|
||||
showConsole: () => { },
|
||||
maximizeConsole: () => { },
|
||||
stdout: (text) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(text);
|
||||
},
|
||||
fullScreen: () => { },
|
||||
exit: () => { },
|
||||
showModalOverlay: () => { },
|
||||
hideModalOverlay: () => { },
|
||||
stdoutMaxWidth: () => {
|
||||
return 100;
|
||||
},
|
||||
forceRender: () => { },
|
||||
termSaveState: () => { },
|
||||
termRestoreState: () => { },
|
||||
};
|
||||
}
|
||||
execCommand(argv) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (!argv.length)
|
||||
return this.execCommand(['help']);
|
||||
// reg.logger().debug('execCommand()', argv);
|
||||
const commandName = argv[0];
|
||||
this.activeCommand_ = this.findCommandByName(commandName);
|
||||
let outException = null;
|
||||
try {
|
||||
if (this.gui().isDummy() && !this.activeCommand_.supportsUi('cli'))
|
||||
throw new Error((0, locale_1._)('The command "%s" is only available in GUI mode', this.activeCommand_.name()));
|
||||
const cmdArgs = cliUtils.makeCommandArgs(this.activeCommand_, argv);
|
||||
yield this.activeCommand_.action(cmdArgs);
|
||||
}
|
||||
catch (error) {
|
||||
outException = error;
|
||||
}
|
||||
this.activeCommand_ = null;
|
||||
if (outException)
|
||||
throw outException;
|
||||
});
|
||||
}
|
||||
currentCommand() {
|
||||
return this.activeCommand_;
|
||||
}
|
||||
loadKeymaps() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const defaultKeyMap = [
|
||||
{ keys: [':'], type: 'function', command: 'enter_command_line_mode' },
|
||||
{ keys: ['TAB'], type: 'function', command: 'focus_next' },
|
||||
{ keys: ['SHIFT_TAB'], type: 'function', command: 'focus_previous' },
|
||||
{ keys: ['UP'], type: 'function', command: 'move_up' },
|
||||
{ keys: ['DOWN'], type: 'function', command: 'move_down' },
|
||||
{ keys: ['PAGE_UP'], type: 'function', command: 'page_up' },
|
||||
{ keys: ['PAGE_DOWN'], type: 'function', command: 'page_down' },
|
||||
{ keys: ['ENTER'], type: 'function', command: 'activate' },
|
||||
{ keys: ['DELETE', 'BACKSPACE'], type: 'function', command: 'delete' },
|
||||
{ keys: ['n'], type: 'function', command: 'next_link' },
|
||||
{ keys: ['b'], type: 'function', command: 'previous_link' },
|
||||
{ keys: ['o'], type: 'function', command: 'open_link' },
|
||||
{ keys: [' '], type: 'prompt', command: 'todo toggle $n' },
|
||||
{ keys: ['tc'], type: 'function', command: 'toggle_console' },
|
||||
{ keys: ['tm'], type: 'function', command: 'toggle_metadata' },
|
||||
{ keys: ['ti'], type: 'function', command: 'toggle_ids' },
|
||||
{ keys: ['/'], type: 'prompt', command: 'search ""', cursorPosition: -2 },
|
||||
{ keys: ['mn'], type: 'prompt', command: 'mknote ""', cursorPosition: -2 },
|
||||
{ keys: ['mt'], type: 'prompt', command: 'mktodo ""', cursorPosition: -2 },
|
||||
{ keys: ['mb'], type: 'prompt', command: 'mkbook ""', cursorPosition: -2 },
|
||||
{ keys: ['yn'], type: 'prompt', command: 'cp $n ""', cursorPosition: -2 },
|
||||
{ keys: ['dn'], type: 'prompt', command: 'mv $n ""', cursorPosition: -2 },
|
||||
];
|
||||
// Filter the keymap item by command so that items in keymap.json can override
|
||||
// the default ones.
|
||||
const itemsByCommand = {};
|
||||
for (let i = 0; i < defaultKeyMap.length; i++) {
|
||||
itemsByCommand[defaultKeyMap[i].command] = defaultKeyMap[i];
|
||||
}
|
||||
const filePath = `${Setting_1.default.value('profileDir')}/keymap.json`;
|
||||
if (yield (0, fs_extra_1.pathExists)(filePath)) {
|
||||
try {
|
||||
let configString = yield (0, fs_extra_1.readFile)(filePath, 'utf-8');
|
||||
configString = configString.replace(/^\s*\/\/.*/, ''); // Strip off comments
|
||||
const keymap = JSON.parse(configString);
|
||||
for (let keymapIndex = 0; keymapIndex < keymap.length; keymapIndex++) {
|
||||
const item = keymap[keymapIndex];
|
||||
itemsByCommand[item.command] = item;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
let msg = error.message ? error.message : '';
|
||||
msg = `Could not load keymap ${filePath}\n${msg}`;
|
||||
error.message = msg;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
const output = [];
|
||||
for (const n in itemsByCommand) {
|
||||
if (!itemsByCommand.hasOwnProperty(n))
|
||||
continue;
|
||||
output.push(itemsByCommand[n]);
|
||||
}
|
||||
// Map reserved shortcuts to their equivalent key
|
||||
// https://github.com/cronvel/terminal-kit/issues/101
|
||||
for (let i = 0; i < output.length; i++) {
|
||||
const newKeys = output[i].keys.map(k => {
|
||||
k = k.replace(/CTRL_H/g, 'BACKSPACE');
|
||||
k = k.replace(/CTRL_I/g, 'TAB');
|
||||
k = k.replace(/CTRL_M/g, 'ENTER');
|
||||
return k;
|
||||
});
|
||||
output[i].keys = newKeys;
|
||||
}
|
||||
return output;
|
||||
});
|
||||
}
|
||||
commandList(argv) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (argv.length && argv[0] === 'batch') {
|
||||
const commands = [];
|
||||
const commandLines = splitCommandBatch(yield (0, fs_extra_1.readFile)(argv[1], 'utf-8'));
|
||||
for (const commandLine of commandLines) {
|
||||
if (!commandLine.trim())
|
||||
continue;
|
||||
const splitted = (0, utils_1.splitCommandString)(commandLine.trim());
|
||||
commands.push(splitted);
|
||||
}
|
||||
return commands;
|
||||
}
|
||||
else {
|
||||
return [argv];
|
||||
}
|
||||
});
|
||||
}
|
||||
// We need this special case here because by the time the `version` command
|
||||
// runs, the keychain has already been setup.
|
||||
checkIfKeychainEnabled(argv) {
|
||||
return argv.indexOf('version') < 0;
|
||||
}
|
||||
start(argv) {
|
||||
const _super = Object.create(null, {
|
||||
start: { get: () => super.start }
|
||||
});
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const keychainEnabled = this.checkIfKeychainEnabled(argv);
|
||||
argv = yield _super.start.call(this, argv, { keychainEnabled });
|
||||
cliUtils.setStdout((object) => {
|
||||
return this.stdout(object);
|
||||
});
|
||||
this.initRedux();
|
||||
// If we have some arguments left at this point, it's a command
|
||||
// so execute it.
|
||||
if (argv.length) {
|
||||
this.gui_ = this.dummyGui();
|
||||
this.currentFolder_ = yield Folder_1.default.load(Setting_1.default.value('activeFolderId'));
|
||||
yield this.applySettingsSideEffects();
|
||||
try {
|
||||
const commands = yield this.commandList(argv);
|
||||
for (const command of commands) {
|
||||
yield this.execCommand(command);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
if (this.showStackTraces_) {
|
||||
console.error(error);
|
||||
}
|
||||
else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(error.message);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
yield Setting_1.default.saveAll();
|
||||
// Need to call exit() explicitly, otherwise Node wait for any timeout to complete
|
||||
// https://stackoverflow.com/questions/18050095
|
||||
process.exit(0);
|
||||
}
|
||||
else {
|
||||
// Otherwise open the GUI
|
||||
const keymap = yield this.loadKeymaps();
|
||||
const AppGui = require('./app-gui.js');
|
||||
this.gui_ = new AppGui(this, this.store(), keymap);
|
||||
this.gui_.setLogger(this.logger());
|
||||
yield this.gui_.start();
|
||||
// Since the settings need to be loaded before the store is created, it will never
|
||||
// receive the SETTING_UPDATE_ALL even, which mean state.settings will not be
|
||||
// initialised. So we manually call dispatchUpdateAll() to force an update.
|
||||
Setting_1.default.dispatchUpdateAll();
|
||||
yield (0, folders_screen_utils_js_1.refreshFolders)((action) => this.store().dispatch(action));
|
||||
const tags = yield Tag_1.default.allWithNotes();
|
||||
ResourceService_1.default.runInBackground();
|
||||
RevisionService_1.default.instance().runInBackground();
|
||||
this.dispatch({
|
||||
type: 'TAG_UPDATE_ALL',
|
||||
items: tags,
|
||||
});
|
||||
this.store().dispatch({
|
||||
type: 'FOLDER_SELECT',
|
||||
id: Setting_1.default.value('activeFolderId'),
|
||||
});
|
||||
this.startRotatingLogMaintenance(Setting_1.default.value('profileDir'));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let application_ = null;
|
||||
|
||||
function app() {
|
||||
if (application_) return application_;
|
||||
application_ = new Application();
|
||||
return application_;
|
||||
if (application_)
|
||||
return application_;
|
||||
application_ = new Application();
|
||||
return application_;
|
||||
}
|
||||
|
||||
module.exports = { app };
|
||||
exports.default = app;
|
||||
//# sourceMappingURL=app.js.map
|
472
packages/app-cli/app/app.ts
Normal file
@@ -0,0 +1,472 @@
|
||||
import BaseApplication from '@joplin/lib/BaseApplication';
|
||||
import { refreshFolders } from '@joplin/lib/folders-screen-utils.js';
|
||||
import ResourceService from '@joplin/lib/services/ResourceService';
|
||||
import BaseModel, { ModelType } from '@joplin/lib/BaseModel';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
import BaseItem from '@joplin/lib/models/BaseItem';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
import Tag from '@joplin/lib/models/Tag';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import { reg } from '@joplin/lib/registry.js';
|
||||
import { fileExtension } from '@joplin/lib/path-utils';
|
||||
import { splitCommandString } from '@joplin/utils';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { pathExists, readFile, readdirSync } from 'fs-extra';
|
||||
import RevisionService from '@joplin/lib/services/RevisionService';
|
||||
import shim from '@joplin/lib/shim';
|
||||
import setupCommand from './setupCommand';
|
||||
import { FolderEntity, NoteEntity } from '@joplin/lib/services/database/types';
|
||||
const { cliUtils } = require('./cli-utils.js');
|
||||
const Cache = require('@joplin/lib/Cache');
|
||||
const { splitCommandBatch } = require('@joplin/lib/string-utils');
|
||||
|
||||
class Application extends BaseApplication {
|
||||
|
||||
private commands_: Record<string, any> = {};
|
||||
private commandMetadata_: any = null;
|
||||
private activeCommand_: any = null;
|
||||
private allCommandsLoaded_ = false;
|
||||
private gui_: any = null;
|
||||
private cache_ = new Cache();
|
||||
|
||||
public gui() {
|
||||
return this.gui_;
|
||||
}
|
||||
|
||||
public commandStdoutMaxWidth() {
|
||||
return this.gui().stdoutMaxWidth();
|
||||
}
|
||||
|
||||
public async guessTypeAndLoadItem(pattern: string, options: any = null) {
|
||||
let type = BaseModel.TYPE_NOTE;
|
||||
if (pattern.indexOf('/') === 0) {
|
||||
type = BaseModel.TYPE_FOLDER;
|
||||
pattern = pattern.substr(1);
|
||||
}
|
||||
return this.loadItem(type, pattern, options);
|
||||
}
|
||||
|
||||
public async loadItem(type: ModelType | 'folderOrNote', pattern: string, options: any = null) {
|
||||
const output = await this.loadItems(type, pattern, options);
|
||||
|
||||
if (output.length > 1) {
|
||||
// output.sort((a, b) => { return a.user_updated_time < b.user_updated_time ? +1 : -1; });
|
||||
|
||||
// let answers = { 0: _('[Cancel]') };
|
||||
// for (let i = 0; i < output.length; i++) {
|
||||
// answers[i + 1] = output[i].title;
|
||||
// }
|
||||
|
||||
// Not really useful with new UI?
|
||||
throw new Error(_('More than one item match "%s". Please narrow down your query.', pattern));
|
||||
|
||||
// let msg = _('More than one item match "%s". Please select one:', pattern);
|
||||
// const response = await cliUtils.promptMcq(msg, answers);
|
||||
// if (!response) return null;
|
||||
|
||||
// return output[response - 1];
|
||||
} else {
|
||||
return output.length ? output[0] : null;
|
||||
}
|
||||
}
|
||||
|
||||
public async loadItems(type: ModelType | 'folderOrNote', pattern: string, options: any = null): Promise<(FolderEntity | NoteEntity)[]> {
|
||||
if (type === 'folderOrNote') {
|
||||
const folders: FolderEntity[] = await this.loadItems(BaseModel.TYPE_FOLDER, pattern, options);
|
||||
if (folders.length) return folders;
|
||||
return await this.loadItems(BaseModel.TYPE_NOTE, pattern, options);
|
||||
}
|
||||
|
||||
pattern = pattern ? pattern.toString() : '';
|
||||
|
||||
if (type === BaseModel.TYPE_FOLDER && (pattern === Folder.conflictFolderTitle() || pattern === Folder.conflictFolderId())) return [Folder.conflictFolder()];
|
||||
|
||||
if (!options) options = {};
|
||||
|
||||
const parent = options.parent ? options.parent : app().currentFolder();
|
||||
const ItemClass = BaseItem.itemClass(type);
|
||||
|
||||
if (type === BaseModel.TYPE_NOTE && pattern.indexOf('*') >= 0) {
|
||||
// Handle it as pattern
|
||||
if (!parent) throw new Error(_('No notebook selected.'));
|
||||
return await Note.previews(parent.id, { titlePattern: pattern });
|
||||
} else {
|
||||
// Single item
|
||||
let item = null;
|
||||
if (type === BaseModel.TYPE_NOTE) {
|
||||
if (!parent) throw new Error(_('No notebook has been specified.'));
|
||||
item = await ItemClass.loadFolderNoteByField(parent.id, 'title', pattern);
|
||||
} else {
|
||||
item = await ItemClass.loadByTitle(pattern);
|
||||
}
|
||||
if (item) return [item];
|
||||
|
||||
item = await ItemClass.load(pattern); // Load by id
|
||||
if (item) return [item];
|
||||
|
||||
if (pattern.length >= 2) {
|
||||
return await ItemClass.loadByPartialId(pattern);
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public setupCommand(cmd: string) {
|
||||
return setupCommand(cmd, (t: string) => this.stdout(t), () => this.store(), () => this.gui());
|
||||
}
|
||||
|
||||
public stdout(text: string) {
|
||||
return this.gui().stdout(text);
|
||||
}
|
||||
|
||||
public async exit(code = 0) {
|
||||
const doExit = async () => {
|
||||
this.gui().exit();
|
||||
await super.exit(code);
|
||||
};
|
||||
|
||||
// Give it a few seconds to cancel otherwise exit anyway
|
||||
shim.setTimeout(async () => {
|
||||
await doExit();
|
||||
}, 5000);
|
||||
|
||||
if (await reg.syncTarget().syncStarted()) {
|
||||
this.stdout(_('Cancelling background synchronisation... Please wait.'));
|
||||
const sync = await reg.syncTarget().synchronizer();
|
||||
await sync.cancel();
|
||||
}
|
||||
|
||||
await doExit();
|
||||
}
|
||||
|
||||
public commands(uiType: string = null) {
|
||||
if (!this.allCommandsLoaded_) {
|
||||
// eslint-disable-next-line github/array-foreach -- Old code before rule was applied
|
||||
readdirSync(__dirname).forEach(path => {
|
||||
if (path.indexOf('command-') !== 0) return;
|
||||
if (path.endsWith('.test.js')) return;
|
||||
const ext = fileExtension(path);
|
||||
if (ext !== 'js') return;
|
||||
|
||||
const CommandClass = require(`./${path}`);
|
||||
let cmd = new CommandClass();
|
||||
if (!cmd.enabled()) return;
|
||||
cmd = this.setupCommand(cmd);
|
||||
this.commands_[cmd.name()] = cmd;
|
||||
});
|
||||
|
||||
this.allCommandsLoaded_ = true;
|
||||
}
|
||||
|
||||
if (uiType !== null) {
|
||||
const temp: Record<string, any> = {};
|
||||
for (const n in this.commands_) {
|
||||
if (!this.commands_.hasOwnProperty(n)) continue;
|
||||
const c = this.commands_[n];
|
||||
if (!c.supportsUi(uiType)) continue;
|
||||
temp[n] = c;
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
|
||||
return this.commands_;
|
||||
}
|
||||
|
||||
public async commandNames() {
|
||||
const metadata = await this.commandMetadata();
|
||||
const output = [];
|
||||
for (const n in metadata) {
|
||||
if (!metadata.hasOwnProperty(n)) continue;
|
||||
output.push(n);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
public async commandMetadata() {
|
||||
if (this.commandMetadata_) return this.commandMetadata_;
|
||||
|
||||
let output = await this.cache_.getItem('metadata');
|
||||
if (output) {
|
||||
this.commandMetadata_ = output;
|
||||
return { ...this.commandMetadata_ };
|
||||
}
|
||||
|
||||
const commands = this.commands();
|
||||
|
||||
output = {};
|
||||
for (const n in commands) {
|
||||
if (!commands.hasOwnProperty(n)) continue;
|
||||
const cmd = commands[n];
|
||||
output[n] = cmd.metadata();
|
||||
}
|
||||
|
||||
await this.cache_.setItem('metadata', output, 1000 * 60 * 60 * 24);
|
||||
|
||||
this.commandMetadata_ = output;
|
||||
return { ...this.commandMetadata_ };
|
||||
}
|
||||
|
||||
public hasGui() {
|
||||
return this.gui() && !this.gui().isDummy();
|
||||
}
|
||||
|
||||
public findCommandByName(name: string) {
|
||||
if (this.commands_[name]) return this.commands_[name];
|
||||
|
||||
let CommandClass = null;
|
||||
try {
|
||||
CommandClass = require(`${__dirname}/command-${name}.js`);
|
||||
} catch (error) {
|
||||
if (error.message && error.message.indexOf('Cannot find module') >= 0) {
|
||||
const e: any = new Error(_('No such command: %s', name));
|
||||
e.type = 'notFound';
|
||||
throw e;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
let cmd = new CommandClass();
|
||||
cmd = this.setupCommand(cmd);
|
||||
this.commands_[name] = cmd;
|
||||
return this.commands_[name];
|
||||
}
|
||||
|
||||
public dummyGui() {
|
||||
return {
|
||||
isDummy: () => {
|
||||
return true;
|
||||
},
|
||||
prompt: (initialText = '', promptString = '', options: any = null) => {
|
||||
return cliUtils.prompt(initialText, promptString, options);
|
||||
},
|
||||
showConsole: () => {},
|
||||
maximizeConsole: () => {},
|
||||
stdout: (text: string) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(text);
|
||||
},
|
||||
fullScreen: () => {},
|
||||
exit: () => {},
|
||||
showModalOverlay: () => {},
|
||||
hideModalOverlay: () => {},
|
||||
stdoutMaxWidth: () => {
|
||||
return 100;
|
||||
},
|
||||
forceRender: () => {},
|
||||
termSaveState: () => {},
|
||||
termRestoreState: () => {},
|
||||
};
|
||||
}
|
||||
|
||||
public async execCommand(argv: string[]): Promise<any> {
|
||||
if (!argv.length) return this.execCommand(['help']);
|
||||
// reg.logger().debug('execCommand()', argv);
|
||||
const commandName = argv[0];
|
||||
this.activeCommand_ = this.findCommandByName(commandName);
|
||||
|
||||
let outException = null;
|
||||
try {
|
||||
if (this.gui().isDummy() && !this.activeCommand_.supportsUi('cli')) throw new Error(_('The command "%s" is only available in GUI mode', this.activeCommand_.name()));
|
||||
const cmdArgs = cliUtils.makeCommandArgs(this.activeCommand_, argv);
|
||||
await this.activeCommand_.action(cmdArgs);
|
||||
} catch (error) {
|
||||
outException = error;
|
||||
}
|
||||
this.activeCommand_ = null;
|
||||
if (outException) throw outException;
|
||||
}
|
||||
|
||||
public currentCommand() {
|
||||
return this.activeCommand_;
|
||||
}
|
||||
|
||||
public async loadKeymaps() {
|
||||
interface KeyMapItem {
|
||||
keys: string[];
|
||||
type: 'function' | 'prompt';
|
||||
command: string;
|
||||
cursorPosition?: number;
|
||||
}
|
||||
|
||||
const defaultKeyMap: KeyMapItem[] = [
|
||||
{ keys: [':'], type: 'function', command: 'enter_command_line_mode' },
|
||||
{ keys: ['TAB'], type: 'function', command: 'focus_next' },
|
||||
{ keys: ['SHIFT_TAB'], type: 'function', command: 'focus_previous' },
|
||||
{ keys: ['UP'], type: 'function', command: 'move_up' },
|
||||
{ keys: ['DOWN'], type: 'function', command: 'move_down' },
|
||||
{ keys: ['PAGE_UP'], type: 'function', command: 'page_up' },
|
||||
{ keys: ['PAGE_DOWN'], type: 'function', command: 'page_down' },
|
||||
{ keys: ['ENTER'], type: 'function', command: 'activate' },
|
||||
{ keys: ['DELETE', 'BACKSPACE'], type: 'function', command: 'delete' },
|
||||
{ keys: ['n'], type: 'function', command: 'next_link' },
|
||||
{ keys: ['b'], type: 'function', command: 'previous_link' },
|
||||
{ keys: ['o'], type: 'function', command: 'open_link' },
|
||||
{ keys: [' '], type: 'prompt', command: 'todo toggle $n' },
|
||||
{ keys: ['tc'], type: 'function', command: 'toggle_console' },
|
||||
{ keys: ['tm'], type: 'function', command: 'toggle_metadata' },
|
||||
{ keys: ['ti'], type: 'function', command: 'toggle_ids' },
|
||||
{ keys: ['/'], type: 'prompt', command: 'search ""', cursorPosition: -2 },
|
||||
{ keys: ['mn'], type: 'prompt', command: 'mknote ""', cursorPosition: -2 },
|
||||
{ keys: ['mt'], type: 'prompt', command: 'mktodo ""', cursorPosition: -2 },
|
||||
{ keys: ['mb'], type: 'prompt', command: 'mkbook ""', cursorPosition: -2 },
|
||||
{ keys: ['yn'], type: 'prompt', command: 'cp $n ""', cursorPosition: -2 },
|
||||
{ keys: ['dn'], type: 'prompt', command: 'mv $n ""', cursorPosition: -2 },
|
||||
];
|
||||
|
||||
// Filter the keymap item by command so that items in keymap.json can override
|
||||
// the default ones.
|
||||
const itemsByCommand: Record<string, KeyMapItem> = {};
|
||||
|
||||
for (let i = 0; i < defaultKeyMap.length; i++) {
|
||||
itemsByCommand[defaultKeyMap[i].command] = defaultKeyMap[i];
|
||||
}
|
||||
|
||||
const filePath = `${Setting.value('profileDir')}/keymap.json`;
|
||||
if (await pathExists(filePath)) {
|
||||
try {
|
||||
let configString = await readFile(filePath, 'utf-8');
|
||||
configString = configString.replace(/^\s*\/\/.*/, ''); // Strip off comments
|
||||
const keymap = JSON.parse(configString);
|
||||
for (let keymapIndex = 0; keymapIndex < keymap.length; keymapIndex++) {
|
||||
const item = keymap[keymapIndex];
|
||||
itemsByCommand[item.command] = item;
|
||||
}
|
||||
} catch (error) {
|
||||
let msg = error.message ? error.message : '';
|
||||
msg = `Could not load keymap ${filePath}\n${msg}`;
|
||||
error.message = msg;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const output = [];
|
||||
for (const n in itemsByCommand) {
|
||||
if (!itemsByCommand.hasOwnProperty(n)) continue;
|
||||
output.push(itemsByCommand[n]);
|
||||
}
|
||||
|
||||
// Map reserved shortcuts to their equivalent key
|
||||
// https://github.com/cronvel/terminal-kit/issues/101
|
||||
for (let i = 0; i < output.length; i++) {
|
||||
const newKeys = output[i].keys.map(k => {
|
||||
k = k.replace(/CTRL_H/g, 'BACKSPACE');
|
||||
k = k.replace(/CTRL_I/g, 'TAB');
|
||||
k = k.replace(/CTRL_M/g, 'ENTER');
|
||||
return k;
|
||||
});
|
||||
output[i].keys = newKeys;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public async commandList(argv: string[]) {
|
||||
if (argv.length && argv[0] === 'batch') {
|
||||
const commands = [];
|
||||
const commandLines = splitCommandBatch(await readFile(argv[1], 'utf-8'));
|
||||
|
||||
for (const commandLine of commandLines) {
|
||||
if (!commandLine.trim()) continue;
|
||||
const splitted = splitCommandString(commandLine.trim());
|
||||
commands.push(splitted);
|
||||
}
|
||||
return commands;
|
||||
} else {
|
||||
return [argv];
|
||||
}
|
||||
}
|
||||
|
||||
// We need this special case here because by the time the `version` command
|
||||
// runs, the keychain has already been setup.
|
||||
public checkIfKeychainEnabled(argv: string[]) {
|
||||
return argv.indexOf('version') < 0;
|
||||
}
|
||||
|
||||
public async start(argv: string[]) {
|
||||
const keychainEnabled = this.checkIfKeychainEnabled(argv);
|
||||
|
||||
argv = await super.start(argv, { keychainEnabled });
|
||||
|
||||
cliUtils.setStdout((object: any) => {
|
||||
return this.stdout(object);
|
||||
});
|
||||
|
||||
this.initRedux();
|
||||
|
||||
// If we have some arguments left at this point, it's a command
|
||||
// so execute it.
|
||||
if (argv.length) {
|
||||
this.gui_ = this.dummyGui();
|
||||
|
||||
this.currentFolder_ = await Folder.load(Setting.value('activeFolderId'));
|
||||
|
||||
await this.applySettingsSideEffects();
|
||||
|
||||
try {
|
||||
const commands = await this.commandList(argv);
|
||||
for (const command of commands) {
|
||||
await this.execCommand(command);
|
||||
}
|
||||
} catch (error) {
|
||||
if (this.showStackTraces_) {
|
||||
console.error(error);
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(error.message);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await Setting.saveAll();
|
||||
|
||||
// Need to call exit() explicitly, otherwise Node wait for any timeout to complete
|
||||
// https://stackoverflow.com/questions/18050095
|
||||
process.exit(0);
|
||||
} else {
|
||||
// Otherwise open the GUI
|
||||
const keymap = await this.loadKeymaps();
|
||||
|
||||
const AppGui = require('./app-gui.js');
|
||||
this.gui_ = new AppGui(this, this.store(), keymap);
|
||||
this.gui_.setLogger(this.logger());
|
||||
await this.gui_.start();
|
||||
|
||||
// Since the settings need to be loaded before the store is created, it will never
|
||||
// receive the SETTING_UPDATE_ALL even, which mean state.settings will not be
|
||||
// initialised. So we manually call dispatchUpdateAll() to force an update.
|
||||
Setting.dispatchUpdateAll();
|
||||
|
||||
await refreshFolders((action: any) => this.store().dispatch(action));
|
||||
|
||||
const tags = await Tag.allWithNotes();
|
||||
|
||||
ResourceService.runInBackground();
|
||||
|
||||
RevisionService.instance().runInBackground();
|
||||
|
||||
this.dispatch({
|
||||
type: 'TAG_UPDATE_ALL',
|
||||
items: tags,
|
||||
});
|
||||
|
||||
this.store().dispatch({
|
||||
type: 'FOLDER_SELECT',
|
||||
id: Setting.value('activeFolderId'),
|
||||
});
|
||||
|
||||
this.startRotatingLogMaintenance(Setting.value('profileDir'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let application_: Application = null;
|
||||
|
||||
function app() {
|
||||
if (application_) return application_;
|
||||
application_ = new Application();
|
||||
return application_;
|
||||
}
|
||||
|
||||
export default app;
|
@@ -1,4 +1,4 @@
|
||||
const { app } = require('./app.js');
|
||||
const app = require('./app').default;
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
const Folder = require('@joplin/lib/models/Folder').default;
|
||||
const Tag = require('@joplin/lib/models/Tag').default;
|
||||
|
@@ -1,33 +1,34 @@
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const BaseItem = require('@joplin/lib/models/BaseItem').default;
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
import BaseCommand from './base-command';
|
||||
import BaseItem from '@joplin/lib/models/BaseItem';
|
||||
import BaseModel from '@joplin/lib/BaseModel';
|
||||
const { toTitleCase } = require('@joplin/lib/string-utils.js');
|
||||
const { reg } = require('@joplin/lib/registry.js');
|
||||
const markdownUtils = require('@joplin/lib/markdownUtils').default;
|
||||
const Database = require('@joplin/lib/database').default;
|
||||
const shim = require('@joplin/lib/shim').default;
|
||||
import { reg } from '@joplin/lib/registry.js';
|
||||
import markdownUtils, { MarkdownTableRow } from '@joplin/lib/markdownUtils';
|
||||
import Database from '@joplin/lib/database';
|
||||
import shim from '@joplin/lib/shim';
|
||||
|
||||
class Command extends BaseCommand {
|
||||
usage() {
|
||||
public override usage() {
|
||||
return 'apidoc <file>';
|
||||
}
|
||||
|
||||
description() {
|
||||
public override description() {
|
||||
return 'Build the API doc';
|
||||
}
|
||||
|
||||
enabled() {
|
||||
public override enabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
createPropertiesTable(tableFields) {
|
||||
private createPropertiesTable(tableFields: MarkdownTableRow[]) {
|
||||
const headers = [
|
||||
{ name: 'name', label: 'Name' },
|
||||
{
|
||||
name: 'type',
|
||||
label: 'Type',
|
||||
filter: value => {
|
||||
return Database.enumName('fieldType', value);
|
||||
filter: (value: string|number) => {
|
||||
const valueAsNumber = typeof value === 'number' ? value : parseInt(value, 10);
|
||||
return Database.enumName('fieldType', valueAsNumber);
|
||||
},
|
||||
},
|
||||
{ name: 'description', label: 'Description' },
|
||||
@@ -36,7 +37,7 @@ class Command extends BaseCommand {
|
||||
return markdownUtils.createMarkdownTable(headers, tableFields);
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
public override async action(args: any) {
|
||||
const models = [
|
||||
{
|
||||
type: BaseModel.TYPE_NOTE,
|
@@ -1,19 +1,19 @@
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
const shim = require('@joplin/lib/shim').default;
|
||||
import BaseCommand from './base-command';
|
||||
import app from './app';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import BaseModel from '@joplin/lib/BaseModel';
|
||||
import shim from '@joplin/lib/shim';
|
||||
|
||||
class Command extends BaseCommand {
|
||||
usage() {
|
||||
public override usage() {
|
||||
return 'attach <note> <file>';
|
||||
}
|
||||
|
||||
description() {
|
||||
public override description() {
|
||||
return _('Attaches the given file to the note.');
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
public override async action(args: any) {
|
||||
const title = args['note'];
|
||||
|
||||
const note = await app().loadItem(BaseModel.TYPE_NOTE, title, { parent: app().currentFolder() });
|
@@ -1,24 +1,24 @@
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
const BaseItem = require('@joplin/lib/models/BaseItem').default;
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
import BaseCommand from './base-command';
|
||||
import app from './app';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import BaseModel from '@joplin/lib/BaseModel';
|
||||
import BaseItem from '@joplin/lib/models/BaseItem';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
|
||||
class Command extends BaseCommand {
|
||||
usage() {
|
||||
public override usage() {
|
||||
return 'cat <note>';
|
||||
}
|
||||
|
||||
description() {
|
||||
public override description() {
|
||||
return _('Displays the given note.');
|
||||
}
|
||||
|
||||
options() {
|
||||
public override options() {
|
||||
return [['-v, --verbose', _('Displays the complete information about note.')]];
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
public override async action(args: any) {
|
||||
const title = args['note'];
|
||||
|
||||
const item = await app().loadItem(BaseModel.TYPE_NOTE, title, { parent: app().currentFolder() });
|
@@ -1,19 +1,20 @@
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { _, setLocale } = require('@joplin/lib/locale');
|
||||
const { app } = require('./app.js');
|
||||
const fs = require('fs-extra');
|
||||
const Setting = require('@joplin/lib/models/Setting').default;
|
||||
import BaseCommand from './base-command';
|
||||
import { _, setLocale } from '@joplin/lib/locale';
|
||||
import app from './app';
|
||||
import * as fs from 'fs-extra';
|
||||
import Setting, { AppType } from '@joplin/lib/models/Setting';
|
||||
import { ReadStream } from 'tty';
|
||||
|
||||
class Command extends BaseCommand {
|
||||
usage() {
|
||||
public override usage() {
|
||||
return 'config [name] [value]';
|
||||
}
|
||||
|
||||
description() {
|
||||
public override description() {
|
||||
return _('Gets or sets a config value. If [value] is not provided, it will show the value of [name]. If neither [name] nor [value] is provided, it will list the current configuration.');
|
||||
}
|
||||
|
||||
options() {
|
||||
public override options() {
|
||||
return [
|
||||
['-v, --verbose', _('Also displays unset and hidden config variables.')],
|
||||
['--export', 'Writes all settings to STDOUT as JSON including secure variables.'],
|
||||
@@ -21,11 +22,12 @@ class Command extends BaseCommand {
|
||||
['--import-file <file>', 'Reads in settings from <file>. <file> must contain valid JSON.'],
|
||||
];
|
||||
}
|
||||
async __importSettings(inputStream) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
private async __importSettings(inputStream: ReadStream|fs.ReadStream) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
// being defensive and not attempting to settle twice
|
||||
let isSettled = false;
|
||||
const chunks = [];
|
||||
const chunks: any = [];
|
||||
|
||||
inputStream.on('readable', () => {
|
||||
let chunk;
|
||||
@@ -64,13 +66,14 @@ class Command extends BaseCommand {
|
||||
});
|
||||
});
|
||||
}
|
||||
async action(args) {
|
||||
|
||||
public override async action(args: any) {
|
||||
const verbose = args.options.verbose;
|
||||
const isExport = args.options.export;
|
||||
const isImport = args.options.import || args.options.importFile;
|
||||
const importFile = args.options.importFile;
|
||||
|
||||
const renderKeyValue = name => {
|
||||
const renderKeyValue = (name: string) => {
|
||||
const md = Setting.settingMetadata(name);
|
||||
let value = Setting.value(name);
|
||||
if (typeof value === 'object' || Array.isArray(value)) value = JSON.stringify(value);
|
||||
@@ -84,11 +87,11 @@ class Command extends BaseCommand {
|
||||
};
|
||||
|
||||
if (isExport || (!isImport && !args.value)) {
|
||||
const keys = Setting.keys(!verbose, 'cli');
|
||||
const keys = Setting.keys(!verbose, AppType.Cli);
|
||||
keys.sort();
|
||||
|
||||
if (isExport) {
|
||||
const resultObj = keys.reduce((acc, key) => {
|
||||
const resultObj = keys.reduce<Record<string, any>>((acc, key) => {
|
||||
const value = Setting.value(key);
|
||||
if (!verbose && !value) return acc;
|
||||
acc[key] = value;
|
||||
@@ -113,7 +116,7 @@ class Command extends BaseCommand {
|
||||
}
|
||||
|
||||
if (isImport) {
|
||||
let fileStream = process.stdin;
|
||||
let fileStream: ReadStream|fs.ReadStream = process.stdin;
|
||||
if (importFile) {
|
||||
fileStream = fs.createReadStream(importFile, { autoClose: true });
|
||||
}
|
@@ -1,19 +1,19 @@
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
import BaseCommand from './base-command';
|
||||
import app from './app';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import BaseModel from '@joplin/lib/BaseModel';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
|
||||
class Command extends BaseCommand {
|
||||
usage() {
|
||||
public override usage() {
|
||||
return 'cp <note> [notebook]';
|
||||
}
|
||||
|
||||
description() {
|
||||
public override description() {
|
||||
return _('Duplicates the notes matching <note> to [notebook]. If no notebook is specified the note is duplicated in the current notebook.');
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
public override async action(args: any) {
|
||||
let folder = null;
|
||||
if (args['notebook']) {
|
||||
folder = await app().loadItem(BaseModel.TYPE_FOLDER, args['notebook']);
|
||||
@@ -28,7 +28,7 @@ class Command extends BaseCommand {
|
||||
|
||||
for (let i = 0; i < notes.length; i++) {
|
||||
const newNote = await Note.copyToFolder(notes[i].id, folder.id);
|
||||
Note.updateGeolocation(newNote.id);
|
||||
void Note.updateGeolocation(newNote.id);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,38 +0,0 @@
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
const time = require('@joplin/lib/time').default;
|
||||
|
||||
class Command extends BaseCommand {
|
||||
usage() {
|
||||
return 'done <note>';
|
||||
}
|
||||
|
||||
description() {
|
||||
return _('Marks a to-do as done.');
|
||||
}
|
||||
|
||||
static async handleAction(commandInstance, args, isCompleted) {
|
||||
const note = await app().loadItem(BaseModel.TYPE_NOTE, args.note);
|
||||
commandInstance.encryptionCheck(note);
|
||||
if (!note) throw new Error(_('Cannot find "%s".', args.note));
|
||||
if (!note.is_todo) throw new Error(_('Note is not a to-do: "%s"', args.note));
|
||||
|
||||
const todoCompleted = !!note.todo_completed;
|
||||
|
||||
if (isCompleted === todoCompleted) return;
|
||||
|
||||
await Note.save({
|
||||
id: note.id,
|
||||
todo_completed: isCompleted ? time.unixMs() : 0,
|
||||
});
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
await Command.handleAction(this, args, true);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Command;
|
39
packages/app-cli/app/command-done.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import BaseCommand from './base-command';
|
||||
import app from './app';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import BaseModel from '@joplin/lib/BaseModel';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
import time from '@joplin/lib/time';
|
||||
import { NoteEntity } from '@joplin/lib/services/database/types';
|
||||
|
||||
class Command extends BaseCommand {
|
||||
public override usage() {
|
||||
return 'done <note>';
|
||||
}
|
||||
|
||||
public override description() {
|
||||
return _('Marks a to-do as done.');
|
||||
}
|
||||
|
||||
public static async handleAction(commandInstance: BaseCommand, args: any, isCompleted: boolean) {
|
||||
const note: NoteEntity = await app().loadItem(BaseModel.TYPE_NOTE, args.note);
|
||||
commandInstance.encryptionCheck(note);
|
||||
if (!note) throw new Error(_('Cannot find "%s".', args.note));
|
||||
if (!note.is_todo) throw new Error(_('Note is not a to-do: "%s"', args.note));
|
||||
|
||||
const todoCompleted = !!note.todo_completed;
|
||||
|
||||
if (isCompleted === todoCompleted) return;
|
||||
|
||||
await Note.save({
|
||||
id: note.id,
|
||||
todo_completed: isCompleted ? time.unixMs() : 0,
|
||||
});
|
||||
}
|
||||
|
||||
public override async action(args: any) {
|
||||
await Command.handleAction(this, args, true);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Command;
|
@@ -1,22 +1,22 @@
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const Folder = require('@joplin/lib/models/Folder').default;
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
const Tag = require('@joplin/lib/models/Tag').default;
|
||||
import BaseCommand from './base-command';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
import Tag from '@joplin/lib/models/Tag';
|
||||
|
||||
class Command extends BaseCommand {
|
||||
usage() {
|
||||
public override usage() {
|
||||
return 'dump';
|
||||
}
|
||||
|
||||
description() {
|
||||
public override description() {
|
||||
return 'Dumps the complete database as JSON.';
|
||||
}
|
||||
|
||||
hidden() {
|
||||
public override hidden() {
|
||||
return true;
|
||||
}
|
||||
|
||||
async action() {
|
||||
public override async action() {
|
||||
let items = [];
|
||||
const folders = await Folder.all();
|
||||
for (let i = 0; i < folders.length; i++) {
|
@@ -1,24 +1,24 @@
|
||||
const fs = require('fs-extra');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { splitCommandString } = require('@joplin/utils');
|
||||
const uuid = require('@joplin/lib/uuid').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
const Setting = require('@joplin/lib/models/Setting').default;
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
import * as fs from 'fs-extra';
|
||||
import BaseCommand from './base-command';
|
||||
import { splitCommandString } from '@joplin/utils';
|
||||
import uuid from '@joplin/lib/uuid';
|
||||
import app from './app';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import BaseModel from '@joplin/lib/BaseModel';
|
||||
|
||||
class Command extends BaseCommand {
|
||||
usage() {
|
||||
public override usage() {
|
||||
return 'edit <note>';
|
||||
}
|
||||
|
||||
description() {
|
||||
public override description() {
|
||||
return _('Edit note.');
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
let tempFilePath = null;
|
||||
public override async action(args: any) {
|
||||
let tempFilePath: string|null = null;
|
||||
|
||||
const onFinishedEditing = async () => {
|
||||
if (tempFilePath) fs.removeSync(tempFilePath);
|
@@ -1,23 +0,0 @@
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
usage() {
|
||||
return 'exit';
|
||||
}
|
||||
|
||||
description() {
|
||||
return _('Exits the application.');
|
||||
}
|
||||
|
||||
compatibleUis() {
|
||||
return ['gui'];
|
||||
}
|
||||
|
||||
async action() {
|
||||
await app().exit();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Command;
|
23
packages/app-cli/app/command-exit.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import BaseCommand from './base-command';
|
||||
import app from './app';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
|
||||
class Command extends BaseCommand {
|
||||
public override usage() {
|
||||
return 'exit';
|
||||
}
|
||||
|
||||
public override description() {
|
||||
return _('Exits the application.');
|
||||
}
|
||||
|
||||
public override compatibleUis() {
|
||||
return ['gui'];
|
||||
}
|
||||
|
||||
public override async action() {
|
||||
await app().exit();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Command;
|
@@ -1,25 +1,25 @@
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const Setting = require('@joplin/lib/models/Setting').default;
|
||||
const ReportService = require('@joplin/lib/services/ReportService').default;
|
||||
const fs = require('fs-extra');
|
||||
import BaseCommand from './base-command';
|
||||
import app from './app';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import ReportService from '@joplin/lib/services/ReportService';
|
||||
import * as fs from 'fs-extra';
|
||||
|
||||
class Command extends BaseCommand {
|
||||
usage() {
|
||||
public override usage() {
|
||||
return 'export-sync-status';
|
||||
}
|
||||
|
||||
description() {
|
||||
public override description() {
|
||||
return 'Export sync status';
|
||||
}
|
||||
|
||||
hidden() {
|
||||
public override hidden() {
|
||||
return true;
|
||||
}
|
||||
|
||||
async action() {
|
||||
public override async action() {
|
||||
const service = new ReportService();
|
||||
const csv = await service.basicItemList({ format: 'csv' });
|
||||
const csv = await service.basicItemList({ format: 'csv' }) as string;
|
||||
const filePath = `${Setting.value('profileDir')}/syncReport-${new Date().getTime()}.csv`;
|
||||
await fs.writeFileSync(filePath, csv);
|
||||
this.stdout(`Sync status exported to ${filePath}`);
|
@@ -1,19 +1,20 @@
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const InteropService = require('@joplin/lib/services/interop/InteropService').default;
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
import BaseCommand from './base-command';
|
||||
import InteropService from '@joplin/lib/services/interop/InteropService';
|
||||
import BaseModel from '@joplin/lib/BaseModel';
|
||||
import app from './app';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { ExportOptions } from '@joplin/lib/services/interop/types';
|
||||
|
||||
class Command extends BaseCommand {
|
||||
usage() {
|
||||
public override usage() {
|
||||
return 'export <path>';
|
||||
}
|
||||
|
||||
description() {
|
||||
public override description() {
|
||||
return _('Exports Joplin data to the given path. By default, it will export the complete database including notebooks, notes, tags and resources.');
|
||||
}
|
||||
|
||||
options() {
|
||||
public override options() {
|
||||
const service = InteropService.instance();
|
||||
const formats = service
|
||||
.modules()
|
||||
@@ -23,8 +24,8 @@ class Command extends BaseCommand {
|
||||
return [['--format <format>', _('Destination format: %s', formats.join(', '))], ['--note <note>', _('Exports only the given note.')], ['--notebook <notebook>', _('Exports only the given notebook.')]];
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
const exportOptions = {};
|
||||
public override async action(args: any) {
|
||||
const exportOptions: ExportOptions = {};
|
||||
exportOptions.path = args.path;
|
||||
|
||||
exportOptions.format = args.options.format ? args.options.format : 'jex';
|
||||
@@ -34,11 +35,11 @@ class Command extends BaseCommand {
|
||||
if (args.options.note) {
|
||||
const notes = await app().loadItems(BaseModel.TYPE_NOTE, args.options.note, { parent: app().currentFolder() });
|
||||
if (!notes.length) throw new Error(_('Cannot find "%s".', args.options.note));
|
||||
exportOptions.sourceNoteIds = notes.map(n => n.id);
|
||||
exportOptions.sourceNoteIds = notes.map((n: any) => n.id);
|
||||
} else if (args.options.notebook) {
|
||||
const folders = await app().loadItems(BaseModel.TYPE_FOLDER, args.options.notebook);
|
||||
if (!folders.length) throw new Error(_('Cannot find "%s".', args.options.notebook));
|
||||
exportOptions.sourceFolderIds = folders.map(n => n.id);
|
||||
exportOptions.sourceFolderIds = folders.map((n: any) => n.id);
|
||||
}
|
||||
|
||||
const service = InteropService.instance();
|
@@ -1,19 +1,19 @@
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
import BaseCommand from './base-command';
|
||||
import app from './app';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import BaseModel from '@joplin/lib/BaseModel';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
|
||||
class Command extends BaseCommand {
|
||||
usage() {
|
||||
public override usage() {
|
||||
return 'geoloc <note>';
|
||||
}
|
||||
|
||||
description() {
|
||||
public override description() {
|
||||
return _('Displays a geolocation URL for the note.');
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
public override async action(args: any) {
|
||||
const title = args['note'];
|
||||
|
||||
const item = await app().loadItem(BaseModel.TYPE_NOTE, title, { parent: app().currentFolder() });
|
@@ -1,19 +1,19 @@
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
import BaseCommand from './base-command';
|
||||
import app from './app';
|
||||
const { renderCommandHelp } = require('./help-utils.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
const { cliUtils } = require('./cli-utils.js');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
usage() {
|
||||
public override usage() {
|
||||
return 'help [command]';
|
||||
}
|
||||
|
||||
description() {
|
||||
public override description() {
|
||||
return _('Displays usage information.');
|
||||
}
|
||||
|
||||
allCommands() {
|
||||
private allCommands() {
|
||||
const commands = app().commands(app().uiType());
|
||||
const output = [];
|
||||
for (const n in commands) {
|
||||
@@ -29,7 +29,7 @@ class Command extends BaseCommand {
|
||||
return output;
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
public override async action(args: any) {
|
||||
const stdoutWidth = app().commandStdoutMaxWidth();
|
||||
|
||||
if (args.command === 'shortcuts' || args.command === 'keymap') {
|
||||
@@ -52,7 +52,7 @@ class Command extends BaseCommand {
|
||||
|
||||
for (let i = 0; i < keymap.length; i++) {
|
||||
const item = keymap[i];
|
||||
const keys = item.keys.map(k => (k === ' ' ? '(SPACE)' : k));
|
||||
const keys = item.keys.map((k: string) => (k === ' ' ? '(SPACE)' : k));
|
||||
rows.push([keys.join(', '), item.command]);
|
||||
}
|
||||
|
@@ -1,20 +1,22 @@
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const InteropService = require('@joplin/lib/services/interop/InteropService').default;
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
import BaseCommand from './base-command';
|
||||
import InteropService from '@joplin/lib/services/interop/InteropService';
|
||||
import BaseModel from '@joplin/lib/BaseModel';
|
||||
const { cliUtils } = require('./cli-utils.js');
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
import app from './app';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { ImportOptions } from '@joplin/lib/services/interop/types';
|
||||
import { unique } from '@joplin/lib/array';
|
||||
|
||||
class Command extends BaseCommand {
|
||||
usage() {
|
||||
public override usage() {
|
||||
return 'import <path> [notebook]';
|
||||
}
|
||||
|
||||
description() {
|
||||
public override description() {
|
||||
return _('Imports data into Joplin.');
|
||||
}
|
||||
|
||||
options() {
|
||||
public override options() {
|
||||
const service = InteropService.instance();
|
||||
const formats = service
|
||||
.modules()
|
||||
@@ -22,18 +24,18 @@ class Command extends BaseCommand {
|
||||
.map(m => m.format);
|
||||
|
||||
return [
|
||||
['--format <format>', _('Source format: %s', ['auto'].concat(formats).join(', '))],
|
||||
['--format <format>', _('Source format: %s', ['auto'].concat(unique(formats)).join(', '))],
|
||||
['-f, --force', _('Do not ask for confirmation.')],
|
||||
['--output-format <output-format>', _('Output format: %s', 'md, html')],
|
||||
];
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
public override async action(args: any) {
|
||||
const folder = await app().loadItem(BaseModel.TYPE_FOLDER, args.notebook);
|
||||
|
||||
if (args.notebook && !folder) throw new Error(_('Cannot find "%s".', args.notebook));
|
||||
|
||||
const importOptions = {};
|
||||
const importOptions: ImportOptions = {};
|
||||
importOptions.path = args.path;
|
||||
importOptions.format = args.options.format ? args.options.format : 'auto';
|
||||
importOptions.destinationFolderId = folder ? folder.id : null;
|
@@ -1,28 +1,28 @@
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
const Folder = require('@joplin/lib/models/Folder').default;
|
||||
const Setting = require('@joplin/lib/models/Setting').default;
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
import BaseCommand from './base-command';
|
||||
import app from './app';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import BaseModel from '@joplin/lib/BaseModel';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
const { sprintf } = require('sprintf-js');
|
||||
const time = require('@joplin/lib/time').default;
|
||||
import time from '@joplin/lib/time';
|
||||
const { cliUtils } = require('./cli-utils.js');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
usage() {
|
||||
public override usage() {
|
||||
return 'ls [note-pattern]';
|
||||
}
|
||||
|
||||
description() {
|
||||
public override description() {
|
||||
return _('Displays the notes in the current notebook. Use `ls /` to display the list of notebooks.');
|
||||
}
|
||||
|
||||
enabled() {
|
||||
public override enabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
options() {
|
||||
public override 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).')],
|
||||
@@ -33,12 +33,12 @@ class Command extends BaseCommand {
|
||||
];
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
public override async action(args: any) {
|
||||
const pattern = args['note-pattern'];
|
||||
let items = [];
|
||||
const options = args.options;
|
||||
|
||||
const queryOptions = {};
|
||||
const queryOptions: any = {};
|
||||
if (options.limit) queryOptions.limit = options.limit;
|
||||
if (options.sort) {
|
||||
queryOptions.orderBy = options.sort;
|
@@ -1,5 +1,5 @@
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
import app from './app';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import BaseModel from '@joplin/lib/BaseModel';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
|
@@ -1,5 +1,5 @@
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const app = require('./app').default;
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const app = require('./app').default;
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
|
||||
|
@@ -1,20 +1,20 @@
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
const Folder = require('@joplin/lib/models/Folder').default;
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
import BaseCommand from './base-command';
|
||||
import app from './app';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import BaseModel from '@joplin/lib/BaseModel';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
|
||||
class Command extends BaseCommand {
|
||||
usage() {
|
||||
public override usage() {
|
||||
return 'mv <item> [notebook]';
|
||||
}
|
||||
|
||||
description() {
|
||||
public override description() {
|
||||
return _('Moves the given <item> to [notebook]');
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
public override async action(args: any) {
|
||||
const pattern = args['item'];
|
||||
const destination = args['notebook'];
|
||||
let folder = null;
|
@@ -1,20 +1,20 @@
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
const Folder = require('@joplin/lib/models/Folder').default;
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
import BaseCommand from './base-command';
|
||||
import app from './app';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import BaseModel from '@joplin/lib/BaseModel';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
|
||||
class Command extends BaseCommand {
|
||||
usage() {
|
||||
public override usage() {
|
||||
return 'ren <item> <name>';
|
||||
}
|
||||
|
||||
description() {
|
||||
public override description() {
|
||||
return _('Renames the given <item> (note or notebook) to <name>.');
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
public override async action(args: any) {
|
||||
const pattern = args['item'];
|
||||
const name = args['name'];
|
||||
|
@@ -1,23 +1,23 @@
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const Folder = require('@joplin/lib/models/Folder').default;
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
import BaseCommand from './base-command';
|
||||
import app from './app';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
import BaseModel from '@joplin/lib/BaseModel';
|
||||
|
||||
class Command extends BaseCommand {
|
||||
usage() {
|
||||
public override usage() {
|
||||
return 'rmbook <notebook>';
|
||||
}
|
||||
|
||||
description() {
|
||||
public override description() {
|
||||
return _('Deletes the given notebook.');
|
||||
}
|
||||
|
||||
options() {
|
||||
public override options() {
|
||||
return [['-f, --force', _('Deletes the notebook without asking for confirmation.')]];
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
public override async action(args: any) {
|
||||
const pattern = args['notebook'];
|
||||
const force = args.options && args.options.force === true;
|
||||
|
@@ -1,23 +1,23 @@
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
import BaseCommand from './base-command';
|
||||
import app from './app';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
import BaseModel from '@joplin/lib/BaseModel';
|
||||
|
||||
class Command extends BaseCommand {
|
||||
usage() {
|
||||
public override usage() {
|
||||
return 'rmnote <note-pattern>';
|
||||
}
|
||||
|
||||
description() {
|
||||
public override description() {
|
||||
return _('Deletes the notes matching <note-pattern>.');
|
||||
}
|
||||
|
||||
options() {
|
||||
public override options() {
|
||||
return [['-f, --force', _('Deletes the notes without asking for confirmation.')]];
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
public override async action(args: any) {
|
||||
const pattern = args['note-pattern'];
|
||||
const force = args.options && args.options.force === true;
|
||||
|
||||
@@ -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' });
|
||||
if (!ok) return;
|
||||
const ids = notes.map(n => n.id);
|
||||
const ids = notes.map((n: any) => n.id);
|
||||
await Note.batchDelete(ids);
|
||||
}
|
||||
}
|
@@ -1,16 +1,16 @@
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
const Database = require('@joplin/lib/database').default;
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
import BaseCommand from './base-command';
|
||||
import app from './app';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import BaseModel from '@joplin/lib/BaseModel';
|
||||
import Database from '@joplin/lib/database';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
|
||||
class Command extends BaseCommand {
|
||||
usage() {
|
||||
public override usage() {
|
||||
return 'set <note> <name> [value]';
|
||||
}
|
||||
|
||||
description() {
|
||||
public override description() {
|
||||
const fields = Note.fields();
|
||||
const s = [];
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
@@ -22,7 +22,7 @@ class Command extends BaseCommand {
|
||||
return _('Sets the property <name> of the given <note> to the given [value]. Possible properties are:\n\n%s', s.join(', '));
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
public override async action(args: any) {
|
||||
const title = args['note'];
|
||||
const propName = args['name'];
|
||||
let propValue = args['value'];
|
||||
@@ -36,7 +36,7 @@ class Command extends BaseCommand {
|
||||
|
||||
const timestamp = Date.now();
|
||||
|
||||
const newNote = {
|
||||
const newNote: any = {
|
||||
id: notes[i].id,
|
||||
type_: notes[i].type_,
|
||||
updated_time: timestamp,
|
@@ -1,5 +1,5 @@
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const app = require('./app').default;
|
||||
const Setting = require('@joplin/lib/models/Setting').default;
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const ReportService = require('@joplin/lib/services/ReportService').default;
|
||||
|
@@ -7,7 +7,7 @@ import Synchronizer from '@joplin/lib/Synchronizer';
|
||||
import { masterKeysWithoutPassword } from '@joplin/lib/services/e2ee/utils';
|
||||
import { appTypeToLockType } from '@joplin/lib/services/synchronizer/LockHandler';
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
import app from './app';
|
||||
const { OneDriveApiNodeUtils } = require('@joplin/lib/onedrive-api-node-utils.js');
|
||||
import { reg } from '@joplin/lib/registry';
|
||||
const { cliUtils } = require('./cli-utils.js');
|
||||
|
@@ -1,5 +1,5 @@
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const app = require('./app').default;
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const Tag = require('@joplin/lib/models/Tag').default;
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const app = require('./app').default;
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
|
@@ -1,22 +1,22 @@
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
import BaseCommand from './base-command';
|
||||
import app from './app';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import BaseModel from '@joplin/lib/BaseModel';
|
||||
|
||||
class Command extends BaseCommand {
|
||||
usage() {
|
||||
public override usage() {
|
||||
return 'use <notebook>';
|
||||
}
|
||||
|
||||
description() {
|
||||
public override description() {
|
||||
return _('Switches to [notebook] - all further operations will happen within this notebook.');
|
||||
}
|
||||
|
||||
compatibleUis() {
|
||||
public override compatibleUis() {
|
||||
return ['cli'];
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
public override async action(args: any) {
|
||||
const folder = await app().loadItem(BaseModel.TYPE_FOLDER, args['notebook']);
|
||||
if (!folder) throw new Error(_('Cannot find "%s".', args['notebook']));
|
||||
app().switchCurrentFolder(folder);
|
@@ -1,17 +1,17 @@
|
||||
const BaseCommand = require('./base-command').default;
|
||||
import BaseCommand from './base-command';
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const versionInfo = require('@joplin/lib/versionInfo').default;
|
||||
|
||||
class Command extends BaseCommand {
|
||||
usage() {
|
||||
public override usage() {
|
||||
return 'version';
|
||||
}
|
||||
|
||||
description() {
|
||||
public override description() {
|
||||
return _('Displays version information');
|
||||
}
|
||||
|
||||
async action() {
|
||||
public override async action() {
|
||||
this.stdout(versionInfo(require('./package.json'), {}).message);
|
||||
}
|
||||
}
|
@@ -10,7 +10,7 @@ if (compareVersion(nodeVersion, '10.0.0') < 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const { app } = require('./app.js');
|
||||
const app = require('./app').default;
|
||||
const Folder = require('@joplin/lib/models/Folder').default;
|
||||
const Resource = require('@joplin/lib/models/Resource').default;
|
||||
const BaseItem = require('@joplin/lib/models/BaseItem').default;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { app } = require('../app');
|
||||
import app from '../app';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
import BaseCommand from '../base-command';
|
||||
import setupCommand from '../setupCommand';
|
||||
|
@@ -3,6 +3,7 @@ const { shimInit } = require('@joplin/lib/shim-init-node.js');
|
||||
const shim = require('@joplin/lib/shim').default;
|
||||
const sharp = require('sharp');
|
||||
const nodeSqlite = require('sqlite3');
|
||||
require('../../jest.base-setup.js')();
|
||||
|
||||
let keytar;
|
||||
try {
|
||||
|
@@ -35,19 +35,19 @@
|
||||
],
|
||||
"owner": "Laurent Cozic"
|
||||
},
|
||||
"version": "2.13.1",
|
||||
"version": "2.14.0",
|
||||
"bin": "./main.js",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@joplin/lib": "~2.13",
|
||||
"@joplin/renderer": "~2.13",
|
||||
"@joplin/utils": "~2.13",
|
||||
"@joplin/lib": "~2.14",
|
||||
"@joplin/renderer": "~2.14",
|
||||
"@joplin/utils": "~2.14",
|
||||
"aws-sdk": "2.1340.0",
|
||||
"chalk": "4.1.2",
|
||||
"compare-version": "0.1.2",
|
||||
"fs-extra": "11.1.1",
|
||||
"fs-extra": "11.2.0",
|
||||
"html-entities": "1.4.0",
|
||||
"image-type": "3.1.0",
|
||||
"keytar": "7.9.0",
|
||||
@@ -57,7 +57,7 @@
|
||||
"proper-lockfile": "4.1.2",
|
||||
"read-chunk": "2.1.0",
|
||||
"server-destroy": "1.0.1",
|
||||
"sharp": "0.32.6",
|
||||
"sharp": "0.33.1",
|
||||
"sprintf-js": "1.1.3",
|
||||
"sqlite3": "5.1.6",
|
||||
"string-padding": "1.0.2",
|
||||
@@ -70,10 +70,10 @@
|
||||
"yargs-parser": "21.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@joplin/tools": "~2.13",
|
||||
"@types/fs-extra": "11.0.3",
|
||||
"@types/jest": "29.5.5",
|
||||
"@types/node": "18.18.7",
|
||||
"@joplin/tools": "~2.14",
|
||||
"@types/fs-extra": "11.0.4",
|
||||
"@types/jest": "29.5.8",
|
||||
"@types/node": "18.19.4",
|
||||
"@types/proper-lockfile": "^4.1.2",
|
||||
"gulp": "4.0.2",
|
||||
"jest": "29.7.0",
|
||||
|
@@ -40,6 +40,10 @@ describe('HtmlToMd', () => {
|
||||
htmlToMdOptions.preserveNestedTables = true;
|
||||
}
|
||||
|
||||
if (htmlFilename.indexOf('text_color') === 0) {
|
||||
htmlToMdOptions.preserveColorStyles = true;
|
||||
}
|
||||
|
||||
const html = await readFile(htmlPath, 'utf8');
|
||||
let expectedMd = await readFile(mdPath, 'utf8');
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
|
||||
import MarkupToHtml, { MarkupLanguage, RenderResult } from '@joplin/renderer/MarkupToHtml';
|
||||
import MarkupToHtml, { MarkupLanguage } from '@joplin/renderer/MarkupToHtml';
|
||||
import { RenderResult } from '@joplin/renderer/types';
|
||||
|
||||
describe('MarkupToHtml', () => {
|
||||
|
||||
|
@@ -291,4 +291,29 @@ describe('MdToHtml', () => {
|
||||
expect(html.html).toContain(opening + trimmedTex + closing);
|
||||
}
|
||||
});
|
||||
|
||||
it('should render inline KaTeX after a numbered equation', async () => {
|
||||
const mdToHtml = newTestMdToHtml();
|
||||
|
||||
// This test is intended to verify that inline KaTeX renders correctly
|
||||
// after creating a numbered equation with \begin{align}...\end{align}.
|
||||
//
|
||||
// See https://github.com/laurent22/joplin/issues/9455 for details.
|
||||
|
||||
const markdown = [
|
||||
'$$',
|
||||
'\\begin{align}\\text{Block}\\end{align}',
|
||||
'$$',
|
||||
'',
|
||||
'$\\text{Inline}$',
|
||||
].join('\n');
|
||||
const { html } = await mdToHtml.render(markdown, null, { bodyOnly: true });
|
||||
|
||||
// Because we don't control the output of KaTeX, this test should be as general as
|
||||
// possible while still verifying that rendering (without an error) occurs.
|
||||
|
||||
// Should have rendered the inline and block content without errors
|
||||
expect(html).toContain('Inline</span>');
|
||||
expect(html).toContain('Block</span>');
|
||||
});
|
||||
});
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<en-note><div>Plain note</div></en-note>]]></content><created>20201223T163948Z</created><updated>20201223T163953Z</updated><note-attributes><author>laurent22777@gmail.com</author><source>desktop.win</source><source-application>evernote.win32</source-application></note-attributes></note><note><title>Note 2</title><content><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd">
|
||||
|
||||
<en-note><div><div><br/></div><table style="border-collapse: collapse; min-width: 100%;"><colgroup><col style="width: 130px;"/><col style="width: 130px;"/><col style="width: 130px;"/></colgroup><tbody><tr<td style="width: 130px; padding: 8px; border: 1px solid;"><div>test</div></td><td style="width: 130px; padding: 8px; border: 1px solid;"><div>test</div></td><td style="width: 130px; padding: 8px; border: 1px solid;"><div><br/></div></td></tr><tr><td style="width: 130px; padding: 8px; border: 1px solid;"><div>bl</div></td><td style="width: 130px; padding: 8px; border: 1px solid;"><div>bla</div></td><td style="width: 130px; padding: 8px; border: 1px solid;"><div>bla</div></td></tr></tbody></table><div><br/></div></div><div><br/></div></en-note>]]></content><created>20201223T164010Z</created><updated>20201223T164023Z</updated><note-attributes><author>laurent22777@gmail.com</author><source>desktop.win</source><source-application>evernote.win32</source-application></note-attributes></note><note><title>plain note 2</title><content><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
|
||||
<en-note><div><div><br/></div><table style="border-collapse: collapse; min-width: 100%;"><colgroup><col style="width: 130px;"/><col style="width: 130px;"/><col style="width: 130px;"/></colgroup><tbody><tr<td style="width: 130px; padding: 8px; border: 1px solid;"><div>test</div></td><td style="width: 130px; padding: 8px; border: 1px solid;"><div>test</div></td><td style="width: 130px; padding: 8px; border: 1px solid;"><div><br/></div></td></tr><tr><td style="width: 130px; padding: 8px; border: 1px solid;"><div>bl</div></t><td style="width: 130px; padding: 8px; border: 1px solid;"><div>bla</div></td><td style="width: 130px; padding: 8px; border: 1px solid;"><div>bla</div></td></tr></tbody></table><div><br/></div></div><div><br/></div></en-note>]]></content><created>20201223T164010Z</created><updated>20201223T164023Z</updated><note-attributes><author>laurent22777@gmail.com</author><source>desktop.win</source><source-application>evernote.win32</source-application></note-attributes></note><note><title>plain note 2</title><content><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd">
|
||||
|
||||
<en-note><div><br/></div></en-note>]]></content><created>20201223T164236Z</created><note-attributes><author>laurent22777@gmail.com</author><source>desktop.win</source><source-application>evernote.win32</source-application></note-attributes></note></en-export>
|
||||
|
49
packages/app-cli/tests/enex_to_md/linked_notes.enex
Normal file
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE en-export SYSTEM "http://xml.evernote.com/pub/evernote-export4.dtd">
|
||||
<en-export export-date="20231224T151504Z" application="Evernote" version="10.68.2">
|
||||
<note>
|
||||
<title>Note 1</title>
|
||||
<created>20160730T164129Z</created>
|
||||
<updated>20231224T151443Z</updated>
|
||||
<content>
|
||||
<![CDATA[<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"><en-note><div><a href="evernote:///view/5223870/s49/9cd5e810-fa03-429a-8194-ab847f2f1ab2/c99d9e01-ca35-4c75-ba63-f0c0ef97787d/" rel="noopener noreferrer" rev="en_rl_none">Note 2</a><a href="evernote:///view/5223870/s49/9cd5e810-fa03-429a-8194-ab847f2f1ab2/c99d9e01-ca35-4c75-ba63-f0c0ef97787d/" rel="noopener noreferrer" rev="en_rl_none">Note 3</a></div></en-note> ]]>
|
||||
</content>
|
||||
</note>
|
||||
<note>
|
||||
<title>Note 2</title>
|
||||
<created>20160730T111759Z</created>
|
||||
<updated>20160730T111807Z</updated>
|
||||
<content>
|
||||
<![CDATA[<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"><en-note><div>Testing</div></en-note> ]]>
|
||||
</content>
|
||||
</note>
|
||||
<note>
|
||||
<title>Note 3</title>
|
||||
<created>20160730T111759Z</created>
|
||||
<updated>20160730T111807Z</updated>
|
||||
<content>
|
||||
<![CDATA[<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"><en-note><div><a href="evernote:///view/5223870/s49/9cd5e810-fa03-429a-8194-ab847f2f1ab2/c99d9e01-ca35-4c75-ba63-f0c0ef97787d/" rel="noopener noreferrer" rev="en_rl_none">Ambiguous note</a></div></en-note> ]]>
|
||||
</content>
|
||||
</note>
|
||||
<note>
|
||||
<title>Ambiguous note</title>
|
||||
<created>20160730T111759Z</created>
|
||||
<updated>20160730T111807Z</updated>
|
||||
<content>
|
||||
<![CDATA[<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"><en-note><div>Testing</div></en-note> ]]>
|
||||
</content>
|
||||
</note>
|
||||
<note>
|
||||
<title>Ambiguous note</title>
|
||||
<created>20160730T111759Z</created>
|
||||
<updated>20160730T111807Z</updated>
|
||||
<content>
|
||||
<![CDATA[<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"><en-note><div>Testing</div></en-note> ]]>
|
||||
</content>
|
||||
</note>
|
||||
</en-export>
|
@@ -0,0 +1,12 @@
|
||||
<table>
|
||||
<div></div> <!-- INVALID! -->
|
||||
<tr>
|
||||
<td>one</td>
|
||||
<td>two</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<div></div> <!-- INVALID! -->
|
||||
<td>three</td>
|
||||
<td>four</td>
|
||||
</tr>
|
||||
</table>
|
@@ -0,0 +1,4 @@
|
||||
| | |
|
||||
| --- | --- |
|
||||
| one | two |
|
||||
| three | four |
|
@@ -1,10 +1,10 @@
|
||||
<body>
|
||||
<table border="5px" bordercolor="#8707B0">
|
||||
<table>
|
||||
<tr>
|
||||
<td>Left side of the main table</td>
|
||||
<td>
|
||||
<table border="5px" bordercolor="#F35557">
|
||||
<h4 align="center">Nested Table</h4>
|
||||
<table>
|
||||
<b>Nested Table</b>
|
||||
<tr>
|
||||
<td>nested table C1</td>
|
||||
<td>nested table C2</td>
|
||||
|
@@ -1 +1 @@
|
||||
<table border="5px" bordercolor="#8707B0"><tbody><tr><td>Left side of the main table</td><td><h4 align="center">Nested Table</h4><table border="5px" bordercolor="#F35557"><tbody><tr><td>nested table C1</td><td>nested table C2</td></tr><tr><td>nested table</td><td>nested table</td></tr></tbody></table></td></tr></tbody></table>
|
||||
<table><tbody><tr><td>Left side of the main table</td><td><b>Nested Table</b><table><tbody><tr><td>nested table C1</td><td>nested table C2</td></tr><tr><td>nested table</td><td>nested table</td></tr></tbody></table></td></tr></tbody></table>
|
31
packages/app-cli/tests/html_to_md/table_with_code_3.html
Normal file
@@ -0,0 +1,31 @@
|
||||
<div id="rendered-md">
|
||||
<table class="jop-noMdConv">
|
||||
<thead class="jop-noMdConv">
|
||||
<tr class="jop-noMdConv">
|
||||
<th class="jop-noMdConv">Code</th>
|
||||
<th class="jop-noMdConv">Description</th>
|
||||
</tr>
|
||||
<tr class="jop-noMdConv">
|
||||
<td class="jop-noMdConv">
|
||||
<pre class="jop-noMdConv"><code class="jop-noMdConv">const test = "hello";
|
||||
|
||||
// Another line
|
||||
console.log('Test...');
|
||||
|
||||
// Blank lines
|
||||
|
||||
|
||||
|
||||
// Should not break things.</code></pre>
|
||||
</td>
|
||||
<td class="jop-noMdConv">abcda</td>
|
||||
</tr>
|
||||
<tr class="jop-noMdConv">
|
||||
<td class="jop-noMdConv">
|
||||
<pre class="jop-noMdConv"><code class="jop-noMdConv">const test = "hello";</code></pre>
|
||||
</td>
|
||||
<td class="jop-noMdConv">abcd</td>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
10
packages/app-cli/tests/html_to_md/table_with_code_3.md
Normal file
@@ -0,0 +1,10 @@
|
||||
<table class="jop-noMdConv"><thead class="jop-noMdConv"><tr class="jop-noMdConv"><th class="jop-noMdConv">Code</th><th class="jop-noMdConv">Description</th></tr><tr class="jop-noMdConv"><td class="jop-noMdConv"><pre class="jop-noMdConv"><code class="">const test = "hello";
|
||||
<!-- -->
|
||||
// Another line
|
||||
console.log('Test...');
|
||||
<!-- -->
|
||||
// Blank lines
|
||||
<!-- -->
|
||||
<!-- -->
|
||||
<!-- -->
|
||||
// Should not break things.</code></pre></td><td class="jop-noMdConv">abcda</td></tr><tr class="jop-noMdConv"><td class="jop-noMdConv"><pre class="jop-noMdConv"><code class="">const test = "hello";</code></pre></td><td class="jop-noMdConv">abcd</td></tr></thead></table>
|
1
packages/app-cli/tests/html_to_md/text_color.html
Normal file
@@ -0,0 +1 @@
|
||||
<p>Text <span style="color: #fff000;">Text</span></p>
|
1
packages/app-cli/tests/html_to_md/text_color.md
Normal file
@@ -0,0 +1 @@
|
||||
Text <span style="color: #fff000;">Text</span>
|
@@ -0,0 +1,3 @@
|
||||
<nav class="table-of-contents"><ul><li><a href="#test">test</a></li><li><a href="#test-2">test</a></li><li><a href="#test2">test2</a></li></ul></nav><h1 id="test">test</h1>
|
||||
<h1 id="test-2">test</h1>
|
||||
<h1 id="test2">test2</h1>
|
5
packages/app-cli/tests/md_to_html/toc_duplicate_items.md
Normal file
@@ -0,0 +1,5 @@
|
||||
[toc]
|
||||
|
||||
# test
|
||||
# test
|
||||
# test2
|
BIN
packages/app-cli/tests/ocr_samples/dummy.pdf
Normal file
BIN
packages/app-cli/tests/ocr_samples/testocr.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
packages/app-cli/tests/ocr_samples/with_bullets.png
Normal file
After Width: | Height: | Size: 117 KiB |
@@ -1,5 +1,5 @@
|
||||
import PluginRunner from '../../../app/services/plugins/PluginRunner';
|
||||
import PluginService from '@joplin/lib/services/plugins/PluginService';
|
||||
import PluginService, { PluginSettings } from '@joplin/lib/services/plugins/PluginService';
|
||||
import { ContentScriptType } from '@joplin/lib/services/plugins/api/types';
|
||||
import MdToHtml from '@joplin/renderer/MdToHtml';
|
||||
import shim from '@joplin/lib/shim';
|
||||
@@ -304,4 +304,33 @@ describe('services_PluginService', () => {
|
||||
expect(JSON.parse(folders[0].title)).toBe(expectedPath);
|
||||
}));
|
||||
|
||||
it('should uninstall multiple plugins', async () => {
|
||||
const service = newPluginService();
|
||||
const pluginId1 = 'org.joplinapp.FirstJplPlugin';
|
||||
const pluginId2 = 'org.joplinapp.plugins.TocDemo';
|
||||
const pluginPath1 = `${testPluginDir}/jpl_test/${pluginId1}.jpl`;
|
||||
const pluginPath2 = `${testPluginDir}/toc/${pluginId2}.jpl`;
|
||||
|
||||
await service.installPlugin(pluginPath1);
|
||||
await service.installPlugin(pluginPath2);
|
||||
|
||||
// Both should be installed
|
||||
expect(await fs.pathExists(`${Setting.value('pluginDir')}/${pluginId1}.jpl`)).toBe(true);
|
||||
expect(await fs.pathExists(`${Setting.value('pluginDir')}/${pluginId2}.jpl`)).toBe(true);
|
||||
|
||||
const pluginSettings: PluginSettings = {
|
||||
[pluginId1]: { enabled: true, deleted: true, hasBeenUpdated: false },
|
||||
[pluginId2]: { enabled: true, deleted: true, hasBeenUpdated: false },
|
||||
};
|
||||
|
||||
const newPluginSettings = await service.uninstallPlugins(pluginSettings);
|
||||
|
||||
// Should have deleted plugins
|
||||
expect(await fs.pathExists(`${Setting.value('pluginDir')}/${pluginId1}.jpl`)).toBe(false);
|
||||
expect(await fs.pathExists(`${Setting.value('pluginDir')}${pluginId2}.jpl`)).toBe(false);
|
||||
|
||||
// Should clear deleted plugins from settings
|
||||
expect(newPluginSettings[pluginId1]).toBe(undefined);
|
||||
expect(newPluginSettings[pluginId2]).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
@@ -1,11 +1,9 @@
|
||||
import { installDefaultPlugins, getDefaultPluginsInstallState, setSettingsForDefaultPlugins, checkPreInstalledDefaultPlugins } from '@joplin/lib/services/plugins/defaultPlugins/defaultPluginsUtils';
|
||||
import { afterDefaultPluginsLoaded, getDefaultPluginPathsAndSettings } from '@joplin/lib/services/plugins/defaultPlugins/defaultPluginsUtils';
|
||||
import PluginRunner from '../../../app/services/plugins/PluginRunner';
|
||||
import { pathExists } from 'fs-extra';
|
||||
import { checkThrow, setupDatabaseAndSynchronizer, supportDir, switchClient } from '@joplin/lib/testing/test-utils';
|
||||
import PluginService, { defaultPluginSetting, DefaultPluginsInfo, PluginSettings } from '@joplin/lib/services/plugins/PluginService';
|
||||
import PluginService, { defaultPluginSetting, DefaultPluginsInfo } from '@joplin/lib/services/plugins/PluginService';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
|
||||
const testPluginDir = `${supportDir}/plugins`;
|
||||
|
||||
function newPluginService(appVersion = '1.4') {
|
||||
const runner = new PluginRunner();
|
||||
@@ -27,232 +25,168 @@ function newPluginService(appVersion = '1.4') {
|
||||
describe('defaultPluginsUtils', () => {
|
||||
|
||||
const pluginsId = ['joplin.plugin.ambrt.backlinksToNote', 'org.joplinapp.plugins.ToggleSidebars'];
|
||||
const defaultPluginsInfo = {
|
||||
'joplin.plugin.ambrt.backlinksToNote': {},
|
||||
'org.joplinapp.plugins.ToggleSidebars': {},
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
await switchClient(1);
|
||||
});
|
||||
|
||||
it('should install default plugins with no previous default plugins installed', (async () => {
|
||||
const testPluginDir = `${supportDir}/pluginRepo/plugins`;
|
||||
it('should load default plugins when nor previously installed', (async () => {
|
||||
const testPluginDir = `${supportDir}/testDefaultPlugins`;
|
||||
Setting.setValue('installedDefaultPlugins', []);
|
||||
|
||||
const service = newPluginService('2.1');
|
||||
|
||||
const pluginSettings = service.unserializePluginSettings(Setting.value('plugins.states'));
|
||||
|
||||
const newPluginsSettings = await installDefaultPlugins(service, testPluginDir, pluginsId, pluginSettings);
|
||||
for (const pluginId of pluginsId) {
|
||||
expect(pluginSettings[pluginId]).toBeFalsy();
|
||||
}
|
||||
|
||||
const installedPluginPath1 = `${Setting.value('pluginDir')}/${pluginsId[0]}.jpl`;
|
||||
const installedPluginPath2 = `${Setting.value('pluginDir')}/${pluginsId[1]}.jpl`;
|
||||
|
||||
expect(await pathExists(installedPluginPath1)).toBe(true);
|
||||
expect(await pathExists(installedPluginPath2)).toBe(true);
|
||||
|
||||
expect(newPluginsSettings[pluginsId[0]]).toMatchObject(defaultPluginSetting());
|
||||
expect(newPluginsSettings[pluginsId[1]]).toMatchObject(defaultPluginSetting());
|
||||
const pluginPathsAndNewSettings = await getDefaultPluginPathsAndSettings(testPluginDir, defaultPluginsInfo, pluginSettings);
|
||||
|
||||
for (const pluginId of pluginsId) {
|
||||
expect(
|
||||
pluginPathsAndNewSettings.pluginSettings[pluginId],
|
||||
).toMatchObject(defaultPluginSetting());
|
||||
}
|
||||
}));
|
||||
|
||||
it('should install default plugins with previous default plugins installed', (async () => {
|
||||
|
||||
const testPluginDir = `${supportDir}/pluginRepo/plugins`;
|
||||
it('should keep already created default plugins disabled with previous default plugins installed', (async () => {
|
||||
const testPluginDir = `${supportDir}/testDefaultPlugins`;
|
||||
Setting.setValue('installedDefaultPlugins', ['org.joplinapp.plugins.ToggleSidebars']);
|
||||
Setting.setValue('plugins.states', {
|
||||
'org.joplinapp.plugins.ToggleSidebars': { ...defaultPluginSetting(), enabled: false },
|
||||
});
|
||||
|
||||
const service = newPluginService('2.1');
|
||||
|
||||
const pluginSettings = service.unserializePluginSettings(Setting.value('plugins.states'));
|
||||
const pluginPathsAndNewSettings = await getDefaultPluginPathsAndSettings(testPluginDir, defaultPluginsInfo, pluginSettings);
|
||||
|
||||
const newPluginsSettings = await installDefaultPlugins(service, testPluginDir, pluginsId, pluginSettings);
|
||||
|
||||
const installedPluginPath1 = `${Setting.value('pluginDir')}/${pluginsId[0]}.jpl`;
|
||||
const installedPluginPath2 = `${Setting.value('pluginDir')}/${pluginsId[1]}.jpl`;
|
||||
|
||||
expect(await pathExists(installedPluginPath1)).toBe(true);
|
||||
expect(await pathExists(installedPluginPath2)).toBe(false);
|
||||
|
||||
expect(newPluginsSettings[pluginsId[0]]).toMatchObject(defaultPluginSetting());
|
||||
expect(newPluginsSettings[pluginsId[1]]).toBeUndefined();
|
||||
// Should still be disabled
|
||||
expect(
|
||||
pluginPathsAndNewSettings.pluginSettings['org.joplinapp.plugins.ToggleSidebars'].enabled,
|
||||
).toBe(false);
|
||||
}));
|
||||
|
||||
it('should get default plugins install state', (async () => {
|
||||
const testCases = [
|
||||
{
|
||||
'installedDefaultPlugins': [''],
|
||||
'loadingPlugins': [`${testPluginDir}/simple`, `${testPluginDir}/jpl_test/org.joplinapp.FirstJplPlugin.jpl`],
|
||||
'plugin1DefaultState': defaultPluginSetting(),
|
||||
'plugin2DefaultState': defaultPluginSetting(),
|
||||
'installedDefaultPlugins1': true,
|
||||
'installedDefaultPlugins2': true,
|
||||
},
|
||||
{
|
||||
'installedDefaultPlugins': [''],
|
||||
'loadingPlugins': [`${testPluginDir}/simple`],
|
||||
'plugin1DefaultState': defaultPluginSetting(),
|
||||
'plugin2DefaultState': undefined,
|
||||
'installedDefaultPlugins1': true,
|
||||
'installedDefaultPlugins2': false,
|
||||
},
|
||||
{
|
||||
'installedDefaultPlugins': ['org.joplinapp.plugins.Simple'],
|
||||
'loadingPlugins': [`${testPluginDir}/simple`, `${testPluginDir}/jpl_test/org.joplinapp.FirstJplPlugin.jpl`],
|
||||
'plugin1DefaultState': undefined,
|
||||
'plugin2DefaultState': defaultPluginSetting(),
|
||||
'installedDefaultPlugins1': true,
|
||||
'installedDefaultPlugins2': true,
|
||||
},
|
||||
{
|
||||
'installedDefaultPlugins': ['org.joplinapp.plugins.Simple'],
|
||||
'loadingPlugins': [`${testPluginDir}/simple`],
|
||||
'plugin1DefaultState': undefined,
|
||||
'plugin2DefaultState': undefined,
|
||||
'installedDefaultPlugins1': true,
|
||||
'installedDefaultPlugins2': false,
|
||||
},
|
||||
];
|
||||
|
||||
for (const testCase of testCases) {
|
||||
const service = newPluginService();
|
||||
const pluginsId = ['org.joplinapp.plugins.Simple', 'org.joplinapp.FirstJplPlugin'];
|
||||
|
||||
Setting.setValue('installedDefaultPlugins', testCase.installedDefaultPlugins);
|
||||
await service.loadAndRunPlugins(testCase.loadingPlugins, {});
|
||||
|
||||
// setting installedDefaultPlugins state
|
||||
const defaultInstallStates: PluginSettings = getDefaultPluginsInstallState(service, pluginsId);
|
||||
|
||||
expect(defaultInstallStates[pluginsId[0]]).toStrictEqual(testCase.plugin1DefaultState);
|
||||
expect(defaultInstallStates[pluginsId[1]]).toStrictEqual(testCase.plugin2DefaultState);
|
||||
|
||||
|
||||
const installedDefaultPlugins = Setting.value('installedDefaultPlugins');
|
||||
expect(installedDefaultPlugins.includes(pluginsId[0])).toBe(testCase.installedDefaultPlugins1);
|
||||
expect(installedDefaultPlugins.includes(pluginsId[1])).toBe(testCase.installedDefaultPlugins2);
|
||||
|
||||
}
|
||||
|
||||
}));
|
||||
|
||||
it('should check pre-installed default plugins', (async () => {
|
||||
// with previous pre-installed default plugins
|
||||
Setting.setValue('installedDefaultPlugins', ['']);
|
||||
let pluginSettings, installedDefaultPlugins;
|
||||
|
||||
pluginSettings = { [pluginsId[0]]: defaultPluginSetting() };
|
||||
checkPreInstalledDefaultPlugins(pluginsId, pluginSettings);
|
||||
|
||||
installedDefaultPlugins = Setting.value('installedDefaultPlugins');
|
||||
expect(installedDefaultPlugins.includes(pluginsId[0])).toBe(true);
|
||||
expect(installedDefaultPlugins.includes(pluginsId[1])).toBe(false);
|
||||
|
||||
|
||||
// with no previous pre-installed default plugins
|
||||
Setting.setValue('installedDefaultPlugins', ['not-a-default-plugin']);
|
||||
pluginSettings = {};
|
||||
checkPreInstalledDefaultPlugins(pluginsId, pluginSettings);
|
||||
|
||||
installedDefaultPlugins = Setting.value('installedDefaultPlugins');
|
||||
expect(installedDefaultPlugins.includes(pluginsId[0])).toBe(false);
|
||||
expect(installedDefaultPlugins.includes(pluginsId[1])).toBe(false);
|
||||
|
||||
}));
|
||||
const sampleJsBundlePlugin = `
|
||||
/* joplin-manifest:
|
||||
{
|
||||
"id": "io.github.jackgruber.backup",
|
||||
"manifest_version": 1,
|
||||
"app_min_version": "1.4",
|
||||
"name": "JS Bundle test",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
*/
|
||||
joplin.plugins.register({
|
||||
onStart: async function() {
|
||||
await joplin.settings.registerSettings({
|
||||
path: {
|
||||
value: "initial-path",
|
||||
type: 2,
|
||||
section: "backupSection",
|
||||
public: true,
|
||||
label: "Backup path",
|
||||
},
|
||||
})
|
||||
},
|
||||
});`;
|
||||
|
||||
it('should set initial settings for default plugins', async () => {
|
||||
const service = newPluginService();
|
||||
|
||||
const pluginScript = `
|
||||
/* joplin-manifest:
|
||||
{
|
||||
"id": "io.github.jackgruber.backup",
|
||||
"manifest_version": 1,
|
||||
"app_min_version": "1.4",
|
||||
"name": "JS Bundle test",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
*/
|
||||
joplin.plugins.register({
|
||||
onStart: async function() {
|
||||
await joplin.settings.registerSettings({
|
||||
path: {
|
||||
value: "initial-path",
|
||||
type: 2,
|
||||
section: "backupSection",
|
||||
public: true,
|
||||
label: "Backup path",
|
||||
},
|
||||
})
|
||||
},
|
||||
});`;
|
||||
|
||||
const plugin = await service.loadPluginFromJsBundle('', pluginScript);
|
||||
const plugin = await service.loadPluginFromJsBundle('', sampleJsBundlePlugin);
|
||||
plugin.builtIn = true;
|
||||
await service.runPlugin(plugin);
|
||||
const runningPlugins = { 'io.github.jackgruber.backup': plugin };
|
||||
|
||||
const defaultPluginsInfo: DefaultPluginsInfo = {
|
||||
'io.github.jackgruber.backup': {
|
||||
version: '1.0.2',
|
||||
settings: {
|
||||
'path': `${Setting.value('profileDir')}`,
|
||||
},
|
||||
},
|
||||
'plugin.calebjohn.rich-markdown': {
|
||||
version: '0.8.3',
|
||||
},
|
||||
};
|
||||
|
||||
// with pre-installed default plugin
|
||||
Setting.setValue('installedDefaultPlugins', ['io.github.jackgruber.backup']);
|
||||
setSettingsForDefaultPlugins(defaultPluginsInfo);
|
||||
const pluginSettings = { 'io.github.jackgruber.backup': defaultPluginSetting() };
|
||||
|
||||
await afterDefaultPluginsLoaded(
|
||||
runningPlugins,
|
||||
defaultPluginsInfo,
|
||||
pluginSettings,
|
||||
);
|
||||
expect(Setting.value('plugin-io.github.jackgruber.backup.path')).toBe('initial-path');
|
||||
await service.destroy();
|
||||
|
||||
// with no pre-installed default plugin
|
||||
Setting.setValue('installedDefaultPlugins', ['']);
|
||||
setSettingsForDefaultPlugins(defaultPluginsInfo);
|
||||
await afterDefaultPluginsLoaded(
|
||||
runningPlugins,
|
||||
defaultPluginsInfo,
|
||||
pluginSettings,
|
||||
);
|
||||
expect(Setting.value('plugin-io.github.jackgruber.backup.path')).toBe(`${Setting.value('profileDir')}`);
|
||||
await service.destroy();
|
||||
});
|
||||
|
||||
it('should not overwrite existing settings for a user-installed version of a built-in plugin', async () => {
|
||||
const service = newPluginService();
|
||||
|
||||
const plugin = await service.loadPluginFromJsBundle('', sampleJsBundlePlugin);
|
||||
plugin.builtIn = false;
|
||||
await service.runPlugin(plugin);
|
||||
|
||||
const defaultPluginsInfo: DefaultPluginsInfo = {
|
||||
'io.github.jackgruber.backup': {
|
||||
settings: {
|
||||
'path': 'overwrite?',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// No pre-installed default plugins
|
||||
Setting.setValue('installedDefaultPlugins', []);
|
||||
|
||||
// The plugin is running and enabled
|
||||
const runningPlugins = { 'io.github.jackgruber.backup': plugin };
|
||||
const pluginSettings = { 'io.github.jackgruber.backup': defaultPluginSetting() };
|
||||
|
||||
await afterDefaultPluginsLoaded(
|
||||
runningPlugins,
|
||||
defaultPluginsInfo,
|
||||
pluginSettings,
|
||||
);
|
||||
|
||||
// Should not overwrite
|
||||
expect(Setting.value('plugin-io.github.jackgruber.backup.path')).toBe('initial-path');
|
||||
});
|
||||
|
||||
it('should not throw error on missing setting key', async () => {
|
||||
|
||||
const service = newPluginService();
|
||||
|
||||
const pluginScript = `
|
||||
/* joplin-manifest:
|
||||
{
|
||||
"id": "io.github.jackgruber.backup",
|
||||
"manifest_version": 1,
|
||||
"app_min_version": "1.4",
|
||||
"name": "JS Bundle test",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
*/
|
||||
joplin.plugins.register({
|
||||
onStart: async function() {
|
||||
await joplin.settings.registerSettings({
|
||||
path: {
|
||||
value: "initial-path",
|
||||
type: 2,
|
||||
section: "backupSection",
|
||||
public: true,
|
||||
label: "Backup path",
|
||||
},
|
||||
})
|
||||
},
|
||||
});`;
|
||||
|
||||
const plugin = await service.loadPluginFromJsBundle('', pluginScript);
|
||||
const plugin = await service.loadPluginFromJsBundle('', sampleJsBundlePlugin);
|
||||
plugin.builtIn = true;
|
||||
await service.runPlugin(plugin);
|
||||
|
||||
const defaultPluginsInfo: DefaultPluginsInfo = {
|
||||
'io.github.jackgruber.backup': {
|
||||
version: '1.0.2',
|
||||
settings: {
|
||||
'path': `${Setting.value('profileDir')}`,
|
||||
'missing-key1': 'someValue',
|
||||
},
|
||||
},
|
||||
'plugin.calebjohn.rich-markdown': {
|
||||
version: '0.8.3',
|
||||
settings: {
|
||||
'missing-key2': 'someValue',
|
||||
},
|
||||
@@ -260,7 +194,10 @@ describe('defaultPluginsUtils', () => {
|
||||
};
|
||||
|
||||
Setting.setValue('installedDefaultPlugins', ['']);
|
||||
expect(checkThrow(() => setSettingsForDefaultPlugins(defaultPluginsInfo))).toBe(false);
|
||||
const pluginSettings = { 'io.github.jackgruber.backup': defaultPluginSetting() };
|
||||
const runningPlugins = { 'io.github.jackgruber.backup': plugin };
|
||||
|
||||
expect(checkThrow(() => afterDefaultPluginsLoaded(runningPlugins, defaultPluginsInfo, pluginSettings))).toBe(false);
|
||||
expect(Setting.value('plugin-io.github.jackgruber.backup.path')).toBe(`${Setting.value('profileDir')}`);
|
||||
await service.destroy();
|
||||
});
|
||||
|
3
packages/app-cli/tests/support/plugins/codemirror6/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
dist/
|
||||
node_modules/
|
||||
publish/
|
@@ -0,0 +1,8 @@
|
||||
*.md
|
||||
!README.md
|
||||
/*.jpl
|
||||
/api
|
||||
/src
|
||||
/dist
|
||||
tsconfig.json
|
||||
webpack.config.js
|
@@ -0,0 +1,78 @@
|
||||
# Plugin development
|
||||
|
||||
This documentation describes how to create a plugin, and how to work with the plugin builder framework and API.
|
||||
|
||||
## Installation
|
||||
|
||||
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||
|
||||
```bash
|
||||
npm install -g yo
|
||||
npm install -g generator-joplin
|
||||
```
|
||||
|
||||
Then generate your new project:
|
||||
|
||||
```bash
|
||||
yo --node-package-manager npm joplin
|
||||
```
|
||||
|
||||
## Structure
|
||||
|
||||
The main two files you will want to look at are:
|
||||
|
||||
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||
|
||||
The file `/plugin.config.json` could also be useful if you intend to use [external scripts](#external-script-files), such as content scripts or webview scripts.
|
||||
|
||||
## Building the plugin
|
||||
|
||||
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||
|
||||
To build the plugin, simply run `npm run dist`.
|
||||
|
||||
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||
|
||||
## Updating the manifest version number
|
||||
|
||||
You can run `npm run updateVersion` to bump the patch part of the version number, so for example 1.0.3 will become 1.0.4. This script will update both the package.json and manifest.json version numbers so as to keep them in sync.
|
||||
|
||||
## Publishing the plugin
|
||||
|
||||
To publish the plugin, add it to npmjs.com by running `npm publish`. Later on, a script will pick up your plugin and add it automatically to the Joplin plugin repository as long as the package satisfies these conditions:
|
||||
|
||||
- In `package.json`, the name starts with "joplin-plugin-". For example, "joplin-plugin-toc".
|
||||
- In `package.json`, the keywords include "joplin-plugin".
|
||||
- In the `publish/` directory, there should be a .jpl and .json file (which are built by `npm run dist`)
|
||||
|
||||
In general all this is done automatically by the plugin generator, which will set the name and keywords of package.json, and will put the right files in the "publish" directory. But if something doesn't work and your plugin doesn't appear in the repository, double-check the above conditions.
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `npm run update`.
|
||||
|
||||
In general this command tries to do the right thing - in particular it's going to merge the changes in package.json and .gitignore instead of overwriting. It will also leave "/src" as well as README.md untouched.
|
||||
|
||||
The file that may cause problem is "webpack.config.js" because it's going to be overwritten. For that reason, if you want to change it, consider creating a separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||
|
||||
## External script files
|
||||
|
||||
By default, the compiler (webpack) is going to compile `src/index.ts` only (as well as any file it imports), and any other file will simply be copied to the plugin package. In some cases this is sufficient, however if you have [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplincontentscripts.html) or [webview scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinviewspanels.html#addscript) you might want to compile them too, in particular in these two cases:
|
||||
|
||||
- The script is a TypeScript file - in which case it has to be compiled to JavaScript.
|
||||
|
||||
- The script requires modules you've added to package.json. In that case, the script, whether JS or TS, must be compiled so that the dependencies are bundled with the JPL file.
|
||||
|
||||
To get such an external script file to compile, you need to add it to the `extraScripts` array in `plugin.config.json`. The path you add should be relative to /src. For example, if you have a file in "/src/webviews/index.ts", the path should be set to "webviews/index.ts". Once compiled, the file will always be named with a .js extension. So you will get "webviews/index.js" in the plugin package, and that's the path you should use to reference the file.
|
||||
|
||||
## More information
|
||||
|
||||
- [Joplin Plugin API](https://joplinapp.org/api/references/plugin_api/classes/joplin.html)
|
||||
- [Joplin Data API](https://joplinapp.org/help/api/references/rest_api)
|
||||
- [Joplin Plugin Manifest](https://joplinapp.org/api/references/plugin_manifest/)
|
||||
- Ask for help on the [forum](https://discourse.joplinapp.org/) or our [Discord channel](https://discord.gg/VSj7AFHvpq)
|
||||
|
||||
## License
|
||||
|
||||
MIT © Laurent Cozic
|
@@ -0,0 +1,9 @@
|
||||
# CodeMirror 6 sample plugin
|
||||
|
||||
This plugin demonstrates how to use the CodeMirror 6 plugin API. See `src/contentScript.ts`.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> At the time of this writing, the CodeMirror 6 editor is in beta and must be enabled
|
||||
> in settings.
|
||||
>
|
14
packages/app-cli/tests/support/plugins/codemirror6/api/Global.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
import Plugin from '../Plugin';
|
||||
import Joplin from './Joplin';
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
export default class Global {
|
||||
private joplin_;
|
||||
constructor(implementation: any, plugin: Plugin, store: any);
|
||||
get joplin(): Joplin;
|
||||
get process(): any;
|
||||
}
|
74
packages/app-cli/tests/support/plugins/codemirror6/api/Joplin.d.ts
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
import Plugin from '../Plugin';
|
||||
import JoplinData from './JoplinData';
|
||||
import JoplinPlugins from './JoplinPlugins';
|
||||
import JoplinWorkspace from './JoplinWorkspace';
|
||||
import JoplinFilters from './JoplinFilters';
|
||||
import JoplinCommands from './JoplinCommands';
|
||||
import JoplinViews from './JoplinViews';
|
||||
import JoplinInterop from './JoplinInterop';
|
||||
import JoplinSettings from './JoplinSettings';
|
||||
import JoplinContentScripts from './JoplinContentScripts';
|
||||
import JoplinClipboard from './JoplinClipboard';
|
||||
import JoplinWindow from './JoplinWindow';
|
||||
import BasePlatformImplementation from '../BasePlatformImplementation';
|
||||
import JoplinImaging from './JoplinImaging';
|
||||
/**
|
||||
* This is the main entry point to the Joplin API. You can access various services using the provided accessors.
|
||||
*
|
||||
* The API is now relatively stable and in general maintaining backward compatibility is a top priority, so you shouldn't except much breakages.
|
||||
*
|
||||
* If a breaking change ever becomes needed, best effort will be done to:
|
||||
*
|
||||
* - Deprecate features instead of removing them, so as to give you time to fix the issue;
|
||||
* - Document breaking changes in the changelog;
|
||||
*
|
||||
* So if you are developing a plugin, please keep an eye on the changelog as everything will be in there with information about how to update your code.
|
||||
*/
|
||||
export default class Joplin {
|
||||
private data_;
|
||||
private plugins_;
|
||||
private imaging_;
|
||||
private workspace_;
|
||||
private filters_;
|
||||
private commands_;
|
||||
private views_;
|
||||
private interop_;
|
||||
private settings_;
|
||||
private contentScripts_;
|
||||
private clipboard_;
|
||||
private window_;
|
||||
private implementation_;
|
||||
constructor(implementation: BasePlatformImplementation, plugin: Plugin, store: any);
|
||||
get data(): JoplinData;
|
||||
get clipboard(): JoplinClipboard;
|
||||
get imaging(): JoplinImaging;
|
||||
get window(): JoplinWindow;
|
||||
get plugins(): JoplinPlugins;
|
||||
get workspace(): JoplinWorkspace;
|
||||
get contentScripts(): JoplinContentScripts;
|
||||
/**
|
||||
* @ignore
|
||||
*
|
||||
* Not sure if it's the best way to hook into the app
|
||||
* so for now disable filters.
|
||||
*/
|
||||
get filters(): JoplinFilters;
|
||||
get commands(): JoplinCommands;
|
||||
get views(): JoplinViews;
|
||||
get interop(): JoplinInterop;
|
||||
get settings(): JoplinSettings;
|
||||
/**
|
||||
* It is not possible to bundle native packages with a plugin, because they
|
||||
* need to work cross-platforms. Instead access to certain useful native
|
||||
* packages is provided using this function.
|
||||
*
|
||||
* Currently these packages are available:
|
||||
*
|
||||
* - [sqlite3](https://www.npmjs.com/package/sqlite3)
|
||||
* - [fs-extra](https://www.npmjs.com/package/fs-extra)
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/nativeModule)
|
||||
*/
|
||||
require(_path: string): any;
|
||||
versionInfo(): Promise<import("./types").VersionInfo>;
|
||||
}
|
23
packages/app-cli/tests/support/plugins/codemirror6/api/JoplinClipboard.d.ts
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
export default class JoplinClipboard {
|
||||
private electronClipboard_;
|
||||
private electronNativeImage_;
|
||||
constructor(electronClipboard: any, electronNativeImage: any);
|
||||
readText(): Promise<string>;
|
||||
writeText(text: string): Promise<void>;
|
||||
readHtml(): Promise<string>;
|
||||
writeHtml(html: string): Promise<void>;
|
||||
/**
|
||||
* Returns the image in [data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) format.
|
||||
*/
|
||||
readImage(): Promise<string>;
|
||||
/**
|
||||
* Takes an image in [data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) format.
|
||||
*/
|
||||
writeImage(dataUrl: string): Promise<void>;
|
||||
/**
|
||||
* Returns the list available formats (mime types).
|
||||
*
|
||||
* For example [ 'text/plain', 'text/html' ]
|
||||
*/
|
||||
availableFormats(): Promise<string[]>;
|
||||
}
|
89
packages/app-cli/tests/support/plugins/codemirror6/api/JoplinCommands.d.ts
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
import { Command } from './types';
|
||||
/**
|
||||
* This class allows executing or registering new Joplin commands. Commands
|
||||
* can be executed or associated with
|
||||
* {@link JoplinViewsToolbarButtons | toolbar buttons} or
|
||||
* {@link JoplinViewsMenuItems | menu items}.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/register_command)
|
||||
*
|
||||
* ## Executing Joplin's internal commands
|
||||
*
|
||||
* It is also possible to execute internal Joplin's commands which, as of
|
||||
* now, are not well documented. You can find the list directly on GitHub
|
||||
* though at the following locations:
|
||||
*
|
||||
* * [Main screen commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/gui/MainScreen/commands)
|
||||
* * [Global commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/commands)
|
||||
* * [Editor commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts)
|
||||
*
|
||||
* To view what arguments are supported, you can open any of these files
|
||||
* and look at the `execute()` command.
|
||||
*
|
||||
* ## Executing editor commands
|
||||
*
|
||||
* There might be a situation where you want to invoke editor commands
|
||||
* without using a {@link JoplinContentScripts | contentScript}. For this
|
||||
* reason Joplin provides the built in `editor.execCommand` command.
|
||||
*
|
||||
* `editor.execCommand` should work with any core command in both the
|
||||
* [CodeMirror](https://codemirror.net/doc/manual.html#execCommand) and
|
||||
* [TinyMCE](https://www.tiny.cloud/docs/api/tinymce/tinymce.editorcommands/#execcommand) editors,
|
||||
* as well as most functions calls directly on a CodeMirror editor object (extensions).
|
||||
*
|
||||
* * [CodeMirror commands](https://codemirror.net/doc/manual.html#commands)
|
||||
* * [TinyMCE core editor commands](https://www.tiny.cloud/docs/advanced/editor-command-identifiers/#coreeditorcommands)
|
||||
*
|
||||
* `editor.execCommand` supports adding arguments for the commands.
|
||||
*
|
||||
* ```typescript
|
||||
* await joplin.commands.execute('editor.execCommand', {
|
||||
* name: 'madeUpCommand', // CodeMirror and TinyMCE
|
||||
* args: [], // CodeMirror and TinyMCE
|
||||
* ui: false, // TinyMCE only
|
||||
* value: '', // TinyMCE only
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* [View the example using the CodeMirror editor](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/tests/support/plugins/codemirror_content_script/src/index.ts)
|
||||
*
|
||||
*/
|
||||
export default class JoplinCommands {
|
||||
/**
|
||||
* <span class="platform-desktop">desktop</span> Executes the given
|
||||
* command.
|
||||
*
|
||||
* The command can take any number of arguments, and the supported
|
||||
* arguments will vary based on the command. For custom commands, this
|
||||
* is the `args` passed to the `execute()` function. For built-in
|
||||
* commands, you can find the supported arguments by checking the links
|
||||
* above.
|
||||
*
|
||||
* ```typescript
|
||||
* // Create a new note in the current notebook:
|
||||
* await joplin.commands.execute('newNote');
|
||||
*
|
||||
* // Create a new sub-notebook under the provided notebook
|
||||
* // Note: internally, notebooks are called "folders".
|
||||
* await joplin.commands.execute('newFolder', "SOME_FOLDER_ID");
|
||||
* ```
|
||||
*/
|
||||
execute(commandName: string, ...args: any[]): Promise<any | void>;
|
||||
/**
|
||||
* <span class="platform-desktop">desktop</span> Registers a new command.
|
||||
*
|
||||
* ```typescript
|
||||
* // Register a new commmand called "testCommand1"
|
||||
*
|
||||
* await joplin.commands.register({
|
||||
* name: 'testCommand1',
|
||||
* label: 'My Test Command 1',
|
||||
* iconName: 'fas fa-music',
|
||||
* execute: () => {
|
||||
* alert('Testing plugin command 1');
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
register(command: Command): Promise<void>;
|
||||
}
|
40
packages/app-cli/tests/support/plugins/codemirror6/api/JoplinContentScripts.d.ts
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
import Plugin from '../Plugin';
|
||||
import { ContentScriptType } from './types';
|
||||
export default class JoplinContentScripts {
|
||||
private plugin;
|
||||
constructor(plugin: Plugin);
|
||||
/**
|
||||
* Registers a new content script. Unlike regular plugin code, which runs in
|
||||
* a separate process, content scripts run within the main process code and
|
||||
* thus allow improved performances and more customisations in specific
|
||||
* cases. It can be used for example to load a Markdown or editor plugin.
|
||||
*
|
||||
* Note that registering a content script in itself will do nothing - it
|
||||
* will only be loaded in specific cases by the relevant app modules (eg.
|
||||
* the Markdown renderer or the code editor). So it is not a way to inject
|
||||
* and run arbitrary code in the app, which for safety and performance
|
||||
* reasons is not supported.
|
||||
*
|
||||
* The plugin generator provides a way to build any content script you might
|
||||
* want to package as well as its dependencies. See the [Plugin Generator
|
||||
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
|
||||
* for more information.
|
||||
*
|
||||
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
||||
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
||||
*
|
||||
* See also the [postMessage demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/post_messages)
|
||||
*
|
||||
* @param type Defines how the script will be used. See the type definition for more information about each supported type.
|
||||
* @param id A unique ID for the content script.
|
||||
* @param scriptPath Must be a path relative to the plugin main script. For example, if your file content_script.js is next to your index.ts file, you would set `scriptPath` to `"./content_script.js`.
|
||||
*/
|
||||
register(type: ContentScriptType, id: string, scriptPath: string): Promise<void>;
|
||||
/**
|
||||
* Listens to a messages sent from the content script using postMessage().
|
||||
* See {@link ContentScriptType} for more information as well as the
|
||||
* [postMessage
|
||||
* demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/post_messages)
|
||||
*/
|
||||
onMessage(contentScriptId: string, callback: any): Promise<void>;
|
||||
}
|
73
packages/app-cli/tests/support/plugins/codemirror6/api/JoplinData.d.ts
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
import { ModelType } from '../../../BaseModel';
|
||||
import Plugin from '../Plugin';
|
||||
import { Path } from './types';
|
||||
/**
|
||||
* This module provides access to the Joplin data API: https://joplinapp.org/help/api/references/rest_api
|
||||
* This is the main way to retrieve data, such as notes, notebooks, tags, etc.
|
||||
* or to update them or delete them.
|
||||
*
|
||||
* This is also what you would use to search notes, via the `search` endpoint.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/simple)
|
||||
*
|
||||
* In general you would use the methods in this class as if you were using a REST API. There are four methods that map to GET, POST, PUT and DELETE calls.
|
||||
* And each method takes these parameters:
|
||||
*
|
||||
* * `path`: This is an array that represents the path to the resource in the form `["resouceName", "resourceId", "resourceLink"]` (eg. ["tags", ":id", "notes"]). The "resources" segment is the name of the resources you want to access (eg. "notes", "folders", etc.). If not followed by anything, it will refer to all the resources in that collection. The optional "resourceId" points to a particular resources within the collection. Finally, an optional "link" can be present, which links the resource to a collection of resources. This can be used in the API for example to retrieve all the notes associated with a tag.
|
||||
* * `query`: (Optional) The query parameters. In a URL, this is the part after the question mark "?". In this case, it should be an object with key/value pairs.
|
||||
* * `data`: (Optional) Applies to PUT and POST calls only. The request body contains the data you want to create or modify, for example the content of a note or folder.
|
||||
* * `files`: (Optional) Used to create new resources and associate them with files.
|
||||
*
|
||||
* Please refer to the [Joplin API documentation](https://joplinapp.org/help/api/references/rest_api) for complete details about each call. As the plugin runs within the Joplin application **you do not need an authorisation token** to use this API.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* ```typescript
|
||||
* // Get a note ID, title and body
|
||||
* const noteId = 'some_note_id';
|
||||
* const note = await joplin.data.get(['notes', noteId], { fields: ['id', 'title', 'body'] });
|
||||
*
|
||||
* // Get all folders
|
||||
* const folders = await joplin.data.get(['folders']);
|
||||
*
|
||||
* // Set the note body
|
||||
* await joplin.data.put(['notes', noteId], null, { body: "New note body" });
|
||||
*
|
||||
* // Create a new note under one of the folders
|
||||
* await joplin.data.post(['notes'], null, { body: "my new note", title: "some title", parent_id: folders[0].id });
|
||||
* ```
|
||||
*/
|
||||
export default class JoplinData {
|
||||
private api_;
|
||||
private pathSegmentRegex_;
|
||||
private plugin;
|
||||
constructor(plugin: Plugin);
|
||||
private serializeApiBody;
|
||||
private pathToString;
|
||||
get(path: Path, query?: any): Promise<any>;
|
||||
post(path: Path, query?: any, body?: any, files?: any[]): Promise<any>;
|
||||
put(path: Path, query?: any, body?: any, files?: any[]): Promise<any>;
|
||||
delete(path: Path, query?: any): Promise<any>;
|
||||
itemType(itemId: string): Promise<ModelType>;
|
||||
resourcePath(resourceId: string): Promise<string>;
|
||||
/**
|
||||
* Gets an item user data. User data are key/value pairs. The `key` can be any
|
||||
* arbitrary string, while the `value` can be of any type supported by
|
||||
* [JSON.stringify](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#description)
|
||||
*
|
||||
* User data is synchronised across devices, and each value wil be merged based on their timestamp:
|
||||
*
|
||||
* - If value is modified by client 1, then modified by client 2, it will take the value from client 2
|
||||
* - If value is modified by client 1, then deleted by client 2, the value will be deleted after merge
|
||||
* - If value is deleted by client 1, then updated by client 2, the value will be restored and set to the value from client 2 after merge
|
||||
*/
|
||||
userDataGet<T>(itemType: ModelType, itemId: string, key: string): Promise<T>;
|
||||
/**
|
||||
* Sets a note user data. See {@link JoplinData.userDataGet} for more details.
|
||||
*/
|
||||
userDataSet<T>(itemType: ModelType, itemId: string, key: string, value: T): Promise<void>;
|
||||
/**
|
||||
* Deletes a note user data. See {@link JoplinData.userDataGet} for more details.
|
||||
*/
|
||||
userDataDelete(itemType: ModelType, itemId: string, key: string): Promise<void>;
|
||||
}
|
10
packages/app-cli/tests/support/plugins/codemirror6/api/JoplinFilters.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* @ignore
|
||||
*
|
||||
* Not sure if it's the best way to hook into the app
|
||||
* so for now disable filters.
|
||||
*/
|
||||
export default class JoplinFilters {
|
||||
on(name: string, callback: Function): Promise<void>;
|
||||
off(name: string, callback: Function): Promise<void>;
|
||||
}
|
61
packages/app-cli/tests/support/plugins/codemirror6/api/JoplinImaging.d.ts
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
import { Rectangle } from './types';
|
||||
export interface Implementation {
|
||||
nativeImage: any;
|
||||
}
|
||||
export interface CreateFromBufferOptions {
|
||||
width?: number;
|
||||
height?: number;
|
||||
scaleFactor?: number;
|
||||
}
|
||||
export interface ResizeOptions {
|
||||
width?: number;
|
||||
height?: number;
|
||||
quality?: 'good' | 'better' | 'best';
|
||||
}
|
||||
export type Handle = string;
|
||||
/**
|
||||
* Provides imaging functions to resize or process images. You create an image
|
||||
* using one of the `createFrom` functions, then use the other functions to
|
||||
* process the image.
|
||||
*
|
||||
* Images are associated with a handle which is what will be available to the
|
||||
* plugin. Once you are done with an image, free it using the `free()` function.
|
||||
*
|
||||
* [View the
|
||||
* example](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/tests/support/plugins/imaging/src/index.ts)
|
||||
*
|
||||
*/
|
||||
export default class JoplinImaging {
|
||||
private implementation_;
|
||||
private images_;
|
||||
constructor(implementation: Implementation);
|
||||
private createImageHandle;
|
||||
private imageByHandle;
|
||||
private cacheImage;
|
||||
createFromPath(filePath: string): Promise<Handle>;
|
||||
createFromResource(resourceId: string): Promise<Handle>;
|
||||
getSize(handle: Handle): Promise<any>;
|
||||
resize(handle: Handle, options?: ResizeOptions): Promise<string>;
|
||||
crop(handle: Handle, rectange: Rectangle): Promise<string>;
|
||||
toPngFile(handle: Handle, filePath: string): Promise<void>;
|
||||
/**
|
||||
* Quality is between 0 and 100
|
||||
*/
|
||||
toJpgFile(handle: Handle, filePath: string, quality?: number): Promise<void>;
|
||||
private tempFilePath;
|
||||
/**
|
||||
* Creates a new Joplin resource from the image data. The image will be
|
||||
* first converted to a JPEG.
|
||||
*/
|
||||
toJpgResource(handle: Handle, resourceProps: any, quality?: number): Promise<import("../../database/types").ResourceEntity>;
|
||||
/**
|
||||
* Creates a new Joplin resource from the image data. The image will be
|
||||
* first converted to a PNG.
|
||||
*/
|
||||
toPngResource(handle: Handle, resourceProps: any): Promise<import("../../database/types").ResourceEntity>;
|
||||
/**
|
||||
* Image data is not automatically deleted by Joplin so make sure you call
|
||||
* this method on the handle once you are done.
|
||||
*/
|
||||
free(handle: Handle): Promise<void>;
|
||||
}
|
17
packages/app-cli/tests/support/plugins/codemirror6/api/JoplinInterop.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import { ExportModule, ImportModule } from './types';
|
||||
/**
|
||||
* Provides a way to create modules to import external data into Joplin or to export notes into any arbitrary format.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/json_export)
|
||||
*
|
||||
* To implement an import or export module, you would simply define an object with various event handlers that are called
|
||||
* by the application during the import/export process.
|
||||
*
|
||||
* See the documentation of the [[ExportModule]] and [[ImportModule]] for more information.
|
||||
*
|
||||
* You may also want to refer to the Joplin API documentation to see the list of properties for each item (note, notebook, etc.) - https://joplinapp.org/help/api/references/rest_api
|
||||
*/
|
||||
export default class JoplinInterop {
|
||||
registerExportModule(module: ExportModule): Promise<void>;
|
||||
registerImportModule(module: ImportModule): Promise<void>;
|
||||
}
|
43
packages/app-cli/tests/support/plugins/codemirror6/api/JoplinPlugins.d.ts
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
import Plugin from '../Plugin';
|
||||
import { ContentScriptType, Script } from './types';
|
||||
/**
|
||||
* This class provides access to plugin-related features.
|
||||
*/
|
||||
export default class JoplinPlugins {
|
||||
private plugin;
|
||||
constructor(plugin: Plugin);
|
||||
/**
|
||||
* Registers a new plugin. This is the entry point when creating a plugin. You should pass a simple object with an `onStart` method to it.
|
||||
* That `onStart` method will be executed as soon as the plugin is loaded.
|
||||
*
|
||||
* ```typescript
|
||||
* joplin.plugins.register({
|
||||
* onStart: async function() {
|
||||
* // Run your plugin code here
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
register(script: Script): Promise<void>;
|
||||
/**
|
||||
* @deprecated Use joplin.contentScripts.register()
|
||||
*/
|
||||
registerContentScript(type: ContentScriptType, id: string, scriptPath: string): Promise<void>;
|
||||
/**
|
||||
* Gets the plugin own data directory path. Use this to store any
|
||||
* plugin-related data. Unlike [[installationDir]], any data stored here
|
||||
* will be persisted.
|
||||
*/
|
||||
dataDir(): Promise<string>;
|
||||
/**
|
||||
* Gets the plugin installation directory. This can be used to access any
|
||||
* asset that was packaged with the plugin. This directory should be
|
||||
* considered read-only because any data you store here might be deleted or
|
||||
* re-created at any time. To store new persistent data, use [[dataDir]].
|
||||
*/
|
||||
installationDir(): Promise<string>;
|
||||
/**
|
||||
* @deprecated Use joplin.require()
|
||||
*/
|
||||
require(_path: string): any;
|
||||
}
|
63
packages/app-cli/tests/support/plugins/codemirror6/api/JoplinSettings.d.ts
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
import Plugin from '../Plugin';
|
||||
import { SettingItem, SettingSection } from './types';
|
||||
export interface ChangeEvent {
|
||||
/**
|
||||
* Setting keys that have been changed
|
||||
*/
|
||||
keys: string[];
|
||||
}
|
||||
export type ChangeHandler = (event: ChangeEvent) => void;
|
||||
export declare const namespacedKey: (pluginId: string, key: string) => string;
|
||||
/**
|
||||
* This API allows registering new settings and setting sections, as well as getting and setting settings. Once a setting has been registered it will appear in the config screen and be editable by the user.
|
||||
*
|
||||
* Settings are essentially key/value pairs.
|
||||
*
|
||||
* Note: Currently this API does **not** provide access to Joplin's built-in settings. This is by design as plugins that modify user settings could give unexpected results
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/settings)
|
||||
*/
|
||||
export default class JoplinSettings {
|
||||
private plugin_;
|
||||
constructor(plugin: Plugin);
|
||||
/**
|
||||
* Registers new settings.
|
||||
* Note that registering a setting item is dynamic and will be gone next time Joplin starts.
|
||||
* What it means is that you need to register the setting every time the plugin starts (for example in the onStart event).
|
||||
* The setting value however will be preserved from one launch to the next so there is no risk that it will be lost even if for some
|
||||
* reason the plugin fails to start at some point.
|
||||
*/
|
||||
registerSettings(settings: Record<string, SettingItem>): Promise<void>;
|
||||
/**
|
||||
* @deprecated Use joplin.settings.registerSettings()
|
||||
*
|
||||
* Registers a new setting.
|
||||
*/
|
||||
registerSetting(key: string, settingItem: SettingItem): Promise<void>;
|
||||
/**
|
||||
* Registers a new setting section. Like for registerSetting, it is dynamic and needs to be done every time the plugin starts.
|
||||
*/
|
||||
registerSection(name: string, section: SettingSection): Promise<void>;
|
||||
/**
|
||||
* Gets a setting value (only applies to setting you registered from your plugin)
|
||||
*/
|
||||
value(key: string): Promise<any>;
|
||||
/**
|
||||
* Sets a setting value (only applies to setting you registered from your plugin)
|
||||
*/
|
||||
setValue(key: string, value: any): Promise<void>;
|
||||
/**
|
||||
* Gets a global setting value, including app-specific settings and those set by other plugins.
|
||||
*
|
||||
* The list of available settings is not documented yet, but can be found by looking at the source code:
|
||||
*
|
||||
* https://github.com/laurent22/joplin/blob/dev/packages/lib/models/Setting.ts#L142
|
||||
*/
|
||||
globalValue(key: string): Promise<any>;
|
||||
/**
|
||||
* Called when one or multiple settings of your plugin have been changed.
|
||||
* - For performance reasons, this event is triggered with a delay.
|
||||
* - You will only get events for your own plugin settings.
|
||||
*/
|
||||
onChange(handler: ChangeHandler): Promise<void>;
|
||||
}
|
31
packages/app-cli/tests/support/plugins/codemirror6/api/JoplinViews.d.ts
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
import Plugin from '../Plugin';
|
||||
import JoplinViewsDialogs from './JoplinViewsDialogs';
|
||||
import JoplinViewsMenuItems from './JoplinViewsMenuItems';
|
||||
import JoplinViewsMenus from './JoplinViewsMenus';
|
||||
import JoplinViewsToolbarButtons from './JoplinViewsToolbarButtons';
|
||||
import JoplinViewsPanels from './JoplinViewsPanels';
|
||||
import JoplinViewsNoteList from './JoplinViewsNoteList';
|
||||
/**
|
||||
* This namespace provides access to view-related services.
|
||||
*
|
||||
* All view services provide a `create()` method which you would use to create the view object, whether it's a dialog, a toolbar button or a menu item.
|
||||
* In some cases, the `create()` method will return a [[ViewHandle]], which you would use to act on the view, for example to set certain properties or call some methods.
|
||||
*/
|
||||
export default class JoplinViews {
|
||||
private store;
|
||||
private plugin;
|
||||
private panels_;
|
||||
private menuItems_;
|
||||
private menus_;
|
||||
private toolbarButtons_;
|
||||
private dialogs_;
|
||||
private noteList_;
|
||||
private implementation_;
|
||||
constructor(implementation: any, plugin: Plugin, store: any);
|
||||
get dialogs(): JoplinViewsDialogs;
|
||||
get panels(): JoplinViewsPanels;
|
||||
get menuItems(): JoplinViewsMenuItems;
|
||||
get menus(): JoplinViewsMenus;
|
||||
get toolbarButtons(): JoplinViewsToolbarButtons;
|
||||
get noteList(): JoplinViewsNoteList;
|
||||
}
|