1
0
mirror of https://github.com/laurent22/joplin.git synced 2026-01-05 00:12:33 +02:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Laurent Cozic
f37bd462a2 custom layout 2020-12-02 17:26:35 +00:00
198 changed files with 85475 additions and 100624 deletions

View File

@@ -10,8 +10,6 @@ highlight.pack.js
Modules/TinyMCE/IconPack/postinstall.js
Modules/TinyMCE/JoplinLists/
Modules/TinyMCE/langs/
packages/turndown/
packages/turndown-plugin-gfm/
node_modules/
packages/lib/lib/lib.js
packages/lib/locales/index.js
@@ -70,9 +68,6 @@ packages/app-cli/app/services/plugins/PluginRunner.js.map
packages/app-cli/tests/EnexToMd.d.ts
packages/app-cli/tests/EnexToMd.js
packages/app-cli/tests/EnexToMd.js.map
packages/app-cli/tests/HtmlToMd.d.ts
packages/app-cli/tests/HtmlToMd.js
packages/app-cli/tests/HtmlToMd.js.map
packages/app-cli/tests/InMemoryCache.d.ts
packages/app-cli/tests/InMemoryCache.js
packages/app-cli/tests/InMemoryCache.js.map
@@ -109,9 +104,6 @@ packages/app-cli/tests/models_Note.js.map
packages/app-cli/tests/models_Setting.d.ts
packages/app-cli/tests/models_Setting.js
packages/app-cli/tests/models_Setting.js.map
packages/app-cli/tests/services/plugins/api/JoplinViewMenuItem.d.ts
packages/app-cli/tests/services/plugins/api/JoplinViewMenuItem.js
packages/app-cli/tests/services/plugins/api/JoplinViewMenuItem.js.map
packages/app-cli/tests/services/plugins/api/JoplinWorkspace.d.ts
packages/app-cli/tests/services/plugins/api/JoplinWorkspace.js
packages/app-cli/tests/services/plugins/api/JoplinWorkspace.js.map
@@ -271,9 +263,6 @@ packages/app-cli/tests/synchronizer_MigrationHandler.js.map
packages/app-cli/tests/test-utils-synchronizer.d.ts
packages/app-cli/tests/test-utils-synchronizer.js
packages/app-cli/tests/test-utils-synchronizer.js.map
packages/app-cli/tests/test-utils.d.ts
packages/app-cli/tests/test-utils.js
packages/app-cli/tests/test-utils.js.map
packages/app-desktop/ElectronAppWrapper.d.ts
packages/app-desktop/ElectronAppWrapper.js
packages/app-desktop/ElectronAppWrapper.js.map
@@ -913,9 +902,6 @@ packages/lib/errorUtils.js.map
packages/lib/eventManager.d.ts
packages/lib/eventManager.js
packages/lib/eventManager.js.map
packages/lib/fs-driver-base.d.ts
packages/lib/fs-driver-base.js
packages/lib/fs-driver-base.js.map
packages/lib/fs-driver-node.d.ts
packages/lib/fs-driver-node.js
packages/lib/fs-driver-node.js.map
@@ -973,12 +959,6 @@ packages/lib/services/ExternalEditWatcher.js.map
packages/lib/services/KeymapService.d.ts
packages/lib/services/KeymapService.js
packages/lib/services/KeymapService.js.map
packages/lib/services/KeymapService_keycodeToElectronMap.d.ts
packages/lib/services/KeymapService_keycodeToElectronMap.js
packages/lib/services/KeymapService_keycodeToElectronMap.js.map
packages/lib/services/KeymapService_keysRegExp.d.ts
packages/lib/services/KeymapService_keysRegExp.js
packages/lib/services/KeymapService_keysRegExp.js.map
packages/lib/services/KvStore.d.ts
packages/lib/services/KvStore.js
packages/lib/services/KvStore.js.map
@@ -1330,9 +1310,6 @@ packages/renderer/MdToHtml/linkReplacement.js.map
packages/renderer/MdToHtml/linkReplacement.test.d.ts
packages/renderer/MdToHtml/linkReplacement.test.js
packages/renderer/MdToHtml/linkReplacement.test.js.map
packages/renderer/MdToHtml/renderMedia.d.ts
packages/renderer/MdToHtml/renderMedia.js
packages/renderer/MdToHtml/renderMedia.js.map
packages/renderer/MdToHtml/rules/checkbox.d.ts
packages/renderer/MdToHtml/rules/checkbox.js
packages/renderer/MdToHtml/rules/checkbox.js.map
@@ -1357,9 +1334,6 @@ packages/renderer/MdToHtml/rules/image.js.map
packages/renderer/MdToHtml/rules/katex.d.ts
packages/renderer/MdToHtml/rules/katex.js
packages/renderer/MdToHtml/rules/katex.js.map
packages/renderer/MdToHtml/rules/link_close.d.ts
packages/renderer/MdToHtml/rules/link_close.js
packages/renderer/MdToHtml/rules/link_close.js.map
packages/renderer/MdToHtml/rules/link_open.d.ts
packages/renderer/MdToHtml/rules/link_open.js
packages/renderer/MdToHtml/rules/link_open.js.map

26
.gitignore vendored
View File

@@ -59,9 +59,6 @@ packages/app-cli/app/services/plugins/PluginRunner.js.map
packages/app-cli/tests/EnexToMd.d.ts
packages/app-cli/tests/EnexToMd.js
packages/app-cli/tests/EnexToMd.js.map
packages/app-cli/tests/HtmlToMd.d.ts
packages/app-cli/tests/HtmlToMd.js
packages/app-cli/tests/HtmlToMd.js.map
packages/app-cli/tests/InMemoryCache.d.ts
packages/app-cli/tests/InMemoryCache.js
packages/app-cli/tests/InMemoryCache.js.map
@@ -98,9 +95,6 @@ packages/app-cli/tests/models_Note.js.map
packages/app-cli/tests/models_Setting.d.ts
packages/app-cli/tests/models_Setting.js
packages/app-cli/tests/models_Setting.js.map
packages/app-cli/tests/services/plugins/api/JoplinViewMenuItem.d.ts
packages/app-cli/tests/services/plugins/api/JoplinViewMenuItem.js
packages/app-cli/tests/services/plugins/api/JoplinViewMenuItem.js.map
packages/app-cli/tests/services/plugins/api/JoplinWorkspace.d.ts
packages/app-cli/tests/services/plugins/api/JoplinWorkspace.js
packages/app-cli/tests/services/plugins/api/JoplinWorkspace.js.map
@@ -260,9 +254,6 @@ packages/app-cli/tests/synchronizer_MigrationHandler.js.map
packages/app-cli/tests/test-utils-synchronizer.d.ts
packages/app-cli/tests/test-utils-synchronizer.js
packages/app-cli/tests/test-utils-synchronizer.js.map
packages/app-cli/tests/test-utils.d.ts
packages/app-cli/tests/test-utils.js
packages/app-cli/tests/test-utils.js.map
packages/app-desktop/ElectronAppWrapper.d.ts
packages/app-desktop/ElectronAppWrapper.js
packages/app-desktop/ElectronAppWrapper.js.map
@@ -902,9 +893,6 @@ packages/lib/errorUtils.js.map
packages/lib/eventManager.d.ts
packages/lib/eventManager.js
packages/lib/eventManager.js.map
packages/lib/fs-driver-base.d.ts
packages/lib/fs-driver-base.js
packages/lib/fs-driver-base.js.map
packages/lib/fs-driver-node.d.ts
packages/lib/fs-driver-node.js
packages/lib/fs-driver-node.js.map
@@ -962,12 +950,6 @@ packages/lib/services/ExternalEditWatcher.js.map
packages/lib/services/KeymapService.d.ts
packages/lib/services/KeymapService.js
packages/lib/services/KeymapService.js.map
packages/lib/services/KeymapService_keycodeToElectronMap.d.ts
packages/lib/services/KeymapService_keycodeToElectronMap.js
packages/lib/services/KeymapService_keycodeToElectronMap.js.map
packages/lib/services/KeymapService_keysRegExp.d.ts
packages/lib/services/KeymapService_keysRegExp.js
packages/lib/services/KeymapService_keysRegExp.js.map
packages/lib/services/KvStore.d.ts
packages/lib/services/KvStore.js
packages/lib/services/KvStore.js.map
@@ -1319,9 +1301,6 @@ packages/renderer/MdToHtml/linkReplacement.js.map
packages/renderer/MdToHtml/linkReplacement.test.d.ts
packages/renderer/MdToHtml/linkReplacement.test.js
packages/renderer/MdToHtml/linkReplacement.test.js.map
packages/renderer/MdToHtml/renderMedia.d.ts
packages/renderer/MdToHtml/renderMedia.js
packages/renderer/MdToHtml/renderMedia.js.map
packages/renderer/MdToHtml/rules/checkbox.d.ts
packages/renderer/MdToHtml/rules/checkbox.js
packages/renderer/MdToHtml/rules/checkbox.js.map
@@ -1346,9 +1325,6 @@ packages/renderer/MdToHtml/rules/image.js.map
packages/renderer/MdToHtml/rules/katex.d.ts
packages/renderer/MdToHtml/rules/katex.js
packages/renderer/MdToHtml/rules/katex.js.map
packages/renderer/MdToHtml/rules/link_close.d.ts
packages/renderer/MdToHtml/rules/link_close.js
packages/renderer/MdToHtml/rules/link_close.js.map
packages/renderer/MdToHtml/rules/link_open.d.ts
packages/renderer/MdToHtml/rules/link_open.js
packages/renderer/MdToHtml/rules/link_open.js.map
@@ -1373,4 +1349,4 @@ packages/renderer/pathUtils.js.map
packages/renderer/utils.d.ts
packages/renderer/utils.js
packages/renderer/utils.js.map
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD

View File

@@ -84,7 +84,6 @@ The Web Clipper is a browser extension that allows you to save web pages and scr
- [How to enable end-to-end encryption](https://github.com/laurent22/joplin/blob/dev/readme/e2ee.md)
- [What is a conflict?](https://github.com/laurent22/joplin/blob/dev/readme/conflict.md)
- [How to enable debug mode](https://github.com/laurent22/joplin/blob/dev/readme/debugging.md)
- [About the Rich Text editor limitations](https://github.com/laurent22/joplin/blob/dev/readme/rich_text_editor.md)
- [FAQ](https://github.com/laurent22/joplin/blob/dev/readme/faq.md)
- Joplin API - Get Started
@@ -436,45 +435,44 @@ 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) | [أحمد باشا إبراهيم](mailto:fi_ahmed_bacha@esi.dz) | 77%
![](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 | 33%
![](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) | 79%
![](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) | | 64%
![](https://joplinapp.org/images/flags/es/catalonia.png) | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ca.po) | jmontane, 2019 | 92%
![](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) | [Hrvoje Mandić](mailto:trbuhom@net.hr) | 26%
![](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) | 96%
![](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: | 79%
![](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) | [Ettore Atalan](mailto:atalanttore@users.noreply.github.com) | 96%
![](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) | | 63%
![](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) | 96%
![](https://joplinapp.org/images/flags/esperanto.png) | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 36%
![](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) | | 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 | 96%
![](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) | 42%
![](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) | [Fathy AR](mailto:16875937+fathyar@users.noreply.github.com) | 88%
![](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/be.png) | Nederlands (België, Belgique, Belgien) | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_BE.po) | | 33%
![](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) | 96%
![](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) | 85%
![](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) | 79%
![](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) | | 95%
![](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) | [Renato Nunes Bastos](mailto:rnbastos@gmail.com) | 93%
![](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) | [João Duarte](mailto:jduar@protonmail.com) | 95%
![](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) | 74%
![](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) | | 41%
![](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) | 68%
![](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) | | 50%
![](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) | | 82%
![](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/gr.png) | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 92%
![](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) | 92%
![](https://joplinapp.org/images/flags/country-4x3/rs.png) | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 69%
![](https://joplinapp.org/images/flags/country-4x3/cn.png) | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [WhiredPlanck](mailto:fungdaat31@outlook.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) | 91%
![](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) | 97%
![](https://joplinapp.org/images/flags/country-4x3/arableague.png) | Arabic | [ar](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/ar.po) | [أحمد باشا إبراهيم](mailto:fi_ahmed_bacha@esi.dz) | 78%
![](https://joplinapp.org/images/flags/es/basque_country.png) | Basque | [eu](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/eu.po) | juan.abasolo@ehu.eus | 33%
![](https://joplinapp.org/images/flags/country-4x3/ba.png) | Bosnian | [bs_BA](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/bs_BA.po) | [Derviš T.](mailto:dervis.t@pm.me) | 82%
![](https://joplinapp.org/images/flags/country-4x3/bg.png) | Bulgarian | [bg_BG](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/bg_BG.po) | | 65%
![](https://joplinapp.org/images/flags/es/catalonia.png) | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/ca.po) | jmontane, 2019 | 95%
![](https://joplinapp.org/images/flags/country-4x3/hr.png) | Croatian | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/hr_HR.po) | [Hrvoje Mandić](mailto:trbuhom@net.hr) | 27%
![](https://joplinapp.org/images/flags/country-4x3/cz.png) | Czech | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/cs_CZ.po) | [Lukas Helebrandt](mailto:lukas@aiya.cz) | 98%
![](https://joplinapp.org/images/flags/country-4x3/dk.png) | Dansk | [da_DK](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/da_DK.po) | [Morten Juhl-Johansen Zölde-Fejér](mailto:mjjzf@syntaktisk.) | 72%
![](https://joplinapp.org/images/flags/country-4x3/de.png) | Deutsch | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/de_DE.po) | [Ettore Atalan](mailto:atalanttore@users.noreply.github.com) | 97%
![](https://joplinapp.org/images/flags/country-4x3/ee.png) | Eesti Keel | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/et_EE.po) | | 65%
![](https://joplinapp.org/images/flags/country-4x3/gb.png) | English (UK) | [en_GB](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/en_GB.po) | | 100%
![](https://joplinapp.org/images/flags/country-4x3/us.png) | English (US) | [en_US](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/en_US.po) | | 100%
![](https://joplinapp.org/images/flags/country-4x3/es.png) | Español | [es_ES](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/es_ES.po) | [Mario Campo](mailto:mario.campo@gmail.com) | 98%
![](https://joplinapp.org/images/flags/esperanto.png) | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/eo.po) | Marton Paulo | 37%
![](https://joplinapp.org/images/flags/country-4x3/fr.png) | Français | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/fr_FR.po) | Laurent Cozic | 98%
![](https://joplinapp.org/images/flags/es/galicia.png) | Galician | [gl_ES](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/gl_ES.po) | [Marcos Lans](mailto:marcoslansgarza@gmail.com) | 42%
![](https://joplinapp.org/images/flags/country-4x3/id.png) | Indonesian | [id_ID](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/id_ID.po) | [Fathy AR](mailto:16875937+fathyar@users.noreply.github.com) | 92%
![](https://joplinapp.org/images/flags/country-4x3/it.png) | Italiano | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/it_IT.po) | [Alessandro Bernardello](mailto:mailfilledwithspam@gmail.com) | 98%
![](https://joplinapp.org/images/flags/country-4x3/nl.png) | Nederlands | [nl_NL](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/nl_NL.po) | [MetBril](mailto:metbril@users.noreply.github.com) | 94%
![](https://joplinapp.org/images/flags/country-4x3/be.png) | Nederlands | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/nl_BE.po) | | 33%
![](https://joplinapp.org/images/flags/country-4x3/no.png) | Norwegian | [nb_NO](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/nb_NO.po) | [Mats Estensen](mailto:code@mxe.no) | 87%
![](https://joplinapp.org/images/flags/country-4x3/ir.png) | Persian | [fa](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/fa.po) | [Kourosh Firoozbakht](mailto:kourox@protonmail.com) | 82%
![](https://joplinapp.org/images/flags/country-4x3/pl.png) | Polski | [pl_PL](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/pl_PL.po) | | 97%
![](https://joplinapp.org/images/flags/country-4x3/pt.png) | Português | [pt_PT](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/pt_PT.po) | [João Duarte](mailto:jduar@protonmail.com) | 98%
![](https://joplinapp.org/images/flags/country-4x3/br.png) | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/pt_BR.po) | [Renato Nunes Bastos](mailto:rnbastos@gmail.com) | 96%
![](https://joplinapp.org/images/flags/country-4x3/ro.png) | Română | [ro](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/ro.po) | [Cristi Duluta](mailto:cristi.duluta@gmail.com) | 77%
![](https://joplinapp.org/images/flags/country-4x3/si.png) | Slovenian | [sl_SI](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/sl_SI.po) | | 41%
![](https://joplinapp.org/images/flags/country-4x3/se.png) | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 70%
![](https://joplinapp.org/images/flags/country-4x3/th.png) | Thai | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/th_TH.po) | | 52%
![](https://joplinapp.org/images/flags/country-4x3/vi.png) | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/vi.po) | | 84%
![](https://joplinapp.org/images/flags/country-4x3/tr.png) | Türkçe | [tr_TR](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/tr_TR.po) | [Arda Kılıçdağı](mailto:arda@kilicdagi.com) | 97%
![](https://joplinapp.org/images/flags/country-4x3/gr.png) | Ελληνικά | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 95%
![](https://joplinapp.org/images/flags/country-4x3/ru.png) | Русский | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/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/app-cli/locales/sr_RS.po) | | 70%
![](https://joplinapp.org/images/flags/country-4x3/cn.png) | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/zh_CN.po) | [WhiredPlanck](mailto:fungdaat31@outlook.com) | 95%
![](https://joplinapp.org/images/flags/country-4x3/tw.png) | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/zh_TW.po) | [Yaoze Ye](mailto:yaozeye@yahoo.co.jp) | 94%
![](https://joplinapp.org/images/flags/country-4x3/jp.png) | 日本語 | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 99%
![](https://joplinapp.org/images/flags/country-4x3/kr.png) | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 99%
<!-- LOCALE-TABLE-AUTO-GENERATED -->
# Contributors

View File

@@ -17,7 +17,6 @@
"buildDoc": "./packages/tools/build-all.sh",
"buildPluginDoc": "typedoc --name 'Joplin Plugin API Documentation' --mode file -theme './Assets/PluginDocTheme/' --readme './Assets/PluginDocTheme/index.md' --excludeNotExported --excludeExternals --excludePrivate --excludeProtected --out docs/api/references/plugin_api packages/lib/services/plugins/api/",
"buildTranslations": "npm run tsc && node packages/tools/build-translation.js",
"buildTranslationsNoTsc": "node packages/tools/build-translation.js",
"buildWebsite": "npm run buildApiDoc && node ./packages/tools/build-website.js && npm run buildPluginDoc",
"clean": "lerna clean -y && lerna run clean",
"generateDatabaseTypes": "node packages/tools/generate-database-types",

View File

@@ -54,5 +54,4 @@ module.exports = {
testEnvironment: 'node',
setupFilesAfterEnv: [`${__dirname}/jest.setup.js`],
slowTestThreshold: 20,
};

View File

@@ -1,2 +1,2 @@
test data/
data/
export/

View File

@@ -1,15 +1,24 @@
import shim from '@joplin/lib/shim';
/* eslint-disable no-unused-vars */
const os = require('os');
const time = require('@joplin/lib/time').default;
const { filename } = require('@joplin/lib/path-utils');
const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('./test-utils.js');
const Folder = require('@joplin/lib/models/Folder.js');
const Note = require('@joplin/lib/models/Note.js');
const BaseModel = require('@joplin/lib/BaseModel').default;
const shim = require('@joplin/lib/shim').default;
const HtmlToMd = require('@joplin/lib/HtmlToMd');
const { enexXmlToMd } = require('@joplin/lib/import-enex-md-gen.js');
describe('HtmlToMd', function() {
// beforeEach(async (done) => {
// await setupDatabaseAndSynchronizer(1);
// await switchClient(1);
// done();
// });
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
done();
});
it('should convert from Html to Markdown', (async () => {
const basePath = `${__dirname}/html_to_md`;
@@ -23,11 +32,11 @@ describe('HtmlToMd', function() {
const htmlPath = `${basePath}/${htmlFilename}`;
const mdPath = `${basePath}/${filename(htmlFilename)}.md`;
// if (htmlFilename !== 'anchor_same_title_and_url.html') continue;
// if (htmlFilename !== 'joplin_source_2.html') continue;
// if (htmlFilename.indexOf('image_preserve_size') !== 0) continue;
const htmlToMdOptions: any = {};
const htmlToMdOptions = {};
if (htmlFilename === 'anchor_local.html') {
// Normally the list of anchor names in the document are retrieved from the HTML code
@@ -52,29 +61,16 @@ describe('HtmlToMd', function() {
}
if (actualMd !== expectedMd) {
const result = [];
result.push('');
result.push(`Error converting file: ${htmlFilename}`);
result.push('--------------------------------- Got:');
result.push(actualMd.split('\n').map((l: string) => `"${l}"`).join('\n'));
result.push('--------------------------------- Expected:');
result.push(expectedMd.split('\n').map((l: string) => `"${l}"`).join('\n'));
result.push('--------------------------------------------');
result.push('');
console.info(result.join('\n'));
// console.info('');
// console.info(`Error converting file: ${htmlFilename}`);
// console.info('--------------------------------- Got:');
// console.info(actualMd);
// console.info('--------------------------------- Raw:');
// console.info(actualMd.split('\n'));
// console.info('--------------------------------- Expected:');
// console.info(expectedMd.split('\n'));
// console.info('--------------------------------------------');
// console.info('');
console.info('');
console.info(`Error converting file: ${htmlFilename}`);
console.info('--------------------------------- Got:');
console.info(actualMd);
console.info('--------------------------------- Raw:');
console.info(actualMd.split('\n'));
console.info('--------------------------------- Expected:');
console.info(expectedMd.split('\n'));
console.info('--------------------------------------------');
console.info('');
expect(false).toBe(true);
// return;

View File

@@ -1,8 +0,0 @@
<ul>
<li><a href="https://example.com"/>https://example.com</a></li>
<li><a href="http://example.com"/>http://example.com</a></li>
<li><a href="file:///mnt/c/test.txt"/>file:///mnt/c/test.txt</a></li>
<li><a href="https://example.com" title="with title"/>https://example.com</a></li>
<li><a href="example.com"/>example.com</a></li>
<li><a href="test@example.com"/>test@example.com</a></li>
</ul>

View File

@@ -1,6 +0,0 @@
- https://example.com
- http://example.com
- file:///mnt/c/test.txt
- [https://example.com](https://example.com "with title")
- [example.com](example.com)
- [test@example.com](test@example.com)

View File

@@ -1 +0,0 @@
<pre style="font-family: monospace;"><span>├── myproj_app<br>│ ├── api.py<br>│ └── Dockerfile</span></pre>

View File

@@ -1,5 +0,0 @@
```
├── myproj_app
│ ├── api.py
│ └── Dockerfile
```

View File

@@ -1,7 +1,6 @@
import Setting from '@joplin/lib/models/Setting';
import BaseModel from '@joplin/lib/BaseModel';
import shim from '@joplin/lib/shim';
import markdownUtils from '@joplin/lib/markdownUtils';
const { sortedIds, createNTestNotes, setupDatabaseAndSynchronizer, switchClient, checkThrowAsync } = require('./test-utils.js');
const Folder = require('@joplin/lib/models/Folder.js');
const Note = require('@joplin/lib/models/Note.js');
@@ -218,8 +217,6 @@ describe('models_Note', function() {
const t1 = r1.updated_time;
const t2 = r2.updated_time;
const resourceDirE = markdownUtils.escapeLinkUrl(resourceDir);
const testCases = [
[
false,
@@ -244,17 +241,17 @@ describe('models_Note', function() {
[
true,
`![](:/${r1.id})`,
`![](file://${resourceDirE}/${r1.id}.jpg?t=${t1})`,
`![](file://${resourceDir}/${r1.id}.jpg?t=${t1})`,
],
[
true,
`![](:/${r1.id}) ![](:/${r1.id}) ![](:/${r2.id})`,
`![](file://${resourceDirE}/${r1.id}.jpg?t=${t1}) ![](file://${resourceDirE}/${r1.id}.jpg?t=${t1}) ![](file://${resourceDirE}/${r2.id}.jpg?t=${t2})`,
`![](file://${resourceDir}/${r1.id}.jpg?t=${t1}) ![](file://${resourceDir}/${r1.id}.jpg?t=${t1}) ![](file://${resourceDir}/${r2.id}.jpg?t=${t2})`,
],
[
true,
`![](:/${r3.id})`,
`![](file://${resourceDirE}/${r3.id}.pdf)`,
`![](file://${resourceDir}/${r3.id}.pdf)`,
],
];

View File

@@ -1,57 +0,0 @@
import KeymapService from '@joplin/lib/services/KeymapService';
import PluginService from '@joplin/lib/services/plugins/PluginService';
const { newPluginService, newPluginScript, setupDatabaseAndSynchronizer, switchClient, afterEachCleanUp } = require('../../../test-utils');
describe('JoplinViewMenuItem', () => {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
done();
});
afterEach(async () => {
await afterEachCleanUp();
});
test('should register commands with the keymap service', async () => {
const service = new newPluginService() as PluginService;
KeymapService.instance().initialize();
const pluginScript = newPluginScript(`
joplin.plugins.register({
onStart: async function() {
await joplin.commands.register({
name: 'testCommand1',
label: 'My Test Command 1',
iconName: 'fas fa-music',
execute: async () => {},
});
await joplin.views.menuItems.create('myMenuItem1', 'testCommand1', 'tools', { accelerator: 'CmdOrCtrl+Alt+Shift+B' });
await joplin.commands.register({
name: 'testCommand2',
label: 'My Test Command 2',
iconName: 'fas fa-music',
execute: async () => {},
});
await joplin.views.menuItems.create('myMenuItem2', 'testCommand2', 'tools');
},
});
`);
const plugin = await service.loadPluginFromJsBundle('', pluginScript);
await service.runPlugin(plugin);
const commandNames = KeymapService.instance().getCommandNames();
expect(commandNames.includes('testCommand1')).toBe(true);
expect(commandNames.includes('testCommand2')).toBe(true);
await service.destroy();
});
});

View File

@@ -257,8 +257,8 @@ describe('services_EncryptionService', function() {
await service.loadMasterKey_(masterKey, '123456', true);
const sourcePath = `${__dirname}/../tests/support/photo.jpg`;
const encryptedPath = `${Setting.value('tempDir')}/photo.crypted`;
const decryptedPath = `${Setting.value('tempDir')}/photo.jpg`;
const encryptedPath = `${__dirname}/data/photo.crypted`;
const decryptedPath = `${__dirname}/data/photo.jpg`;
await service.encryptFile(sourcePath, encryptedPath);
await service.decryptFile(encryptedPath, decryptedPath);

View File

@@ -2,7 +2,8 @@ import InteropService from '@joplin/lib/services/interop/InteropService';
import { CustomExportContext, CustomImportContext, Module, ModuleType } from '@joplin/lib/services/interop/types';
import shim from '@joplin/lib/shim';
const { fileContentEqual, setupDatabaseAndSynchronizer, switchClient, checkThrowAsync, exportDir } = require('./test-utils.js');
const { fileContentEqual, setupDatabaseAndSynchronizer, switchClient, checkThrowAsync } = require('./test-utils.js');
const Folder = require('@joplin/lib/models/Folder.js');
const Note = require('@joplin/lib/models/Note.js');
const Tag = require('@joplin/lib/models/Tag.js');
@@ -10,6 +11,10 @@ const Resource = require('@joplin/lib/models/Resource.js');
const fs = require('fs-extra');
const ArrayUtils = require('@joplin/lib/ArrayUtils');
function exportDir() {
return `${__dirname}/export`;
}
async function recreateExportDir() {
const dir = exportDir();
await fs.remove(dir);

View File

@@ -2,7 +2,7 @@
const fs = require('fs-extra');
const { setupDatabaseAndSynchronizer, switchClient, exportDir } = require('./test-utils.js');
const { setupDatabaseAndSynchronizer, switchClient } = require('./test-utils.js');
const InteropService_Exporter_Md = require('@joplin/lib/services/interop/InteropService_Exporter_Md').default;
const BaseModel = require('@joplin/lib/BaseModel').default;
const Folder = require('@joplin/lib/models/Folder.js');
@@ -10,27 +10,29 @@ const Resource = require('@joplin/lib/models/Resource.js');
const Note = require('@joplin/lib/models/Note.js');
const shim = require('@joplin/lib/shim').default;
const exportDir = `${__dirname}/export`;
describe('services_InteropService_Exporter_Md', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
await fs.remove(exportDir());
await fs.mkdirp(exportDir());
await fs.remove(exportDir);
await fs.mkdirp(exportDir);
done();
});
it('should create resources directory', (async () => {
const service = new InteropService_Exporter_Md();
await service.init(exportDir());
await service.init(exportDir);
expect(await shim.fsDriver().exists(`${exportDir()}/_resources/`)).toBe(true);
expect(await shim.fsDriver().exists(`${exportDir}/_resources/`)).toBe(true);
}));
it('should create note paths and add them to context', (async () => {
const exporter = new InteropService_Exporter_Md();
await exporter.init(exportDir());
await exporter.init(exportDir);
const itemsToExport = [];
const queueExportItem = (itemType, itemOrId) => {
@@ -72,7 +74,7 @@ describe('services_InteropService_Exporter_Md', function() {
it('should handle duplicate note names', (async () => {
const exporter = new InteropService_Exporter_Md();
await exporter.init(exportDir());
await exporter.init(exportDir);
const itemsToExport = [];
const queueExportItem = (itemType, itemOrId) => {
@@ -99,7 +101,7 @@ describe('services_InteropService_Exporter_Md', function() {
it('should not override existing files', (async () => {
const exporter = new InteropService_Exporter_Md();
await exporter.init(exportDir());
await exporter.init(exportDir);
const itemsToExport = [];
const queueExportItem = (itemType, itemOrId) => {
@@ -116,7 +118,7 @@ describe('services_InteropService_Exporter_Md', function() {
await exporter.processItem(Folder.modelType(), folder1);
// Create a file with the path of note1 before processing note1
await shim.fsDriver().writeFile(`${exportDir()}/folder1/note1.md`, 'Note content', 'utf-8');
await shim.fsDriver().writeFile(`${exportDir}/folder1/note1.md`, 'Note content', 'utf-8');
await exporter.prepareForProcessingItemType(BaseModel.TYPE_NOTE, itemsToExport);
@@ -126,7 +128,7 @@ describe('services_InteropService_Exporter_Md', function() {
it('should save resource files in _resource directory', (async () => {
const exporter = new InteropService_Exporter_Md();
await exporter.init(exportDir());
await exporter.init(exportDir);
const itemsToExport = [];
const queueExportItem = (itemType, itemOrId) => {
@@ -157,13 +159,13 @@ describe('services_InteropService_Exporter_Md', function() {
await exporter.processResource(resource1, Resource.fullPath(resource1));
await exporter.processResource(resource2, Resource.fullPath(resource2));
expect(await shim.fsDriver().exists(`${exportDir()}/_resources/${Resource.filename(resource1)}`)).toBe(true, 'Resource file should be copied to _resources directory.');
expect(await shim.fsDriver().exists(`${exportDir()}/_resources/${Resource.filename(resource2)}`)).toBe(true, 'Resource file should be copied to _resources directory.');
expect(await shim.fsDriver().exists(`${exportDir}/_resources/${Resource.filename(resource1)}`)).toBe(true, 'Resource file should be copied to _resources directory.');
expect(await shim.fsDriver().exists(`${exportDir}/_resources/${Resource.filename(resource2)}`)).toBe(true, 'Resource file should be copied to _resources directory.');
}));
it('should create folders in fs', (async () => {
const exporter = new InteropService_Exporter_Md();
await exporter.init(exportDir());
await exporter.init(exportDir);
const itemsToExport = [];
const queueExportItem = (itemType, itemOrId) => {
@@ -187,14 +189,14 @@ describe('services_InteropService_Exporter_Md', function() {
await exporter.prepareForProcessingItemType(BaseModel.TYPE_NOTE, itemsToExport);
await exporter.processItem(Note.modelType(), note2);
expect(await shim.fsDriver().exists(`${exportDir()}/folder1`)).toBe(true, 'Folder should be created in filesystem.');
expect(await shim.fsDriver().exists(`${exportDir()}/folder1/folder2`)).toBe(true, 'Folder should be created in filesystem.');
expect(await shim.fsDriver().exists(`${exportDir()}/folder1/folder3`)).toBe(true, 'Folder should be created in filesystem.');
expect(await shim.fsDriver().exists(`${exportDir}/folder1`)).toBe(true, 'Folder should be created in filesystem.');
expect(await shim.fsDriver().exists(`${exportDir}/folder1/folder2`)).toBe(true, 'Folder should be created in filesystem.');
expect(await shim.fsDriver().exists(`${exportDir}/folder1/folder3`)).toBe(true, 'Folder should be created in filesystem.');
}));
it('should save notes in fs', (async () => {
const exporter = new InteropService_Exporter_Md();
await exporter.init(exportDir());
await exporter.init(exportDir);
const itemsToExport = [];
const queueExportItem = (itemType, itemOrId) => {
@@ -224,14 +226,14 @@ describe('services_InteropService_Exporter_Md', function() {
await exporter.processItem(Note.modelType(), note2);
await exporter.processItem(Note.modelType(), note3);
expect(await shim.fsDriver().exists(`${exportDir()}/${exporter.context().notePaths[note1.id]}`)).toBe(true, 'File should be saved in filesystem.');
expect(await shim.fsDriver().exists(`${exportDir()}/${exporter.context().notePaths[note2.id]}`)).toBe(true, 'File should be saved in filesystem.');
expect(await shim.fsDriver().exists(`${exportDir()}/${exporter.context().notePaths[note3.id]}`)).toBe(true, 'File should be saved in filesystem.');
expect(await shim.fsDriver().exists(`${exportDir}/${exporter.context().notePaths[note1.id]}`)).toBe(true, 'File should be saved in filesystem.');
expect(await shim.fsDriver().exists(`${exportDir}/${exporter.context().notePaths[note2.id]}`)).toBe(true, 'File should be saved in filesystem.');
expect(await shim.fsDriver().exists(`${exportDir}/${exporter.context().notePaths[note3.id]}`)).toBe(true, 'File should be saved in filesystem.');
}));
it('should replace resource ids with relative paths', (async () => {
const exporter = new InteropService_Exporter_Md();
await exporter.init(exportDir());
await exporter.init(exportDir);
const itemsToExport = [];
const queueExportItem = (itemType, itemOrId) => {
@@ -267,8 +269,8 @@ describe('services_InteropService_Exporter_Md', function() {
await exporter.processItem(Note.modelType(), note1);
await exporter.processItem(Note.modelType(), note2);
const note1_body = await shim.fsDriver().readFile(`${exportDir()}/${exporter.context().notePaths[note1.id]}`);
const note2_body = await shim.fsDriver().readFile(`${exportDir()}/${exporter.context().notePaths[note2.id]}`);
const note1_body = await shim.fsDriver().readFile(`${exportDir}/${exporter.context().notePaths[note1.id]}`);
const note2_body = await shim.fsDriver().readFile(`${exportDir}/${exporter.context().notePaths[note2.id]}`);
expect(note1_body).toContain('](../_resources/resource1.jpg)', 'Resource id should be replaced with a relative path.');
expect(note2_body).toContain('](../../_resources/resource2.jpg)', 'Resource id should be replaced with a relative path.');
@@ -276,7 +278,7 @@ describe('services_InteropService_Exporter_Md', function() {
it('should replace note ids with relative paths', (async () => {
const exporter = new InteropService_Exporter_Md();
await exporter.init(exportDir());
await exporter.init(exportDir);
const itemsToExport = [];
const queueExportItem = (itemType, itemOrId) => {
@@ -316,9 +318,9 @@ describe('services_InteropService_Exporter_Md', function() {
await exporter.processItem(Note.modelType(), note2);
await exporter.processItem(Note.modelType(), note3);
const note1_body = await shim.fsDriver().readFile(`${exportDir()}/${exporter.context().notePaths[note1.id]}`);
const note2_body = await shim.fsDriver().readFile(`${exportDir()}/${exporter.context().notePaths[note2.id]}`);
const note3_body = await shim.fsDriver().readFile(`${exportDir()}/${exporter.context().notePaths[note3.id]}`);
const note1_body = await shim.fsDriver().readFile(`${exportDir}/${exporter.context().notePaths[note1.id]}`);
const note2_body = await shim.fsDriver().readFile(`${exportDir}/${exporter.context().notePaths[note2.id]}`);
const note3_body = await shim.fsDriver().readFile(`${exportDir}/${exporter.context().notePaths[note3.id]}`);
expect(note1_body).toContain('](../folder3/note3.md)', 'Note id should be replaced with a relative path.');
expect(note2_body).toContain('](../../folder3/note3.md)', 'Resource id should be replaced with a relative path.');
@@ -328,7 +330,7 @@ describe('services_InteropService_Exporter_Md', function() {
it('should url encode relative note links', (async () => {
const exporter = new InteropService_Exporter_Md();
await exporter.init(exportDir());
await exporter.init(exportDir);
const itemsToExport = [];
const queueExportItem = (itemType, itemOrId) => {
@@ -349,7 +351,7 @@ describe('services_InteropService_Exporter_Md', function() {
await exporter.processItem(Note.modelType(), note1);
await exporter.processItem(Note.modelType(), note2);
const note2_body = await shim.fsDriver().readFile(`${exportDir()}/${exporter.context().notePaths[note2.id]}`);
const note2_body = await shim.fsDriver().readFile(`${exportDir}/${exporter.context().notePaths[note2.id]}`);
expect(note2_body).toContain('[link](../folder%20with%20space1/note1%20name%20with%20space.md)', 'Whitespace in URL should be encoded');
}));
});

View File

@@ -511,17 +511,4 @@ describe('services_SearchEngine', function() {
expect((await engine.search('"- [ ]"', { searchType: SearchEngine.SEARCH_TYPE_BASIC })).length).toBe(1);
expect((await engine.search('"[ ]"', { searchType: SearchEngine.SEARCH_TYPE_BASIC })).length).toBe(2);
}));
it('should not mistake cyrillic "l" for latin "n"', (async () => {
const n1 = await Note.save({ title: 'latin n', body: 'n' });
const n2 = await Note.save({ title: 'cyrillic l', body: 'л' });
await engine.syncTables();
expect((await engine.search('n')).length).toBe(1);
expect((await engine.search('n'))[0].id).toBe(n1.id);
expect((await engine.search('л')).length).toBe(1);
expect((await engine.search('л'))[0].id).toBe(n2.id);
}));
});

View File

@@ -7,13 +7,8 @@ joplin.plugins.register({
console.info('Alarm was triggered for note: ', note);
});
joplin.workspace.onSyncStart(async (event:any) => {
console.info('Sync has started...');
});
joplin.workspace.onSyncComplete(async (event:any) => {
joplin.workspace.onSyncComplete(async () => {
console.info('Sync has completed');
console.info('With errors:', event.withErrors);
});
},
});

View File

@@ -0,0 +1,86 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.localNotesFoldersSameAsRemote = exports.remoteResources = exports.remoteNotesFoldersResources = exports.remoteNotesAndFolders = exports.allNotesFolders = void 0;
const BaseModel_1 = require("@joplin/lib/BaseModel");
const { fileApi } = require('./test-utils.js');
const Folder = require('@joplin/lib/models/Folder.js');
const Note = require('@joplin/lib/models/Note.js');
const BaseItem = require('@joplin/lib/models/BaseItem.js');
function allNotesFolders() {
return __awaiter(this, void 0, void 0, function* () {
const folders = yield Folder.all();
const notes = yield Note.all();
return folders.concat(notes);
});
}
exports.allNotesFolders = allNotesFolders;
function remoteItemsByTypes(types) {
return __awaiter(this, void 0, void 0, function* () {
const list = yield fileApi().list('', { includeDirs: false, syncItemsOnly: true });
if (list.has_more)
throw new Error('Not implemented!!!');
const files = list.items;
const output = [];
for (const file of files) {
const remoteContent = yield fileApi().get(file.path);
const content = yield BaseItem.unserialize(remoteContent);
if (types.indexOf(content.type_) < 0)
continue;
output.push(content);
}
return output;
});
}
function remoteNotesAndFolders() {
return __awaiter(this, void 0, void 0, function* () {
return remoteItemsByTypes([BaseModel_1.default.TYPE_NOTE, BaseModel_1.default.TYPE_FOLDER]);
});
}
exports.remoteNotesAndFolders = remoteNotesAndFolders;
function remoteNotesFoldersResources() {
return __awaiter(this, void 0, void 0, function* () {
return remoteItemsByTypes([BaseModel_1.default.TYPE_NOTE, BaseModel_1.default.TYPE_FOLDER, BaseModel_1.default.TYPE_RESOURCE]);
});
}
exports.remoteNotesFoldersResources = remoteNotesFoldersResources;
function remoteResources() {
return __awaiter(this, void 0, void 0, function* () {
return remoteItemsByTypes([BaseModel_1.default.TYPE_RESOURCE]);
});
}
exports.remoteResources = remoteResources;
function localNotesFoldersSameAsRemote(locals, expect) {
return __awaiter(this, void 0, void 0, function* () {
let error = null;
try {
const nf = yield remoteNotesAndFolders();
expect(locals.length).toBe(nf.length);
for (let i = 0; i < locals.length; i++) {
const dbItem = locals[i];
const path = BaseItem.systemPath(dbItem);
const remote = yield fileApi().stat(path);
expect(!!remote).toBe(true);
if (!remote)
continue;
let remoteContent = yield fileApi().get(path);
remoteContent = dbItem.type_ == BaseModel_1.default.TYPE_NOTE ? yield Note.unserialize(remoteContent) : yield Folder.unserialize(remoteContent);
expect(remoteContent.title).toBe(dbItem.title);
}
}
catch (e) {
error = e;
}
expect(error).toBe(null);
});
}
exports.localNotesFoldersSameAsRemote = localNotesFoldersSameAsRemote;
//# sourceMappingURL=test-utils-synchronizer.js.map

View File

@@ -1,24 +1,10 @@
/* eslint-disable require-atomic-updates */
import BaseApplication from '@joplin/lib/BaseApplication';
import BaseModel from '@joplin/lib/BaseModel';
import Logger, { TargetType, LoggerWrapper } from '@joplin/lib/Logger';
import Setting from '@joplin/lib/models/Setting';
import BaseService from '@joplin/lib/services/BaseService';
import FsDriverNode from '@joplin/lib/fs-driver-node';
import time from '@joplin/lib/time';
import shim from '@joplin/lib/shim';
import uuid from '@joplin/lib/uuid';
import ResourceService from '@joplin/lib/services/ResourceService';
import KeymapService from '@joplin/lib/services/KeymapService';
import KvStore from '@joplin/lib/services/KvStore';
import KeychainServiceDriver from '@joplin/lib/services/keychain/KeychainServiceDriver.node';
import KeychainServiceDriverDummy from '@joplin/lib/services/keychain/KeychainServiceDriver.dummy';
import PluginRunner from '../app/services/plugins/PluginRunner';
import PluginService from '@joplin/lib/services/plugins/PluginService';
const fs = require('fs-extra');
const { JoplinDatabase } = require('@joplin/lib/joplin-database.js');
const { DatabaseDriverNode } = require('@joplin/lib/database-driver-node.js');
const BaseApplication = require('@joplin/lib/BaseApplication').default;
const BaseModel = require('@joplin/lib/BaseModel').default;
const Folder = require('@joplin/lib/models/Folder.js');
const Note = require('@joplin/lib/models/Note.js');
const ItemChange = require('@joplin/lib/models/ItemChange.js');
@@ -26,6 +12,8 @@ const Resource = require('@joplin/lib/models/Resource.js');
const Tag = require('@joplin/lib/models/Tag.js');
const NoteTag = require('@joplin/lib/models/NoteTag.js');
const Revision = require('@joplin/lib/models/Revision.js');
const Logger = require('@joplin/lib/Logger').default;
const Setting = require('@joplin/lib/models/Setting').default;
const MasterKey = require('@joplin/lib/models/MasterKey');
const BaseItem = require('@joplin/lib/models/BaseItem.js');
const { FileApi } = require('@joplin/lib/file-api.js');
@@ -35,7 +23,12 @@ const { FileApiDriverWebDav } = require('@joplin/lib/file-api-driver-webdav.js')
const { FileApiDriverDropbox } = require('@joplin/lib/file-api-driver-dropbox.js');
const { FileApiDriverOneDrive } = require('@joplin/lib/file-api-driver-onedrive.js');
const { FileApiDriverAmazonS3 } = require('@joplin/lib/file-api-driver-amazon-s3.js');
const BaseService = require('@joplin/lib/services/BaseService').default;
const FsDriverNode = require('@joplin/lib/fs-driver-node').default;
const time = require('@joplin/lib/time').default;
const { shimInit } = require('@joplin/lib/shim-init-node.js');
const shim = require('@joplin/lib/shim').default;
const uuid = require('@joplin/lib/uuid').default;
const SyncTargetRegistry = require('@joplin/lib/SyncTargetRegistry.js');
const SyncTargetMemory = require('@joplin/lib/SyncTargetMemory.js');
const SyncTargetFilesystem = require('@joplin/lib/SyncTargetFilesystem.js');
@@ -45,14 +38,20 @@ const SyncTargetDropbox = require('@joplin/lib/SyncTargetDropbox.js');
const SyncTargetAmazonS3 = require('@joplin/lib/SyncTargetAmazonS3.js');
const EncryptionService = require('@joplin/lib/services/EncryptionService.js');
const DecryptionWorker = require('@joplin/lib/services/DecryptionWorker.js');
const ResourceService = require('@joplin/lib/services/ResourceService').default;
const RevisionService = require('@joplin/lib/services/RevisionService.js');
const ResourceFetcher = require('@joplin/lib/services/ResourceFetcher.js');
const KvStore = require('@joplin/lib/services/KvStore').default;
const WebDavApi = require('@joplin/lib/WebDavApi');
const DropboxApi = require('@joplin/lib/DropboxApi');
const { OneDriveApi } = require('@joplin/lib/onedrive-api');
const { loadKeychainServiceAndSettings } = require('@joplin/lib/services/SettingUtils');
const KeychainServiceDriver = require('@joplin/lib/services/keychain/KeychainServiceDriver.node').default;
const KeychainServiceDriverDummy = require('@joplin/lib/services/keychain/KeychainServiceDriver.dummy').default;
const md5 = require('md5');
const S3 = require('aws-sdk/clients/s3');
const PluginRunner = require('../app/services/plugins/PluginRunner').default;
const PluginService = require('@joplin/lib/services/plugins/PluginService').default;
const { Dirnames } = require('@joplin/lib/services/synchronizer/utils/types');
const sharp = require('sharp');
@@ -63,16 +62,16 @@ const sharp = require('sharp');
// Jest, to make debugging easier, but it's not clear how to get this info).
const suiteName_ = uuid.createNano();
const databases_: any[] = [];
let synchronizers_: any[] = [];
const synchronizerContexts_: any = {};
const fileApis_: any = {};
const encryptionServices_: any[] = [];
const revisionServices_: any[] = [];
const decryptionWorkers_: any[] = [];
const resourceServices_: any[] = [];
const resourceFetchers_: any[] = [];
const kvStores_: KvStore[] = [];
const databases_ = [];
let synchronizers_ = [];
const synchronizerContexts_ = {};
const fileApis_ = {};
const encryptionServices_ = [];
const revisionServices_ = [];
const decryptionWorkers_ = [];
const resourceServices_ = [];
const resourceFetchers_ = [];
const kvStores_ = [];
let currentClient_ = 1;
// The line `process.on('unhandledRejection'...` in all the test files is going to
@@ -100,11 +99,7 @@ FileApiDriverLocal.fsDriver_ = fsDriver;
const logDir = `${__dirname}/../tests/logs`;
const baseTempDir = `${__dirname}/../tests/tmp/${suiteName_}`;
// We add a space in the data directory path as that will help uncover
// various space-in-path issues.
const dataDir = `${__dirname}/test data/${suiteName_}`;
const dataDir = `${__dirname}/data/${suiteName_}`;
fs.mkdirpSync(logDir, 0o755);
fs.mkdirpSync(baseTempDir, 0o755);
fs.mkdirpSync(dataDir);
@@ -117,7 +112,7 @@ SyncTargetRegistry.addClass(SyncTargetDropbox);
SyncTargetRegistry.addClass(SyncTargetAmazonS3);
let syncTargetName_ = '';
let syncTargetId_: number = null;
let syncTargetId_ = null;
let sleepTime = 0;
let isNetworkSyncTarget_ = false;
@@ -125,7 +120,7 @@ function syncTargetName() {
return syncTargetName_;
}
function setSyncTargetName(name: string) {
function setSyncTargetName(name) {
if (name === syncTargetName_) return syncTargetName_;
const previousName = syncTargetName_;
syncTargetName_ = name;
@@ -146,19 +141,20 @@ setSyncTargetName('memory');
const syncDir = `${__dirname}/../tests/sync/${suiteName_}`;
// 90 seconds now that the tests are running in parallel and have been
// split into smaller suites might not be necessary but for now leave it
// anyway.
let defaultJestTimeout = 90 * 1000;
if (isNetworkSyncTarget_) defaultJestTimeout = 60 * 1000 * 10;
jest.setTimeout(defaultJestTimeout);
// TODO: Should probably update this for Jest?
// let defaultJasmineTimeout = 90 * 1000;
// if (isNetworkSyncTarget_) defaultJasmineTimeout = 60 * 1000 * 10;
// if (typeof jasmine !== 'undefined') jasmine.DEFAULT_TIMEOUT_INTERVAL = defaultJasmineTimeout;
const dbLogger = new Logger();
dbLogger.addTarget(TargetType.Console);
dbLogger.addTarget('console');
// dbLogger.addTarget('file', { path: `${logDir}/log.txt` });
dbLogger.setLevel(Logger.LEVEL_WARN);
const logger = new Logger();
logger.addTarget(TargetType.Console);
logger.addTarget('console');
// logger.addTarget('file', { path: `${logDir}/log.txt` });
logger.setLevel(Logger.LEVEL_WARN); // Set to DEBUG to display sync process in console
Logger.initializeGlobalLogger(logger);
@@ -188,16 +184,16 @@ function isNetworkSyncTarget() {
return isNetworkSyncTarget_;
}
function sleep(n: number) {
return new Promise((resolve) => {
function sleep(n) {
return new Promise((resolve, reject) => {
shim.setTimeout(() => {
resolve();
}, Math.round(n * 1000));
});
}
function msleep(ms: number) {
return new Promise((resolve) => {
function msleep(ms) {
return new Promise((resolve, reject) => {
shim.setTimeout(() => {
resolve();
}, ms);
@@ -210,10 +206,9 @@ function currentClientId() {
async function afterEachCleanUp() {
await ItemChange.waitForAllSaved();
KeymapService.destroyInstance();
}
async function switchClient(id: number, options: any = null) {
async function switchClient(id, options = null) {
options = Object.assign({}, { keychainEnabled: false }, options);
if (!databases_[id]) throw new Error(`Call setupDatabaseAndSynchronizer(${id}) first!!`);
@@ -238,7 +233,7 @@ async function switchClient(id: number, options: any = null) {
Setting.setValue('sync.wipeOutFailSafe', false); // To keep things simple, always disable fail-safe unless explicitely set in the test itself
}
async function clearDatabase(id: number = null) {
async function clearDatabase(id = null) {
if (id === null) id = currentClient_;
if (!databases_[id]) return;
@@ -269,19 +264,13 @@ async function clearDatabase(id: number = null) {
await databases_[id].transactionExecBatch(queries);
}
async function setupDatabase(id: number = null, options: any = null) {
async function setupDatabase(id = null, options = null) {
options = Object.assign({}, { keychainEnabled: false }, options);
if (id === null) id = currentClient_;
Setting.cancelScheduleSave();
// Note that this was changed from `Setting.cache_ = []` to `await
// Setting.reset()` during the TypeScript conversion. Normally this is
// more correct but something to keep in mind anyway in case there are
// some strange async issue related to settings when the tests are
// running.
await Setting.reset();
Setting.cache_ = null;
if (databases_[id]) {
BaseModel.setDb(databases_[id]);
@@ -306,27 +295,22 @@ async function setupDatabase(id: number = null, options: any = null) {
await loadKeychainServiceAndSettings(options.keychainEnabled ? KeychainServiceDriver : KeychainServiceDriverDummy);
}
function exportDir(id: number = null) {
if (id === null) id = currentClient_;
return `${dataDir}/export`;
}
function resourceDirName(id: number = null) {
function resourceDirName(id = null) {
if (id === null) id = currentClient_;
return `resources-${id}`;
}
function resourceDir(id: number = null) {
function resourceDir(id = null) {
if (id === null) id = currentClient_;
return `${dataDir}/${resourceDirName(id)}`;
}
function pluginDir(id: number = null) {
function pluginDir(id = null) {
if (id === null) id = currentClient_;
return `${dataDir}/plugins-${id}`;
}
async function setupDatabaseAndSynchronizer(id: number, options: any = null) {
async function setupDatabaseAndSynchronizer(id, options = null) {
if (id === null) id = currentClient_;
BaseService.logger_ = logger;
@@ -363,12 +347,12 @@ async function setupDatabaseAndSynchronizer(id: number, options: any = null) {
await fileApi().clearRoot();
}
function db(id: number = null) {
function db(id = null) {
if (id === null) id = currentClient_;
return databases_[id];
}
function synchronizer(id: number = null) {
function synchronizer(id = null) {
if (id === null) id = currentClient_;
return synchronizers_[id];
}
@@ -376,7 +360,7 @@ function synchronizer(id: number = null) {
// This is like calling synchronizer.start() but it handles the
// complexity of passing around the sync context depending on
// the client.
async function synchronizerStart(id: number = null, extraOptions: any = null) {
async function synchronizerStart(id = null, extraOptions = null) {
if (id === null) id = currentClient_;
const context = synchronizerContexts_[id];
const options = Object.assign({}, extraOptions);
@@ -386,41 +370,41 @@ async function synchronizerStart(id: number = null, extraOptions: any = null) {
return newContext;
}
function encryptionService(id: number = null) {
function encryptionService(id = null) {
if (id === null) id = currentClient_;
return encryptionServices_[id];
}
function kvStore(id: number = null) {
function kvStore(id = null) {
if (id === null) id = currentClient_;
const o = kvStores_[id];
o.setDb(db(id));
return o;
}
function revisionService(id: number = null) {
function revisionService(id = null) {
if (id === null) id = currentClient_;
return revisionServices_[id];
}
function decryptionWorker(id: number = null) {
function decryptionWorker(id = null) {
if (id === null) id = currentClient_;
const o = decryptionWorkers_[id];
o.setKvStore(kvStore(id));
return o;
}
function resourceService(id: number = null) {
function resourceService(id = null) {
if (id === null) id = currentClient_;
return resourceServices_[id];
}
function resourceFetcher(id: number = null) {
function resourceFetcher(id = null) {
if (id === null) id = currentClient_;
return resourceFetchers_[id];
}
async function loadEncryptionMasterKey(id: number = null, useExisting = false) {
async function loadEncryptionMasterKey(id = null, useExisting = false) {
const service = encryptionService(id);
let masterKey = null;
@@ -503,7 +487,7 @@ function fileApi() {
return fileApis_[syncTargetId_];
}
function objectsEqual(o1: any, o2: any) {
function objectsEqual(o1, o2) {
if (Object.getOwnPropertyNames(o1).length !== Object.getOwnPropertyNames(o2).length) return false;
for (const n in o1) {
if (!o1.hasOwnProperty(n)) continue;
@@ -512,7 +496,7 @@ function objectsEqual(o1: any, o2: any) {
return true;
}
async function checkThrowAsync(asyncFn: Function) {
async function checkThrowAsync(asyncFn) {
let hasThrown = false;
try {
await asyncFn();
@@ -522,7 +506,7 @@ async function checkThrowAsync(asyncFn: Function) {
return hasThrown;
}
async function expectThrow(asyncFn: Function, errorCode: any = undefined) {
async function expectThrow(asyncFn, errorCode = undefined) {
let hasThrown = false;
let thrownError = null;
try {
@@ -533,7 +517,7 @@ async function expectThrow(asyncFn: Function, errorCode: any = undefined) {
}
if (!hasThrown) {
expect('not throw').toBe('throw');
expect('not throw').toBe('throw', 'Expected function to throw an error but did not');
} else if (thrownError.code !== errorCode) {
console.error(thrownError);
expect(`error code: ${thrownError.code}`).toBe(`error code: ${errorCode}`);
@@ -542,7 +526,7 @@ async function expectThrow(asyncFn: Function, errorCode: any = undefined) {
}
}
async function expectNotThrow(asyncFn: Function) {
async function expectNotThrow(asyncFn) {
let thrownError = null;
try {
await asyncFn();
@@ -552,13 +536,13 @@ async function expectNotThrow(asyncFn: Function) {
if (thrownError) {
console.error(thrownError);
expect(thrownError.message).toBe('');
expect(thrownError.message).toBe('', 'Expected function not to throw an error but it did');
} else {
expect(true).toBe(true);
}
}
function checkThrow(fn: Function) {
function checkThrow(fn) {
let hasThrown = false;
try {
fn();
@@ -568,7 +552,7 @@ function checkThrow(fn: Function) {
return hasThrown;
}
function fileContentEqual(path1: string, path2: string) {
function fileContentEqual(path1, path2) {
const fs = require('fs-extra');
const content1 = fs.readFileSync(path1, 'base64');
const content2 = fs.readFileSync(path2, 'base64');
@@ -607,19 +591,19 @@ async function allSyncTargetItemsEncrypted() {
return totalCount === encryptedCount;
}
function id(a: any) {
function id(a) {
return a.id;
}
function ids(a: any[]) {
function ids(a) {
return a.map(n => n.id);
}
function sortedIds(a: any[]) {
function sortedIds(a) {
return ids(a).sort();
}
function at(a: any[], indexes: any[]) {
function at(a, indexes) {
const out = [];
for (let i = 0; i < indexes.length; i++) {
out.push(a[indexes[i]]);
@@ -627,7 +611,7 @@ function at(a: any[], indexes: any[]) {
return out;
}
async function createNTestFolders(n: number) {
async function createNTestFolders(n) {
const folders = [];
for (let i = 0; i < n; i++) {
const folder = await Folder.save({ title: 'folder' });
@@ -637,7 +621,7 @@ async function createNTestFolders(n: number) {
return folders;
}
async function createNTestNotes(n: number, folder: any, tagIds: string[] = null, title: string = 'note') {
async function createNTestNotes(n, folder, tagIds = null, title = 'note') {
const notes = [];
for (let i = 0; i < n; i++) {
const title_ = n > 1 ? `${title}${i}` : title;
@@ -654,7 +638,7 @@ async function createNTestNotes(n: number, folder: any, tagIds: string[] = null,
return notes;
}
async function createNTestTags(n: number) {
async function createNTestTags(n) {
const tags = [];
for (let i = 0; i < n; i++) {
const tag = await Tag.save({ title: 'tag' });
@@ -664,7 +648,7 @@ async function createNTestTags(n: number) {
return tags;
}
function tempFilePath(ext: string) {
function tempFilePath(ext) {
return `${Setting.value('tempDir')}/${md5(Date.now() + Math.random())}.${ext}`;
}
@@ -691,7 +675,7 @@ function newPluginService(appVersion = '1.4') {
return service;
}
function newPluginScript(script: string) {
function newPluginScript(script) {
return `
/* joplin-manifest:
{
@@ -721,11 +705,6 @@ function newPluginScript(script: string) {
// Application for feature integration testing
class TestApp extends BaseApplication {
private hasGui_: boolean;
private middlewareCalls_: any[];
private logger_: LoggerWrapper;
constructor(hasGui = true) {
super();
this.hasGui_ = hasGui;
@@ -737,7 +716,7 @@ class TestApp extends BaseApplication {
return this.hasGui_;
}
async start(argv: any[]) {
async start(argv) {
this.logger_.info('Test app starting...');
if (!argv.includes('--profile')) {
@@ -758,7 +737,7 @@ class TestApp extends BaseApplication {
this.logger_.info('Test app started...');
}
async generalMiddleware(store: any, next: any, action: any) {
async generalMiddleware(store, next, action) {
this.middlewareCalls_.push(true);
try {
await super.generalMiddleware(store, next, action);
@@ -792,4 +771,4 @@ class TestApp extends BaseApplication {
}
}
module.exports = { exportDir, newPluginService, newPluginScript, synchronizerStart, afterEachCleanUp, syncTargetName, setSyncTargetName, syncDir, createTempDir, isNetworkSyncTarget, kvStore, expectThrow, logger, expectNotThrow, resourceService, resourceFetcher, tempFilePath, allSyncTargetItemsEncrypted, msleep, setupDatabase, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync, checkThrow, encryptionService, loadEncryptionMasterKey, fileContentEqual, decryptionWorker, currentClientId, id, ids, sortedIds, at, createNTestNotes, createNTestFolders, createNTestTags, TestApp };
module.exports = { newPluginService, newPluginScript, synchronizerStart, afterEachCleanUp, syncTargetName, setSyncTargetName, syncDir, createTempDir, isNetworkSyncTarget, kvStore, expectThrow, logger, expectNotThrow, resourceService, resourceFetcher, tempFilePath, allSyncTargetItemsEncrypted, msleep, setupDatabase, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync, checkThrow, encryptionService, loadEncryptionMasterKey, fileContentEqual, decryptionWorker, currentClientId, id, ids, sortedIds, at, createNTestNotes, createNTestFolders, createNTestTags, TestApp };

View File

@@ -85,6 +85,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
}
editorRef.current.setSelections(newSelections);
}
editorRef.current.focus();
}, []);
const addListItem = useCallback((string1, defaultText = '') => {
@@ -96,6 +97,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
} else {
wrapSelectionWithStrings(string1, '', defaultText);
}
editorRef.current.focus();
}
}, [wrapSelectionWithStrings]);
@@ -139,7 +141,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
} else {
reg.logger().warn('CodeMirror: unsupported drop item: ', cmd);
}
} else if (cmd.name === 'editor.focus') {
} else if (cmd.name === 'focus') {
editorRef.current.focus();
} else {
commandProcessed = false;
@@ -168,7 +170,6 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
textItalic: () => wrapSelectionWithStrings('*', '*', _('emphasised text')),
textLink: async () => {
const url = await dialogs.prompt(_('Insert Hyperlink'));
editorRef.current.focus();
if (url) wrapSelectionWithStrings('[', `](${url})`);
},
textCode: () => {

View File

@@ -247,7 +247,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
if (cmd.name === 'insertText') {
const result = await markupToHtml.current(MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN, cmd.value, { bodyOnly: true });
editor.insertContent(result.html);
} else if (cmd.name === 'editor.focus') {
} else if (cmd.name === 'focus') {
editor.focus();
} else if (cmd.name === 'dropItems') {
if (cmd.value.type === 'notes') {

View File

@@ -283,6 +283,10 @@ function NoteEditor(props: NoteEditorProps) {
const onMessage = useMessageHandler(scrollWhenReady, setScrollWhenReady, editorRef, setLocalSearchResultCount, props.dispatch, formNote);
const introductionPostLinkClick = useCallback(() => {
bridge().openExternal('https://www.patreon.com/posts/34246624');
}, []);
const externalEditWatcher_noteChange = useCallback((event) => {
if (event.id === formNote.id) {
const newFormNote = {
@@ -410,19 +414,9 @@ function NoteEditor(props: NoteEditorProps) {
throw new Error(`Invalid editor: ${props.bodyEditor}`);
}
const onRichTextReadMoreLinkClick = useCallback(() => {
bridge().openExternal('https://joplinapp.org/rich_text_editor');
}, []);
const onRichTextDismissLinkClick = useCallback(() => {
Setting.setValue('richTextBannerDismissed', true);
}, []);
const wysiwygBanner = props.bodyEditor !== 'TinyMCE' || props.richTextBannerDismissed ? null : (
<div style={styles.warningBanner}>
{_('This Rich Text editor has a number of limitations and it is recommended to be aware of them before using it.')}
&nbsp;&nbsp;<a onClick={onRichTextReadMoreLinkClick} style={styles.warningBannerLink} href="#">[ {_('Read more about it')} ]</a>
&nbsp;&nbsp;<a onClick={onRichTextDismissLinkClick} style={styles.warningBannerLink} href="#">[ {_('Dismiss')} ]</a>
const wysiwygBanner = props.bodyEditor !== 'TinyMCE' ? null : (
<div style={{ ...styles.warningBanner }}>
This is an experimental Rich Text editor for evaluation only. Please do not use with important notes as you may lose some data! See the <a style={styles.urlColor} onClick={introductionPostLinkClick} href="#">introduction post</a> for more information. To switch to the Markdown Editor please press the "Toggle editors" in the top right-hand corner.
</div>
);
@@ -573,7 +567,6 @@ const mapStateToProps = (state: AppState) => {
editorNoteStatuses: state.editorNoteStatuses,
syncStarted: state.syncStarted,
themeId: state.settings.theme,
richTextBannerDismissed: state.settings.richTextBannerDismissed,
watchedNoteFiles: state.watchedNoteFiles,
notesParentType: state.notesParentType,
selectedNoteTags: state.selectedNoteTags,

View File

@@ -57,7 +57,6 @@ function styles_(props: Props) {
fontSize: Math.round(theme.textStyle.fontSize * 1.5),
backgroundColor: theme.backgroundColor,
border: 'none',
width: '100%',
},
titleDate: {

View File

@@ -92,9 +92,6 @@ const declarations: CommandDeclaration[] = [
{
name: 'editor.setText',
},
{
name: 'editor.focus',
},
];
export default declarations;

View File

@@ -10,7 +10,7 @@ export const declaration: CommandDeclaration = {
export const runtime = (comp: any): CommandRuntime => {
return {
execute: async () => {
comp.editorRef.current.execCommand({ name: 'editor.focus' });
comp.editorRef.current.execCommand({ name: 'focus' });
},
enabledCondition: 'oneNoteSelected',
};

View File

@@ -33,16 +33,9 @@ export default function styles(props: NoteEditorProps) {
fontFamily: theme.fontFamily,
padding: 10,
fontSize: theme.fontSize,
lineHeight: '1.6em',
marginTop: 5,
marginBottom: 5,
},
warningBannerLink: {
color: theme.color,
fontFamily: theme.fontFamily,
fontSize: theme.fontSize,
fontWeight: 'bold',
},
tinyMCE: {
width: '100%',
height: '100%',
@@ -62,7 +55,7 @@ export default function styles(props: NoteEditorProps) {
padding: 10,
marginLeft: 5,
marginBottom: 10,
color: theme.colorWarn,
backgroundColor: theme.warningBackgroundColor,
},
resourceWatchBannerLine: {

View File

@@ -34,7 +34,6 @@ export interface NoteEditorProps {
plugins: PluginStates;
toolbarButtonInfos: ToolbarButtonInfo[];
setTagsToolbarButtonInfo: ToolbarButtonInfo;
richTextBannerDismissed: boolean;
}
export interface NoteBodyEditorProps {

View File

@@ -161,7 +161,7 @@ export default function useFormNote(dependencies: HookDependencies) {
if (Setting.value(focusSettingName) === 'title') {
if (titleInputRef.current) titleInputRef.current.focus();
} else {
if (editorRef.current) editorRef.current.execCommand({ name: 'editor.focus' });
if (editorRef.current) editorRef.current.execCommand({ name: 'focus' });
}
});
}

View File

@@ -1,6 +1,6 @@
import { LayoutItem, LayoutItemDirection } from './types';
import validateLayout from './validateLayout';
import { canMove, MoveDirection, moveHorizontal, moveVertical } from './movements';
import { canMove, ItemSide, MoveDirection, moveHorizontal, moveItemNear, moveVertical } from './movements';
import findItemByKey from './findItemByKey';
describe('movements', () => {
@@ -225,4 +225,37 @@ describe('movements', () => {
expect(layout.children[1].width).toBe(undefined);
});
test('should move an item near another one', () => {
let layout: LayoutItem = validateLayout({
key: 'root',
width: 200,
height: 100,
direction: LayoutItemDirection.Row,
children: [
{
key: 'col1',
resizableRight: true,
direction: LayoutItemDirection.Column,
children: [
{ key: 'item1' },
{ key: 'item2' },
],
},
{
key: 'col2',
},
],
});
// If parent is a ROW and putting item on top of sibling - first move to
// the left, then move right so that item goes inside new container
layout = moveItemNear(layout, 'item1', 'col2', ItemSide.Top);
console.info(layout);
// expect(layout.children[0].width).toBe(20);
// expect(layout.children[1].width).toBe(undefined);
});
});

View File

@@ -3,6 +3,8 @@ import { LayoutItem, LayoutItemDirection, tempContainerPrefix } from './types';
import produce from 'immer';
import uuid from '@joplin/lib/uuid';
import validateLayout from './validateLayout';
import findItemByKey from './findItemByKey';
import removeItem from './removeItem';
export enum MoveDirection {
Up = 'up',
@@ -11,6 +13,13 @@ export enum MoveDirection {
Right = 'right',
}
export enum ItemSide {
Top = 'top',
Bottom = 'bottom',
Left = 'left',
Right = 'right',
}
enum MovementDirection {
Horizontal = 1,
Vertical = 2,
@@ -188,3 +197,42 @@ export function move(layout: LayoutItem, key: string, direction: MoveDirection):
if (direction === MoveDirection.Right) return moveHorizontal(layout, key, +1);
throw new Error('Unreachable');
}
export function moveItemNear(layout:LayoutItem, itemKey:string, nearItemKey:string, side: ItemSide): LayoutItem {
const itemToInsert = findItemByKey(layout, itemKey);
const layoutWithoutItem = removeItem(layout, itemKey);
let moveDirection:MoveDirection = null;
const sideToDirectionMap = {
[ItemSide.Top]: MoveDirection.Up,
[ItemSide.Bottom]: MoveDirection.Down,
[ItemSide.Left]: MoveDirection.Left,
[ItemSide.Right]: MoveDirection.Right,
};
const layoutWithInsertedItem = produce(layoutWithoutItem, (draft: any) => {
iterateItems(draft, (itemIndex: number, item: LayoutItem, parent: LayoutItem) => {
if (item.key !== nearItemKey) return true;
parent.children.splice(itemIndex, 0, itemToInsert);
if (
parent.direction === LayoutItemDirection.Column && side === ItemSide.Top ||
parent.direction === LayoutItemDirection.Row && side === ItemSide.Left
) {
// Nothing to do - already in position
} else {
moveDirection = sideToDirectionMap[side];
}
return false;
});
});
console.info(JSON.stringify(layoutWithInsertedItem, null, 4));
console.info('MOVE', moveDirection);
const updatedLayout = moveDirection ? move(layoutWithInsertedItem, itemKey, moveDirection) : layoutWithInsertedItem;
return validateLayout(updatedLayout);
}

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "1.5.4",
"version": "1.5.3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -2119,7 +2119,6 @@
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
"dev": true,
"requires": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
@@ -2129,7 +2128,6 @@
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"dev": true,
"requires": {
"react-is": "^16.7.0"
}
@@ -2206,14 +2204,12 @@
"@types/prop-types": {
"version": "15.7.3",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
"integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==",
"dev": true
"integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw=="
},
"@types/react": {
"version": "16.9.55",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.55.tgz",
"integrity": "sha512-6KLe6lkILeRwyyy7yG9rULKJ0sXplUsl98MGoCfpteXf9sPWFWWMknDcsvubcpaTdBuxtsLF6HDUwdApZL/xIg==",
"dev": true,
"requires": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
@@ -2222,8 +2218,7 @@
"csstype": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.4.tgz",
"integrity": "sha512-xc8DUsCLmjvCfoD7LTGE0ou2MIWLx0K9RCZwSHMOdynqRsP4MtUcLeqh1HcQ2dInwDTqn+3CE0/FZh1et+p4jA==",
"dev": true
"integrity": "sha512-xc8DUsCLmjvCfoD7LTGE0ou2MIWLx0K9RCZwSHMOdynqRsP4MtUcLeqh1HcQ2dInwDTqn+3CE0/FZh1et+p4jA=="
}
}
},
@@ -2231,7 +2226,6 @@
"version": "0.63.34",
"resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.63.34.tgz",
"integrity": "sha512-6syTIfUt+DY4mJBoO0Y5i4jsDg0I7v31XIPRgsJZAlHeMY9p9GehtGd4VpQKB/NgzLiGzx9ahJPE8w+0lt/WxA==",
"dev": true,
"requires": {
"@types/react": "*"
}
@@ -2312,7 +2306,6 @@
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.4.tgz",
"integrity": "sha512-78f5Zuy0v/LTQNOYfpH+CINHpchzMMmAt9amY2YNtSgsk1TmlKm8L2Wijss/mtTrsUAVTm2CdGB8VOM65vA8xg==",
"dev": true,
"requires": {
"@types/hoist-non-react-statics": "*",
"@types/react": "*",
@@ -2323,8 +2316,7 @@
"csstype": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.4.tgz",
"integrity": "sha512-xc8DUsCLmjvCfoD7LTGE0ou2MIWLx0K9RCZwSHMOdynqRsP4MtUcLeqh1HcQ2dInwDTqn+3CE0/FZh1et+p4jA==",
"dev": true
"integrity": "sha512-xc8DUsCLmjvCfoD7LTGE0ou2MIWLx0K9RCZwSHMOdynqRsP4MtUcLeqh1HcQ2dInwDTqn+3CE0/FZh1et+p4jA=="
}
}
},

View File

@@ -1,14 +1,13 @@
{
"name": "@joplin/app-desktop",
"version": "1.5.4",
"version": "1.5.3",
"description": "Joplin for Desktop",
"main": "main.js",
"private": true,
"scripts": {
"dist": "npm run electronRebuild && npx electron-builder",
"dist": "node_modules/.bin/electron-builder",
"build": "gulp build",
"postinstall": "npm run build",
"electronRebuild": "gulp electronRebuild",
"postinstall": "npm run build && gulp electronRebuild",
"tsc": "node node_modules/typescript/bin/tsc --project tsconfig.json",
"watch": "node node_modules/typescript/bin/tsc --watch --project tsconfig.json",
"start": "gulp build && electron . --env dev --log-level debug --no-welcome --open-dev-tools",
@@ -99,7 +98,6 @@
"@types/node": "^14.14.6",
"@types/react": "16.9.55",
"@types/react-redux": "^7.1.9",
"@types/styled-components": "^5.1.4",
"ajv": "^6.5.0",
"app-builder-bin": "^1.9.11",
"babel-cli": "^6.26.0",
@@ -124,6 +122,7 @@
"@fortawesome/fontawesome-free": "^5.13.0",
"@joplin/lib": "^1.0.9",
"@joplin/renderer": "^1.0.17",
"@types/styled-components": "^5.1.4",
"async-mutex": "^0.1.3",
"codemirror": "^5.56.0",
"color": "^3.1.2",

View File

@@ -2,24 +2,6 @@ const fs = require('fs');
const path = require('path');
const electron_notarize = require('electron-notarize');
function execCommand(command) {
const exec = require('child_process').exec;
return new Promise((resolve, reject) => {
exec(command, (error, stdout, stderr) => {
if (error) {
if (error.signal == 'SIGTERM') {
resolve('Process was killed');
} else {
reject(new Error([stdout.trim(), stderr.trim()].join('\n')));
}
} else {
resolve([stdout.trim(), stderr.trim()].join('\n'));
}
});
});
}
module.exports = async function(params) {
if (process.platform !== 'darwin') return;
@@ -66,16 +48,5 @@ module.exports = async function(params) {
ascProvider: process.env.APPLE_ASC_PROVIDER,
});
// It appears that electron-notarize doesn't staple the app, but without
// this we were still getting the malware warning when launching the app.
// Stapling the app means attaching the notarization ticket to it, so that
// if the user is offline, macOS can still check if the app was notarized.
// So it seems to be more or less optional, but at least in our case it
// wasn't.
console.log('Staple notarization ticket to the app...');
const staplerCmd = `xcrun stapler staple "${appPath}"`;
console.log(`> ${staplerCmd}`);
console.log(await execCommand(staplerCmd));
console.log(`Done notarizing ${appId}`);
};

View File

@@ -14,7 +14,6 @@ import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import net.cozic.joplin.share.SharePackage;
import android.webkit.WebView;
public class MainApplication extends Application implements ReactApplication {
@@ -69,15 +68,6 @@ public class MainApplication extends Application implements ReactApplication {
SoLoader.init(this, /* native exopackage */ false);
initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
// To allow debugging the webview using the Chrome developer tools.
// Open chrome://inspect/#devices to view the device and connect to it
// IMPORTANT: USB debugging must be enabled on the device for it to work.
// https://github.com/react-native-webview/react-native-webview/blob/master/docs/Debugging.md
if (BuildConfig.DEBUG) {
WebView.setWebContentsDebuggingEnabled(true);
}
}
/**

View File

@@ -15,6 +15,14 @@ interface UseSourceResult {
injectedJs: string[];
}
let markupToHtml_: any = null;
function markupToHtml() {
if (markupToHtml_) return markupToHtml_;
markupToHtml_ = markupLanguageUtils.newMarkupToHtml();
return markupToHtml_;
}
function usePrevious(value: any, initialValue: any = null): any {
const ref = useRef(initialValue);
useEffect(() => {
@@ -37,10 +45,6 @@ export default function useSource(noteBody: string, noteMarkupLanguage: number,
};
}, [themeId, paddingBottom]);
const markupToHtml = useMemo(() => {
return markupLanguageUtils.newMarkupToHtml();
}, [isFirstRender]);
// To address https://github.com/laurent22/joplin/issues/433
//
// If a checkbox in a note is ticked, the body changes, which normally
@@ -54,7 +58,7 @@ export default function useSource(noteBody: string, noteMarkupLanguage: number,
//
// IMPORTANT: KEEP noteBody AS THE FIRST dependency in the array as the
// below logic rely on this.
const effectDependencies = [noteBody, resourceLoadedTime, noteMarkupLanguage, themeId, rendererTheme, highlightedKeywords, noteResources, noteHash, isFirstRender, markupToHtml];
const effectDependencies = [noteBody, resourceLoadedTime, noteMarkupLanguage, themeId, rendererTheme, highlightedKeywords, noteResources, noteHash, isFirstRender];
const previousDeps = usePrevious(effectDependencies, []);
const changedDeps = effectDependencies.reduce((accum: any, dependency: any, index: any) => {
if (dependency !== previousDeps[index]) {
@@ -90,9 +94,9 @@ export default function useSource(noteBody: string, noteMarkupLanguage: number,
// it doesn't contain info about the resource download state. Because of that, if we were to use the markupToHtml() cache
// it wouldn't re-render at all. We don't need this cache in any way because this hook is only triggered when we know
// something has changed.
markupToHtml.clearCache(noteMarkupLanguage);
markupToHtml().clearCache(noteMarkupLanguage);
const result = await markupToHtml.render(
const result = await markupToHtml().render(
noteMarkupLanguage,
bodyToRender,
rendererTheme,

View File

@@ -1,5 +1,5 @@
const RNFS = require('react-native-fs');
const FsDriverBase = require('@joplin/lib/fs-driver-base').default;
const FsDriverBase = require('@joplin/lib/fs-driver-base');
const RNFetchBlob = require('rn-fetch-blob').default;
class FsDriverRN extends FsDriverBase {

View File

@@ -1,9 +1,9 @@
const TurndownService = require('@joplin/turndown');
const turndownPluginGfm = require('@joplin/turndown-plugin-gfm').gfm;
const TurndownService = require('joplin-turndown');
const markdownUtils = require('./markdownUtils').default;
class HtmlToMd {
parse(html, options = {}) {
const turndownPluginGfm = require('joplin-turndown-plugin-gfm').gfm;
const turndown = new TurndownService({
headingStyle: 'atx',
anchorNames: options.anchorNames ? options.anchorNames.map(n => n.trim().toLowerCase()) : [],
@@ -12,7 +12,6 @@ class HtmlToMd {
bulletListMarker: '-',
emDelimiter: '*',
strongDelimiter: '**',
br: '',
});
turndown.use(turndownPluginGfm);
turndown.remove('script');

View File

@@ -121,10 +121,6 @@ export default class Synchronizer {
}
}
private static reportHasErrors(report: any): boolean {
return !!report && !!report.errors && !!report.errors.length;
}
static reportToLines(report: any) {
const lines = [];
if (report.createLocal) lines.push(_('Created local items: %d.', report.createLocal));
@@ -136,7 +132,7 @@ export default class Synchronizer {
if (report.fetchingTotal && report.fetchingProcessed) lines.push(_('Fetched items: %d/%d.', report.fetchingProcessed, report.fetchingTotal));
if (report.cancelling && !report.completedTime) lines.push(_('Cancelling...'));
if (report.completedTime) lines.push(_('Completed: %s', time.formatMsToLocal(report.completedTime)));
if (this.reportHasErrors(report)) lines.push(_('Last error: %s', report.errors[report.errors.length - 1].toString().substr(0, 500)));
if (report.errors && report.errors.length) lines.push(_('Last error: %s', report.errors[report.errors.length - 1].toString().substr(0, 500)));
return lines;
}
@@ -197,7 +193,7 @@ export default class Synchronizer {
this.logger().info(`Total notes: ${noteCount}`);
this.logger().info(`Total resources: ${resourceCount}`);
if (Synchronizer.reportHasErrors(report)) {
if (report.errors && report.errors.length) {
this.logger().warn('There was some errors:');
for (let i = 0; i < report.errors.length; i++) {
const e = report.errors[i];
@@ -319,7 +315,6 @@ export default class Synchronizer {
const outputContext = Object.assign({}, lastContext);
this.dispatch({ type: 'SYNC_STARTED' });
eventManager.emit('syncStart');
this.logSyncOperation('starting', null, null, `Starting synchronisation to target ${syncTargetId}... [${synchronizationId}]`);
@@ -904,14 +899,11 @@ export default class Synchronizer {
await this.logSyncSummary(this.progressReport_);
eventManager.emit('syncComplete', {
withErrors: Synchronizer.reportHasErrors(this.progressReport_),
});
this.onProgress_ = function() {};
this.progressReport_ = {};
this.dispatch({ type: 'SYNC_COMPLETED', isFullSync: this.isFullSync(syncSteps) });
eventManager.emit('syncComplete');
this.state_ = 'idle';

View File

@@ -1,48 +1,15 @@
import time from './time';
import Setting from './models/Setting';
import { filename, fileExtension } from './path-utils';
const { filename, fileExtension } = require('./path-utils');
const time = require('./time').default;
const Setting = require('./models/Setting').default;
const md5 = require('md5');
export interface Stat {
birthtime: number;
mtime: number;
isDirectory(): boolean;
path: string;
size: number;
}
export interface ReadDirStatsOptions {
recursive: boolean;
}
export default class FsDriverBase {
public async stat(_path: string): Promise<Stat> {
throw new Error('Not implemented');
}
public async readDirStats(_path: string, _options: ReadDirStatsOptions = null): Promise<Stat[]> {
throw new Error('Not implemented');
}
public async exists(_path: string): Promise<boolean> {
throw new Error('Not implemented');
}
public async remove(_path: string): Promise<void> {
throw new Error('Not implemented');
}
public async isDirectory(path: string) {
class FsDriverBase {
async isDirectory(path) {
const stat = await this.stat(path);
return !stat ? false : stat.isDirectory();
}
public async writeFile(_path: string, _content: string, _encoding: string = 'base64'): Promise<void> {
throw new Error('Not implemented');
}
protected async readDirStatsHandleRecursion_(basePath: string, stat: Stat, output: Stat[], options: ReadDirStatsOptions): Promise<Stat[]> {
async readDirStatsHandleRecursion_(basePath, stat, output, options) {
if (options.recursive && stat.isDirectory()) {
const subPath = `${basePath}/${stat.path}`;
const subStats = await this.readDirStats(subPath, options);
@@ -56,7 +23,7 @@ export default class FsDriverBase {
return output;
}
public async findUniqueFilename(name: string, reservedNames: string[] = null): Promise<string> {
async findUniqueFilename(name, reservedNames = null) {
if (reservedNames === null) {
reservedNames = [];
}
@@ -80,7 +47,7 @@ export default class FsDriverBase {
}
}
public async removeAllThatStartWith(dirPath: string, filenameStart: string) {
async removeAllThatStartWith(dirPath, filenameStart) {
if (!filenameStart || !dirPath) throw new Error('dirPath and filenameStart cannot be empty');
const stats = await this.readDirStats(dirPath);
@@ -92,7 +59,7 @@ export default class FsDriverBase {
}
}
public async waitTillExists(path: string, timeout: number = 10000) {
async waitTillExists(path, timeout = 10000) {
const startTime = Date.now();
while (true) {
@@ -105,7 +72,7 @@ export default class FsDriverBase {
// TODO: move out of here and make it part of joplin-renderer
// or assign to option using .bind(fsDriver())
public async cacheCssToFile(cssStrings: string[]) {
async cacheCssToFile(cssStrings) {
const cssString = Array.isArray(cssStrings) ? cssStrings.join('\n') : cssStrings;
const cssFilePath = `${Setting.value('tempDir')}/${md5(escape(cssString))}.css`;
if (!(await this.exists(cssFilePath))) {
@@ -117,5 +84,6 @@ export default class FsDriverBase {
mime: 'text/css',
};
}
}
module.exports = FsDriverBase;

View File

@@ -1,8 +1,8 @@
import { resolve as nodeResolve } from 'path';
import FsDriverBase, { Stat } from './fs-driver-base';
import time from './time';
const fs = require('fs-extra');
const time = require('./time').default;
const FsDriverBase = require('./fs-driver-base');
export default class FsDriverNode extends FsDriverBase {
@@ -125,7 +125,7 @@ export default class FsDriverNode extends FsDriverBase {
throw this.fsErrorToJsError_(error);
}
let output: Stat[] = [];
let output = [];
for (let i = 0; i < items.length; i++) {
const item = items[i];
const stat = await this.stat(`${path}/${item}`);

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

@@ -1,5 +1,5 @@
var locales = {};
var stats = {};
const locales = {};
const stats = {};
locales['en_GB'] = require('./en_GB.json');
locales['ar'] = require('./ar.json');
locales['bg_BG'] = require('./bg_BG.json');
@@ -15,7 +15,6 @@ locales['es_ES'] = require('./es_ES.json');
locales['et_EE'] = require('./et_EE.json');
locales['eu'] = require('./eu.json');
locales['fa'] = require('./fa.json');
locales['fi_FI'] = require('./fi_FI.json');
locales['fr_FR'] = require('./fr_FR.json');
locales['gl_ES'] = require('./gl_ES.json');
locales['hr_HR'] = require('./hr_HR.json');
@@ -39,43 +38,42 @@ locales['tr_TR'] = require('./tr_TR.json');
locales['vi'] = require('./vi.json');
locales['zh_CN'] = require('./zh_CN.json');
locales['zh_TW'] = require('./zh_TW.json');
stats['ar'] = {"percentDone":77};
stats['eu'] = {"percentDone":33};
stats['bs_BA'] = {"percentDone":79};
stats['bg_BG'] = {"percentDone":64};
stats['ca'] = {"percentDone":92};
stats['hr_HR'] = {"percentDone":26};
stats['cs_CZ'] = {"percentDone":96};
stats['da_DK'] = {"percentDone":79};
stats['de_DE'] = {"percentDone":96};
stats['et_EE'] = {"percentDone":63};
stats['en_GB'] = {"percentDone":100};
stats['en_US'] = {"percentDone":100};
stats['es_ES'] = {"percentDone":96};
stats['eo'] = {"percentDone":36};
stats['fi_FI'] = {"percentDone":97};
stats['fr_FR'] = {"percentDone":96};
stats['gl_ES'] = {"percentDone":42};
stats['id_ID'] = {"percentDone":88};
stats['it_IT'] = {"percentDone":97};
stats['nl_BE'] = {"percentDone":33};
stats['nl_NL'] = {"percentDone":96};
stats['nb_NO'] = {"percentDone":85};
stats['fa'] = {"percentDone":79};
stats['pl_PL'] = {"percentDone":95};
stats['pt_BR'] = {"percentDone":93};
stats['pt_PT'] = {"percentDone":95};
stats['ro'] = {"percentDone":74};
stats['sl_SI'] = {"percentDone":41};
stats['sv'] = {"percentDone":68};
stats['th_TH'] = {"percentDone":50};
stats['vi'] = {"percentDone":82};
stats['tr_TR'] = {"percentDone":94};
stats['el_GR'] = {"percentDone":92};
stats['ru_RU'] = {"percentDone":92};
stats['sr_RS'] = {"percentDone":69};
stats['zh_CN'] = {"percentDone":97};
stats['zh_TW'] = {"percentDone":91};
stats['ja_JP'] = {"percentDone":97};
stats['ko'] = {"percentDone":97};
module.exports = { locales: locales, stats: stats };
stats['ar'] = { 'percentDone': 78 };
stats['eu'] = { 'percentDone': 33 };
stats['bs_BA'] = { 'percentDone': 82 };
stats['bg_BG'] = { 'percentDone': 65 };
stats['ca'] = { 'percentDone': 95 };
stats['hr_HR'] = { 'percentDone': 27 };
stats['cs_CZ'] = { 'percentDone': 98 };
stats['da_DK'] = { 'percentDone': 72 };
stats['de_DE'] = { 'percentDone': 97 };
stats['et_EE'] = { 'percentDone': 65 };
stats['en_GB'] = { 'percentDone': 100 };
stats['en_US'] = { 'percentDone': 100 };
stats['es_ES'] = { 'percentDone': 98 };
stats['eo'] = { 'percentDone': 37 };
stats['fr_FR'] = { 'percentDone': 98 };
stats['gl_ES'] = { 'percentDone': 42 };
stats['id_ID'] = { 'percentDone': 92 };
stats['it_IT'] = { 'percentDone': 98 };
stats['nl_NL'] = { 'percentDone': 94 };
stats['nl_BE'] = { 'percentDone': 33 };
stats['nb_NO'] = { 'percentDone': 87 };
stats['fa'] = { 'percentDone': 82 };
stats['pl_PL'] = { 'percentDone': 97 };
stats['pt_PT'] = { 'percentDone': 98 };
stats['pt_BR'] = { 'percentDone': 96 };
stats['ro'] = { 'percentDone': 77 };
stats['sl_SI'] = { 'percentDone': 41 };
stats['sv'] = { 'percentDone': 70 };
stats['th_TH'] = { 'percentDone': 52 };
stats['vi'] = { 'percentDone': 84 };
stats['tr_TR'] = { 'percentDone': 97 };
stats['el_GR'] = { 'percentDone': 95 };
stats['ru_RU'] = { 'percentDone': 94 };
stats['sr_RS'] = { 'percentDone': 70 };
stats['zh_CN'] = { 'percentDone': 95 };
stats['zh_TW'] = { 'percentDone': 94 };
stats['ja_JP'] = { 'percentDone': 99 };
stats['ko'] = { 'percentDone': 99 };
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

View File

@@ -171,7 +171,7 @@ class Note extends BaseItem {
useAbsolutePaths: false,
}, options);
let pathsToTry = [];
const pathsToTry = [];
if (options.useAbsolutePaths) {
pathsToTry.push(`file://${Setting.value('resourceDir')}`);
pathsToTry.push(`file://${shim.pathRelativeToCwd(Setting.value('resourceDir'))}`);
@@ -179,21 +179,6 @@ class Note extends BaseItem {
pathsToTry.push(Resource.baseRelativeDirectoryPath());
}
// We support both the escaped and unescaped versions because both
// of those paths are valid:
//
// [](file://C:/I like spaces in paths/abcdefg.jpg)
// [](file://C:/I%20like%20spaces%20in%20paths/abcdefg.jpg)
//
// https://discourse.joplinapp.org/t/12986/4
const temp = [];
for (const p of pathsToTry) {
temp.push(p);
temp.push(markdownUtils.escapeLinkUrl(p));
}
pathsToTry = temp;
this.logger().debug('replaceResourceExternalToInternalLinks', 'options:', options, 'pathsToTry:', pathsToTry);
for (const basePath of pathsToTry) {

View File

@@ -350,8 +350,6 @@ class Setting extends BaseModel {
// to the last folder that was selected.
activeFolderId: { value: '', type: SettingItemType.String, public: false },
richTextBannerDismissed: { value: false, type: SettingItemType.Bool, public: false },
firstStart: { value: true, type: SettingItemType.Bool, public: false },
locale: {
value: defaultLocale(),
@@ -596,9 +594,6 @@ class Setting extends BaseModel {
'markdown.plugin.fountain': { value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable Fountain syntax support')}${wysiwygYes}` },
'markdown.plugin.mermaid': { value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable Mermaid diagrams support')}${wysiwygYes}` },
'markdown.plugin.audioPlayer': { value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable audio player')}${wysiwygNo}` },
'markdown.plugin.videoPlayer': { value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable video player')}${wysiwygNo}` },
'markdown.plugin.pdfViewer': { value: !mobilePlatform, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['desktop'], label: () => `${_('Enable PDF viewer')}${wysiwygNo}` },
'markdown.plugin.mark': { value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable ==mark== syntax')}${wysiwygNo}` },
'markdown.plugin.footnote': { value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable footnotes')}${wysiwygNo}` },
'markdown.plugin.toc': { value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable table of contents extension')}${wysiwygNo}` },

File diff suppressed because it is too large Load Diff

View File

@@ -17,16 +17,14 @@
},
"devDependencies": {
"@types/jest": "^26.0.15",
"@types/node": "^14.14.6",
"jest": "^26.6.3",
"@types/node": "^14.14.6",
"typescript": "^4.0.5"
},
"dependencies": {
"@joplin/fork-htmlparser2": "^4.1.8",
"@joplin/fork-sax": "^1.2.12",
"@joplin/renderer": "^1.0.17",
"@joplin/turndown": "^4.0.30",
"@joplin/turndown-plugin-gfm": "^1.0.12",
"async-mutex": "^0.1.3",
"aws-sdk": "^2.588.0",
"base-64": "^0.1.0",
@@ -35,6 +33,7 @@
"chokidar": "^3.4.3",
"color": "3.1.2",
"compare-versions": "^3.6.0",
"diacritics": "^1.3.0",
"diff-match-patch": "^1.0.4",
"es6-promise-pool": "^2.5.0",
"file-uri-to-path": "^1.0.0",
@@ -46,6 +45,8 @@
"image-data-uri": "^2.0.0",
"image-type": "^3.0.0",
"immer": "^7.0.14",
"joplin-turndown": "^4.0.30",
"joplin-turndown-plugin-gfm": "^1.0.12",
"levenshtein": "^1.0.5",
"lodash": "^4.17.20",
"markdown-it": "^10.0.0",

View File

@@ -1,16 +1,274 @@
import eventManager from '../eventManager';
import shim from '../shim';
import { _ } from '../locale';
import keysRegExp from './KeymapService_keysRegExp';
import keycodeToElectronMap from './KeymapService_keycodeToElectronMap';
const BaseService = require('./BaseService').default;
const keysRegExp = /^([0-9A-Z)!@#$%^&*(:+<_>?~{|}";=,\-./`[\\\]']|F1*[1-9]|F10|F2[0-4]|Plus|Space|Tab|Backspace|Delete|Insert|Return|Enter|Up|Down|Left|Right|Home|End|PageUp|PageDown|Escape|Esc|VolumeUp|VolumeDown|VolumeMute|MediaNextTrack|MediaPreviousTrack|MediaStop|MediaPlayPause|PrintScreen|Numlock|Scrolllock|Capslock|num([0-9]|dec|add|sub|mult|div))$/;
const modifiersRegExp = {
darwin: /^(Ctrl|Option|Shift|Cmd)$/,
default: /^(Ctrl|Alt|AltGr|Shift|Super)$/,
};
const keycodeToElectronMap = [
'', // [0]
'', // [1]
'', // [2]
'', // [3]
'', // [4]
'', // [5]
'', // [6]
'', // [7]
'Backspace', // [8]
'Tab', // [9]
'', // [10]
'', // [11]
'Clear', // [12]
'Enter', // [13]
'', // [14]
'', // [15]
'Shift', // [16]
'Ctrl', // [17]
'Alt', // [18]
'', // [19]
'Capslock', // [20]
'', // [21]
'', // [22]
'', // [23]
'', // [24]
'', // [25]
'', // [26]
'Esc', // [27]
'', // [28]
'', // [29]
'', // [30]
'', // [31]
'Space', // [32]
'PageUp', // [33]
'PageDown', // [34]
'End', // [35]
'Home', // [36]
'Left', // [37]
'Up', // [38]
'Right', // [39]
'Down', // [40]
'', // [41]
'', // [42]
'', // [43]
'PrintScreen', // [44]
'Insert', // [45]
'Delete', // [46]
'', // [47]
'0', // [48]
'1', // [49]
'2', // [50]
'3', // [51]
'4', // [52]
'5', // [53]
'6', // [54]
'7', // [55]
'8', // [56]
'9', // [57]
':', // [58]
';', // [59]
'<', // [60]
'=', // [61]
'>', // [62]
'?', // [63]
'@', // [64]
'A', // [65]
'B', // [66]
'C', // [67]
'D', // [68]
'E', // [69]
'F', // [70]
'G', // [71]
'H', // [72]
'I', // [73]
'J', // [74]
'K', // [75]
'L', // [76]
'M', // [77]
'N', // [78]
'O', // [79]
'P', // [80]
'Q', // [81]
'R', // [82]
'S', // [83]
'T', // [84]
'U', // [85]
'V', // [86]
'W', // [87]
'X', // [88]
'Y', // [89]
'Z', // [90]
'Super', // [91] Super: Windows Key (Windows) or Command Key (Mac)
'', // [92]
'ContextMenu', // [93]
'', // [94]
'', // [95]
'num0', // [96]
'num1', // [97]
'num2', // [98]
'num3', // [99]
'num4', // [100]
'num5', // [101]
'num6', // [102]
'num7', // [103]
'num8', // [104]
'num9', // [105]
'nummult', // [106] *
'numadd', // [107] +
'', // [108]
'numsub', // [109] -
'numdec', // [110]
'numdiv', // [111] ÷
'F1', // [112]
'F2', // [113]
'F3', // [114]
'F4', // [115]
'F5', // [116]
'F6', // [117]
'F7', // [118]
'F8', // [119]
'F9', // [120]
'F10', // [121]
'F11', // [122]
'F12', // [123]
'F13', // [124]
'F14', // [125]
'F15', // [126]
'F16', // [127]
'F17', // [128]
'F18', // [129]
'F19', // [130]
'F20', // [131]
'F21', // [132]
'F22', // [133]
'F23', // [134]
'F24', // [135]
'', // [136]
'', // [137]
'', // [138]
'', // [139]
'', // [140]
'', // [141]
'', // [142]
'', // [143]
'Numlock', // [144]
'Scrolllock', // [145]
'', // [146]
'', // [147]
'', // [148]
'', // [149]
'', // [150]
'', // [151]
'', // [152]
'', // [153]
'', // [154]
'', // [155]
'', // [156]
'', // [157]
'', // [158]
'', // [159]
'', // [160]
'!', // [161]
'"', // [162]
'#', // [163]
'$', // [164]
'%', // [165]
'&', // [166]
'_', // [167]
'(', // [168]
')', // [169]
'*', // [170]
'Plus', // [171]
'|', // [172]
'-', // [173]
'{', // [174]
'}', // [175]
'~', // [176]
'', // [177]
'', // [178]
'', // [179]
'', // [180]
'VolumeMute', // [181]
'VolumeDown', // [182]
'VolumeUp', // [183]
'', // [184]
'', // [185]
';', // [186]
'=', // [187]
',', // [188]
'-', // [189]
'.', // [190]
'/', // [191]
'`', // [192]
'', // [193]
'', // [194]
'', // [195]
'', // [196]
'', // [197]
'', // [198]
'', // [199]
'', // [200]
'', // [201]
'', // [202]
'', // [203]
'', // [204]
'', // [205]
'', // [206]
'', // [207]
'', // [208]
'', // [209]
'', // [210]
'', // [211]
'', // [212]
'', // [213]
'', // [214]
'', // [215]
'', // [216]
'', // [217]
'', // [218]
'[', // [219]
'\\', // [220]
']', // [221]
'\'', // [222]
'', // [223]
'', // [224]
'AltGr', // [225]
'', // [226]
'', // [227]
'', // [228]
'', // [229]
'', // [230]
'', // [231]
'', // [232]
'', // [233]
'', // [234]
'', // [235]
'', // [236]
'', // [237]
'', // [238]
'', // [239]
'', // [240]
'', // [241]
'', // [242]
'', // [243]
'', // [244]
'', // [245]
'', // [246]
'', // [247]
'', // [248]
'', // [249]
'', // [250]
'', // [251]
'', // [252]
'', // [253]
'', // [254]
'', // [255]
];
const defaultKeymapItems = {
darwin: [
{ accelerator: 'Cmd+N', command: 'newNote' },

View File

@@ -1,262 +0,0 @@
// This is the keycode map for Electron.
const keycodeToElectronMap = [
'', // [0]
'', // [1]
'', // [2]
'', // [3]
'', // [4]
'', // [5]
'', // [6]
'', // [7]
'Backspace', // [8]
'Tab', // [9]
'', // [10]
'', // [11]
'Clear', // [12]
'Enter', // [13]
'', // [14]
'', // [15]
'Shift', // [16]
'Ctrl', // [17]
'Alt', // [18]
'', // [19]
'Capslock', // [20]
'', // [21]
'', // [22]
'', // [23]
'', // [24]
'', // [25]
'', // [26]
'Esc', // [27]
'', // [28]
'', // [29]
'', // [30]
'', // [31]
'Space', // [32]
'PageUp', // [33]
'PageDown', // [34]
'End', // [35]
'Home', // [36]
'Left', // [37]
'Up', // [38]
'Right', // [39]
'Down', // [40]
'', // [41]
'', // [42]
'', // [43]
'PrintScreen', // [44]
'Insert', // [45]
'Delete', // [46]
'', // [47]
'0', // [48]
'1', // [49]
'2', // [50]
'3', // [51]
'4', // [52]
'5', // [53]
'6', // [54]
'7', // [55]
'8', // [56]
'9', // [57]
':', // [58]
';', // [59]
'<', // [60]
'=', // [61]
'>', // [62]
'?', // [63]
'@', // [64]
'A', // [65]
'B', // [66]
'C', // [67]
'D', // [68]
'E', // [69]
'F', // [70]
'G', // [71]
'H', // [72]
'I', // [73]
'J', // [74]
'K', // [75]
'L', // [76]
'M', // [77]
'N', // [78]
'O', // [79]
'P', // [80]
'Q', // [81]
'R', // [82]
'S', // [83]
'T', // [84]
'U', // [85]
'V', // [86]
'W', // [87]
'X', // [88]
'Y', // [89]
'Z', // [90]
'Super', // [91] Super: Windows Key (Windows) or Command Key (Mac)
'', // [92]
'ContextMenu', // [93]
'', // [94]
'', // [95]
'num0', // [96]
'num1', // [97]
'num2', // [98]
'num3', // [99]
'num4', // [100]
'num5', // [101]
'num6', // [102]
'num7', // [103]
'num8', // [104]
'num9', // [105]
'nummult', // [106] *
'numadd', // [107] +
'', // [108]
'numsub', // [109] -
'numdec', // [110]
'numdiv', // [111] ÷
'F1', // [112]
'F2', // [113]
'F3', // [114]
'F4', // [115]
'F5', // [116]
'F6', // [117]
'F7', // [118]
'F8', // [119]
'F9', // [120]
'F10', // [121]
'F11', // [122]
'F12', // [123]
'F13', // [124]
'F14', // [125]
'F15', // [126]
'F16', // [127]
'F17', // [128]
'F18', // [129]
'F19', // [130]
'F20', // [131]
'F21', // [132]
'F22', // [133]
'F23', // [134]
'F24', // [135]
'', // [136]
'', // [137]
'', // [138]
'', // [139]
'', // [140]
'', // [141]
'', // [142]
'', // [143]
'Numlock', // [144]
'Scrolllock', // [145]
'', // [146]
'', // [147]
'', // [148]
'', // [149]
'', // [150]
'', // [151]
'', // [152]
'', // [153]
'', // [154]
'', // [155]
'', // [156]
'', // [157]
'', // [158]
'', // [159]
'', // [160]
'!', // [161]
'"', // [162]
'#', // [163]
'$', // [164]
'%', // [165]
'&', // [166]
'_', // [167]
'(', // [168]
')', // [169]
'*', // [170]
'Plus', // [171]
'|', // [172]
'-', // [173]
'{', // [174]
'}', // [175]
'~', // [176]
'', // [177]
'', // [178]
'', // [179]
'', // [180]
'VolumeMute', // [181]
'VolumeDown', // [182]
'VolumeUp', // [183]
'', // [184]
'', // [185]
';', // [186]
'=', // [187]
',', // [188]
'-', // [189]
'.', // [190]
'/', // [191]
'`', // [192]
'', // [193]
'', // [194]
'', // [195]
'', // [196]
'', // [197]
'', // [198]
'', // [199]
'', // [200]
'', // [201]
'', // [202]
'', // [203]
'', // [204]
'', // [205]
'', // [206]
'', // [207]
'', // [208]
'', // [209]
'', // [210]
'', // [211]
'', // [212]
'', // [213]
'', // [214]
'', // [215]
'', // [216]
'', // [217]
'', // [218]
'[', // [219]
'\\', // [220]
']', // [221]
'\'', // [222]
'', // [223]
'', // [224]
'AltGr', // [225]
'', // [226]
'', // [227]
'', // [228]
'', // [229]
'', // [230]
'', // [231]
'', // [232]
'', // [233]
'', // [234]
'', // [235]
'', // [236]
'', // [237]
'', // [238]
'', // [239]
'', // [240]
'', // [241]
'', // [242]
'', // [243]
'', // [244]
'', // [245]
'', // [246]
'', // [247]
'', // [248]
'', // [249]
'', // [250]
'', // [251]
'', // [252]
'', // [253]
'', // [254]
'', // [255]
];
export default keycodeToElectronMap;

View File

@@ -1,9 +0,0 @@
// We move this regex outside KeymapService because it makes gettext parsing
// fail. In fact it doesn't fail at the regex itself but at the next backtick
// into the code. Probably their parser see a backtick in the regex and opens a
// JS template string, while it shouldn't.
// https://discourse.joplinapp.org/t/translations/12832?u=laurent
const keysRegExp = /^([0-9A-Z)!@#$%^&*(:+<_>?~{|}";=,\-./`[\\\]']|F1*[1-9]|F10|F2[0-4]|Plus|Space|Tab|Backspace|Delete|Insert|Return|Enter|Up|Down|Left|Right|Home|End|PageUp|PageDown|Escape|Esc|VolumeUp|VolumeDown|VolumeMute|MediaNextTrack|MediaPreviousTrack|MediaStop|MediaPlayPause|PrintScreen|Numlock|Scrolllock|Capslock|num([0-9]|dec|add|sub|mult|div))$/;
export default keysRegExp;

View File

@@ -23,7 +23,7 @@ export default class KvStore extends BaseService {
this.instance_ = null;
}
public constructor() {
private constructor() {
super();
this.incMutex_ = new Mutex();
}

View File

@@ -33,30 +33,6 @@ export default class ToolbarButtonUtils {
return this.service_;
}
// Editor commands will focus the editor after they're executed
private isEditorCommand(commandName: string) {
return (commandName.indexOf('editor.') === 0 ||
// These commands are grandfathered in, but in the future
// all editor commands should start with "editor."
// WARNING: Some commands such as textLink are not defined here
// because they are more complex and handle focus manually
commandName === 'textCopy' ||
commandName === 'textCut' ||
commandName === 'textPaste' ||
commandName === 'textSelectAll' ||
commandName === 'textBold' ||
commandName === 'textItalic' ||
commandName === 'textCode' ||
commandName === 'attachFile' ||
commandName === 'textNumberedList' ||
commandName === 'textBulletedList' ||
commandName === 'textCheckbox' ||
commandName === 'textHeading' ||
commandName === 'textHorizontalRule' ||
commandName === 'insertDateTime'
);
}
private commandToToolbarButton(commandName: string, whenClauseContext: any): ToolbarButtonInfo {
const newEnabled = this.service.isEnabled(commandName, whenClauseContext);
const newTitle = this.service.title(commandName);
@@ -76,11 +52,8 @@ export default class ToolbarButtonUtils {
tooltip: this.service.label(commandName),
iconName: command.declaration.iconName,
enabled: newEnabled,
onClick: async () => {
onClick: () => {
void this.service.execute(commandName);
if (this.isEditorCommand(commandName)) {
void this.service.execute('editor.focus');
}
},
title: newTitle,
};

View File

@@ -1,17 +1,11 @@
import { _ } from '../../locale';
import InteropService_Exporter_Base from './InteropService_Exporter_Base';
import InteropService_Exporter_Raw from './InteropService_Exporter_Raw';
import shim from '../../shim';
const InteropService_Exporter_Base = require('./InteropService_Exporter_Base').default;
const InteropService_Exporter_Raw = require('./InteropService_Exporter_Raw').default;
const fs = require('fs-extra');
const shim = require('../../shim').default;
export default class InteropService_Exporter_Jex extends InteropService_Exporter_Base {
private tempDir_: string;
private destPath_: string;
private rawExporter_: InteropService_Exporter_Raw;
public async init(destPath: string) {
async init(destPath: string) {
if (await shim.fsDriver().isDirectory(destPath)) throw new Error(`Path is a directory: ${destPath}`);
this.tempDir_ = await this.temporaryDirectory_(false);
@@ -20,15 +14,15 @@ export default class InteropService_Exporter_Jex extends InteropService_Exporter
await this.rawExporter_.init(this.tempDir_);
}
public async processItem(itemType: number, item: any) {
async processItem(itemType: number, item: any) {
return this.rawExporter_.processItem(itemType, item);
}
public async processResource(resource: any, filePath: string) {
async processResource(resource: any, filePath: string) {
return this.rawExporter_.processResource(resource, filePath);
}
public async close() {
async close() {
const stats = await shim.fsDriver().readDirStats(this.tempDir_, { recursive: true });
const filePaths = stats.filter((a: any) => !a.isDirectory()).map((a: any) => a.path);

View File

@@ -14,7 +14,7 @@ export default class JoplinViewsMenuItems {
private store: any;
private plugin: Plugin;
public constructor(plugin: Plugin, store: any) {
constructor(plugin: Plugin, store: any) {
this.store = store;
this.plugin = plugin;
}
@@ -22,7 +22,7 @@ export default class JoplinViewsMenuItems {
/**
* Creates a new menu item and associate it with the given command. You can specify under which menu the item should appear using the `location` parameter.
*/
public async create(id: string, commandName: string, location: MenuItemLocation = MenuItemLocation.Tools, options: CreateMenuItemOptions = null) {
async create(id: string, commandName: string, location: MenuItemLocation = MenuItemLocation.Tools, options: CreateMenuItemOptions = null) {
if (typeof location !== 'string') {
this.plugin.deprecationNotice('1.5', 'Creating a view without an ID is deprecated. To fix it, change your call to `joplin.views.menuItem.create("my-unique-id", ...)`');
options = location as any;
@@ -35,17 +35,8 @@ export default class JoplinViewsMenuItems {
const controller = new MenuItemController(handle, this.plugin.id, this.store, commandName, location);
this.plugin.addViewController(controller);
// Register the command with the keymap service - not that if no
// accelerator is provided, we still register the command, so that
// it appears in the keymap editor, which will allow the user to
// set a custom shortcut.
//
// https://discourse.joplinapp.org/t/plugin-note-tabs/12752/39
if (options && options.accelerator) {
KeymapService.instance().registerCommandAccelerator(commandName, options.accelerator);
} else {
KeymapService.instance().registerCommandAccelerator(commandName, null);
}
}

View File

@@ -3,6 +3,20 @@ import createViewHandle from '../utils/createViewHandle';
import WebviewController, { ContainerType } from '../WebviewController';
import { ViewHandle } from './types';
interface InitialPosition {
nextTo: string,
}
// initialPosition: {
// nextTo: 'editor',
// side: '',top, right, bottom , left
// size: 100,
// }
interface CreateOptions {
initialPosition: InitialPosition,
}
/**
* Allows creating and managing view panels. View panels currently are
* displayed at the right of the sidebar and allows displaying any HTML
@@ -29,7 +43,7 @@ export default class JoplinViewsPanels {
/**
* Creates a new panel
*/
async create(id: string): Promise<ViewHandle> {
async create(id: string, options:CreateOptions = null): Promise<ViewHandle> {
if (!id) {
this.plugin.deprecationNotice('1.5', 'Creating a view without an ID is deprecated. To fix it, change your call to `joplin.views.panels.create("my-unique-id")`');
id = `${this.plugin.viewCount}`;

View File

@@ -19,14 +19,7 @@ interface ItemChangeEvent {
event: ItemChangeEventType;
}
interface SyncStartEvent {
// Tells whether there were errors during sync or not. The log will
// have the complete information about any error.
withErrors: boolean;
}
type ItemChangeHandler = (event: ItemChangeEvent)=> void;
type SyncStartHandler = (event: SyncStartEvent)=> void;
/**
* The workspace service provides access to all the parts of Joplin that
@@ -87,15 +80,8 @@ export default class JoplinWorkspace {
/**
* Called when an alarm associated with a to-do is triggered.
*/
public async onNoteAlarmTrigger(handler: Function): Promise<Disposable> {
return makeListener(eventManager, 'noteAlarmTrigger', handler);
}
/**
* Called when the synchronisation process is starting.
*/
public async onSyncStart(handler: SyncStartHandler): Promise<Disposable> {
return makeListener(eventManager, 'syncStart', handler);
public async onNoteAlarmTrigger(callback: Function): Promise<Disposable> {
return makeListener(eventManager, 'noteAlarmTrigger', callback);
}
/**

View File

@@ -4,7 +4,8 @@ const Setting = require('../../models/Setting').default;
const Note = require('../../models/Note.js');
const BaseModel = require('../../BaseModel').default;
const ItemChangeUtils = require('../ItemChangeUtils');
const { pregQuote, scriptType, removeDiacritics } = require('../../string-utils.js');
const { pregQuote, scriptType } = require('../../string-utils.js');
const removeDiacritics = require('diacritics').remove;
const { sprintf } = require('sprintf-js');
const filterParser = require('./filterParser').default;
const queryBuilder = require('./queryBuilder').default;

View File

@@ -41,7 +41,7 @@ const theme: Theme = {
searchMarkerBackgroundColor: '#F7D26E',
searchMarkerColor: 'black',
warningBackgroundColor: '#013F74',
warningBackgroundColor: '#CC6600',
tableBackgroundColor: 'rgb(40, 41, 42)',
codeBackgroundColor: 'rgb(47, 48, 49)',

View File

@@ -135,11 +135,11 @@ export default class HtmlToHtml {
enableLongPress: options.enableLongPress,
});
if (!r.html) return null;
if (!r) return null;
return {
type: 'replaceElement',
html: r.html,
html: r,
};
});
}

View File

@@ -32,7 +32,6 @@ const rules: RendererRules = {
checkbox: require('./MdToHtml/rules/checkbox').default,
katex: require('./MdToHtml/rules/katex').default,
link_open: require('./MdToHtml/rules/link_open').default,
link_close: require('./MdToHtml/rules/link_close').default,
html_image: require('./MdToHtml/rules/html_image').default,
highlight_keywords: require('./MdToHtml/rules/highlight_keywords').default,
code_inline: require('./MdToHtml/rules/code_inline').default,
@@ -97,19 +96,11 @@ interface PluginAssets {
[pluginName: string]: PluginAsset[];
}
export interface Link {
href: string;
resource: any;
resourceReady: boolean;
resourceFullPath: string;
}
interface PluginContext {
css: any;
pluginAssets: any;
cache: any;
userData: any;
currentLinks: Link[];
}
interface RenderResultPluginAsset {
@@ -151,10 +142,6 @@ export interface RuleOptions {
// linkRenderingType = 1 is the regular rendering and clicking on it is handled via embedded JS (in onclick attribute)
// linkRenderingType = 2 gives a plain link with no JS. Caller needs to handle clicking on the link.
linkRenderingType?: number;
audioPlayerEnabled: boolean;
videoPlayerEnabled: boolean;
pdfViewerEnabled: boolean;
}
export default class MdToHtml {
@@ -214,16 +201,10 @@ export default class MdToHtml {
}
private pluginOptions(name: string) {
// Currently link_close is only used to append the media player to
// the resource links so we use the mediaPlayers plugin options for
// it.
if (name === 'link_close') name = 'mediaPlayers';
let o = this.pluginOptions_[name] ? this.pluginOptions_[name] : {};
o = Object.assign({
enabled: true,
}, o);
return o;
}
@@ -367,10 +348,6 @@ export default class MdToHtml {
codeTheme: 'atom-one-light.css',
theme: Object.assign({}, defaultNoteStyle, theme),
plugins: {},
audioPlayerEnabled: this.pluginEnabled('audioPlayer'),
videoPlayerEnabled: this.pluginEnabled('videoPlayer'),
pdfViewerEnabled: this.pluginEnabled('pdfViewer'),
}, options);
// The "codeHighlightCacheKey" option indicates what set of cached object should be
@@ -396,7 +373,6 @@ export default class MdToHtml {
pluginAssets: {},
cache: this.contextCache_,
userData: {},
currentLinks: [],
};
const markdownIt = new MarkdownIt({

View File

@@ -3,12 +3,12 @@ import linkReplacement from './linkReplacement';
describe('linkReplacement', () => {
test('should handle non-resource links', () => {
const r = linkReplacement('https://example.com/test').html;
const r = linkReplacement('https://example.com/test');
expect(r).toBe('<a data-from-md href=\'https://example.com/test\' onclick=\'postMessage("https://example.com/test", { resourceId: "" }); return false;\'>');
});
test('should handle non-resource links - simple rendering', () => {
const r = linkReplacement('https://example.com/test', { linkRenderingType: 2 }).html;
const r = linkReplacement('https://example.com/test', { linkRenderingType: 2 });
expect(r).toBe('<a data-from-md href=\'https://example.com/test\'>');
});
@@ -25,7 +25,7 @@ describe('linkReplacement', () => {
},
},
},
}).html;
});
expect(r).toBe(`<a data-from-md data-resource-id='${resourceId}' href='#' onclick='postMessage("joplin://${resourceId}", { resourceId: "${resourceId}" }); return false;'><span class="resource-icon fa-joplin"></span>`);
});
@@ -43,7 +43,7 @@ describe('linkReplacement', () => {
},
},
},
}).html;
});
// Since the icon is embedded as SVG, we only check for the prefix
const expectedPrefix = `<a class="not-loaded-resource resource-status-notDownloaded" data-resource-id="${resourceId}"><img src="data:image/svg+xml;utf8`;

View File

@@ -14,14 +14,7 @@ export interface Options {
enableLongPress?: boolean;
}
export interface LinkReplacementResult {
html: string;
resource: any;
resourceReady: boolean;
resourceFullPath: string;
}
export default function(href: string, options: Options = null): LinkReplacementResult {
export default function(href: string, options: Options = null) {
options = {
title: '',
resources: {},
@@ -42,7 +35,6 @@ export default function(href: string, options: Options = null): LinkReplacementR
let hrefAttr = '#';
let mime = '';
let resourceId = '';
let resource = null;
if (isResourceUrl) {
resourceId = resourceHrefInfo.itemId;
@@ -52,18 +44,11 @@ export default function(href: string, options: Options = null): LinkReplacementR
if (result && result.item) {
if (!title) title = result.item.title;
mime = result.item.mime;
resource = result.item;
}
if (result && resourceStatus !== 'ready' && !options.plainResourceRendering) {
const icon = utils.resourceStatusFile(resourceStatus);
return {
resourceReady: false,
html: `<a class="not-loaded-resource resource-status-${resourceStatus}" data-resource-id="${resourceId}">` + `<img src="data:image/svg+xml;utf8,${htmlentities(icon)}"/>`,
resource,
resourceFullPath: null,
};
return `<a class="not-loaded-resource resource-status-${resourceStatus}" data-resource-id="${resourceId}">` + `<img src="data:image/svg+xml;utf8,${htmlentities(icon)}"/>`;
} else {
href = `joplin://${resourceId}`;
if (resourceHrefInfo.hash) href += `#${resourceHrefInfo.hash}`;
@@ -115,10 +100,5 @@ export default function(href: string, options: Options = null): LinkReplacementR
if (js) attrHtml.push(js);
}
return {
html: `<a ${attrHtml.join(' ')}>${icon}`,
resourceReady: true,
resource,
resourceFullPath: resource && options.ResourceModel ? options.ResourceModel.fullPath(resource) : null,
};
return `<a ${attrHtml.join(' ')}>${icon}`;
}

View File

@@ -1,41 +0,0 @@
import { Link } from '../MdToHtml';
import { toForwardSlashes } from '../pathUtils';
const Entities = require('html-entities').AllHtmlEntities;
const htmlentities = new Entities().encode;
export interface Options {
audioPlayerEnabled: boolean;
videoPlayerEnabled: boolean;
pdfViewerEnabled: boolean;
}
export default function(link: Link, options: Options) {
const resource = link.resource;
if (!link.resourceReady || !resource || !resource.mime) return '';
const escapedResourcePath = htmlentities(`file://${toForwardSlashes(link.resourceFullPath)}`);
const escapedMime = htmlentities(resource.mime);
if (options.videoPlayerEnabled && resource.mime.indexOf('video/') === 0) {
return `
<video class="media-player media-video" controls>
<source src="${escapedResourcePath}" type="${escapedMime}">
</video>
`;
}
if (options.audioPlayerEnabled && resource.mime.indexOf('audio/') === 0) {
return `
<audio class="media-player media-audio" controls>
<source src="${escapedResourcePath}" type="${escapedMime}">
</audio>
`;
}
if (options.pdfViewerEnabled && resource.mime === 'application/pdf') {
return `<object data="${escapedResourcePath}" class="media-player media-pdf" type="${escapedMime}"></object>`;
}
return '';
}

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