Compare commits
504 Commits
android-v3
...
join_serve
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c44aad544e | ||
|
|
996a0894ae | ||
|
|
66fa3fc808 | ||
|
|
dab55daf95 | ||
|
|
7f1c31e03f | ||
|
|
0a8255f091 | ||
|
|
9f3e6650a9 | ||
|
|
4a17da3df5 | ||
|
|
2c4f0d4d8c | ||
|
|
9c1c2fb0d4 | ||
|
|
2332e4bf62 | ||
|
|
a488ac1b27 | ||
|
|
6daa41ca66 | ||
|
|
cc9517f1a2 | ||
|
|
200a471e55 | ||
|
|
c21d37bd91 | ||
|
|
e36cd0e60b | ||
|
|
871f55bf11 | ||
|
|
22c9fed663 | ||
|
|
ea362d7a82 | ||
|
|
9ae9347f89 | ||
|
|
ae8bb902f9 | ||
|
|
90eeec23de | ||
|
|
474fd094c4 | ||
|
|
937d8fa4f7 | ||
|
|
45c9844616 | ||
|
|
12b8ef5a54 | ||
|
|
18f72c224e | ||
|
|
7ca3aaa83f | ||
|
|
04b1443e5a | ||
|
|
c461741778 | ||
|
|
2865b0a803 | ||
|
|
21e49be22f | ||
|
|
fef761cbab | ||
|
|
c15a353dc2 | ||
|
|
ffb32766c1 | ||
|
|
038908550e | ||
|
|
42f59134ae | ||
|
|
fc0014c0b5 | ||
|
|
42d8df3036 | ||
|
|
1fad9ca1cc | ||
|
|
ae289be77a | ||
|
|
7f6bfe9c6e | ||
|
|
ead4001b7a | ||
|
|
7b95ef72a0 | ||
|
|
a4556bf598 | ||
|
|
8d6268dc92 | ||
|
|
7ffcbdf60a | ||
|
|
76989ddc45 | ||
|
|
1db1254617 | ||
|
|
9810bffddc | ||
|
|
b25e18107b | ||
|
|
edc5fe5d1b | ||
|
|
7ffb44b3a4 | ||
|
|
32f4c33140 | ||
|
|
1a7b09c91c | ||
|
|
e5bf8e0e58 | ||
|
|
94725c533c | ||
|
|
359c92b64f | ||
|
|
8f8b8ad943 | ||
|
|
dd2f329fd5 | ||
|
|
813f594cb4 | ||
|
|
0e0ce49867 | ||
|
|
e485d318b7 | ||
|
|
4e82d81df1 | ||
|
|
d5dbda201b | ||
|
|
831258506b | ||
|
|
67f3329ecb | ||
|
|
ed7e6751f0 | ||
|
|
35e69486d3 | ||
|
|
918c8830e0 | ||
|
|
c3b4a4b955 | ||
|
|
44a14fabbd | ||
|
|
49399cd1fa | ||
|
|
fc4cd2e942 | ||
|
|
cd6e457dc5 | ||
|
|
2e9bf3a4e5 | ||
|
|
547ceea4b0 | ||
|
|
776ff5e7ea | ||
|
|
2b3bac0d43 | ||
|
|
e48efe2e8d | ||
|
|
5f6382fbc0 | ||
|
|
3d5d82081a | ||
|
|
cff96b1306 | ||
|
|
98c5a9c096 | ||
|
|
e92430b3ed | ||
|
|
848d1bfe64 | ||
|
|
a386283530 | ||
|
|
6101031269 | ||
|
|
2fc3431f46 | ||
|
|
361fa2c768 | ||
|
|
f4a0a2466b | ||
|
|
dbf225d6ad | ||
|
|
4773a3831c | ||
|
|
6a19690581 | ||
|
|
b7a771d58d | ||
|
|
e3daefb81a | ||
|
|
b4253dace8 | ||
|
|
fcf3be1be1 | ||
|
|
99aebbad81 | ||
|
|
81b695a2a9 | ||
|
|
2dbba27357 | ||
|
|
8713cd2fd8 | ||
|
|
d0fc4ea21b | ||
|
|
8bd62800ef | ||
|
|
00f9e932e6 | ||
|
|
b8b55e4a55 | ||
|
|
ef5be2ded3 | ||
|
|
00702dde00 | ||
|
|
2a6af9bed9 | ||
|
|
c26fe0960b | ||
|
|
ab9d36fc08 | ||
|
|
28eb53bd9f | ||
|
|
3097c3e589 | ||
|
|
08371ef718 | ||
|
|
561716efea | ||
|
|
0d457d1bde | ||
|
|
8c11f17c93 | ||
|
|
f7a90ee1d2 | ||
|
|
8822409f7c | ||
|
|
cd3e7f485a | ||
|
|
8d42b01d4f | ||
|
|
2c37197641 | ||
|
|
c2c37b3741 | ||
|
|
3e770300dc | ||
|
|
683291d5df | ||
|
|
d239035417 | ||
|
|
5ef37d9de0 | ||
|
|
1111bde017 | ||
|
|
468cf00d77 | ||
|
|
3c5b41b992 | ||
|
|
5f66c51dba | ||
|
|
bfeaa67ec4 | ||
|
|
348fd0333f | ||
|
|
51c4d6d6ef | ||
|
|
09d77a65e8 | ||
|
|
d1aec4a9f7 | ||
|
|
cab1525589 | ||
|
|
a52f3fea9e | ||
|
|
dfbd5eb8ed | ||
|
|
3131f36033 | ||
|
|
dc5b2cfa21 | ||
|
|
cad0f35fcc | ||
|
|
38ea92ff57 | ||
|
|
830deada22 | ||
|
|
38cd4033ea | ||
|
|
02900752d9 | ||
|
|
091e9813b5 | ||
|
|
e61e5ac32a | ||
|
|
414970c9a1 | ||
|
|
d4ed49ff23 | ||
|
|
8751d5d152 | ||
|
|
2e846fe15d | ||
|
|
e54b7696d9 | ||
|
|
553c61d628 | ||
|
|
6a15db3a36 | ||
|
|
6f1d0a4b90 | ||
|
|
33b995672c | ||
|
|
8ee46bb4e7 | ||
|
|
b35d9a64cf | ||
|
|
64ef74dd01 | ||
|
|
53035839a5 | ||
|
|
af5287de99 | ||
|
|
45a7554774 | ||
|
|
b06ffe3d25 | ||
|
|
53ea51b758 | ||
|
|
820acdc1f0 | ||
|
|
ef0a79666e | ||
|
|
d096a90c0e | ||
|
|
191775310e | ||
|
|
4fc351b861 | ||
|
|
396decd26c | ||
|
|
01f8fa7bef | ||
|
|
c40856ac7e | ||
|
|
d869cce413 | ||
|
|
a83e8311d8 | ||
|
|
aa884fcb39 | ||
|
|
be2a4c3e24 | ||
|
|
520eec555b | ||
|
|
1281fdb9d2 | ||
|
|
6029353fd1 | ||
|
|
8d1d1be79e | ||
|
|
fd180ae0b4 | ||
|
|
6fdfd6eae6 | ||
|
|
cd5bb575c8 | ||
|
|
2df56530ae | ||
|
|
7987137470 | ||
|
|
a1dcd2fd8f | ||
|
|
7826dc064a | ||
|
|
eedf083bfd | ||
|
|
d4aa1f8f8d | ||
|
|
738e749d51 | ||
|
|
8fe818c0b0 | ||
|
|
e603452fad | ||
|
|
3827637b54 | ||
|
|
1da7c54e5f | ||
|
|
e24ebffba6 | ||
|
|
97fa85a3f7 | ||
|
|
defe36bba1 | ||
|
|
e5bd77836a | ||
|
|
8f5e628303 | ||
|
|
6850c8128b | ||
|
|
8a797fdf23 | ||
|
|
1ae550c0aa | ||
|
|
e7e0529f52 | ||
|
|
2381e44c7f | ||
|
|
a59e975f73 | ||
|
|
2d703b6292 | ||
|
|
b8db70f707 | ||
|
|
c91513b6b5 | ||
|
|
a57ada97ef | ||
|
|
d8677a70dd | ||
|
|
15839a19fd | ||
|
|
8f1d55c1fc | ||
|
|
98c18711f7 | ||
|
|
24ff4612fb | ||
|
|
f832eb38ff | ||
|
|
91dc23c23f | ||
|
|
d1913493ab | ||
|
|
fd2b22ed68 | ||
|
|
14b56f19df | ||
|
|
0b082a985b | ||
|
|
53dcac22d0 | ||
|
|
2c721a76b7 | ||
|
|
b68cfd6d9e | ||
|
|
affebedc4b | ||
|
|
a714ef4807 | ||
|
|
596f99aad3 | ||
|
|
c530d35b36 | ||
|
|
5a5c734e2a | ||
|
|
f7eb483d9a | ||
|
|
7f3c7e807c | ||
|
|
a50fc02b32 | ||
|
|
63702e9e34 | ||
|
|
92c67aab4e | ||
|
|
91535870a2 | ||
|
|
d4bb277417 | ||
|
|
90f87d1496 | ||
|
|
b07752b3ab | ||
|
|
98effef4c5 | ||
|
|
32a919eb81 | ||
|
|
e124fd5c9f | ||
|
|
c5f9290402 | ||
|
|
c80cdadc99 | ||
|
|
d96dcef109 | ||
|
|
33b889ca38 | ||
|
|
fa78ea0173 | ||
|
|
6705712f80 | ||
|
|
2785b7f7d9 | ||
|
|
f04831406e | ||
|
|
fdffc81834 | ||
|
|
6f113df2d6 | ||
|
|
8b8b6fbe36 | ||
|
|
1ef8fd529b | ||
|
|
9547a459cb | ||
|
|
be1d092cab | ||
|
|
517669ee27 | ||
|
|
72fc97116f | ||
|
|
77ca6b3447 | ||
|
|
b227d337d0 | ||
|
|
a6e671d45b | ||
|
|
47c82a7e75 | ||
|
|
bafa1576f2 | ||
|
|
48956df439 | ||
|
|
4716065295 | ||
|
|
f801bbfb27 | ||
|
|
4a043f68ad | ||
|
|
cac93e9f9c | ||
|
|
e1e5c9aeb0 | ||
|
|
382cb257ab | ||
|
|
711d214741 | ||
|
|
6f375be8b9 | ||
|
|
a118615e06 | ||
|
|
912bf7463f | ||
|
|
cfc29832a2 | ||
|
|
737fd132e3 | ||
|
|
9fc76f4e4c | ||
|
|
981f15d85c | ||
|
|
a59594db3b | ||
|
|
8c8190e2e9 | ||
|
|
d7e7ff77e8 | ||
|
|
e33c142c5a | ||
|
|
97d3a8243d | ||
|
|
f1716a3edb | ||
|
|
1436f5867d | ||
|
|
d754b8fe0c | ||
|
|
4f58055cc1 | ||
|
|
98697e1db4 | ||
|
|
8ac65a08c1 | ||
|
|
2b86d83290 | ||
|
|
09cafe99d1 | ||
|
|
6fce844cbf | ||
|
|
52de8c071f | ||
|
|
537543cc8a | ||
|
|
ff16453299 | ||
|
|
210deec495 | ||
|
|
e96baea005 | ||
|
|
ae24b91f25 | ||
|
|
f2e5118bf5 | ||
|
|
72698ec573 | ||
|
|
68abc27c6a | ||
|
|
1acb3d0726 | ||
|
|
5bf97dc3b8 | ||
|
|
e0e04fbc91 | ||
|
|
625cd1221c | ||
|
|
110d5bde2d | ||
|
|
93a85b3207 | ||
|
|
ff305f42fd | ||
|
|
99ba854ee1 | ||
|
|
0795c67354 | ||
|
|
38b368e997 | ||
|
|
f9ffe6c4e6 | ||
|
|
5adc0170fc | ||
|
|
f54c364b4d | ||
|
|
9f541b9b9d | ||
|
|
bd0af08c57 | ||
|
|
ac06c6750d | ||
|
|
23b07094b7 | ||
|
|
7eefc016de | ||
|
|
c002be76cd | ||
|
|
2cd29aaaea | ||
|
|
4cb6b01c71 | ||
|
|
91c79b9488 | ||
|
|
fc516d05b3 | ||
|
|
2769c9586c | ||
|
|
fd15d5a6d3 | ||
|
|
7237d7faa7 | ||
|
|
3025d62568 | ||
|
|
e9a9f68568 | ||
|
|
5b5dcf34a1 | ||
|
|
9e8500c148 | ||
|
|
4f1999f921 | ||
|
|
6ee9571069 | ||
|
|
10663b1494 | ||
|
|
f25db9bbd7 | ||
|
|
44ac261304 | ||
|
|
eac995a209 | ||
|
|
15c973e885 | ||
|
|
1762f9485f | ||
|
|
7777f8428f | ||
|
|
948aa9db4f | ||
|
|
fdde04ee85 | ||
|
|
f77a20f5d5 | ||
|
|
d43aa2a3e6 | ||
|
|
04d5ce13c2 | ||
|
|
3b764ba06a | ||
|
|
5492ce55fa | ||
|
|
f6b3f9860c | ||
|
|
88f687ba6a | ||
|
|
1f0a98999f | ||
|
|
69135c3bea | ||
|
|
c27d542a4b | ||
|
|
bd1c2534c5 | ||
|
|
72513b520c | ||
|
|
ec0f9ef9bc | ||
|
|
818bc3218a | ||
|
|
82760a5b6a | ||
|
|
5ba9a16cfd | ||
|
|
68fc91fdc7 | ||
|
|
bdc4687327 | ||
|
|
3a9f57e13f | ||
|
|
b72c48c693 | ||
|
|
f1e42f3bac | ||
|
|
93c908286d | ||
|
|
4eb8777ed0 | ||
|
|
5e1909cee0 | ||
|
|
2e7b312415 | ||
|
|
7735a59fc1 | ||
|
|
41d6e912a7 | ||
|
|
4c2fae8423 | ||
|
|
b72c134890 | ||
|
|
58a9c229bb | ||
|
|
d8c203bb8a | ||
|
|
9020c07825 | ||
|
|
e884da8312 | ||
|
|
d134ea8bfe | ||
|
|
faa44468f3 | ||
|
|
85585d16d2 | ||
|
|
b9c5b8f187 | ||
|
|
da8e638359 | ||
|
|
6482ab5a4e | ||
|
|
ec74abe754 | ||
|
|
859bc8d88e | ||
|
|
56ed471a2f | ||
|
|
650594ecea | ||
|
|
3e9bb914e5 | ||
|
|
f75e911a4e | ||
|
|
78fb07d4c7 | ||
|
|
6390ef43ed | ||
|
|
78c5c4d7c3 | ||
|
|
0d1d50768b | ||
|
|
57093b35ea | ||
|
|
cba5cf660b | ||
|
|
0024722c79 | ||
|
|
bc2832e78f | ||
|
|
424cc96d36 | ||
|
|
56fd5d828f | ||
|
|
03843b087a | ||
|
|
b179509dd3 | ||
|
|
f6851314d2 | ||
|
|
eaec45cb3f | ||
|
|
9be954496c | ||
|
|
ac289c5198 | ||
|
|
98ef5e619b | ||
|
|
62faa48aac | ||
|
|
5daa7a1f4c | ||
|
|
32be071601 | ||
|
|
0dc63dd306 | ||
|
|
78ed58187a | ||
|
|
b8b8dd8011 | ||
|
|
0bc72b45be | ||
|
|
c52523134d | ||
|
|
aff871eee6 | ||
|
|
a5a68a2238 | ||
|
|
e066b8f9bc | ||
|
|
e7827a3a64 | ||
|
|
4ceca647dc | ||
|
|
4185afebdb | ||
|
|
c530b07f45 | ||
|
|
0ed7daaed8 | ||
|
|
2eb107c716 | ||
|
|
c99780db1b | ||
|
|
ac05b7d389 | ||
|
|
9719d82c47 | ||
|
|
48694a585f | ||
|
|
b577a27887 | ||
|
|
9f649c9fc2 | ||
|
|
8c9c5d13bd | ||
|
|
96692de93c | ||
|
|
3d8e1dd146 | ||
|
|
227e41b69a | ||
|
|
a616e26a0f | ||
|
|
ba0e7e2226 | ||
|
|
b5a4ba554d | ||
|
|
9037da8f2d | ||
|
|
6998606ec9 | ||
|
|
66d52c90a3 | ||
|
|
f6fb1f7fbf | ||
|
|
3aac6043da | ||
|
|
ae170e0aa0 | ||
|
|
371f027a24 | ||
|
|
37422f316e | ||
|
|
a9f284ae45 | ||
|
|
fd2f69cc73 | ||
|
|
c4eab3c79c | ||
|
|
a0b9c6376e | ||
|
|
e2fc056369 | ||
|
|
453b4705b1 | ||
|
|
4128061e40 | ||
|
|
432b0ca870 | ||
|
|
c484cd2e48 | ||
|
|
58f0725c6b | ||
|
|
bf8fbec0cd | ||
|
|
f1d452f130 | ||
|
|
26012cd7d5 | ||
|
|
a414241541 | ||
|
|
0f13bf9d51 | ||
|
|
c142c5c5c0 | ||
|
|
af5c0135dc | ||
|
|
8a811b9e78 | ||
|
|
602484f143 | ||
|
|
dc84db1657 | ||
|
|
f5882ecfcc | ||
|
|
30000c34ec | ||
|
|
6e3df1bd90 | ||
|
|
67196ac0b2 | ||
|
|
69646b5522 | ||
|
|
9147afce9a | ||
|
|
c92701c52f | ||
|
|
ab3e9d1a3e | ||
|
|
f9cab8843b | ||
|
|
c36289c024 | ||
|
|
60b6db8cd4 | ||
|
|
bbd8f6f40e | ||
|
|
34b7f4e1f8 | ||
|
|
06b681d897 | ||
|
|
f02a94bef5 | ||
|
|
ae6b57c5a5 | ||
|
|
88ab916008 | ||
|
|
97b0ffc263 | ||
|
|
ff8848d138 | ||
|
|
2b686e6318 | ||
|
|
b913d18882 | ||
|
|
a2c9a01722 | ||
|
|
000d23c20f | ||
|
|
9e9f2f2930 | ||
|
|
c5a1a759c7 | ||
|
|
0b6a1c75ba | ||
|
|
53a0f8ddbc | ||
|
|
67eabb5038 | ||
|
|
983fced410 | ||
|
|
4f5bbc1132 | ||
|
|
2f10235ecb | ||
|
|
cfa7d6cb31 | ||
|
|
f5d62a50fe | ||
|
|
b52f5435aa | ||
|
|
bfd5bfc004 | ||
|
|
82965fe991 | ||
|
|
b2c162c25b | ||
|
|
022e76fe8d | ||
|
|
4b2d1895fd | ||
|
|
534507a31f | ||
|
|
5b4a300c81 | ||
|
|
1de0a59313 |
@@ -9,8 +9,9 @@ API_KEY=random-string
|
||||
QUEUE_TTL=900000
|
||||
QUEUE_RETRY_COUNT=2
|
||||
QUEUE_MAINTENANCE_INTERVAL=30000
|
||||
IMAGE_MAX_DIMENSION=400
|
||||
|
||||
HTR_CLI_DOCKER_IMAGE=joplin/htr-cli:0.0.2
|
||||
HTR_CLI_DOCKER_IMAGE=joplin/htr-cli:latest
|
||||
# Fullpath to images folder e.g.:
|
||||
#HTR_CLI_IMAGES_FOLDER=/home/user/joplin/packages/transcribe/images
|
||||
HTR_CLI_IMAGES_FOLDER=
|
||||
@@ -18,6 +19,8 @@ HTR_CLI_IMAGES_FOLDER=
|
||||
QUEUE_DRIVER=pg
|
||||
# QUEUE_DRIVER=sqlite
|
||||
|
||||
FILE_STORAGE_MAINTENANCE_INTERVAL=3600000
|
||||
FILE_STORAGE_TTL=604800000 # one week
|
||||
|
||||
# =============================================================================
|
||||
# Queue driver
|
||||
|
||||
@@ -90,12 +90,13 @@ plugin_types/
|
||||
readme/
|
||||
packages/react-native-vosk/lib/
|
||||
packages/lib/countable/Countable.js
|
||||
packages/onenote-converter/pkg/onenote_converter.js
|
||||
packages/onenote-converter/renderer/pkg/*
|
||||
|
||||
# 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/cli-integration-tests.js
|
||||
packages/app-cli/app/command-apidoc.js
|
||||
packages/app-cli/app/command-attach.js
|
||||
packages/app-cli/app/command-batch.js
|
||||
@@ -117,6 +118,8 @@ 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-publish.test.js
|
||||
packages/app-cli/app/command-publish.js
|
||||
packages/app-cli/app/command-ren.js
|
||||
packages/app-cli/app/command-restore.js
|
||||
packages/app-cli/app/command-rmbook.test.js
|
||||
@@ -129,6 +132,8 @@ packages/app-cli/app/command-share.test.js
|
||||
packages/app-cli/app/command-share.js
|
||||
packages/app-cli/app/command-sync.js
|
||||
packages/app-cli/app/command-testing.js
|
||||
packages/app-cli/app/command-unpublish.test.js
|
||||
packages/app-cli/app/command-unpublish.js
|
||||
packages/app-cli/app/command-use.js
|
||||
packages/app-cli/app/command-version.js
|
||||
packages/app-cli/app/gui/FolderListWidget.js
|
||||
@@ -265,6 +270,7 @@ packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/useEditorCommands.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/useKeymap.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/useRefocusOnVisiblePaneChange.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/useSyncEditorValue.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/PlainEditor/PlainEditor.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/styles/index.js
|
||||
@@ -275,6 +281,7 @@ packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/shouldPasteResources.
|
||||
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/useCursorPositioning.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useEditDialog.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useEditDialogEventListeners.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useKeyboardRefocusHandler.js
|
||||
@@ -316,6 +323,7 @@ packages/app-desktop/gui/NoteEditor/utils/useEffectiveNoteId.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/useFolder.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/useFormNote.test.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/useFormNote.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/useInitialCursorLocation.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/useMessageHandler.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/useNoteSearchBar.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/usePluginEditorView.test.js
|
||||
@@ -599,6 +607,7 @@ packages/app-desktop/tools/generateLatestArm64Yml.js
|
||||
packages/app-desktop/tools/githubReleasesUtils.js
|
||||
packages/app-desktop/tools/modifyReleaseAssets.js
|
||||
packages/app-desktop/tools/notarizeMacApp.js
|
||||
packages/app-desktop/tools/resolveSourceMap.js
|
||||
packages/app-desktop/utils/7zip/getPathToExecutable7Zip.js
|
||||
packages/app-desktop/utils/7zip/pathToBundled7Zip.js
|
||||
packages/app-desktop/utils/checkForUpdatesUtils.test.js
|
||||
@@ -672,6 +681,8 @@ packages/app-mobile/components/ExtendedWebView/index.js
|
||||
packages/app-mobile/components/ExtendedWebView/index.web.js
|
||||
packages/app-mobile/components/ExtendedWebView/types.js
|
||||
packages/app-mobile/components/ExtendedWebView/utils/useCss.js
|
||||
packages/app-mobile/components/FeedbackBanner.test.js
|
||||
packages/app-mobile/components/FeedbackBanner.js
|
||||
packages/app-mobile/components/FolderPicker.js
|
||||
packages/app-mobile/components/Icon.js
|
||||
packages/app-mobile/components/IconButton.js
|
||||
@@ -690,6 +701,7 @@ packages/app-mobile/components/NoteEditor/ImageEditor/ImageEditor.js
|
||||
packages/app-mobile/components/NoteEditor/ImageEditor/autosave.js
|
||||
packages/app-mobile/components/NoteEditor/ImageEditor/isEditableResource.js
|
||||
packages/app-mobile/components/NoteEditor/ImageEditor/promptRestoreAutosave.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownEditor.test.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownEditor.js
|
||||
packages/app-mobile/components/NoteEditor/NoteEditor.test.js
|
||||
packages/app-mobile/components/NoteEditor/NoteEditor.js
|
||||
@@ -698,7 +710,6 @@ packages/app-mobile/components/NoteEditor/RichTextEditor.js
|
||||
packages/app-mobile/components/NoteEditor/SearchPanel.js
|
||||
packages/app-mobile/components/NoteEditor/WarningBanner.js
|
||||
packages/app-mobile/components/NoteEditor/commandDeclarations.js
|
||||
packages/app-mobile/components/NoteEditor/hooks/useCodeMirrorPlugins.js
|
||||
packages/app-mobile/components/NoteEditor/hooks/useEditorCommandHandler.test.js
|
||||
packages/app-mobile/components/NoteEditor/hooks/useEditorCommandHandler.js
|
||||
packages/app-mobile/components/NoteEditor/testing/createTestEditorProps.js
|
||||
@@ -719,6 +730,8 @@ packages/app-mobile/components/SearchInput.js
|
||||
packages/app-mobile/components/SelectDateTimeDialog.js
|
||||
packages/app-mobile/components/SideMenu.js
|
||||
packages/app-mobile/components/SideMenuContentNote.js
|
||||
packages/app-mobile/components/SyncWizard/JoplinCloudIcon.js
|
||||
packages/app-mobile/components/SyncWizard/SyncWizard.js
|
||||
packages/app-mobile/components/TagEditor.test.js
|
||||
packages/app-mobile/components/TagEditor.js
|
||||
packages/app-mobile/components/TextInput.js
|
||||
@@ -735,6 +748,7 @@ packages/app-mobile/components/base-screen.js
|
||||
packages/app-mobile/components/biometrics/BiometricPopup.js
|
||||
packages/app-mobile/components/biometrics/biometricAuthenticate.js
|
||||
packages/app-mobile/components/biometrics/sensorInfo.js
|
||||
packages/app-mobile/components/buttons/CardButton.js
|
||||
packages/app-mobile/components/buttons/FloatingActionButton.js
|
||||
packages/app-mobile/components/buttons/LabelledIconButton.js
|
||||
packages/app-mobile/components/buttons/MultiTouchableOpacity.js
|
||||
@@ -810,7 +824,6 @@ packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/InstallButto
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/WrappedPluginStates.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/mockRepositoryApiConstructor.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/newRepoApi.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/pluginServiceSetup.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/utils/openWebsiteForPlugin.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/utils/usePluginCallbacks.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/utils/usePluginItem.js
|
||||
@@ -836,6 +849,7 @@ packages/app-mobile/components/screens/NoteTagsDialog.js
|
||||
packages/app-mobile/components/screens/Notes/NewNoteButton.test.js
|
||||
packages/app-mobile/components/screens/Notes/NewNoteButton.js
|
||||
packages/app-mobile/components/screens/Notes/Notes.js
|
||||
packages/app-mobile/components/screens/SearchScreen/SearchResults.test.js
|
||||
packages/app-mobile/components/screens/SearchScreen/SearchResults.js
|
||||
packages/app-mobile/components/screens/SearchScreen/index.js
|
||||
packages/app-mobile/components/screens/ShareManager/AcceptedShareItem.js
|
||||
@@ -868,6 +882,7 @@ packages/app-mobile/contentScripts/imageEditorBundle/utils/useEditorMessenger.js
|
||||
packages/app-mobile/contentScripts/markdownEditorBundle/contentScript.js
|
||||
packages/app-mobile/contentScripts/markdownEditorBundle/types.js
|
||||
packages/app-mobile/contentScripts/markdownEditorBundle/useWebViewSetup.js
|
||||
packages/app-mobile/contentScripts/markdownEditorBundle/utils/useCodeMirrorPlugins.js
|
||||
packages/app-mobile/contentScripts/rendererBundle/contentScript/Renderer.test.js
|
||||
packages/app-mobile/contentScripts/rendererBundle/contentScript/Renderer.js
|
||||
packages/app-mobile/contentScripts/rendererBundle/contentScript/index.js
|
||||
@@ -897,14 +912,13 @@ packages/app-mobile/services/AlarmServiceDriver.web.js
|
||||
packages/app-mobile/services/BackButtonService.js
|
||||
packages/app-mobile/services/commands/stateToWhenClauseContext.js
|
||||
packages/app-mobile/services/e2ee/RSA.react-native.js
|
||||
packages/app-mobile/services/e2ee/RSA.react-native.web.js
|
||||
packages/app-mobile/services/e2ee/crypto.js
|
||||
packages/app-mobile/services/plugins/PlatformImplementation.js
|
||||
packages/app-mobile/services/profiles/index.js
|
||||
packages/app-mobile/services/voiceTyping/VoiceTyping.js
|
||||
packages/app-mobile/services/voiceTyping/utils/unzip.android.js
|
||||
packages/app-mobile/services/voiceTyping/utils/unzip.js
|
||||
packages/app-mobile/services/voiceTyping/vosk.android.js
|
||||
packages/app-mobile/services/voiceTyping/vosk.js
|
||||
packages/app-mobile/services/voiceTyping/whisper.test.js
|
||||
packages/app-mobile/services/voiceTyping/whisper.js
|
||||
packages/app-mobile/setupQuickActions.js
|
||||
@@ -918,6 +932,7 @@ packages/app-mobile/utils/ShareUtils.test.js
|
||||
packages/app-mobile/utils/ShareUtils.js
|
||||
packages/app-mobile/utils/TlsUtils.js
|
||||
packages/app-mobile/utils/appDefaultState.js
|
||||
packages/app-mobile/utils/appReducer.js
|
||||
packages/app-mobile/utils/autodetectTheme.js
|
||||
packages/app-mobile/utils/buildStartupTasks.js
|
||||
packages/app-mobile/utils/checkPermissions.js
|
||||
@@ -939,6 +954,7 @@ packages/app-mobile/utils/fs-driver/testUtil/verifyDirectoryMatches.js
|
||||
packages/app-mobile/utils/getPackageInfo.js
|
||||
packages/app-mobile/utils/getVersionInfoText.js
|
||||
packages/app-mobile/utils/hooks/useBackHandler.js
|
||||
packages/app-mobile/utils/hooks/useIsScreenReaderEnabled.js
|
||||
packages/app-mobile/utils/hooks/useKeyboardState.js
|
||||
packages/app-mobile/utils/hooks/useOnLongPressProps.js
|
||||
packages/app-mobile/utils/hooks/useReduceMotionEnabled.js
|
||||
@@ -956,6 +972,7 @@ packages/app-mobile/utils/pickDocument.js
|
||||
packages/app-mobile/utils/polyfills/bufferPolyfill.js
|
||||
packages/app-mobile/utils/polyfills/crypto-polyfill/index.js
|
||||
packages/app-mobile/utils/polyfills/index.js
|
||||
packages/app-mobile/utils/polyfills/index.web.js
|
||||
packages/app-mobile/utils/setupNotifications.js
|
||||
packages/app-mobile/utils/shareFile.js
|
||||
packages/app-mobile/utils/shareHandler.js
|
||||
@@ -966,6 +983,7 @@ packages/app-mobile/utils/shim-init-react/shimInitShared.js
|
||||
packages/app-mobile/utils/testing/createMockReduxStore.js
|
||||
packages/app-mobile/utils/testing/getWebViewDomById.js
|
||||
packages/app-mobile/utils/testing/getWebViewWindowById.js
|
||||
packages/app-mobile/utils/testing/mockPluginServiceSetup.js
|
||||
packages/app-mobile/utils/testing/setupGlobalStore.js
|
||||
packages/app-mobile/utils/testing/testingLibrary.js
|
||||
packages/app-mobile/utils/types.js
|
||||
@@ -1004,6 +1022,9 @@ packages/editor/CodeMirror/editorCommands/sortSelectedLines.test.js
|
||||
packages/editor/CodeMirror/editorCommands/sortSelectedLines.js
|
||||
packages/editor/CodeMirror/editorCommands/supportsCommand.js
|
||||
packages/editor/CodeMirror/extensions/biDirectionalTextExtension.js
|
||||
packages/editor/CodeMirror/extensions/ctrlClickActionExtension.js
|
||||
packages/editor/CodeMirror/extensions/ctrlClickCheckboxExtension.js
|
||||
packages/editor/CodeMirror/extensions/highlightActiveLineExtension.js
|
||||
packages/editor/CodeMirror/extensions/keyUpHandlerExtension.js
|
||||
packages/editor/CodeMirror/extensions/links/ctrlClickLinksExtension.js
|
||||
packages/editor/CodeMirror/extensions/links/followLinkTooltipExtension.test.js
|
||||
@@ -1034,6 +1055,7 @@ packages/editor/CodeMirror/extensions/rendering/types.js
|
||||
packages/editor/CodeMirror/extensions/rendering/utils/makeBlockReplaceExtension.js
|
||||
packages/editor/CodeMirror/extensions/rendering/utils/makeInlineReplaceExtension.js
|
||||
packages/editor/CodeMirror/extensions/rendering/utils/nodeIntersectsSelection.js
|
||||
packages/editor/CodeMirror/extensions/searchExtension.test.js
|
||||
packages/editor/CodeMirror/extensions/searchExtension.js
|
||||
packages/editor/CodeMirror/extensions/selectedNoteIdExtension.js
|
||||
packages/editor/CodeMirror/getScrollFraction.js
|
||||
@@ -1074,14 +1096,22 @@ packages/editor/CodeMirror/utils/isInSyntaxNode.js
|
||||
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/allLanguages.js
|
||||
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/defaultLanguage.js
|
||||
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/lookUpLanguage.js
|
||||
packages/editor/CodeMirror/utils/markdown/getCheckboxAtPosition.js
|
||||
packages/editor/CodeMirror/utils/markdown/renumberSelectedLists.test.js
|
||||
packages/editor/CodeMirror/utils/markdown/renumberSelectedLists.js
|
||||
packages/editor/CodeMirror/utils/markdown/stripBlockquote.js
|
||||
packages/editor/CodeMirror/utils/markdown/toggleCheckboxAt.js
|
||||
packages/editor/CodeMirror/utils/setupVim.js
|
||||
packages/editor/ProseMirror/commands.test.js
|
||||
packages/editor/ProseMirror/commands.js
|
||||
packages/editor/CodeMirror/vendor/announceSearchMatch.js
|
||||
packages/editor/ProseMirror/commands/commands.test.js
|
||||
packages/editor/ProseMirror/commands/commands.js
|
||||
packages/editor/ProseMirror/commands/focusEditor.js
|
||||
packages/editor/ProseMirror/createEditor.js
|
||||
packages/editor/ProseMirror/index.js
|
||||
packages/editor/ProseMirror/plugins/detailsPlugin.test.js
|
||||
packages/editor/ProseMirror/plugins/detailsPlugin.js
|
||||
packages/editor/ProseMirror/plugins/imagePlugin.test.js
|
||||
packages/editor/ProseMirror/plugins/imagePlugin.js
|
||||
packages/editor/ProseMirror/plugins/inputRulesPlugin.js
|
||||
packages/editor/ProseMirror/plugins/joplinEditablePlugin/createEditorDialog.js
|
||||
packages/editor/ProseMirror/plugins/joplinEditablePlugin/joplinEditablePlugin.test.js
|
||||
@@ -1093,26 +1123,43 @@ packages/editor/ProseMirror/plugins/linkTooltipPlugin.test.js
|
||||
packages/editor/ProseMirror/plugins/linkTooltipPlugin.js
|
||||
packages/editor/ProseMirror/plugins/listPlugin.js
|
||||
packages/editor/ProseMirror/plugins/originalMarkupPlugin.js
|
||||
packages/editor/ProseMirror/plugins/resourcePlaceholderPlugin.js
|
||||
packages/editor/ProseMirror/plugins/searchPlugin.js
|
||||
packages/editor/ProseMirror/plugins/tablePlugin.js
|
||||
packages/editor/ProseMirror/plugins/utils/createExternalEditorPlugin.js
|
||||
packages/editor/ProseMirror/plugins/utils/createFloatingButtonPlugin.js
|
||||
packages/editor/ProseMirror/schema.js
|
||||
packages/editor/ProseMirror/styles.js
|
||||
packages/editor/ProseMirror/testing/createTestEditor.js
|
||||
packages/editor/ProseMirror/testing/createTestEditorWithSerializer.js
|
||||
packages/editor/ProseMirror/types.js
|
||||
packages/editor/ProseMirror/utils/SelectableNodeView.js
|
||||
packages/editor/ProseMirror/utils/UndoStackSynchronizer.js
|
||||
packages/editor/ProseMirror/utils/canReplaceSelectionWith.js
|
||||
packages/editor/ProseMirror/utils/computeSelectionFormatting.js
|
||||
packages/editor/ProseMirror/utils/dom/createButton.js
|
||||
packages/editor/ProseMirror/utils/dom/createTextArea.js
|
||||
packages/editor/ProseMirror/utils/dom/createTextNode.js
|
||||
packages/editor/ProseMirror/utils/dom/createUniqueId.js
|
||||
packages/editor/ProseMirror/utils/dom/showModal.js
|
||||
packages/editor/ProseMirror/utils/extractSelectedLinesTo.test.js
|
||||
packages/editor/ProseMirror/utils/extractSelectedLinesTo.js
|
||||
packages/editor/ProseMirror/utils/forEachHeading.js
|
||||
packages/editor/ProseMirror/utils/jumpToHash.js
|
||||
packages/editor/ProseMirror/utils/makeLinksClickableInElement.js
|
||||
packages/editor/ProseMirror/utils/postprocessEditorOutput.test.js
|
||||
packages/editor/ProseMirror/utils/postprocessEditorOutput.js
|
||||
packages/editor/ProseMirror/utils/preprocessEditorInput.test.js
|
||||
packages/editor/ProseMirror/utils/preprocessEditorInput.js
|
||||
packages/editor/ProseMirror/utils/sanitizeHtml.js
|
||||
packages/editor/ProseMirror/utils/selectFirstInstanceOfNode.js
|
||||
packages/editor/ProseMirror/utils/trimEmptyParagraphs.js
|
||||
packages/editor/ProseMirror/vendor/changedDescendants.js
|
||||
packages/editor/ProseMirror/vendor/icons/addColumnRight.js
|
||||
packages/editor/ProseMirror/vendor/icons/addRowBelow.js
|
||||
packages/editor/ProseMirror/vendor/icons/icon.js
|
||||
packages/editor/ProseMirror/vendor/icons/removeColumn.js
|
||||
packages/editor/ProseMirror/vendor/icons/removeRow.js
|
||||
packages/editor/ProseMirror/vendor/icons/types.js
|
||||
packages/editor/ProseMirror/vendor/splitBlockAs.js
|
||||
packages/editor/SelectionFormatting.js
|
||||
packages/editor/events.js
|
||||
@@ -1381,20 +1428,26 @@ packages/lib/services/database/migrations/45.js
|
||||
packages/lib/services/database/migrations/46.js
|
||||
packages/lib/services/database/migrations/47.js
|
||||
packages/lib/services/database/migrations/48.js
|
||||
packages/lib/services/database/migrations/49.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
|
||||
packages/lib/services/e2ee/EncryptionService.js
|
||||
packages/lib/services/e2ee/RSA.node.js
|
||||
packages/lib/services/e2ee/crypto.test.js
|
||||
packages/lib/services/e2ee/crypto.js
|
||||
packages/lib/services/e2ee/cryptoShared.js
|
||||
packages/lib/services/e2ee/cryptoTestUtils.js
|
||||
packages/lib/services/e2ee/ppk.test.js
|
||||
packages/lib/services/e2ee/ppk.js
|
||||
packages/lib/services/e2ee/ppkTestUtils.js
|
||||
packages/lib/services/e2ee/ppk/RSA.node.js
|
||||
packages/lib/services/e2ee/ppk/ppk.test.js
|
||||
packages/lib/services/e2ee/ppk/ppk.js
|
||||
packages/lib/services/e2ee/ppk/ppkTestUtils.js
|
||||
packages/lib/services/e2ee/ppk/webCrypto/LongDataWrapper.js
|
||||
packages/lib/services/e2ee/ppk/webCrypto/StringToBufferWrapper.js
|
||||
packages/lib/services/e2ee/ppk/webCrypto/WebCryptoRsa.js
|
||||
packages/lib/services/e2ee/ppk/webCrypto/buildRsaCryptoProvider.test.js
|
||||
packages/lib/services/e2ee/ppk/webCrypto/buildRsaCryptoProvider.js
|
||||
packages/lib/services/e2ee/types.js
|
||||
packages/lib/services/e2ee/utils.test.js
|
||||
packages/lib/services/e2ee/utils.js
|
||||
@@ -1499,6 +1552,7 @@ packages/lib/services/plugins/utils/createViewHandle.js
|
||||
packages/lib/services/plugins/utils/executeSandboxCall.js
|
||||
packages/lib/services/plugins/utils/getActivePluginEditorView.js
|
||||
packages/lib/services/plugins/utils/getActivePluginEditorViews.js
|
||||
packages/lib/services/plugins/utils/getPluginHelpUrl.js
|
||||
packages/lib/services/plugins/utils/getPluginIssueReportUrl.test.js
|
||||
packages/lib/services/plugins/utils/getPluginIssueReportUrl.js
|
||||
packages/lib/services/plugins/utils/getPluginNamespacedSettingKey.js
|
||||
@@ -1590,6 +1644,7 @@ packages/lib/services/synchronizer/Synchronizer.sharing.test.js
|
||||
packages/lib/services/synchronizer/Synchronizer.tags.test.js
|
||||
packages/lib/services/synchronizer/Synchronizer.tools.test.js
|
||||
packages/lib/services/synchronizer/gui/useSyncTargetUpgrade.js
|
||||
packages/lib/services/synchronizer/handleConflictAction.test.js
|
||||
packages/lib/services/synchronizer/migrations/1.js
|
||||
packages/lib/services/synchronizer/migrations/2.js
|
||||
packages/lib/services/synchronizer/migrations/3.js
|
||||
@@ -1703,6 +1758,7 @@ packages/plugin-repo-cli/lib/gitCompareUrl.test.js
|
||||
packages/plugin-repo-cli/lib/gitCompareUrl.js
|
||||
packages/plugin-repo-cli/lib/overrideUtils.test.js
|
||||
packages/plugin-repo-cli/lib/overrideUtils.js
|
||||
packages/plugin-repo-cli/lib/searchPlugins.js
|
||||
packages/plugin-repo-cli/lib/types.js
|
||||
packages/plugin-repo-cli/lib/updateReadme.test.js
|
||||
packages/plugin-repo-cli/lib/updateReadme.js
|
||||
@@ -1765,6 +1821,7 @@ packages/tools/fuzzer/Client.js
|
||||
packages/tools/fuzzer/ClientPool.js
|
||||
packages/tools/fuzzer/Server.js
|
||||
packages/tools/fuzzer/constants.js
|
||||
packages/tools/fuzzer/model/FolderRecord.js
|
||||
packages/tools/fuzzer/sync-fuzzer.js
|
||||
packages/tools/fuzzer/types.js
|
||||
packages/tools/fuzzer/utils/SeededRandom.js
|
||||
@@ -1817,6 +1874,8 @@ packages/tools/updateMarkdownDoc.js
|
||||
packages/tools/utils/discourse.test.js
|
||||
packages/tools/utils/discourse.js
|
||||
packages/tools/utils/loadSponsors.js
|
||||
packages/tools/utils/parsePluralLocalizationForm.js
|
||||
packages/tools/utils/parsePlurallLocalizationForm.test.js
|
||||
packages/tools/utils/translation.js
|
||||
packages/tools/validateFilenames.js
|
||||
packages/tools/website/build.js
|
||||
|
||||
27
.github/workflows/build-android.yml
vendored
@@ -40,4 +40,29 @@ jobs:
|
||||
cd packages/app-mobile/android
|
||||
sed -i -- 's/signingConfig signingConfigs.release/signingConfig signingConfigs.debug/' app/build.gradle
|
||||
./gradlew assembleRelease
|
||||
|
||||
|
||||
- name: Verify alignment
|
||||
run: |
|
||||
cd packages/app-mobile/android/app
|
||||
APK_FILE="./build/outputs/apk/release/app-release.apk"
|
||||
if test ! -f "$APK_FILE" ; then
|
||||
echo "APK file not found."
|
||||
exit 1
|
||||
else
|
||||
echo "APK file found at: $APK_FILE"
|
||||
fi
|
||||
|
||||
BUILD_TOOLS_PATH="$ANDROID_HOME/build-tools/"
|
||||
if test ! -d "$BUILD_TOOLS_PATH" ; then
|
||||
echo "Build tools not found at $BUILD_TOOLS_PATH ($ANDROID_HOME, $BUILD_TOOLS_VERSION)"
|
||||
exit 1
|
||||
fi
|
||||
# The build-tools/ directory contains different subdirectories
|
||||
# for each build tools version. As a result, there may be multiple
|
||||
# zipalign tools. Select the most recent (biggest two-digit version number):
|
||||
ZIPALIGN_PATH="$(find $BUILD_TOOLS_PATH -name "zipalign" -print | sort | tail -n1)"
|
||||
if test ! -x "$ZIPALIGN_PATH" ; then
|
||||
echo "zipalign not found (searching in $BUILD_TOOLS_PATH, candidate: $ZIPALIGN_PATH)"
|
||||
exit 1
|
||||
fi
|
||||
"$ZIPALIGN_PATH" -c -P 16 -v 4 "$APK_FILE"
|
||||
|
||||
50
.github/workflows/github-actions-main.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
matrix:
|
||||
# Do not use unbuntu-latest because it causes `The operation was canceled` failures:
|
||||
# https://github.com/actions/runner-images/issues/6709
|
||||
os: [macos-13, ubuntu-22.04, windows-2025, ubuntu-22.04-arm]
|
||||
os: [macos-15-intel, ubuntu-22.04, windows-2025, ubuntu-22.04-arm]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -31,6 +31,16 @@ jobs:
|
||||
sudo apt-get update || true
|
||||
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin
|
||||
|
||||
- name: Free disk space
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo rm -rf /usr/local/lib/android || true
|
||||
sudo rm -rf /usr/share/dotnet || true
|
||||
sudo rm -rf /opt/ghc || true
|
||||
docker system prune -af || true
|
||||
docker builder prune -af || true
|
||||
sudo rm -rf /var/lib/docker/tmp/* || true
|
||||
|
||||
# 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
|
||||
@@ -40,6 +50,22 @@ jobs:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
# - name: Test Windows app signing
|
||||
# if: runner.os == 'Windows'
|
||||
# env:
|
||||
# GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
# IS_CONTINUOUS_INTEGRATION: 1
|
||||
# BUILD_SEQUENCIAL: 1
|
||||
# SSL_ESIGNER_USER_NAME: ${{ secrets.SSL_ESIGNER_USER_NAME }}
|
||||
# SSL_ESIGNER_USER_PASSWORD: ${{ secrets.SSL_ESIGNER_USER_PASSWORD }}
|
||||
# SSL_ESIGNER_CREDENTIAL_ID: ${{ secrets.SSL_ESIGNER_CREDENTIAL_ID }}
|
||||
# SSL_ESIGNER_USER_TOTP: ${{ secrets.SSL_ESIGNER_USER_TOTP }}
|
||||
# SIGN_APPLICATION: 1
|
||||
# # To ensure that the operations stop on failure, all commands
|
||||
# # should be on one line with "&&" in between.
|
||||
# run: |
|
||||
# yarn install && cd packages/app-desktop && yarn dist
|
||||
|
||||
- name: Run tests, build and publish Linux and macOS apps
|
||||
if: runner.os == 'Linux' || runner.os == 'macOs'
|
||||
env:
|
||||
@@ -61,11 +87,14 @@ jobs:
|
||||
- name: Build and publish Windows app
|
||||
if: runner.os == 'Windows' && startsWith(github.ref, 'refs/tags/v')
|
||||
env:
|
||||
CSC_KEY_PASSWORD: ${{ secrets.WINDOWS_CSC_KEY_PASSWORD }}
|
||||
CSC_LINK: ${{ secrets.WINDOWS_CSC_LINK }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
IS_CONTINUOUS_INTEGRATION: 1
|
||||
BUILD_SEQUENCIAL: 1
|
||||
SSL_ESIGNER_USER_NAME: ${{ secrets.SSL_ESIGNER_USER_NAME }}
|
||||
SSL_ESIGNER_USER_PASSWORD: ${{ secrets.SSL_ESIGNER_USER_PASSWORD }}
|
||||
SSL_ESIGNER_CREDENTIAL_ID: ${{ secrets.SSL_ESIGNER_CREDENTIAL_ID }}
|
||||
SSL_ESIGNER_USER_TOTP: ${{ secrets.SSL_ESIGNER_USER_TOTP }}
|
||||
SIGN_APPLICATION: 1
|
||||
# To ensure that the operations stop on failure, all commands
|
||||
# should be on one line with "&&" in between.
|
||||
run: |
|
||||
@@ -122,6 +151,16 @@ jobs:
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: Free disk space
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo rm -rf /usr/local/lib/android || true
|
||||
sudo rm -rf /usr/share/dotnet || true
|
||||
sudo rm -rf /opt/ghc || true
|
||||
docker system prune -af || true
|
||||
docker builder prune -af || true
|
||||
sudo rm -rf /var/lib/docker/tmp/* || true
|
||||
|
||||
- name: Install Yarn
|
||||
run: |
|
||||
# https://yarnpkg.com/getting-started/install
|
||||
@@ -149,7 +188,7 @@ jobs:
|
||||
- name: Check HTTP request
|
||||
run: |
|
||||
# Need to pass environment variables:
|
||||
docker run -p 22300:22300 joplin/server:$(dpkg --print-architecture)-0.0.0 node dist/app.js --env dev &
|
||||
docker run --env MAX_TIME_DRIFT=0 --publish 22300:22300 joplin/server:$(dpkg --print-architecture)-0.0.0 node dist/app.js --env dev &
|
||||
|
||||
# Wait for server to start
|
||||
sleep 120
|
||||
@@ -175,5 +214,4 @@ jobs:
|
||||
if [[ "$actual_body" != "$expected_body" ]]; then
|
||||
echo 'Failed while checking the body response after request to /api/ping'
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
fi
|
||||
81
.gitignore
vendored
@@ -69,6 +69,7 @@ docs/**/*.mustache
|
||||
packages/app-cli/app/LinkSelector.js
|
||||
packages/app-cli/app/app.js
|
||||
packages/app-cli/app/base-command.js
|
||||
packages/app-cli/app/cli-integration-tests.js
|
||||
packages/app-cli/app/command-apidoc.js
|
||||
packages/app-cli/app/command-attach.js
|
||||
packages/app-cli/app/command-batch.js
|
||||
@@ -90,6 +91,8 @@ 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-publish.test.js
|
||||
packages/app-cli/app/command-publish.js
|
||||
packages/app-cli/app/command-ren.js
|
||||
packages/app-cli/app/command-restore.js
|
||||
packages/app-cli/app/command-rmbook.test.js
|
||||
@@ -102,6 +105,8 @@ packages/app-cli/app/command-share.test.js
|
||||
packages/app-cli/app/command-share.js
|
||||
packages/app-cli/app/command-sync.js
|
||||
packages/app-cli/app/command-testing.js
|
||||
packages/app-cli/app/command-unpublish.test.js
|
||||
packages/app-cli/app/command-unpublish.js
|
||||
packages/app-cli/app/command-use.js
|
||||
packages/app-cli/app/command-version.js
|
||||
packages/app-cli/app/gui/FolderListWidget.js
|
||||
@@ -238,6 +243,7 @@ packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/useEditorCommands.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/useKeymap.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/useRefocusOnVisiblePaneChange.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/useSyncEditorValue.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/PlainEditor/PlainEditor.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/styles/index.js
|
||||
@@ -248,6 +254,7 @@ packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/shouldPasteResources.
|
||||
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/useCursorPositioning.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useEditDialog.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useEditDialogEventListeners.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useKeyboardRefocusHandler.js
|
||||
@@ -289,6 +296,7 @@ packages/app-desktop/gui/NoteEditor/utils/useEffectiveNoteId.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/useFolder.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/useFormNote.test.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/useFormNote.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/useInitialCursorLocation.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/useMessageHandler.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/useNoteSearchBar.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/usePluginEditorView.test.js
|
||||
@@ -572,6 +580,7 @@ packages/app-desktop/tools/generateLatestArm64Yml.js
|
||||
packages/app-desktop/tools/githubReleasesUtils.js
|
||||
packages/app-desktop/tools/modifyReleaseAssets.js
|
||||
packages/app-desktop/tools/notarizeMacApp.js
|
||||
packages/app-desktop/tools/resolveSourceMap.js
|
||||
packages/app-desktop/utils/7zip/getPathToExecutable7Zip.js
|
||||
packages/app-desktop/utils/7zip/pathToBundled7Zip.js
|
||||
packages/app-desktop/utils/checkForUpdatesUtils.test.js
|
||||
@@ -645,6 +654,8 @@ packages/app-mobile/components/ExtendedWebView/index.js
|
||||
packages/app-mobile/components/ExtendedWebView/index.web.js
|
||||
packages/app-mobile/components/ExtendedWebView/types.js
|
||||
packages/app-mobile/components/ExtendedWebView/utils/useCss.js
|
||||
packages/app-mobile/components/FeedbackBanner.test.js
|
||||
packages/app-mobile/components/FeedbackBanner.js
|
||||
packages/app-mobile/components/FolderPicker.js
|
||||
packages/app-mobile/components/Icon.js
|
||||
packages/app-mobile/components/IconButton.js
|
||||
@@ -663,6 +674,7 @@ packages/app-mobile/components/NoteEditor/ImageEditor/ImageEditor.js
|
||||
packages/app-mobile/components/NoteEditor/ImageEditor/autosave.js
|
||||
packages/app-mobile/components/NoteEditor/ImageEditor/isEditableResource.js
|
||||
packages/app-mobile/components/NoteEditor/ImageEditor/promptRestoreAutosave.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownEditor.test.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownEditor.js
|
||||
packages/app-mobile/components/NoteEditor/NoteEditor.test.js
|
||||
packages/app-mobile/components/NoteEditor/NoteEditor.js
|
||||
@@ -671,7 +683,6 @@ packages/app-mobile/components/NoteEditor/RichTextEditor.js
|
||||
packages/app-mobile/components/NoteEditor/SearchPanel.js
|
||||
packages/app-mobile/components/NoteEditor/WarningBanner.js
|
||||
packages/app-mobile/components/NoteEditor/commandDeclarations.js
|
||||
packages/app-mobile/components/NoteEditor/hooks/useCodeMirrorPlugins.js
|
||||
packages/app-mobile/components/NoteEditor/hooks/useEditorCommandHandler.test.js
|
||||
packages/app-mobile/components/NoteEditor/hooks/useEditorCommandHandler.js
|
||||
packages/app-mobile/components/NoteEditor/testing/createTestEditorProps.js
|
||||
@@ -692,6 +703,8 @@ packages/app-mobile/components/SearchInput.js
|
||||
packages/app-mobile/components/SelectDateTimeDialog.js
|
||||
packages/app-mobile/components/SideMenu.js
|
||||
packages/app-mobile/components/SideMenuContentNote.js
|
||||
packages/app-mobile/components/SyncWizard/JoplinCloudIcon.js
|
||||
packages/app-mobile/components/SyncWizard/SyncWizard.js
|
||||
packages/app-mobile/components/TagEditor.test.js
|
||||
packages/app-mobile/components/TagEditor.js
|
||||
packages/app-mobile/components/TextInput.js
|
||||
@@ -708,6 +721,7 @@ packages/app-mobile/components/base-screen.js
|
||||
packages/app-mobile/components/biometrics/BiometricPopup.js
|
||||
packages/app-mobile/components/biometrics/biometricAuthenticate.js
|
||||
packages/app-mobile/components/biometrics/sensorInfo.js
|
||||
packages/app-mobile/components/buttons/CardButton.js
|
||||
packages/app-mobile/components/buttons/FloatingActionButton.js
|
||||
packages/app-mobile/components/buttons/LabelledIconButton.js
|
||||
packages/app-mobile/components/buttons/MultiTouchableOpacity.js
|
||||
@@ -783,7 +797,6 @@ packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/InstallButto
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/WrappedPluginStates.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/mockRepositoryApiConstructor.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/newRepoApi.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/pluginServiceSetup.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/utils/openWebsiteForPlugin.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/utils/usePluginCallbacks.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/utils/usePluginItem.js
|
||||
@@ -809,6 +822,7 @@ packages/app-mobile/components/screens/NoteTagsDialog.js
|
||||
packages/app-mobile/components/screens/Notes/NewNoteButton.test.js
|
||||
packages/app-mobile/components/screens/Notes/NewNoteButton.js
|
||||
packages/app-mobile/components/screens/Notes/Notes.js
|
||||
packages/app-mobile/components/screens/SearchScreen/SearchResults.test.js
|
||||
packages/app-mobile/components/screens/SearchScreen/SearchResults.js
|
||||
packages/app-mobile/components/screens/SearchScreen/index.js
|
||||
packages/app-mobile/components/screens/ShareManager/AcceptedShareItem.js
|
||||
@@ -841,6 +855,7 @@ packages/app-mobile/contentScripts/imageEditorBundle/utils/useEditorMessenger.js
|
||||
packages/app-mobile/contentScripts/markdownEditorBundle/contentScript.js
|
||||
packages/app-mobile/contentScripts/markdownEditorBundle/types.js
|
||||
packages/app-mobile/contentScripts/markdownEditorBundle/useWebViewSetup.js
|
||||
packages/app-mobile/contentScripts/markdownEditorBundle/utils/useCodeMirrorPlugins.js
|
||||
packages/app-mobile/contentScripts/rendererBundle/contentScript/Renderer.test.js
|
||||
packages/app-mobile/contentScripts/rendererBundle/contentScript/Renderer.js
|
||||
packages/app-mobile/contentScripts/rendererBundle/contentScript/index.js
|
||||
@@ -870,14 +885,13 @@ packages/app-mobile/services/AlarmServiceDriver.web.js
|
||||
packages/app-mobile/services/BackButtonService.js
|
||||
packages/app-mobile/services/commands/stateToWhenClauseContext.js
|
||||
packages/app-mobile/services/e2ee/RSA.react-native.js
|
||||
packages/app-mobile/services/e2ee/RSA.react-native.web.js
|
||||
packages/app-mobile/services/e2ee/crypto.js
|
||||
packages/app-mobile/services/plugins/PlatformImplementation.js
|
||||
packages/app-mobile/services/profiles/index.js
|
||||
packages/app-mobile/services/voiceTyping/VoiceTyping.js
|
||||
packages/app-mobile/services/voiceTyping/utils/unzip.android.js
|
||||
packages/app-mobile/services/voiceTyping/utils/unzip.js
|
||||
packages/app-mobile/services/voiceTyping/vosk.android.js
|
||||
packages/app-mobile/services/voiceTyping/vosk.js
|
||||
packages/app-mobile/services/voiceTyping/whisper.test.js
|
||||
packages/app-mobile/services/voiceTyping/whisper.js
|
||||
packages/app-mobile/setupQuickActions.js
|
||||
@@ -891,6 +905,7 @@ packages/app-mobile/utils/ShareUtils.test.js
|
||||
packages/app-mobile/utils/ShareUtils.js
|
||||
packages/app-mobile/utils/TlsUtils.js
|
||||
packages/app-mobile/utils/appDefaultState.js
|
||||
packages/app-mobile/utils/appReducer.js
|
||||
packages/app-mobile/utils/autodetectTheme.js
|
||||
packages/app-mobile/utils/buildStartupTasks.js
|
||||
packages/app-mobile/utils/checkPermissions.js
|
||||
@@ -912,6 +927,7 @@ packages/app-mobile/utils/fs-driver/testUtil/verifyDirectoryMatches.js
|
||||
packages/app-mobile/utils/getPackageInfo.js
|
||||
packages/app-mobile/utils/getVersionInfoText.js
|
||||
packages/app-mobile/utils/hooks/useBackHandler.js
|
||||
packages/app-mobile/utils/hooks/useIsScreenReaderEnabled.js
|
||||
packages/app-mobile/utils/hooks/useKeyboardState.js
|
||||
packages/app-mobile/utils/hooks/useOnLongPressProps.js
|
||||
packages/app-mobile/utils/hooks/useReduceMotionEnabled.js
|
||||
@@ -929,6 +945,7 @@ packages/app-mobile/utils/pickDocument.js
|
||||
packages/app-mobile/utils/polyfills/bufferPolyfill.js
|
||||
packages/app-mobile/utils/polyfills/crypto-polyfill/index.js
|
||||
packages/app-mobile/utils/polyfills/index.js
|
||||
packages/app-mobile/utils/polyfills/index.web.js
|
||||
packages/app-mobile/utils/setupNotifications.js
|
||||
packages/app-mobile/utils/shareFile.js
|
||||
packages/app-mobile/utils/shareHandler.js
|
||||
@@ -939,6 +956,7 @@ packages/app-mobile/utils/shim-init-react/shimInitShared.js
|
||||
packages/app-mobile/utils/testing/createMockReduxStore.js
|
||||
packages/app-mobile/utils/testing/getWebViewDomById.js
|
||||
packages/app-mobile/utils/testing/getWebViewWindowById.js
|
||||
packages/app-mobile/utils/testing/mockPluginServiceSetup.js
|
||||
packages/app-mobile/utils/testing/setupGlobalStore.js
|
||||
packages/app-mobile/utils/testing/testingLibrary.js
|
||||
packages/app-mobile/utils/types.js
|
||||
@@ -977,6 +995,9 @@ packages/editor/CodeMirror/editorCommands/sortSelectedLines.test.js
|
||||
packages/editor/CodeMirror/editorCommands/sortSelectedLines.js
|
||||
packages/editor/CodeMirror/editorCommands/supportsCommand.js
|
||||
packages/editor/CodeMirror/extensions/biDirectionalTextExtension.js
|
||||
packages/editor/CodeMirror/extensions/ctrlClickActionExtension.js
|
||||
packages/editor/CodeMirror/extensions/ctrlClickCheckboxExtension.js
|
||||
packages/editor/CodeMirror/extensions/highlightActiveLineExtension.js
|
||||
packages/editor/CodeMirror/extensions/keyUpHandlerExtension.js
|
||||
packages/editor/CodeMirror/extensions/links/ctrlClickLinksExtension.js
|
||||
packages/editor/CodeMirror/extensions/links/followLinkTooltipExtension.test.js
|
||||
@@ -1007,6 +1028,7 @@ packages/editor/CodeMirror/extensions/rendering/types.js
|
||||
packages/editor/CodeMirror/extensions/rendering/utils/makeBlockReplaceExtension.js
|
||||
packages/editor/CodeMirror/extensions/rendering/utils/makeInlineReplaceExtension.js
|
||||
packages/editor/CodeMirror/extensions/rendering/utils/nodeIntersectsSelection.js
|
||||
packages/editor/CodeMirror/extensions/searchExtension.test.js
|
||||
packages/editor/CodeMirror/extensions/searchExtension.js
|
||||
packages/editor/CodeMirror/extensions/selectedNoteIdExtension.js
|
||||
packages/editor/CodeMirror/getScrollFraction.js
|
||||
@@ -1047,14 +1069,22 @@ packages/editor/CodeMirror/utils/isInSyntaxNode.js
|
||||
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/allLanguages.js
|
||||
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/defaultLanguage.js
|
||||
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/lookUpLanguage.js
|
||||
packages/editor/CodeMirror/utils/markdown/getCheckboxAtPosition.js
|
||||
packages/editor/CodeMirror/utils/markdown/renumberSelectedLists.test.js
|
||||
packages/editor/CodeMirror/utils/markdown/renumberSelectedLists.js
|
||||
packages/editor/CodeMirror/utils/markdown/stripBlockquote.js
|
||||
packages/editor/CodeMirror/utils/markdown/toggleCheckboxAt.js
|
||||
packages/editor/CodeMirror/utils/setupVim.js
|
||||
packages/editor/ProseMirror/commands.test.js
|
||||
packages/editor/ProseMirror/commands.js
|
||||
packages/editor/CodeMirror/vendor/announceSearchMatch.js
|
||||
packages/editor/ProseMirror/commands/commands.test.js
|
||||
packages/editor/ProseMirror/commands/commands.js
|
||||
packages/editor/ProseMirror/commands/focusEditor.js
|
||||
packages/editor/ProseMirror/createEditor.js
|
||||
packages/editor/ProseMirror/index.js
|
||||
packages/editor/ProseMirror/plugins/detailsPlugin.test.js
|
||||
packages/editor/ProseMirror/plugins/detailsPlugin.js
|
||||
packages/editor/ProseMirror/plugins/imagePlugin.test.js
|
||||
packages/editor/ProseMirror/plugins/imagePlugin.js
|
||||
packages/editor/ProseMirror/plugins/inputRulesPlugin.js
|
||||
packages/editor/ProseMirror/plugins/joplinEditablePlugin/createEditorDialog.js
|
||||
packages/editor/ProseMirror/plugins/joplinEditablePlugin/joplinEditablePlugin.test.js
|
||||
@@ -1066,26 +1096,43 @@ packages/editor/ProseMirror/plugins/linkTooltipPlugin.test.js
|
||||
packages/editor/ProseMirror/plugins/linkTooltipPlugin.js
|
||||
packages/editor/ProseMirror/plugins/listPlugin.js
|
||||
packages/editor/ProseMirror/plugins/originalMarkupPlugin.js
|
||||
packages/editor/ProseMirror/plugins/resourcePlaceholderPlugin.js
|
||||
packages/editor/ProseMirror/plugins/searchPlugin.js
|
||||
packages/editor/ProseMirror/plugins/tablePlugin.js
|
||||
packages/editor/ProseMirror/plugins/utils/createExternalEditorPlugin.js
|
||||
packages/editor/ProseMirror/plugins/utils/createFloatingButtonPlugin.js
|
||||
packages/editor/ProseMirror/schema.js
|
||||
packages/editor/ProseMirror/styles.js
|
||||
packages/editor/ProseMirror/testing/createTestEditor.js
|
||||
packages/editor/ProseMirror/testing/createTestEditorWithSerializer.js
|
||||
packages/editor/ProseMirror/types.js
|
||||
packages/editor/ProseMirror/utils/SelectableNodeView.js
|
||||
packages/editor/ProseMirror/utils/UndoStackSynchronizer.js
|
||||
packages/editor/ProseMirror/utils/canReplaceSelectionWith.js
|
||||
packages/editor/ProseMirror/utils/computeSelectionFormatting.js
|
||||
packages/editor/ProseMirror/utils/dom/createButton.js
|
||||
packages/editor/ProseMirror/utils/dom/createTextArea.js
|
||||
packages/editor/ProseMirror/utils/dom/createTextNode.js
|
||||
packages/editor/ProseMirror/utils/dom/createUniqueId.js
|
||||
packages/editor/ProseMirror/utils/dom/showModal.js
|
||||
packages/editor/ProseMirror/utils/extractSelectedLinesTo.test.js
|
||||
packages/editor/ProseMirror/utils/extractSelectedLinesTo.js
|
||||
packages/editor/ProseMirror/utils/forEachHeading.js
|
||||
packages/editor/ProseMirror/utils/jumpToHash.js
|
||||
packages/editor/ProseMirror/utils/makeLinksClickableInElement.js
|
||||
packages/editor/ProseMirror/utils/postprocessEditorOutput.test.js
|
||||
packages/editor/ProseMirror/utils/postprocessEditorOutput.js
|
||||
packages/editor/ProseMirror/utils/preprocessEditorInput.test.js
|
||||
packages/editor/ProseMirror/utils/preprocessEditorInput.js
|
||||
packages/editor/ProseMirror/utils/sanitizeHtml.js
|
||||
packages/editor/ProseMirror/utils/selectFirstInstanceOfNode.js
|
||||
packages/editor/ProseMirror/utils/trimEmptyParagraphs.js
|
||||
packages/editor/ProseMirror/vendor/changedDescendants.js
|
||||
packages/editor/ProseMirror/vendor/icons/addColumnRight.js
|
||||
packages/editor/ProseMirror/vendor/icons/addRowBelow.js
|
||||
packages/editor/ProseMirror/vendor/icons/icon.js
|
||||
packages/editor/ProseMirror/vendor/icons/removeColumn.js
|
||||
packages/editor/ProseMirror/vendor/icons/removeRow.js
|
||||
packages/editor/ProseMirror/vendor/icons/types.js
|
||||
packages/editor/ProseMirror/vendor/splitBlockAs.js
|
||||
packages/editor/SelectionFormatting.js
|
||||
packages/editor/events.js
|
||||
@@ -1354,20 +1401,26 @@ packages/lib/services/database/migrations/45.js
|
||||
packages/lib/services/database/migrations/46.js
|
||||
packages/lib/services/database/migrations/47.js
|
||||
packages/lib/services/database/migrations/48.js
|
||||
packages/lib/services/database/migrations/49.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
|
||||
packages/lib/services/e2ee/EncryptionService.js
|
||||
packages/lib/services/e2ee/RSA.node.js
|
||||
packages/lib/services/e2ee/crypto.test.js
|
||||
packages/lib/services/e2ee/crypto.js
|
||||
packages/lib/services/e2ee/cryptoShared.js
|
||||
packages/lib/services/e2ee/cryptoTestUtils.js
|
||||
packages/lib/services/e2ee/ppk.test.js
|
||||
packages/lib/services/e2ee/ppk.js
|
||||
packages/lib/services/e2ee/ppkTestUtils.js
|
||||
packages/lib/services/e2ee/ppk/RSA.node.js
|
||||
packages/lib/services/e2ee/ppk/ppk.test.js
|
||||
packages/lib/services/e2ee/ppk/ppk.js
|
||||
packages/lib/services/e2ee/ppk/ppkTestUtils.js
|
||||
packages/lib/services/e2ee/ppk/webCrypto/LongDataWrapper.js
|
||||
packages/lib/services/e2ee/ppk/webCrypto/StringToBufferWrapper.js
|
||||
packages/lib/services/e2ee/ppk/webCrypto/WebCryptoRsa.js
|
||||
packages/lib/services/e2ee/ppk/webCrypto/buildRsaCryptoProvider.test.js
|
||||
packages/lib/services/e2ee/ppk/webCrypto/buildRsaCryptoProvider.js
|
||||
packages/lib/services/e2ee/types.js
|
||||
packages/lib/services/e2ee/utils.test.js
|
||||
packages/lib/services/e2ee/utils.js
|
||||
@@ -1472,6 +1525,7 @@ packages/lib/services/plugins/utils/createViewHandle.js
|
||||
packages/lib/services/plugins/utils/executeSandboxCall.js
|
||||
packages/lib/services/plugins/utils/getActivePluginEditorView.js
|
||||
packages/lib/services/plugins/utils/getActivePluginEditorViews.js
|
||||
packages/lib/services/plugins/utils/getPluginHelpUrl.js
|
||||
packages/lib/services/plugins/utils/getPluginIssueReportUrl.test.js
|
||||
packages/lib/services/plugins/utils/getPluginIssueReportUrl.js
|
||||
packages/lib/services/plugins/utils/getPluginNamespacedSettingKey.js
|
||||
@@ -1563,6 +1617,7 @@ packages/lib/services/synchronizer/Synchronizer.sharing.test.js
|
||||
packages/lib/services/synchronizer/Synchronizer.tags.test.js
|
||||
packages/lib/services/synchronizer/Synchronizer.tools.test.js
|
||||
packages/lib/services/synchronizer/gui/useSyncTargetUpgrade.js
|
||||
packages/lib/services/synchronizer/handleConflictAction.test.js
|
||||
packages/lib/services/synchronizer/migrations/1.js
|
||||
packages/lib/services/synchronizer/migrations/2.js
|
||||
packages/lib/services/synchronizer/migrations/3.js
|
||||
@@ -1676,6 +1731,7 @@ packages/plugin-repo-cli/lib/gitCompareUrl.test.js
|
||||
packages/plugin-repo-cli/lib/gitCompareUrl.js
|
||||
packages/plugin-repo-cli/lib/overrideUtils.test.js
|
||||
packages/plugin-repo-cli/lib/overrideUtils.js
|
||||
packages/plugin-repo-cli/lib/searchPlugins.js
|
||||
packages/plugin-repo-cli/lib/types.js
|
||||
packages/plugin-repo-cli/lib/updateReadme.test.js
|
||||
packages/plugin-repo-cli/lib/updateReadme.js
|
||||
@@ -1738,6 +1794,7 @@ packages/tools/fuzzer/Client.js
|
||||
packages/tools/fuzzer/ClientPool.js
|
||||
packages/tools/fuzzer/Server.js
|
||||
packages/tools/fuzzer/constants.js
|
||||
packages/tools/fuzzer/model/FolderRecord.js
|
||||
packages/tools/fuzzer/sync-fuzzer.js
|
||||
packages/tools/fuzzer/types.js
|
||||
packages/tools/fuzzer/utils/SeededRandom.js
|
||||
@@ -1790,6 +1847,8 @@ packages/tools/updateMarkdownDoc.js
|
||||
packages/tools/utils/discourse.test.js
|
||||
packages/tools/utils/discourse.js
|
||||
packages/tools/utils/loadSponsors.js
|
||||
packages/tools/utils/parsePluralLocalizationForm.js
|
||||
packages/tools/utils/parsePlurallLocalizationForm.test.js
|
||||
packages/tools/utils/translation.js
|
||||
packages/tools/validateFilenames.js
|
||||
packages/tools/website/build.js
|
||||
|
||||
36
.yarn/patches/depd-npm-1.1.2-b0c8414da7.patch
Normal file
@@ -0,0 +1,36 @@
|
||||
# Patch to remove eval. This allows using depd in an environment with
|
||||
# a strict Content-Security-Policy.
|
||||
# Ref: https://github.com/dougwilson/nodejs-depd/pull/33
|
||||
diff --git a/index.js b/index.js
|
||||
index d758d3c8f58a60bf27ef377ad77639bf10ce7854..2bad40d4eeba553d3bcfb206873eac059067ae3b 100644
|
||||
--- a/index.js
|
||||
+++ b/index.js
|
||||
@@ -399,19 +399,20 @@ function wrapfunction (fn, message) {
|
||||
throw new TypeError('argument fn must be a function')
|
||||
}
|
||||
|
||||
- var args = createArgumentsString(fn.length)
|
||||
- var deprecate = this // eslint-disable-line no-unused-vars
|
||||
var stack = getStack()
|
||||
var site = callSiteLocation(stack[1])
|
||||
|
||||
site.name = fn.name
|
||||
|
||||
- // eslint-disable-next-line no-eval
|
||||
- var deprecatedfn = eval('(function (' + args + ') {\n' +
|
||||
- '"use strict"\n' +
|
||||
- 'log.call(deprecate, message, site)\n' +
|
||||
- 'return fn.apply(this, arguments)\n' +
|
||||
- '})')
|
||||
+ var deprecatedfn
|
||||
+ var self = this
|
||||
+ deprecatedfn = function () {
|
||||
+ 'use strict'
|
||||
+ log.call(self, message, site)
|
||||
+ return fn.apply(this, arguments)
|
||||
+ }
|
||||
+ Object.defineProperty(deprecatedfn, 'length', { value: fn.length })
|
||||
+ Object.defineProperty(deprecatedfn, 'name', { value: fn.name })
|
||||
|
||||
return deprecatedfn
|
||||
}
|
||||
35
.yarn/patches/depd-npm-2.0.0-b6c51a4b43.patch
Normal file
@@ -0,0 +1,35 @@
|
||||
# Patch to remove eval. This allows using depd in an environment with
|
||||
# a strict Content-Security-Policy.
|
||||
# Ref: https://github.com/dougwilson/nodejs-depd/pull/33
|
||||
diff --git a/index.js b/index.js
|
||||
index 1bf2fcfdeffc984e5ad792eec08744c29d4a4590..1b24aa2414458bc651abfdded81b103c131efeaa 100644
|
||||
--- a/index.js
|
||||
+++ b/index.js
|
||||
@@ -415,19 +415,19 @@ function wrapfunction (fn, message) {
|
||||
throw new TypeError('argument fn must be a function')
|
||||
}
|
||||
|
||||
- var args = createArgumentsString(fn.length)
|
||||
var stack = getStack()
|
||||
var site = callSiteLocation(stack[1])
|
||||
|
||||
site.name = fn.name
|
||||
|
||||
- // eslint-disable-next-line no-new-func
|
||||
- var deprecatedfn = new Function('fn', 'log', 'deprecate', 'message', 'site',
|
||||
- '"use strict"\n' +
|
||||
- 'return function (' + args + ') {' +
|
||||
- 'log.call(deprecate, message, site)\n' +
|
||||
- 'return fn.apply(this, arguments)\n' +
|
||||
- '}')(fn, log, this, message, site)
|
||||
+ var self = this
|
||||
+ var deprecatedfn = function () {
|
||||
+ 'use strict'
|
||||
+ log.call(self, message, site)
|
||||
+ return fn.apply(this, arguments)
|
||||
+ }
|
||||
+ Object.defineProperty(deprecatedfn, 'length', { value: fn.length })
|
||||
+ Object.defineProperty(deprecatedfn, 'name', { value: fn.name })
|
||||
|
||||
return deprecatedfn
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
diff --git a/android/build.gradle b/android/build.gradle
|
||||
index 6afcbbf0cc8ca2d69dd78077d61e59a90b2136bb..9f8d72b4ec5b2b3d290975d6a255917c95300854 100644
|
||||
--- a/android/build.gradle
|
||||
+++ b/android/build.gradle
|
||||
@@ -67,19 +67,19 @@ repositories {
|
||||
}
|
||||
|
||||
// Generate UUIDs for each models contained in android/src/main/assets/
|
||||
-tasks.register('genUUID') {
|
||||
- doLast {
|
||||
- fileTree(dir: "$rootDir/app/src/main/assets", exclude: ['*/*']).visit { fileDetails ->
|
||||
- if (fileDetails.directory) {
|
||||
- def odir = file("$rootDir/app/src/main/assets/$fileDetails.relativePath")
|
||||
- def ofile = file("$odir/uuid")
|
||||
- mkdir odir
|
||||
- ofile.text = UUID.randomUUID().toString()
|
||||
- }
|
||||
- }
|
||||
- }
|
||||
-}
|
||||
-preBuild.dependsOn genUUID
|
||||
+// tasks.register('genUUID') {
|
||||
+// doLast {
|
||||
+// fileTree(dir: "$rootDir/app/src/main/assets", exclude: ['*/*']).visit { fileDetails ->
|
||||
+// if (fileDetails.directory) {
|
||||
+// def odir = file("$rootDir/app/src/main/assets/$fileDetails.relativePath")
|
||||
+// def ofile = file("$odir/uuid")
|
||||
+// mkdir odir
|
||||
+// ofile.text = UUID.randomUUID().toString()
|
||||
+// }
|
||||
+// }
|
||||
+// }
|
||||
+// }
|
||||
+// preBuild.dependsOn genUUID
|
||||
|
||||
def kotlin_version = getExtOrDefault('kotlinVersion')
|
||||
|
||||
diff --git a/android/src/main/java/com/reactnativevosk/VoskModule.kt b/android/src/main/java/com/reactnativevosk/VoskModule.kt
|
||||
index 0e2b6595b1b2cf1ee01c6c64239c4b0ea37fce19..5a8539b9cce8951967640dba755e29a4e3ff404a 100644
|
||||
--- a/android/src/main/java/com/reactnativevosk/VoskModule.kt
|
||||
+++ b/android/src/main/java/com/reactnativevosk/VoskModule.kt
|
||||
@@ -19,13 +19,25 @@ class VoskModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
||||
return "Vosk"
|
||||
}
|
||||
|
||||
+ @ReactMethod
|
||||
+ fun addListener(type: String?) {
|
||||
+ // Keep: Required for RN built in Event Emitter Calls.
|
||||
+ }
|
||||
+
|
||||
+ @ReactMethod
|
||||
+ fun removeListeners(type: Int?) {
|
||||
+ // Keep: Required for RN built in Event Emitter Calls.
|
||||
+ }
|
||||
+
|
||||
override fun onResult(hypothesis: String) {
|
||||
// Get text data from string object
|
||||
val text = getHypothesisText(hypothesis)
|
||||
|
||||
// Stop recording if data found
|
||||
if (text != null && text.isNotEmpty()) {
|
||||
- cleanRecognizer();
|
||||
+ // Don't auto-stop the recogniser - we want to do that when the user
|
||||
+ // presses on "stop" only.
|
||||
+ // cleanRecognizer();
|
||||
sendEvent("onResult", text)
|
||||
}
|
||||
}
|
||||
@@ -93,12 +105,11 @@ class VoskModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
||||
@ReactMethod
|
||||
fun loadModel(path: String, promise: Promise) {
|
||||
cleanModel();
|
||||
- StorageService.unpack(context, path, "models",
|
||||
- { model: Model? ->
|
||||
- this.model = model
|
||||
- promise.resolve("Model successfully loaded")
|
||||
- }
|
||||
- ) { e: IOException ->
|
||||
+
|
||||
+ try {
|
||||
+ this.model = Model(path);
|
||||
+ promise.resolve("Model successfully loaded")
|
||||
+ } catch (e: IOException) {
|
||||
this.model = null
|
||||
promise.reject(e)
|
||||
}
|
||||
@@ -153,6 +164,25 @@ class VoskModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
||||
cleanRecognizer();
|
||||
}
|
||||
|
||||
+ @ReactMethod
|
||||
+ fun stopOnly() {
|
||||
+ if (speechService != null) {
|
||||
+ speechService!!.stop()
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @ReactMethod
|
||||
+ fun cleanup() {
|
||||
+ if (speechService != null) {
|
||||
+ speechService!!.shutdown();
|
||||
+ speechService = null
|
||||
+ }
|
||||
+ if (recognizer != null) {
|
||||
+ recognizer!!.close();
|
||||
+ recognizer = null;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
@ReactMethod
|
||||
fun unload() {
|
||||
cleanRecognizer();
|
||||
diff --git a/lib/typescript/index.d.ts b/lib/typescript/index.d.ts
|
||||
index 441e41cc402cca3a60b34978ef4fea976076259c..a173acebb4b314402550442ad471e0f7c706e3c4 100644
|
||||
--- a/lib/typescript/index.d.ts
|
||||
+++ b/lib/typescript/index.d.ts
|
||||
@@ -10,6 +10,8 @@ export default class Vosk {
|
||||
currentRegisteredEvents: EmitterSubscription[];
|
||||
start: (grammar?: string[] | null) => Promise<String>;
|
||||
stop: () => void;
|
||||
+ stopOnly: () => void;
|
||||
+ cleanup: () => void;
|
||||
unload: () => void;
|
||||
onResult: (onResult: (e: VoskEvent) => void) => EventSubscription;
|
||||
onFinalResult: (onFinalResult: (e: VoskEvent) => void) => EventSubscription;
|
||||
diff --git a/package.json b/package.json
|
||||
index 707eddb8d68007f93071ac659c5b087c935c5f01..90ebe20f224eeec472c377df1fef9b15f2ff8200 100644
|
||||
--- a/package.json
|
||||
+++ b/package.json
|
||||
@@ -11,12 +11,9 @@
|
||||
"src",
|
||||
"lib",
|
||||
"android",
|
||||
- "ios",
|
||||
"cpp",
|
||||
- "react-native-vosk.podspec",
|
||||
"!lib/typescript/example",
|
||||
"!android/build",
|
||||
- "!ios/build",
|
||||
"!**/__tests__",
|
||||
"!**/__fixtures__",
|
||||
"!**/__mocks__"
|
||||
diff --git a/react-native-vosk.podspec b/react-native-vosk.podspec
|
||||
deleted file mode 100644
|
||||
index e3d41b90c5eef890c7a5108aaf16ac07d34a698b..0000000000000000000000000000000000000000
|
||||
--- a/react-native-vosk.podspec
|
||||
+++ /dev/null
|
||||
@@ -1,41 +0,0 @@
|
||||
-require "json"
|
||||
-
|
||||
-package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
||||
-folly_version = '2021.06.28.00-v2'
|
||||
-folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
|
||||
-
|
||||
-Pod::Spec.new do |s|
|
||||
- s.name = "react-native-vosk"
|
||||
- s.version = package["version"]
|
||||
- s.summary = package["description"]
|
||||
- s.homepage = package["homepage"]
|
||||
- s.license = package["license"]
|
||||
- s.authors = package["author"]
|
||||
-
|
||||
- s.platforms = { :ios => "10.0" }
|
||||
- s.source = { :git => "https://github.com/riderodd/react-native-vosk.git", :tag => "#{s.version}" }
|
||||
-
|
||||
- s.source_files = "ios/**/*.{h,m,mm,swift}"
|
||||
- s.resource_bundles = { 'Vosk' => ['ios/Vosk/*'] }
|
||||
-
|
||||
- s.dependency "React-Core"
|
||||
- s.frameworks = "Accelerate"
|
||||
- s.library = "c++"
|
||||
- s.vendored_frameworks = "ios/libvosk.xcframework"
|
||||
- s.requires_arc = true
|
||||
-
|
||||
- # Don't install the dependencies when we run `pod install` in the old architecture.
|
||||
- if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
|
||||
- s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
|
||||
- s.pod_target_xcconfig = {
|
||||
- "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
|
||||
- "CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
|
||||
- }
|
||||
-
|
||||
- s.dependency "React-Codegen"
|
||||
- s.dependency "RCT-Folly", folly_version
|
||||
- s.dependency "RCTRequired"
|
||||
- s.dependency "RCTTypeSafety"
|
||||
- s.dependency "ReactCommon/turbomodule/core"
|
||||
- end
|
||||
-end
|
||||
diff --git a/src/index.tsx b/src/index.tsx
|
||||
index d9f90c921d89b1b4d85e145443ed3376546a368a..29e4068dbd7500828a73145bd25497a52c9bf638 100644
|
||||
--- a/src/index.tsx
|
||||
+++ b/src/index.tsx
|
||||
@@ -69,6 +69,15 @@ export default class Vosk {
|
||||
VoskModule.stop();
|
||||
};
|
||||
|
||||
+ stopOnly = () => {
|
||||
+ VoskModule.stopOnly();
|
||||
+ };
|
||||
+
|
||||
+ cleanup = () => {
|
||||
+ this.cleanListeners();
|
||||
+ VoskModule.cleanup();
|
||||
+ };
|
||||
+
|
||||
unload = () => {
|
||||
this.cleanListeners();
|
||||
VoskModule.unload();
|
||||
BIN
Assets/WebsiteAssets/images/news/20250922-mobile-rte.png
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
Assets/WebsiteAssets/images/news/20250922-note-history.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
Assets/WebsiteAssets/images/news/20250922-publish-notes.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
Assets/WebsiteAssets/images/news/20250922-scan-notebook.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
Assets/WebsiteAssets/images/news/20250922-tag-editor.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
Assets/WebsiteAssets/images/sponsors/DamanGame.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Assets/WebsiteAssets/images/sponsors/DoMyEssay.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
Assets/WebsiteAssets/images/sponsors/EssayShark.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Assets/WebsiteAssets/images/sponsors/NotGamStop.jpg
Normal file
|
After Width: | Height: | Size: 109 KiB |
BIN
Assets/WebsiteAssets/images/sponsors/PokiesLab.png
Normal file
|
After Width: | Height: | Size: 378 KiB |
BIN
Assets/WebsiteAssets/images/sponsors/Pokiesman.png
Normal file
|
After Width: | Height: | Size: 295 KiB |
BIN
Assets/WebsiteAssets/images/sponsors/SocialKings.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
Assets/WebsiteAssets/images/sponsors/WriteMyEssay.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
215
Assets/WebsiteAssets/locales/hr_HR.po
Normal file
@@ -0,0 +1,215 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: Milo Ivir <mail@mivirtype.de>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: hr_HR\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 3.6\n"
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/partials/plan.mustache:13
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/partials/plan.mustache:9
|
||||
msgid "/month"
|
||||
msgstr "/mjesec"
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/partials/plan.mustache:19
|
||||
msgid "/year"
|
||||
msgstr "/godina"
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/plans.mustache:8
|
||||
msgid ""
|
||||
"<a href=\"https://joplincloud.com\">Joplin Cloud</a> allows you to "
|
||||
"synchronise your notes across devices. It also lets you publish notes, and "
|
||||
"collaborate on notebooks with your friends, family or colleagues."
|
||||
msgstr ""
|
||||
"<a href=\"https://joplincloud.com\">Joplin Cloud</a> omogućuje "
|
||||
"sinkronizaciju bilješki na različitim uređajima. Omogućuje i objavljivanje "
|
||||
"bilješki i suradnju na bilježnicama s prijateljima, obitelji ili kolegama."
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:205
|
||||
msgid "<span class=\"frame-bg frame-bg-yellow-lg\">Customise</span> it"
|
||||
msgstr "<span class=\"frame-bg frame-bg-yellow-lg\">Prilagodi</span> uslugu"
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:104
|
||||
msgid "<span class=\"frame-bg frame-bg-yellow\">Multimedia</span> notes"
|
||||
msgstr "<span class=\"frame-bg frame-bg-yellow\">Multimedijske</span> bilješke"
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:256
|
||||
msgid "100% <span class=\"frame-bg frame-bg-yellow-lg\">your data</span>"
|
||||
msgstr "100 % <span class=\"frame-bg frame-bg-yellow-lg\">tvoji podaci</span>"
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:298
|
||||
msgid "A <span class=\"frame-bg frame-bg-yellow-lg\">French</span> Alternative"
|
||||
msgstr ""
|
||||
"<span class=\"frame-bg frame-bg-yellow-lg\">Francuska</span> alternativa"
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:236
|
||||
msgid ""
|
||||
"Access your notes from your computer, phone or tablet by synchronising with "
|
||||
"various services, including Joplin Cloud, Dropbox and OneDrive. The app is "
|
||||
"available on Windows, macOS, Linux, Android and iOS. A terminal app is also "
|
||||
"available!"
|
||||
msgstr ""
|
||||
"Pristupi svojim bilješkama s računala, mobitela ili tableta sinkronizacijom "
|
||||
"s raznim uslugama, uključujući Joplin Cloud, Dropbox i OneDrive. Program je "
|
||||
"dostupan za Windows, macOS, Linux, Android i iOS sustave. Dostupan je i "
|
||||
"program za terminal!"
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/plans.mustache:49
|
||||
msgid ""
|
||||
"Already have a Joplin Cloud account? <a href=\"https://"
|
||||
"joplincloud.com\">Login now</a>"
|
||||
msgstr ""
|
||||
"Već imaš Joplin Cloud račun? <a href=\"https://joplincloud.com\">Prijavi se "
|
||||
"sada</a>"
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:208
|
||||
msgid ""
|
||||
"Customise the app with plugins, custom themes and multiple text editors "
|
||||
"(Rich Text or Markdown). Or create your own scripts and plugins using the "
|
||||
"Extension API."
|
||||
msgstr ""
|
||||
"Prilagodi program pomoću dodataka, prilagođenih tema i uređivača teksta "
|
||||
"(formatirani tekst ili Markdown). Ili izradi vlastita skripta i dodatke "
|
||||
"pomoću API-ja za proširenja."
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:242
|
||||
msgid "Download it now"
|
||||
msgstr "Preuzmi sada"
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:112
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:63
|
||||
msgid "Download the app"
|
||||
msgstr "Preuzmi program"
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:213
|
||||
msgid "Find out more"
|
||||
msgstr "Saznaj više"
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:54
|
||||
msgid "Free your <span class=\"frame-bg frame-bg-blue\">notes</span>"
|
||||
msgstr "Oslobodi svoje <span class=\"frame-bg frame-bg-blue\">bilješke</span>"
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:175
|
||||
msgid "Get the clipper"
|
||||
msgstr "Nabavi Clipper"
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:107
|
||||
msgid ""
|
||||
"Images, videos, PDFs and audio files are supported. Create math expressions "
|
||||
"and diagrams directly from the app. Take photos with the mobile app and save "
|
||||
"them to a note."
|
||||
msgstr ""
|
||||
"Podržane su slike, videozapisi, PDF-ovi i audio datoteke. Stvori matematičke "
|
||||
"izraze i dijagrame izravno iz programa. Snimaj fotografije s programom za "
|
||||
"mobitel i spremi ih u bilješku."
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:327
|
||||
msgid "In the <span class=\"frame-bg frame-bg-yellow\">Press</span>"
|
||||
msgstr "<span class=\"frame-bg frame-bg-yellow\">Recenzije</span>"
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/plans.mustache:5
|
||||
msgid "Joplin Cloud <span class=\"frame-bg frame-bg-yellow\">plans</span>"
|
||||
msgstr "Joplin Cloud <span class=\"frame-bg frame-bg-yellow\">tarife</span>"
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:301
|
||||
msgid ""
|
||||
"Joplin Cloud is based in France. This means your data is protected by strict "
|
||||
"European Union privacy laws. In addition, Joplin Cloud implements strong end-"
|
||||
"to-end encryption so that not even us can have access to your data."
|
||||
msgstr ""
|
||||
"Joplin Cloud ima sjedište u Francuskoj. To znači da su tvoji podaci "
|
||||
"zaštićeni strogim zakonima o privatnosti Europske unije. Osim toga, Joplin "
|
||||
"Cloud implementira snažno sveobuhvatno šifriranje (end-to-end encryption) "
|
||||
"tako da čak ni mi ne možemo pristupiti tvojim podacima."
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:57
|
||||
msgid ""
|
||||
"Joplin is an open source note-taking app. Capture your thoughts and securely "
|
||||
"access them from any device."
|
||||
msgstr ""
|
||||
"Joplin je program za bilješke otvorenog koda. Zabilježi svoje misli i "
|
||||
"sigurno im pristupi s bilo kojeg uređaja."
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:262
|
||||
msgid "More about E2EE"
|
||||
msgstr "Više o E2EE"
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:391
|
||||
msgid "Our <span class=\"frame-bg frame-bg-blue-lg\">sponsors</span>"
|
||||
msgstr "Naši <span class=\"frame-bg frame-bg-blue-lg\">sponzori</span>"
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/plans.mustache:23
|
||||
msgid "Pay Monthly"
|
||||
msgstr "Plaćaj mjesečno"
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/plans.mustache:30
|
||||
msgid "Pay Yearly"
|
||||
msgstr "Plaćaj godišnje"
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:167
|
||||
msgid ""
|
||||
"Save <span class=\"frame-bg frame-bg-blue\">web pages</span> <br>as notes"
|
||||
msgstr ""
|
||||
"Spremaj <span class=\"frame-bg frame-bg-blue\">web stranice</span> <br>kao "
|
||||
"bilješke"
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:65
|
||||
msgid "Sign up with Joplin Cloud"
|
||||
msgstr "Registriraj se na Joplin Cloud"
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:394
|
||||
msgid "Thank you for your support!"
|
||||
msgstr "Hvala ti na podršci!"
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:257
|
||||
msgid ""
|
||||
"The app is open source and your notes are saved to an open format, so you'll "
|
||||
"always have access to them. Uses End-To-End Encryption (E2EE) to secure your "
|
||||
"notes and ensure no-one but yourself can access them."
|
||||
msgstr ""
|
||||
"Program je otvorenog koda i tvoje se bilješke spremaju u otvorenom formatu, "
|
||||
"tako da ćeš im uvijek moći pristupiti. Program koristi sveobuhvatno "
|
||||
"šifriranje – engl. End-To-End Encryption (E2EE) – kako bi zaštitila tvoje "
|
||||
"bilješke i osigurala da im nitko osim tebe ne može pristupiti."
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:144
|
||||
msgid "Try it now"
|
||||
msgstr "Isprobaj sada"
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:170
|
||||
msgid ""
|
||||
"Use the web clipper extension, available on Chrome and Firefox, to save web "
|
||||
"pages or take screenshots as notes."
|
||||
msgstr ""
|
||||
"Koristi proširenje Web Clipper, dostupno za Chrome i Firefox, za spremanje "
|
||||
"web stranica ili snimanje ekrana kao bilješku."
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:138
|
||||
msgid ""
|
||||
"With Joplin Cloud, share your notes with your friends, family or colleagues "
|
||||
"and collaborate on them."
|
||||
msgstr ""
|
||||
"Joplin Cloud ti omogućuje da dijeliš bilješke s prijateljima, obitelji ili "
|
||||
"kolegama te da na njima surađujete."
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:137
|
||||
msgid "Work <span class=\"frame-bg frame-bg-yellow\">together</span>"
|
||||
msgstr "<span class=\"frame-bg frame-bg-yellow\">Surađuj</span> s drugima"
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:141
|
||||
msgid ""
|
||||
"You can also publish a note to the internet and share the URL with others."
|
||||
msgstr "Bilješke možeš objaviti i na internetu te dijeliti URL s drugima."
|
||||
|
||||
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:233
|
||||
msgid ""
|
||||
"Your notes, <span class=\"frame-bg frame-bg-blue-lg\">everywhere</span> you "
|
||||
"are"
|
||||
msgstr ""
|
||||
"Tvoje bilješke, <span class=\"frame-bg frame-bg-blue-lg\">gdje god</span> se "
|
||||
"nalaziš"
|
||||
@@ -1,4 +1,77 @@
|
||||
<?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, 28 Apr 2025 00:00:00 GMT</lastBuildDate><atom:link href="https://joplinapp.org/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Mon, 28 Apr 2025 00:00:00 GMT</pubDate><item><title><![CDATA[What's new in Joplin 3.3]]></title><description><![CDATA[<h2>Desktop application<a name="desktop-application" href="#desktop-application" class="heading-anchor">🔗</a></h2>
|
||||
<?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, 22 Sep 2025 00:00:00 GMT</lastBuildDate><atom:link href="https://joplinapp.org/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Mon, 22 Sep 2025 00:00:00 GMT</pubDate><item><title><![CDATA[What's new in Joplin 3.4]]></title><description><![CDATA[<p>Joplin 3.4 includes many bug fixes and improvements, with a focus on the mobile app.</p>
|
||||
<h2>Mobile<a name="mobile" href="#mobile" class="heading-anchor">🔗</a></h2>
|
||||
<h3>Rich Text Editor<a name="rich-text-editor" href="#rich-text-editor" class="heading-anchor">🔗</a></h3>
|
||||
<p>The mobile app now includes a beta <a href="https://joplinapp.org/help/apps/rich_text_editor">Rich Text Editor</a>! The new editor renders formatting/math/images within the editor:</p>
|
||||
<img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20250922-mobile-rte.png" width="400" alt="screenshot: Mobile Rich Text Editor editing the welcome notes. Images, headings, etc are rendering."/>
|
||||
<p>To try it, 1) open a note in the default Markdown editor 2) open the note actions menu (the three vertical dots) for the note and 3) click “Edit as Rich Text”.</p>
|
||||
<p>Be aware that this editor is still in active development and <a href="https://github.com/laurent22/joplin/issues/12840">has a number of known limitations and issues</a>. The Rich Text editor is based on <a href="https://prosemirror.net/">ProseMirror</a> and will behave differently from the desktop Rich Text Editor in many cases.</p>
|
||||
<h3>Support for publishing notes with Joplin Cloud and Server<a name="support-for-publishing-notes-with-joplin-cloud-and-server" href="#support-for-publishing-notes-with-joplin-cloud-and-server" class="heading-anchor">🔗</a></h3>
|
||||
<p>It's now possible to <a href="https://joplinapp.org/help/apps/publish_note">publish notes</a> from the mobile app! To do so, open the “Properties” menu for a note, then click “Publish/unpublish”:</p>
|
||||
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20250922-publish-notes.png" alt="screenshot: A Publish/unpublish note action is shown in the "Note properties" sidebar, just below a "Previous versions" button"></p>
|
||||
<p>Next, in the “publish note” dialog, click “Copy shareable link”. Notes can later be unpublished by clicking "Unpublish" in the publication dialog.</p>
|
||||
<h3>Viewing note history<a name="viewing-note-history" href="#viewing-note-history" class="heading-anchor">🔗</a></h3>
|
||||
<p>It is now possible to view and restore previous note versions from the mobile app. Like the "publish note" feature, previous note versions can be accessed from the note properties menu.</p>
|
||||
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20250922-note-history.png" alt="screenshot: The note history page"></p>
|
||||
<p>As on desktop, the note history feature can be configured from the “Note History” tab in settings.</p>
|
||||
<h3>Updated tag dialog<a name="updated-tag-dialog" href="#updated-tag-dialog" class="heading-anchor">🔗</a></h3>
|
||||
<p>The tag dialog has been redesigned, with a new UI for adding, removing, and creating new tags:<br>
|
||||
<img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20250922-tag-editor.png" width="500" alt="screenshot: Tag dialog now consists of three sections: Added tags, Add new tags, Actions."/></p>
|
||||
<h3>Android: Improved voice typing<a name="android-improved-voice-typing" href="#android-improved-voice-typing" class="heading-anchor">🔗</a></h3>
|
||||
<p>The voice typing feature on Android has been updated with <a href="https://github.com/laurent22/joplin/pull/12404">improved silence detection</a> and a new “<a href="https://github.com/laurent22/joplin/pull/12370">custom glossary</a>” setting. Voice typing also now <a href="https://github.com/laurent22/joplin/pull/12352">defaults to a more accurate (but somewhat slower) model</a>.</p>
|
||||
<h3>Quickly creating a note from multiple photos<a name="quickly-creating-a-note-from-multiple-photos" href="#quickly-creating-a-note-from-multiple-photos" class="heading-anchor">🔗</a></h3>
|
||||
<p>A “scan notebook” action has been added to the “New note” menu:</p>
|
||||
<img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20250922-scan-notebook.png" width="500"/>
|
||||
<p>This action allows quickly creating a new note with multiple pictures taken from the camera.</p>
|
||||
<h2>Desktop<a name="desktop" href="#desktop" class="heading-anchor">🔗</a></h2>
|
||||
<h3>More Markdown Editor settings<a name="more-markdown-editor-settings" href="#more-markdown-editor-settings" class="heading-anchor">🔗</a></h3>
|
||||
<p>The "Note" tab in settings now includes new settings for the Markdown editor, including:</p>
|
||||
<ul>
|
||||
<li>An option to render headers, lists, and certain other formatting within the editor.</li>
|
||||
<li>An option to render images in the editor.</li>
|
||||
</ul>
|
||||
<p>When enabled, these settings bring the Markdown editor closer to the Rich Text Editor, without <a href="https://joplinapp.org/help/apps/rich_text_editor">some of the Rich Text Editor's limitations</a>.</p>
|
||||
<p>These settings are also available on mobile.</p>
|
||||
<h3>Smaller application size and faster startup<a name="smaller-application-size-and-faster-startup" href="#smaller-application-size-and-faster-startup" class="heading-anchor">🔗</a></h3>
|
||||
<p>We've made the desktop application roughly 33% smaller! In addition to faster application startup, this means that the desktop app should be faster to download take up less space.</p>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Joplin version</th>
|
||||
<th>Previous size (v3.3.13)</th>
|
||||
<th>New size (v3.4.12)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Joplin for MacOS (ARM)</td>
|
||||
<td>211 MB</td>
|
||||
<td>141 MB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Joplin for Windows (installer)</td>
|
||||
<td>321 MB</td>
|
||||
<td>219 MB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Joplin for Windows (portable)</td>
|
||||
<td>320 MB</td>
|
||||
<td>219 MB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Joplin for Linux (AppImage)</td>
|
||||
<td>219 MB</td>
|
||||
<td>147 MB</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h2>Terminal app<a name="terminal-app" href="#terminal-app" class="heading-anchor">🔗</a></h2>
|
||||
<h3>Collapsible folders<a name="collapsible-folders" href="#collapsible-folders" class="heading-anchor">🔗</a></h3>
|
||||
<p>The <a href="https://joplinapp.org/help/apps/terminal/">terminal application</a> now supports expanding and collapsing folders by pressing <kbd>z</kbd>. For additional information, see <a href="https://github.com/laurent22/joplin/pull/12718">the original pull request</a>.</p>
|
||||
<h3>Managing shared notebooks and published notes<a name="managing-shared-notebooks-and-published-notes" href="#managing-shared-notebooks-and-published-notes" class="heading-anchor">🔗</a></h3>
|
||||
<p>New commands have been added to the terminal app, including <code>publish</code>, <code>unpublish</code>, and <code>share</code>. This allows the terminal app to manage shared folders and published notes.</p>
|
||||
<h2>Bug fixes<a name="bug-fixes" href="#bug-fixes" class="heading-anchor">🔗</a></h2>
|
||||
<p>For the full list of changes, see <a href="https://joplinapp.org/help/about/changelog/desktop/">the desktop changelog</a> and <a href="https://joplinapp.org/help/about/changelog/android/">the mobile changelog</a>.</p>
|
||||
]]></description><link>https://joplinapp.org/news/20250922-release-3-4</link><guid isPermaLink="false">20250922-release-3-4</guid><pubDate>Mon, 22 Sep 2025 00:00:00 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[What's new in Joplin 3.3]]></title><description><![CDATA[<h2>Desktop application<a name="desktop-application" href="#desktop-application" class="heading-anchor">🔗</a></h2>
|
||||
<h3>Accessibility improvements<a name="accessibility-improvements" href="#accessibility-improvements" class="heading-anchor">🔗</a></h3>
|
||||
<p>The Joplin 3.3 release introduces significant accessibility enhancements designed to make the application more inclusive and user-friendly. Users can now benefit from improved keyboard navigation, thanks to newly added shortcuts and clearer labels that streamline interaction across the interface. We've also added a "go to viewer" menu item that moves focus from the note editor to the note viewer. Focus is moved to the location in the viewer corresponding to the location of the cursor in the editor.</p>
|
||||
<p>Screen reader support has been bolstered, ensuring elements like the note list and sidebar are easier to toggle and interact with. These updates make the application more usable for individuals relying on assistive technologies.</p>
|
||||
@@ -446,10 +519,4 @@ sys 0m38.013s</p>
|
||||
<p>Unfortunately we cannot publish the Android version because it is based on a framework version that Google does not accept. To upgrade the app a lot of changes are needed and another round of pre-releases, and therefore there will not be a 2.9 version for Google Play. You may however download the official APK directly from there: <a href="https://github.com/laurent22/joplin-android/releases/tag/android-v2.9.8">Android 2.9 Official Release</a></p>
|
||||
<p>This is the reality of app stores in general - small developers being imposed never ending new requirements by all-powerful companies, and by the time a version is finally ready we can't even publish it because yet more requirements are in place.</p>
|
||||
<p>For the record the current 2.9 app works perfectly fine. It targets Android 11, which is only 2 years old and is still supported (and installed on millions of phones). Google requires us to target Android 12 which only came out last year.</p>
|
||||
]]></description><link>https://joplinapp.org/news/20221216-release-2-9</link><guid isPermaLink="false">20221216-release-2-9</guid><pubDate>Fri, 16 Dec 2022 00:00:00 GMT</pubDate><twitter-text>What's new in Joplin 2.9</twitter-text></item><item><title><![CDATA[Joplin is hiring!]]></title><description><![CDATA[<p>Joplin is an open source note-taking app. Capture your thoughts and securely access them from any device.</p>
|
||||
<p>We are looking to hire two JavaScript software developers to work on the desktop, mobile, and server applications. All those are built using modern technologies, including React, React Native and Electron with a strong focus on test units.</p>
|
||||
<p>You need to demonstrate some experience with at least some of these technologies, and willing to learn more and touch various different projects.</p>
|
||||
<p>You will be part of a small team, so you will have an opportunity for a high-impact role, targeting hundreds of thousands of users.</p>
|
||||
<p>If you're interested please contact us at job-AT-joplin.cloud</p>
|
||||
<p>No agencies please.</p>
|
||||
]]></description><link>https://joplinapp.org/news/20221209-job</link><guid isPermaLink="false">20221209-job</guid><pubDate>Fri, 09 Dec 2022 00:00:00 GMT</pubDate><twitter-text>Joplin is hiring!</twitter-text></item></channel></rss>
|
||||
]]></description><link>https://joplinapp.org/news/20221216-release-2-9</link><guid isPermaLink="false">20221216-release-2-9</guid><pubDate>Fri, 16 Dec 2022 00:00:00 GMT</pubDate><twitter-text>What's new in Joplin 2.9</twitter-text></item></channel></rss>
|
||||
@@ -63,11 +63,24 @@ FROM node:18-slim
|
||||
ARG user=joplin
|
||||
RUN useradd --create-home --shell /bin/bash $user
|
||||
|
||||
# Install PM2 and set home directory. Setting the PM2 data dir so modules/config persist regardless
|
||||
# of user home.
|
||||
RUN npm i -g pm2@5.4.3 && mkdir -p /opt/pm2 && chown -R $user:$user /opt/pm2
|
||||
ENV PM2_HOME=/opt/pm2
|
||||
|
||||
USER $user
|
||||
|
||||
COPY --chown=$user:$user --from=builder /build/packages /home/$user/packages
|
||||
COPY --chown=$user:$user --from=builder /usr/bin/tini /usr/local/bin/tini
|
||||
|
||||
# We download a specific version of the plugin to prevent pm2 from fetching the latest, since it may
|
||||
# not have been properly audited (that fact was used to spread malware at some point). Ref:
|
||||
# https://github.com/laurent22/joplin/issues/12754
|
||||
RUN pm2 install https://registry.npmjs.org/pm2-logrotate/-/pm2-logrotate-3.0.0.tgz \
|
||||
&& pm2 set pm2-logrotate:max_size 100MB \
|
||||
&& pm2 set pm2-logrotate:retain 5 \
|
||||
&& pm2 set pm2-logrotate:compress true
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV RUNNING_IN_DOCKER=1
|
||||
EXPOSE ${APP_PORT}
|
||||
|
||||
@@ -31,7 +31,7 @@ Please see the [donation page](https://github.com/laurent22/joplin/blob/dev/read
|
||||
# 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://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> <a href="https://bestkru.com"><img title="BestKru" width="256" src="https://joplinapp.org/images/sponsors/BestKru.png"/></a> <a href="https://www.socialfollowers.uk/buy-tiktok-followers/"><img title="Social Followers" width="256" src="https://joplinapp.org/images/sponsors/SocialFollowers.png"/></a> <a href="https://stormlikes.com/"><img title="Stormlikes" width="256" src="https://joplinapp.org/images/sponsors/Stormlikes.png"/></a> <a href="https://route4me.com"><img title="Route4Me" width="256" src="https://joplinapp.org/images/sponsors/Route4Me.png"/></a> <a href="https://topagency.webflow.io"><img title="WebDesignAgency" width="256" src="https://joplinapp.org/images/sponsors/WebDesignAgency.png" alt="topagency"/></a> <a href="https://realgambling.ca/"><img title="RealGambling.ca" width="256" src="https://joplinapp.org/images/sponsors/RealGambling.png" alt="RealGambling.ca"/></a> <a href="https://essaypro.com/"><img title="write an essay online with EssayPro" width="256" src="https://joplinapp.org/images/sponsors/EssayPro.png" alt="write an essay online with EssayPro"/></a> <a href="https://www.slotozilla.com/nz/no-deposit-bonus"><img title="casino without making any upfront cost" width="256" src="https://joplinapp.org/images/sponsors/Slotozilla.png" alt="casino without making any upfront cost"/></a> <a href="https://essaywriter.pro"><img title="write my essay services by EssayWriter" width="256" src="https://joplinapp.org/images/sponsors/EssayWriterPro.png" alt="write my essay services by EssayWriter"/></a> <a href="https://essayservice.com"><img title="quick and reliable service to write my paper for me" width="256" src="https://joplinapp.org/images/sponsors/EssayService.png" alt="quick and reliable service to write my paper for me"/></a> <a href="https://writepaper.com/"><img title="best service to write my paper for me" width="256" src="https://joplinapp.org/images/sponsors/WritePaper.png" alt="best service to write my paper for me"/></a> <a href="https://paperwriter.com/"><img title="high-quality paper writing service PaperWriter" width="256" src="https://joplinapp.org/images/sponsors/PaperWriter.png" alt="high-quality paper writing service PaperWriter"/></a> <a href="https://homeworkguy.org/someone-to-take-my-online-class"><img title="someone to take my online class" width="256" src="https://joplinapp.org/images/sponsors/HomeworkGuy.png" alt="someone to take my online class"/></a> <a href="https://www.bestetf.net/"><img title="BestETF" width="256" src="https://joplinapp.org/images/sponsors/BestEtf.png" alt="BestETF"/></a> <a href="https://freespinny.io/free-spins-no-deposit/"><img title="Freespinny.io Free Spins Bonus site" width="256" src="https://joplinapp.org/images/sponsors/Freespinny.png" alt="Freespinny.io Free Spins Bonus site"/></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://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> <a href="https://bestkru.com"><img title="BestKru" width="256" src="https://joplinapp.org/images/sponsors/BestKru.png"/></a> <a href="https://www.socialfollowers.uk/buy-tiktok-followers/"><img title="Social Followers" width="256" src="https://joplinapp.org/images/sponsors/SocialFollowers.png"/></a> <a href="https://stormlikes.com/"><img title="Stormlikes" width="256" src="https://joplinapp.org/images/sponsors/Stormlikes.png"/></a> <a href="https://route4me.com"><img title="Route4Me" width="256" src="https://joplinapp.org/images/sponsors/Route4Me.png"/></a> <a href="https://topagency.webflow.io"><img title="WebDesignAgency" width="256" src="https://joplinapp.org/images/sponsors/WebDesignAgency.png" alt="topagency"/></a> <a href="https://www.slotozilla.com/nz/no-deposit-bonus"><img title="casino without making any upfront cost" width="256" src="https://joplinapp.org/images/sponsors/Slotozilla.png" alt="casino without making any upfront cost"/></a> <a href="https://writepaper.com/"><img title="best service to write my paper for me" width="256" src="https://joplinapp.org/images/sponsors/WritePaper.png" alt="best service to write my paper for me"/></a> <a href="https://paperwriter.com/"><img title="high-quality paper writing service PaperWriter" width="256" src="https://joplinapp.org/images/sponsors/PaperWriter.png" alt="high-quality paper writing service PaperWriter"/></a> <a href="https://www.bestetf.net/"><img title="BestETF" width="256" src="https://joplinapp.org/images/sponsors/BestEtf.png" alt="BestETF"/></a> <a href="https://freespinny.io/free-spins-no-deposit/"><img title="Freespinny.io Free Spins Bonus site" width="256" src="https://joplinapp.org/images/sponsors/Freespinny.png" alt="Freespinny.io Free Spins Bonus site"/></a> <a href="https://essayshark.com"><img title="EssayShark - essay writers for hire" width="256" src="https://joplinapp.org/images/sponsors/EssayShark.png" alt="EssayShark - essay writers for hire"/></a> <a href="https://pokieslab1.com/real-money-pokies/"><img title="Australian Real Money Pokies" width="256" src="https://joplinapp.org/images/sponsors/PokiesLab.png" alt="Australian Real Money Pokies"/></a> <a href="https://pokiesman1.net/real-money-pokies/"><img title="Australian Real Money Pokies" width="256" src="https://joplinapp.org/images/sponsors/Pokiesman.png" alt="Australian Real Money Pokies"/></a> <a href="https://domyessay.com"><img title="Essay writers DoMyEssay are dedicated to providing top-notch, custom-written papers that meet your academic requirements" width="256" src="https://joplinapp.org/images/sponsors/DoMyEssay.png" alt="DoMyEssay"/></a> <a href="https://essaypro.com/"><img title="best essay writing service" width="256" src="https://joplinapp.org/images/sponsors/EssayPro.png" alt="best essay writing service"/></a> <a href="https://socialkings.online"><img title="Boost your reach and buy real followers" width="256" src="https://joplinapp.org/images/sponsors/SocialKings.png" alt="Boost your reach and buy real followers"/></a> <a href="https://uk.notgamstop.com/bonuses/free-spins-no-deposit-no-gamstop/"><img title="free spins no deposit at NotGamstop" width="256" src="https://joplinapp.org/images/sponsors/NotGamStop.jpg" alt="free spins no deposit at NotGamstop"/></a> <a href="https://www.writemyessay.com/"><img title="writing service for students WriteMyEssay" width="256" src="https://joplinapp.org/images/sponsors/WriteMyEssay.png" alt="writing service for students WriteMyEssay"/></a> <a href="https://essayservice.com/"><img title="For those in need of immediate academic assistance, EssayService offers a fast and reliable service to write my essay for me now, ensuring high-quality results within tight deadlines" width="256" src="https://joplinapp.org/images/sponsors/EssayService.png" alt="For those in need of immediate academic assistance, EssayService offers a fast and reliable service to write my essay for me now, ensuring high-quality results within tight deadlines"/></a>
|
||||
<!-- SPONSORS-ORG -->
|
||||
|
||||
* * *
|
||||
@@ -40,9 +40,8 @@ Please see the [donation page](https://github.com/laurent22/joplin/blob/dev/read
|
||||
| | | | |
|
||||
| :---: | :---: | :---: | :---: |
|
||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/97193607?s=96&v=4"/></br>[Akhil-CM](https://github.com/Akhil-CM) | <img width="50" src="https://avatars2.githubusercontent.com/u/552452?s=96&v=4"/></br>[andypiper](https://github.com/andypiper) | <img width="50" src="https://avatars2.githubusercontent.com/u/215668?s=96&v=4"/></br>[avanderberg](https://github.com/avanderberg) | <img width="50" src="https://avatars2.githubusercontent.com/u/67130?s=96&v=4"/></br>[chr15m](https://github.com/chr15m) |
|
||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/1177810?s=96&v=4"/></br>[felixstorm](https://github.com/felixstorm) | <img width="50" src="https://avatars2.githubusercontent.com/u/8030470?s=96&v=4"/></br>[Galliver7](https://github.com/Galliver7) | <img width="50" src="https://avatars2.githubusercontent.com/u/64712218?s=96&v=4"/></br>[Hegghammer](https://github.com/Hegghammer) | <img width="50" src="https://avatars2.githubusercontent.com/u/11947658?s=96&v=4"/></br>[KentBrockman](https://github.com/KentBrockman) |
|
||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/42319182?s=96&v=4"/></br>[marcdw1289](https://github.com/marcdw1289) | <img width="50" src="https://avatars2.githubusercontent.com/u/1788010?s=96&v=4"/></br>[maxtruxa](https://github.com/maxtruxa) | <img width="50" src="https://avatars2.githubusercontent.com/u/327998?s=96&v=4"/></br>[sif](https://github.com/sif) | <img width="50" src="https://avatars2.githubusercontent.com/u/765564?s=96&v=4"/></br>[taskcruncher](https://github.com/taskcruncher) |
|
||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/668977?s=96&v=4"/></br>[ugoertz](https://github.com/ugoertz) | | | |
|
||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/1177810?s=96&v=4"/></br>[felixstorm](https://github.com/felixstorm) | <img width="50" src="https://avatars2.githubusercontent.com/u/11947658?s=96&v=4"/></br>[KentBrockman](https://github.com/KentBrockman) | <img width="50" src="https://avatars2.githubusercontent.com/u/42319182?s=96&v=4"/></br>[marcdw1289](https://github.com/marcdw1289) | <img width="50" src="https://avatars2.githubusercontent.com/u/668977?s=96&v=4"/></br>[ugoertz](https://github.com/ugoertz) |
|
||||
| | | | |
|
||||
<!-- SPONSORS-GITHUB -->
|
||||
|
||||
# Community
|
||||
|
||||
@@ -9,20 +9,20 @@
|
||||
"vips.dev": {
|
||||
"platforms": ["aarch64-darwin"],
|
||||
},
|
||||
"nodejs": "23.10.0",
|
||||
"nodejs": "23.11.0",
|
||||
"pkg-config": "latest",
|
||||
"darwin.apple_sdk.frameworks.Foundation": { // satisfies missing CoreText/CoreText.h
|
||||
// https://github.com/NixOS/nixpkgs/blob/master/pkgs/os-specific/darwin/apple-sdk/default.nix
|
||||
"version": "",
|
||||
"platforms": ["aarch64-darwin", "x86_64-darwin"],
|
||||
},
|
||||
"python": "3.13.2",
|
||||
"python": "3.13.3",
|
||||
"bat": "latest",
|
||||
"electron": {
|
||||
"version": "latest",
|
||||
"excluded_platforms": ["aarch64-darwin", "x86_64-darwin"],
|
||||
},
|
||||
"git": "2.47.2",
|
||||
"git": "2.48.1",
|
||||
},
|
||||
"shell": {
|
||||
"init_hook": [
|
||||
|
||||
@@ -16,12 +16,10 @@
|
||||
# SLAVE_POSTGRES_PORT=5433
|
||||
# SLAVE_POSTGRES_HOST=localhost
|
||||
|
||||
version: '2'
|
||||
|
||||
services:
|
||||
|
||||
postgresql-master:
|
||||
image: 'bitnami/postgresql:17.3.0'
|
||||
image: 'bitnamilegacy/postgresql:17.4.0'
|
||||
ports:
|
||||
- '5432:5432'
|
||||
environment:
|
||||
@@ -38,7 +36,7 @@ services:
|
||||
- POSTGRESQL_EXTRA_FLAGS=-c work_mem=100000 -c log_statement=all
|
||||
|
||||
postgresql-slave:
|
||||
image: 'bitnami/postgresql:17.3.0'
|
||||
image: 'bitnamilegacy/postgresql:17.4.0'
|
||||
ports:
|
||||
- '5433:5432'
|
||||
depends_on:
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
# This compose file can be used in development to run both the database and app
|
||||
# within Docker.
|
||||
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
# - This would typically be mapped to port to 443 (TLS) with a reverse proxy.
|
||||
# - If Joplin Server does not need to be accessible over the internet, the port can be mapped to 22300.
|
||||
|
||||
version: '3'
|
||||
|
||||
networks:
|
||||
app-network:
|
||||
transcribe-network:
|
||||
|
||||
13
fastlane/metadata/android/hr/full_description.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
<strong>Joplin</strong> je besplatan program otvorenog koda za bilješke i popis zadataka koji može obraditi veliki broj bilješki organizirane u bilježnice. Bilješke se mogu pretraživati, kopirati, označavati i mijenjati izravno iz programa ili iz vlastitog uređivača teksta.
|
||||
|
||||
Bilješke su u <a href="https://joplinapp.org/help/apps/markdown">Markdown formatu</a>.
|
||||
|
||||
Iz Evernotea izvezene bilješke <a href="https://joplinapp.org/help/apps/import_export">mogu se uvesti</a> u Joplin, uključujući formatirani sadržaj (koji se pretvara u Markdown), resurse (slike, privitke itd.) i potpune metapodatke (geografski podaci mjesta, vrijeme aktualiziranja, vrijeme stvaranja itd.). Mogu se uvesti i obične Markdown datoteke.
|
||||
|
||||
Joplin radi ponajprije s lokalnim podacima (offline first), što znači da uvijek imaš sve svoje podatke na mobitelu ili računalu. To osigurava da su tvoje bilješke uvijek dostupne, bez obzira je li imaš internetsku vezu ili ne.</p>
|
||||
|
||||
Bilješke se mogu sigurno <a href="https://joplinapp.org/help/apps/sync">sinkronizirati</a> pomoću <a href="https://joplinapp.org/help/apps/sync/e2ee">sveobuhvatnog šifriranja</a> s raznim uslugama u oblaku, uključujući Nextcloud, Dropbox, OneDrive i <a href="https://joplinapp.org/plans/">Joplin Cloud</a>.
|
||||
|
||||
Pretraživanje cijelog teksta dostupno je na svim platformama za brzo pronalaženje potrebnih informacija. Program se može prilagoditi pomoću dodataka i tema, a možeš stvoriti i vlastite.
|
||||
|
||||
Program je dostupan za Windows, Linux, macOS, Android i iOS sustave. <a href="https://joplinapp.org/help/apps/clipper">Web Clipper</a>, za spremanje web stranica i snimaka ekrana iz tvog preglednika, je također dostupan za <a href="https://addons.mozilla.org/firefox/addon/joplin-web-clipper/">Firefox</a> i <a href="https://chrome.google.com/webstore/detail/joplin-web-clipper/alofnhikmmkdbbbgpnglcpdollgjjfek">Chrome</a>.
|
||||
1
fastlane/metadata/android/hr/short_description.txt
Normal file
@@ -0,0 +1 @@
|
||||
Program za bilješke i popis zadataka sa sinkronizacijom između Linuxa, macOS-a, Windowsa i mobitela
|
||||
17
package.json
@@ -76,20 +76,20 @@
|
||||
"cspell": "5.21.2",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-interactive": "10.8.0",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"eslint-plugin-jest": "27.9.0",
|
||||
"eslint-plugin-promise": "6.6.0",
|
||||
"eslint-plugin-react": "7.37.4",
|
||||
"eslint-plugin-react": "7.37.5",
|
||||
"execa": "5.1.1",
|
||||
"fs-extra": "11.2.0",
|
||||
"glob": "11.0.2",
|
||||
"glob": "11.0.3",
|
||||
"gulp": "4.0.2",
|
||||
"husky": "9.1.7",
|
||||
"lerna": "3.22.1",
|
||||
"lint-staged": "15.5.2",
|
||||
"madge": "8.0.0",
|
||||
"npm-package-json-lint": "8.0.0",
|
||||
"typescript": "5.8.2"
|
||||
"typescript": "5.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/fs-extra": "11.0.4",
|
||||
@@ -101,7 +101,6 @@
|
||||
"packageManager": "yarn@4.9.2",
|
||||
"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.57.1#./.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",
|
||||
"nanoid": "patch:nanoid@npm%3A3.3.7#./.yarn/patches/nanoid-npm-3.3.7-98824ba130.patch",
|
||||
@@ -118,6 +117,12 @@
|
||||
"pdfjs-dist@*": "patch:pdfjs-dist@npm%3A3.11.174#./.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch",
|
||||
"pdfjs-dist@3.11.174": "patch:pdfjs-dist@npm%3A3.11.174#./.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch",
|
||||
"canvas@npm:^2.11.2": "link:./.yarn/joplin-empty-package/",
|
||||
"node-gyp@npm:^9.0.0": "11.2.0"
|
||||
"node-gyp@npm:^9.0.0": "11.2.0",
|
||||
"depd@npm:^2.0.0": "patch:depd@npm%3A2.0.0#~/.yarn/patches/depd-npm-2.0.0-b6c51a4b43.patch",
|
||||
"depd@npm:~2.0.0": "patch:depd@npm%3A2.0.0#~/.yarn/patches/depd-npm-2.0.0-b6c51a4b43.patch",
|
||||
"depd@npm:~1.1.2": "patch:depd@npm%3A2.0.0#~/.yarn/patches/depd-npm-2.0.0-b6c51a4b43.patch",
|
||||
"depd@npm:2.0.0": "patch:depd@npm%3A2.0.0#~/.yarn/patches/depd-npm-2.0.0-b6c51a4b43.patch",
|
||||
"depd@npm:^1.1.2": "patch:depd@npm%3A2.0.0#~/.yarn/patches/depd-npm-2.0.0-b6c51a4b43.patch",
|
||||
"depd@npm:^1.1.0": "patch:depd@npm%3A2.0.0#~/.yarn/patches/depd-npm-2.0.0-b6c51a4b43.patch"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,6 +380,13 @@ class AppGui {
|
||||
this.widget('noteList').toggleShowIds();
|
||||
}
|
||||
|
||||
toggleFolderCollapse() {
|
||||
const folderList = this.widget('folderList');
|
||||
if (folderList && folderList.toggleFolderCollapse) {
|
||||
folderList.toggleFolderCollapse();
|
||||
}
|
||||
}
|
||||
|
||||
widget(name) {
|
||||
if (name === 'root') return this.rootWidget_;
|
||||
return this.rootWidget_.childByName(name);
|
||||
@@ -506,6 +513,8 @@ class AppGui {
|
||||
this.toggleNoteMetadata();
|
||||
} else if (cmd === 'toggle_ids') {
|
||||
this.toggleFolderIds();
|
||||
} else if (cmd === 'toggle_folder_collapse') {
|
||||
this.toggleFolderCollapse();
|
||||
} else if (cmd === 'enter_command_line_mode') {
|
||||
const cmd = await this.widget('statusBar').prompt();
|
||||
if (!cmd) return;
|
||||
|
||||
@@ -220,6 +220,7 @@ class Application extends BaseApplication {
|
||||
return { ...this.commandMetadata_ };
|
||||
}
|
||||
|
||||
|
||||
public hasGui() {
|
||||
return this.gui() && !this.gui().isDummy();
|
||||
}
|
||||
@@ -330,6 +331,7 @@ class Application extends BaseApplication {
|
||||
{ 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 },
|
||||
{ keys: ['z'], type: 'function', command: 'toggle_folder_collapse' },
|
||||
];
|
||||
|
||||
// Filter the keymap item by command so that items in keymap.json can override
|
||||
@@ -415,8 +417,10 @@ class Application extends BaseApplication {
|
||||
if (argv.length) {
|
||||
this.gui_ = this.dummyGui();
|
||||
|
||||
const initialFolder = await Folder.load(Setting.value('activeFolderId'));
|
||||
await this.switchCurrentFolder(initialFolder);
|
||||
await this.applySettingsSideEffects();
|
||||
await this.refreshCurrentFolder();
|
||||
|
||||
try {
|
||||
await this.execCommand(argv);
|
||||
} catch (error) {
|
||||
@@ -430,6 +434,7 @@ class Application extends BaseApplication {
|
||||
}
|
||||
|
||||
await Setting.saveAll();
|
||||
await this.database_.close();
|
||||
|
||||
// Need to call exit() explicitly, otherwise Node wait for any timeout to complete
|
||||
// https://stackoverflow.com/questions/18050095
|
||||
|
||||
@@ -2,33 +2,44 @@
|
||||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
const fs = require('fs-extra');
|
||||
const Logger = require('@joplin/utils/Logger').default;
|
||||
const { dirname } = require('@joplin/lib/path-utils');
|
||||
import * as fs from 'fs-extra';
|
||||
import Logger, { TargetType } from '@joplin/utils/Logger';
|
||||
import { dirname } from '@joplin/lib/path-utils';
|
||||
const { DatabaseDriverNode } = require('@joplin/lib/database-driver-node.js');
|
||||
const JoplinDatabase = require('@joplin/lib/JoplinDatabase').default;
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
const Folder = require('@joplin/lib/models/Folder').default;
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
const Setting = require('@joplin/lib/models/Setting').default;
|
||||
import JoplinDatabase from '@joplin/lib/JoplinDatabase';
|
||||
import BaseModel from '@joplin/lib/BaseModel';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
const { sprintf } = require('sprintf-js');
|
||||
const exec = require('child_process').exec;
|
||||
const nodeSqlite = require('sqlite3');
|
||||
const { loadKeychainServiceAndSettings } = require('@joplin/lib/services/SettingUtils');
|
||||
const { default: shimInitCli } = require('./utils/shimInitCli');
|
||||
|
||||
const baseDir = `${dirname(__dirname)}/tests/cli-integration`;
|
||||
const joplinAppPath = `${__dirname}/main.js`;
|
||||
|
||||
shimInitCli({ nodeSqlite, appVersion: () => require('../package.json').version, keytar: null });
|
||||
require('@joplin/lib/testing/test-utils');
|
||||
|
||||
const logger = new Logger();
|
||||
logger.addTarget('console');
|
||||
logger.addTarget(TargetType.Console);
|
||||
logger.setLevel(Logger.LEVEL_ERROR);
|
||||
|
||||
const dbLogger = new Logger();
|
||||
dbLogger.addTarget('console');
|
||||
dbLogger.addTarget(TargetType.Console);
|
||||
dbLogger.setLevel(Logger.LEVEL_INFO);
|
||||
|
||||
const db = new JoplinDatabase(new DatabaseDriverNode());
|
||||
db.setLogger(dbLogger);
|
||||
|
||||
function createClient(id) {
|
||||
interface Client {
|
||||
id: number;
|
||||
profileDir: string;
|
||||
}
|
||||
|
||||
function createClient(id: number): Client {
|
||||
return {
|
||||
id: id,
|
||||
profileDir: `${baseDir}/client${id}`,
|
||||
@@ -37,13 +48,13 @@ function createClient(id) {
|
||||
|
||||
const client = createClient(1);
|
||||
|
||||
function execCommand(client, command) {
|
||||
function execCommand(client: Client, command: string) {
|
||||
const exePath = `node ${joplinAppPath}`;
|
||||
const cmd = `${exePath} --update-geolocation-disabled --env dev --profile ${client.profileDir} ${command}`;
|
||||
logger.info(`${client.id}: ${command}`);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(cmd, (error, stdout, stderr) => {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
exec(cmd, (error: string, stdout: string, stderr: string) => {
|
||||
if (error) {
|
||||
logger.error(stderr);
|
||||
reject(error);
|
||||
@@ -54,17 +65,17 @@ function execCommand(client, command) {
|
||||
});
|
||||
}
|
||||
|
||||
function assertTrue(v) {
|
||||
function assertTrue(v: unknown) {
|
||||
if (!v) throw new Error(sprintf('Expected "true", got "%s"."', v));
|
||||
process.stdout.write('.');
|
||||
}
|
||||
|
||||
function assertFalse(v) {
|
||||
function assertFalse(v: unknown) {
|
||||
if (v) throw new Error(sprintf('Expected "false", got "%s"."', v));
|
||||
process.stdout.write('.');
|
||||
}
|
||||
|
||||
function assertEquals(expected, real) {
|
||||
function assertEquals(expected: unknown, real: unknown) {
|
||||
if (expected !== real) throw new Error(sprintf('Expecting "%s", got "%s"', expected, real));
|
||||
process.stdout.write('.');
|
||||
}
|
||||
@@ -73,7 +84,7 @@ async function clearDatabase() {
|
||||
await db.transactionExecBatch(['DELETE FROM folders', 'DELETE FROM notes', 'DELETE FROM tags', 'DELETE FROM note_tags', 'DELETE FROM resources', 'DELETE FROM deleted_items']);
|
||||
}
|
||||
|
||||
const testUnits = {};
|
||||
const testUnits: Record<string, ()=> Promise<void>> = {};
|
||||
|
||||
testUnits.testFolders = async () => {
|
||||
await execCommand(client, 'mkbook nb1');
|
||||
@@ -85,10 +96,16 @@ testUnits.testFolders = async () => {
|
||||
await execCommand(client, 'mkbook nb1');
|
||||
|
||||
folders = await Folder.all();
|
||||
assertEquals(1, folders.length);
|
||||
assertEquals(2, folders.length);
|
||||
assertEquals('nb1', folders[0].title);
|
||||
assertEquals('nb1', folders[1].title);
|
||||
|
||||
await execCommand(client, 'rm -r -f nb1');
|
||||
await execCommand(client, 'rmbook -p -f nb1');
|
||||
|
||||
folders = await Folder.all();
|
||||
assertEquals(1, folders.length);
|
||||
|
||||
await execCommand(client, 'rmbook -p -f nb1');
|
||||
|
||||
folders = await Folder.all();
|
||||
assertEquals(0, folders.length);
|
||||
@@ -102,7 +119,7 @@ testUnits.testNotes = async () => {
|
||||
assertEquals(1, notes.length);
|
||||
assertEquals('n1', notes[0].title);
|
||||
|
||||
await execCommand(client, 'rm -f n1');
|
||||
await execCommand(client, 'rmnote -p -f n1');
|
||||
notes = await Note.all();
|
||||
assertEquals(0, notes.length);
|
||||
|
||||
@@ -112,12 +129,19 @@ testUnits.testNotes = async () => {
|
||||
notes = await Note.all();
|
||||
assertEquals(2, notes.length);
|
||||
|
||||
await execCommand(client, 'rm -f \'blabla*\'');
|
||||
// Should fail to delete a non-existent note
|
||||
let failed = false;
|
||||
try {
|
||||
await execCommand(client, 'rmnote -f \'blabla*\'');
|
||||
} catch (error) {
|
||||
failed = true;
|
||||
}
|
||||
assertEquals(failed, true);
|
||||
|
||||
notes = await Note.all();
|
||||
assertEquals(2, notes.length);
|
||||
|
||||
await execCommand(client, 'rm -f \'n*\'');
|
||||
await execCommand(client, 'rmnote -f -p \'n*\'');
|
||||
|
||||
notes = await Note.all();
|
||||
assertEquals(0, notes.length);
|
||||
@@ -140,10 +164,12 @@ testUnits.testCat = async () => {
|
||||
|
||||
testUnits.testConfig = async () => {
|
||||
await execCommand(client, 'config editor vim');
|
||||
await Setting.reset();
|
||||
await Setting.load();
|
||||
assertEquals('vim', Setting.value('editor'));
|
||||
|
||||
await execCommand(client, 'config editor subl');
|
||||
await Setting.reset();
|
||||
await Setting.load();
|
||||
assertEquals('subl', Setting.value('editor'));
|
||||
|
||||
@@ -201,15 +227,47 @@ testUnits.testMv = async () => {
|
||||
await execCommand(client, 'mknote note2');
|
||||
await execCommand(client, 'mknote note3');
|
||||
await execCommand(client, 'mknote blabla');
|
||||
await execCommand(client, 'mv \'note*\' nb2');
|
||||
|
||||
notes1 = await Note.previews(f1.id);
|
||||
notes2 = await Note.previews(f2.id);
|
||||
|
||||
assertEquals(4, notes1.length);
|
||||
assertEquals(1, notes2.length);
|
||||
|
||||
await execCommand(client, 'mv \'note*\' nb2');
|
||||
|
||||
notes2 = await Note.previews(f2.id);
|
||||
notes1 = await Note.previews(f1.id);
|
||||
|
||||
assertEquals(1, notes1.length);
|
||||
assertEquals(4, notes2.length);
|
||||
};
|
||||
|
||||
testUnits.testUse = async () => {
|
||||
await execCommand(client, 'mkbook nb1');
|
||||
await execCommand(client, 'mkbook nb2');
|
||||
await execCommand(client, 'mknote n1');
|
||||
await execCommand(client, 'mknote n2');
|
||||
|
||||
const f1 = await Folder.loadByTitle('nb1');
|
||||
const f2 = await Folder.loadByTitle('nb2');
|
||||
let notes1 = await Note.previews(f1.id);
|
||||
let notes2 = await Note.previews(f2.id);
|
||||
|
||||
assertEquals(0, notes1.length);
|
||||
assertEquals(2, notes2.length);
|
||||
|
||||
await execCommand(client, 'use nb1');
|
||||
await execCommand(client, 'mknote note2');
|
||||
await execCommand(client, 'mknote note3');
|
||||
|
||||
notes1 = await Note.previews(f1.id);
|
||||
notes2 = await Note.previews(f2.id);
|
||||
|
||||
assertEquals(2, notes1.length);
|
||||
assertEquals(2, notes2.length);
|
||||
};
|
||||
|
||||
async function main() {
|
||||
await fs.remove(baseDir);
|
||||
|
||||
@@ -217,7 +275,9 @@ async function main() {
|
||||
|
||||
await db.open({ name: `${client.profileDir}/database.sqlite` });
|
||||
BaseModel.setDb(db);
|
||||
await Setting.load();
|
||||
Setting.setConstant('rootProfileDir', client.profileDir);
|
||||
Setting.setConstant('profileDir', client.profileDir);
|
||||
await loadKeychainServiceAndSettings([]);
|
||||
|
||||
let onlyThisTest = 'testMv';
|
||||
onlyThisTest = '';
|
||||
@@ -234,7 +294,7 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
main(process.argv).catch(error => {
|
||||
main().catch(error => {
|
||||
console.info('');
|
||||
logger.error(error);
|
||||
});
|
||||
@@ -6,6 +6,7 @@ import app from './app';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { ImportOptions } from '@joplin/lib/services/interop/types';
|
||||
import { unique } from '@joplin/lib/array';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
|
||||
class Command extends BaseCommand {
|
||||
public override usage() {
|
||||
@@ -32,14 +33,16 @@ class Command extends BaseCommand {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
public override async action(args: any) {
|
||||
const folder = await app().loadItem(BaseModel.TYPE_FOLDER, args.notebook);
|
||||
let destinationFolder = await app().loadItem(BaseModel.TYPE_FOLDER, args.notebook);
|
||||
|
||||
if (args.notebook && !folder) throw new Error(_('Cannot find "%s".', args.notebook));
|
||||
if (args.notebook && !destinationFolder) throw new Error(_('Cannot find "%s".', args.notebook));
|
||||
|
||||
if (!destinationFolder) destinationFolder = await Folder.defaultFolder();
|
||||
|
||||
const importOptions: ImportOptions = {};
|
||||
importOptions.path = args.path;
|
||||
importOptions.format = args.options.format ? args.options.format : 'auto';
|
||||
importOptions.destinationFolderId = folder ? folder.id : null;
|
||||
importOptions.destinationFolderId = destinationFolder ? destinationFolder.id : null;
|
||||
|
||||
let lastProgress = '';
|
||||
|
||||
|
||||
104
packages/app-cli/app/command-publish.test.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import ShareService from '@joplin/lib/services/share/ShareService';
|
||||
import mockShareService from '@joplin/lib/testing/share/mockShareService';
|
||||
import { createFolderTree, setupDatabaseAndSynchronizer, switchClient, waitFor } from '@joplin/lib/testing/test-utils';
|
||||
import { setupApplication, setupCommandForTesting } from './utils/testUtils';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
const Command = require('./command-publish');
|
||||
|
||||
const setUpCommand = () => {
|
||||
const onStdout = jest.fn();
|
||||
const command = setupCommandForTesting(Command, onStdout);
|
||||
|
||||
return { command, onStdout };
|
||||
};
|
||||
|
||||
describe('command-publish', () => {
|
||||
beforeEach(async () => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
await switchClient(1);
|
||||
await setupApplication();
|
||||
|
||||
mockShareService({
|
||||
getShares: async () => {
|
||||
return { items: [] };
|
||||
},
|
||||
postShares: async () => ({ id: 'test-id' }),
|
||||
getShareInvitations: async () => null,
|
||||
}, ShareService.instance());
|
||||
});
|
||||
|
||||
test('should publish a note', async () => {
|
||||
const { command, onStdout } = setUpCommand();
|
||||
|
||||
const testFolder = await Folder.save({ title: 'Test' });
|
||||
const testNote = await Note.save({ title: 'test', parent_id: testFolder.id });
|
||||
|
||||
await command.action({
|
||||
note: testNote.id,
|
||||
options: {
|
||||
force: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Should be shared
|
||||
await waitFor(async () => {
|
||||
expect(await Note.load(testNote.id)).toMatchObject({
|
||||
is_shared: 1,
|
||||
});
|
||||
});
|
||||
|
||||
// Should have logged the publication URL
|
||||
expect(onStdout).toHaveBeenCalled();
|
||||
expect(onStdout.mock.lastCall[0]).toMatch(/Published at URL:/);
|
||||
});
|
||||
|
||||
test('should be enabled for Joplin Server and Cloud sync targets', () => {
|
||||
const { command } = setUpCommand();
|
||||
|
||||
Setting.setValue('sync.target', 1);
|
||||
expect(command.enabled()).toBe(false);
|
||||
|
||||
const supportedSyncTargets = [9, 10, 11];
|
||||
for (const id of supportedSyncTargets) {
|
||||
Setting.setValue('sync.target', id);
|
||||
expect(command.enabled()).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
test('should not ask for confirmation if a note is already published', async () => {
|
||||
const { command } = setUpCommand();
|
||||
|
||||
const promptMock = jest.fn(() => true);
|
||||
command.setPrompt(promptMock);
|
||||
|
||||
await createFolderTree('', [
|
||||
{
|
||||
title: 'folder 1',
|
||||
children: [
|
||||
{
|
||||
title: 'note 1',
|
||||
body: 'test',
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
const noteId = (await Note.loadByTitle('note 1')).id;
|
||||
|
||||
// Should ask for confirmation when first sharing
|
||||
await command.action({
|
||||
note: noteId,
|
||||
options: { },
|
||||
});
|
||||
expect(promptMock).toHaveBeenCalledTimes(1);
|
||||
expect(await Note.load(noteId)).toMatchObject({ is_shared: 1 });
|
||||
|
||||
// Should not ask for confirmation if called again for the same note
|
||||
await command.action({
|
||||
note: noteId,
|
||||
options: { },
|
||||
});
|
||||
expect(promptMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
64
packages/app-cli/app/command-publish.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import BaseCommand from './base-command';
|
||||
import app from './app';
|
||||
import Logger from '@joplin/utils/Logger';
|
||||
import ShareService from '@joplin/lib/services/share/ShareService';
|
||||
import { ModelType } from '@joplin/lib/BaseModel';
|
||||
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import { reg } from '@joplin/lib/registry';
|
||||
|
||||
const logger = Logger.create('command-publish');
|
||||
|
||||
type Args = {
|
||||
note: string;
|
||||
options: {
|
||||
force?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
class Command extends BaseCommand {
|
||||
public usage() {
|
||||
return 'publish [note]';
|
||||
}
|
||||
|
||||
public description() {
|
||||
return _('Publishes a note to Joplin Server or Joplin Cloud');
|
||||
}
|
||||
|
||||
public options() {
|
||||
return [
|
||||
['-f, --force', _('Do not ask for user confirmation.')],
|
||||
];
|
||||
}
|
||||
|
||||
public enabled() {
|
||||
return SyncTargetRegistry.isJoplinServerOrCloud(Setting.value('sync.target'));
|
||||
}
|
||||
|
||||
public async action(args: Args) {
|
||||
const targetNote = await app().loadItemOrFail(ModelType.Note, args.note);
|
||||
const parent = await app().loadItem(ModelType.Folder, targetNote.parent_id);
|
||||
|
||||
const force = args.options.force;
|
||||
const alreadyShared = !!targetNote.is_shared;
|
||||
const ok = force || alreadyShared ? true : await this.prompt(
|
||||
_('Publish note "%s" (in notebook "%s")?', targetNote.title, parent.title ?? '<root>'),
|
||||
{ booleanAnswerDefault: 'n' },
|
||||
);
|
||||
if (!ok) return;
|
||||
|
||||
logger.info('Share note: ', targetNote.id);
|
||||
const share = await ShareService.instance().shareNote(targetNote.id, false);
|
||||
|
||||
this.stdout(_('Synchronising...'));
|
||||
await reg.waitForSyncFinishedThenSync();
|
||||
|
||||
const userId = ShareService.instance().userId;
|
||||
const shareUrl = ShareService.instance().shareUrl(userId, share);
|
||||
this.stdout(_('Published at URL: %s', shareUrl));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Command;
|
||||
@@ -149,6 +149,7 @@ class Command extends BaseCommand {
|
||||
waiting: invitation.status === ShareUserStatus.Waiting,
|
||||
rejected: invitation.status === ShareUserStatus.Rejected,
|
||||
folderId: invitation.share.folder_id,
|
||||
canWrite: !!invitation.can_write,
|
||||
fromUser: {
|
||||
email: invitation.share.user?.email,
|
||||
},
|
||||
|
||||
43
packages/app-cli/app/command-unpublish.test.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import ShareService from '@joplin/lib/services/share/ShareService';
|
||||
import mockShareService from '@joplin/lib/testing/share/mockShareService';
|
||||
import { setupDatabaseAndSynchronizer, switchClient, waitFor } from '@joplin/lib/testing/test-utils';
|
||||
import { setupApplication, setupCommandForTesting } from './utils/testUtils';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
const Command = require('./command-unpublish');
|
||||
|
||||
|
||||
describe('command-unpublish', () => {
|
||||
beforeEach(async () => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
await switchClient(1);
|
||||
await setupApplication();
|
||||
|
||||
mockShareService({
|
||||
getShares: async () => {
|
||||
return { items: [{ id: 'test-id' }] };
|
||||
},
|
||||
postShares: async () => {
|
||||
throw new Error('Unexpected call to postShares');
|
||||
},
|
||||
getShareInvitations: async () => null,
|
||||
}, ShareService.instance());
|
||||
});
|
||||
|
||||
test('should unpublish a note', async () => {
|
||||
const command = setupCommandForTesting(Command, ()=>{});
|
||||
|
||||
const testFolder = await Folder.save({ title: 'Test' });
|
||||
const testNote = await Note.save({ title: 'test', parent_id: testFolder.id, is_shared: 1 });
|
||||
|
||||
await command.action({
|
||||
note: testNote.id,
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(await Note.load(testNote.id)).toMatchObject({
|
||||
is_shared: 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
57
packages/app-cli/app/command-unpublish.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import BaseCommand from './base-command';
|
||||
import app from './app';
|
||||
import Logger from '@joplin/utils/Logger';
|
||||
import ShareService from '@joplin/lib/services/share/ShareService';
|
||||
import { ModelType } from '@joplin/lib/BaseModel';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import { reg } from '@joplin/lib/registry';
|
||||
|
||||
const logger = Logger.create('command-unpublish');
|
||||
|
||||
type Args = {
|
||||
note: string;
|
||||
};
|
||||
|
||||
class Command extends BaseCommand {
|
||||
public usage() {
|
||||
return 'publish [note]';
|
||||
}
|
||||
|
||||
public description() {
|
||||
return _('Publishes a note to Joplin Server or Joplin Cloud');
|
||||
}
|
||||
|
||||
public options() {
|
||||
return [
|
||||
['-f, --force', _('Do not ask for user confirmation.')],
|
||||
];
|
||||
}
|
||||
|
||||
public enabled() {
|
||||
return SyncTargetRegistry.isJoplinServerOrCloud(Setting.value('sync.target'));
|
||||
}
|
||||
|
||||
public async action(args: Args) {
|
||||
const targetNote = await app().loadItemOrFail(ModelType.Note, args.note);
|
||||
|
||||
if (!targetNote.is_shared) {
|
||||
throw new Error(_('Note not published: %s', targetNote.title));
|
||||
}
|
||||
|
||||
logger.info('Unshare note: ', targetNote.id);
|
||||
await ShareService.instance().unshareNote(targetNote.id);
|
||||
|
||||
const note = await Note.load(targetNote.id);
|
||||
if (note.is_shared) {
|
||||
throw new Error('Assertion failure: The note is still shared.');
|
||||
}
|
||||
|
||||
this.stdout(_('Synchronising...'));
|
||||
await reg.waitForSyncFinishedThenSync();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Command;
|
||||
@@ -2,6 +2,7 @@ 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';
|
||||
|
||||
class Command extends BaseCommand {
|
||||
public override usage() {
|
||||
@@ -20,6 +21,18 @@ class Command extends BaseCommand {
|
||||
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']));
|
||||
|
||||
// Auto-expand parent folders in GUI if present
|
||||
if (app().gui() && app().gui().widget && app().gui().widget('folderList')) {
|
||||
const folderListWidget = app().gui().widget('folderList');
|
||||
if (folderListWidget.expandToFolder) {
|
||||
// Get all folders to pass to expandToFolder
|
||||
const folders = await Folder.all();
|
||||
folderListWidget.folders = folders; // Ensure widget has current folders
|
||||
folderListWidget.expandToFolder(folder.id);
|
||||
}
|
||||
}
|
||||
|
||||
app().switchCurrentFolder(folder);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,14 @@ import BaseModel from '@joplin/lib/BaseModel';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { FolderEntity } from '@joplin/lib/services/database/types';
|
||||
import { getDisplayParentId, getTrashFolderId } from '@joplin/lib/services/trash';
|
||||
import {
|
||||
getDisplayParentId,
|
||||
getTrashFolderId,
|
||||
} from '@joplin/lib/services/trash';
|
||||
const ListWidget = require('tkwidgets/ListWidget.js');
|
||||
|
||||
export default class FolderListWidget extends ListWidget {
|
||||
|
||||
export default class FolderListWidget extends ListWidget {
|
||||
private folders_: FolderEntity[] = [];
|
||||
|
||||
public constructor() {
|
||||
@@ -31,7 +34,18 @@ export default class FolderListWidget extends ListWidget {
|
||||
if (item === '-') {
|
||||
output.push('-'.repeat(this.innerWidth));
|
||||
} else if (item.type_ === Folder.modelType()) {
|
||||
output.push(' '.repeat(this.folderDepth(this.folders, item.id)));
|
||||
const depth = this.folderDepth(this.folders, item.id);
|
||||
output.push(' '.repeat(depth));
|
||||
|
||||
// Add collapse/expand indicator
|
||||
const hasChildren = this.folderHasChildren_(this.folders, item.id);
|
||||
if (hasChildren) {
|
||||
const collapsedFolders = Setting.value('collapsedFolderIds');
|
||||
const isCollapsed = collapsedFolders.includes(item.id);
|
||||
output.push(isCollapsed ? '[+] ' : '[-] ');
|
||||
} else {
|
||||
output.push(' '); // Space for alignment
|
||||
}
|
||||
|
||||
if (this.showIds) {
|
||||
output.push(Folder.shortId(item.id));
|
||||
@@ -65,7 +79,10 @@ export default class FolderListWidget extends ListWidget {
|
||||
let output = 0;
|
||||
while (true) {
|
||||
const folder = BaseModel.byId(folders, folderId);
|
||||
const folderParentId = getDisplayParentId(folder, folders.find(f => f.id === folder.parent_id));
|
||||
const folderParentId = getDisplayParentId(
|
||||
folder,
|
||||
folders.find((f) => f.id === folder.parent_id),
|
||||
);
|
||||
if (!folder || !folderParentId) return output;
|
||||
output++;
|
||||
folderId = folderParentId;
|
||||
@@ -153,7 +170,10 @@ export default class FolderListWidget extends ListWidget {
|
||||
public folderHasChildren_(folders: FolderEntity[], folderId: string) {
|
||||
for (let i = 0; i < folders.length; i++) {
|
||||
const folder = folders[i];
|
||||
const folderParentId = getDisplayParentId(folder, folders.find(f => f.id === folder.parent_id));
|
||||
const folderParentId = getDisplayParentId(
|
||||
folder,
|
||||
folders.find((f) => f.id === folder.parent_id),
|
||||
);
|
||||
if (folderParentId === folderId) return true;
|
||||
}
|
||||
return false;
|
||||
@@ -161,7 +181,12 @@ export default class FolderListWidget extends ListWidget {
|
||||
|
||||
public render() {
|
||||
if (this.updateItems_) {
|
||||
this.logger().debug('Rebuilding items...', this.notesParentType, this.selectedJoplinItemId, this.selectedSearchId);
|
||||
this.logger().debug(
|
||||
'Rebuilding items...',
|
||||
this.notesParentType,
|
||||
this.selectedJoplinItemId,
|
||||
this.selectedSearchId,
|
||||
);
|
||||
const wasSelectedItemId = this.selectedJoplinItemId;
|
||||
const previousParentType = this.notesParentType;
|
||||
|
||||
@@ -170,12 +195,20 @@ export default class FolderListWidget extends ListWidget {
|
||||
const orderFolders = (parentId: string) => {
|
||||
for (let i = 0; i < this.folders.length; i++) {
|
||||
const f = this.folders[i];
|
||||
const originalParent = this.folders_.find(f => f.id === f.parent_id);
|
||||
const originalParent = this.folders_.find(
|
||||
(f) => f.id === f.parent_id,
|
||||
);
|
||||
|
||||
const folderParentId = getDisplayParentId(f, originalParent); // f.parent_id ? f.parent_id : '';
|
||||
if (folderParentId === parentId) {
|
||||
newItems.push(f);
|
||||
if (this.folderHasChildren_(this.folders, f.id)) orderFolders(f.id);
|
||||
// Only recurse into children if the folder is not collapsed
|
||||
if (this.folderHasChildren_(this.folders, f.id)) {
|
||||
const collapsedFolders = Setting.value('collapsedFolderIds');
|
||||
if (!collapsedFolders.includes(f.id)) {
|
||||
orderFolders(f.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -221,4 +254,53 @@ export default class FolderListWidget extends ListWidget {
|
||||
const index = this.itemIndexByKey('id', itemId);
|
||||
this.currentIndex = index >= 0 ? index : 0;
|
||||
}
|
||||
|
||||
public toggleFolderCollapse() {
|
||||
const item = this.currentItem;
|
||||
if (item && item.type_ === Folder.modelType() && this.folderHasChildren_(this.folders, item.id)) {
|
||||
const collapsedFolders = Setting.value('collapsedFolderIds');
|
||||
const isCollapsed = collapsedFolders.includes(item.id);
|
||||
if (isCollapsed) {
|
||||
const newCollapsed = collapsedFolders.filter((id: string) => id !== item.id);
|
||||
Setting.setValue('collapsedFolderIds', newCollapsed);
|
||||
} else {
|
||||
Setting.setValue('collapsedFolderIds', [...collapsedFolders, item.id]);
|
||||
}
|
||||
this.updateItems_ = true;
|
||||
this.invalidate();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public expandToFolder(folderId: string) {
|
||||
// Find all parent folders and expand them
|
||||
const parentsToExpand: string[] = [];
|
||||
let currentId = folderId;
|
||||
|
||||
while (currentId) {
|
||||
const folder = BaseModel.byId(this.folders, currentId);
|
||||
if (!folder) break;
|
||||
|
||||
const parentId = getDisplayParentId(
|
||||
folder,
|
||||
this.folders.find((f) => f.id === folder.parent_id),
|
||||
);
|
||||
if (parentId) {
|
||||
parentsToExpand.unshift(parentId);
|
||||
currentId = parentId;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Expand all parent folders
|
||||
const collapsedFolders = Setting.value('collapsedFolderIds');
|
||||
const newCollapsed = collapsedFolders.filter((id: string) => !parentsToExpand.includes(id));
|
||||
Setting.setValue('collapsedFolderIds', newCollapsed);
|
||||
|
||||
this.updateItems_ = true;
|
||||
this.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,15 +35,15 @@
|
||||
],
|
||||
"owner": "Laurent Cozic"
|
||||
},
|
||||
"version": "3.4.0",
|
||||
"version": "3.5.1",
|
||||
"bin": "./main.js",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@joplin/lib": "~3.4",
|
||||
"@joplin/renderer": "~3.4",
|
||||
"@joplin/utils": "~3.4",
|
||||
"@joplin/lib": "~3.5",
|
||||
"@joplin/renderer": "~3.5",
|
||||
"@joplin/utils": "~3.5",
|
||||
"aws-sdk": "2.1340.0",
|
||||
"chalk": "4.1.2",
|
||||
"compare-version": "0.1.2",
|
||||
@@ -57,7 +57,7 @@
|
||||
"proper-lockfile": "4.1.2",
|
||||
"redux": "4.2.1",
|
||||
"server-destroy": "1.0.1",
|
||||
"sharp": "0.34.1",
|
||||
"sharp": "0.34.3",
|
||||
"sprintf-js": "1.1.3",
|
||||
"sqlite3": "5.1.6",
|
||||
"string-padding": "1.0.2",
|
||||
@@ -70,14 +70,14 @@
|
||||
"yargs-parser": "21.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@joplin/tools": "~3.4",
|
||||
"@joplin/tools": "~3.5",
|
||||
"@types/fs-extra": "11.0.4",
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/node": "18.19.100",
|
||||
"@types/node": "18.19.130",
|
||||
"@types/proper-lockfile": "^4.1.2",
|
||||
"gulp": "4.0.2",
|
||||
"jest": "29.7.0",
|
||||
"temp": "0.9.4",
|
||||
"typescript": "5.8.2"
|
||||
"typescript": "5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<en-note>
|
||||
<div>
|
||||
<en-media style="--en-viewerProps:{};" type="image/jpeg" hash="e2d4887c5a32ab1686276c7c5ae733ef" width="1.125in" />
|
||||
</div>
|
||||
<div>
|
||||
<br />
|
||||
</div>
|
||||
</en-note>
|
||||
@@ -0,0 +1,8 @@
|
||||
<en-note>
|
||||
<div>
|
||||
<img src=":/e2d4887c5a32ab1686276c7c5ae733ef" style="--en-viewerProps:{};" type="image/jpeg" hash="e2d4887c5a32ab1686276c7c5ae733ef" width="108" alt="attachment-image" />
|
||||
</div>
|
||||
<div>
|
||||
<br/>
|
||||
</div>
|
||||
</en-note>
|
||||
@@ -1,6 +1,8 @@
|
||||
<en-note>
|
||||
<div><a href=":/21ca2b948f222a38802940ec7e2e5de3" hash="21ca2b948f222a38802940ec7e2e5de3" type="application/pdf" style="cursor:pointer;" alt="attachment-1">attachment-1</a></div>
|
||||
<div>
|
||||
<br>
|
||||
<a href=':/21ca2b948f222a38802940ec7e2e5de3' hash="21ca2b948f222a38802940ec7e2e5de3" type="application/pdf" style="cursor:pointer;" alt="attachment-1"> attachment-1</a>
|
||||
</div>
|
||||
<div>
|
||||
<br/>
|
||||
</div>
|
||||
</en-note>
|
||||
@@ -1,16 +1,11 @@
|
||||
<en-note>
|
||||
<div>
|
||||
<p>For example, consider an exported Evernote list with todo checkboxes like this:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<div><input checked="checked" type="checkbox" onclick="return false;">Foo</div>
|
||||
</li>
|
||||
<li>
|
||||
<div><input type="checkbox" onclick="return false;"><b>Bar</b></div>
|
||||
</li>
|
||||
<li>
|
||||
<div><input type="checkbox" onclick="return false;"><i>Baz</i></div>
|
||||
</li>
|
||||
<li><div><input checked="checked" type="checkbox" onclick="return false;" />Foo</div></li>
|
||||
<li><div><input type="checkbox" onclick="return false;" /><b>Bar</b></div></li>
|
||||
<li><div><input type="checkbox" onclick="return false;" /><i>Baz</i></div></li>
|
||||
</ul>
|
||||
</div>
|
||||
</en-note>
|
||||
@@ -1,19 +1,11 @@
|
||||
<en-note>
|
||||
<div>
|
||||
<p>In Evernote a checklist is not the same as a list with checkboxes.</p>
|
||||
<ul style="--en-todo:true;">
|
||||
<li style="--en-checked:false;">
|
||||
<input type="checkbox" onclick="return false;">
|
||||
<div>One</div>
|
||||
</li>
|
||||
<li style="--en-checked:true;">
|
||||
<input checked="checked" type="checkbox" onclick="return false;">
|
||||
<div>Two</div>
|
||||
</li>
|
||||
<li style="--en-checked:false;">
|
||||
<input type="checkbox" onclick="return false;">
|
||||
<div>Three</div>
|
||||
</li>
|
||||
|
||||
<ul STYLE="--en-todo:true;">
|
||||
<li STYLE="--en-checked:false;"> <input type="checkbox" onclick="return false;" /><div>One</div></li>
|
||||
<li STYLE="--en-checked:true;"> <input checked="checked" type="checkbox" onclick="return false;" /><div>Two</div>
|
||||
</li><li STYLE="--en-checked:false;"> <input type="checkbox" onclick="return false;" /><div>Three</div></li>
|
||||
</ul>
|
||||
</div>
|
||||
</en-note>
|
||||
@@ -1,12 +1 @@
|
||||
<en-note>
|
||||
<div>
|
||||
<audio controls="" preload="none" style="width:480px;">
|
||||
<source src=":/9168ee833d03c5ea7c730ac6673978c1" type="audio/mp4">
|
||||
<p>Your browser does not support HTML5 audio.</p>
|
||||
</audio>
|
||||
<p><a href=":/9168ee833d03c5ea7c730ac6673978c1">audio test</a></p>
|
||||
</div>
|
||||
<div>
|
||||
<br>
|
||||
</div>
|
||||
</en-note>
|
||||
<en-note><div><audio controls preload="none" style="width:480px;"><source src=":/9168ee833d03c5ea7c730ac6673978c1" type="audio/mp4" /><p>Your browser does not support HTML5 audio.</p></audio><p><a href=":/9168ee833d03c5ea7c730ac6673978c1">audio test</a></p></div><div><br/></div></en-note>
|
||||
@@ -1,12 +1 @@
|
||||
<en-note>
|
||||
<div><input type="checkbox" onclick="return false;">This is a test</div>
|
||||
<div><input type="checkbox" onclick="return false;">A test for <span style="font-weight: bold;">bold</span></div>
|
||||
<div>
|
||||
<input type="checkbox" onclick="return false;">A test for <i>italic</i>
|
||||
<br>
|
||||
</div>
|
||||
<div>
|
||||
<br>
|
||||
</div>
|
||||
<div><i><img src=":/89ce7da62c6b2832929a6964237e98e9" hash="89ce7da62c6b2832929a6964237e98e9" type="image/jpeg" alt=""></i></div>
|
||||
</en-note>
|
||||
<en-note><div><input type="checkbox" onclick="return false;" />This is a test</div><div><input type="checkbox" onclick="return false;" />A test for <span STYLE="font-weight: bold;">bold</span></div><div><input type="checkbox" onclick="return false;" />A test for <i>italic</i><br/></div><div><br/></div><div><i><img src=":/89ce7da62c6b2832929a6964237e98e9" hash="89ce7da62c6b2832929a6964237e98e9" type="image/jpeg" alt="" /></i></div></en-note>
|
||||
@@ -1,3 +1,3 @@
|
||||
<en-note>
|
||||
<h1 style="box-sizing:inherit;font-family:"Guardian TextSans Web", "Helvetica Neue", Helvetica, Arial, sans-serif;margin-top:0.2em;margin-bottom:0.35em;font-size:2.125em;font-weight:600;line-height:1.3;">Association Between mRNA Vaccination and COVID-19 Hospitalization and Disease Severity</h1>
|
||||
<h1 STYLE="box-sizing:inherit;font-family:"Guardian TextSans Web", "Helvetica Neue", Helvetica, Arial, sans-serif;margin-top:0.2em;margin-bottom:0.35em;font-size:2.125em;font-weight:600;line-height:1.3;">Association Between mRNA Vaccination and COVID-19 Hospitalization and Disease Severity</h1>
|
||||
</en-note>
|
||||
@@ -1,3 +1,5 @@
|
||||
<en-note>
|
||||
<div><img style="margin:0px;padding:0px;outline:0px;width:74px;height:36px;position:absolute;bottom:-5px;left:0px;transform:translate(0px, 100%);stroke-dasharray:90;transition:stroke-dashoffset 0.5s cubic-bezier(0.97, 0.16, 0.62, 0.76) 0s;stroke-dashoffset:0;" src="data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' data-evernote-id='97' class='js-evernote-checked'%3e%3cuse xlink:href='https://wordminds.com/wp-content/themes/wordminds/assets/img/hint_left.svg%23hint_left' data-evernote-id='98' class='js-evernote-checked'%3e%3c/use%3e%3c/svg%3e"></div>
|
||||
<div>
|
||||
<img STYLE="margin:0px;padding:0px;outline:0px;width:74px;height:36px;position:absolute;bottom:-5px;left:0px;transform:translate(0px, 100%);stroke-dasharray:90;transition:stroke-dashoffset 0.5s cubic-bezier(0.97, 0.16, 0.62, 0.76) 0s;stroke-dashoffset:0;" SRC="data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' data-evernote-id='97' class='js-evernote-checked'%3e%3cuse xlink:href='https://wordminds.com/wp-content/themes/wordminds/assets/img/hint_left.svg%23hint_left' data-evernote-id='98' class='js-evernote-checked'%3e%3c/use%3e%3c/svg%3e"/>
|
||||
</div>
|
||||
</en-note>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE en-export SYSTEM "http://xml.evernote.com/pub/evernote-export4.dtd">
|
||||
<en-export export-date="20230724T173816Z" application="Evernote" version="10.58.8">
|
||||
<note>
|
||||
<title>test.json</title>
|
||||
<content><![CDATA[<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd">
|
||||
<en-note><en-media hash="ac91cc691d21261b222681dd38c1e4ad" type="application/json"/></en-note>
|
||||
]]>
|
||||
</content>
|
||||
<created>20191002T075850Z</created>
|
||||
<updated>20191002T075850Z</updated>
|
||||
<note-attributes><latitude>48.79547119140625</latitude><longitude>9.809423921920198</longitude><altitude>398.0</altitude><author>Laurent</author><source>desktop.mac</source></note-attributes>
|
||||
<resource><data>eyAidGVzdCI6IDEyMyB9</data><mime>application/json</mime><width>0</width><height>0</height><resource-attributes><file-name>test.json</file-name><attachment>false</attachment></resource-attributes></resource></note></en-export>
|
||||
@@ -2,7 +2,7 @@ import PluginRunner from '../../../app/services/plugins/PluginRunner';
|
||||
import PluginService, { PluginSettings, defaultPluginSetting } 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';
|
||||
import shim, { MobilePlatform } from '@joplin/lib/shim';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import * as fs from 'fs-extra';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
@@ -310,7 +310,7 @@ describe('services_PluginService', () => {
|
||||
|
||||
let resetPlatformMock = () => {};
|
||||
if (!isDesktop) {
|
||||
resetPlatformMock = mockMobilePlatform('android').reset;
|
||||
resetPlatformMock = mockMobilePlatform(MobilePlatform.Android).reset;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
BIN
packages/app-cli/tests/support/onenote/onenote_desktop.one
Normal file
@@ -0,0 +1 @@
|
||||
content
|
||||
@@ -0,0 +1 @@
|
||||
content
|
||||
@@ -0,0 +1 @@
|
||||
content
|
||||
@@ -0,0 +1,3 @@
|
||||
1. File without extension and leading `./`: [file1](./file1). Gets imported, but filename is converted to extension, like `<internal_id>.file1`
|
||||
2. File without extension: [file2](file2). Not imported at all.
|
||||
3. File with extension: [file3](file3.text). Gets imported properly.
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "Joplin Web Clipper [DEV]",
|
||||
"version": "3.4.0",
|
||||
"version": "3.5.0",
|
||||
"description": "Capture and save web pages and screenshots from your browser to Joplin.",
|
||||
"homepage_url": "https://joplinapp.org",
|
||||
"content_security_policy": {
|
||||
|
||||
@@ -407,7 +407,17 @@ export default class ElectronAppWrapper {
|
||||
isGoingToExit = true;
|
||||
} else {
|
||||
event.preventDefault();
|
||||
this.hide();
|
||||
|
||||
const w = this.win_;
|
||||
if (!w) return;
|
||||
|
||||
if (w.isFullScreen()) {
|
||||
// leave fullscreen, then hide
|
||||
w.once('leave-full-screen', () => w.hide());
|
||||
w.setFullScreen(false);
|
||||
} else {
|
||||
w.hide();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const hasBackgroundWindows = this.secondaryWindows_.size > 0;
|
||||
@@ -612,7 +622,11 @@ export default class ElectronAppWrapper {
|
||||
console.warn('The window object was not available during the click event from tray icon');
|
||||
return;
|
||||
}
|
||||
this.mainWindow().show();
|
||||
if (!this.mainWindow().isVisible()) {
|
||||
this.mainWindow().show();
|
||||
} else {
|
||||
this.mainWindow().hide();
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Cannot create tray', error);
|
||||
|
||||
@@ -86,8 +86,14 @@ export default class InteropServiceHelper {
|
||||
// pdfs.
|
||||
// https://github.com/laurent22/joplin/issues/6254.
|
||||
await win.webContents.executeJavaScript('document.querySelectorAll(\'details\').forEach(el=>el.setAttribute(\'open\',\'\'))');
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
const data = await win.webContents.printToPDF(options as any);
|
||||
const data = await win.webContents.printToPDF({
|
||||
...options,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Partially refactored old code before rule was applied
|
||||
pageSize: options.pageSize as any,
|
||||
// Allows users to override the CSS page size.
|
||||
// See https://github.com/laurent22/joplin/issues/13096
|
||||
preferCSSPageSize: true,
|
||||
});
|
||||
resolve(data);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
|
||||
@@ -52,7 +52,7 @@ describe('app.reducer', () => {
|
||||
...createAppDefaultState({}),
|
||||
backgroundWindows: {
|
||||
testWindow: {
|
||||
...createAppDefaultWindowState(),
|
||||
...createAppDefaultWindowState(null),
|
||||
windowId: 'testWindow',
|
||||
|
||||
visibleDialogs: {
|
||||
|
||||
@@ -26,10 +26,21 @@ export interface AppStateDialog {
|
||||
props: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface EditorScrollPercents {
|
||||
export interface NoteIdToScrollPercent {
|
||||
[noteId: string]: number;
|
||||
}
|
||||
|
||||
type RichTextEditorSelectionBookmark = unknown;
|
||||
|
||||
export interface EditorCursorLocations {
|
||||
readonly richText?: RichTextEditorSelectionBookmark;
|
||||
readonly markdown?: number;
|
||||
}
|
||||
|
||||
export interface NoteIdToEditorCursorLocations {
|
||||
[noteId: string]: EditorCursorLocations;
|
||||
}
|
||||
|
||||
export interface VisibleDialogs {
|
||||
[dialogKey: string]: boolean;
|
||||
}
|
||||
@@ -42,6 +53,9 @@ export interface AppWindowState extends WindowState {
|
||||
devToolsVisible: boolean;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
watchedResources: any;
|
||||
|
||||
lastEditorScrollPercents: NoteIdToScrollPercent;
|
||||
lastEditorCursorLocations: NoteIdToEditorCursorLocations;
|
||||
}
|
||||
|
||||
interface BackgroundWindowStates {
|
||||
@@ -55,7 +69,6 @@ export interface AppState extends State, AppWindowState {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
navHistory: any[];
|
||||
watchedNoteFiles: string[];
|
||||
lastEditorScrollPercents: EditorScrollPercents;
|
||||
focusedField: string;
|
||||
layoutMoveMode: boolean;
|
||||
startupPluginsLoaded: boolean;
|
||||
@@ -66,7 +79,7 @@ export interface AppState extends State, AppWindowState {
|
||||
isResettingLayout: boolean;
|
||||
}
|
||||
|
||||
export const createAppDefaultWindowState = (): AppWindowState => {
|
||||
export const createAppDefaultWindowState = (globalState: AppState|null): AppWindowState => {
|
||||
return {
|
||||
...defaultWindowState,
|
||||
visibleDialogs: {},
|
||||
@@ -75,6 +88,12 @@ export const createAppDefaultWindowState = (): AppWindowState => {
|
||||
editorCodeView: true,
|
||||
devToolsVisible: false,
|
||||
watchedResources: {},
|
||||
|
||||
// Maintain the scroll and cursor location for secondary windows separate from the
|
||||
// main window. This prevents scrolling in a secondary window from changing/resetting
|
||||
// the default scroll position in the main window:
|
||||
lastEditorCursorLocations: globalState?.lastEditorCursorLocations ?? {},
|
||||
lastEditorScrollPercents: globalState?.lastEditorScrollPercents ?? {},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -82,7 +101,7 @@ export const createAppDefaultWindowState = (): AppWindowState => {
|
||||
export function createAppDefaultState(resourceEditWatcherDefaultState: any): AppState {
|
||||
return {
|
||||
...defaultState,
|
||||
...createAppDefaultWindowState(),
|
||||
...createAppDefaultWindowState(null),
|
||||
route: {
|
||||
type: 'NAV_GO',
|
||||
routeName: 'Main',
|
||||
@@ -90,7 +109,6 @@ export function createAppDefaultState(resourceEditWatcherDefaultState: any): App
|
||||
},
|
||||
navHistory: [],
|
||||
watchedNoteFiles: [],
|
||||
lastEditorScrollPercents: {},
|
||||
visibleDialogs: {}, // empty object if no dialog is visible. Otherwise contains the list of visible dialogs.
|
||||
focusedField: null,
|
||||
layoutMoveMode: false,
|
||||
@@ -299,6 +317,18 @@ export default function(state: AppState, action: any) {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'EDITOR_CURSOR_POSITION_SET':
|
||||
{
|
||||
newState = { ...state };
|
||||
const newCursorLocations = { ...newState.lastEditorCursorLocations };
|
||||
newCursorLocations[action.noteId] = {
|
||||
...(newCursorLocations[action.noteId] ?? {}),
|
||||
...action.location,
|
||||
};
|
||||
newState.lastEditorCursorLocations = newCursorLocations;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'NOTE_DEVTOOLS_TOGGLE':
|
||||
newState = { ...state };
|
||||
newState.devToolsVisible = !newState.devToolsVisible;
|
||||
|
||||
@@ -63,6 +63,8 @@ import { refreshFolders } from '@joplin/lib/folders-screen-utils';
|
||||
import initializeCommandService from './utils/initializeCommandService';
|
||||
import OcrDriverBase from '@joplin/lib/services/ocr/OcrDriverBase';
|
||||
import PerformanceLogger from '@joplin/lib/PerformanceLogger';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
import Resource from '@joplin/lib/models/Resource';
|
||||
|
||||
const perfLogger = PerformanceLogger.create();
|
||||
|
||||
@@ -683,6 +685,11 @@ class Application extends BaseApplication {
|
||||
debug: new DebugService(reg.db()),
|
||||
resourceService: ResourceService.instance(),
|
||||
searchEngine: SearchEngine.instance(),
|
||||
shim,
|
||||
Note,
|
||||
Folder,
|
||||
Resource,
|
||||
Setting,
|
||||
ocrService: () => this.ocrService_,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { stateUtils } from '@joplin/lib/reducer';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
import { createAppDefaultWindowState } from '../app.reducer';
|
||||
import { AppState, createAppDefaultWindowState } from '../app.reducer';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
|
||||
export const declaration: CommandDeclaration = {
|
||||
@@ -25,7 +25,7 @@ export const runtime = (): CommandRuntime => {
|
||||
folderId: note.parent_id,
|
||||
windowId: `window-${noteId}-${idCounter++}`,
|
||||
defaultAppWindowState: {
|
||||
...createAppDefaultWindowState(),
|
||||
...createAppDefaultWindowState(context.state as AppState),
|
||||
noteVisiblePanes: Setting.value('noteVisiblePanes'),
|
||||
editorCodeView: Setting.value('editor.codeView'),
|
||||
},
|
||||
|
||||
@@ -225,7 +225,7 @@ const Button = React.forwardRef(({
|
||||
animation={iconAnimation}
|
||||
mr={iconOnly ? '0' : '6px'}
|
||||
color={color}
|
||||
className={iconName}
|
||||
className={`${iconName} icon`}
|
||||
role='img'
|
||||
/>;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { PluginManifest } from '@joplin/lib/services/plugins/utils/types';
|
||||
import bridge from '../../../../services/bridge';
|
||||
import { ItemEvent, PluginItem } from '@joplin/lib/components/shared/config/plugins/types';
|
||||
import PluginService from '@joplin/lib/services/plugins/PluginService';
|
||||
import getPluginHelpUrl from '@joplin/lib/services/plugins/utils/getPluginHelpUrl';
|
||||
|
||||
export enum InstallState {
|
||||
NotInstalled = 1,
|
||||
@@ -150,8 +151,7 @@ export default function(props: Props) {
|
||||
|
||||
const onNameClick = useCallback(() => {
|
||||
const manifest = item.manifest;
|
||||
if (!manifest.homepage_url) return;
|
||||
void bridge().openExternal(manifest.homepage_url);
|
||||
void bridge().openExternal(getPluginHelpUrl(manifest.id));
|
||||
}, [item]);
|
||||
|
||||
const onRecommendedClick = useCallback(() => {
|
||||
|
||||
@@ -7,6 +7,7 @@ import useKeyboardHandler from './DialogButtonRow/useKeyboardHandler';
|
||||
export interface ButtonSpec {
|
||||
name: string;
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export interface ClickEvent {
|
||||
@@ -51,21 +52,29 @@ export default function DialogButtonRow(props: Props) {
|
||||
if (props.onClick) props.onClick(event);
|
||||
}, [props.onClick]);
|
||||
|
||||
const onKeyDown = useKeyboardHandler({ onOkButtonClick, onCancelButtonClick });
|
||||
const okButtonShow = props.okButtonShow ?? true;
|
||||
const cancelButtonShow = props.cancelButtonShow ?? true;
|
||||
const canClickOk = okButtonShow && !props.okButtonDisabled;
|
||||
const canClickCancel = cancelButtonShow && !props.cancelButtonDisabled;
|
||||
|
||||
const onKeyDown = useKeyboardHandler({
|
||||
onOkButtonClick: canClickOk ? onOkButtonClick : null,
|
||||
onCancelButtonClick: canClickCancel ? onCancelButtonClick : null,
|
||||
});
|
||||
|
||||
const buttonComps = [];
|
||||
|
||||
if (props.customButtons) {
|
||||
for (const b of props.customButtons) {
|
||||
buttonComps.push(
|
||||
<button key={b.name} style={buttonStyle} onClick={() => onCustomButtonClick({ buttonName: b.name })} onKeyDown={onKeyDown}>
|
||||
<button key={b.name} style={buttonStyle} onClick={() => onCustomButtonClick({ buttonName: b.name })} disabled={b.disabled} onKeyDown={onKeyDown}>
|
||||
{b.label}
|
||||
</button>,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (props.okButtonShow !== false) {
|
||||
if (okButtonShow) {
|
||||
buttonComps.push(
|
||||
<button disabled={props.okButtonDisabled} key="ok" style={buttonStyle} onClick={onOkButtonClick} ref={props.okButtonRef} onKeyDown={onKeyDown}>
|
||||
{props.okButtonLabel ? props.okButtonLabel : _('OK')}
|
||||
@@ -73,7 +82,7 @@ export default function DialogButtonRow(props: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
if (props.cancelButtonShow !== false) {
|
||||
if (cancelButtonShow) {
|
||||
buttonComps.push(
|
||||
<button disabled={props.cancelButtonDisabled} key="cancel" style={{ ...buttonStyle }} onClick={onCancelButtonClick}>
|
||||
{props.cancelButtonLabel ? props.cancelButtonLabel : _('Cancel')}
|
||||
|
||||
@@ -2,11 +2,10 @@ import * as React from 'react';
|
||||
import { useEffect, useState, useRef, useCallback } from 'react';
|
||||
import { isInsideContainer } from '@joplin/lib/dom';
|
||||
|
||||
type OnButtonClick = ()=> void;
|
||||
interface Props {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
|
||||
onOkButtonClick: Function;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
|
||||
onCancelButtonClick: Function;
|
||||
onOkButtonClick: null|OnButtonClick;
|
||||
onCancelButtonClick: null|OnButtonClick;
|
||||
}
|
||||
|
||||
const globalKeydownHandlers: string[] = [];
|
||||
@@ -48,15 +47,17 @@ export default (props: Props) => {
|
||||
|
||||
if (!isTopDialog() || isInSubModal(event.target)) return;
|
||||
|
||||
if (event.keyCode === 13) {
|
||||
if (event.keyCode === 13 && props.onOkButtonClick) {
|
||||
if ('nodeName' in event.target && event.target.nodeName === 'INPUT') {
|
||||
const target = event.target as HTMLInputElement;
|
||||
|
||||
if (target.type !== 'button' && target.type !== 'checkbox') {
|
||||
event.preventDefault();
|
||||
props.onOkButtonClick();
|
||||
}
|
||||
}
|
||||
} else if (event.keyCode === 27) {
|
||||
} else if (event.keyCode === 27 && props.onCancelButtonClick) {
|
||||
event.preventDefault();
|
||||
props.onCancelButtonClick();
|
||||
}
|
||||
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
|
||||
|
||||
@@ -15,7 +15,7 @@ import { connect } from 'react-redux';
|
||||
import { AppState } from '../../app.reducer';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import CommandService from '@joplin/lib/services/CommandService';
|
||||
import { PublicPrivateKeyPair } from '@joplin/lib/services/e2ee/ppk';
|
||||
import { PublicPrivateKeyPair } from '@joplin/lib/services/e2ee/ppk/ppk';
|
||||
import ToggleAdvancedSettingsButton from '../ConfigScreen/controls/ToggleAdvancedSettingsButton';
|
||||
import MacOSMissingPasswordHelpLink from '../ConfigScreen/controls/MissingPasswordHelpLink';
|
||||
|
||||
|
||||
@@ -172,7 +172,12 @@ export default function NoteContentPropertiesDialog(props: NoteContentProperties
|
||||
<div style={{ ...labelCompStyle, marginTop: 10 }}>
|
||||
{readTimeLabel}
|
||||
</div>
|
||||
<DialogButtonRow themeId={props.themeId} onClick={buttonRow_click} okButtonShow={false} cancelButtonLabel={_('Close')}/>
|
||||
<DialogButtonRow
|
||||
themeId={props.themeId}
|
||||
onClick={buttonRow_click}
|
||||
okButtonShow={false}
|
||||
cancelButtonLabel={_('Close')}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,36 @@ const logger = Logger.create('useEditorSearch');
|
||||
// Registers a helper CodeMirror extension to be used with
|
||||
// useEditorSearchHandler.
|
||||
|
||||
export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulation) {
|
||||
interface SetMarkersOptions {
|
||||
selectedIndex: number;
|
||||
searchTimestamp: number;
|
||||
showEditorMarkers?: boolean;
|
||||
withSelection?: boolean;
|
||||
}
|
||||
type Keyword = { value: string };
|
||||
|
||||
export type OnSetMarkers = (cm: CodeMirror5Emulation, keywords: Keyword[], options: SetMarkersOptions)=> number;
|
||||
|
||||
|
||||
// Modified from codemirror/addons/search/search.js
|
||||
const searchOverlay = (query: RegExp) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
return { token: function(stream: any) {
|
||||
query.lastIndex = stream.pos;
|
||||
const match = query.exec(stream.string);
|
||||
if (match && match.index === stream.pos) {
|
||||
stream.pos += match[0].length || 1;
|
||||
return 'search-marker';
|
||||
} else if (match) {
|
||||
stream.pos = match.index;
|
||||
} else {
|
||||
stream.skipToEnd();
|
||||
}
|
||||
return null;
|
||||
} };
|
||||
};
|
||||
|
||||
export default function useEditorSearchExtension() {
|
||||
|
||||
const [markers, setMarkers] = useState([]);
|
||||
const [overlay, setOverlay] = useState(null);
|
||||
@@ -48,23 +77,6 @@ export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulatio
|
||||
setOverlayTimeout(null);
|
||||
}, [scrollbarMarks, overlay, overlayTimeout]);
|
||||
|
||||
// Modified from codemirror/addons/search/search.js
|
||||
const searchOverlay = useCallback((query: RegExp) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
return { token: function(stream: any) {
|
||||
query.lastIndex = stream.pos;
|
||||
const match = query.exec(stream.string);
|
||||
if (match && match.index === stream.pos) {
|
||||
stream.pos += match[0].length || 1;
|
||||
return 'search-marker';
|
||||
} else if (match) {
|
||||
stream.pos = match.index;
|
||||
} else {
|
||||
stream.skipToEnd();
|
||||
}
|
||||
return null;
|
||||
} };
|
||||
}, []);
|
||||
|
||||
// Highlights the currently active found work
|
||||
// It's possible to get tricky with this functions and just use findNext/findPrev
|
||||
@@ -115,16 +127,17 @@ export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulatio
|
||||
};
|
||||
}, []);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
CodeMirror?.defineExtension('setMarkers', function(keywords: any, options: any) {
|
||||
const onSetMarkers: OnSetMarkers = (cm, keywords, options) => {
|
||||
// Pass arguments in via options to allow the extension to work if multiple editors are open simultaneously
|
||||
// See https://github.com/laurent22/joplin/issues/13399.
|
||||
if (!options) {
|
||||
options = { selectedIndex: 0, searchTimestamp: 0 };
|
||||
}
|
||||
|
||||
if (options.showEditorMarkers === false) {
|
||||
clearMarkers();
|
||||
clearOverlay(this);
|
||||
return;
|
||||
clearOverlay(cm);
|
||||
return 0;
|
||||
}
|
||||
|
||||
clearMarkers();
|
||||
@@ -145,7 +158,7 @@ export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulatio
|
||||
const scrollTo = i === 0 && (previousKeywordValue !== keyword.value || previousIndex !== options.selectedIndex || options.searchTimestamp !== previousSearchTimestamp);
|
||||
|
||||
try {
|
||||
const match = highlightSearch(this, searchTerm, options.selectedIndex, scrollTo, !!options.withSelection);
|
||||
const match = highlightSearch(cm, searchTerm, options.selectedIndex, scrollTo, !!options.withSelection);
|
||||
if (match) marks.push(match);
|
||||
} catch (error) {
|
||||
if (error.name !== 'SyntaxError') {
|
||||
@@ -165,7 +178,7 @@ export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulatio
|
||||
// SEARCHOVERLAY
|
||||
// We only want to highlight all matches when there is only 1 search term
|
||||
if (keywords.length !== 1 || keywords[0].value === '') {
|
||||
clearOverlay(this);
|
||||
clearOverlay(cm);
|
||||
const prev = keywords.length > 1 ? keywords[0].value : '';
|
||||
setPreviousKeywordValue(prev);
|
||||
return 0;
|
||||
@@ -175,22 +188,22 @@ export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulatio
|
||||
|
||||
// Determine the number of matches in the source, this is passed on
|
||||
// to the NoteEditor component
|
||||
const regexMatches = this.getValue().match(searchTerm);
|
||||
const regexMatches = cm.getValue().match(searchTerm);
|
||||
const nMatches = regexMatches ? regexMatches.length : 0;
|
||||
|
||||
// Don't bother clearing and re-calculating the overlay if the search term
|
||||
// hasn't changed
|
||||
if (keywords[0].value === previousKeywordValue) return nMatches;
|
||||
|
||||
clearOverlay(this);
|
||||
clearOverlay(cm);
|
||||
setPreviousKeywordValue(keywords[0].value);
|
||||
|
||||
// These operations are pretty slow, so we won't add use them until the user
|
||||
// has finished typing, 500ms is probably enough time
|
||||
const timeout = shim.setTimeout(() => {
|
||||
const scrollMarks = this.showMatchesOnScrollbar?.(searchTerm, true, 'cm-search-marker-scrollbar');
|
||||
const scrollMarks = cm.showMatchesOnScrollbar?.(searchTerm, true, 'cm-search-marker-scrollbar');
|
||||
const overlay = searchOverlay(searchTerm);
|
||||
this.addOverlay(overlay);
|
||||
cm.addOverlay(overlay);
|
||||
setOverlay(overlay);
|
||||
setScrollbarMarks(scrollMarks);
|
||||
}, 500);
|
||||
@@ -199,5 +212,9 @@ export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulatio
|
||||
overlayTimeoutRef.current = timeout;
|
||||
|
||||
return nMatches;
|
||||
});
|
||||
};
|
||||
const onSetMarkersRef = useRef(onSetMarkers);
|
||||
onSetMarkersRef.current = onSetMarkers;
|
||||
|
||||
return { onSetMarkersRef };
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import { RefObject, useEffect, useMemo, useRef } from 'react';
|
||||
import usePrevious from '../../../../hooks/usePrevious';
|
||||
import { RenderedBody } from './types';
|
||||
import { SearchMarkers } from '../../../utils/useSearchMarkers';
|
||||
import CodeMirror5Emulation from '@joplin/editor/CodeMirror/CodeMirror5Emulation/CodeMirror5Emulation';
|
||||
import useEditorSearchExtension from './useEditorSearchExtension';
|
||||
const debounce = require('debounce');
|
||||
|
||||
interface Props {
|
||||
@@ -10,8 +12,7 @@ interface Props {
|
||||
searchMarkers: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
webviewRef: RefObject<any>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
editorRef: RefObject<any>;
|
||||
editorRef: RefObject<CodeMirror5Emulation>;
|
||||
|
||||
noteContent: string;
|
||||
renderedBody: RenderedBody;
|
||||
@@ -23,6 +24,8 @@ const useEditorSearchHandler = (props: Props) => {
|
||||
webviewRef, editorRef, renderedBody, noteContent, searchMarkers, showEditorMarkers,
|
||||
} = props;
|
||||
|
||||
const { onSetMarkersRef } = useEditorSearchExtension();
|
||||
|
||||
const previousContent = usePrevious(noteContent);
|
||||
const previousRenderedBody = usePrevious(renderedBody);
|
||||
const previousSearchMarkers = usePrevious(searchMarkers);
|
||||
@@ -31,15 +34,15 @@ const useEditorSearchHandler = (props: Props) => {
|
||||
|
||||
// Fixes https://github.com/laurent22/joplin/issues/7565
|
||||
const debouncedMarkers = useMemo(() => debounce((searchMarkers: SearchMarkers) => {
|
||||
if (!editorRef.current) return;
|
||||
if (!onSetMarkersRef.current) return;
|
||||
|
||||
if (showEditorMarkersRef.current) {
|
||||
const matches = editorRef.current.setMarkers(searchMarkers.keywords, searchMarkers.options);
|
||||
const matches = onSetMarkersRef.current(editorRef.current, searchMarkers.keywords, searchMarkers.options);
|
||||
props.setLocalSearchResultCount(matches);
|
||||
} else {
|
||||
editorRef.current.setMarkers(searchMarkers.keywords, { ...searchMarkers.options, showEditorMarkers: false });
|
||||
onSetMarkersRef.current(editorRef.current, searchMarkers.keywords, { ...searchMarkers.options, showEditorMarkers: false });
|
||||
}
|
||||
}, 50), [editorRef, props.setLocalSearchResultCount]);
|
||||
}, 50), [editorRef, onSetMarkersRef, props.setLocalSearchResultCount]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!searchMarkers) return () => {};
|
||||
@@ -59,7 +62,7 @@ const useEditorSearchHandler = (props: Props) => {
|
||||
}
|
||||
return () => {};
|
||||
}, [
|
||||
editorRef,
|
||||
onSetMarkersRef,
|
||||
webviewRef,
|
||||
searchMarkers,
|
||||
previousSearchMarkers,
|
||||
@@ -71,6 +74,10 @@ const useEditorSearchHandler = (props: Props) => {
|
||||
debouncedMarkers,
|
||||
]);
|
||||
|
||||
return {
|
||||
// Returned to allow quickly setting the initial search markers just after the editor loads.
|
||||
onSetInitialMarkersRef: onSetMarkersRef,
|
||||
};
|
||||
};
|
||||
|
||||
export default useEditorSearchHandler;
|
||||
|
||||
@@ -695,7 +695,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
||||
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
|
||||
}, [renderedBody, webviewReady]);
|
||||
|
||||
useEditorSearchHandler({
|
||||
const { onSetInitialMarkersRef } = useEditorSearchHandler({
|
||||
setLocalSearchResultCount: props.setLocalSearchResultCount,
|
||||
searchMarkers: props.searchMarkers,
|
||||
webviewRef,
|
||||
@@ -737,6 +737,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
||||
<Editor
|
||||
value={props.content}
|
||||
searchMarkers={props.searchMarkers}
|
||||
onSetMarkersRef={onSetInitialMarkersRef}
|
||||
ref={editorRef}
|
||||
mode={props.contentMarkupLanguage === MarkupToHtml.MARKUP_LANGUAGE_HTML ? 'xml' : 'joplin-markdown'}
|
||||
codeMirrorTheme={styles.editor.codeMirrorTheme}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { useEffect, useImperativeHandle, useState, useRef, useCallback, forwardRef } from 'react';
|
||||
import { useEffect, useImperativeHandle, useState, useRef, useCallback, forwardRef, RefObject } from 'react';
|
||||
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
|
||||
|
||||
import CodeMirror from 'codemirror';
|
||||
@@ -16,7 +16,7 @@ import useListIdent from './utils/useListIdent';
|
||||
import useScrollUtils from './utils/useScrollUtils';
|
||||
import useCursorUtils from './utils/useCursorUtils';
|
||||
import useLineSorting from './utils/useLineSorting';
|
||||
import useEditorSearch from '../utils/useEditorSearchExtension';
|
||||
import { OnSetMarkers } from '../utils/useEditorSearchExtension';
|
||||
import useJoplinMode from './utils/useJoplinMode';
|
||||
import useKeymap from './utils/useKeymap';
|
||||
import useExternalPlugins from './utils/useExternalPlugins';
|
||||
@@ -67,11 +67,17 @@ import 'codemirror/mode/diff/diff';
|
||||
import 'codemirror/mode/erlang/erlang';
|
||||
import 'codemirror/mode/sql/sql';
|
||||
|
||||
interface ExtendedWindow {
|
||||
CodeMirror?: unknown;
|
||||
}
|
||||
declare const window: ExtendedWindow;
|
||||
|
||||
|
||||
export interface EditorProps {
|
||||
value: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
searchMarkers: any;
|
||||
onSetMarkersRef: RefObject<OnSetMarkers>;
|
||||
mode: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
style: any;
|
||||
@@ -100,13 +106,20 @@ function Editor(props: EditorProps, ref: any) {
|
||||
const editorParent = useRef(null);
|
||||
const lastEditTime = useRef(NaN);
|
||||
|
||||
useEffect(() => {
|
||||
window.CodeMirror = CodeMirror;
|
||||
|
||||
return () => {
|
||||
window.CodeMirror = undefined;
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Codemirror plugins add new commands to codemirror (or change it's behavior)
|
||||
// This command adds the smartListIndent function which will be bound to tab
|
||||
useListIdent(CodeMirror);
|
||||
useScrollUtils(CodeMirror);
|
||||
useCursorUtils(CodeMirror);
|
||||
useLineSorting(CodeMirror);
|
||||
useEditorSearch(CodeMirror);
|
||||
useJoplinMode(CodeMirror);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
const pluginOptions: any = useExternalPlugins(CodeMirror, props.plugins);
|
||||
@@ -215,7 +228,7 @@ function Editor(props: EditorProps, ref: any) {
|
||||
// It's possible for searchMarkers to be available before the editor
|
||||
// In these cases we set the markers asap so the user can see them as
|
||||
// soon as the editor is ready
|
||||
if (props.searchMarkers) { cm.setMarkers(props.searchMarkers.keywords, props.searchMarkers.options); }
|
||||
if (props.searchMarkers) { props.onSetMarkersRef.current(cm, props.searchMarkers.keywords, props.searchMarkers.options); }
|
||||
|
||||
return () => {
|
||||
// Clean up codemirror
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
|
||||
import bridge from '../../../../../../services/bridge';
|
||||
import { contentScriptsToCodeMirrorPlugin } from '@joplin/lib/services/plugins/utils/loadContentScripts';
|
||||
import { extname } from 'path';
|
||||
import shim from '@joplin/lib/shim';
|
||||
@@ -7,6 +8,18 @@ import uuid from '@joplin/lib/uuid';
|
||||
|
||||
import { reg } from '@joplin/lib/registry';
|
||||
|
||||
const addPluginDependency = (path: string) => {
|
||||
const id = `content-script-${encodeURIComponent(path)}`;
|
||||
if (document.getElementById(id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const element = document.createElement('script');
|
||||
element.setAttribute('id', id);
|
||||
element.setAttribute('src', path);
|
||||
document.head.appendChild(element);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
export default function useExternalPlugins(CodeMirror: any, plugins: PluginStates) {
|
||||
const [options, setOptions] = useState({});
|
||||
@@ -23,7 +36,14 @@ export default function useExternalPlugins(CodeMirror: any, plugins: PluginState
|
||||
if (mod.codeMirrorResources) {
|
||||
for (const asset of mod.codeMirrorResources) {
|
||||
try {
|
||||
require(`codemirror/${asset}`);
|
||||
let assetPath = shim.fsDriver().resolveRelativePathWithinDir(`${bridge().vendorDir()}/lib/codemirror/`, asset);
|
||||
|
||||
// Compatibility with old versions of Joplin, where the file extension was automatically added by require().
|
||||
if (extname(assetPath) === '') {
|
||||
assetPath += '.js';
|
||||
}
|
||||
|
||||
addPluginDependency(assetPath);
|
||||
} catch (error) {
|
||||
error.message = `${asset} is not a valid CodeMirror asset, keymap or mode. You can find a list of valid assets here: https://codemirror.net/doc/manual.html#addons`;
|
||||
throw error;
|
||||
|
||||
@@ -30,6 +30,8 @@ import useEditorSearchHandler from '../utils/useEditorSearchHandler';
|
||||
import CommandService from '@joplin/lib/services/CommandService';
|
||||
import useRefocusOnVisiblePaneChange from './utils/useRefocusOnVisiblePaneChange';
|
||||
import { WindowIdContext } from '../../../../NewWindowOrIFrame';
|
||||
import eventManager, { EventName, ResourceChangeEvent } from '@joplin/lib/eventManager';
|
||||
import useSyncEditorValue from './utils/useSyncEditorValue';
|
||||
|
||||
const logger = Logger.create('CodeMirror6');
|
||||
const logDebug = (message: string) => logger.debug(message);
|
||||
@@ -166,9 +168,8 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
||||
},
|
||||
scrollTo: (options: ScrollOptions) => {
|
||||
if (options.type === ScrollOptionTypes.Hash) {
|
||||
if (!webviewRef.current) return;
|
||||
const hash: string = options.value;
|
||||
webviewRef.current.send('scrollToHash', hash);
|
||||
webviewRef.current?.send('scrollToHash', hash);
|
||||
editorRef.current.jumpToHash(hash);
|
||||
} else if (options.type === ScrollOptionTypes.Percent) {
|
||||
const percent = options.value as number;
|
||||
@@ -272,6 +273,17 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
||||
props.noteId, props.useCustomPdfViewer,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const listener = (event: ResourceChangeEvent) => {
|
||||
editorRef.current?.onResourceChanged(event.id);
|
||||
};
|
||||
|
||||
eventManager.on(EventName.ResourceChange, listener);
|
||||
return () => {
|
||||
eventManager.off(EventName.ResourceChange, listener);
|
||||
};
|
||||
}, [props.resourceInfos]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!webviewReady) return;
|
||||
|
||||
@@ -330,6 +342,7 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
||||
} else if (event.kind === EditorEventType.Change) {
|
||||
codeMirror_change(event.value);
|
||||
} else if (event.kind === EditorEventType.SelectionRangeChange) {
|
||||
props.onCursorMotion({ markdown: event.from });
|
||||
setSelectionRange({ from: event.from, to: event.to });
|
||||
} else if (event.kind === EditorEventType.UpdateSearchDialog) {
|
||||
if (lastSearchState.current?.searchText !== event.searchState.searchText) {
|
||||
@@ -343,7 +356,7 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
||||
} else if (event.kind === EditorEventType.FollowLink) {
|
||||
void CommandService.instance().execute('openItem', event.link);
|
||||
}
|
||||
}, [editor_scroll, codeMirror_change, props.setLocalSearch, props.setShowLocalSearch]);
|
||||
}, [editor_scroll, codeMirror_change, props.setLocalSearch, props.setShowLocalSearch, props.onCursorMotion]);
|
||||
|
||||
const onSelectPastBeginning = useCallback(() => {
|
||||
void CommandService.instance().execute('focusElement', 'noteTitle');
|
||||
@@ -366,6 +379,7 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
||||
katexEnabled: Setting.value('markdown.plugin.katex'),
|
||||
inlineRenderingEnabled: Setting.value('editor.inlineRendering'),
|
||||
imageRenderingEnabled: Setting.value('editor.imageRendering'),
|
||||
highlightActiveLine: Setting.value('editor.highlightActiveLine'),
|
||||
themeData: {
|
||||
...styles.globalTheme,
|
||||
marginLeft: 0,
|
||||
@@ -387,15 +401,17 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
||||
props.tabMovesFocus,
|
||||
]);
|
||||
|
||||
// Update the editor's value
|
||||
useEffect(() => {
|
||||
// Include the noteId in the update props to give plugins access
|
||||
// to the current note ID.
|
||||
const updateProps = { noteId: props.noteId };
|
||||
if (editorRef.current?.updateBody(props.content, updateProps)) {
|
||||
editorRef.current?.clearHistory();
|
||||
}
|
||||
}, [props.content, props.noteId]);
|
||||
const initialCursorLocationRef = useRef(0);
|
||||
initialCursorLocationRef.current = props.initialCursorLocation.markdown ?? 0;
|
||||
|
||||
useSyncEditorValue({
|
||||
content: props.content,
|
||||
visiblePanes: props.visiblePanes,
|
||||
onMessage: props.onMessage,
|
||||
editorRef,
|
||||
noteId: props.noteId,
|
||||
initialCursorLocationRef,
|
||||
});
|
||||
|
||||
const renderEditor = () => {
|
||||
return (
|
||||
@@ -403,6 +419,7 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
||||
<Editor
|
||||
style={styles.editor}
|
||||
initialText={props.content}
|
||||
initialSelectionRef={initialCursorLocationRef}
|
||||
initialNoteId={props.noteId}
|
||||
ref={editorRef}
|
||||
settings={editorSettings}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { ForwardedRef } from 'react';
|
||||
import { ForwardedRef, RefObject } from 'react';
|
||||
import { useEffect, useState, useRef, forwardRef, useImperativeHandle } from 'react';
|
||||
import { EditorProps, LogMessageCallback, OnEventCallback, ContentScriptData } from '@joplin/editor/types';
|
||||
import createEditor from '@joplin/editor/CodeMirror/createEditor';
|
||||
@@ -11,7 +11,6 @@ import PluginService from '@joplin/lib/services/plugins/PluginService';
|
||||
import setupVim from '@joplin/editor/CodeMirror/utils/setupVim';
|
||||
import { dirname } from 'path';
|
||||
import useKeymap from './utils/useKeymap';
|
||||
import useEditorSearch from '../utils/useEditorSearchExtension';
|
||||
import CommandService from '@joplin/lib/services/CommandService';
|
||||
import { SearchMarkers } from '../../../utils/useSearchMarkers';
|
||||
import localisation from './utils/localisation';
|
||||
@@ -23,6 +22,7 @@ import getResourceBaseUrl from '../../../utils/getResourceBaseUrl';
|
||||
interface Props extends EditorProps {
|
||||
style: React.CSSProperties;
|
||||
pluginStates: PluginStates;
|
||||
initialSelectionRef: RefObject<number>;
|
||||
|
||||
onEditorPaste: (event: Event)=> void;
|
||||
externalSearch: SearchMarkers;
|
||||
@@ -43,8 +43,6 @@ const Editor = (props: Props, ref: ForwardedRef<CodeMirrorControl>) => {
|
||||
onLogMessageRef.current = props.onLogMessage;
|
||||
}, [props.onEvent, props.onLogMessage]);
|
||||
|
||||
useEditorSearch(editor);
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor) {
|
||||
return () => {};
|
||||
@@ -110,11 +108,12 @@ const Editor = (props: Props, ref: ForwardedRef<CodeMirrorControl>) => {
|
||||
|
||||
const editor = createEditor(editorContainerRef.current, {
|
||||
...editorProps,
|
||||
resolveImageSrc: async src => {
|
||||
resolveImageSrc: async (src, reloadCounter) => {
|
||||
const url = parseResourceUrl(src);
|
||||
if (!url.itemId) return null;
|
||||
const item = await Resource.load(url.itemId);
|
||||
return `${getResourceBaseUrl()}/${resourceFilename(item)}`;
|
||||
if (!item) return null;
|
||||
return `${getResourceBaseUrl()}/${resourceFilename(item)}${reloadCounter ? `?r=${reloadCounter}` : ''}`;
|
||||
},
|
||||
});
|
||||
editor.addStyles({
|
||||
@@ -126,6 +125,9 @@ const Editor = (props: Props, ref: ForwardedRef<CodeMirrorControl>) => {
|
||||
direction: 'unset',
|
||||
},
|
||||
});
|
||||
const cursor = props.initialSelectionRef.current;
|
||||
editor.select(cursor, cursor);
|
||||
|
||||
setEditor(editor);
|
||||
|
||||
return () => {
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { useEffect, useRef, RefObject } from 'react';
|
||||
import { OnMessage } from '../../../../utils/types';
|
||||
import CodeMirrorControl from '@joplin/editor/CodeMirror/CodeMirrorControl';
|
||||
|
||||
interface Props {
|
||||
content: string;
|
||||
|
||||
visiblePanes: string[];
|
||||
onMessage: OnMessage;
|
||||
editorRef: RefObject<CodeMirrorControl>;
|
||||
noteId: string;
|
||||
initialCursorLocationRef: RefObject<number>;
|
||||
}
|
||||
|
||||
// Updates the editor's value as necessary
|
||||
const useSyncEditorValue = ({ content, visiblePanes, onMessage, editorRef, noteId, initialCursorLocationRef }: Props) => {
|
||||
const visiblePanesRef = useRef(visiblePanes);
|
||||
visiblePanesRef.current = visiblePanes;
|
||||
const onMessageRef = useRef(onMessage);
|
||||
onMessageRef.current = onMessage;
|
||||
|
||||
const lastNoteIdRef = useRef(noteId);
|
||||
|
||||
useEffect(() => {
|
||||
// Include the noteId in the update props to give plugins access
|
||||
// to the current note ID.
|
||||
const updateProps = { noteId: noteId };
|
||||
if (editorRef.current?.updateBody(content, updateProps)) {
|
||||
editorRef.current?.clearHistory();
|
||||
|
||||
// Only reset the cursor location when switching notes. If, for example,
|
||||
// the note is updated from a secondary window, the cursor location shouldn't
|
||||
// reset.
|
||||
const noteChanged = lastNoteIdRef.current !== noteId;
|
||||
if (noteChanged) {
|
||||
const cursorLocation = initialCursorLocationRef.current;
|
||||
editorRef.current?.select(cursorLocation, cursorLocation);
|
||||
}
|
||||
lastNoteIdRef.current = noteId;
|
||||
|
||||
// If the viewer isn't visible, the content should be considered rendered
|
||||
// after the editor has finished updating:
|
||||
if (!visiblePanesRef.current.includes('viewer')) {
|
||||
onMessageRef.current({ channel: 'noteRenderComplete' });
|
||||
}
|
||||
}
|
||||
}, [content, noteId, editorRef, initialCursorLocationRef]);
|
||||
};
|
||||
|
||||
export default useSyncEditorValue;
|
||||
@@ -23,7 +23,7 @@ import { themeStyle } from '@joplin/lib/theme';
|
||||
import { loadScript } from '../../../utils/loadScript';
|
||||
import bridge from '../../../../services/bridge';
|
||||
import { TinyMceEditorEvents } from './utils/types';
|
||||
import type { Editor, EditorEvent } from 'tinymce';
|
||||
import type { Bookmark, Editor, EditorEvent } from 'tinymce';
|
||||
import { joplinCommandToTinyMceCommands, TinyMceCommand } from './utils/joplinCommandToTinyMceCommands';
|
||||
import shouldPasteResources from './utils/shouldPasteResources';
|
||||
import lightTheme from '@joplin/lib/themes/light';
|
||||
@@ -47,6 +47,7 @@ import Setting from '@joplin/lib/models/Setting';
|
||||
import useTextPatternsLookup, { TextPatternContext } from './utils/useTextPatternsLookup';
|
||||
import { toFileProtocolPath } from '@joplin/utils/path';
|
||||
import { RenderResultPluginAsset } from '@joplin/renderer/types';
|
||||
import useCursorPositioning from './utils/useCursorPositioning';
|
||||
|
||||
const logger = Logger.create('TinyMCE');
|
||||
|
||||
@@ -918,8 +919,6 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: Ref<NoteBodyEditorRef>) => {
|
||||
|
||||
editor.on('SetContent', () => {
|
||||
preprocessContent();
|
||||
|
||||
props_onMessage.current({ channel: 'noteRenderComplete' });
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1046,6 +1045,12 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: Ref<NoteBodyEditorRef>) => {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { onRestoreCursorPosition } = useCursorPositioning({
|
||||
initialCursorLocation: props.initialCursorLocation.richText as Bookmark,
|
||||
onCursorUpdate: props.onCursorMotion,
|
||||
editor,
|
||||
});
|
||||
|
||||
const lastNoteIdRef = useRef(props.noteId);
|
||||
useEffect(() => {
|
||||
if (!editor) return () => {};
|
||||
@@ -1113,8 +1118,12 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: Ref<NoteBodyEditorRef>) => {
|
||||
// times would result in an empty note.
|
||||
// https://github.com/laurent22/joplin/issues/3534
|
||||
editor.undoManager.reset();
|
||||
|
||||
// Only restore the cursor position from the global state when switching notes.
|
||||
// See https://github.com/laurent22/joplin/issues/13579
|
||||
onRestoreCursorPosition();
|
||||
} else {
|
||||
// Restore the cursor location
|
||||
// Restore the cursor location from the current note
|
||||
editor.selection.bookmarkManager.moveToBookmark(bookmark);
|
||||
}
|
||||
|
||||
@@ -1123,6 +1132,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: Ref<NoteBodyEditorRef>) => {
|
||||
resourceInfos: props.resourceInfos,
|
||||
contentKey: props.contentKey,
|
||||
};
|
||||
props_onMessage.current({ channel: 'noteRenderComplete' });
|
||||
}
|
||||
|
||||
const allAssetsOptions: NoteStyleOptions = {
|
||||
@@ -1387,16 +1397,18 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: Ref<NoteBodyEditorRef>) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
async function onCopy(event: any) {
|
||||
const copiedContent = editor.selection.getContent();
|
||||
if (!copiedContent) return;
|
||||
copyHtmlToClipboard(copiedContent);
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
async function onCut(event: any) {
|
||||
event.preventDefault();
|
||||
const selectedContent = editor.selection.getContent();
|
||||
if (!selectedContent) return;
|
||||
copyHtmlToClipboard(selectedContent);
|
||||
editor.insertContent('');
|
||||
event.preventDefault();
|
||||
onChangeHandler();
|
||||
}
|
||||
|
||||
@@ -1444,7 +1456,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: Ref<NoteBodyEditorRef>) => {
|
||||
// `compositionend` means that a user has finished entering a Chinese
|
||||
// (or other languages that require IME) character.
|
||||
editor.on(TinyMceEditorEvents.CompositionEnd, onChangeHandler);
|
||||
editor.on(TinyMceEditorEvents.Cut, onCut);
|
||||
editor.on(TinyMceEditorEvents.Cut, onCut, true);
|
||||
editor.on(TinyMceEditorEvents.JoplinChange, onChangeHandler);
|
||||
editor.on(TinyMceEditorEvents.Undo, onChangeHandler);
|
||||
editor.on(TinyMceEditorEvents.Redo, onChangeHandler);
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { Bookmark, Editor } from 'tinymce';
|
||||
import { OnCursorMotion } from '../../../utils/types';
|
||||
|
||||
interface Props {
|
||||
initialCursorLocation: Bookmark;
|
||||
editor: Editor;
|
||||
onCursorUpdate: OnCursorMotion;
|
||||
}
|
||||
|
||||
const useCursorPositioning = ({ initialCursorLocation, editor, onCursorUpdate }: Props) => {
|
||||
const initialCursorLocationRef = useRef(initialCursorLocation);
|
||||
initialCursorLocationRef.current = initialCursorLocation;
|
||||
|
||||
const appliedInitialCursorLocationRef = useRef(false);
|
||||
const onRestoreCursorPosition = useCallback(() => {
|
||||
if (editor) {
|
||||
if (initialCursorLocationRef.current) {
|
||||
editor.selection.moveToBookmark(initialCursorLocationRef.current);
|
||||
}
|
||||
|
||||
appliedInitialCursorLocationRef.current = true;
|
||||
}
|
||||
}, [editor]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor) return () => {};
|
||||
|
||||
const onSelectionChange = () => {
|
||||
// Wait until the initial cursor position has been set. This avoids resetting
|
||||
// the initial cursor position to zero when the editor first loads.
|
||||
if (!appliedInitialCursorLocationRef.current) return;
|
||||
|
||||
// Use an offset bookmark -- the default bookmark type is not preserved after unloading
|
||||
// and reloading the editor.
|
||||
const offsetBookmarkId = 2;
|
||||
onCursorUpdate({
|
||||
richText: editor.selection.getBookmark(offsetBookmarkId, true),
|
||||
});
|
||||
};
|
||||
|
||||
editor.on('SelectionChange', onSelectionChange);
|
||||
|
||||
return () => {
|
||||
editor.off('SelectionChange', onSelectionChange);
|
||||
};
|
||||
}, [editor, onCursorUpdate, onRestoreCursorPosition]);
|
||||
|
||||
return { onRestoreCursorPosition };
|
||||
};
|
||||
|
||||
export default useCursorPositioning;
|
||||
@@ -18,7 +18,7 @@ import { NoteEditorProps, FormNote, OnChangeEvent, AllAssetsOptions, NoteBodyEdi
|
||||
import CommandService from '@joplin/lib/services/CommandService';
|
||||
import Button, { ButtonLevel } from '../Button/Button';
|
||||
import eventManager, { EventName } from '@joplin/lib/eventManager';
|
||||
import { AppState } from '../../app.reducer';
|
||||
import { AppState, EditorCursorLocations } from '../../app.reducer';
|
||||
import ToolbarButtonUtils, { ToolbarButtonInfo } from '@joplin/lib/services/commands/ToolbarButtonUtils';
|
||||
import { _, _n } from '@joplin/lib/locale';
|
||||
import NoteTitleBar from './NoteTitle/NoteTitleBar';
|
||||
@@ -57,6 +57,7 @@ import StatusBar from './StatusBar';
|
||||
import useVisiblePluginEditorViewIds from '@joplin/lib/hooks/plugins/useVisiblePluginEditorViewIds';
|
||||
import useConnectToEditorPlugin from './utils/useConnectToEditorPlugin';
|
||||
import getResourceBaseUrl from './utils/getResourceBaseUrl';
|
||||
import useInitialCursorLocation from './utils/useInitialCursorLocation';
|
||||
|
||||
const debounce = require('debounce');
|
||||
|
||||
@@ -329,13 +330,14 @@ function NoteEditorContent(props: NoteEditorProps) {
|
||||
});
|
||||
}, [formNote, setFormNote, handleProvisionalFlag, props.dispatch]);
|
||||
|
||||
const { scrollWhenReady, clearScrollWhenReady } = useScrollWhenReadyOptions({
|
||||
const { scrollWhenReadyRef, clearScrollWhenReady } = useScrollWhenReadyOptions({
|
||||
noteId: formNote.id,
|
||||
selectedNoteHash: props.selectedNoteHash,
|
||||
lastEditorScrollPercents: props.lastEditorScrollPercents,
|
||||
editorRef,
|
||||
editorName: props.bodyEditor,
|
||||
});
|
||||
const onMessage = useMessageHandler(scrollWhenReady, clearScrollWhenReady, windowId, editorRef, setLocalSearchResultCount, props.dispatch, formNote, htmlToMarkdown, markupToHtml);
|
||||
const onMessage = useMessageHandler(scrollWhenReadyRef, clearScrollWhenReady, windowId, editorRef, setLocalSearchResultCount, props.dispatch, formNote, htmlToMarkdown, markupToHtml);
|
||||
|
||||
useResourceUnwatcher({ noteId: formNote.id, windowId });
|
||||
|
||||
@@ -409,6 +411,14 @@ function NoteEditorContent(props: NoteEditorProps) {
|
||||
});
|
||||
}, [props.dispatch]);
|
||||
|
||||
const onCursorMotion = useCallback((location: EditorCursorLocations) => {
|
||||
props.dispatch({
|
||||
type: 'EDITOR_CURSOR_POSITION_SET',
|
||||
noteId: formNoteRef.current.id,
|
||||
location,
|
||||
});
|
||||
}, [props.dispatch]);
|
||||
|
||||
function renderNoNotes(rootStyle: React.CSSProperties) {
|
||||
const emptyDivStyle = {
|
||||
backgroundColor: 'black',
|
||||
@@ -419,6 +429,9 @@ function NoteEditorContent(props: NoteEditorProps) {
|
||||
}
|
||||
|
||||
const searchMarkers = useSearchMarkers(showLocalSearch, localSearchMarkerOptions, props.searches, props.selectedSearchId, props.highlightedWords);
|
||||
const initialCursorLocation = useInitialCursorLocation({
|
||||
lastEditorCursorLocations: props.lastEditorCursorLocations, noteId: props.noteId,
|
||||
});
|
||||
|
||||
const markupLanguage = formNote.markup_language;
|
||||
const editorProps: NoteBodyEditorPropsAndRef = {
|
||||
@@ -432,6 +445,7 @@ function NoteEditorContent(props: NoteEditorProps) {
|
||||
content: formNote.body,
|
||||
contentMarkupLanguage: markupLanguage,
|
||||
contentOriginalCss: formNote.originalCss,
|
||||
initialCursorLocation,
|
||||
resourceInfos: resourceInfos,
|
||||
resourceDirectory: Setting.value('resourceDir'),
|
||||
htmlToMarkdown: htmlToMarkdown,
|
||||
@@ -442,6 +456,7 @@ function NoteEditorContent(props: NoteEditorProps) {
|
||||
dispatch: props.dispatch,
|
||||
noteToolbar: null,
|
||||
onScroll: onScroll,
|
||||
onCursorMotion,
|
||||
setLocalSearchResultCount: setLocalSearchResultCount,
|
||||
setLocalSearch: localSearch_change,
|
||||
setShowLocalSearch,
|
||||
@@ -729,6 +744,7 @@ const mapStateToProps = (state: AppState, ownProps: ConnectProps) => {
|
||||
notesParentType: windowState.notesParentType,
|
||||
selectedNoteTags: windowState.selectedNoteTags,
|
||||
lastEditorScrollPercents: state.lastEditorScrollPercents,
|
||||
lastEditorCursorLocations: state.lastEditorCursorLocations,
|
||||
selectedNoteHash: windowState.selectedNoteHash,
|
||||
searches: state.searches,
|
||||
selectedSearchId: windowState.selectedSearchId,
|
||||
|
||||
@@ -15,6 +15,7 @@ const joplinRendererUtils = require('@joplin/renderer').utils;
|
||||
const { clipboard } = require('electron');
|
||||
import * as mimeUtils from '@joplin/lib/mime-utils';
|
||||
import bridge from '../../../services/bridge';
|
||||
import { getCollator, getCollatorLocale } from '@joplin/lib/models/utils/getCollator';
|
||||
const md5 = require('md5');
|
||||
const path = require('path');
|
||||
|
||||
@@ -43,22 +44,30 @@ export async function commandAttachFileToBody(body: string, filePaths: string[]
|
||||
if (!filePaths || !filePaths.length) return null;
|
||||
}
|
||||
|
||||
const collatorLocale = getCollatorLocale();
|
||||
const collator = getCollator(collatorLocale);
|
||||
filePaths = filePaths.sort((a, b) => {
|
||||
return collator.compare(a, b);
|
||||
});
|
||||
|
||||
let pos = options.position ?? 0;
|
||||
|
||||
for (let i = 0; i < filePaths.length; i++) {
|
||||
const filePath = filePaths[i];
|
||||
const beforeLen = body.length;
|
||||
try {
|
||||
logger.info(`Attaching ${filePath}`);
|
||||
const newBody = await shim.attachFileToNoteBody(body, filePath, options.position, {
|
||||
const newBody = await shim.attachFileToNoteBody(body, filePath, pos, {
|
||||
createFileURL: options.createFileURL,
|
||||
resizeLargeImages: Setting.value('imageResizing'),
|
||||
markupLanguage: options.markupLanguage,
|
||||
resourceSuffix: i > 0 ? ' ' : '',
|
||||
resourcePrefix: i > 0 ? ' ' : '',
|
||||
});
|
||||
|
||||
if (!newBody) {
|
||||
logger.info('File attachment was cancelled');
|
||||
return null;
|
||||
}
|
||||
|
||||
pos += newBody.length - beforeLen;
|
||||
body = newBody;
|
||||
logger.info('File was attached.');
|
||||
} catch (error) {
|
||||
@@ -66,7 +75,6 @@ export async function commandAttachFileToBody(body: string, filePaths: string[]
|
||||
bridge().showErrorMessageBox(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ import { MarkupToHtmlOptions } from '../../hooks/useMarkupToHtml';
|
||||
import { ScrollbarSize } from '@joplin/lib/models/settings/builtInMetadata';
|
||||
import { RefObject, SetStateAction } from 'react';
|
||||
import * as React from 'react';
|
||||
import { ResourceEntity, ResourceLocalStateEntity } from '@joplin/lib/services/database/types';
|
||||
import { EditorCursorLocations, NoteIdToEditorCursorLocations, NoteIdToScrollPercent } from '../../../app.reducer';
|
||||
|
||||
export interface AllAssetsOptions {
|
||||
contentMaxWidthTarget?: string;
|
||||
@@ -39,8 +41,8 @@ export interface NoteEditorProps {
|
||||
notesParentType: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
selectedNoteTags: any[];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
lastEditorScrollPercents: any;
|
||||
lastEditorScrollPercents: NoteIdToScrollPercent;
|
||||
lastEditorCursorLocations: NoteIdToEditorCursorLocations;
|
||||
selectedNoteHash: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
searches: any[];
|
||||
@@ -82,6 +84,14 @@ export interface NoteBodyEditorRef {
|
||||
export { MarkupToHtmlOptions };
|
||||
export type MarkupToHtmlHandler = (markupLanguage: MarkupLanguage, markup: string, options: MarkupToHtmlOptions)=> Promise<RenderResult>;
|
||||
export type HtmlToMarkdownHandler = (markupLanguage: number, html: string, originalCss: string, parseOptions?: ParseOptions)=> Promise<string>;
|
||||
export type OnCursorMotion = (event: EditorCursorLocations)=> void;
|
||||
|
||||
export interface MessageEvent {
|
||||
channel: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Partially refactored old code before rule was applied
|
||||
args?: any[];
|
||||
}
|
||||
export type OnMessage = (event: MessageEvent)=> void;
|
||||
|
||||
export interface NoteBodyEditorProps {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
@@ -101,12 +111,13 @@ export interface NoteBodyEditorProps {
|
||||
contentKey: string;
|
||||
contentMarkupLanguage: number;
|
||||
contentOriginalCss: string;
|
||||
initialCursorLocation: EditorCursorLocations;
|
||||
onChange(event: OnChangeEvent): void;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
onWillChange(event: any): void;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
onMessage(event: any): void;
|
||||
onMessage: OnMessage;
|
||||
onScroll(event: { percent: number }): void;
|
||||
onCursorMotion: OnCursorMotion;
|
||||
markupToHtml: MarkupToHtmlHandler;
|
||||
htmlToMarkdown: HtmlToMarkdownHandler;
|
||||
allAssets: (markupLanguage: MarkupLanguage, options: AllAssetsOptions)=> Promise<RenderResultPluginAsset[]>;
|
||||
@@ -214,10 +225,8 @@ export function defaultFormNote(): FormNote {
|
||||
}
|
||||
|
||||
export interface ResourceInfo {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
localState: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
item: any;
|
||||
localState: ResourceLocalStateEntity;
|
||||
item: ResourceEntity;
|
||||
}
|
||||
|
||||
export interface ResourceInfos {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { useMemo } from 'react';
|
||||
import { EditorCursorLocations, NoteIdToEditorCursorLocations } from '../../../app.reducer';
|
||||
|
||||
interface Props {
|
||||
lastEditorCursorLocations: NoteIdToEditorCursorLocations;
|
||||
noteId: string;
|
||||
}
|
||||
|
||||
const useInitialCursorLocation = ({ noteId, lastEditorCursorLocations }: Props) => {
|
||||
const lastCursorLocation = lastEditorCursorLocations[noteId];
|
||||
|
||||
return useMemo((): EditorCursorLocations => {
|
||||
return lastCursorLocation ?? { };
|
||||
}, [lastCursorLocation]);
|
||||
};
|
||||
|
||||
export default useInitialCursorLocation;
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback } from 'react';
|
||||
import { FormNote, HtmlToMarkdownHandler, MarkupToHtmlHandler, ScrollOptions } from './types';
|
||||
import { RefObject, useCallback } from 'react';
|
||||
import { FormNote, HtmlToMarkdownHandler, MarkupToHtmlHandler, ScrollOptions, MessageEvent } from './types';
|
||||
import contextMenu from './contextMenu';
|
||||
import CommandService from '@joplin/lib/services/CommandService';
|
||||
import PostMessageService from '@joplin/lib/services/PostMessageService';
|
||||
@@ -8,7 +8,7 @@ import { reg } from '@joplin/lib/registry';
|
||||
import bridge from '../../../services/bridge';
|
||||
|
||||
export default function useMessageHandler(
|
||||
scrollWhenReady: ScrollOptions|null,
|
||||
scrollWhenReadyRef: RefObject<ScrollOptions|null>,
|
||||
clearScrollWhenReady: ()=> void,
|
||||
windowId: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
@@ -21,8 +21,7 @@ export default function useMessageHandler(
|
||||
htmlToMd: HtmlToMarkdownHandler,
|
||||
mdToHtml: MarkupToHtmlHandler,
|
||||
) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
return useCallback(async (event: any) => {
|
||||
return useCallback(async (event: MessageEvent) => {
|
||||
const msg = event.channel ? event.channel : '';
|
||||
const args = event.args;
|
||||
const arg0 = args && args.length >= 1 ? args[0] : null;
|
||||
@@ -35,8 +34,8 @@ export default function useMessageHandler(
|
||||
s.splice(0, 1);
|
||||
reg.logger().error(s.join(':'));
|
||||
} else if (msg === 'noteRenderComplete') {
|
||||
if (scrollWhenReady) {
|
||||
const options = { ...scrollWhenReady };
|
||||
if (scrollWhenReadyRef.current) {
|
||||
const options = { ...scrollWhenReadyRef.current };
|
||||
clearScrollWhenReady();
|
||||
editorRef.current.scrollTo(options);
|
||||
}
|
||||
@@ -78,5 +77,5 @@ export default function useMessageHandler(
|
||||
// bridge().showErrorMessageBox(_('Unsupported link or message: %s', msg));
|
||||
}
|
||||
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
|
||||
}, [dispatch, setLocalSearchResultCount, scrollWhenReady, formNote]);
|
||||
}, [dispatch, setLocalSearchResultCount, scrollWhenReadyRef, formNote]);
|
||||
}
|
||||
|
||||
@@ -1,41 +1,48 @@
|
||||
import { RefObject, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { RefObject, useCallback, useRef } from 'react';
|
||||
import { NoteBodyEditorRef, ScrollOptions, ScrollOptionTypes } from './types';
|
||||
import usePrevious from '@joplin/lib/hooks/usePrevious';
|
||||
import type { EditorScrollPercents } from '../../../app.reducer';
|
||||
import type { NoteIdToScrollPercent } from '../../../app.reducer';
|
||||
import useNowEffect from '@joplin/lib/hooks/useNowEffect';
|
||||
|
||||
interface Props {
|
||||
noteId: string;
|
||||
editorName: string;
|
||||
selectedNoteHash: string;
|
||||
lastEditorScrollPercents: EditorScrollPercents;
|
||||
lastEditorScrollPercents: NoteIdToScrollPercent;
|
||||
editorRef: RefObject<NoteBodyEditorRef>;
|
||||
}
|
||||
|
||||
const useScrollWhenReadyOptions = ({ noteId, selectedNoteHash, lastEditorScrollPercents, editorRef }: Props) => {
|
||||
const [scrollWhenReady, setScrollWhenReady] = useState<ScrollOptions|null>(null);
|
||||
const useScrollWhenReadyOptions = ({ noteId, editorName, selectedNoteHash, lastEditorScrollPercents, editorRef }: Props) => {
|
||||
const scrollWhenReadyRef = useRef<ScrollOptions|null>(null);
|
||||
|
||||
const previousNoteId = usePrevious(noteId);
|
||||
const lastScrollPercentsRef = useRef<EditorScrollPercents>(null);
|
||||
const noteIdChanged = noteId !== previousNoteId;
|
||||
const previousEditor = usePrevious(editorName);
|
||||
const editorChanged = editorName !== previousEditor;
|
||||
const lastScrollPercentsRef = useRef<NoteIdToScrollPercent>(null);
|
||||
lastScrollPercentsRef.current = lastEditorScrollPercents;
|
||||
|
||||
useEffect(() => {
|
||||
if (noteId === previousNoteId) return;
|
||||
// This needs to be a nowEffect to prevent race conditions
|
||||
useNowEffect(() => {
|
||||
if (!editorChanged && !noteIdChanged) return () => {};
|
||||
|
||||
if (editorRef.current) {
|
||||
editorRef.current.resetScroll();
|
||||
}
|
||||
|
||||
const lastScrollPercent = lastScrollPercentsRef.current[noteId] || 0;
|
||||
setScrollWhenReady({
|
||||
scrollWhenReadyRef.current = {
|
||||
type: selectedNoteHash ? ScrollOptionTypes.Hash : ScrollOptionTypes.Percent,
|
||||
value: selectedNoteHash ? selectedNoteHash : lastScrollPercent,
|
||||
});
|
||||
}, [noteId, previousNoteId, selectedNoteHash, editorRef]);
|
||||
};
|
||||
return () => {};
|
||||
}, [editorChanged, noteIdChanged, noteId, selectedNoteHash, editorRef]);
|
||||
|
||||
const clearScrollWhenReady = useCallback(() => {
|
||||
setScrollWhenReady(null);
|
||||
scrollWhenReadyRef.current = null;
|
||||
}, []);
|
||||
|
||||
return { scrollWhenReady, clearScrollWhenReady };
|
||||
return { scrollWhenReadyRef, clearScrollWhenReady };
|
||||
};
|
||||
|
||||
export default useScrollWhenReadyOptions;
|
||||
|
||||
@@ -343,6 +343,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
||||
style={styles.input}
|
||||
id={uniqueId(key)}
|
||||
name={uniqueId(key)}
|
||||
autoFocus
|
||||
/>;
|
||||
|
||||
editCompHandler = () => {
|
||||
@@ -363,6 +364,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
||||
id={uniqueId(key)}
|
||||
name={uniqueId(key)}
|
||||
aria-invalid={!this.state.isValid.location}
|
||||
autoFocus
|
||||
/>
|
||||
{
|
||||
this.state.isValid.location ? null
|
||||
@@ -387,6 +389,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
||||
style={styles.input}
|
||||
id={uniqueId(key)}
|
||||
name={uniqueId(key)}
|
||||
autoFocus
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -498,7 +501,12 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
||||
<div role='table' aria-labelledby='note-properties-dialog-title'>
|
||||
{noteComps}
|
||||
</div>
|
||||
<DialogButtonRow themeId={this.props.themeId} okButtonShow={!this.isReadOnly()} okButtonRef={this.okButton} onClick={this.buttonRow_click}/>
|
||||
<DialogButtonRow
|
||||
themeId={this.props.themeId}
|
||||
okButtonShow={!this.isReadOnly()}
|
||||
okButtonRef={this.okButton}
|
||||
onClick={this.buttonRow_click}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -251,8 +251,6 @@ export default class PromptDialog extends React.Component<Props, any> {
|
||||
} else {
|
||||
onClose(true);
|
||||
}
|
||||
} else if (event.key === 'Escape') {
|
||||
onClose(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -309,7 +307,7 @@ export default class PromptDialog extends React.Component<Props, any> {
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog className='prompt-dialog' contentStyle={styles.dialog}>
|
||||
<Dialog className='prompt-dialog' contentStyle={styles.dialog} onCancel={() => onClose(false, 'cancel')}>
|
||||
<label style={styles.label}>{this.props.label ? this.props.label : ''}</label>
|
||||
<div style={{ display: 'inline-block', color: 'black', backgroundColor: theme.backgroundColor }}>
|
||||
{inputComp}
|
||||
|
||||
@@ -71,7 +71,7 @@ async function initialize() {
|
||||
panes: Setting.value('noteVisiblePanes'),
|
||||
});
|
||||
|
||||
InteropService.instance().document = document;
|
||||
InteropService.instance().domParser = new DOMParser();
|
||||
InteropService.instance().xmlSerializer = new XMLSerializer();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as React from 'react';
|
||||
import Dialog from '../Dialog';
|
||||
import DialogButtonRow, { ClickEvent, ButtonSpec } from '../DialogButtonRow';
|
||||
import DialogButtonRow, { ClickEvent } from '../DialogButtonRow';
|
||||
import DialogTitle from '../DialogTitle';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { FolderEntity } from '@joplin/lib/services/database/types';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
import ShareService, { ApiShare } from '@joplin/lib/services/share/ShareService';
|
||||
@@ -129,7 +129,6 @@ function ShareFolderDialog(props: Props) {
|
||||
const [share, setShare] = useState<StateShare>(null);
|
||||
const [shareUsers, setShareUsers] = useState<StateShareUser[]>([]);
|
||||
const [shareState, setShareState] = useState<ShareState>(ShareState.Idle);
|
||||
const [customButtons, setCustomButtons] = useState<ButtonSpec[]>([]);
|
||||
const [recipientsBeingUpdated, setRecipientsBeingUpdated] = useState<Record<string, boolean>>({});
|
||||
|
||||
async function synchronize(event: AsyncEffectEvent = null) {
|
||||
@@ -163,13 +162,6 @@ function ShareFolderDialog(props: Props) {
|
||||
void ShareService.instance().refreshShareUsers(share.id);
|
||||
}, [share]);
|
||||
|
||||
useEffect(() => {
|
||||
setCustomButtons(share ? [{
|
||||
name: 'unshare',
|
||||
label: _('Unshare'),
|
||||
}] : []);
|
||||
}, [share]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!share) return;
|
||||
const sus = props.shareUsers[share.id];
|
||||
@@ -177,10 +169,6 @@ function ShareFolderDialog(props: Props) {
|
||||
setShareUsers(sus);
|
||||
}, [share, props.shareUsers]);
|
||||
|
||||
useEffect(() => {
|
||||
void ShareService.instance().refreshShares();
|
||||
}, [props.folderId]);
|
||||
|
||||
const permissionsFromString = (p: string): SharePermissions => {
|
||||
return {
|
||||
can_read: 1,
|
||||
@@ -269,7 +257,7 @@ function ShareFolderDialog(props: Props) {
|
||||
}, []);
|
||||
|
||||
function renderAddRecipient() {
|
||||
const disabled = shareState !== ShareState.Idle;
|
||||
const disabled = shareState !== ShareState.Idle && shareState !== ShareState.Synchronizing;
|
||||
|
||||
const dropdown = !props.canUseSharePermissions ? null : <Dropdown className="permission-dropdown" options={permissionOptions} value={recipientPermissions} onChange={recipientPermissions_change}/>;
|
||||
|
||||
@@ -395,6 +383,17 @@ function ShareFolderDialog(props: Props) {
|
||||
props.onClose();
|
||||
}
|
||||
|
||||
const customButtons = useMemo(() => {
|
||||
return share ? [{
|
||||
name: 'unshare',
|
||||
label: _('Unshare'),
|
||||
// Don't allow unsharing the folder during the "create" action. Doing so might
|
||||
// be able to cause issues similar to #13518 (e.g. if the "unshare" action completes while
|
||||
// the "share" action is still in progress).
|
||||
disabled: shareState === ShareState.Creating || shareState === ShareState.Synchronizing,
|
||||
}] : [];
|
||||
}, [share, shareState]);
|
||||
|
||||
function renderContent() {
|
||||
return (
|
||||
<StyledRoot className="share-folder-dialog">
|
||||
|
||||
@@ -25,14 +25,13 @@ interface Props {
|
||||
const SidebarComponent = (props: Props) => {
|
||||
const renderSynchronizeButton = (type: string) => {
|
||||
const label = type === 'sync' ? _('Synchronise') : _('Cancel');
|
||||
const iconAnimation = type !== 'sync' ? 'icon-infinite-rotation 1s linear infinite' : '';
|
||||
|
||||
return (
|
||||
<StyledSynchronizeButton
|
||||
level={ButtonLevel.SidebarSecondary}
|
||||
className={`sidebar-sync-button ${type === 'sync' ? '' : '-syncing'}`}
|
||||
iconName="icon-sync"
|
||||
key="sync_button"
|
||||
iconAnimation={iconAnimation}
|
||||
title={label}
|
||||
onClick={() => {
|
||||
void CommandService.instance().execute('synchronize', type !== 'sync');
|
||||
|
||||
@@ -356,6 +356,7 @@ const useOnRenderItem = (props: Props) => {
|
||||
onClick={tagItem_click}
|
||||
onTagDrop={onTagDrop_}
|
||||
onContextMenu={onItemContextMenu}
|
||||
label={item.label}
|
||||
tag={tag}
|
||||
itemCount={itemCount}
|
||||
index={index}
|
||||
@@ -384,7 +385,7 @@ const useOnRenderItem = (props: Props) => {
|
||||
anchorRef={anchorRef}
|
||||
selected={selected}
|
||||
folderId={folder.id}
|
||||
folderTitle={Folder.displayTitle(folder)}
|
||||
folderTitle={item.label}
|
||||
folderIcon={Folder.unserializeIcon(folder.icon)}
|
||||
depth={item.depth}
|
||||
isExpanded={isExpanded}
|
||||
|
||||
@@ -49,6 +49,24 @@ const getParentOffset = (childIndex: number, listItems: ListItem[]): number|null
|
||||
return null;
|
||||
};
|
||||
|
||||
const findNextTypeAheadMatch = (selectedIndex: number, query: string, listItems: ListItem[]) => {
|
||||
const matches = (item: ListItem) => {
|
||||
return item.label.startsWith(query);
|
||||
};
|
||||
const indexBefore = listItems.slice(0, selectedIndex).findIndex(matches);
|
||||
// Search in all results **after** the current. This prevents the current item from
|
||||
// always being identified as the next match, if the user repeatedly presses the
|
||||
// same key.
|
||||
const startAfter = selectedIndex + 1;
|
||||
let indexAfter = listItems.slice(startAfter).findIndex(matches);
|
||||
if (indexAfter !== -1) {
|
||||
indexAfter += startAfter;
|
||||
}
|
||||
// Prefer jumping to the next match, rather than the previous
|
||||
const matchingIndex = indexAfter !== -1 ? indexAfter : indexBefore;
|
||||
return matchingIndex;
|
||||
};
|
||||
|
||||
const useOnSidebarKeyDownHandler = (props: Props) => {
|
||||
const { updateSelectedIndex, listItems, selectedIndex, collapsedFolderIds, dispatch } = props;
|
||||
|
||||
@@ -56,6 +74,8 @@ const useOnSidebarKeyDownHandler = (props: Props) => {
|
||||
const selectedItem = listItems[selectedIndex];
|
||||
let indexChange = 0;
|
||||
|
||||
const ctrlAltOrMeta = event.ctrlKey || event.altKey || event.metaKey;
|
||||
|
||||
if (selectedItem && isToggleShortcut(event.code, selectedItem, collapsedFolderIds)) {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -82,9 +102,22 @@ const useOnSidebarKeyDownHandler = (props: Props) => {
|
||||
indexChange = 1;
|
||||
} else if ((event.ctrlKey || event.metaKey) && event.code === 'KeyA') { // ctrl+a or cmd+a
|
||||
event.preventDefault();
|
||||
} else if (event.code === 'Home') {
|
||||
event.preventDefault();
|
||||
updateSelectedIndex(0);
|
||||
indexChange = 0;
|
||||
} else if (event.code === 'End') {
|
||||
event.preventDefault();
|
||||
updateSelectedIndex(listItems.length - 1);
|
||||
indexChange = 0;
|
||||
} else if (event.code === 'Enter' && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
void CommandService.instance().execute('focusElement', 'noteList');
|
||||
} else if (selectedIndex && selectedIndex >= 0 && event.key.length === 1 && !ctrlAltOrMeta) {
|
||||
const nextMatch = findNextTypeAheadMatch(selectedIndex, event.key, listItems);
|
||||
if (nextMatch !== -1) {
|
||||
indexChange = nextMatch - selectedIndex;
|
||||
}
|
||||
}
|
||||
|
||||
if (indexChange !== 0) {
|
||||
|
||||
@@ -4,6 +4,8 @@ import { FolderEntity, TagsWithNoteCountEntity } from '@joplin/lib/services/data
|
||||
import { buildFolderTree, renderFolders, renderTags } from '@joplin/lib/components/shared/side-menu-shared';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import toggleHeader from './utils/toggleHeader';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
import Tag from '@joplin/lib/models/Tag';
|
||||
|
||||
interface Props {
|
||||
tags: TagsWithNoteCountEntity[];
|
||||
@@ -18,6 +20,7 @@ const useSidebarListData = (props: Props): ListItem[] => {
|
||||
return renderTags<ListItem>(props.tags, (tag): TagListItem => {
|
||||
return {
|
||||
kind: ListItemType.Tag,
|
||||
label: Tag.displayTitle(tag),
|
||||
tag,
|
||||
key: tag.id,
|
||||
depth: 1,
|
||||
@@ -38,6 +41,7 @@ const useSidebarListData = (props: Props): ListItem[] => {
|
||||
return renderFolders<ListItem>(renderProps, (folder, hasChildren, depth): FolderListItem => {
|
||||
return {
|
||||
kind: ListItemType.Folder,
|
||||
label: Folder.displayTitle(folder),
|
||||
folder,
|
||||
hasChildren,
|
||||
// The toplevel headers have depth 1, so the toplevel notebook needs
|
||||
@@ -65,9 +69,9 @@ const useSidebarListData = (props: Props): ListItem[] => {
|
||||
hasChildren: folderItems.items.length > 0,
|
||||
};
|
||||
const foldersSectionContent: ListItem[] = props.folderHeaderIsExpanded ? [
|
||||
{ kind: ListItemType.AllNotes, key: 'all-notes', depth: 2, hasChildren: false },
|
||||
{ kind: ListItemType.AllNotes, label: _('All notes'), key: 'all-notes', depth: 2, hasChildren: false },
|
||||
...folderItems.items,
|
||||
{ kind: ListItemType.Spacer, key: 'after-folders-spacer', depth: 1, hasChildren: false },
|
||||
{ kind: ListItemType.Spacer, label: '', key: 'after-folders-spacer', depth: 1, hasChildren: false },
|
||||
] : [];
|
||||
|
||||
const tagsHeader: HeaderListItem = {
|
||||
|
||||
@@ -7,7 +7,6 @@ import Setting from '@joplin/lib/models/Setting';
|
||||
import MenuUtils from '@joplin/lib/services/commands/MenuUtils';
|
||||
import CommandService from '@joplin/lib/services/CommandService';
|
||||
import PerFolderSortOrderService from '../../../services/sortOrder/PerFolderSortOrderService';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { connect } from 'react-redux';
|
||||
import EmptyExpandLink from './EmptyExpandLink';
|
||||
import ListItemWrapper, { ListItemRef } from './ListItemWrapper';
|
||||
@@ -70,7 +69,7 @@ const AllNotesItem: React.FC<Props> = props => {
|
||||
onClick={onAllNotesClick_}
|
||||
onContextMenu={toggleAllNotesContextMenu}
|
||||
>
|
||||
{_('All notes')}
|
||||
{props.item.label}
|
||||
</StyledListItemAnchor>
|
||||
</ListItemWrapper>
|
||||
);
|
||||
|
||||
@@ -58,7 +58,9 @@ const HeaderItem: React.FC<Props> = props => {
|
||||
{...item.extraProps}
|
||||
onDrop={props.onDrop}
|
||||
>
|
||||
<StyledHeader onClick={onClick}>
|
||||
<StyledHeader
|
||||
onClick={onClick}
|
||||
>
|
||||
<StyledHeaderIcon aria-hidden='true' role='img' className={item.iconName}/>
|
||||
<StyledHeaderLabel>{item.label}</StyledHeaderLabel>
|
||||
</StyledHeader>
|
||||
|
||||