1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-08-24 20:19:10 +02:00

Compare commits

...

30 Commits

Author SHA1 Message Date
Laurent Cozic
b01f82bb33 Android release v2.0.1 2021-05-16 17:54:13 +02:00
Laurent Cozic
b6c9edba21 Tools: Make sure branch has been pushed before releasing Android version 2021-05-16 17:50:50 +02:00
Laurent Cozic
f7d164be6e Desktop: Allow unsharing a note 2021-05-16 17:28:49 +02:00
Laurent Cozic
6f2f24171d Desktop: Add Share Notebook menu item 2021-05-16 15:21:55 +02:00
Laurent Cozic
12cc64008b typo 2021-05-16 12:49:05 +02:00
Laurent Cozic
b9955f58d3 Server: Refactor ShareType 2021-05-16 12:46:58 +02:00
Laurent Cozic
489995daef Server: Fixed deleting a note that has been shared 2021-05-16 12:42:58 +02:00
Laurent Cozic
e156ee1b58 Server: Generate only one share link per note 2021-05-16 12:33:36 +02:00
Laurent Cozic
a24b0091ad Server: Go back to home page when there is an error and user is logged in 2021-05-16 12:19:18 +02:00
Laurent Cozic
2655b6deee Merge branch 'dev' of github.com:laurent22/joplin into dev 2021-05-16 11:48:15 +02:00
Laurent Cozic
45c40f7395 Server: Fixed log page 2021-05-16 11:46:16 +02:00
Woosuk Park
ecb0eee355 All: Translation: Update ko.po (#4976)
* Some Translated. and some modified

* ko.po Update
2021-05-16 02:39:43 -04:00
suixinio
4916f4cc92 All: Translation: Update zh_CN.po (#4969)
This PR updates zh_CN.po, with just a minor translation fix.
2021-05-15 15:58:48 -04:00
Laurent Cozic
15fe119256 Desktop: Made sync more reliable by making it skip items that time out, and improved sync status screen 2021-05-15 20:56:49 +02:00
Laurent Cozic
0b46880a00 Desktop: Fixes #4926: Fixed issue with empty panels being created by plugins 2021-05-15 17:30:56 +02:00
Laurent Cozic
deaa731983 Update translations 2021-05-15 16:15:55 +02:00
Laurent Cozic
d061bb1a4f typo 2021-05-15 16:06:24 +02:00
Laurent Cozic
03db0c5486 Desktop: Resolves #4462: Improved usability when plugin repository cannot be connected to 2021-05-15 16:04:10 +02:00
Laurent Cozic
bb275e671d Tools: Allow running the test units with Postgres 2021-05-15 15:13:08 +02:00
Laurent Cozic
2d0580ff71 Server: Fixed /items page when using Postgres 2021-05-15 15:10:40 +02:00
Laurent Cozic
2331d3487b Desktop, Cli: Resolves #4968: Import SVG as images when importing ENEX files 2021-05-15 13:42:57 +02:00
Laurent Cozic
f1380fd51d Server: Fixes #4540: Make sure temp files are deleted after upload is done 2021-05-15 12:13:46 +02:00
Laurent Cozic
d462dab8eb Update French translation 2021-05-15 11:36:52 +02:00
Laurent Cozic
cf37b74d9a Merge branch 'dev' of github.com:laurent22/joplin into dev 2021-05-15 11:14:40 +02:00
Laurent Cozic
aec3ea9c0c Desktop, Cli: Fixes #4965: Improved importing Evernote notes that contain codeblocks 2021-05-15 11:12:11 +02:00
Helmut K. C. Tessarek
1f5aa70acd Update translations (for new server/client code) 2021-05-14 13:27:17 -04:00
Harris Arvanitis
416637ce83 All: Translation: Update el_GR.po (#4961) 2021-05-14 13:23:13 -04:00
Gen Neko
99c4b0bc01 All: Translation: Update ja_JP.po (#4960) 2021-05-14 13:22:35 -04:00
Laurent Cozic
74d8fec98a Desktop release v2.0.1 2021-05-14 17:17:40 +02:00
Laurent Cozic
321a58c356 Prepare for v2 2021-05-14 17:17:02 +02:00
167 changed files with 17518 additions and 13509 deletions

View File

@@ -578,6 +578,9 @@ packages/app-desktop/gui/ResizableLayout/utils/persist.test.js.map
packages/app-desktop/gui/ResizableLayout/utils/removeItem.d.ts
packages/app-desktop/gui/ResizableLayout/utils/removeItem.js
packages/app-desktop/gui/ResizableLayout/utils/removeItem.js.map
packages/app-desktop/gui/ResizableLayout/utils/removeKeylessItems.d.ts
packages/app-desktop/gui/ResizableLayout/utils/removeKeylessItems.js
packages/app-desktop/gui/ResizableLayout/utils/removeKeylessItems.js.map
packages/app-desktop/gui/ResizableLayout/utils/setLayoutItemProps.d.ts
packages/app-desktop/gui/ResizableLayout/utils/setLayoutItemProps.js
packages/app-desktop/gui/ResizableLayout/utils/setLayoutItemProps.js.map
@@ -677,6 +680,9 @@ packages/app-desktop/gui/style/StyledFormLabel.js.map
packages/app-desktop/gui/style/StyledInput.d.ts
packages/app-desktop/gui/style/StyledInput.js
packages/app-desktop/gui/style/StyledInput.js.map
packages/app-desktop/gui/style/StyledLink.d.ts
packages/app-desktop/gui/style/StyledLink.js
packages/app-desktop/gui/style/StyledLink.js.map
packages/app-desktop/gui/style/StyledMessage.d.ts
packages/app-desktop/gui/style/StyledMessage.js
packages/app-desktop/gui/style/StyledMessage.js.map
@@ -1100,6 +1106,9 @@ packages/lib/services/UndoRedoService.js.map
packages/lib/services/WhenClause.d.ts
packages/lib/services/WhenClause.js
packages/lib/services/WhenClause.js.map
packages/lib/services/WhenClause.test.d.ts
packages/lib/services/WhenClause.test.js
packages/lib/services/WhenClause.test.js.map
packages/lib/services/commands/MenuUtils.d.ts
packages/lib/services/commands/MenuUtils.js
packages/lib/services/commands/MenuUtils.js.map

View File

@@ -76,7 +76,7 @@ module.exports = {
// Warn only for now because fixing everything would take too much
// refactoring, but new code should try to stick to it.
'complexity': ['warn', { max: 10 }],
// 'complexity': ['warn', { max: 10 }],
// Checks rules of Hooks
'react-hooks/rules-of-hooks': 'error',

9
.gitignore vendored
View File

@@ -564,6 +564,9 @@ packages/app-desktop/gui/ResizableLayout/utils/persist.test.js.map
packages/app-desktop/gui/ResizableLayout/utils/removeItem.d.ts
packages/app-desktop/gui/ResizableLayout/utils/removeItem.js
packages/app-desktop/gui/ResizableLayout/utils/removeItem.js.map
packages/app-desktop/gui/ResizableLayout/utils/removeKeylessItems.d.ts
packages/app-desktop/gui/ResizableLayout/utils/removeKeylessItems.js
packages/app-desktop/gui/ResizableLayout/utils/removeKeylessItems.js.map
packages/app-desktop/gui/ResizableLayout/utils/setLayoutItemProps.d.ts
packages/app-desktop/gui/ResizableLayout/utils/setLayoutItemProps.js
packages/app-desktop/gui/ResizableLayout/utils/setLayoutItemProps.js.map
@@ -663,6 +666,9 @@ packages/app-desktop/gui/style/StyledFormLabel.js.map
packages/app-desktop/gui/style/StyledInput.d.ts
packages/app-desktop/gui/style/StyledInput.js
packages/app-desktop/gui/style/StyledInput.js.map
packages/app-desktop/gui/style/StyledLink.d.ts
packages/app-desktop/gui/style/StyledLink.js
packages/app-desktop/gui/style/StyledLink.js.map
packages/app-desktop/gui/style/StyledMessage.d.ts
packages/app-desktop/gui/style/StyledMessage.js
packages/app-desktop/gui/style/StyledMessage.js.map
@@ -1086,6 +1092,9 @@ packages/lib/services/UndoRedoService.js.map
packages/lib/services/WhenClause.d.ts
packages/lib/services/WhenClause.js
packages/lib/services/WhenClause.js.map
packages/lib/services/WhenClause.test.d.ts
packages/lib/services/WhenClause.test.js
packages/lib/services/WhenClause.test.js.map
packages/lib/services/commands/MenuUtils.d.ts
packages/lib/services/commands/MenuUtils.js
packages/lib/services/commands/MenuUtils.js.map

View File

@@ -511,47 +511,47 @@ Current translations:
<!-- LOCALE-TABLE-AUTO-GENERATED -->
&nbsp; | Language | Po File | Last translator | Percent done
---|---|---|---|---
![](https://joplinapp.org/images/flags/country-4x3/arableague.png) | Arabic | [ar](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ar.po) | [Whaell O](mailto:Whaell@protonmail.com) | 98%
![](https://joplinapp.org/images/flags/es/basque_country.png) | Basque | [eu](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eu.po) | juan.abasolo@ehu.eus | 31%
![](https://joplinapp.org/images/flags/country-4x3/ba.png) | Bosnian (Bosna i Hercegovina) | [bs_BA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bs_BA.po) | [Derviš T.](mailto:dervis.t@pm.me) | 76%
![](https://joplinapp.org/images/flags/country-4x3/bg.png) | Bulgarian (България) | [bg_BG](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bg_BG.po) | | 59%
![](https://joplinapp.org/images/flags/es/catalonia.png) | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ca.po) | jmontane, 2019 | 84%
![](https://joplinapp.org/images/flags/country-4x3/hr.png) | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Milo Ivir](mailto:mail@milotype.de) | 99%
![](https://joplinapp.org/images/flags/country-4x3/cz.png) | Czech (Česká republika) | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/cs_CZ.po) | [Lukas Helebrandt](mailto:lukas@aiya.cz) | 88%
![](https://joplinapp.org/images/flags/country-4x3/dk.png) | Dansk (Danmark) | [da_DK](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/da_DK.po) | Mustafa Al-Dailemi (dailemi@hotmail.com)Language-Team: | 98%
![](https://joplinapp.org/images/flags/country-4x3/de.png) | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [Atalanttore](mailto:atalanttore@googlemail.com) | 98%
![](https://joplinapp.org/images/flags/country-4x3/ee.png) | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 58%
![](https://joplinapp.org/images/flags/country-4x3/arableague.png) | Arabic | [ar](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ar.po) | [Whaell O](mailto:Whaell@protonmail.com) | 96%
![](https://joplinapp.org/images/flags/es/basque_country.png) | Basque | [eu](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eu.po) | juan.abasolo@ehu.eus | 30%
![](https://joplinapp.org/images/flags/country-4x3/ba.png) | Bosnian (Bosna i Hercegovina) | [bs_BA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bs_BA.po) | [Derviš T.](mailto:dervis.t@pm.me) | 75%
![](https://joplinapp.org/images/flags/country-4x3/bg.png) | Bulgarian (България) | [bg_BG](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bg_BG.po) | | 58%
![](https://joplinapp.org/images/flags/es/catalonia.png) | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ca.po) | jmontane, 2019 | 83%
![](https://joplinapp.org/images/flags/country-4x3/hr.png) | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Milo Ivir](mailto:mail@milotype.de) | 96%
![](https://joplinapp.org/images/flags/country-4x3/cz.png) | Czech (Česká republika) | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/cs_CZ.po) | [Lukas Helebrandt](mailto:lukas@aiya.cz) | 86%
![](https://joplinapp.org/images/flags/country-4x3/dk.png) | Dansk (Danmark) | [da_DK](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/da_DK.po) | Mustafa Al-Dailemi (dailemi@hotmail.com)Language-Team: | 96%
![](https://joplinapp.org/images/flags/country-4x3/de.png) | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [Atalanttore](mailto:atalanttore@googlemail.com) | 95%
![](https://joplinapp.org/images/flags/country-4x3/ee.png) | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 57%
![](https://joplinapp.org/images/flags/country-4x3/gb.png) | English (United Kingdom) | [en_GB](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_GB.po) | | 100%
![](https://joplinapp.org/images/flags/country-4x3/us.png) | English (United States of America) | [en_US](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_US.po) | | 100%
![](https://joplinapp.org/images/flags/country-4x3/es.png) | Español (España) | [es_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/es_ES.po) | [Mario Campo](mailto:mario.campo@gmail.com) | 97%
![](https://joplinapp.org/images/flags/country-4x3/es.png) | Español (España) | [es_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/es_ES.po) | [Mario Campo](mailto:mario.campo@gmail.com) | 94%
![](https://joplinapp.org/images/flags/esperanto.png) | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 33%
![](https://joplinapp.org/images/flags/country-4x3/fi.png) | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | mrkaato | 97%
![](https://joplinapp.org/images/flags/country-4x3/fr.png) | Français (France) | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fr_FR.po) | Laurent Cozic | 94%
![](https://joplinapp.org/images/flags/es/galicia.png) | Galician (España) | [gl_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/gl_ES.po) | [Marcos Lans](mailto:marcoslansgarza@gmail.com) | 39%
![](https://joplinapp.org/images/flags/country-4x3/id.png) | Indonesian (Indonesia) | [id_ID](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/id_ID.po) | [eresytter](mailto:42007357+eresytter@users.noreply.github.com) | 95%
![](https://joplinapp.org/images/flags/country-4x3/it.png) | Italiano (Italia) | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/it_IT.po) | [Alessandro Bernardello](mailto:mailfilledwithspam@gmail.com) | 97%
![](https://joplinapp.org/images/flags/country-4x3/hu.png) | Magyar (Magyarország) | [hu_HU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hu_HU.po) | [Szőke Sándor](mailto:mail@szokesandor.hu) | 90%
![](https://joplinapp.org/images/flags/country-4x3/be.png) | Nederlands (België, Belgique, Belgien) | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_BE.po) | | 94%
![](https://joplinapp.org/images/flags/country-4x3/nl.png) | Nederlands (Nederland) | [nl_NL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_NL.po) | [MetBril](mailto:metbril@users.noreply.github.com) | 97%
![](https://joplinapp.org/images/flags/country-4x3/no.png) | Norwegian (Norge, Noreg) | [nb_NO](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nb_NO.po) | [Mats Estensen](mailto:code@mxe.no) | 78%
![](https://joplinapp.org/images/flags/country-4x3/ir.png) | Persian | [fa](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fa.po) | [Kourosh Firoozbakht](mailto:kourox@protonmail.com) | 73%
![](https://joplinapp.org/images/flags/country-4x3/pl.png) | Polski (Polska) | [pl_PL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pl_PL.po) | [konhi](mailto:hello.konhi@gmail.com) | 97%
![](https://joplinapp.org/images/flags/country-4x3/br.png) | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_BR.po) | [Nicolas Suzuki](mailto:nicolas.suzuki@pm.me) | 97%
![](https://joplinapp.org/images/flags/country-4x3/pt.png) | Português (Portugal) | [pt_PT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_PT.po) | [Diogo Caveiro](mailto:dcaveiro@yahoo.com) | 97%
![](https://joplinapp.org/images/flags/country-4x3/ro.png) | Română | [ro](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ro.po) | [Cristi Duluta](mailto:cristi.duluta@gmail.com) | 68%
![](https://joplinapp.org/images/flags/country-4x3/si.png) | Slovenian (Slovenija) | [sl_SI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sl_SI.po) | [Martin Korelič](mailto:martin.korelic@protonmail.com) | 98%
![](https://joplinapp.org/images/flags/country-4x3/se.png) | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 63%
![](https://joplinapp.org/images/flags/country-4x3/th.png) | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 46%
![](https://joplinapp.org/images/flags/country-4x3/vi.png) | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 75%
![](https://joplinapp.org/images/flags/country-4x3/tr.png) | Türkçe (Türkiye) | [tr_TR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/tr_TR.po) | [Arda Kılıçdağı](mailto:arda@kilicdagi.com) | 97%
![](https://joplinapp.org/images/flags/country-4x3/ua.png) | Ukrainian (Україна) | [uk_UA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/uk_UA.po) | [Vyacheslav Andreykiv](mailto:vandreykiv@gmail.com) | 97%
![](https://joplinapp.org/images/flags/country-4x3/gr.png) | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 84%
![](https://joplinapp.org/images/flags/country-4x3/ru.png) | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Sergey Segeda](mailto:thesermanarm@gmail.com) | 97%
![](https://joplinapp.org/images/flags/country-4x3/rs.png) | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 73%
![](https://joplinapp.org/images/flags/country-4x3/cn.png) | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [Yang Zhang](mailto:zyangmath@gmail.com) | 97%
![](https://joplinapp.org/images/flags/country-4x3/tw.png) | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [Yaoze Ye](mailto:yaozeye@yahoo.co.jp) | 95%
![](https://joplinapp.org/images/flags/country-4x3/jp.png) | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 98%
![](https://joplinapp.org/images/flags/country-4x3/kr.png) | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 99%
![](https://joplinapp.org/images/flags/country-4x3/fi.png) | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | mrkaato | 94%
![](https://joplinapp.org/images/flags/country-4x3/fr.png) | Français (France) | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fr_FR.po) | Laurent Cozic | 99%
![](https://joplinapp.org/images/flags/es/galicia.png) | Galician (España) | [gl_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/gl_ES.po) | [Marcos Lans](mailto:marcoslansgarza@gmail.com) | 38%
![](https://joplinapp.org/images/flags/country-4x3/id.png) | Indonesian (Indonesia) | [id_ID](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/id_ID.po) | [eresytter](mailto:42007357+eresytter@users.noreply.github.com) | 93%
![](https://joplinapp.org/images/flags/country-4x3/it.png) | Italiano (Italia) | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/it_IT.po) | [Alessandro Bernardello](mailto:mailfilledwithspam@gmail.com) | 94%
![](https://joplinapp.org/images/flags/country-4x3/hu.png) | Magyar (Magyarország) | [hu_HU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hu_HU.po) | [Szőke Sándor](mailto:mail@szokesandor.hu) | 88%
![](https://joplinapp.org/images/flags/country-4x3/be.png) | Nederlands (België, Belgique, Belgien) | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_BE.po) | | 92%
![](https://joplinapp.org/images/flags/country-4x3/nl.png) | Nederlands (Nederland) | [nl_NL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_NL.po) | [MetBril](mailto:metbril@users.noreply.github.com) | 95%
![](https://joplinapp.org/images/flags/country-4x3/no.png) | Norwegian (Norge, Noreg) | [nb_NO](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nb_NO.po) | [Mats Estensen](mailto:code@mxe.no) | 76%
![](https://joplinapp.org/images/flags/country-4x3/ir.png) | Persian | [fa](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fa.po) | [Kourosh Firoozbakht](mailto:kourox@protonmail.com) | 71%
![](https://joplinapp.org/images/flags/country-4x3/pl.png) | Polski (Polska) | [pl_PL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pl_PL.po) | [konhi](mailto:hello.konhi@gmail.com) | 94%
![](https://joplinapp.org/images/flags/country-4x3/br.png) | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_BR.po) | [Nicolas Suzuki](mailto:nicolas.suzuki@pm.me) | 94%
![](https://joplinapp.org/images/flags/country-4x3/pt.png) | Português (Portugal) | [pt_PT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_PT.po) | [Diogo Caveiro](mailto:dcaveiro@yahoo.com) | 94%
![](https://joplinapp.org/images/flags/country-4x3/ro.png) | Română | [ro](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ro.po) | [Cristi Duluta](mailto:cristi.duluta@gmail.com) | 66%
![](https://joplinapp.org/images/flags/country-4x3/si.png) | Slovenian (Slovenija) | [sl_SI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sl_SI.po) | [Martin Korelič](mailto:martin.korelic@protonmail.com) | 96%
![](https://joplinapp.org/images/flags/country-4x3/se.png) | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 61%
![](https://joplinapp.org/images/flags/country-4x3/th.png) | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 45%
![](https://joplinapp.org/images/flags/country-4x3/vi.png) | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 73%
![](https://joplinapp.org/images/flags/country-4x3/tr.png) | Türkçe (Türkiye) | [tr_TR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/tr_TR.po) | [Arda Kılıçdağı](mailto:arda@kilicdagi.com) | 94%
![](https://joplinapp.org/images/flags/country-4x3/ua.png) | Ukrainian (Україна) | [uk_UA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/uk_UA.po) | [Vyacheslav Andreykiv](mailto:vandreykiv@gmail.com) | 94%
![](https://joplinapp.org/images/flags/country-4x3/gr.png) | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 97%
![](https://joplinapp.org/images/flags/country-4x3/ru.png) | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Sergey Segeda](mailto:thesermanarm@gmail.com) | 94%
![](https://joplinapp.org/images/flags/country-4x3/rs.png) | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 71%
![](https://joplinapp.org/images/flags/country-4x3/cn.png) | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [Yang Zhang](mailto:zyangmath@gmail.com) | 94%
![](https://joplinapp.org/images/flags/country-4x3/tw.png) | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [Yaoze Ye](mailto:yaozeye@yahoo.co.jp) | 92%
![](https://joplinapp.org/images/flags/country-4x3/jp.png) | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 97%
![](https://joplinapp.org/images/flags/country-4x3/kr.png) | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 96%
<!-- LOCALE-TABLE-AUTO-GENERATED -->
# Contributors

View File

@@ -8,9 +8,8 @@
"license": "MIT",
"scripts": {
"audit": "lerna-audit",
"bootstrap": "lerna bootstrap --no-ci",
"bootstrapIgnoreScripts": "lerna bootstrap --ignore-scripts --no-ci",
"bootstrapServerOnly": "lerna bootstrap --no-ci --include-dependents --include-dependencies --scope @joplin/server",
"bootstrap": "lerna bootstrap --force-local --no-ci",
"bootstrapServerOnly": "lerna bootstrap --force-local --no-ci --include-dependents --include-dependencies --scope @joplin/server",
"build": "lerna run build && npm run tsc",
"buildApiDoc": "npm start --prefix=packages/app-cli -- apidoc ../../readme/api/references/rest_api.md",
"buildDoc": "./packages/tools/build-all.sh",

View File

@@ -5,7 +5,7 @@
"author": "Laurent Cozic",
"private": true,
"scripts": {
"test": "jest --config=jest.config.js --bail --forceExit",
"test": "jest --verbose=false --config=jest.config.js --bail --forceExit",
"test-one": "jest --verbose=false --config=jest.config.js --bail --forceExit",
"test-ci": "jest --config=jest.config.js --forceExit",
"build": "gulp build",
@@ -31,7 +31,7 @@
],
"owner": "Laurent Cozic"
},
"version": "1.8.1",
"version": "2.0.0",
"bin": {
"joplin": "./main.js"
},

View File

@@ -1,14 +1,16 @@
For example, consider a web page like this:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
</head>
```
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
</head>
<body>
<script src="page-scripts/page-script.js"></script>
</body>
</html>
<body>
<script src="page-scripts/page-script.js"></script>
</body>
</html>
```
The script "page-script.js" does this:

View File

@@ -1,7 +1,9 @@
Subshell:
(
set -e
false
echo Unreachable
) && echo Great success
```
(
set -e
false
echo Unreachable
) && echo Great success
```

View File

@@ -1 +1 @@
jq -r '.[]|[.index, .name, .section, .award, .industry]|join("\t")' raw.json |pbcopy
`jq -r '.[]|[.index, .name, .section, .award, .industry]|join("\t")' raw.json |pbcopy`

View File

@@ -0,0 +1 @@
<div><div>code block:</div><div><br/></div><div style="box-sizing: border-box; padding: 8px; font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; font-size: 12px; color: rgb(51, 51, 51); border-radius: 4px; background-color: rgb(251, 250, 248); border: 1px solid rgba(0, 0, 0, 0.15);-en-codeblock:true;"><div>public static void main(String[] args) {</div><div><span>&nbsp; &nbsp; System.out.println('Hello World');</span><br/></div><div>}</div></div><div><br/></div><div>end of code block</div></div>

View File

@@ -0,0 +1,9 @@
code block:
```
public static void main(String[] args) {
    System.out.println('Hello World');
}
```
end of code block

View File

@@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "Joplin Web Clipper [DEV]",
"version": "1.8.0",
"version": "2.0.0",
"description": "Capture and save web pages and screenshots from your browser to Joplin.",
"homepage_url": "https://joplinapp.org",
"content_security_policy": "script-src 'self'; object-src 'self'",

View File

@@ -13,8 +13,13 @@ import { PluginItem } from './PluginBox';
import RepositoryApi from '@joplin/lib/services/plugins/RepositoryApi';
import Setting from '@joplin/lib/models/Setting';
import useOnInstallHandler, { OnPluginSettingChangeEvent } from './useOnInstallHandler';
import Logger from '@joplin/lib/Logger';
import StyledMessage from '../../../style/StyledMessage';
import StyledLink from '../../../style/StyledLink';
const { space } = require('styled-system');
const logger = Logger.create('PluginState');
const maxWidth: number = 320;
const Root = styled.div`
@@ -32,6 +37,11 @@ const ToolsButton = styled(Button)`
margin-right: 6px;
`;
const RepoApiErrorMessage = styled(StyledMessage)`
max-width: ${props => props.maxWidth}px;
margin-bottom: 10px;
`;
interface Props {
value: any;
themeId: number;
@@ -84,6 +94,8 @@ export default function(props: Props) {
const [manifestsLoaded, setManifestsLoaded] = useState<boolean>(false);
const [updatingPluginsIds, setUpdatingPluginIds] = useState<Record<string, boolean>>({});
const [canBeUpdatedPluginIds, setCanBeUpdatedPluginIds] = useState<Record<string, boolean>>({});
const [repoApiError, setRepoApiError] = useState<Error>(null);
const [fetchManifestTime, setFetchManifestTime] = useState<number>(Date.now());
const pluginService = PluginService.instance();
@@ -96,9 +108,25 @@ export default function(props: Props) {
useEffect(() => {
let cancelled = false;
async function fetchManifests() {
await repoApi().loadManifests();
setManifestsLoaded(false);
setRepoApiError(null);
let loadError: Error = null;
try {
await repoApi().loadManifests();
} catch (error) {
logger.error(error);
loadError = error;
}
if (cancelled) return;
setManifestsLoaded(true);
if (loadError) {
setManifestsLoaded(false);
setRepoApiError(loadError);
} else {
setManifestsLoaded(true);
}
}
void fetchManifests();
@@ -106,7 +134,7 @@ export default function(props: Props) {
return () => {
cancelled = true;
};
}, []);
}, [fetchManifestTime]);
useEffect(() => {
if (!manifestsLoaded) return () => {};
@@ -252,7 +280,7 @@ export default function(props: Props) {
function renderSearchArea() {
return (
<div style={{ marginBottom: 20 }}>
<div style={{ marginBottom: 0 }}>
<SearchPlugins
disabled={!manifestsLoaded}
maxWidth={maxWidth}
@@ -268,11 +296,18 @@ export default function(props: Props) {
);
}
function renderRepoApiError() {
if (!repoApiError) return null;
return <RepoApiErrorMessage maxWidth={maxWidth} type="error">{_('Could not connect to plugin repository')} - <StyledLink href="#" onClick={() => { setFetchManifestTime(Date.now()); }}>{_('Try again')}</StyledLink></RepoApiErrorMessage>;
}
function renderBottomArea() {
if (searchQuery) return null;
return (
<div>
{renderRepoApiError()}
<div style={{ display: 'flex', flexDirection: 'row', maxWidth }}>
<ToolsButton tooltip={_('Plugin tools')} iconName="fas fa-cog" level={ButtonLevel.Secondary} onClick={onToolsClick}/>
<div style={{ display: 'flex', flex: 1 }}>

View File

@@ -101,7 +101,7 @@ export default function(props: Props) {
onChange={onChange}
onSearchButtonClick={onSearchButtonClick}
searchStarted={searchStarted}
placeholder={_('Search for plugins...')}
placeholder={props.disabled ? _('Please wait...') : _('Search for plugins...')}
disabled={props.disabled}
/>
</div>

View File

@@ -34,6 +34,7 @@ import ShareFolderDialog from '../ShareFolderDialog/ShareFolderDialog';
import { ShareInvitation } from '@joplin/lib/services/share/reducer';
import ShareService from '@joplin/lib/services/share/ShareService';
import { reg } from '@joplin/lib/registry';
import removeKeylessItems from '../ResizableLayout/utils/removeKeylessItems';
const { connect } = require('react-redux');
const { PromptDialog } = require('../PromptDialog.min.js');
@@ -234,6 +235,14 @@ class MainScreenComponent extends React.Component<Props, State> {
try {
output = loadLayout(Object.keys(userLayout).length ? userLayout : null, defaultLayout, rootLayoutSize);
// For unclear reasons, layout items sometimes end up witout a key.
// In that case, we can't do anything with them, so remove them
// here. It could be due to the deprecated plugin API, which allowed
// creating panel without a key, although in this case it should
// have been set automatically.
// https://github.com/laurent22/joplin/issues/4926
output = removeKeylessItems(output);
if (!findItemByKey(output, 'sideBar') || !findItemByKey(output, 'noteList') || !findItemByKey(output, 'editor')) {
throw new Error('"sideBar", "noteList" and "editor" must be present in the layout');
}

View File

@@ -18,6 +18,6 @@ export const runtime = (comp: any): CommandRuntime => {
},
});
},
enabledCondition: 'folderIsShareRootAndOwnedByUser || !folderIsShared',
enabledCondition: 'joplinServerConnected && (folderIsShareRootAndOwnedByUser || !folderIsShared)',
};
};

View File

@@ -18,5 +18,6 @@ export const runtime = (comp: any): CommandRuntime => {
},
});
},
enabledCondition: 'joplinServerConnected && someNotesSelected',
};
};

View File

@@ -695,11 +695,18 @@ function useMenu(props: Props) {
},
],
},
folder: {
label: _('Note&book'),
submenu: [
menuItemDic.showShareFolderDialog,
],
},
note: {
label: _('&Note'),
submenu: [
menuItemDic.toggleExternalEditing,
menuItemDic.setTags,
menuItemDic.showShareNoteDialog,
separator(),
menuItemDic.showNoteContentProperties,
],
@@ -818,6 +825,7 @@ function useMenu(props: Props) {
rootMenus.edit,
rootMenus.view,
rootMenus.go,
rootMenus.folder,
rootMenus.note,
rootMenus.tools,
rootMenus.help,

View File

@@ -110,6 +110,7 @@ function NoteListItem(props: NoteListItemProps, ref: any) {
let listItemTitleStyle = Object.assign({}, props.style.listItemTitle);
listItemTitleStyle.paddingLeft = !item.is_todo ? hPadding : 4;
if (item.is_shared) listItemTitleStyle.color = theme.colorWarn;
if (item.is_todo && !!item.todo_completed) listItemTitleStyle = Object.assign(listItemTitleStyle, props.style.listItemTitleCompleted);
const displayTitle = Note.displayTitle(item);

View File

@@ -0,0 +1,30 @@
import produce from 'immer';
import iterateItems from './iterateItems';
import { LayoutItem } from './types';
import validateLayout from './validateLayout';
interface ItemToRemove {
parent: LayoutItem;
index: number;
}
export default function(layout: LayoutItem): LayoutItem {
const itemsToRemove: ItemToRemove[] = [];
const output = produce(layout, (layoutDraft: LayoutItem) => {
iterateItems(layoutDraft, (itemIndex: number, item: LayoutItem, parent: LayoutItem) => {
if (!item.key) itemsToRemove.push({ parent, index: itemIndex });
return true;
});
itemsToRemove.sort((a: ItemToRemove, b: ItemToRemove) => {
return a.index > b.index ? -1 : +1;
});
for (const item of itemsToRemove) {
item.parent.children.splice(item.index, 1);
}
});
return output !== layout ? validateLayout(output) : layout;
}

View File

@@ -10,19 +10,21 @@ import { reg } from '@joplin/lib/registry';
import Dialog from './Dialog';
import DialogTitle from './DialogTitle';
import ShareService from '@joplin/lib/services/share/ShareService';
import { StateShare } from '@joplin/lib/services/share/reducer';
import { NoteEntity } from '@joplin/lib/services/database/types';
import Button from './Button/Button';
import { connect } from 'react-redux';
import { AppState } from '../app';
const { clipboard } = require('electron');
interface ShareNoteDialogProps {
interface Props {
themeId: number;
noteIds: Array<string>;
onClose: Function;
shares: StateShare[];
}
interface SharesMap {
[key: string]: any;
}
function styles_(props: ShareNoteDialogProps) {
function styles_(props: Props) {
return buildStyle('ShareNoteDialog', props.themeId, (theme: any) => {
return {
noteList: {
@@ -60,17 +62,21 @@ function styles_(props: ShareNoteDialogProps) {
});
}
export default function ShareNoteDialog(props: ShareNoteDialogProps) {
export function ShareNoteDialog(props: Props) {
console.info('Render ShareNoteDialog');
const [notes, setNotes] = useState<any[]>([]);
const [notes, setNotes] = useState<NoteEntity[]>([]);
const [sharesState, setSharesState] = useState<string>('unknown');
const [shares, setShares] = useState<SharesMap>({});
// const [shares, setShares] = useState<SharesMap>({});
const noteCount = notes.length;
const theme = themeStyle(props.themeId);
const styles = styles_(props);
useEffect(() => {
void ShareService.instance().refreshShares();
}, []);
useEffect(() => {
async function fetchNotes() {
const result = [];
@@ -87,9 +93,9 @@ export default function ShareNoteDialog(props: ShareNoteDialogProps) {
props.onClose();
};
const copyLinksToClipboard = (shares: SharesMap) => {
const copyLinksToClipboard = (shares: StateShare[]) => {
const links = [];
for (const n in shares) links.push(ShareService.instance().shareUrl(shares[n]));
for (const share of shares) links.push(ShareService.instance().shareUrl(share));
clipboard.writeText(links.join('\n'));
};
@@ -109,15 +115,13 @@ export default function ShareNoteDialog(props: ShareNoteDialogProps) {
setSharesState('creating');
const newShares = Object.assign({}, shares);
const newShares: StateShare[] = [];
for (const note of notes) {
const share = await service.shareNote(note.id);
newShares[note.id] = share;
newShares.push(share);
}
setShares(newShares);
setSharesState('synchronizing');
await reg.waitForSyncFinishedThenSync();
setSharesState('creating');
@@ -125,6 +129,8 @@ export default function ShareNoteDialog(props: ShareNoteDialogProps) {
copyLinksToClipboard(newShares);
setSharesState('created');
await ShareService.instance().refreshShares();
} catch (error) {
if (error.code === 404 && !hasSynced) {
reg.logger().info('ShareNoteDialog: Note does not exist on server - trying to sync it.', error);
@@ -142,34 +148,53 @@ export default function ShareNoteDialog(props: ShareNoteDialogProps) {
}
};
const removeNoteButton_click = (event: any) => {
const newNotes = [];
for (let i = 0; i < notes.length; i++) {
const n = notes[i];
if (n.id === event.noteId) continue;
newNotes.push(n);
}
setNotes(newNotes);
// const removeNoteButton_click = (event: any) => {
// const newNotes = [];
// for (let i = 0; i < notes.length; i++) {
// const n = notes[i];
// if (n.id === event.noteId) continue;
// newNotes.push(n);
// }
// setNotes(newNotes);
// };
const unshareNoteButton_click = async (event: any) => {
await ShareService.instance().unshareNote(event.noteId);
await ShareService.instance().refreshShares();
};
const renderNote = (note: any) => {
const removeButton = notes.length <= 1 ? null : (
<button onClick={() => removeNoteButton_click({ noteId: note.id })} style={styles.noteRemoveButton}>
<i style={styles.noteRemoveButtonIcon} className={'fa fa-times'}></i>
</button>
const renderNote = (note: NoteEntity) => {
const unshareButton = !props.shares.find(s => s.note_id === note.id) ? null : (
<Button tooltip={_('Unshare note')} iconName="fas fa-share-alt" onClick={() => unshareNoteButton_click({ noteId: note.id })}/>
);
// const removeButton = notes.length <= 1 ? null : (
// <Button iconName="fa fa-times" onClick={() => removeNoteButton_click({ noteId: note.id })}/>
// );
// const unshareButton = !shares[note.id] ? null : (
// <button onClick={() => unshareNoteButton_click({ noteId: note.id })} style={styles.noteRemoveButton}>
// <i style={styles.noteRemoveButtonIcon} className={'fas fa-share-alt'}></i>
// </button>
// );
// const removeButton = notes.length <= 1 ? null : (
// <button onClick={() => removeNoteButton_click({ noteId: note.id })} style={styles.noteRemoveButton}>
// <i style={styles.noteRemoveButtonIcon} className={'fa fa-times'}></i>
// </button>
// );
return (
<div key={note.id} style={styles.note}>
<span style={styles.noteTitle}>{note.title}</span>{removeButton}
<span style={styles.noteTitle}>{note.title}</span>{unshareButton}
</div>
);
};
const renderNoteList = (notes: any) => {
const noteComps = [];
for (const noteId of Object.keys(notes)) {
noteComps.push(renderNote(notes[noteId]));
for (const note of notes) {
noteComps.push(renderNote(note));
}
return <div style={styles.noteList}>{noteComps}</div>;
};
@@ -194,7 +219,12 @@ export default function ShareNoteDialog(props: ShareNoteDialogProps) {
<button disabled={['creating', 'synchronizing'].indexOf(sharesState) >= 0} style={styles.copyShareLinkButton} onClick={shareLinkButton_click}>{_n('Copy Shareable Link', 'Copy Shareable Links', noteCount)}</button>
<div style={theme.textStyle}>{statusMessage(sharesState)}</div>
{renderEncryptionWarningMessage()}
<DialogButtonRow themeId={props.themeId} onClick={buttonRow_click} okButtonShow={false} cancelButtonLabel={_('Close')}/>
<DialogButtonRow
themeId={props.themeId}
onClick={buttonRow_click}
okButtonShow={false}
cancelButtonLabel={_('Close')}
/>
</div>
);
}
@@ -203,3 +233,11 @@ export default function ShareNoteDialog(props: ShareNoteDialogProps) {
<Dialog renderContent={renderContent}/>
);
}
const mapStateToProps = (state: AppState) => {
return {
shares: state.shareService.shares.filter(s => !!s.note_id),
};
};
export default connect(mapStateToProps)(ShareNoteDialog as any);

View File

@@ -69,7 +69,6 @@ function listItemTextColor(props: any) {
export const StyledListItemAnchor = styled.a`
font-size: ${(props: any) => Math.round(props.theme.fontSize * 1.0833333)}px;
// font-weight: 500;
text-decoration: none;
color: ${(props: any) => listItemTextColor(props)};
cursor: default;

View File

@@ -87,12 +87,15 @@ function StatusScreen(props: Props) {
itemsHtml.push(renderSectionTitleHtml(section.title, section.title));
let currentListKey = '';
let listItems: any[] = [];
for (const n in section.body) {
if (!section.body.hasOwnProperty(n)) continue;
const item = section.body[n];
let text = '';
let retryLink = null;
let itemType = null;
if (typeof item === 'object') {
if (item.canRetry) {
const onClick = async () => {
@@ -107,18 +110,40 @@ function StatusScreen(props: Props) {
);
}
text = item.text;
itemType = item.type;
} else {
text = item;
}
if (itemType === 'openList') {
currentListKey = item.key;
continue;
}
if (itemType === 'closeList') {
itemsHtml.push(<ul key={currentListKey}>{listItems}</ul>);
currentListKey = '';
listItems = [];
continue;
}
if (!text) text = '\xa0';
itemsHtml.push(
<div style={theme.textStyle} key={`item_${n}`}>
<span>{text}</span>
{retryLink}
</div>
);
if (currentListKey) {
listItems.push(
<li style={theme.textStyle} key={`item_${n}`}>
<span>{text}</span>
{retryLink}
</li>
);
} else {
itemsHtml.push(
<div style={theme.textStyle} key={`item_${n}`}>
<span>{text}</span>
{retryLink}
</div>
);
}
}
if (section.canRetryAll) {

View File

@@ -45,5 +45,7 @@ export default function() {
'editor.swapLineUp',
'editor.swapLineDown',
'toggleSafeMode',
'showShareNoteDialog',
'showShareFolderDialog',
];
}

View File

@@ -0,0 +1,9 @@
import styled from 'styled-components';
const StyledLink = styled.a`
font-size: ${props => props.theme.fontSize}px;
color: ${props => props.theme.urlColor};
font-family: ${props => props.theme.fontFamily};
`;
export default StyledLink;

View File

@@ -7,6 +7,7 @@ const StyledMessage = styled.div`
color: ${props => props.theme.color};
font-family: ${props => props.theme.fontFamily};
padding: ${props => props.type === 'error' ? props.theme.mainPadding : '0'}px;
word-break: break-all;
`;
export default StyledMessage;

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "1.8.5",
"version": "2.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "1.8.5",
"version": "2.0.1",
"description": "Joplin for Desktop",
"main": "main.js",
"private": true,

View File

@@ -35,6 +35,7 @@ if [ "$1" == "1" ]; then
echo 'mkbook "other"' >> "$CMD_FILE"
echo 'use "shared"' >> "$CMD_FILE"
echo 'mknote "note 1"' >> "$CMD_FILE"
echo 'mknote "note 2"' >> "$CMD_FILE"
fi
cd "$ROOT_DIR/packages/app-cli"

View File

@@ -141,8 +141,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2097631
versionName "1.8.5"
versionCode 2097632
versionName "2.0.1"
ndk {
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}

View File

@@ -492,7 +492,7 @@
INFOPLIST_FILE = Joplin/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 10.8.1;
MARKETING_VERSION = 20.0.0;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -519,7 +519,7 @@
INFOPLIST_FILE = Joplin/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 10.8.1;
MARKETING_VERSION = 20.0.0;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -666,7 +666,7 @@
INFOPLIST_FILE = ShareExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 10.8.1;
MARKETING_VERSION = 20.0.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension;
@@ -697,7 +697,7 @@
INFOPLIST_FILE = ShareExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 10.8.1;
MARKETING_VERSION = 20.0.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@@ -1,7 +1,7 @@
{
"manifest_version": 1,
"id": "<%= pluginId %>",
"app_min_version": "1.8",
"app_min_version": "2.0",
"version": "1.0.0",
"name": "<%= pluginName %>",
"description": "<%= pluginDescription %>",

View File

@@ -1,6 +1,6 @@
{
"name": "generator-joplin",
"version": "1.8.1",
"version": "2.0.0",
"description": "Scaffolds out a new Joplin plugin",
"homepage": "https://github.com/laurent22/joplin/tree/dev/packages/generator-joplin",
"author": {
@@ -34,4 +34,4 @@
"repository": "https://github.com/laurent22/generator-joplin",
"license": "MIT",
"private": true
}
}

View File

@@ -28,6 +28,22 @@ interface RemoteItem {
type_?: number;
}
function isCannotSyncError(error: any): boolean {
if (!error) return false;
if (['rejectedByTarget', 'fileNotFound'].indexOf(error.code) >= 0) return true;
// If the request times out we give up too because sometimes it's due to the
// file being large or some other connection issues, and we don't want that
// file to block the sync process. The user can choose to retry later on.
//
// message: "network timeout at: .....
// name: "FetchError"
// type: "request-timeout"
if (error.type === 'request-timeout' || error.message.includes('network timeout')) return true;
return false;
}
export default class Synchronizer {
private db_: any;
@@ -514,7 +530,7 @@ export default class Synchronizer {
await this.apiCall('put', remoteContentPath, null, { path: localResourceContentPath, source: 'file', shareId: local.share_id });
} catch (error) {
if (error && ['rejectedByTarget', 'fileNotFound'].indexOf(error.code) >= 0) {
if (isCannotSyncError(error)) {
await handleCannotSyncItem(ItemClass, syncTargetId, local, error.message);
action = null;
} else {

View File

@@ -31,6 +31,7 @@ interface Section {
interface ParserStateTag {
name: string;
visible: boolean;
isCodeBlock: boolean;
}
interface ParserStateList {
@@ -443,6 +444,20 @@ function isInvisibleBlock(context: any, attributes: any) {
return display && display.indexOf('none') === 0;
}
function trimBlockOpenAndClose(lines: string[]): string[] {
const output = lines.slice();
while (output.length && [BLOCK_OPEN, BLOCK_CLOSE, ''].includes(output[0])) {
output.splice(0, 1);
}
while (output.length && [BLOCK_OPEN, BLOCK_CLOSE, ''].includes(output[output.length - 1])) {
output.pop();
}
return output;
}
function isSpanWithStyle(attributes: any) {
if (attributes != undefined) {
if ('style' in attributes) {
@@ -484,6 +499,16 @@ function displaySaxWarning(context: any, message: string) {
console.warn(line.join(': '));
}
function isCodeBlock(context: any, nodeName: string, attributes: any) {
if (nodeName === 'code') return true;
if (attributes && attributes.style) {
const enCodeBlock = cssValue(context, attributes.style, '-en-codeblock');
if (enCodeBlock && enCodeBlock.toLowerCase() === 'true') return true;
}
return false;
}
// function removeSectionParent(section:Section | string) {
// if (typeof section === 'string') return section;
@@ -575,11 +600,13 @@ function enexXmlToMdArray(stream: any, resources: ResourceEntity[]): Promise<Ene
const nodeAttributes = attributeToLowerCase(node);
const n = node.name.toLowerCase();
const isVisible = !isInvisibleBlock(this, nodeAttributes);
state.tags.push({
const tagInfo: ParserStateTag = {
name: n,
visible: isVisible,
});
isCodeBlock: isCodeBlock(this, n, nodeAttributes),
};
state.tags.push(tagInfo);
const currentList = state.lists && state.lists.length ? state.lists[state.lists.length - 1] : null;
@@ -673,6 +700,25 @@ function enexXmlToMdArray(stream: any, resources: ResourceEntity[]): Promise<Ene
lines: [],
parent: section,
};
section.lines.push(newSection);
section = newSection;
} else if (tagInfo.isCodeBlock) {
// state.inPre = false;
// const previousIsPre = state.tags.length ? state.tags[state.tags.length - 1].name === 'pre' : false;
// if (previousIsPre) {
// section.lines.pop();
// }
state.inCode.push(true);
state.currentCode = '';
const newSection: Section = {
type: SectionType.Code,
lines: [],
parent: section,
};
section.lines.push(newSection);
section = newSection;
} else if (isBlockTag(n)) {
@@ -750,18 +796,6 @@ function enexXmlToMdArray(stream: any, resources: ResourceEntity[]): Promise<Ene
} else if (n == 'blockquote') {
section.lines.push(BLOCK_OPEN);
state.inQuote = true;
} else if (n === 'code') {
state.inCode.push(true);
state.currentCode = '';
const newSection: Section = {
type: SectionType.Code,
lines: [],
parent: section,
};
section.lines.push(newSection);
section = newSection;
} else if (n === 'pre') {
section.lines.push(BLOCK_OPEN);
state.inPre = true;
@@ -871,6 +905,28 @@ function enexXmlToMdArray(stream: any, resources: ResourceEntity[]): Promise<Ene
// End of note
} else if (!poppedTag.visible) {
if (section && section.parent) section = section.parent;
} else if (poppedTag.isCodeBlock) {
state.inCode.pop();
if (!state.inCode.length) {
// When a codeblock is wrapped in <pre><code>, it will have
// extra empty lines added by the "pre" logic, but since we
// are in a codeblock we should actually trim those.
const codeLines = trimBlockOpenAndClose(processMdArrayNewLines(section.lines).split('\n'));
section.lines = [];
if (codeLines.length > 1) {
section.lines.push('\n\n```\n');
for (let i = 0; i < codeLines.length; i++) {
if (i > 0) section.lines.push('\n');
section.lines.push(codeLines[i]);
}
section.lines.push('\n```\n\n');
} else {
section.lines.push(`\`${markdownUtils.escapeInlineCode(codeLines.join(''))}\``);
}
if (section && section.parent) section = section.parent;
}
} else if (isNewLineOnlyEndTag(n)) {
section.lines.push(BLOCK_CLOSE);
} else if (n == 'td' || n == 'th') {
@@ -897,23 +953,6 @@ function enexXmlToMdArray(stream: any, resources: ResourceEntity[]): Promise<Ene
} else if (n == 'blockquote') {
section.lines.push(BLOCK_OPEN);
state.inQuote = false;
} else if (n === 'code') {
state.inCode.pop();
if (!state.inCode.length) {
const codeLines = processMdArrayNewLines(section.lines).split('\n');
section.lines = [];
if (codeLines.length > 1) {
for (let i = 0; i < codeLines.length; i++) {
if (i > 0) section.lines.push('\n');
section.lines.push(`\t${codeLines[i]}`);
}
} else {
section.lines.push(`\`${codeLines.join('')}\``);
}
if (section && section.parent) section = section.parent;
}
} else if (n === 'pre') {
state.inPre = false;
section.lines.push(BLOCK_CLOSE);

View File

@@ -578,7 +578,7 @@ function localesFromLanguageCode(languageCode: string, locales: string[]): strin
});
}
function _(s: string, ...args: any[]) {
function _(s: string, ...args: any[]): string {
const strings = localeStrings(currentLocale_);
let result = strings[s];
if (result === '' || result === undefined) result = s;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -41,45 +41,45 @@ locales['uk_UA'] = require('./uk_UA.json');
locales['vi'] = require('./vi.json');
locales['zh_CN'] = require('./zh_CN.json');
locales['zh_TW'] = require('./zh_TW.json');
stats['ar'] = {"percentDone":98};
stats['eu'] = {"percentDone":31};
stats['bs_BA'] = {"percentDone":76};
stats['bg_BG'] = {"percentDone":59};
stats['ca'] = {"percentDone":84};
stats['hr_HR'] = {"percentDone":99};
stats['cs_CZ'] = {"percentDone":88};
stats['da_DK'] = {"percentDone":98};
stats['de_DE'] = {"percentDone":98};
stats['et_EE'] = {"percentDone":58};
stats['ar'] = {"percentDone":96};
stats['eu'] = {"percentDone":30};
stats['bs_BA'] = {"percentDone":75};
stats['bg_BG'] = {"percentDone":58};
stats['ca'] = {"percentDone":83};
stats['hr_HR'] = {"percentDone":96};
stats['cs_CZ'] = {"percentDone":86};
stats['da_DK'] = {"percentDone":96};
stats['de_DE'] = {"percentDone":95};
stats['et_EE'] = {"percentDone":57};
stats['en_GB'] = {"percentDone":100};
stats['en_US'] = {"percentDone":100};
stats['es_ES'] = {"percentDone":97};
stats['es_ES'] = {"percentDone":94};
stats['eo'] = {"percentDone":33};
stats['fi_FI'] = {"percentDone":97};
stats['fr_FR'] = {"percentDone":94};
stats['gl_ES'] = {"percentDone":39};
stats['id_ID'] = {"percentDone":95};
stats['it_IT'] = {"percentDone":97};
stats['hu_HU'] = {"percentDone":90};
stats['nl_BE'] = {"percentDone":94};
stats['nl_NL'] = {"percentDone":97};
stats['nb_NO'] = {"percentDone":78};
stats['fa'] = {"percentDone":73};
stats['pl_PL'] = {"percentDone":97};
stats['pt_BR'] = {"percentDone":97};
stats['pt_PT'] = {"percentDone":97};
stats['ro'] = {"percentDone":68};
stats['sl_SI'] = {"percentDone":98};
stats['sv'] = {"percentDone":63};
stats['th_TH'] = {"percentDone":46};
stats['vi'] = {"percentDone":75};
stats['tr_TR'] = {"percentDone":97};
stats['uk_UA'] = {"percentDone":97};
stats['el_GR'] = {"percentDone":84};
stats['ru_RU'] = {"percentDone":97};
stats['sr_RS'] = {"percentDone":73};
stats['zh_CN'] = {"percentDone":97};
stats['zh_TW'] = {"percentDone":95};
stats['ja_JP'] = {"percentDone":98};
stats['ko'] = {"percentDone":99};
stats['fi_FI'] = {"percentDone":94};
stats['fr_FR'] = {"percentDone":99};
stats['gl_ES'] = {"percentDone":38};
stats['id_ID'] = {"percentDone":93};
stats['it_IT'] = {"percentDone":94};
stats['hu_HU'] = {"percentDone":88};
stats['nl_BE'] = {"percentDone":92};
stats['nl_NL'] = {"percentDone":95};
stats['nb_NO'] = {"percentDone":76};
stats['fa'] = {"percentDone":71};
stats['pl_PL'] = {"percentDone":94};
stats['pt_BR'] = {"percentDone":94};
stats['pt_PT'] = {"percentDone":94};
stats['ro'] = {"percentDone":66};
stats['sl_SI'] = {"percentDone":96};
stats['sv'] = {"percentDone":61};
stats['th_TH'] = {"percentDone":45};
stats['vi'] = {"percentDone":73};
stats['tr_TR'] = {"percentDone":94};
stats['uk_UA'] = {"percentDone":94};
stats['el_GR'] = {"percentDone":97};
stats['ru_RU'] = {"percentDone":94};
stats['sr_RS'] = {"percentDone":71};
stats['zh_CN'] = {"percentDone":94};
stats['zh_TW'] = {"percentDone":92};
stats['ja_JP'] = {"percentDone":97};
stats['ko'] = {"percentDone":96};
module.exports = { locales: locales, stats: stats };

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -41,6 +41,11 @@ const markdownUtils = {
return text;
},
escapeInlineCode(text: string): string {
// https://github.com/github/markup/issues/363#issuecomment-55499909
return text.replace(/`/g, '``');
},
unescapeLinkUrl(url: string) {
url = url.replace(/%28/g, '(');
url = url.replace(/%29/g, ')');

View File

@@ -756,6 +756,10 @@ export default class BaseItem extends BaseModel {
return this.db().transactionExecBatch(queries);
}
public static async saveSyncEnabled(itemType: ModelType, itemId: string) {
await this.db().exec('DELETE FROM sync_items WHERE item_type = ? AND item_id = ?', [itemType, itemId]);
}
// When an item is deleted, its associated sync_items data is not immediately deleted for
// performance reason. So this function is used to look for these remaining sync_items and
// delete them.

View File

@@ -347,7 +347,8 @@ export default class Folder extends BaseItem {
public static async updateResourceShareIds() {
// Find all resources where share_id is different from parent note
// share_id. Then update share_id on all these resources. Essentially it
// makes it match the resource share_id to the note share_id.
// makes it match the resource share_id to the note share_id. At the
// same time we also process the is_shared property.
const rows = await this.db().selectAll(`
SELECT r.id, n.share_id, n.is_shared
FROM note_resources nr

View File

@@ -307,7 +307,7 @@ export default class Note extends BaseItem {
includeTimestamps: true,
}, options);
const output = ['id', 'title', 'is_todo', 'todo_completed', 'todo_due', 'parent_id', 'encryption_applied', 'order', 'markup_language', 'is_conflict'];
const output = ['id', 'title', 'is_todo', 'todo_completed', 'todo_due', 'parent_id', 'encryption_applied', 'order', 'markup_language', 'is_conflict', 'is_shared'];
if (options.includeTimestamps) {
output.push('updated_time');

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/lib",
"version": "1.8.2",
"version": "2.0.0",
"description": "Joplin Core library",
"author": "Laurent Cozic",
"homepage": "",

View File

@@ -12,6 +12,7 @@ const imageMimeTypes = [
'image/png',
'image/prs.btif',
'image/prs.pti',
'image/svg+xml',
'image/t38',
'image/tiff',
'image/tiff-fx',

View File

@@ -10,6 +10,36 @@ import Resource from '../models/Resource';
import { _ } from '../locale';
const { toTitleCase } = require('../string-utils.js');
enum CanRetryType {
E2EE = 'e2ee',
ResourceDownload = 'resourceDownload',
ItemSync = 'itemSync',
}
enum ReportItemType {
OpenList = 'openList',
CloseList = 'closeList',
}
type RerportItemOrString = ReportItem | string;
interface ReportSection {
title: string;
body: RerportItemOrString[];
name?: string;
canRetryAll?: boolean;
retryAllHandler?: ()=> void;
}
interface ReportItem {
type?: ReportItemType;
key?: string;
text?: string;
canRetry?: boolean;
canRetryType?: CanRetryType;
retryHandler?: ()=> void;
}
export default class ReportService {
csvEscapeCell(cell: string) {
cell = this.csvValueToString(cell);
@@ -110,10 +140,10 @@ export default class ReportService {
return output;
}
async status(syncTarget: number) {
async status(syncTarget: number): Promise<ReportSection[]> {
const r = await this.syncStatus(syncTarget);
const sections = [];
let section: any = null;
const sections: ReportSection[] = [];
let section: ReportSection = null;
const disabledItems = await BaseItem.syncDisabledItems(syncTarget);
@@ -122,17 +152,29 @@ export default class ReportService {
section.body.push(_('These items will remain on the device but will not be uploaded to the sync target. In order to find these items, either search for the title or the ID (which is displayed in brackets above).'));
section.body.push('');
section.body.push({ type: ReportItemType.OpenList, key: 'disabledSyncItems' });
for (let i = 0; i < disabledItems.length; i++) {
const row = disabledItems[i];
let msg: string = '';
if (row.location === BaseItem.SYNC_ITEM_LOCATION_LOCAL) {
section.body.push(_('%s (%s) could not be uploaded: %s', row.item.title, row.item.id, row.syncInfo.sync_disabled_reason));
msg = _('%s (%s) could not be uploaded: %s', row.item.title, row.item.id, row.syncInfo.sync_disabled_reason);
} else {
section.body.push(_('Item "%s" could not be downloaded: %s', row.syncInfo.item_id, row.syncInfo.sync_disabled_reason));
msg = _('Item "%s" could not be downloaded: %s', row.syncInfo.item_id, row.syncInfo.sync_disabled_reason);
}
section.body.push({
text: msg,
canRetry: true,
canRetryType: CanRetryType.ItemSync,
retryHandler: async () => {
await BaseItem.saveSyncEnabled(row.item.type_, row.item.id);
},
});
}
section.body.push({ type: ReportItemType.CloseList });
sections.push(section);
}
@@ -150,7 +192,7 @@ export default class ReportService {
section.body.push({
text: _('%s: %s', toTitleCase(BaseModel.modelTypeToName(row.type_)), row.id),
canRetry: true,
canRetryType: 'e2ee',
canRetryType: CanRetryType.E2EE,
retryHandler: async () => {
await DecryptionWorker.instance().clearDisabledItem(row.type_, row.id);
void DecryptionWorker.instance().scheduleStart();
@@ -158,11 +200,12 @@ export default class ReportService {
});
}
const retryHandlers: any[] = [];
const retryHandlers: Function[] = [];
for (let i = 0; i < section.body.length; i++) {
if (section.body[i].canRetry) {
retryHandlers.push(section.body[i].retryHandler);
const item: RerportItemOrString = section.body[i];
if (typeof item !== 'string' && item.canRetry) {
retryHandlers.push(item.retryHandler);
}
}
@@ -210,7 +253,7 @@ export default class ReportService {
section.body.push({
text: _('%s (%s): %s', row.resource_title, row.resource_id, row.fetch_error),
canRetry: true,
canRetryType: 'resourceDownload',
canRetryType: CanRetryType.ResourceDownload,
retryHandler: async () => {
await Resource.resetErrorStatus(row.resource_id);
void ResourceFetcher.instance().autoAddResources();

View File

@@ -0,0 +1,39 @@
import WhenClause from './WhenClause';
describe('WhenClause', function() {
test('should work with simple condition', async function() {
const wc = new WhenClause('test1 && test2');
expect(wc.evaluate({
test1: true,
test2: true,
})).toBe(true);
expect(wc.evaluate({
test1: true,
test2: false,
})).toBe(false);
});
test('should work with parenthesis', async function() {
const wc = new WhenClause('(test1 && test2) || test3 && (test4 && !test5)');
expect(wc.evaluate({
test1: true,
test2: true,
test3: true,
test4: true,
test5: true,
})).toBe(true);
expect(wc.evaluate({
test1: false,
test2: true,
test3: false,
test4: false,
test5: true,
})).toBe(false);
});
});

View File

@@ -1,17 +1,71 @@
import { ContextKeyExpr, ContextKeyExpression } from './contextkey/contextkey';
import { ContextKeyExpr, ContextKeyExpression, IContext } from './contextkey/contextkey';
// We would like to support expressions with brackets but VSCode When Clauses
// don't support this. To support this, we split the expressions with brackets
// into sub-expressions, which can then be parsed and executed separately by the
// When Clause library.
interface AdvancedExpression {
// (test1 && test2) || test3
original: string;
// __sub_1 || test3
compiledText: string;
// { __sub_1: "test1 && test2" }
subExpressions: any;
}
function parseAdvancedExpression(advancedExpression: string): AdvancedExpression {
let subExpressionIndex = -1;
let subExpressions: string = '';
let currentSubExpressionKey = '';
const subContext: any = {};
let inBrackets = false;
for (let i = 0; i < advancedExpression.length; i++) {
const c = advancedExpression[i];
if (c === '(') {
if (inBrackets) throw new Error('Nested brackets not supported');
inBrackets = true;
subExpressionIndex++;
currentSubExpressionKey = `__sub_${subExpressionIndex}`;
subContext[currentSubExpressionKey] = '';
continue;
}
if (c === ')') {
if (!inBrackets) throw new Error('Closing bracket without an opening one');
inBrackets = false;
subExpressions += currentSubExpressionKey;
currentSubExpressionKey = '';
continue;
}
if (inBrackets) {
subContext[currentSubExpressionKey] += c;
} else {
subExpressions += c;
}
}
return {
compiledText: subExpressions,
subExpressions: subContext,
original: advancedExpression,
};
}
export default class WhenClause {
private expression_: string;
private expression_: AdvancedExpression;
private validate_: boolean;
private rules_: ContextKeyExpression = null;
private ruleCache_: Record<string, ContextKeyExpression> = {};
constructor(expression: string, validate: boolean) {
this.expression_ = expression;
public constructor(expression: string, validate: boolean = true) {
this.expression_ = parseAdvancedExpression(expression);
this.validate_ = validate;
}
private createContext(ctx: any) {
private createContext(ctx: any): IContext {
return {
getValue: (key: string) => {
return ctx[key];
@@ -19,21 +73,28 @@ export default class WhenClause {
};
}
private get rules(): ContextKeyExpression {
if (!this.rules_) {
this.rules_ = ContextKeyExpr.deserialize(this.expression_);
}
return this.rules_;
private rules(exp: string): ContextKeyExpression {
if (this.ruleCache_[exp]) return this.ruleCache_[exp];
this.ruleCache_[exp] = ContextKeyExpr.deserialize(exp);
return this.ruleCache_[exp];
}
public evaluate(context: any): boolean {
if (this.validate_) this.validate(context);
return this.rules.evaluate(this.createContext(context));
const subContext: any = {};
for (const k in this.expression_.subExpressions) {
const subExp = this.expression_.subExpressions[k];
subContext[k] = this.rules(subExp).evaluate(this.createContext(context));
}
const fullContext = { ...context, ...subContext };
return this.rules(this.expression_.compiledText).evaluate(this.createContext(fullContext));
}
public validate(context: any) {
const keys = this.rules.keys();
const keys = this.rules(this.expression_.original.replace(/[()]/g, ' ')).keys();
for (const key of keys) {
if (!(key in context)) throw new Error(`No such key: ${key}`);
}

View File

@@ -27,6 +27,7 @@ export interface WhenClauseContext {
noteIsHtml: boolean;
folderIsShareRootAndOwnedByUser: boolean;
folderIsShared: boolean;
joplinServerConnected: boolean;
}
export default function stateToWhenClauseContext(state: State, options: WhenClauseContextOptions = null): WhenClauseContext {
@@ -42,7 +43,7 @@ export default function stateToWhenClauseContext(state: State, options: WhenClau
// const commandNoteId = options.commandNoteId || selectedNoteId;
// const commandNote:NoteEntity = commandNoteId ? BaseModel.byId(state.notes, commandNoteId) : null;
const commandFolderId = options.commandFolderId;
const commandFolderId = options.commandFolderId || state.selectedFolderId;
const commandFolder: FolderEntity = commandFolderId ? BaseModel.byId(state.folders, commandFolderId) : null;
return {
@@ -75,5 +76,7 @@ export default function stateToWhenClauseContext(state: State, options: WhenClau
// Current context folder
folderIsShareRootAndOwnedByUser: commandFolder ? isRootSharedFolder(commandFolder) && isSharedFolderOwner(state, commandFolder.id) : false,
folderIsShared: commandFolder ? !!commandFolder.share_id : false,
joplinServerConnected: state.settings['sync.target'] === 9,
};
}

View File

@@ -27,11 +27,12 @@ export interface Command {
execute(...args: any[]): Promise<any | void>;
/**
* Defines whether the command should be enabled or disabled, which in turns affects
* the enabled state of any associated button or menu item.
* Defines whether the command should be enabled or disabled, which in turns
* affects the enabled state of any associated button or menu item.
*
* The condition should be expressed as a "when-clause" (as in Visual Studio Code). It's a simple boolean expression that evaluates to
* `true` or `false`. It supports the following operators:
* The condition should be expressed as a "when-clause" (as in Visual Studio
* Code). It's a simple boolean expression that evaluates to `true` or
* `false`. It supports the following operators:
*
* Operator | Symbol | Example
* -- | -- | --
@@ -40,7 +41,17 @@ export interface Command {
* Or | \|\| | "noteIsTodo \|\| noteTodoCompleted"
* And | && | "oneNoteSelected && !inConflictFolder"
*
* Currently the supported context variables aren't documented, but you can [find the list here](https://github.com/laurent22/joplin/blob/dev/packages/lib/services/commands/stateToWhenClauseContext.ts).
* Joplin, unlike VSCode, also supports parenthesis, which allows creating
* more complex expressions such as `cond1 || (cond2 && cond3)`. Only one
* level of parenthesis is possible (nested ones aren't supported).
*
* Currently the supported context variables aren't documented, but you can
* find the list below:
*
* - [Global When
* Clauses](https://github.com/laurent22/joplin/blob/dev/packages/lib/services/commands/stateToWhenClauseContext.ts).
* - [Desktop app When
* Clauses](https://github.com/laurent22/joplin/blob/dev/packages/app-desktop/services/commands/stateToWhenClauseContext.ts).
*
* Note: Commands are enabled by default unless you use this property.
*/

View File

@@ -104,7 +104,7 @@ export default class ShareService {
await Folder.updateAllShareIds();
}
public async shareNote(noteId: string) {
public async shareNote(noteId: string): Promise<StateShare> {
const note = await Note.load(noteId);
if (!note) throw new Error(`No such note: ${noteId}`);
@@ -115,6 +115,24 @@ export default class ShareService {
return share;
}
public async unshareNote(noteId: string) {
const note = await Note.load(noteId);
if (!note) throw new Error(`No such note: ${noteId}`);
const shares = await this.refreshShares();
const noteShares = shares.filter(s => s.note_id === noteId);
const promises: Promise<void>[] = [];
for (const share of noteShares) {
promises.push(this.deleteShare(share.id));
}
await Promise.all(promises);
await Note.save({ id: note.id, is_shared: 0 });
}
public shareUrl(share: StateShare): string {
return `${this.api().baseUrl()}/shares/${share.id}`;
}
@@ -170,13 +188,15 @@ export default class ShareService {
});
}
public async refreshShares() {
public async refreshShares(): Promise<StateShare[]> {
const result = await this.loadShares();
this.store.dispatch({
type: 'SHARE_SET',
shares: result.items,
});
return result.items;
}
public async refreshShareUsers(shareId: string) {

View File

@@ -11,7 +11,7 @@ const theme: Theme = {
oddBackgroundColor: '#eeeeee',
color: '#32373F', // For regular text
colorError: 'red',
colorWarn: '#9A5B00',
colorWarn: 'rgb(228 86 0)',
colorFaded: '#7C8B9E', // For less important text
colorBright: '#000000', // For important text
dividerColor: '#dddddd',

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/plugin-repo-cli",
"version": "1.8.2",
"version": "2.0.0",
"description": "",
"main": "index.js",
"bin": {

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/renderer",
"version": "1.8.2",
"version": "2.0.0",
"description": "The Joplin note renderer, used the mobile and desktop application",
"repository": "https://github.com/laurent22/joplin/tree/dev/packages/renderer",
"main": "index.js",

View File

@@ -91,3 +91,13 @@ From `packages/server`, run `npm run start-dev`
# Changelog
[View the changelog](https://github.com/laurent22/joplin/blob/dev/readme/changelog_server.md)
# License
Copyright (c) 2017-2021 Laurent Cozic
Personal Use License
Joplin Server is available for personal use only. For example you may host the software on your own server for non-commercial activity.
To obtain a license for commercial purposes, please contact us.

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/server",
"version": "1.8.0",
"version": "2.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/server",
"version": "1.8.0",
"version": "2.0.1",
"private": true,
"scripts": {
"start-dev": "nodemon --config nodemon.json dist/app.js --env dev",

View File

@@ -237,9 +237,8 @@ export function changeTypeToString(t: ChangeType): string {
}
export enum ShareType {
Link = 1, // When a note is shared via a public link
App = 2, // When a note is shared with another user on the same server instance
JoplinRootFolder = 3,
Note = 1, // When a note is shared via a public link
Folder = 3, // When a complete folder is shared with another Joplin Server user
}
export enum ShareUserStatus {

View File

@@ -52,6 +52,7 @@ export default async function(ctx: AppContext) {
content: {
error,
stack: ctx.env === Env.Dev ? error.stack : '',
owner: ctx.owner,
},
};
ctx.response.body = await mustache().renderView(view);

View File

@@ -30,7 +30,7 @@ describe('ChangeModel', function() {
const item1 = await createFolder(session.id, { title: 'folder' });
{
const changes = (await changeModel.allForUser(user.id)).items;
const changes = (await changeModel.delta(user.id)).items;
expect(changes.length).toBe(1);
expect(changes[0].item_id).toBe(item1.id);
expect(changes[0].type).toBe(ChangeType.Create);
@@ -60,7 +60,7 @@ describe('ChangeModel', function() {
// We don't get CREATE 1 because item 1 has been deleted. And we
// also don't get any UPDATE event since they've been compressed
// down to the CREATE events.
const changes = (await changeModel.allForUser(user.id)).items;
const changes = (await changeModel.delta(user.id)).items;
expect(changes.length).toBe(2);
expect(changes[0].item_id).toBe(item2.id);
expect(changes[0].type).toBe(ChangeType.Create);
@@ -89,7 +89,7 @@ describe('ChangeModel', function() {
//
// Then CREATE 1 is removed since item 1 has been deleted and UPDATE
// 2a is compressed down to CREATE 2.
const page1 = (await changeModel.allForUser(user.id, pagination));
const page1 = (await changeModel.delta(user.id, pagination));
let changes = page1.items;
expect(changes.length).toBe(1);
expect(page1.has_more).toBe(true);
@@ -98,7 +98,7 @@ describe('ChangeModel', function() {
// In the second page, we get all the expected events since nothing
// has been compressed.
const page2 = (await changeModel.allForUser(user.id, { ...pagination, cursor: page1.cursor }));
const page2 = (await changeModel.delta(user.id, { ...pagination, cursor: page1.cursor }));
changes = page2.items;
expect(changes.length).toBe(3);
// Although there are no more changes, it's not possible to know
@@ -112,7 +112,7 @@ describe('ChangeModel', function() {
expect(changes[2].type).toBe(ChangeType.Create);
// Check that we indeed reached the end of the feed.
const page3 = (await changeModel.allForUser(user.id, { ...pagination, cursor: page2.cursor }));
const page3 = (await changeModel.delta(user.id, { ...pagination, cursor: page2.cursor }));
expect(page3.items.length).toBe(0);
expect(page3.has_more).toBe(false);
}
@@ -127,7 +127,7 @@ describe('ChangeModel', function() {
await msleep(1); const item1 = await makeTestItem(user.id, 1); // CREATE 1
await msleep(1); await itemModel.saveForUser(user.id, { id: item1.id, name: `test_mod${i++}` }); // UPDATE 1
await expectThrow(async () => changeModel.allForUser(user.id, { limit: 1, cursor: 'invalid' }), 'resyncRequired');
await expectThrow(async () => changeModel.delta(user.id, { limit: 1, cursor: 'invalid' }), 'resyncRequired');
});
});

View File

@@ -1,8 +1,9 @@
import { Knex } from 'knex';
import { Change, ChangeType, Item, Uuid } from '../db';
import { md5 } from '../utils/crypto';
import { ErrorResyncRequired } from '../utils/errors';
import BaseModel, { SaveOptions } from './BaseModel';
import { PaginatedResults } from './utils/pagination';
import { PaginatedResults, Pagination, PaginationOrderDir } from './utils/pagination';
export interface ChangeWithItem {
item: Item;
@@ -26,17 +27,13 @@ export interface ChangePreviousItem {
jop_share_id: string;
}
export function defaultChangePagination(): ChangePagination {
export function defaultDeltaPagination(): ChangePagination {
return {
limit: 100,
cursor: '',
};
}
interface AllForUserOptions {
compressChanges?: boolean;
}
export default class ChangeModel extends BaseModel<Change> {
public get tableName(): string {
@@ -71,24 +68,7 @@ export default class ChangeModel extends BaseModel<Change> {
return results;
}
public async allForUser(userId: Uuid, pagination: ChangePagination = null, options: AllForUserOptions = null): Promise<PaginatedChanges> {
options = {
compressChanges: true,
...options,
};
pagination = {
...defaultChangePagination(),
...pagination,
};
let changeAtCursor: Change = null;
if (pagination.cursor) {
changeAtCursor = await this.load(pagination.cursor) as Change;
if (!changeAtCursor) throw new ErrorResyncRequired();
}
private changesForUserQuery(userId: Uuid): Knex.QueryBuilder {
// When need to get:
//
// - All the CREATE and DELETE changes associated with the user
@@ -98,7 +78,7 @@ export default class ChangeModel extends BaseModel<Change> {
// UPDATE changes do not have the user_id set because they are specific
// to the item, not to a particular user.
const query = this
return this
.db('changes')
.select([
'id',
@@ -116,8 +96,53 @@ export default class ChangeModel extends BaseModel<Change> {
// https://github.com/knex/knex/issues/1851
.orWhereRaw('type = ? AND item_id IN (SELECT item_id FROM user_items WHERE user_id = ?)', [ChangeType.Update, userId]);
});
}
// If a cursor was provided, apply it to both queries.
public async allByUser(userId: Uuid, pagination: Pagination = null): Promise<PaginatedChanges> {
pagination = {
page: 1,
limit: 100,
order: [{ by: 'counter', dir: PaginationOrderDir.ASC }],
...pagination,
};
const query = this.changesForUserQuery(userId);
const countQuery = query.clone();
const itemCount = (await countQuery.count('id', { as: 'total' }))[0].total;
void query
.orderBy(pagination.order[0].by, pagination.order[0].dir)
.offset((pagination.page - 1) * pagination.limit)
.limit(pagination.limit) as any[];
const changes = await query;
return {
items: changes,
// If we have changes, we return the ID of the latest changes from which delta sync can resume.
// If there's no change, we return the previous cursor.
cursor: changes.length ? changes[changes.length - 1].id : pagination.cursor,
has_more: changes.length >= pagination.limit,
page_count: itemCount !== null ? Math.ceil(itemCount / pagination.limit) : undefined,
};
}
public async delta(userId: Uuid, pagination: ChangePagination = null): Promise<PaginatedChanges> {
pagination = {
...defaultDeltaPagination(),
...pagination,
};
let changeAtCursor: Change = null;
if (pagination.cursor) {
changeAtCursor = await this.load(pagination.cursor) as Change;
if (!changeAtCursor) throw new ErrorResyncRequired();
}
const query = this.changesForUserQuery(userId);
// If a cursor was provided, apply it to the query.
if (changeAtCursor) {
void query.where('counter', '>', changeAtCursor.counter);
}
@@ -128,7 +153,7 @@ export default class ChangeModel extends BaseModel<Change> {
const changes = await query;
const finalChanges = options.compressChanges ? await this.removeDeletedItems(this.compressChanges(changes)) : changes;
const finalChanges = await this.removeDeletedItems(this.compressChanges(changes));
return {
items: finalChanges,

View File

@@ -378,6 +378,7 @@ export default class ItemModel extends BaseModel<Item> {
public async childrenCount(userId: Uuid, pathQuery: string = ''): Promise<number> {
const query = this.childrenQuery(userId, pathQuery);
query.groupBy('items.id');
return query.count();
}
@@ -415,7 +416,7 @@ export default class ItemModel extends BaseModel<Item> {
const path = await this.joplinItemPath(jopId);
if (!path.length) throw new ApiError(`Cannot retrieve path for item: ${jopId}`, null, 'noPathForItem');
const rootFolderItem = path[path.length - 1];
const share = await this.models().share().itemShare(ShareType.JoplinRootFolder, rootFolderItem.id);
const share = await this.models().share().itemShare(ShareType.Folder, rootFolderItem.id);
if (!share) return null;
return {

Some files were not shown because too many files have changed in this diff Show More