You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-08-27 20:29:45 +02:00
Compare commits
73 Commits
v2.10.7
...
note-list-
Author | SHA1 | Date | |
---|---|---|---|
|
2ea412331b | ||
|
0bf4da3cf5 | ||
|
3e299f1ab9 | ||
|
42873d3829 | ||
|
016311a0ae | ||
|
8d592ac1e0 | ||
|
93f4928ea8 | ||
|
ea855602b9 | ||
|
45da775649 | ||
|
4983327f90 | ||
|
fddbe6cf6c | ||
|
57f00c612d | ||
|
235288e903 | ||
|
38be744c3e | ||
|
2384ec8792 | ||
|
85f7caa0eb | ||
|
ad0f0414c4 | ||
|
437320f90c | ||
|
c1db7182ac | ||
|
aa4af69afc | ||
|
21a39af97b | ||
|
0812cc5944 | ||
|
1fb5d2c6c5 | ||
|
1b9901d232 | ||
|
f15d2793cc | ||
|
ad4d71dbe1 | ||
|
4bee6ffc90 | ||
|
01f63b3d97 | ||
|
b19b590efc | ||
|
6b7577f94d | ||
|
4d09b14522 | ||
|
9f1e95324d | ||
|
f98314346d | ||
|
fa659b615a | ||
|
e4aafa7edb | ||
|
811c40b074 | ||
|
405c528ef0 | ||
|
a176216374 | ||
|
8d0b090f66 | ||
|
337c5ed61c | ||
|
b32d39860b | ||
|
a091608f72 | ||
|
9686ee7833 | ||
|
3b55dcac65 | ||
|
d524d11d8a | ||
|
9c080ec631 | ||
|
da11476fd7 | ||
|
d157b9cfc7 | ||
|
bc8c61748a | ||
|
3ad727889b | ||
|
8df128bb7a | ||
|
5a4568f4db | ||
|
170295919a | ||
|
2262cbfdfd | ||
|
43e40bcf5a | ||
|
e60595f0de | ||
|
527a7c115d | ||
|
c22f910500 | ||
|
667b7969ff | ||
|
1bbf065142 | ||
|
606cad3c3a | ||
|
c9612dc8b8 | ||
|
6ee30e22a6 | ||
|
a6536e1ef9 | ||
|
92cf5abd08 | ||
|
d1e545ac2c | ||
|
ec6567c68d | ||
|
3b236307f7 | ||
|
14ac54bf3d | ||
|
e53b796523 | ||
|
9d189b2b34 | ||
|
84545cf26d | ||
|
c427a6d0a5 |
@@ -79,6 +79,8 @@ packages/app-cli/app/LinkSelector.js
|
||||
packages/app-cli/app/base-command.js
|
||||
packages/app-cli/app/command-done.test.js
|
||||
packages/app-cli/app/command-e2ee.js
|
||||
packages/app-cli/app/command-mkbook.js
|
||||
packages/app-cli/app/command-mkbook.test.js
|
||||
packages/app-cli/app/command-settingschema.js
|
||||
packages/app-cli/app/command-sync.js
|
||||
packages/app-cli/app/command-testing.js
|
||||
@@ -833,6 +835,7 @@ packages/renderer/index.js
|
||||
packages/renderer/noteStyle.js
|
||||
packages/renderer/pathUtils.js
|
||||
packages/renderer/utils.js
|
||||
packages/tools/build-release-stats.js
|
||||
packages/tools/buildServerDocker.js
|
||||
packages/tools/buildServerDocker.test.js
|
||||
packages/tools/bundleDefaultPlugins.js
|
||||
|
@@ -175,9 +175,7 @@ module.exports = {
|
||||
'project': './tsconfig.eslint.json',
|
||||
},
|
||||
'rules': {
|
||||
// Warn only because it would make it difficult to convert JS classes to TypeScript, unless we
|
||||
// make everything public which is not great. New code however should specify member accessibility.
|
||||
'@typescript-eslint/explicit-member-accessibility': ['warn'],
|
||||
'@typescript-eslint/explicit-member-accessibility': ['error'],
|
||||
'@typescript-eslint/type-annotation-spacing': ['error', { 'before': false, 'after': true }],
|
||||
'@typescript-eslint/no-inferrable-types': ['error', { 'ignoreParameters': true, 'ignoreProperties': true }],
|
||||
'@typescript-eslint/comma-dangle': ['error', {
|
||||
|
2
.github/workflows/cla.yml
vendored
2
.github/workflows/cla.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
- name: "CLA Assistant"
|
||||
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
|
||||
# Beta Release
|
||||
uses: contributor-assistant/github-action@v2.2.1
|
||||
uses: contributor-assistant/github-action@v2.3.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# the below token should have repo scope and must be manually added by you in the repository's secret
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -67,6 +67,8 @@ packages/app-cli/app/LinkSelector.js
|
||||
packages/app-cli/app/base-command.js
|
||||
packages/app-cli/app/command-done.test.js
|
||||
packages/app-cli/app/command-e2ee.js
|
||||
packages/app-cli/app/command-mkbook.js
|
||||
packages/app-cli/app/command-mkbook.test.js
|
||||
packages/app-cli/app/command-settingschema.js
|
||||
packages/app-cli/app/command-sync.js
|
||||
packages/app-cli/app/command-testing.js
|
||||
@@ -821,6 +823,7 @@ packages/renderer/index.js
|
||||
packages/renderer/noteStyle.js
|
||||
packages/renderer/pathUtils.js
|
||||
packages/renderer/utils.js
|
||||
packages/tools/build-release-stats.js
|
||||
packages/tools/buildServerDocker.js
|
||||
packages/tools/buildServerDocker.test.js
|
||||
packages/tools/bundleDefaultPlugins.js
|
||||
|
BIN
Assets/WebsiteAssets/images/news/20230202-jdll.jpg
Normal file
BIN
Assets/WebsiteAssets/images/news/20230202-jdll.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 238 KiB |
@@ -1,4 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Joplin]]></title><description><![CDATA[Joplin, the open source note-taking application]]></description><link>https://joplinapp.org</link><generator>RSS for Node</generator><lastBuildDate>Mon, 16 Jan 2023 00:00:00 GMT</lastBuildDate><atom:link href="https://joplinapp.org/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Mon, 16 Jan 2023 00:00:00 GMT</pubDate><item><title><![CDATA[Introducing the "GitHub Actions Raw Log Viewer" extension for Chrome]]></title><description><![CDATA[<p>If you've ever used GitHub Actions, you will find that they provide by default a nice coloured output for the log. It looks good and it's even interactive! (You can click to collapse/expand blocks of text) But unfortunately it doesn't scale to large workflows, like we have for Joplin - the log can freeze and it will take forever to search for something. Indeed searching is done in "real time"... which mostly means it will freeze for a minute or two for each letter you type in the search box. Not great.</p>
|
||||
<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Joplin]]></title><description><![CDATA[Joplin, the open source note-taking application]]></description><link>https://joplinapp.org</link><generator>RSS for Node</generator><lastBuildDate>Thu, 02 Mar 2023 00:00:00 GMT</lastBuildDate><atom:link href="https://joplinapp.org/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Thu, 02 Mar 2023 00:00:00 GMT</pubDate><item><title><![CDATA[Joplin will participate in JdLL 2023!]]></title><description><![CDATA[<p>On 1 and 2 April 2023, we will have a stand for Joplin at the <a href="https://www.jdll.org/">Journées du Logiciel Libre</a> in Lyon, France. The JdLL has been taking place in Lyon for 24 years and is a popular open source conference in France. We had a stand in 2020 and 2021 but that was cancelled due to Covid, so this year is a first for Joplin!</p>
|
||||
<p>Admission is free, so don't hesitate to come and meet us, exchange ideas and learn more about Joplin!</p>
|
||||
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20230202-jdll.jpg" alt="Joplin at JdLL 2023"></p>
|
||||
]]></description><link>https://joplinapp.org/news/20230302-jdll-2023/</link><guid isPermaLink="false">20230302-jdll-2023</guid><pubDate>Thu, 02 Mar 2023 00:00:00 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[Introducing the "GitHub Actions Raw Log Viewer" extension for Chrome]]></title><description><![CDATA[<p>If you've ever used GitHub Actions, you will find that they provide by default a nice coloured output for the log. It looks good and it's even interactive! (You can click to collapse/expand blocks of text) But unfortunately it doesn't scale to large workflows, like we have for Joplin - the log can freeze and it will take forever to search for something. Indeed searching is done in "real time"... which mostly means it will freeze for a minute or two for each letter you type in the search box. Not great.</p>
|
||||
<p>Thankfully GitHub provides an alternative access: the raw logs. This is much better because they will open as plain text, without any styling or JS magic, which means you can use the browser native search and it will be fast.</p>
|
||||
<p>But now the problem is that raw logs look like this:</p>
|
||||
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20230116-ga-raw-log.png" alt="Raw log without extension"></p>
|
||||
@@ -294,9 +297,4 @@
|
||||
<p>This release also includes about 30 various bug fixes and improvements.</p>
|
||||
<p>A notable one is a fix for GotoAnything, which recently wasn't working on first try.</p>
|
||||
<p>The plugin screen has also been improved so that search works even when GitHub is down or blocked, as it is in China in particular.</p>
|
||||
]]></description><link>https://joplinapp.org/news/20210929-144036/</link><guid isPermaLink="false">20210929-144036</guid><pubDate>Wed, 29 Sep 2021 14:40:36 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[Introducing recommended plugins in the next Joplin version]]></title><description><![CDATA[<p>A common request from new users is how to know which plugin is safe to install or not. In fact probably all of them are safe but as a new user that's not necessarily easy to know. So to help with this, the next version of Joplin will support recommended plugins - those will be plugins that meet our standards of quality and performance, and they will be indicated by a small crown tag inside the plugin box. Recommended plugins will also appear on top when searching.</p>
|
||||
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20210901-113415_0.png" alt=""></p>
|
||||
<p>For now, since we don't have a review process, the recommended plugins are those developed by the Joplin team and frequent contributors, because we know those are safe to use.</p>
|
||||
<p>Later we might have a review process and add more recommended plugins. That being said, in the meantime even if a plugin is not marked as recommended, there's a good chance it is still safe and have good performance too. Often you can search for it on the forum and if it's active with many users commenting, you're most likely good to go.</p>
|
||||
<p>But if there's any doubt, the recommended tag is a good way to be sure.</p>
|
||||
]]></description><link>https://joplinapp.org/news/20210901-113415/</link><guid isPermaLink="false">20210901-113415</guid><pubDate>Wed, 01 Sep 2021 11:34:15 GMT</pubDate><twitter-text></twitter-text></item></channel></rss>
|
||||
]]></description><link>https://joplinapp.org/news/20210929-144036/</link><guid isPermaLink="false">20210929-144036</guid><pubDate>Wed, 29 Sep 2021 14:40:36 GMT</pubDate><twitter-text></twitter-text></item></channel></rss>
|
60
README.md
60
README.md
@@ -530,47 +530,47 @@ Current translations:
|
||||
<!-- LOCALE-TABLE-AUTO-GENERATED -->
|
||||
| Language | Po File | Last translator | Percent done
|
||||
---|---|---|---|---
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/arableague.png" width="16px"/> | Arabic | [ar](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ar.po) | [Whaell O](mailto:Whaell@protonmail.com) | 81%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/arableague.png" width="16px"/> | Arabic | [ar](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ar.po) | [Whaell O](mailto:Whaell@protonmail.com) | 80%
|
||||
<img src="https://joplinapp.org/images/flags/es/basque_country.png" width="16px"/> | Basque | [eu](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eu.po) | juan.abasolo@ehu.eus | 23%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ba.png" width="16px"/> | 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) | 58%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/bg.png" width="16px"/> | Bulgarian (България) | [bg_BG](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bg_BG.po) | | 45%
|
||||
<img src="https://joplinapp.org/images/flags/es/catalonia.png" width="16px"/> | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ca.po) | [Xavi Ivars](mailto:xavi.ivars@gmail.com) | 90%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/hr.png" width="16px"/> | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Milo Ivir](mailto:mail@milotype.de) | 90%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/cz.png" width="16px"/> | Czech (Česká republika) | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/cs_CZ.po) | [Michal Stanke](mailto:michal@stanke.cz) | 78%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/dk.png" width="16px"/> | Dansk (Danmark) | [da_DK](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/da_DK.po) | ERYpTION | 97%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/de.png" width="16px"/> | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [MrKanister](mailto:pueblos_spatulas@aleeas.com) | 98%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ee.png" width="16px"/> | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 45%
|
||||
<img src="https://joplinapp.org/images/flags/es/catalonia.png" width="16px"/> | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ca.po) | [Xavi Ivars](mailto:xavi.ivars@gmail.com) | 89%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/hr.png" width="16px"/> | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Milo Ivir](mailto:mail@milotype.de) | 89%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/cz.png" width="16px"/> | Czech (Česká republika) | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/cs_CZ.po) | [Michal Stanke](mailto:michal@stanke.cz) | 77%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/dk.png" width="16px"/> | Dansk (Danmark) | [da_DK](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/da_DK.po) | ERYpTION | 99%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/de.png" width="16px"/> | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [MrKanister](mailto:pueblos_spatulas@aleeas.com) | 99%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ee.png" width="16px"/> | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 44%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/gb.png" width="16px"/> | English (United Kingdom) | [en_GB](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_GB.po) | | 100%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/us.png" width="16px"/> | English (United States of America) | [en_US](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_US.po) | | 100%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/es.png" width="16px"/> | Español (España) | [es_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/es_ES.po) | [Francisco Mora](mailto:francisco.m.collao@gmail.com) | 89%
|
||||
<img src="https://joplinapp.org/images/flags/esperanto.png" width="16px"/> | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 26%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/fi.png" width="16px"/> | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | mrkaato0 | 96%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/fr.png" width="16px"/> | Français (France) | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fr_FR.po) | Laurent Cozic | 100%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/es.png" width="16px"/> | Español (España) | [es_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/es_ES.po) | [Francisco Villaverde](mailto:teko.gr@gmail.com) | 99%
|
||||
<img src="https://joplinapp.org/images/flags/esperanto.png" width="16px"/> | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 25%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/fi.png" width="16px"/> | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | mrkaato0 | 95%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/fr.png" width="16px"/> | Français (France) | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fr_FR.po) | Laurent Cozic | 99%
|
||||
<img src="https://joplinapp.org/images/flags/es/galicia.png" width="16px"/> | Galician (España) | [gl_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/gl_ES.po) | [Marcos Lans](mailto:marcoslansgarza@gmail.com) | 29%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/id.png" width="16px"/> | Indonesian (Indonesia) | [id_ID](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/id_ID.po) | [Wisnu Adi Santoso](mailto:waditos@gmail.com) | 90%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/id.png" width="16px"/> | Indonesian (Indonesia) | [id_ID](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/id_ID.po) | [Wisnu Adi Santoso](mailto:waditos@gmail.com) | 89%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/it.png" width="16px"/> | Italiano (Italia) | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/it_IT.po) | [Manuel Tassi](mailto:mannivuwiki@gmail.com) | 81%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/hu.png" width="16px"/> | Magyar (Magyarország) | [hu_HU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hu_HU.po) | [Magyari Balázs](mailto:balmag@gmail.com) | 78%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/be.png" width="16px"/> | Nederlands (België, Belgique, Belgien) | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_BE.po) | | 79%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/nl.png" width="16px"/> | Nederlands (Nederland) | [nl_NL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_NL.po) | [MHolkamp](mailto:mholkamp@users.noreply.github.com) | 89%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/no.png" width="16px"/> | Norwegian (Norge, Noreg) | [nb_NO](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nb_NO.po) | [Mats Estensen](mailto:code@mxe.no) | 89%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ir.png" width="16px"/> | Persian | [fa](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fa.po) | [Kourosh Firoozbakht](mailto:kourox@protonmail.com) | 56%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/pl.png" width="16px"/> | Polski (Polska) | [pl_PL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pl_PL.po) | [X3NO](mailto:X3NO@disroot.org) | 90%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/br.png" width="16px"/> | 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) | 89%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/nl.png" width="16px"/> | Nederlands (Nederland) | [nl_NL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_NL.po) | [MHolkamp](mailto:mholkamp@users.noreply.github.com) | 88%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/no.png" width="16px"/> | Norwegian (Norge, Noreg) | [nb_NO](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nb_NO.po) | [Mats Estensen](mailto:code@mxe.no) | 88%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ir.png" width="16px"/> | Persian | [fa](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fa.po) | [Kourosh Firoozbakht](mailto:kourox@protonmail.com) | 55%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/pl.png" width="16px"/> | Polski (Polska) | [pl_PL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pl_PL.po) | [X3NO](mailto:X3NO@disroot.org) | 91%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/br.png" width="16px"/> | 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) | 88%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/pt.png" width="16px"/> | Português (Portugal) | [pt_PT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_PT.po) | [Diogo Caveiro](mailto:dcaveiro@yahoo.com) | 73%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ro.png" width="16px"/> | Română | [ro](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ro.po) | [Cristi Duluta](mailto:cristi.duluta@gmail.com) | 51%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/si.png" width="16px"/> | Slovenian (Slovenija) | [sl_SI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sl_SI.po) | [Martin Korelič](mailto:martin.korelic@protonmail.com) | 81%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/se.png" width="16px"/> | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 97%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/th.png" width="16px"/> | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 37%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/vn.png" width="16px"/> | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 79%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/tr.png" width="16px"/> | 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) | 90%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ua.png" width="16px"/> | Ukrainian (Україна) | [uk_UA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/uk_UA.po) | [Vyacheslav Andreykiv](mailto:vandreykiv@gmail.com) | 73%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/gr.png" width="16px"/> | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 89%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ru.png" width="16px"/> | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Sergey Segeda](mailto:thesermanarm@gmail.com) | 81%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/rs.png" width="16px"/> | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 66%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/cn.png" width="16px"/> | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [KaneGreen](mailto:737445366KG@Gmail.com) | 95%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/tw.png" width="16px"/> | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [Kevin Hsu](mailto:kevin.hsu.hws@gmail.com) | 90%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/jp.png" width="16px"/> | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 90%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/kr.png" width="16px"/> | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 90%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/si.png" width="16px"/> | Slovenian (Slovenija) | [sl_SI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sl_SI.po) | [Martin Korelič](mailto:martin.korelic@protonmail.com) | 80%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/se.png" width="16px"/> | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 96%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/th.png" width="16px"/> | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 36%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/vn.png" width="16px"/> | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 78%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/tr.png" width="16px"/> | 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) | 89%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ua.png" width="16px"/> | Ukrainian (Україна) | [uk_UA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/uk_UA.po) | [Vyacheslav Andreykiv](mailto:vandreykiv@gmail.com) | 72%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/gr.png" width="16px"/> | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 88%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ru.png" width="16px"/> | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Dmitriy Q](mailto:krotesk@mail.ru) | 99%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/rs.png" width="16px"/> | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 65%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/cn.png" width="16px"/> | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [wh201906](mailto:wh201906@yandex.com) | 97%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/tw.png" width="16px"/> | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [Kevin Hsu](mailto:kevin.hsu.hws@gmail.com) | 89%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/jp.png" width="16px"/> | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 89%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/kr.png" width="16px"/> | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 89%
|
||||
<!-- LOCALE-TABLE-AUTO-GENERATED -->
|
||||
|
||||
# Contributors
|
||||
|
@@ -6,7 +6,6 @@ const tasks = {
|
||||
buildCommandIndex: require('./packages/tools/gulp/tasks/buildCommandIndex'),
|
||||
completePublishAll: {
|
||||
fn: async () => {
|
||||
|
||||
await utils.execCommandVerbose('git', ['add', '-A']);
|
||||
await utils.execCommandVerbose('git', ['commit', '-m', 'Releasing sub-packages']);
|
||||
|
||||
@@ -17,6 +16,10 @@ const tasks = {
|
||||
// https://github.com/lerna/lerna/issues/2788
|
||||
await utils.execCommandVerbose('lerna', ['publish', 'from-package', '-y', '--no-verify-access']);
|
||||
|
||||
await utils.execCommandVerbose('yarn', ['install']);
|
||||
await utils.execCommandVerbose('git', ['add', '-A']);
|
||||
await utils.execCommandVerbose('git', ['commit', '-m', 'Lock file']);
|
||||
|
||||
await utils.execCommandVerbose('git', ['push']);
|
||||
},
|
||||
},
|
||||
|
@@ -78,8 +78,8 @@
|
||||
"gulp": "4.0.2",
|
||||
"husky": "3.1.0",
|
||||
"lerna": "3.22.1",
|
||||
"lint-staged": "13.1.1",
|
||||
"madge": "5.0.2",
|
||||
"lint-staged": "13.1.2",
|
||||
"madge": "6.0.0",
|
||||
"npm-package-json-lint": "6.4.0",
|
||||
"typedoc": "0.17.8",
|
||||
"typescript": "4.9.4"
|
||||
|
@@ -6,14 +6,14 @@ interface LinkStoreEntry {
|
||||
}
|
||||
|
||||
class LinkSelector {
|
||||
noteId_: string;
|
||||
scrollTop_: number;
|
||||
renderedText_: string;
|
||||
currentLinkIndex_: number;
|
||||
linkStore_: LinkStoreEntry[];
|
||||
linkRegex_: RegExp;
|
||||
private noteId_: string;
|
||||
private scrollTop_: number;
|
||||
private renderedText_: string;
|
||||
private currentLinkIndex_: number;
|
||||
private linkStore_: LinkStoreEntry[];
|
||||
private linkRegex_: RegExp;
|
||||
|
||||
constructor() {
|
||||
public constructor() {
|
||||
this.noteId_ = null;
|
||||
this.scrollTop_ = null; // used so 'o' won't open unhighlighted link after scrolling
|
||||
this.renderedText_ = null;
|
||||
@@ -22,22 +22,22 @@ class LinkSelector {
|
||||
this.linkRegex_ = /http:\/\/[0-9.]+:[0-9]+\/[0-9]+/g;
|
||||
}
|
||||
|
||||
get link(): string | null {
|
||||
public get link(): string | null {
|
||||
if (this.currentLinkIndex_ === null) return null;
|
||||
return this.linkStore_[this.currentLinkIndex_].link;
|
||||
}
|
||||
|
||||
get noteX(): number | null {
|
||||
public get noteX(): number | null {
|
||||
if (this.currentLinkIndex_ === null) return null;
|
||||
return this.linkStore_[this.currentLinkIndex_].noteX;
|
||||
}
|
||||
|
||||
get noteY(): number | null {
|
||||
public get noteY(): number | null {
|
||||
if (this.currentLinkIndex_ === null) return null;
|
||||
return this.linkStore_[this.currentLinkIndex_].noteY;
|
||||
}
|
||||
|
||||
findLinks(renderedText: string): LinkStoreEntry[] {
|
||||
public findLinks(renderedText: string): LinkStoreEntry[] {
|
||||
const newLinkStore: LinkStoreEntry[] = [];
|
||||
const lines: string[] = renderedText.split('\n');
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
@@ -56,19 +56,19 @@ class LinkSelector {
|
||||
return newLinkStore;
|
||||
}
|
||||
|
||||
updateText(renderedText: string): void {
|
||||
public updateText(renderedText: string): void {
|
||||
this.currentLinkIndex_ = null;
|
||||
this.renderedText_ = renderedText;
|
||||
this.linkStore_ = this.findLinks(this.renderedText_);
|
||||
}
|
||||
|
||||
updateNote(textWidget: any): void {
|
||||
public updateNote(textWidget: any): void {
|
||||
this.noteId_ = textWidget.noteId;
|
||||
this.scrollTop_ = textWidget.scrollTop_;
|
||||
this.updateText(textWidget.renderedText_);
|
||||
}
|
||||
|
||||
scrollWidget(textWidget: any): void {
|
||||
public scrollWidget(textWidget: any): void {
|
||||
if (this.currentLinkIndex_ === null) return;
|
||||
|
||||
const noteY = this.linkStore_[this.currentLinkIndex_].noteY;
|
||||
@@ -93,7 +93,7 @@ class LinkSelector {
|
||||
return;
|
||||
}
|
||||
|
||||
changeLink(textWidget: any, offset: number): void | null {
|
||||
public changeLink(textWidget: any, offset: number): void | null {
|
||||
if (textWidget.noteId !== this.noteId_) {
|
||||
this.updateNote(textWidget);
|
||||
this.changeLink(textWidget, offset);
|
||||
@@ -123,7 +123,7 @@ class LinkSelector {
|
||||
return;
|
||||
}
|
||||
|
||||
openLink(textWidget: any): void {
|
||||
public openLink(textWidget: any): void {
|
||||
if (textWidget.noteId !== this.noteId_) return;
|
||||
if (textWidget.renderedText_ !== this.renderedText_) return;
|
||||
if (textWidget.scrollTop_ !== this.scrollTop_) return;
|
||||
|
@@ -1,97 +0,0 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const locale_1 = require("@joplin/lib/locale");
|
||||
const registry_js_1 = require("@joplin/lib/registry.js");
|
||||
class BaseCommand {
|
||||
constructor() {
|
||||
this.stdout_ = null;
|
||||
this.prompt_ = null;
|
||||
}
|
||||
usage() {
|
||||
throw new Error('Usage not defined');
|
||||
}
|
||||
encryptionCheck(item) {
|
||||
if (item && item.encryption_applied)
|
||||
throw new Error((0, locale_1._)('Cannot change encrypted item'));
|
||||
}
|
||||
description() {
|
||||
throw new Error('Description not defined');
|
||||
}
|
||||
action(_args) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
throw new Error('Action not defined');
|
||||
});
|
||||
}
|
||||
compatibleUis() {
|
||||
return ['cli', 'gui'];
|
||||
}
|
||||
supportsUi(ui) {
|
||||
return this.compatibleUis().indexOf(ui) >= 0;
|
||||
}
|
||||
options() {
|
||||
return [];
|
||||
}
|
||||
hidden() {
|
||||
return false;
|
||||
}
|
||||
enabled() {
|
||||
return true;
|
||||
}
|
||||
cancellable() {
|
||||
return false;
|
||||
}
|
||||
cancel() {
|
||||
return __awaiter(this, void 0, void 0, function* () { });
|
||||
}
|
||||
name() {
|
||||
const r = this.usage().split(' ');
|
||||
return r[0];
|
||||
}
|
||||
setDispatcher(fn) {
|
||||
this.dispatcher_ = fn;
|
||||
}
|
||||
dispatch(action) {
|
||||
if (!this.dispatcher_)
|
||||
throw new Error('Dispatcher not defined');
|
||||
return this.dispatcher_(action);
|
||||
}
|
||||
setStdout(fn) {
|
||||
this.stdout_ = fn;
|
||||
}
|
||||
stdout(text) {
|
||||
if (this.stdout_)
|
||||
this.stdout_(text);
|
||||
}
|
||||
setPrompt(fn) {
|
||||
this.prompt_ = fn;
|
||||
}
|
||||
prompt(message, options = null) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (!this.prompt_)
|
||||
throw new Error('Prompt is undefined');
|
||||
return yield this.prompt_(message, options);
|
||||
});
|
||||
}
|
||||
metadata() {
|
||||
return {
|
||||
name: this.name(),
|
||||
usage: this.usage(),
|
||||
options: this.options(),
|
||||
hidden: this.hidden(),
|
||||
};
|
||||
}
|
||||
logger() {
|
||||
return registry_js_1.reg.logger();
|
||||
}
|
||||
}
|
||||
exports.default = BaseCommand;
|
||||
//# sourceMappingURL=base-command.js.map
|
@@ -7,80 +7,80 @@ export default class BaseCommand {
|
||||
protected prompt_: any = null;
|
||||
protected dispatcher_: any;
|
||||
|
||||
usage(): string {
|
||||
public usage(): string {
|
||||
throw new Error('Usage not defined');
|
||||
}
|
||||
|
||||
encryptionCheck(item: any) {
|
||||
public encryptionCheck(item: any) {
|
||||
if (item && item.encryption_applied) throw new Error(_('Cannot change encrypted item'));
|
||||
}
|
||||
|
||||
description() {
|
||||
public description() {
|
||||
throw new Error('Description not defined');
|
||||
}
|
||||
|
||||
async action(_args: any) {
|
||||
public async action(_args: any) {
|
||||
throw new Error('Action not defined');
|
||||
}
|
||||
|
||||
compatibleUis() {
|
||||
public compatibleUis() {
|
||||
return ['cli', 'gui'];
|
||||
}
|
||||
|
||||
supportsUi(ui: string) {
|
||||
public supportsUi(ui: string) {
|
||||
return this.compatibleUis().indexOf(ui) >= 0;
|
||||
}
|
||||
|
||||
options(): any[] {
|
||||
public options(): any[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
hidden() {
|
||||
public hidden() {
|
||||
return false;
|
||||
}
|
||||
|
||||
enabled() {
|
||||
public enabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
cancellable() {
|
||||
public cancellable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
async cancel() {}
|
||||
public async cancel() {}
|
||||
|
||||
name() {
|
||||
public name() {
|
||||
const r = this.usage().split(' ');
|
||||
return r[0];
|
||||
}
|
||||
|
||||
setDispatcher(fn: Function) {
|
||||
public setDispatcher(fn: Function) {
|
||||
this.dispatcher_ = fn;
|
||||
}
|
||||
|
||||
dispatch(action: any) {
|
||||
public dispatch(action: any) {
|
||||
if (!this.dispatcher_) throw new Error('Dispatcher not defined');
|
||||
return this.dispatcher_(action);
|
||||
}
|
||||
|
||||
setStdout(fn: Function) {
|
||||
public setStdout(fn: Function) {
|
||||
this.stdout_ = fn;
|
||||
}
|
||||
|
||||
stdout(text: string) {
|
||||
public stdout(text: string) {
|
||||
if (this.stdout_) this.stdout_(text);
|
||||
}
|
||||
|
||||
setPrompt(fn: Function) {
|
||||
public setPrompt(fn: Function) {
|
||||
this.prompt_ = fn;
|
||||
}
|
||||
|
||||
async prompt(message: string, options: any = null) {
|
||||
public async prompt(message: string, options: any = null) {
|
||||
if (!this.prompt_) throw new Error('Prompt is undefined');
|
||||
return await this.prompt_(message, options);
|
||||
}
|
||||
|
||||
metadata() {
|
||||
public metadata() {
|
||||
return {
|
||||
name: this.name(),
|
||||
usage: this.usage(),
|
||||
@@ -89,7 +89,7 @@ export default class BaseCommand {
|
||||
};
|
||||
}
|
||||
|
||||
logger() {
|
||||
public logger() {
|
||||
return reg.logger();
|
||||
}
|
||||
}
|
||||
|
@@ -12,15 +12,15 @@ const imageType = require('image-type');
|
||||
const readChunk = require('read-chunk');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
usage() {
|
||||
public usage() {
|
||||
return 'e2ee <command> [path]';
|
||||
}
|
||||
|
||||
description() {
|
||||
public description() {
|
||||
return _('Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, `status`, `decrypt-file`, and `target-status`.'); // `generate-ppk`
|
||||
}
|
||||
|
||||
options() {
|
||||
public options() {
|
||||
return [
|
||||
// This is here mostly for testing - shouldn't be used
|
||||
['-p, --password <password>', 'Use this password as master password (For security reasons, it is not recommended to use this option).'],
|
||||
@@ -30,7 +30,7 @@ class Command extends BaseCommand {
|
||||
];
|
||||
}
|
||||
|
||||
async action(args: any) {
|
||||
public async action(args: any) {
|
||||
const options = args.options;
|
||||
|
||||
const askForMasterKey = async (error: any) => {
|
||||
|
@@ -1,21 +0,0 @@
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const Folder = require('@joplin/lib/models/Folder').default;
|
||||
|
||||
class Command extends BaseCommand {
|
||||
usage() {
|
||||
return 'mkbook <new-notebook>';
|
||||
}
|
||||
|
||||
description() {
|
||||
return _('Creates a new notebook.');
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
const folder = await Folder.save({ title: args['new-notebook'] }, { userSideValidation: true });
|
||||
app().switchCurrentFolder(folder);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Command;
|
50
packages/app-cli/app/command-mkbook.test.ts
Normal file
50
packages/app-cli/app/command-mkbook.test.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { setupDatabaseAndSynchronizer, switchClient } from '@joplin/lib/testing/test-utils';
|
||||
import { setupCommandForTesting, setupApplication } from './utils/testUtils';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
const Command = require('./command-mkbook');
|
||||
|
||||
|
||||
describe('command-mkbook', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
await switchClient(1);
|
||||
await setupApplication();
|
||||
});
|
||||
|
||||
|
||||
it('should create a subfolder in first folder', async () => {
|
||||
const command = setupCommandForTesting(Command);
|
||||
await command.action({ 'new-notebook': 'folder1', options: {} });
|
||||
await command.action({ 'new-notebook': 'folder1_1', options: { parent: 'folder1' } });
|
||||
|
||||
const folder1 = await Folder.loadByTitle('folder1');
|
||||
const folder1_1 = await Folder.loadByTitle('folder1_1');
|
||||
|
||||
expect(folder1.title).toBe('folder1');
|
||||
expect(folder1_1.parent_id).toBe(folder1.id);
|
||||
});
|
||||
|
||||
it('should not be possible to create a subfolder without an argument.', async () => {
|
||||
const command = setupCommandForTesting(Command);
|
||||
await command.action({ 'new-notebook': 'folder2', options: {} });
|
||||
await expect(command.action({ 'new-notebook': 'folder2_1', options: { parent: true } })).rejects.toThrowError();
|
||||
});
|
||||
|
||||
it('should not be possible to create subfolder in ambiguous destination folder', async () => {
|
||||
const command = setupCommandForTesting(Command);
|
||||
await command.action({ 'new-notebook': 'folder3', options: {} });
|
||||
await command.action({ 'new-notebook': 'folder3', options: {} }); // ambiguous folder
|
||||
await expect(command.action({ 'new-notebook': 'folder3_1', options: { parent: 'folder3' } })).rejects.toThrowError();
|
||||
|
||||
// check if duplicate entries have been created.
|
||||
const folderAll = await Folder.all();
|
||||
const folders3 = folderAll.filter(x => x.title === 'folder3');
|
||||
expect(folders3.length).toBe(2);
|
||||
|
||||
// check if something has been created in one of the duplicate entries.
|
||||
expect(await Folder.childrenIds(folders3[0].id)).toEqual([]);
|
||||
expect(await Folder.childrenIds(folders3[1].id)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
65
packages/app-cli/app/command-mkbook.ts
Normal file
65
packages/app-cli/app/command-mkbook.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import BaseModel from '@joplin/lib/BaseModel';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
import { FolderEntity } from '@joplin/lib/services/database/types';
|
||||
|
||||
class Command extends BaseCommand {
|
||||
public usage() {
|
||||
return 'mkbook <new-notebook>';
|
||||
}
|
||||
|
||||
public description() {
|
||||
return _('Creates a new notebook.');
|
||||
}
|
||||
|
||||
public options() {
|
||||
return [
|
||||
['-p, --parent <parent-notebook>', _('Create a new notebook under a parent notebook.')],
|
||||
];
|
||||
}
|
||||
|
||||
// validDestinationFolder check for presents and ambiguous folders
|
||||
public async validDestinationFolder(targetFolder: string) {
|
||||
|
||||
const destinationFolder = await app().loadItem(BaseModel.TYPE_FOLDER, targetFolder);
|
||||
if (!destinationFolder) {
|
||||
throw new Error(_('Cannot find: "%s"', targetFolder));
|
||||
}
|
||||
|
||||
const destinationDups = await Folder.search({ titlePattern: targetFolder, limit: 2 });
|
||||
if (destinationDups.length > 1) {
|
||||
throw new Error(_('Ambiguous notebook "%s". Please use short notebook id instead - press "ti" to see the short notebook id', targetFolder));
|
||||
}
|
||||
|
||||
return destinationFolder;
|
||||
}
|
||||
|
||||
public async saveAndSwitchFolder(newFolder: FolderEntity) {
|
||||
|
||||
const folder = await Folder.save(newFolder, { userSideValidation: true });
|
||||
app().switchCurrentFolder(folder);
|
||||
|
||||
}
|
||||
|
||||
public async action(args: any) {
|
||||
const targetFolder = args.options.parent;
|
||||
|
||||
const newFolder: FolderEntity = {
|
||||
title: args['new-notebook'],
|
||||
};
|
||||
|
||||
if (targetFolder) {
|
||||
|
||||
const destinationFolder = await this.validDestinationFolder(targetFolder);
|
||||
newFolder.parent_id = destinationFolder.id;
|
||||
await this.saveAndSwitchFolder(newFolder);
|
||||
|
||||
} else {
|
||||
await this.saveAndSwitchFolder(newFolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Command;
|
@@ -23,19 +23,19 @@ function settingTypeToSchemaType(type: SettingItemType): string {
|
||||
}
|
||||
|
||||
class Command extends BaseCommand {
|
||||
usage() {
|
||||
public usage() {
|
||||
return 'settingschema <file>';
|
||||
}
|
||||
|
||||
description() {
|
||||
public description() {
|
||||
return 'Build the setting schema file';
|
||||
}
|
||||
|
||||
enabled() {
|
||||
public enabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
async action(args: any) {
|
||||
public async action(args: any) {
|
||||
const schema: Record<string, any> = {
|
||||
title: 'JSON schema for Joplin setting files',
|
||||
'$id': Setting.schemaUrl,
|
||||
|
@@ -9,11 +9,11 @@ import { appTypeToLockType } from '@joplin/lib/services/synchronizer/LockHandler
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { OneDriveApiNodeUtils } = require('@joplin/lib/onedrive-api-node-utils.js');
|
||||
const { reg } = require('@joplin/lib/registry.js');
|
||||
import { reg } from '@joplin/lib/registry';
|
||||
const { cliUtils } = require('./cli-utils.js');
|
||||
const md5 = require('md5');
|
||||
const locker = require('proper-lockfile');
|
||||
const fs = require('fs-extra');
|
||||
import * as locker from 'proper-lockfile';
|
||||
import { pathExists, writeFile } from 'fs-extra';
|
||||
|
||||
class Command extends BaseCommand {
|
||||
|
||||
@@ -21,15 +21,15 @@ class Command extends BaseCommand {
|
||||
private releaseLockFn_: Function = null;
|
||||
private oneDriveApiUtils_: any = null;
|
||||
|
||||
usage() {
|
||||
public usage() {
|
||||
return 'sync';
|
||||
}
|
||||
|
||||
description() {
|
||||
public description() {
|
||||
return _('Synchronises with remote storage.');
|
||||
}
|
||||
|
||||
options() {
|
||||
public options() {
|
||||
return [
|
||||
['--target <target>', _('Sync to provided target (defaults to sync.target config value)')],
|
||||
['--upgrade', _('Upgrade the sync target to the latest version.')],
|
||||
@@ -37,24 +37,15 @@ class Command extends BaseCommand {
|
||||
];
|
||||
}
|
||||
|
||||
static async lockFile(filePath: string): Promise<Function> {
|
||||
private static async lockFile(filePath: string) {
|
||||
return locker.lock(filePath, { stale: 1000 * 60 * 5 });
|
||||
}
|
||||
|
||||
static isLocked(filePath: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
locker.check(filePath, (error: any, isLocked: boolean) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(isLocked);
|
||||
});
|
||||
});
|
||||
private static async isLocked(filePath: string) {
|
||||
return locker.check(filePath);
|
||||
}
|
||||
|
||||
async doAuth() {
|
||||
public async doAuth() {
|
||||
const syncTarget = reg.syncTarget(this.syncTargetId_);
|
||||
const syncTargetMd = SyncTargetRegistry.idToMetadata(this.syncTargetId_);
|
||||
|
||||
@@ -98,23 +89,23 @@ class Command extends BaseCommand {
|
||||
return false;
|
||||
}
|
||||
|
||||
cancelAuth() {
|
||||
public cancelAuth() {
|
||||
if (this.oneDriveApiUtils_) {
|
||||
this.oneDriveApiUtils_.cancelOAuthDance();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
doingAuth() {
|
||||
public doingAuth() {
|
||||
return !!this.oneDriveApiUtils_;
|
||||
}
|
||||
|
||||
async action(args: any) {
|
||||
public async action(args: any) {
|
||||
this.releaseLockFn_ = null;
|
||||
|
||||
// Lock is unique per profile/database
|
||||
const lockFilePath = `${require('os').tmpdir()}/synclock_${md5(escape(Setting.value('profileDir')))}`; // https://github.com/pvorb/node-md5/issues/41
|
||||
if (!(await fs.pathExists(lockFilePath))) await fs.writeFile(lockFilePath, 'synclock');
|
||||
if (!(await pathExists(lockFilePath))) await writeFile(lockFilePath, 'synclock');
|
||||
|
||||
const useLock = args.options.useLock !== 0;
|
||||
|
||||
@@ -247,7 +238,7 @@ class Command extends BaseCommand {
|
||||
cleanUp();
|
||||
}
|
||||
|
||||
async cancel() {
|
||||
public async cancel() {
|
||||
if (this.doingAuth()) {
|
||||
this.cancelAuth();
|
||||
return;
|
||||
@@ -272,7 +263,7 @@ class Command extends BaseCommand {
|
||||
this.syncTargetId_ = null;
|
||||
}
|
||||
|
||||
cancellable() {
|
||||
public cancellable() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@@ -18,19 +18,19 @@ function itemCount(args: any) {
|
||||
}
|
||||
|
||||
class Command extends BaseCommand {
|
||||
usage() {
|
||||
public usage() {
|
||||
return 'testing <command> [arg0]';
|
||||
}
|
||||
|
||||
description() {
|
||||
public description() {
|
||||
return 'testing';
|
||||
}
|
||||
|
||||
enabled() {
|
||||
public enabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
options(): any[] {
|
||||
public options(): any[] {
|
||||
return [
|
||||
['--folder-count <count>', 'Folders to create'],
|
||||
['--note-count <count>', 'Notes to create'],
|
||||
@@ -40,7 +40,7 @@ class Command extends BaseCommand {
|
||||
];
|
||||
}
|
||||
|
||||
async action(args: any) {
|
||||
public async action(args: any) {
|
||||
const { command, options } = args;
|
||||
|
||||
if (command === 'populate') {
|
||||
|
@@ -5,7 +5,7 @@ const stripAnsi = require('strip-ansi');
|
||||
const { handleAutocompletion } = require('../autocompletion.js');
|
||||
|
||||
export default class StatusBarWidget extends BaseWidget {
|
||||
constructor() {
|
||||
public constructor() {
|
||||
super();
|
||||
|
||||
this.promptState_ = null;
|
||||
@@ -14,20 +14,20 @@ export default class StatusBarWidget extends BaseWidget {
|
||||
this.items_ = [];
|
||||
}
|
||||
|
||||
get name() {
|
||||
public get name() {
|
||||
return 'statusBar';
|
||||
}
|
||||
|
||||
get canHaveFocus() {
|
||||
public get canHaveFocus() {
|
||||
return false;
|
||||
}
|
||||
|
||||
setItemAt(index: number, text: string) {
|
||||
public setItemAt(index: number, text: string) {
|
||||
this.items_[index] = stripAnsi(text).trim();
|
||||
this.invalidate();
|
||||
}
|
||||
|
||||
async prompt(initialText = '', promptString: any = null, options: any = null) {
|
||||
public async prompt(initialText = '', promptString: any = null, options: any = null) {
|
||||
if (this.promptState_) throw new Error('Another prompt already active');
|
||||
if (promptString === null) promptString = ':';
|
||||
if (options === null) options = {};
|
||||
@@ -53,15 +53,15 @@ export default class StatusBarWidget extends BaseWidget {
|
||||
return this.promptState_.promise;
|
||||
}
|
||||
|
||||
get promptActive() {
|
||||
public get promptActive() {
|
||||
return !!this.promptState_;
|
||||
}
|
||||
|
||||
get history() {
|
||||
public get history() {
|
||||
return this.history_;
|
||||
}
|
||||
|
||||
resetCursor() {
|
||||
public resetCursor() {
|
||||
if (!this.promptActive) return;
|
||||
if (!this.inputEventEmitter_) return;
|
||||
|
||||
@@ -70,7 +70,7 @@ export default class StatusBarWidget extends BaseWidget {
|
||||
this.term.moveTo(this.absoluteInnerX + termutils.textLength(this.promptState_.promptString) + this.inputEventEmitter_.getInput().length, this.absoluteInnerY);
|
||||
}
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
super.render();
|
||||
|
||||
const doSaveCursor = !this.promptActive;
|
||||
|
@@ -35,7 +35,7 @@ export default class PluginRunner extends BasePluginRunner {
|
||||
private eventHandlers_: EventHandlers = {};
|
||||
private activeSandboxCalls_: any = {};
|
||||
|
||||
constructor() {
|
||||
public constructor() {
|
||||
super();
|
||||
|
||||
this.eventHandler = this.eventHandler.bind(this);
|
||||
@@ -64,7 +64,7 @@ export default class PluginRunner extends BasePluginRunner {
|
||||
};
|
||||
}
|
||||
|
||||
async run(plugin: Plugin, sandbox: Global): Promise<void> {
|
||||
public async run(plugin: Plugin, sandbox: Global): Promise<void> {
|
||||
return new Promise((resolve: Function, reject: Function) => {
|
||||
const onStarted = () => {
|
||||
plugin.off('started', onStarted);
|
||||
|
@@ -34,7 +34,7 @@
|
||||
],
|
||||
"owner": "Laurent Cozic"
|
||||
},
|
||||
"version": "2.10.0",
|
||||
"version": "2.10.3",
|
||||
"bin": "./main.js",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
@@ -51,7 +51,7 @@
|
||||
"keytar": "7.9.0",
|
||||
"md5": "2.3.0",
|
||||
"node-rsa": "1.1.1",
|
||||
"open": "8.4.1",
|
||||
"open": "8.4.2",
|
||||
"proper-lockfile": "4.1.2",
|
||||
"read-chunk": "2.1.0",
|
||||
"server-destroy": "1.0.1",
|
||||
@@ -72,8 +72,9 @@
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"@types/jest": "29.2.6",
|
||||
"@types/node": "18.11.18",
|
||||
"@types/proper-lockfile": "^4.1.2",
|
||||
"gulp": "4.0.2",
|
||||
"jest": "29.4.2",
|
||||
"jest": "29.4.3",
|
||||
"temp": "0.9.4",
|
||||
"typescript": "4.9.4"
|
||||
}
|
||||
|
@@ -33,7 +33,7 @@ export default class ElectronAppWrapper {
|
||||
private pluginWindows_: PluginWindows = {};
|
||||
private initialCallbackUrl_: string = null;
|
||||
|
||||
constructor(electronApp: any, env: string, profilePath: string, isDebugMode: boolean, initialCallbackUrl: string) {
|
||||
public constructor(electronApp: any, env: string, profilePath: string, isDebugMode: boolean, initialCallbackUrl: string) {
|
||||
this.electronApp_ = electronApp;
|
||||
this.env_ = env;
|
||||
this.isDebugMode_ = isDebugMode;
|
||||
@@ -41,31 +41,31 @@ export default class ElectronAppWrapper {
|
||||
this.initialCallbackUrl_ = initialCallbackUrl;
|
||||
}
|
||||
|
||||
electronApp() {
|
||||
public electronApp() {
|
||||
return this.electronApp_;
|
||||
}
|
||||
|
||||
setLogger(v: Logger) {
|
||||
public setLogger(v: Logger) {
|
||||
this.logger_ = v;
|
||||
}
|
||||
|
||||
logger() {
|
||||
public logger() {
|
||||
return this.logger_;
|
||||
}
|
||||
|
||||
window() {
|
||||
public window() {
|
||||
return this.win_;
|
||||
}
|
||||
|
||||
env() {
|
||||
public env() {
|
||||
return this.env_;
|
||||
}
|
||||
|
||||
initialCallbackUrl() {
|
||||
public initialCallbackUrl() {
|
||||
return this.initialCallbackUrl_;
|
||||
}
|
||||
|
||||
createWindow() {
|
||||
public createWindow() {
|
||||
// Set to true to view errors if the application does not start
|
||||
const debugEarlyBugs = this.env_ === 'dev' || this.isDebugMode_;
|
||||
|
||||
@@ -236,11 +236,11 @@ export default class ElectronAppWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
registerPluginWindow(pluginId: string, window: any) {
|
||||
public registerPluginWindow(pluginId: string, window: any) {
|
||||
this.pluginWindows_[pluginId] = window;
|
||||
}
|
||||
|
||||
async waitForElectronAppReady() {
|
||||
public async waitForElectronAppReady() {
|
||||
if (this.electronApp().isReady()) return Promise.resolve();
|
||||
|
||||
return new Promise<void>((resolve) => {
|
||||
@@ -253,25 +253,25 @@ export default class ElectronAppWrapper {
|
||||
});
|
||||
}
|
||||
|
||||
quit() {
|
||||
public quit() {
|
||||
this.electronApp_.quit();
|
||||
}
|
||||
|
||||
exit(errorCode = 0) {
|
||||
public exit(errorCode = 0) {
|
||||
this.electronApp_.exit(errorCode);
|
||||
}
|
||||
|
||||
trayShown() {
|
||||
public trayShown() {
|
||||
return !!this.tray_;
|
||||
}
|
||||
|
||||
// This method is used in macOS only to hide the whole app (and not just the main window)
|
||||
// including the menu bar. This follows the macOS way of hiding an app.
|
||||
hide() {
|
||||
public hide() {
|
||||
this.electronApp_.hide();
|
||||
}
|
||||
|
||||
buildDir() {
|
||||
public buildDir() {
|
||||
if (this.buildDir_) return this.buildDir_;
|
||||
let dir = `${__dirname}/build`;
|
||||
if (!fs.pathExistsSync(dir)) {
|
||||
@@ -283,7 +283,7 @@ export default class ElectronAppWrapper {
|
||||
return dir;
|
||||
}
|
||||
|
||||
trayIconFilename_() {
|
||||
private trayIconFilename_() {
|
||||
let output = '';
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
@@ -298,7 +298,7 @@ export default class ElectronAppWrapper {
|
||||
}
|
||||
|
||||
// Note: this must be called only after the "ready" event of the app has been dispatched
|
||||
createTray(contextMenu: any) {
|
||||
public createTray(contextMenu: any) {
|
||||
try {
|
||||
this.tray_ = new Tray(`${this.buildDir()}/icons/${this.trayIconFilename_()}`);
|
||||
this.tray_.setToolTip(this.electronApp_.name);
|
||||
@@ -312,13 +312,13 @@ export default class ElectronAppWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
destroyTray() {
|
||||
public destroyTray() {
|
||||
if (!this.tray_) return;
|
||||
this.tray_.destroy();
|
||||
this.tray_ = null;
|
||||
}
|
||||
|
||||
ensureSingleInstance() {
|
||||
public ensureSingleInstance() {
|
||||
if (this.env_ === 'dev') return false;
|
||||
|
||||
const gotTheLock = this.electronApp_.requestSingleInstanceLock();
|
||||
@@ -347,7 +347,7 @@ export default class ElectronAppWrapper {
|
||||
return false;
|
||||
}
|
||||
|
||||
async start() {
|
||||
public async start() {
|
||||
// Since we are doing other async things before creating the window, we might miss
|
||||
// the "ready" event. So we use the function below to make sure that the app is ready.
|
||||
await this.waitForElectronAppReady();
|
||||
@@ -375,7 +375,7 @@ export default class ElectronAppWrapper {
|
||||
});
|
||||
}
|
||||
|
||||
async openCallbackUrl(url: string) {
|
||||
public async openCallbackUrl(url: string) {
|
||||
this.win_.webContents.send('asynchronous-message', 'openCallbackUrl', {
|
||||
url: url,
|
||||
});
|
||||
|
@@ -21,7 +21,7 @@ export class Bridge {
|
||||
private electronWrapper_: ElectronAppWrapper;
|
||||
private lastSelectedPaths_: LastSelectedPath;
|
||||
|
||||
constructor(electronWrapper: ElectronAppWrapper) {
|
||||
public constructor(electronWrapper: ElectronAppWrapper) {
|
||||
this.electronWrapper_ = electronWrapper;
|
||||
this.lastSelectedPaths_ = {
|
||||
file: null,
|
||||
@@ -29,11 +29,11 @@ export class Bridge {
|
||||
};
|
||||
}
|
||||
|
||||
electronApp() {
|
||||
public electronApp() {
|
||||
return this.electronWrapper_;
|
||||
}
|
||||
|
||||
electronIsDev() {
|
||||
public electronIsDev() {
|
||||
return !this.electronApp().electronApp().isPackaged;
|
||||
}
|
||||
|
||||
@@ -60,11 +60,11 @@ export class Bridge {
|
||||
return `${__dirname}/vendor`;
|
||||
}
|
||||
|
||||
env() {
|
||||
public env() {
|
||||
return this.electronWrapper_.env();
|
||||
}
|
||||
|
||||
processArgv() {
|
||||
public processArgv() {
|
||||
return process.argv;
|
||||
}
|
||||
|
||||
@@ -114,44 +114,44 @@ export class Bridge {
|
||||
});
|
||||
}
|
||||
|
||||
window() {
|
||||
public window() {
|
||||
return this.electronWrapper_.window();
|
||||
}
|
||||
|
||||
showItemInFolder(fullPath: string) {
|
||||
public showItemInFolder(fullPath: string) {
|
||||
return require('electron').shell.showItemInFolder(toSystemSlashes(fullPath));
|
||||
}
|
||||
|
||||
newBrowserWindow(options: any) {
|
||||
public newBrowserWindow(options: any) {
|
||||
return new BrowserWindow(options);
|
||||
}
|
||||
|
||||
windowContentSize() {
|
||||
public windowContentSize() {
|
||||
if (!this.window()) return { width: 0, height: 0 };
|
||||
const s = this.window().getContentSize();
|
||||
return { width: s[0], height: s[1] };
|
||||
}
|
||||
|
||||
windowSize() {
|
||||
public windowSize() {
|
||||
if (!this.window()) return { width: 0, height: 0 };
|
||||
const s = this.window().getSize();
|
||||
return { width: s[0], height: s[1] };
|
||||
}
|
||||
|
||||
windowSetSize(width: number, height: number) {
|
||||
public windowSetSize(width: number, height: number) {
|
||||
if (!this.window()) return;
|
||||
return this.window().setSize(width, height);
|
||||
}
|
||||
|
||||
openDevTools() {
|
||||
public openDevTools() {
|
||||
return this.window().webContents.openDevTools();
|
||||
}
|
||||
|
||||
closeDevTools() {
|
||||
public closeDevTools() {
|
||||
return this.window().webContents.closeDevTools();
|
||||
}
|
||||
|
||||
async showSaveDialog(options: any) {
|
||||
public async showSaveDialog(options: any) {
|
||||
const { dialog } = require('electron');
|
||||
if (!options) options = {};
|
||||
if (!('defaultPath' in options) && this.lastSelectedPaths_.file) options.defaultPath = this.lastSelectedPaths_.file;
|
||||
@@ -162,7 +162,7 @@ export class Bridge {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
async showOpenDialog(options: OpenDialogOptions = null) {
|
||||
public async showOpenDialog(options: OpenDialogOptions = null) {
|
||||
const { dialog } = require('electron');
|
||||
if (!options) options = {};
|
||||
let fileType = 'file';
|
||||
@@ -177,13 +177,13 @@ export class Bridge {
|
||||
}
|
||||
|
||||
// Don't use this directly - call one of the showXxxxxxxMessageBox() instead
|
||||
showMessageBox_(window: any, options: any): number {
|
||||
private showMessageBox_(window: any, options: any): number {
|
||||
const { dialog } = require('electron');
|
||||
if (!window) window = this.window();
|
||||
return dialog.showMessageBoxSync(window, options);
|
||||
}
|
||||
|
||||
showErrorMessageBox(message: string) {
|
||||
public showErrorMessageBox(message: string) {
|
||||
return this.showMessageBox_(this.window(), {
|
||||
type: 'error',
|
||||
message: message,
|
||||
@@ -191,7 +191,7 @@ export class Bridge {
|
||||
});
|
||||
}
|
||||
|
||||
showConfirmMessageBox(message: string, options: any = null) {
|
||||
public showConfirmMessageBox(message: string, options: any = null) {
|
||||
options = {
|
||||
buttons: [_('OK'), _('Cancel')],
|
||||
...options,
|
||||
@@ -208,7 +208,7 @@ export class Bridge {
|
||||
}
|
||||
|
||||
/* returns the index of the clicked button */
|
||||
showMessageBox(message: string, options: any = null) {
|
||||
public showMessageBox(message: string, options: any = null) {
|
||||
if (options === null) options = {};
|
||||
|
||||
const result = this.showMessageBox_(this.window(), Object.assign({}, {
|
||||
@@ -220,7 +220,7 @@ export class Bridge {
|
||||
return result;
|
||||
}
|
||||
|
||||
showInfoMessageBox(message: string, options: any = {}) {
|
||||
public showInfoMessageBox(message: string, options: any = {}) {
|
||||
const result = this.showMessageBox_(this.window(), Object.assign({}, {
|
||||
type: 'info',
|
||||
message: message,
|
||||
@@ -229,35 +229,35 @@ export class Bridge {
|
||||
return result === 0;
|
||||
}
|
||||
|
||||
setLocale(locale: string) {
|
||||
public setLocale(locale: string) {
|
||||
setLocale(locale);
|
||||
}
|
||||
|
||||
get Menu() {
|
||||
public get Menu() {
|
||||
return require('electron').Menu;
|
||||
}
|
||||
|
||||
get MenuItem() {
|
||||
public get MenuItem() {
|
||||
return require('electron').MenuItem;
|
||||
}
|
||||
|
||||
openExternal(url: string) {
|
||||
public openExternal(url: string) {
|
||||
return require('electron').shell.openExternal(url);
|
||||
}
|
||||
|
||||
async openItem(fullPath: string) {
|
||||
public async openItem(fullPath: string) {
|
||||
return require('electron').shell.openPath(toSystemSlashes(fullPath));
|
||||
}
|
||||
|
||||
screen() {
|
||||
public screen() {
|
||||
return require('electron').screen;
|
||||
}
|
||||
|
||||
shouldUseDarkColors() {
|
||||
public shouldUseDarkColors() {
|
||||
return nativeTheme.shouldUseDarkColors;
|
||||
}
|
||||
|
||||
addEventListener(name: string, fn: Function) {
|
||||
public addEventListener(name: string, fn: Function) {
|
||||
if (name === 'nativeThemeUpdated') {
|
||||
nativeTheme.on('updated', fn);
|
||||
} else {
|
||||
@@ -265,7 +265,7 @@ export class Bridge {
|
||||
}
|
||||
}
|
||||
|
||||
restart(linuxSafeRestart = true) {
|
||||
public restart(linuxSafeRestart = true) {
|
||||
// Note that in this case we are not sending the "appClose" event
|
||||
// to notify services and component that the app is about to close
|
||||
// but for the current use-case it's not really needed.
|
||||
|
@@ -2,7 +2,6 @@ const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const { clipboard } = require('electron');
|
||||
import ExtensionBadge from './ExtensionBadge';
|
||||
import bridge from '../services/bridge';
|
||||
import { themeStyle } from '@joplin/lib/theme';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import ClipperServer from '@joplin/lib/ClipperServer';
|
||||
@@ -11,37 +10,29 @@ import EncryptionService from '@joplin/lib/services/e2ee/EncryptionService';
|
||||
import { AppState } from '../app.reducer';
|
||||
|
||||
class ClipperConfigScreenComponent extends React.Component {
|
||||
constructor() {
|
||||
public constructor() {
|
||||
super();
|
||||
|
||||
this.copyToken_click = this.copyToken_click.bind(this);
|
||||
}
|
||||
|
||||
disableClipperServer_click() {
|
||||
private disableClipperServer_click() {
|
||||
Setting.setValue('clipperServer.autoStart', false);
|
||||
void ClipperServer.instance().stop();
|
||||
}
|
||||
|
||||
enableClipperServer_click() {
|
||||
private enableClipperServer_click() {
|
||||
Setting.setValue('clipperServer.autoStart', true);
|
||||
void ClipperServer.instance().start();
|
||||
}
|
||||
|
||||
chromeButton_click() {
|
||||
void bridge().openExternal('https://chrome.google.com/webstore/detail/joplin-web-clipper/alofnhikmmkdbbbgpnglcpdollgjjfek');
|
||||
}
|
||||
|
||||
firefoxButton_click() {
|
||||
void bridge().openExternal('https://addons.mozilla.org/en-US/firefox/addon/joplin-web-clipper/');
|
||||
}
|
||||
|
||||
copyToken_click() {
|
||||
private copyToken_click() {
|
||||
clipboard.writeText(this.props.apiToken);
|
||||
|
||||
alert(_('Token has been copied to the clipboard!'));
|
||||
}
|
||||
|
||||
renewToken_click() {
|
||||
private renewToken_click() {
|
||||
if (confirm(_('Are you sure you want to renew the authorisation token?'))) {
|
||||
void EncryptionService.instance()
|
||||
.generateApiToken()
|
||||
@@ -52,7 +43,7 @@ class ClipperConfigScreenComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const containerStyle = Object.assign({}, theme.containerStyle, {
|
||||
|
@@ -26,9 +26,9 @@ const settingKeyToControl: any = {
|
||||
|
||||
class ConfigScreenComponent extends React.Component<any, any> {
|
||||
|
||||
rowStyle_: any = null;
|
||||
private rowStyle_: any = null;
|
||||
|
||||
constructor(props: any) {
|
||||
public constructor(props: any) {
|
||||
super(props);
|
||||
|
||||
shared.init(this, reg);
|
||||
@@ -55,15 +55,15 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
||||
this.handleSettingButton = this.handleSettingButton.bind(this);
|
||||
}
|
||||
|
||||
async checkSyncConfig_() {
|
||||
private async checkSyncConfig_() {
|
||||
await shared.checkSyncConfig(this, this.state.settings);
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
public UNSAFE_componentWillMount() {
|
||||
this.setState({ settings: this.props.settings });
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
public componentDidMount() {
|
||||
if (this.props.defaultSection) {
|
||||
this.setState({ selectedSectionName: this.props.defaultSection }, () => {
|
||||
this.switchSection(this.props.defaultSection);
|
||||
@@ -93,7 +93,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
||||
}
|
||||
}
|
||||
|
||||
sectionByName(name: string) {
|
||||
public sectionByName(name: string) {
|
||||
const sections = shared.settingsSections({ device: 'desktop', settings: this.state.settings });
|
||||
for (const section of sections) {
|
||||
if (section.name === name) return section;
|
||||
@@ -102,7 +102,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
||||
throw new Error(`Invalid section name: ${name}`);
|
||||
}
|
||||
|
||||
screenFromName(screenName: string) {
|
||||
public screenFromName(screenName: string) {
|
||||
if (screenName === 'encryption') return <EncryptionConfigScreen/>;
|
||||
if (screenName === 'server') return <ClipperConfigScreen themeId={this.props.themeId}/>;
|
||||
if (screenName === 'keymap') return <KeymapConfigScreen themeId={this.props.themeId}/>;
|
||||
@@ -110,7 +110,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
||||
throw new Error(`Invalid screen name: ${screenName}`);
|
||||
}
|
||||
|
||||
switchSection(name: string) {
|
||||
public switchSection(name: string) {
|
||||
const section = this.sectionByName(name);
|
||||
let screenName = '';
|
||||
if (section.isScreen) {
|
||||
@@ -125,11 +125,11 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
||||
this.setState({ selectedSectionName: section.name, screenName: screenName });
|
||||
}
|
||||
|
||||
sidebar_selectionChange(event: any) {
|
||||
private sidebar_selectionChange(event: any) {
|
||||
this.switchSection(event.section.name);
|
||||
}
|
||||
|
||||
renderSectionDescription(section: any) {
|
||||
public renderSectionDescription(section: any) {
|
||||
const description = Setting.sectionDescription(section.name);
|
||||
if (!description) return null;
|
||||
|
||||
@@ -141,7 +141,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
||||
);
|
||||
}
|
||||
|
||||
sectionToComponent(key: string, section: any, settings: any, selected: boolean) {
|
||||
public sectionToComponent(key: string, section: any, settings: any, selected: boolean) {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const createSettingComponents = (advanced: boolean) => {
|
||||
@@ -284,7 +284,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
||||
return description ? <div style={this.descriptionStyle(themeId)}>{description}</div> : null;
|
||||
}
|
||||
|
||||
settingToComponent(key: string, value: any) {
|
||||
public settingToComponent(key: string, value: any) {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const output: any = null;
|
||||
@@ -657,26 +657,26 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
||||
}
|
||||
}
|
||||
|
||||
async onApplyClick() {
|
||||
public async onApplyClick() {
|
||||
shared.saveSettings(this);
|
||||
await this.checkNeedRestart();
|
||||
}
|
||||
|
||||
async onSaveClick() {
|
||||
public async onSaveClick() {
|
||||
shared.saveSettings(this);
|
||||
await this.checkNeedRestart();
|
||||
this.props.dispatch({ type: 'NAV_BACK' });
|
||||
}
|
||||
|
||||
onCancelClick() {
|
||||
public onCancelClick() {
|
||||
this.props.dispatch({ type: 'NAV_BACK' });
|
||||
}
|
||||
|
||||
hasChanges() {
|
||||
public hasChanges() {
|
||||
return !!this.state.changedSettingKeys.length;
|
||||
}
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const style = Object.assign({},
|
||||
|
@@ -13,19 +13,19 @@ interface Props {
|
||||
|
||||
class DropboxLoginScreenComponent extends React.Component<any, any> {
|
||||
|
||||
shared_: any;
|
||||
private shared_: any;
|
||||
|
||||
constructor(props: Props) {
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.shared_ = new Shared(this, (msg: string) => bridge().showInfoMessageBox(msg), (msg: string) => bridge().showErrorMessageBox(msg));
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
public UNSAFE_componentWillMount() {
|
||||
this.shared_.refreshUrl();
|
||||
}
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
const style = this.props.style;
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
|
@@ -32,7 +32,7 @@ export default class ErrorBoundary extends React.Component<Props, State> {
|
||||
|
||||
public state: State = { error: null, errorInfo: null, pluginInfos: [], plugins: {} };
|
||||
|
||||
componentDidCatch(error: any, errorInfo: ErrorInfo) {
|
||||
public componentDidCatch(error: any, errorInfo: ErrorInfo) {
|
||||
if (typeof error === 'string') error = { message: error };
|
||||
|
||||
const pluginInfos: PluginInfo[] = [];
|
||||
@@ -58,7 +58,7 @@ export default class ErrorBoundary extends React.Component<Props, State> {
|
||||
this.setState({ error, errorInfo, pluginInfos, plugins });
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
public componentDidMount() {
|
||||
const onAppClose = () => {
|
||||
ipcRenderer.send('asynchronous-message', 'appCloseReply', {
|
||||
canClose: true,
|
||||
@@ -68,12 +68,12 @@ export default class ErrorBoundary extends React.Component<Props, State> {
|
||||
ipcRenderer.on('appClose', onAppClose);
|
||||
}
|
||||
|
||||
renderMessage() {
|
||||
public renderMessage() {
|
||||
const message = this.props.message || 'Joplin encountered a fatal error and could not continue.';
|
||||
return <p>{message}</p>;
|
||||
}
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
if (this.state.error) {
|
||||
const safeMode_click = async () => {
|
||||
Setting.setValue('isSafeMode', true);
|
||||
|
@@ -11,17 +11,17 @@ interface Props {
|
||||
}
|
||||
|
||||
class HelpButtonComponent extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.onClick = this.onClick.bind(this);
|
||||
}
|
||||
|
||||
onClick() {
|
||||
public onClick() {
|
||||
if (this.props.onClick) this.props.onClick();
|
||||
}
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const style = Object.assign({}, this.props.style, { color: theme.color, textDecoration: 'none' });
|
||||
const helpIconStyle = { flex: 0, width: 16, height: 16, marginLeft: 10 };
|
||||
|
@@ -9,7 +9,7 @@ interface Props {
|
||||
}
|
||||
|
||||
class IconButton extends React.Component<Props> {
|
||||
render() {
|
||||
public render() {
|
||||
const style = this.props.style;
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const iconStyle = {
|
||||
|
@@ -24,7 +24,7 @@ interface State {
|
||||
}
|
||||
|
||||
class ImportScreenComponent extends React.Component<Props, State> {
|
||||
UNSAFE_componentWillMount() {
|
||||
public UNSAFE_componentWillMount() {
|
||||
this.setState({
|
||||
doImport: true,
|
||||
filePath: this.props.filePath,
|
||||
@@ -32,7 +32,7 @@ class ImportScreenComponent extends React.Component<Props, State> {
|
||||
});
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(newProps: Props) {
|
||||
public UNSAFE_componentWillReceiveProps(newProps: Props) {
|
||||
if (newProps.filePath) {
|
||||
this.setState(
|
||||
{
|
||||
@@ -47,13 +47,13 @@ class ImportScreenComponent extends React.Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
public componentDidMount() {
|
||||
if (this.state.filePath && this.state.doImport) {
|
||||
void this.doImport();
|
||||
}
|
||||
}
|
||||
|
||||
addMessage(key: string, text: string) {
|
||||
public addMessage(key: string, text: string) {
|
||||
const messages = this.state.messages.slice();
|
||||
|
||||
messages.push({ key: key, text: text });
|
||||
@@ -61,7 +61,7 @@ class ImportScreenComponent extends React.Component<Props, State> {
|
||||
this.setState({ messages: messages });
|
||||
}
|
||||
|
||||
uniqueMessages() {
|
||||
public uniqueMessages() {
|
||||
const output = [];
|
||||
const messages = this.state.messages.slice();
|
||||
const foundKeys = [];
|
||||
@@ -74,7 +74,7 @@ class ImportScreenComponent extends React.Component<Props, State> {
|
||||
return output;
|
||||
}
|
||||
|
||||
async doImport() {
|
||||
public async doImport() {
|
||||
const filePath = this.props.filePath;
|
||||
const folderTitle = await Folder.findUniqueItemTitle(filename(filePath));
|
||||
|
||||
@@ -109,7 +109,7 @@ class ImportScreenComponent extends React.Component<Props, State> {
|
||||
this.setState({ doImport: false });
|
||||
}
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const messages = this.uniqueMessages();
|
||||
|
||||
|
@@ -1,4 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import Logger from '@joplin/lib/Logger';
|
||||
|
||||
const logger = Logger.create('ItemList');
|
||||
|
||||
interface Props {
|
||||
style: any;
|
||||
@@ -8,6 +11,7 @@ interface Props {
|
||||
onKeyDown?: Function;
|
||||
itemRenderer: Function;
|
||||
className?: string;
|
||||
onNoteDrop?: Function;
|
||||
}
|
||||
|
||||
interface State {
|
||||
@@ -20,7 +24,7 @@ class ItemList extends React.Component<Props, State> {
|
||||
private scrollTop_: number;
|
||||
private listRef: any;
|
||||
|
||||
constructor(props: Props) {
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.scrollTop_ = 0;
|
||||
@@ -29,14 +33,15 @@ class ItemList extends React.Component<Props, State> {
|
||||
|
||||
this.onScroll = this.onScroll.bind(this);
|
||||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
this.onDrop = this.onDrop.bind(this);
|
||||
}
|
||||
|
||||
visibleItemCount(props: Props = undefined) {
|
||||
public visibleItemCount(props: Props = undefined) {
|
||||
if (typeof props === 'undefined') props = this.props;
|
||||
return Math.ceil(props.style.height / props.itemHeight);
|
||||
}
|
||||
|
||||
updateStateItemIndexes(props: Props = undefined) {
|
||||
public updateStateItemIndexes(props: Props = undefined) {
|
||||
if (typeof props === 'undefined') props = this.props;
|
||||
|
||||
const topItemIndex = Math.floor(this.scrollTop_ / props.itemHeight);
|
||||
@@ -45,38 +50,66 @@ class ItemList extends React.Component<Props, State> {
|
||||
let bottomItemIndex = topItemIndex + (visibleItemCount - 1);
|
||||
if (bottomItemIndex >= props.items.length) bottomItemIndex = props.items.length - 1;
|
||||
|
||||
// EDGE CASE:
|
||||
// ref: https://github.com/laurent22/joplin/issues/4124
|
||||
// when the note list is hidden, visibleItemCount is negative, and scroll top is positive when a note is selected
|
||||
if (visibleItemCount < 0 && this.scrollTop_ > 0) {
|
||||
logger.warn('Resetting scrollTop to 0. visibleItemCount is negative, scrollTop is positive.');
|
||||
// we will reset the scroll top so that there is no blank space at the top of note list
|
||||
this.scrollTop_ = 0;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
topItemIndex: topItemIndex,
|
||||
bottomItemIndex: bottomItemIndex,
|
||||
});
|
||||
}
|
||||
|
||||
offsetTop() {
|
||||
public offsetTop() {
|
||||
return this.listRef.current ? this.listRef.current.offsetTop : 0;
|
||||
}
|
||||
|
||||
offsetScroll() {
|
||||
public offsetScroll() {
|
||||
return this.scrollTop_;
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
public UNSAFE_componentWillMount() {
|
||||
this.updateStateItemIndexes();
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(newProps: Props) {
|
||||
public UNSAFE_componentWillReceiveProps(newProps: Props) {
|
||||
this.updateStateItemIndexes(newProps);
|
||||
}
|
||||
|
||||
onScroll(event: any) {
|
||||
public componentDidUpdate(): void {
|
||||
// EDGE CASE
|
||||
// scroll top is not updated when item list visibility is toggled
|
||||
// if the user was at the bottom of the item list before hiding, blank spaces are added at the bottom of the item list
|
||||
if (this.offsetScroll() !== this.listRef.current?.scrollTop) {
|
||||
logger.warn(`scrollTop mismatch. Updating scrollTop with current listRef scrollTop(${this.listRef.current.scrollTop})`);
|
||||
// update scroll postion once if there is a mismatch in scroll position after showing item list
|
||||
this.onScroll({
|
||||
target: {
|
||||
scrollTop: this.listRef.current.scrollTop,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public onScroll(event: any) {
|
||||
this.scrollTop_ = event.target.scrollTop;
|
||||
this.updateStateItemIndexes();
|
||||
}
|
||||
|
||||
onKeyDown(event: any) {
|
||||
public onKeyDown(event: any) {
|
||||
if (this.props.onKeyDown) this.props.onKeyDown(event);
|
||||
}
|
||||
|
||||
makeItemIndexVisible(itemIndex: number) {
|
||||
public onDrop(event: any) {
|
||||
if (this.props.onNoteDrop) this.props.onNoteDrop(event);
|
||||
}
|
||||
|
||||
public makeItemIndexVisible(itemIndex: number) {
|
||||
const top = Math.min(this.props.items.length - 1, this.state.topItemIndex);
|
||||
const bottom = Math.max(0, this.state.bottomItemIndex);
|
||||
|
||||
@@ -113,7 +146,7 @@ class ItemList extends React.Component<Props, State> {
|
||||
// return true;
|
||||
// }
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
const items = this.props.items;
|
||||
const style = Object.assign({}, this.props.style, {
|
||||
overflowX: 'hidden',
|
||||
@@ -141,7 +174,7 @@ class ItemList extends React.Component<Props, State> {
|
||||
if (this.props.className) classes.push(this.props.className);
|
||||
|
||||
return (
|
||||
<div ref={this.listRef} className={classes.join(' ')} style={style} onScroll={this.onScroll} onKeyDown={this.onKeyDown}>
|
||||
<div ref={this.listRef} className={classes.join(' ')} style={style} onScroll={this.onScroll} onKeyDown={this.onKeyDown} onDrop={this.onDrop}>
|
||||
{itemComps}
|
||||
</div>
|
||||
);
|
||||
|
@@ -123,7 +123,7 @@ class MainScreenComponent extends React.Component<Props, State> {
|
||||
private styles_: any;
|
||||
private promptOnClose_: Function;
|
||||
|
||||
constructor(props: Props) {
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
@@ -250,11 +250,11 @@ class MainScreenComponent extends React.Component<Props, State> {
|
||||
return this.updateLayoutPluginViews(output, plugins);
|
||||
}
|
||||
|
||||
window_resize() {
|
||||
private window_resize() {
|
||||
this.updateRootLayoutSize();
|
||||
}
|
||||
|
||||
setupAppCloseHandling() {
|
||||
public setupAppCloseHandling() {
|
||||
this.waitForNotesSavedIID_ = null;
|
||||
|
||||
// This event is dispached from the main process when the app is about
|
||||
@@ -289,11 +289,11 @@ class MainScreenComponent extends React.Component<Props, State> {
|
||||
});
|
||||
}
|
||||
|
||||
notePropertiesDialog_close() {
|
||||
private notePropertiesDialog_close() {
|
||||
this.setState({ notePropertiesDialogOptions: {} });
|
||||
}
|
||||
|
||||
noteContentPropertiesDialog_close() {
|
||||
private noteContentPropertiesDialog_close() {
|
||||
this.setState({ noteContentPropertiesDialogOptions: {} });
|
||||
}
|
||||
|
||||
@@ -305,14 +305,14 @@ class MainScreenComponent extends React.Component<Props, State> {
|
||||
this.setState({ shareFolderDialogOptions: { visible: false, folderId: '' } });
|
||||
}
|
||||
|
||||
updateMainLayout(layout: LayoutItem) {
|
||||
public updateMainLayout(layout: LayoutItem) {
|
||||
this.props.dispatch({
|
||||
type: 'MAIN_LAYOUT_SET',
|
||||
value: layout,
|
||||
});
|
||||
}
|
||||
|
||||
updateRootLayoutSize() {
|
||||
public updateRootLayoutSize() {
|
||||
this.updateMainLayout(produce(this.props.mainLayout, (draft: any) => {
|
||||
const s = this.rootLayoutSize();
|
||||
draft.width = s.width;
|
||||
@@ -320,7 +320,7 @@ class MainScreenComponent extends React.Component<Props, State> {
|
||||
}));
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props, prevState: State) {
|
||||
public componentDidUpdate(prevProps: Props, prevState: State) {
|
||||
if (prevProps.style.width !== this.props.style.width ||
|
||||
prevProps.style.height !== this.props.style.height ||
|
||||
this.messageBoxVisible(prevProps) !== this.messageBoxVisible(this.props)
|
||||
@@ -383,24 +383,24 @@ class MainScreenComponent extends React.Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
layoutModeListenerKeyDown(event: any) {
|
||||
public layoutModeListenerKeyDown(event: any) {
|
||||
if (event.key !== 'Escape') return;
|
||||
if (!this.props.layoutMoveMode) return;
|
||||
void CommandService.instance().execute('toggleLayoutMoveMode');
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
public componentDidMount() {
|
||||
window.addEventListener('keydown', this.layoutModeListenerKeyDown);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
public componentWillUnmount() {
|
||||
this.unregisterCommands();
|
||||
|
||||
window.removeEventListener('resize', this.window_resize);
|
||||
window.removeEventListener('keydown', this.layoutModeListenerKeyDown);
|
||||
}
|
||||
|
||||
async waitForNoteToSaved(noteId: string) {
|
||||
public async waitForNoteToSaved(noteId: string) {
|
||||
while (noteId && this.props.editorNoteStatuses[noteId] === 'saving') {
|
||||
// eslint-disable-next-line no-console
|
||||
console.info('Waiting for note to be saved...', this.props.editorNoteStatuses);
|
||||
@@ -408,7 +408,7 @@ class MainScreenComponent extends React.Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
async printTo_(target: string, options: any) {
|
||||
public async printTo_(target: string, options: any) {
|
||||
// Concurrent print calls are disallowed to avoid incorrect settings being restored upon completion
|
||||
if (this.isPrinting_) {
|
||||
// eslint-disable-next-line no-console
|
||||
@@ -449,23 +449,23 @@ class MainScreenComponent extends React.Component<Props, State> {
|
||||
this.isPrinting_ = false;
|
||||
}
|
||||
|
||||
rootLayoutSize() {
|
||||
public rootLayoutSize() {
|
||||
return {
|
||||
width: window.innerWidth,
|
||||
height: this.rowHeight(),
|
||||
};
|
||||
}
|
||||
|
||||
rowHeight() {
|
||||
public rowHeight() {
|
||||
if (!this.props) return 0;
|
||||
return this.props.style.height - (this.messageBoxVisible() ? this.messageBoxHeight() : 0);
|
||||
}
|
||||
|
||||
messageBoxHeight() {
|
||||
public messageBoxHeight() {
|
||||
return 50;
|
||||
}
|
||||
|
||||
styles(themeId: number, width: number, height: number, messageBoxVisible: boolean) {
|
||||
public styles(themeId: number, width: number, height: number, messageBoxVisible: boolean) {
|
||||
const styleKey = [themeId, width, height, messageBoxVisible].join('_');
|
||||
if (styleKey === this.styleKey_) return this.styles_;
|
||||
|
||||
@@ -539,7 +539,7 @@ class MainScreenComponent extends React.Component<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
renderNotification(theme: any, styles: any) {
|
||||
public renderNotification(theme: any, styles: any) {
|
||||
if (!this.messageBoxVisible()) return null;
|
||||
|
||||
const onViewStatusScreen = () => {
|
||||
@@ -658,33 +658,33 @@ class MainScreenComponent extends React.Component<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
messageBoxVisible(props: Props = null) {
|
||||
public messageBoxVisible(props: Props = null) {
|
||||
if (!props) props = this.props;
|
||||
return props.hasDisabledSyncItems || props.showMissingMasterKeyMessage || props.showNeedUpgradingMasterKeyMessage || props.showShouldReencryptMessage || props.hasDisabledEncryptionItems || this.props.shouldUpgradeSyncTarget || props.isSafeMode || this.showShareInvitationNotification(props) || this.props.needApiAuth || this.props.showInstallTemplatesPlugin;
|
||||
}
|
||||
|
||||
registerCommands() {
|
||||
public registerCommands() {
|
||||
for (const command of commands) {
|
||||
CommandService.instance().registerRuntime(command.declaration.name, command.runtime(this));
|
||||
}
|
||||
}
|
||||
|
||||
unregisterCommands() {
|
||||
public unregisterCommands() {
|
||||
for (const command of commands) {
|
||||
CommandService.instance().unregisterRuntime(command.declaration.name);
|
||||
}
|
||||
}
|
||||
|
||||
resizableLayout_resize(event: any) {
|
||||
private resizableLayout_resize(event: any) {
|
||||
this.updateMainLayout(event.layout);
|
||||
}
|
||||
|
||||
resizableLayout_moveButtonClick(event: MoveButtonClickEvent) {
|
||||
private resizableLayout_moveButtonClick(event: MoveButtonClickEvent) {
|
||||
const newLayout = move(this.props.mainLayout, event.itemKey, event.direction);
|
||||
this.updateMainLayout(newLayout);
|
||||
}
|
||||
|
||||
resizableLayout_renderItem(key: string, event: any) {
|
||||
private resizableLayout_renderItem(key: string, event: any) {
|
||||
// Key should never be undefined but somehow it can happen, also not
|
||||
// clear how. For now in this case render nothing so that the app
|
||||
// doesn't crash.
|
||||
@@ -770,7 +770,7 @@ class MainScreenComponent extends React.Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
renderPluginDialogs() {
|
||||
public renderPluginDialogs() {
|
||||
const output = [];
|
||||
const infos = pluginUtils.viewInfosByType(this.props.plugins, 'webview');
|
||||
|
||||
@@ -801,7 +801,7 @@ class MainScreenComponent extends React.Component<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const style = Object.assign(
|
||||
{
|
||||
|
@@ -9,7 +9,7 @@ interface Props {
|
||||
}
|
||||
|
||||
class NavigatorComponent extends React.Component<Props> {
|
||||
UNSAFE_componentWillReceiveProps(newProps: Props) {
|
||||
public UNSAFE_componentWillReceiveProps(newProps: Props) {
|
||||
if (newProps.route) {
|
||||
const screenInfo = this.props.screens[newProps.route.routeName];
|
||||
const devMarker = Setting.value('env') === 'dev' ? ` (DEV - ${Setting.value('profileDir')})` : '';
|
||||
@@ -21,7 +21,7 @@ class NavigatorComponent extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
updateWindowTitle(title: string) {
|
||||
public updateWindowTitle(title: string) {
|
||||
try {
|
||||
if (bridge().window()) bridge().window().setTitle(title);
|
||||
} catch (error) {
|
||||
@@ -29,7 +29,7 @@ class NavigatorComponent extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
if (!this.props.route) throw new Error('Route must not be null');
|
||||
|
||||
const route = this.props.route;
|
||||
|
@@ -62,6 +62,7 @@ export default function useMarkupToHtml(deps: HookDependencies) {
|
||||
postMessageSyntax: 'ipcProxySendToHost',
|
||||
splitted: true,
|
||||
externalAssetsOnly: true,
|
||||
codeHighlightCacheKey: 'useMarkupToHtml',
|
||||
}, options));
|
||||
|
||||
return result;
|
||||
|
@@ -275,7 +275,6 @@ const NoteListComponent = (props: Props) => {
|
||||
onCheckboxClick={noteItem_checkboxClick}
|
||||
onDragStart={noteItem_dragStart}
|
||||
onNoteDragOver={noteItem_noteDragOver}
|
||||
onNoteDrop={noteItem_noteDrop}
|
||||
onTitleClick={noteItem_titleClick}
|
||||
onContextMenu={itemContextMenu}
|
||||
/>;
|
||||
@@ -526,6 +525,7 @@ const NoteListComponent = (props: Props) => {
|
||||
style={props.size}
|
||||
itemRenderer={renderItem}
|
||||
onKeyDown={onKeyDown}
|
||||
onNoteDrop={noteItem_noteDrop}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@@ -155,28 +155,26 @@ function NoteListControls(props: Props) {
|
||||
{renderNewNoteButtons()}
|
||||
<RowContainer>
|
||||
<SearchBar inputRef={searchBarRef}/>
|
||||
<SortOrderButtonsContainer>
|
||||
{showsSortOrderButtons() &&
|
||||
<StyledPairButtonL
|
||||
className="sort-order-field-button"
|
||||
tooltip={sortOrderFieldTooltip()}
|
||||
iconName={sortOrderFieldIcon()}
|
||||
level={ButtonLevel.Secondary}
|
||||
size={ButtonSize.Small}
|
||||
onClick={onSortOrderFieldButtonClick}
|
||||
/>
|
||||
}
|
||||
{showsSortOrderButtons() &&
|
||||
<StyledPairButtonR
|
||||
className="sort-order-reverse-button"
|
||||
tooltip={CommandService.instance().label('toggleNotesSortOrderReverse')}
|
||||
iconName={sortOrderReverseIcon()}
|
||||
level={ButtonLevel.Secondary}
|
||||
size={ButtonSize.Small}
|
||||
onClick={onSortOrderReverseButtonClick}
|
||||
/>
|
||||
}
|
||||
</SortOrderButtonsContainer>
|
||||
{showsSortOrderButtons() &&
|
||||
<SortOrderButtonsContainer>
|
||||
<StyledPairButtonL
|
||||
className="sort-order-field-button"
|
||||
tooltip={sortOrderFieldTooltip()}
|
||||
iconName={sortOrderFieldIcon()}
|
||||
level={ButtonLevel.Secondary}
|
||||
size={ButtonSize.Small}
|
||||
onClick={onSortOrderFieldButtonClick}
|
||||
/>
|
||||
<StyledPairButtonR
|
||||
className="sort-order-reverse-button"
|
||||
tooltip={CommandService.instance().label('toggleNotesSortOrderReverse')}
|
||||
iconName={sortOrderReverseIcon()}
|
||||
level={ButtonLevel.Secondary}
|
||||
size={ButtonSize.Small}
|
||||
onClick={onSortOrderReverseButtonClick}
|
||||
/>
|
||||
</SortOrderButtonsContainer>
|
||||
}
|
||||
</RowContainer>
|
||||
</StyledRoot>
|
||||
);
|
||||
@@ -184,7 +182,8 @@ function NoteListControls(props: Props) {
|
||||
|
||||
const mapStateToProps = (state: AppState) => {
|
||||
return {
|
||||
showNewNoteButtons: state.focusedField !== 'globalSearch',
|
||||
// TODO: showNewNoteButtons and the logic associated is not needed anymore.
|
||||
showNewNoteButtons: true,
|
||||
sortOrderButtonsVisible: state.settings['notes.sortOrder.buttonsVisible'],
|
||||
sortOrderField: state.settings['notes.sortOrder.field'],
|
||||
sortOrderReverse: state.settings['notes.sortOrder.reverse'],
|
||||
|
@@ -56,7 +56,6 @@ interface NoteListItemProps {
|
||||
onCheckboxClick: any;
|
||||
onDragStart: any;
|
||||
onNoteDragOver: any;
|
||||
onNoteDrop: any;
|
||||
onTitleClick: any;
|
||||
onContextMenu(event: React.MouseEvent<HTMLAnchorElement, MouseEvent>): void;
|
||||
}
|
||||
@@ -175,7 +174,6 @@ function NoteListItem(props: NoteListItemProps, ref: any) {
|
||||
<StyledRoot
|
||||
className={classNames}
|
||||
onDragOver={props.onNoteDragOver}
|
||||
onDrop={props.onNoteDrop}
|
||||
width={props.width}
|
||||
height={props.height}
|
||||
isProvisional={props.isProvisional}
|
||||
|
@@ -31,7 +31,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
||||
private styleKey_: number;
|
||||
private styles_: any;
|
||||
|
||||
constructor(props: Props) {
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.revisionsLink_click = this.revisionsLink_click.bind(this);
|
||||
@@ -56,17 +56,17 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
public componentDidMount() {
|
||||
void this.loadNote(this.props.noteId);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
public componentDidUpdate() {
|
||||
if (this.state.editedKey === null) {
|
||||
this.okButton.current.focus();
|
||||
}
|
||||
}
|
||||
|
||||
async loadNote(noteId: string) {
|
||||
public async loadNote(noteId: string) {
|
||||
if (!noteId) {
|
||||
this.setState({ formNote: null });
|
||||
} else {
|
||||
@@ -76,7 +76,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
latLongFromLocation(location: string) {
|
||||
public latLongFromLocation(location: string) {
|
||||
const o: any = {};
|
||||
const l = location.split(',');
|
||||
if (l.length === 2) {
|
||||
@@ -89,7 +89,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
||||
return o;
|
||||
}
|
||||
|
||||
noteToFormNote(note: NoteEntity) {
|
||||
public noteToFormNote(note: NoteEntity) {
|
||||
const formNote: any = {};
|
||||
|
||||
formNote.user_updated_time = time.formatMsToLocal(note.user_updated_time);
|
||||
@@ -113,7 +113,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
||||
return formNote;
|
||||
}
|
||||
|
||||
formNoteToNote(formNote: any) {
|
||||
public formNoteToNote(formNote: any) {
|
||||
const note = Object.assign({ id: formNote.id }, this.latLongFromLocation(formNote.location));
|
||||
note.user_created_time = time.formatLocalToMs(formNote.user_created_time);
|
||||
note.user_updated_time = time.formatLocalToMs(formNote.user_updated_time);
|
||||
@@ -127,7 +127,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
||||
return note;
|
||||
}
|
||||
|
||||
styles(themeId: number) {
|
||||
public styles(themeId: number) {
|
||||
const styleKey = themeId;
|
||||
if (styleKey === this.styleKey_) return this.styles_;
|
||||
|
||||
@@ -168,7 +168,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
||||
return this.styles_;
|
||||
}
|
||||
|
||||
async closeDialog(applyChanges: boolean) {
|
||||
public async closeDialog(applyChanges: boolean) {
|
||||
if (applyChanges) {
|
||||
await this.saveProperty();
|
||||
const note = this.formNoteToNote(this.state.formNote);
|
||||
@@ -183,16 +183,16 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
buttonRow_click(event: any) {
|
||||
private buttonRow_click(event: any) {
|
||||
void this.closeDialog(event.buttonName === 'ok');
|
||||
}
|
||||
|
||||
revisionsLink_click() {
|
||||
private revisionsLink_click() {
|
||||
void this.closeDialog(false);
|
||||
if (this.props.onRevisionLinkClick) this.props.onRevisionLinkClick();
|
||||
}
|
||||
|
||||
editPropertyButtonClick(key: string, initialValue: any) {
|
||||
public editPropertyButtonClick(key: string, initialValue: any) {
|
||||
this.setState({
|
||||
editedKey: key,
|
||||
editedValue: initialValue,
|
||||
@@ -207,7 +207,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
||||
}, 100);
|
||||
}
|
||||
|
||||
async saveProperty() {
|
||||
public async saveProperty() {
|
||||
if (!this.state.editedKey) return;
|
||||
|
||||
return new Promise((resolve: Function) => {
|
||||
@@ -233,7 +233,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
||||
});
|
||||
}
|
||||
|
||||
async cancelProperty() {
|
||||
public async cancelProperty() {
|
||||
return new Promise((resolve: Function) => {
|
||||
this.okButton.current.focus();
|
||||
this.setState({
|
||||
@@ -245,7 +245,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
||||
});
|
||||
}
|
||||
|
||||
createNoteField(key: string, value: any) {
|
||||
public createNoteField(key: string, value: any) {
|
||||
const styles = this.styles(this.props.themeId);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const labelComp = <label style={Object.assign({}, theme.textStyle, theme.controlBoxLabel)}>{this.formatLabel(key)}</label>;
|
||||
@@ -364,12 +364,12 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
formatLabel(key: string) {
|
||||
public formatLabel(key: string) {
|
||||
if (this.keyToLabel_[key]) return this.keyToLabel_[key];
|
||||
return key;
|
||||
}
|
||||
|
||||
formatValue(key: string, note: NoteEntity) {
|
||||
public formatValue(key: string, note: NoteEntity) {
|
||||
if (key === 'location') {
|
||||
if (!Number(note.latitude) && !Number(note.longitude)) return null;
|
||||
const dms = formatcoords(Number(note.latitude), Number(note.longitude));
|
||||
@@ -383,7 +383,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
||||
return (note as any)[key];
|
||||
}
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const formNote = this.state.formNote;
|
||||
|
||||
|
@@ -38,7 +38,7 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
|
||||
private viewerRef_: any;
|
||||
private helpButton_onClick: Function;
|
||||
|
||||
constructor(props: Props) {
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
@@ -57,7 +57,7 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
|
||||
this.webview_ipcMessage = this.webview_ipcMessage.bind(this);
|
||||
}
|
||||
|
||||
style() {
|
||||
public style() {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const style = {
|
||||
@@ -74,7 +74,7 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
|
||||
return style;
|
||||
}
|
||||
|
||||
async viewer_domReady() {
|
||||
private async viewer_domReady() {
|
||||
// this.viewerRef_.current.openDevTools();
|
||||
|
||||
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, this.props.noteId);
|
||||
@@ -90,7 +90,7 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
async importButton_onClick() {
|
||||
private async importButton_onClick() {
|
||||
if (!this.state.note) return;
|
||||
this.setState({ restoring: true });
|
||||
await RevisionService.instance().importRevisionNote(this.state.note);
|
||||
@@ -98,11 +98,11 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
|
||||
alert(RevisionService.instance().restoreSuccessMessage(this.state.note));
|
||||
}
|
||||
|
||||
backButton_click() {
|
||||
private backButton_click() {
|
||||
if (this.props.onBack) this.props.onBack();
|
||||
}
|
||||
|
||||
revisionList_onChange(event: any) {
|
||||
private revisionList_onChange(event: any) {
|
||||
const value = event.target.value;
|
||||
|
||||
if (!value) {
|
||||
@@ -119,7 +119,7 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
async reloadNote() {
|
||||
public async reloadNote() {
|
||||
let noteBody = '';
|
||||
let markupLanguage = MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN;
|
||||
if (!this.state.revisions.length || !this.state.currentRevId) {
|
||||
@@ -153,7 +153,7 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
|
||||
});
|
||||
}
|
||||
|
||||
async webview_ipcMessage(event: any) {
|
||||
private async webview_ipcMessage(event: any) {
|
||||
// For the revision view, we only suppport a minimal subset of the IPC messages.
|
||||
// For example, we don't need interactive checkboxes or sync between viewer and editor view.
|
||||
// We try to get most links work though, except for internal (joplin://) links.
|
||||
@@ -183,7 +183,7 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const style = this.style();
|
||||
|
||||
|
@@ -20,7 +20,7 @@ class NoteSearchBar extends React.Component<Props> {
|
||||
|
||||
private backgroundColor: any;
|
||||
|
||||
constructor(props: Props) {
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.searchInput_change = this.searchInput_change.bind(this);
|
||||
@@ -33,7 +33,7 @@ class NoteSearchBar extends React.Component<Props> {
|
||||
this.backgroundColor = undefined;
|
||||
}
|
||||
|
||||
style() {
|
||||
public style() {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const style = {
|
||||
@@ -46,7 +46,7 @@ class NoteSearchBar extends React.Component<Props> {
|
||||
return style;
|
||||
}
|
||||
|
||||
buttonIconComponent(iconName: string, clickHandler: any, isEnabled: boolean) {
|
||||
public buttonIconComponent(iconName: string, clickHandler: any, isEnabled: boolean) {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const searchButton = {
|
||||
@@ -74,12 +74,12 @@ class NoteSearchBar extends React.Component<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
searchInput_change(event: any) {
|
||||
private searchInput_change(event: any) {
|
||||
const query = event.currentTarget.value;
|
||||
this.triggerOnChange(query);
|
||||
}
|
||||
|
||||
searchInput_keyDown(event: any) {
|
||||
private searchInput_keyDown(event: any) {
|
||||
if (event.keyCode === 13) {
|
||||
// ENTER
|
||||
event.preventDefault();
|
||||
@@ -106,28 +106,28 @@ class NoteSearchBar extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
previousButton_click() {
|
||||
private previousButton_click() {
|
||||
if (this.props.onPrevious) this.props.onPrevious();
|
||||
}
|
||||
|
||||
nextButton_click() {
|
||||
private nextButton_click() {
|
||||
if (this.props.onNext) this.props.onNext();
|
||||
}
|
||||
|
||||
closeButton_click() {
|
||||
private closeButton_click() {
|
||||
if (this.props.onClose) this.props.onClose();
|
||||
}
|
||||
|
||||
triggerOnChange(query: string) {
|
||||
public triggerOnChange(query: string) {
|
||||
if (this.props.onChange) this.props.onChange(query);
|
||||
}
|
||||
|
||||
focus() {
|
||||
public focus() {
|
||||
(this.refs.searchInput as any).focus();
|
||||
(this.refs.searchInput as any).select();
|
||||
}
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
const query = this.props.query ? this.props.query : '';
|
||||
|
||||
// backgroundColor needs to cached to a local variable to prevent the
|
||||
|
@@ -11,7 +11,7 @@ interface Props {
|
||||
}
|
||||
|
||||
class NoteStatusBarComponent extends React.Component<Props> {
|
||||
style() {
|
||||
public style() {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const style = {
|
||||
@@ -24,7 +24,7 @@ class NoteStatusBarComponent extends React.Component<Props> {
|
||||
return style;
|
||||
}
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
const note = this.props.note;
|
||||
return <div style={this.style().root}>{time.formatMsToLocal(note.user_updated_time)}</div>;
|
||||
}
|
||||
|
@@ -17,7 +17,7 @@ export default class NoteTextViewerComponent extends React.Component<Props, any>
|
||||
private webviewRef_: any;
|
||||
private webviewListeners_: any = null;
|
||||
|
||||
constructor(props: any) {
|
||||
public constructor(props: any) {
|
||||
super(props);
|
||||
|
||||
this.webviewRef_ = React.createRef();
|
||||
@@ -41,20 +41,20 @@ export default class NoteTextViewerComponent extends React.Component<Props, any>
|
||||
this.webview_message = this.webview_message.bind(this);
|
||||
}
|
||||
|
||||
webview_domReady(event: any) {
|
||||
private webview_domReady(event: any) {
|
||||
this.domReady_ = true;
|
||||
if (this.props.onDomReady) this.props.onDomReady(event);
|
||||
}
|
||||
|
||||
webview_ipcMessage(event: any) {
|
||||
private webview_ipcMessage(event: any) {
|
||||
if (this.props.onIpcMessage) this.props.onIpcMessage(event);
|
||||
}
|
||||
|
||||
webview_load() {
|
||||
private webview_load() {
|
||||
this.webview_domReady({});
|
||||
}
|
||||
|
||||
webview_message(event: any) {
|
||||
private webview_message(event: any) {
|
||||
if (!event.data || event.data.target !== 'main') return;
|
||||
|
||||
const callName = event.data.name;
|
||||
@@ -68,11 +68,11 @@ export default class NoteTextViewerComponent extends React.Component<Props, any>
|
||||
}
|
||||
}
|
||||
|
||||
domReady() {
|
||||
public domReady() {
|
||||
return this.domReady_;
|
||||
}
|
||||
|
||||
initWebview() {
|
||||
public initWebview() {
|
||||
const wv = this.webviewRef_.current;
|
||||
|
||||
if (!this.webviewListeners_) {
|
||||
@@ -92,7 +92,7 @@ export default class NoteTextViewerComponent extends React.Component<Props, any>
|
||||
this.webviewRef_.current.contentWindow.addEventListener('message', this.webview_message);
|
||||
}
|
||||
|
||||
destroyWebview() {
|
||||
public destroyWebview() {
|
||||
const wv = this.webviewRef_.current;
|
||||
if (!wv || !this.initialized_) return;
|
||||
|
||||
@@ -115,28 +115,28 @@ export default class NoteTextViewerComponent extends React.Component<Props, any>
|
||||
this.domReady_ = false;
|
||||
}
|
||||
|
||||
focus() {
|
||||
public focus() {
|
||||
if (this.webviewRef_.current) {
|
||||
this.webviewRef_.current.focus();
|
||||
}
|
||||
}
|
||||
|
||||
tryInit() {
|
||||
public tryInit() {
|
||||
if (!this.initialized_ && this.webviewRef_.current) {
|
||||
this.initWebview();
|
||||
this.initialized_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
public componentDidMount() {
|
||||
this.tryInit();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
public componentDidUpdate() {
|
||||
this.tryInit();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
public componentWillUnmount() {
|
||||
this.destroyWebview();
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ export default class NoteTextViewerComponent extends React.Component<Props, any>
|
||||
// Wrap WebView functions
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
send(channel: string, arg0: any = null, arg1: any = null) {
|
||||
public send(channel: string, arg0: any = null, arg1: any = null) {
|
||||
const win = this.webviewRef_.current.contentWindow;
|
||||
|
||||
if (channel === 'focus') {
|
||||
@@ -172,7 +172,7 @@ export default class NoteTextViewerComponent extends React.Component<Props, any>
|
||||
// Wrap WebView functions (END)
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
const viewerStyle = Object.assign({}, { border: 'none' }, this.props.viewerStyle);
|
||||
return <iframe className="noteTextViewer" ref={this.webviewRef_} style={viewerStyle} src="gui/note-viewer/index.html"></iframe>;
|
||||
}
|
||||
|
@@ -14,7 +14,7 @@ interface Props {
|
||||
}
|
||||
|
||||
class OneDriveLoginScreenComponent extends React.Component<any, any> {
|
||||
constructor(props: Props) {
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
@@ -22,7 +22,7 @@ class OneDriveLoginScreenComponent extends React.Component<any, any> {
|
||||
};
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
public async componentDidMount() {
|
||||
const log = (s: any) => {
|
||||
this.setState((state: any) => {
|
||||
const authLog = state.authLog.slice();
|
||||
@@ -48,15 +48,15 @@ class OneDriveLoginScreenComponent extends React.Component<any, any> {
|
||||
}
|
||||
}
|
||||
|
||||
startUrl() {
|
||||
public startUrl() {
|
||||
return reg.syncTarget().api().authCodeUrl(this.redirectUrl());
|
||||
}
|
||||
|
||||
redirectUrl() {
|
||||
public redirectUrl() {
|
||||
return reg.syncTarget().api().nativeClientRedirectUrl();
|
||||
}
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const logComps = [];
|
||||
|
@@ -27,13 +27,13 @@ export default class PromptDialog extends React.Component<Props, any> {
|
||||
private styles_: any;
|
||||
private styleKey_: string;
|
||||
|
||||
constructor(props: Props) {
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.answerInput_ = React.createRef();
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
public UNSAFE_componentWillMount() {
|
||||
this.setState({
|
||||
visible: false,
|
||||
answer: this.props.defaultValue ? this.props.defaultValue : '',
|
||||
@@ -41,7 +41,7 @@ export default class PromptDialog extends React.Component<Props, any> {
|
||||
this.focusInput_ = true;
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(newProps: Props) {
|
||||
public UNSAFE_componentWillReceiveProps(newProps: Props) {
|
||||
if ('visible' in newProps && newProps.visible !== this.props.visible) {
|
||||
this.setState({ visible: newProps.visible });
|
||||
if (newProps.visible) this.focusInput_ = true;
|
||||
@@ -52,12 +52,12 @@ export default class PromptDialog extends React.Component<Props, any> {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
public componentDidUpdate() {
|
||||
if (this.focusInput_ && this.answerInput_.current) this.answerInput_.current.focus();
|
||||
this.focusInput_ = false;
|
||||
}
|
||||
|
||||
styles(themeId: number, width: number, height: number, visible: boolean) {
|
||||
public styles(themeId: number, width: number, height: number, visible: boolean) {
|
||||
const styleKey = `${themeId}_${width}_${height}_${visible}`;
|
||||
if (styleKey === this.styleKey_) return this.styles_;
|
||||
|
||||
@@ -181,7 +181,7 @@ export default class PromptDialog extends React.Component<Props, any> {
|
||||
return this.styles_;
|
||||
}
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
const style = this.props.style;
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const buttonTypes = this.props.buttons ? this.props.buttons : ['ok', 'cancel'];
|
||||
|
@@ -135,7 +135,7 @@ const getNextSortingOrderType = (s: SortingType): SortingType => {
|
||||
const MAX_RESOURCES = 10000;
|
||||
|
||||
class ResourceScreenComponent extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
resources: undefined,
|
||||
@@ -147,7 +147,7 @@ class ResourceScreenComponent extends React.Component<Props, State> {
|
||||
};
|
||||
}
|
||||
|
||||
async reloadResources(sorting: ActiveSorting) {
|
||||
public async reloadResources(sorting: ActiveSorting) {
|
||||
this.setState({ isLoading: true });
|
||||
const resources = await Resource.all({
|
||||
order: [{
|
||||
@@ -161,11 +161,11 @@ class ResourceScreenComponent extends React.Component<Props, State> {
|
||||
this.setState({ resources, isLoading: false });
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
public componentDidMount() {
|
||||
void this.reloadResources(this.state.sorting);
|
||||
}
|
||||
|
||||
onResourceDelete(resource: InnerResource) {
|
||||
public onResourceDelete(resource: InnerResource) {
|
||||
const ok = bridge().showConfirmMessageBox(_('Delete attachment "%s"?', resource.title), {
|
||||
buttons: [_('Delete'), _('Cancel')],
|
||||
defaultId: 1,
|
||||
@@ -184,7 +184,7 @@ class ResourceScreenComponent extends React.Component<Props, State> {
|
||||
});
|
||||
}
|
||||
|
||||
openResource(resource: InnerResource) {
|
||||
public openResource(resource: InnerResource) {
|
||||
const resourcePath = Resource.fullPath(resource);
|
||||
const ok = bridge().openExternal(`file://${resourcePath}`);
|
||||
if (!ok) {
|
||||
@@ -192,7 +192,7 @@ class ResourceScreenComponent extends React.Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
onToggleSortOrder(sortOrder: SortingOrder) {
|
||||
public onToggleSortOrder(sortOrder: SortingOrder) {
|
||||
let newSorting = { ...this.state.sorting };
|
||||
if (sortOrder === this.state.sorting.order) {
|
||||
newSorting.type = getNextSortingOrderType(newSorting.type);
|
||||
@@ -206,7 +206,7 @@ class ResourceScreenComponent extends React.Component<Props, State> {
|
||||
void this.reloadResources(newSorting);
|
||||
}
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
const style = this.props.style;
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
|
@@ -5,7 +5,7 @@ import CommandService from '@joplin/lib/services/CommandService';
|
||||
import { AppState } from '../app.reducer';
|
||||
|
||||
class TagItemComponent extends React.Component {
|
||||
render() {
|
||||
public render() {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const style = Object.assign({}, theme.tagStyle);
|
||||
const { title, id } = this.props;
|
||||
|
@@ -13,7 +13,7 @@ interface Props {
|
||||
|
||||
class ToolbarBaseComponent extends React.Component<Props, any> {
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const style: any = Object.assign({
|
||||
|
@@ -6,7 +6,7 @@ interface Props {
|
||||
}
|
||||
|
||||
class ToolbarSpace extends React.Component<Props> {
|
||||
render() {
|
||||
public render() {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const style = Object.assign({}, theme.toolbarStyle);
|
||||
style.minWidth = style.height / 2;
|
||||
|
@@ -7,11 +7,11 @@ const smalltalk = require('smalltalk');
|
||||
const logger = Logger.create('dialogs');
|
||||
|
||||
class Dialogs {
|
||||
async alert(message: string, title = '') {
|
||||
public async alert(message: string, title = '') {
|
||||
await smalltalk.alert(title, message);
|
||||
}
|
||||
|
||||
async confirm(message: string, title = '', options: any = {}) {
|
||||
public async confirm(message: string, title = '', options: any = {}) {
|
||||
try {
|
||||
await smalltalk.confirm(title, message, options);
|
||||
return true;
|
||||
@@ -21,7 +21,7 @@ class Dialogs {
|
||||
}
|
||||
}
|
||||
|
||||
async prompt(message: string, title = '', defaultValue = '', options: any = null) {
|
||||
public async prompt(message: string, title = '', defaultValue = '', options: any = null) {
|
||||
if (options === null) options = {};
|
||||
|
||||
try {
|
||||
|
@@ -154,34 +154,66 @@
|
||||
setPercentScroll(percentScroll_);
|
||||
}
|
||||
|
||||
// Note that this function keeps track of what's been added so as not to add the same CSS files multiple times
|
||||
// It also means that once an asset has been added it is never removed from the view, which in many case is
|
||||
// desirable, but still something to keep in mind.
|
||||
// Note that this function keeps track of what's been added so as not to
|
||||
// add the same CSS files multiple times.
|
||||
function addPluginAssets(assets) {
|
||||
if (!assets) return;
|
||||
|
||||
const pluginAssetsContainer = document.getElementById('joplin-container-pluginAssetsContainer');
|
||||
|
||||
const processedAssetIds = [];
|
||||
|
||||
for (let i = 0; i < assets.length; i++) {
|
||||
const asset = assets[i];
|
||||
|
||||
// # and ? can be used in valid paths and shouldn't be treated as the start of a query or fragment
|
||||
const encodedPath = asset.path
|
||||
.replaceAll('#','%23')
|
||||
.replaceAll('?','%3F')
|
||||
|
||||
const assetId = asset.name ? asset.name : encodedPath;
|
||||
|
||||
processedAssetIds.push(assetId);
|
||||
|
||||
if (pluginAssetsAdded_[assetId]) continue;
|
||||
pluginAssetsAdded_[assetId] = true;
|
||||
|
||||
let element = null;
|
||||
|
||||
if (asset.mime === 'application/javascript') {
|
||||
const script = document.createElement('script');
|
||||
script.src = encodedPath;
|
||||
pluginAssetsContainer.appendChild(script);
|
||||
element = document.createElement('script');
|
||||
element.src = encodedPath;
|
||||
pluginAssetsContainer.appendChild(element);
|
||||
} else if (asset.mime === 'text/css') {
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = encodedPath
|
||||
pluginAssetsContainer.appendChild(link);
|
||||
element = document.createElement('link');
|
||||
element.rel = 'stylesheet';
|
||||
element.href = encodedPath
|
||||
pluginAssetsContainer.appendChild(element);
|
||||
}
|
||||
|
||||
pluginAssetsAdded_[assetId] = {
|
||||
element,
|
||||
}
|
||||
}
|
||||
|
||||
// Once we have added the relevant assets, we also remove those that
|
||||
// are no longer needed. It's necessary in particular for the CSS
|
||||
// generated by noteStyle - if we don't remove it, we might end up
|
||||
// with two or more stylesheet and that will create conflicts.
|
||||
//
|
||||
// It was happening for example when automatically switching from
|
||||
// light to dark theme, and then back to light theme - in that case
|
||||
// the viewer would remain dark because it would use the dark
|
||||
// stylesheet that would still be in the DOM.
|
||||
for (const [assetId, asset] of Object.entries(pluginAssetsAdded_)) {
|
||||
if (!processedAssetIds.includes(assetId)) {
|
||||
try {
|
||||
asset.element.remove();
|
||||
} catch (error) {
|
||||
// We don't throw an exception but we log it since
|
||||
// it shouldn't happen
|
||||
console.warn('Tried to remove an asset but got an error', error);
|
||||
}
|
||||
pluginAssetsAdded_[assetId] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -26,7 +26,7 @@ interface ContextMenuProps {
|
||||
}
|
||||
|
||||
export default class NoteListUtils {
|
||||
static makeContextMenu(noteIds: string[], props: ContextMenuProps) {
|
||||
public static makeContextMenu(noteIds: string[], props: ContextMenuProps) {
|
||||
const cmdService = CommandService.instance();
|
||||
|
||||
const menuUtils = new MenuUtils(cmdService);
|
||||
@@ -212,7 +212,7 @@ export default class NoteListUtils {
|
||||
return menu;
|
||||
}
|
||||
|
||||
static async confirmDeleteNotes(noteIds: string[]) {
|
||||
public static async confirmDeleteNotes(noteIds: string[]) {
|
||||
if (!noteIds.length) return;
|
||||
|
||||
const msg = await Note.deleteMessage(noteIds);
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/app-desktop",
|
||||
"version": "2.10.7",
|
||||
"version": "2.10.8",
|
||||
"description": "Joplin for Desktop",
|
||||
"main": "main.js",
|
||||
"private": true,
|
||||
@@ -120,8 +120,8 @@
|
||||
"electron-rebuild": "3.2.9",
|
||||
"glob": "8.1.0",
|
||||
"gulp": "4.0.2",
|
||||
"jest": "29.4.2",
|
||||
"jest-environment-jsdom": "29.4.2",
|
||||
"jest": "29.4.3",
|
||||
"jest-environment-jsdom": "29.4.3",
|
||||
"js-sha512": "0.8.0",
|
||||
"nan": "2.17.0",
|
||||
"react-test-renderer": "18.2.0",
|
||||
|
@@ -65,7 +65,7 @@ class GotoAnything {
|
||||
public static Dialog: any;
|
||||
public static manifest: any;
|
||||
|
||||
onTrigger(event: any) {
|
||||
public onTrigger(event: any) {
|
||||
this.dispatch({
|
||||
type: 'PLUGINLEGACY_DIALOG_SET',
|
||||
open: true,
|
||||
@@ -85,7 +85,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
||||
private markupToHtml_: MarkupToHtml;
|
||||
private userCallback_: any = null;
|
||||
|
||||
constructor(props: Props) {
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
const startString = props?.userData?.startString ? props?.userData?.startString : '';
|
||||
@@ -119,7 +119,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
||||
if (startString) this.scheduleListUpdate();
|
||||
}
|
||||
|
||||
style() {
|
||||
public style() {
|
||||
const styleKey = [this.props.themeId, this.state.listType, this.state.resultsInBody ? '1' : '0'].join('-');
|
||||
|
||||
if (this.styles_[styleKey]) return this.styles_[styleKey];
|
||||
@@ -184,7 +184,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
||||
return this.styles_[styleKey];
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
public componentDidMount() {
|
||||
document.addEventListener('keydown', this.onKeyDown);
|
||||
|
||||
this.props.dispatch({
|
||||
@@ -193,7 +193,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
public componentWillUnmount() {
|
||||
if (this.listUpdateIID_) shim.clearTimeout(this.listUpdateIID_);
|
||||
document.removeEventListener('keydown', this.onKeyDown);
|
||||
|
||||
@@ -203,7 +203,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
||||
});
|
||||
}
|
||||
|
||||
onKeyDown(event: any) {
|
||||
public onKeyDown(event: any) {
|
||||
if (event.keyCode === 27) { // ESCAPE
|
||||
this.props.dispatch({
|
||||
pluginName: PLUGIN_NAME,
|
||||
@@ -213,7 +213,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
modalLayer_onClick(event: any) {
|
||||
private modalLayer_onClick(event: any) {
|
||||
if (event.currentTarget === event.target) {
|
||||
this.props.dispatch({
|
||||
pluginName: PLUGIN_NAME,
|
||||
@@ -223,17 +223,17 @@ class Dialog extends React.PureComponent<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
helpButton_onClick() {
|
||||
private helpButton_onClick() {
|
||||
this.setState({ showHelp: !this.state.showHelp });
|
||||
}
|
||||
|
||||
input_onChange(event: any) {
|
||||
private input_onChange(event: any) {
|
||||
this.setState({ query: event.target.value });
|
||||
|
||||
this.scheduleListUpdate();
|
||||
}
|
||||
|
||||
scheduleListUpdate() {
|
||||
public scheduleListUpdate() {
|
||||
if (this.listUpdateIID_) shim.clearTimeout(this.listUpdateIID_);
|
||||
|
||||
this.listUpdateIID_ = shim.setTimeout(async () => {
|
||||
@@ -242,12 +242,12 @@ class Dialog extends React.PureComponent<Props, State> {
|
||||
}, 100);
|
||||
}
|
||||
|
||||
async keywords(searchQuery: string) {
|
||||
public async keywords(searchQuery: string) {
|
||||
const parsedQuery = await SearchEngine.instance().parseQuery(searchQuery);
|
||||
return SearchEngine.instance().allParsedQueryTerms(parsedQuery);
|
||||
}
|
||||
|
||||
markupToHtml() {
|
||||
public markupToHtml() {
|
||||
if (this.markupToHtml_) return this.markupToHtml_;
|
||||
this.markupToHtml_ = markupLanguageUtils.newMarkupToHtml();
|
||||
return this.markupToHtml_;
|
||||
@@ -262,7 +262,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
||||
};
|
||||
}
|
||||
|
||||
async updateList() {
|
||||
public async updateList() {
|
||||
let resultsInBody = false;
|
||||
|
||||
if (!this.state.query) {
|
||||
@@ -402,7 +402,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
||||
this.itemListRef.current.makeItemIndexVisible(index);
|
||||
}
|
||||
|
||||
async gotoItem(item: any) {
|
||||
public async gotoItem(item: any) {
|
||||
this.props.dispatch({
|
||||
pluginName: PLUGIN_NAME,
|
||||
type: 'PLUGINLEGACY_DIALOG_SET',
|
||||
@@ -465,7 +465,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
listItem_onClick(event: any) {
|
||||
private listItem_onClick(event: any) {
|
||||
const itemId = event.currentTarget.getAttribute('data-id');
|
||||
const parentId = event.currentTarget.getAttribute('data-parent-id');
|
||||
const itemType = Number(event.currentTarget.getAttribute('data-type'));
|
||||
@@ -478,7 +478,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
||||
});
|
||||
}
|
||||
|
||||
renderItem(item: SearchResult) {
|
||||
public renderItem(item: SearchResult) {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const style = this.style();
|
||||
const isSelected = item.id === this.state.selectedItemId;
|
||||
@@ -502,7 +502,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
selectedItemIndex(results: any[] = undefined, itemId: string = undefined) {
|
||||
public selectedItemIndex(results: any[] = undefined, itemId: string = undefined) {
|
||||
if (typeof results === 'undefined') results = this.state.results;
|
||||
if (typeof itemId === 'undefined') itemId = this.state.selectedItemId;
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
@@ -512,13 +512,13 @@ class Dialog extends React.PureComponent<Props, State> {
|
||||
return -1;
|
||||
}
|
||||
|
||||
selectedItem() {
|
||||
public selectedItem() {
|
||||
const index = this.selectedItemIndex();
|
||||
if (index < 0) return null;
|
||||
return { ...this.state.results[index], commandArgs: this.state.commandArgs };
|
||||
}
|
||||
|
||||
input_onKeyDown(event: any) {
|
||||
private input_onKeyDown(event: any) {
|
||||
const keyCode = event.keyCode;
|
||||
|
||||
if (this.state.results.length > 0 && (keyCode === 40 || keyCode === 38)) { // DOWN / UP
|
||||
@@ -554,7 +554,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
||||
return maxItemCount * itemHeight;
|
||||
}
|
||||
|
||||
renderList() {
|
||||
public renderList() {
|
||||
const style = this.style();
|
||||
|
||||
const itemListStyle = {
|
||||
@@ -573,7 +573,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const style = this.style();
|
||||
const helpComp = !this.state.showHelp ? null : <div className="help-text" style={style.help}>{_('Type a note title or part of its content to jump to it. Or type # followed by a tag name, or @ followed by a notebook name. Or type : to search for commands.')}</div>;
|
||||
|
@@ -6,24 +6,24 @@ import KvStore from '@joplin/lib/services/KvStore';
|
||||
|
||||
export default class PluginAssetsLoader {
|
||||
|
||||
static instance_: PluginAssetsLoader = null;
|
||||
logger_: any = null;
|
||||
private static instance_: PluginAssetsLoader = null;
|
||||
private logger_: any = null;
|
||||
|
||||
static instance() {
|
||||
public static instance() {
|
||||
if (PluginAssetsLoader.instance_) return PluginAssetsLoader.instance_;
|
||||
PluginAssetsLoader.instance_ = new PluginAssetsLoader();
|
||||
return PluginAssetsLoader.instance_;
|
||||
}
|
||||
|
||||
setLogger(logger: any) {
|
||||
public setLogger(logger: any) {
|
||||
this.logger_ = logger;
|
||||
}
|
||||
|
||||
logger() {
|
||||
public logger() {
|
||||
return this.logger_;
|
||||
}
|
||||
|
||||
async importAssets() {
|
||||
public async importAssets() {
|
||||
const destDir = `${Setting.value('resourceDir')}/pluginAssets`;
|
||||
await shim.fsDriver().mkdir(destDir);
|
||||
|
||||
|
@@ -150,8 +150,8 @@ android {
|
||||
applicationId "net.cozic.joplin"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 2097683
|
||||
versionName "2.10.7"
|
||||
versionCode 2097684
|
||||
versionName "2.10.8"
|
||||
// ndk {
|
||||
// abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
|
||||
// }
|
||||
|
@@ -2,7 +2,7 @@ const { BackButtonService } = require('../services/back-button.js');
|
||||
const DialogBox = require('react-native-dialogbox').default;
|
||||
|
||||
export default class BackButtonDialogBox extends DialogBox {
|
||||
constructor(props: any) {
|
||||
public constructor(props: any) {
|
||||
super(props);
|
||||
|
||||
this.backHandler_ = () => {
|
||||
@@ -14,7 +14,7 @@ export default class BackButtonDialogBox extends DialogBox {
|
||||
};
|
||||
}
|
||||
|
||||
async componentDidUpdate() {
|
||||
public async componentDidUpdate() {
|
||||
if (this.state.isVisible) {
|
||||
BackButtonService.addHandler(this.backHandler_);
|
||||
} else {
|
||||
|
@@ -14,7 +14,7 @@ import Setting from '@joplin/lib/models/Setting';
|
||||
Icon.loadFont().catch((error: any) => { console.info(error); });
|
||||
|
||||
class CameraView extends Component {
|
||||
constructor() {
|
||||
public constructor() {
|
||||
super();
|
||||
|
||||
const dimensions = Dimensions.get('window');
|
||||
@@ -34,18 +34,18 @@ class CameraView extends Component {
|
||||
this.onLayout = this.onLayout.bind(this);
|
||||
}
|
||||
|
||||
onLayout(event: any) {
|
||||
public onLayout(event: any) {
|
||||
this.setState({
|
||||
screenWidth: event.nativeEvent.layout.width,
|
||||
screenHeight: event.nativeEvent.layout.height,
|
||||
});
|
||||
}
|
||||
|
||||
back_onPress() {
|
||||
private back_onPress() {
|
||||
if (this.props.onCancel) this.props.onCancel();
|
||||
}
|
||||
|
||||
reverse_onPress() {
|
||||
private reverse_onPress() {
|
||||
if (this.props.cameraType === RNCamera.Constants.Type.back) {
|
||||
Setting.setValue('camera.type', RNCamera.Constants.Type.front);
|
||||
} else {
|
||||
@@ -53,7 +53,7 @@ class CameraView extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
ratio_onPress() {
|
||||
private ratio_onPress() {
|
||||
if (this.state.ratios.length <= 1) return;
|
||||
|
||||
let index = this.state.ratios.indexOf(this.props.cameraRatio);
|
||||
@@ -62,7 +62,7 @@ class CameraView extends Component {
|
||||
Setting.setValue('camera.ratio', this.state.ratios[index]);
|
||||
}
|
||||
|
||||
async photo_onPress() {
|
||||
private async photo_onPress() {
|
||||
if (!this.camera || !this.props.onPhoto) return;
|
||||
|
||||
this.setState({ snapping: true });
|
||||
@@ -79,14 +79,14 @@ class CameraView extends Component {
|
||||
|
||||
}
|
||||
|
||||
async onCameraReady() {
|
||||
public async onCameraReady() {
|
||||
if (this.supportsRatios()) {
|
||||
const ratios = await this.camera.getSupportedRatiosAsync();
|
||||
this.setState({ ratios: ratios });
|
||||
}
|
||||
}
|
||||
|
||||
renderButton(onPress: Function, iconNameOrIcon: any, style: any) {
|
||||
public renderButton(onPress: Function, iconNameOrIcon: any, style: any) {
|
||||
let icon = null;
|
||||
|
||||
if (typeof iconNameOrIcon === 'string') {
|
||||
@@ -112,7 +112,7 @@ class CameraView extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
fitRectIntoBounds(rect: any, bounds: any) {
|
||||
public fitRectIntoBounds(rect: any, bounds: any) {
|
||||
const rectRatio = rect.width / rect.height;
|
||||
const boundsRatio = bounds.width / bounds.height;
|
||||
|
||||
@@ -130,7 +130,7 @@ class CameraView extends Component {
|
||||
return newDimensions;
|
||||
}
|
||||
|
||||
cameraRect(ratio: string) {
|
||||
public cameraRect(ratio: string) {
|
||||
// To keep the calculations simpler, it's assumed that the phone is in
|
||||
// portrait orientation. Then at the end we swap the values if needed.
|
||||
const splitted = ratio.split(':');
|
||||
@@ -152,11 +152,11 @@ class CameraView extends Component {
|
||||
return output;
|
||||
}
|
||||
|
||||
supportsRatios() {
|
||||
public supportsRatios() {
|
||||
return shim.mobilePlatform() === 'android';
|
||||
}
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
const photoIcon = this.state.snapping ? 'md-checkmark' : 'md-camera';
|
||||
|
||||
const displayRatios = this.supportsRatios() && this.state.ratios.length > 1;
|
||||
|
@@ -101,56 +101,56 @@ describe('markdownCommands.toggleList', () => {
|
||||
// );
|
||||
// });
|
||||
|
||||
it('should not preserve indentation when removing sublists', async () => {
|
||||
const preSubListText = '# List test\n * This\n * is\n';
|
||||
const initialDocText = `${preSubListText}\t1. a\n\t2. test\n * of list toggling`;
|
||||
// it('should not preserve indentation when removing sublists', async () => {
|
||||
// const preSubListText = '# List test\n * This\n * is\n';
|
||||
// const initialDocText = `${preSubListText}\t1. a\n\t2. test\n * of list toggling`;
|
||||
|
||||
const editor = await createEditor(
|
||||
initialDocText,
|
||||
EditorSelection.range(preSubListText.length, `${preSubListText}\t1. a\n\t2. test`.length),
|
||||
['ATXHeading1', 'BulletList', 'OrderedList']
|
||||
);
|
||||
// const editor = await createEditor(
|
||||
// initialDocText,
|
||||
// EditorSelection.range(preSubListText.length, `${preSubListText}\t1. a\n\t2. test`.length),
|
||||
// ['ATXHeading1', 'BulletList', 'OrderedList']
|
||||
// );
|
||||
|
||||
// Indentation should not be preserved when removing lists
|
||||
toggleList(ListType.OrderedList)(editor);
|
||||
expect(editor.state.selection.main.from).toBe(preSubListText.length);
|
||||
expect(editor.state.doc.toString()).toBe(
|
||||
'# List test\n * This\n * is\na\ntest\n * of list toggling'
|
||||
);
|
||||
// // Indentation should not be preserved when removing lists
|
||||
// toggleList(ListType.OrderedList)(editor);
|
||||
// expect(editor.state.selection.main.from).toBe(preSubListText.length);
|
||||
// expect(editor.state.doc.toString()).toBe(
|
||||
// '# List test\n * This\n * is\na\ntest\n * of list toggling'
|
||||
// );
|
||||
|
||||
// Put the cursor in the middle of the list
|
||||
editor.dispatch({ selection: EditorSelection.cursor(preSubListText.length) });
|
||||
// // Put the cursor in the middle of the list
|
||||
// editor.dispatch({ selection: EditorSelection.cursor(preSubListText.length) });
|
||||
|
||||
// Sublists should be changed
|
||||
toggleList(ListType.CheckList)(editor);
|
||||
const expectedChecklistPart =
|
||||
'# List test\n - [ ] This\n - [ ] is\n - [ ] a\n - [ ] test\n - [ ] of list toggling';
|
||||
expect(editor.state.doc.toString()).toBe(
|
||||
expectedChecklistPart
|
||||
);
|
||||
// // Sublists should be changed
|
||||
// toggleList(ListType.CheckList)(editor);
|
||||
// const expectedChecklistPart =
|
||||
// '# List test\n - [ ] This\n - [ ] is\n - [ ] a\n - [ ] test\n - [ ] of list toggling';
|
||||
// expect(editor.state.doc.toString()).toBe(
|
||||
// expectedChecklistPart
|
||||
// );
|
||||
|
||||
editor.dispatch({ selection: EditorSelection.cursor(editor.state.doc.length) });
|
||||
editor.dispatch(editor.state.replaceSelection('\n\n\n'));
|
||||
// editor.dispatch({ selection: EditorSelection.cursor(editor.state.doc.length) });
|
||||
// editor.dispatch(editor.state.replaceSelection('\n\n\n'));
|
||||
|
||||
// toggleList should also create a new list if the cursor is on an empty line.
|
||||
toggleList(ListType.OrderedList)(editor);
|
||||
editor.dispatch(editor.state.replaceSelection('Test.\n2. Test2\n3. Test3'));
|
||||
// // toggleList should also create a new list if the cursor is on an empty line.
|
||||
// toggleList(ListType.OrderedList)(editor);
|
||||
// editor.dispatch(editor.state.replaceSelection('Test.\n2. Test2\n3. Test3'));
|
||||
|
||||
expect(editor.state.doc.toString()).toBe(
|
||||
`${expectedChecklistPart}\n\n\n1. Test.\n2. Test2\n3. Test3`
|
||||
);
|
||||
// expect(editor.state.doc.toString()).toBe(
|
||||
// `${expectedChecklistPart}\n\n\n1. Test.\n2. Test2\n3. Test3`
|
||||
// );
|
||||
|
||||
toggleList(ListType.CheckList)(editor);
|
||||
expect(editor.state.doc.toString()).toBe(
|
||||
`${expectedChecklistPart}\n\n\n- [ ] Test.\n- [ ] Test2\n- [ ] Test3`
|
||||
);
|
||||
// toggleList(ListType.CheckList)(editor);
|
||||
// expect(editor.state.doc.toString()).toBe(
|
||||
// `${expectedChecklistPart}\n\n\n- [ ] Test.\n- [ ] Test2\n- [ ] Test3`
|
||||
// );
|
||||
|
||||
// The entire checklist should have been selected (and thus will now be indented)
|
||||
increaseIndent(editor);
|
||||
expect(editor.state.doc.toString()).toBe(
|
||||
`${expectedChecklistPart}\n\n\n\t- [ ] Test.\n\t- [ ] Test2\n\t- [ ] Test3`
|
||||
);
|
||||
});
|
||||
// // The entire checklist should have been selected (and thus will now be indented)
|
||||
// increaseIndent(editor);
|
||||
// expect(editor.state.doc.toString()).toBe(
|
||||
// `${expectedChecklistPart}\n\n\n\t- [ ] Test.\n\t- [ ] Test2\n\t- [ ] Test3`
|
||||
// );
|
||||
// });
|
||||
|
||||
it('should toggle a numbered list without changing its sublists', async () => {
|
||||
const initialDocText = '1. Foo\n2. Bar\n3. Baz\n\t- Test\n\t- of\n\t- sublists\n4. Foo';
|
||||
|
@@ -52,7 +52,7 @@ const styles = StyleSheet.create({
|
||||
|
||||
export default class SelectDateTimeDialog extends React.PureComponent<any, any> {
|
||||
|
||||
constructor(props: any) {
|
||||
public constructor(props: any) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
@@ -67,37 +67,37 @@ export default class SelectDateTimeDialog extends React.PureComponent<any, any>
|
||||
this.onSetDate = this.onSetDate.bind(this);
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(newProps: any) {
|
||||
public UNSAFE_componentWillReceiveProps(newProps: any) {
|
||||
if (newProps.date !== this.state.date) {
|
||||
this.setState({ date: newProps.date });
|
||||
}
|
||||
}
|
||||
|
||||
onAccept() {
|
||||
public onAccept() {
|
||||
if (this.props.onAccept) this.props.onAccept(this.state.date);
|
||||
}
|
||||
|
||||
onReject() {
|
||||
public onReject() {
|
||||
if (this.props.onReject) this.props.onReject();
|
||||
}
|
||||
|
||||
onClear() {
|
||||
public onClear() {
|
||||
if (this.props.onAccept) this.props.onAccept(null);
|
||||
}
|
||||
|
||||
onPickerConfirm(selectedDate: Date) {
|
||||
public onPickerConfirm(selectedDate: Date) {
|
||||
this.setState({ date: selectedDate, showPicker: false });
|
||||
}
|
||||
|
||||
onPickerCancel() {
|
||||
public onPickerCancel() {
|
||||
this.setState({ showPicker: false });
|
||||
}
|
||||
|
||||
onSetDate() {
|
||||
public onSetDate() {
|
||||
this.setState({ showPicker: true });
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
public renderContent() {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
return (
|
||||
@@ -118,7 +118,7 @@ export default class SelectDateTimeDialog extends React.PureComponent<any, any>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
const modalVisible = this.props.shown;
|
||||
|
||||
if (!modalVisible) return null;
|
||||
|
@@ -4,7 +4,7 @@ import { Dimensions } from 'react-native';
|
||||
import { State } from '@joplin/lib/reducer';
|
||||
|
||||
class SideMenuComponent extends SideMenu_ {
|
||||
onLayoutChange(e: any) {
|
||||
public onLayoutChange(e: any) {
|
||||
const { width, height } = e.nativeEvent.layout;
|
||||
const openMenuOffsetPercentage = this.props.openMenuOffset / Dimensions.get('window').width;
|
||||
const openMenuOffset = width * openMenuOffsetPercentage;
|
||||
|
@@ -25,7 +25,7 @@ class AppNavComponent extends Component<Props, State> {
|
||||
private keyboardDidHideListener: EmitterSubscription|null = null;
|
||||
private keyboardWillChangeFrameListener: EmitterSubscription|null = null;
|
||||
|
||||
constructor(props: Props) {
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.previousRouteName_ = null;
|
||||
@@ -35,7 +35,7 @@ class AppNavComponent extends Component<Props, State> {
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
public UNSAFE_componentWillMount() {
|
||||
if (Platform.OS === 'ios') {
|
||||
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this.keyboardDidShow.bind(this));
|
||||
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this.keyboardDidHide.bind(this));
|
||||
@@ -43,7 +43,7 @@ class AppNavComponent extends Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
public componentWillUnmount() {
|
||||
this.keyboardDidShowListener?.remove();
|
||||
this.keyboardDidHideListener?.remove();
|
||||
this.keyboardWillChangeFrameListener?.remove();
|
||||
@@ -53,15 +53,15 @@ class AppNavComponent extends Component<Props, State> {
|
||||
this.keyboardWillChangeFrameListener = null;
|
||||
}
|
||||
|
||||
keyboardDidShow() {
|
||||
public keyboardDidShow() {
|
||||
this.setState({ autoCompletionBarExtraHeight: 30 });
|
||||
}
|
||||
|
||||
keyboardDidHide() {
|
||||
public keyboardDidHide() {
|
||||
this.setState({ autoCompletionBarExtraHeight: 0 });
|
||||
}
|
||||
|
||||
keyboardWillChangeFrame = (evt: KeyboardEvent) => {
|
||||
private keyboardWillChangeFrame = (evt: KeyboardEvent) => {
|
||||
const windowWidth = Dimensions.get('window').width;
|
||||
|
||||
// If the keyboard isn't as wide as the window, the floating keyboard is diabled.
|
||||
@@ -71,7 +71,7 @@ class AppNavComponent extends Component<Props, State> {
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
if (!this.props.route) throw new Error('Route must not be null');
|
||||
|
||||
// Note: certain screens are kept into memory, in particular Notes and Search
|
||||
|
@@ -25,7 +25,7 @@ import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
|
||||
import { openDocumentTree } from '@joplin/react-native-saf-x';
|
||||
|
||||
class ConfigScreenComponent extends BaseScreenComponent {
|
||||
static navigationOptions(): any {
|
||||
public static navigationOptions(): any {
|
||||
return { header: null };
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
|
||||
};
|
||||
}
|
||||
|
||||
async checkFilesystemPermission() {
|
||||
public async checkFilesystemPermission() {
|
||||
if (Platform.OS !== 'android') {
|
||||
// Not implemented yet
|
||||
return true;
|
||||
@@ -212,11 +212,11 @@ class ConfigScreenComponent extends BaseScreenComponent {
|
||||
});
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
public UNSAFE_componentWillMount() {
|
||||
this.setState({ settings: this.props.settings });
|
||||
}
|
||||
|
||||
styles() {
|
||||
public styles() {
|
||||
const themeId = this.props.themeId;
|
||||
const theme = themeStyle(themeId);
|
||||
|
||||
@@ -376,7 +376,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
|
||||
BackButtonService.removeHandler(this.handleBackButtonPress);
|
||||
}
|
||||
|
||||
renderHeader(key: string, title: string) {
|
||||
public renderHeader(key: string, title: string) {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
return (
|
||||
<View key={key} style={this.styles().headerWrapperStyle} onLayout={(event: any) => this.onHeaderLayout(key, event)}>
|
||||
@@ -410,7 +410,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
|
||||
);
|
||||
}
|
||||
|
||||
sectionToComponent(key: string, section: any, settings: any) {
|
||||
public sectionToComponent(key: string, section: any, settings: any) {
|
||||
const settingComps = [];
|
||||
|
||||
for (let i = 0; i < section.metadatas.length; i++) {
|
||||
@@ -474,7 +474,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
|
||||
return !hasDescription ? this.styles().settingContainer : this.styles().settingContainerNoBottomBorder;
|
||||
}
|
||||
|
||||
settingToComponent(key: string, value: any) {
|
||||
public settingToComponent(key: string, value: any) {
|
||||
const themeId = this.props.themeId;
|
||||
const theme = themeStyle(themeId);
|
||||
const output: any = null;
|
||||
@@ -599,7 +599,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
|
||||
return output;
|
||||
}
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
const settings = this.state.settings;
|
||||
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
@@ -51,11 +51,11 @@ const emptyArray: any[] = [];
|
||||
const logger = Logger.create('screens/Note');
|
||||
|
||||
class NoteScreenComponent extends BaseScreenComponent {
|
||||
static navigationOptions(): any {
|
||||
public static navigationOptions(): any {
|
||||
return { header: null };
|
||||
}
|
||||
|
||||
constructor() {
|
||||
public constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
note: Note.new(),
|
||||
@@ -280,7 +280,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
});
|
||||
}
|
||||
|
||||
screenHeader_undoButtonPress() {
|
||||
private screenHeader_undoButtonPress() {
|
||||
if (this.useEditorBeta()) {
|
||||
this.editorRef.current.undo();
|
||||
} else {
|
||||
@@ -288,7 +288,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
}
|
||||
}
|
||||
|
||||
screenHeader_redoButtonPress() {
|
||||
private screenHeader_redoButtonPress() {
|
||||
if (this.useEditorBeta()) {
|
||||
this.editorRef.current.redo();
|
||||
} else {
|
||||
@@ -296,13 +296,13 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
}
|
||||
}
|
||||
|
||||
undoState(noteBody: string = null) {
|
||||
public undoState(noteBody: string = null) {
|
||||
return {
|
||||
body: noteBody === null ? this.state.note.body : noteBody,
|
||||
};
|
||||
}
|
||||
|
||||
styles() {
|
||||
public styles() {
|
||||
const themeId = this.props.themeId;
|
||||
const theme = themeStyle(themeId);
|
||||
|
||||
@@ -392,11 +392,11 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
return this.styles_[cacheKey];
|
||||
}
|
||||
|
||||
isModified() {
|
||||
public isModified() {
|
||||
return shared.isModified(this);
|
||||
}
|
||||
|
||||
async requestGeoLocationPermissions() {
|
||||
public async requestGeoLocationPermissions() {
|
||||
if (!Setting.value('trackLocation')) return;
|
||||
|
||||
const response = await checkPermissions(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, {
|
||||
@@ -413,7 +413,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
}
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
public async componentDidMount() {
|
||||
BackButtonService.addHandler(this.backHandler);
|
||||
NavService.addHandler(this.navHandler);
|
||||
|
||||
@@ -436,11 +436,11 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
void this.requestGeoLocationPermissions();
|
||||
}
|
||||
|
||||
onMarkForDownload(event: any) {
|
||||
public onMarkForDownload(event: any) {
|
||||
void ResourceFetcher.instance().markForDownload(event.resourceId);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: any) {
|
||||
public componentDidUpdate(prevProps: any) {
|
||||
if (this.doFocusUpdate_) {
|
||||
this.doFocusUpdate_ = false;
|
||||
this.focusUpdate();
|
||||
@@ -454,7 +454,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
public componentWillUnmount() {
|
||||
BackButtonService.removeHandler(this.backHandler);
|
||||
NavService.removeHandler(this.navHandler);
|
||||
|
||||
@@ -467,13 +467,13 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
if (this.undoRedoService_) this.undoRedoService_.off('stackChange', this.undoRedoService_stackChange);
|
||||
}
|
||||
|
||||
title_changeText(text: string) {
|
||||
private title_changeText(text: string) {
|
||||
shared.noteComponent_change(this, 'title', text);
|
||||
this.setState({ newAndNoTitleChangeNoteId: null });
|
||||
this.scheduleSave();
|
||||
}
|
||||
|
||||
body_changeText(text: string) {
|
||||
private body_changeText(text: string) {
|
||||
if (!this.undoRedoService_.canUndo) {
|
||||
this.undoRedoService_.push(this.undoState());
|
||||
} else {
|
||||
@@ -484,7 +484,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
this.scheduleSave();
|
||||
}
|
||||
|
||||
body_selectionChange(event: any) {
|
||||
private body_selectionChange(event: any) {
|
||||
if (this.useEditorBeta()) {
|
||||
this.selection = event.selection;
|
||||
} else {
|
||||
@@ -492,34 +492,34 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
}
|
||||
}
|
||||
|
||||
makeSaveAction() {
|
||||
public makeSaveAction() {
|
||||
return async () => {
|
||||
return shared.saveNoteButton_press(this, null, null);
|
||||
};
|
||||
}
|
||||
|
||||
saveActionQueue(noteId: string) {
|
||||
public saveActionQueue(noteId: string) {
|
||||
if (!this.saveActionQueues_[noteId]) {
|
||||
this.saveActionQueues_[noteId] = new AsyncActionQueue(500);
|
||||
}
|
||||
return this.saveActionQueues_[noteId];
|
||||
}
|
||||
|
||||
scheduleSave() {
|
||||
public scheduleSave() {
|
||||
this.saveActionQueue(this.state.note.id).push(this.makeSaveAction());
|
||||
}
|
||||
|
||||
async saveNoteButton_press(folderId: string = null) {
|
||||
private async saveNoteButton_press(folderId: string = null) {
|
||||
await shared.saveNoteButton_press(this, folderId, null);
|
||||
|
||||
Keyboard.dismiss();
|
||||
}
|
||||
|
||||
async saveOneProperty(name: string, value: any) {
|
||||
public async saveOneProperty(name: string, value: any) {
|
||||
await shared.saveOneProperty(this, name, value);
|
||||
}
|
||||
|
||||
async deleteNote_onPress() {
|
||||
private async deleteNote_onPress() {
|
||||
const note = this.state.note;
|
||||
if (!note.id) return;
|
||||
|
||||
@@ -546,7 +546,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
return result;
|
||||
}
|
||||
|
||||
async imageDimensions(uri: string) {
|
||||
public async imageDimensions(uri: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
Image.getSize(
|
||||
uri,
|
||||
@@ -560,7 +560,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
});
|
||||
}
|
||||
|
||||
async resizeImage(localFilePath: string, targetPath: string, mimeType: string) {
|
||||
public async resizeImage(localFilePath: string, targetPath: string, mimeType: string) {
|
||||
const maxSize = Resource.IMAGE_MAX_DIMENSION;
|
||||
|
||||
const dimensions: any = await this.imageDimensions(localFilePath);
|
||||
@@ -609,7 +609,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
return true;
|
||||
}
|
||||
|
||||
async attachFile(pickerResponse: any, fileType: string) {
|
||||
public async attachFile(pickerResponse: any, fileType: string) {
|
||||
if (!pickerResponse) {
|
||||
// User has cancelled
|
||||
return;
|
||||
@@ -727,11 +727,11 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
}
|
||||
}
|
||||
|
||||
takePhoto_onPress() {
|
||||
private takePhoto_onPress() {
|
||||
this.setState({ showCamera: true });
|
||||
}
|
||||
|
||||
cameraView_onPhoto(data: any) {
|
||||
private cameraView_onPhoto(data: any) {
|
||||
void this.attachFile(
|
||||
{
|
||||
uri: data.uri,
|
||||
@@ -743,7 +743,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
this.setState({ showCamera: false });
|
||||
}
|
||||
|
||||
cameraView_onCancel() {
|
||||
private cameraView_onCancel() {
|
||||
this.setState({ showCamera: false });
|
||||
}
|
||||
|
||||
@@ -754,34 +754,30 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
}
|
||||
}
|
||||
|
||||
toggleIsTodo_onPress() {
|
||||
private toggleIsTodo_onPress() {
|
||||
shared.toggleIsTodo_onPress(this);
|
||||
|
||||
this.scheduleSave();
|
||||
}
|
||||
|
||||
tags_onPress() {
|
||||
private tags_onPress() {
|
||||
if (!this.state.note || !this.state.note.id) return;
|
||||
|
||||
this.setState({ noteTagDialogShown: true });
|
||||
}
|
||||
|
||||
async share_onPress() {
|
||||
private async share_onPress() {
|
||||
await Share.share({
|
||||
message: `${this.state.note.title}\n\n${this.state.note.body}`,
|
||||
title: this.state.note.title,
|
||||
});
|
||||
}
|
||||
|
||||
properties_onPress() {
|
||||
private properties_onPress() {
|
||||
this.props.dispatch({ type: 'SIDE_MENU_OPEN' });
|
||||
}
|
||||
|
||||
setAlarm_onPress() {
|
||||
this.setState({ alarmDialogShown: true });
|
||||
}
|
||||
|
||||
async onAlarmDialogAccept(date: Date) {
|
||||
public async onAlarmDialogAccept(date: Date) {
|
||||
const newNote = Object.assign({}, this.state.note);
|
||||
newNote.todo_due = date ? date.getTime() : 0;
|
||||
|
||||
@@ -790,11 +786,11 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
this.setState({ alarmDialogShown: false });
|
||||
}
|
||||
|
||||
onAlarmDialogReject() {
|
||||
public onAlarmDialogReject() {
|
||||
this.setState({ alarmDialogShown: false });
|
||||
}
|
||||
|
||||
async showOnMap_onPress() {
|
||||
private async showOnMap_onPress() {
|
||||
if (!this.state.note.id) return;
|
||||
|
||||
const note = await Note.load(this.state.note.id);
|
||||
@@ -807,7 +803,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
}
|
||||
}
|
||||
|
||||
async showSource_onPress() {
|
||||
private async showSource_onPress() {
|
||||
if (!this.state.note.id) return;
|
||||
|
||||
const note = await Note.load(this.state.note.id);
|
||||
@@ -818,12 +814,12 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
}
|
||||
}
|
||||
|
||||
copyMarkdownLink_onPress() {
|
||||
private copyMarkdownLink_onPress() {
|
||||
const note = this.state.note;
|
||||
Clipboard.setString(Note.markdownTag(note));
|
||||
}
|
||||
|
||||
sideMenuOptions() {
|
||||
public sideMenuOptions() {
|
||||
const note = this.state.note;
|
||||
if (!note) return [];
|
||||
|
||||
@@ -854,7 +850,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
return output;
|
||||
}
|
||||
|
||||
async showAttachMenu() {
|
||||
public async showAttachMenu() {
|
||||
const buttons = [];
|
||||
|
||||
// On iOS, it will show "local files", which means certain files saved from the browser
|
||||
@@ -875,7 +871,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
if (buttonId === 'attachPhoto') void this.attachPhoto_onPress();
|
||||
}
|
||||
|
||||
menuOptions() {
|
||||
public menuOptions() {
|
||||
const note = this.state.note;
|
||||
const isTodo = note && !!note.is_todo;
|
||||
const isSaved = note && note.id;
|
||||
@@ -959,11 +955,11 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
return output;
|
||||
}
|
||||
|
||||
async todoCheckbox_change(checked: boolean) {
|
||||
private async todoCheckbox_change(checked: boolean) {
|
||||
await this.saveOneProperty('todo_completed', checked ? time.unixMs() : 0);
|
||||
}
|
||||
|
||||
scheduleFocusUpdate() {
|
||||
public scheduleFocusUpdate() {
|
||||
if (this.focusUpdateIID_) shim.clearTimeout(this.focusUpdateIID_);
|
||||
|
||||
this.focusUpdateIID_ = shim.setTimeout(() => {
|
||||
@@ -972,7 +968,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
}, 100);
|
||||
}
|
||||
|
||||
focusUpdate() {
|
||||
public focusUpdate() {
|
||||
if (this.focusUpdateIID_) shim.clearTimeout(this.focusUpdateIID_);
|
||||
this.focusUpdateIID_ = null;
|
||||
|
||||
@@ -990,7 +986,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
// }
|
||||
}
|
||||
|
||||
async folderPickerOptions_valueChanged(itemValue: any) {
|
||||
private async folderPickerOptions_valueChanged(itemValue: any) {
|
||||
const note = this.state.note;
|
||||
const isProvisionalNote = this.props.provisionalNoteIds.includes(note.id);
|
||||
|
||||
@@ -1011,7 +1007,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
});
|
||||
}
|
||||
|
||||
folderPickerOptions() {
|
||||
public folderPickerOptions() {
|
||||
const options = {
|
||||
enabled: true,
|
||||
selectedFolderId: this.state.folder ? this.state.folder.id : null,
|
||||
@@ -1024,7 +1020,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
return this.folderPickerOptions_;
|
||||
}
|
||||
|
||||
onBodyViewerLoadEnd() {
|
||||
public onBodyViewerLoadEnd() {
|
||||
shim.setTimeout(() => {
|
||||
this.setState({ HACK_webviewLoadingState: 1 });
|
||||
shim.setTimeout(() => {
|
||||
@@ -1033,11 +1029,11 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
}, 5);
|
||||
}
|
||||
|
||||
onBodyViewerCheckboxChange(newBody: string) {
|
||||
public onBodyViewerCheckboxChange(newBody: string) {
|
||||
void this.saveOneProperty('body', newBody);
|
||||
}
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
if (this.state.isLoading) {
|
||||
return (
|
||||
<View style={this.styles().screen}>
|
||||
|
@@ -21,7 +21,7 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
|
||||
|
||||
private onAppStateChangeSub_: NativeEventSubscription = null;
|
||||
|
||||
constructor() {
|
||||
public constructor() {
|
||||
super();
|
||||
|
||||
this.onAppStateChange_ = async () => {
|
||||
@@ -78,7 +78,7 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
|
||||
};
|
||||
}
|
||||
|
||||
styles() {
|
||||
public styles() {
|
||||
if (!this.styles_) this.styles_ = {};
|
||||
const themeId = this.props.themeId;
|
||||
const cacheKey = themeId;
|
||||
@@ -96,24 +96,24 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
|
||||
return this.styles_[cacheKey];
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
public async componentDidMount() {
|
||||
BackButtonService.addHandler(this.backHandler);
|
||||
await this.refreshNotes();
|
||||
this.onAppStateChangeSub_ = RNAppState.addEventListener('change', this.onAppStateChange_);
|
||||
}
|
||||
|
||||
async componentWillUnmount() {
|
||||
public async componentWillUnmount() {
|
||||
if (this.onAppStateChangeSub_) this.onAppStateChangeSub_.remove();
|
||||
BackButtonService.removeHandler(this.backHandler);
|
||||
}
|
||||
|
||||
async componentDidUpdate(prevProps: any) {
|
||||
public async componentDidUpdate(prevProps: any) {
|
||||
if (prevProps.notesOrder !== this.props.notesOrder || prevProps.selectedFolderId !== this.props.selectedFolderId || prevProps.selectedTagId !== this.props.selectedTagId || prevProps.selectedSmartFilterId !== this.props.selectedSmartFilterId || prevProps.notesParentType !== this.props.notesParentType) {
|
||||
await this.refreshNotes(this.props);
|
||||
}
|
||||
}
|
||||
|
||||
async refreshNotes(props: any = null) {
|
||||
public async refreshNotes(props: any = null) {
|
||||
if (props === null) props = this.props;
|
||||
|
||||
const options = {
|
||||
@@ -149,36 +149,7 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
|
||||
});
|
||||
}
|
||||
|
||||
deleteFolder_onPress(folderId: string) {
|
||||
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
|
||||
dialogs.confirm(this, _('Delete notebook? All notes and sub-notebooks within this notebook will also be deleted.')).then((ok: boolean) => {
|
||||
if (!ok) return;
|
||||
|
||||
Folder.delete(folderId)
|
||||
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
|
||||
.then(() => {
|
||||
this.props.dispatch({
|
||||
type: 'NAV_GO',
|
||||
routeName: 'Notes',
|
||||
smartFilterId: 'c3176726992c11e9ac940492261af972',
|
||||
});
|
||||
})
|
||||
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
|
||||
.catch(error => {
|
||||
alert(error.message);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
editFolder_onPress(folderId: string) {
|
||||
this.props.dispatch({
|
||||
type: 'NAV_GO',
|
||||
routeName: 'Folder',
|
||||
folderId: folderId,
|
||||
});
|
||||
}
|
||||
|
||||
newNoteNavigate = async (folderId: string, isTodo: boolean) => {
|
||||
public newNoteNavigate = async (folderId: string, isTodo: boolean) => {
|
||||
const newNote = await Note.save({
|
||||
parent_id: folderId,
|
||||
is_todo: isTodo ? 1 : 0,
|
||||
@@ -191,7 +162,7 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
|
||||
});
|
||||
};
|
||||
|
||||
parentItem(props: any = null) {
|
||||
public parentItem(props: any = null) {
|
||||
if (!props) props = this.props;
|
||||
|
||||
let output = null;
|
||||
@@ -208,7 +179,7 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
|
||||
return output;
|
||||
}
|
||||
|
||||
folderPickerOptions() {
|
||||
public folderPickerOptions() {
|
||||
const options = {
|
||||
enabled: this.props.noteSelectionEnabled,
|
||||
mustSelect: true,
|
||||
@@ -220,7 +191,7 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
|
||||
return this.folderPickerOptions_;
|
||||
}
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
const parent = this.parentItem();
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
|
@@ -27,11 +27,11 @@ class SearchScreenComponent extends BaseScreenComponent {
|
||||
private styles_: any = {};
|
||||
private scheduleSearchTimer_: any = null;
|
||||
|
||||
static navigationOptions() {
|
||||
public static navigationOptions() {
|
||||
return { header: null } as any;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
public constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
query: '',
|
||||
@@ -39,7 +39,7 @@ class SearchScreenComponent extends BaseScreenComponent {
|
||||
};
|
||||
}
|
||||
|
||||
styles() {
|
||||
public styles() {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
if (this.styles_[this.props.themeId]) return this.styles_[this.props.themeId];
|
||||
@@ -72,17 +72,17 @@ class SearchScreenComponent extends BaseScreenComponent {
|
||||
return this.styles_[this.props.themeId];
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
public componentDidMount() {
|
||||
this.setState({ query: this.props.query });
|
||||
void this.refreshSearch(this.props.query);
|
||||
this.isMounted_ = true;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
public componentWillUnmount() {
|
||||
this.isMounted_ = false;
|
||||
}
|
||||
|
||||
clearButton_press() {
|
||||
private clearButton_press() {
|
||||
this.props.dispatch({
|
||||
type: 'SEARCH_QUERY',
|
||||
query: '',
|
||||
@@ -92,7 +92,7 @@ class SearchScreenComponent extends BaseScreenComponent {
|
||||
void this.refreshSearch('');
|
||||
}
|
||||
|
||||
async refreshSearch(query: string = null) {
|
||||
public async refreshSearch(query: string = null) {
|
||||
if (!this.props.visible) return;
|
||||
|
||||
query = gotoAnythingStyleQuery(query);
|
||||
@@ -130,7 +130,7 @@ class SearchScreenComponent extends BaseScreenComponent {
|
||||
this.setState({ notes: notes });
|
||||
}
|
||||
|
||||
scheduleSearch() {
|
||||
public scheduleSearch() {
|
||||
if (this.scheduleSearchTimer_) clearTimeout(this.scheduleSearchTimer_);
|
||||
|
||||
this.scheduleSearchTimer_ = setTimeout(() => {
|
||||
@@ -139,7 +139,7 @@ class SearchScreenComponent extends BaseScreenComponent {
|
||||
}, 200);
|
||||
}
|
||||
|
||||
searchTextInput_changeText(text: string) {
|
||||
private searchTextInput_changeText(text: string) {
|
||||
this.setState({ query: text });
|
||||
|
||||
this.props.dispatch({
|
||||
@@ -150,7 +150,7 @@ class SearchScreenComponent extends BaseScreenComponent {
|
||||
this.scheduleSearch();
|
||||
}
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
if (!this.isMounted_) return null;
|
||||
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
@@ -517,13 +517,13 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 86;
|
||||
CURRENT_PROJECT_VERSION = 89;
|
||||
DEVELOPMENT_TEAM = A9BXAFS6CT;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Joplin/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 12.10.2;
|
||||
MARKETING_VERSION = 12.10.5;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -546,12 +546,12 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 86;
|
||||
CURRENT_PROJECT_VERSION = 89;
|
||||
DEVELOPMENT_TEAM = A9BXAFS6CT;
|
||||
INFOPLIST_FILE = Joplin/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 12.10.2;
|
||||
MARKETING_VERSION = 12.10.5;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -698,14 +698,14 @@
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 86;
|
||||
CURRENT_PROJECT_VERSION = 89;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = A9BXAFS6CT;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MARKETING_VERSION = 12.10.2;
|
||||
MARKETING_VERSION = 12.10.5;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension;
|
||||
@@ -729,14 +729,14 @@
|
||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 86;
|
||||
CURRENT_PROJECT_VERSION = 89;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = A9BXAFS6CT;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MARKETING_VERSION = 12.10.2;
|
||||
MARKETING_VERSION = 12.10.5;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
@@ -324,7 +324,7 @@ PODS:
|
||||
- React-Core
|
||||
- react-native-get-random-values (1.8.0):
|
||||
- React-Core
|
||||
- react-native-image-picker (5.0.1):
|
||||
- react-native-image-picker (5.0.2):
|
||||
- React-Core
|
||||
- react-native-image-resizer (1.4.5):
|
||||
- React-Core
|
||||
@@ -332,7 +332,7 @@ PODS:
|
||||
- React-Core
|
||||
- react-native-rsa-native (2.0.5):
|
||||
- React
|
||||
- react-native-saf-x (2.10.0):
|
||||
- react-native-saf-x (2.10.2):
|
||||
- React-Core
|
||||
- react-native-safe-area-context (4.5.0):
|
||||
- RCT-Folly
|
||||
@@ -420,7 +420,7 @@ PODS:
|
||||
- React-Core
|
||||
- RNCPushNotificationIOS (1.10.1):
|
||||
- React-Core
|
||||
- RNDateTimePicker (6.7.4):
|
||||
- RNDateTimePicker (6.7.5):
|
||||
- React-Core
|
||||
- RNExitApp (1.1.0):
|
||||
- React
|
||||
@@ -432,7 +432,7 @@ PODS:
|
||||
- React
|
||||
- RNSecureRandom (1.0.1):
|
||||
- React
|
||||
- RNShare (8.1.0):
|
||||
- RNShare (8.2.0):
|
||||
- React-Core
|
||||
- RNVectorIcons (9.2.0):
|
||||
- React-Core
|
||||
@@ -720,11 +720,11 @@ SPEC CHECKSUMS:
|
||||
react-native-fingerprint-scanner: ac6656f18c8e45a7459302b84da41a44ad96dbbe
|
||||
react-native-geolocation: 69f4fd37650b8e7fee91816d395e62dd16f5ab8d
|
||||
react-native-get-random-values: a6ea6a8a65dc93e96e24a11105b1a9c8cfe1d72a
|
||||
react-native-image-picker: 8cb4280e2c1efc3daeb2d9d597f9429a60472e40
|
||||
react-native-image-picker: a5dddebb4d2955ac4712a4ed66b00a85f62a63ac
|
||||
react-native-image-resizer: d9fb629a867335bdc13230ac2a58702bb8c8828f
|
||||
react-native-netinfo: 2517ad504b3d303e90d7a431b0fcaef76d207983
|
||||
react-native-rsa-native: 12132eb627797529fdb1f0d22fd0f8f9678df64a
|
||||
react-native-saf-x: 22bcd49188a04d6d6df254ca33085f26e28879c9
|
||||
react-native-saf-x: db5a33862e7aec0f9f2d4cccfe7264b09b234e2e
|
||||
react-native-safe-area-context: 39c2d8be3328df5d437ac1700f4f3a4f75716acc
|
||||
react-native-slider: 33b8d190b59d4f67a541061bb91775d53d617d9d
|
||||
react-native-sqlite-storage: f6d515e1c446d1e6d026aa5352908a25d4de3261
|
||||
@@ -745,13 +745,13 @@ SPEC CHECKSUMS:
|
||||
rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba
|
||||
RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495
|
||||
RNCPushNotificationIOS: 87b8d16d3ede4532745e05b03c42cff33a36cc45
|
||||
RNDateTimePicker: 2cc0a68b714aba89b4bc017796cd8c8825e0c3a7
|
||||
RNDateTimePicker: 65e1d202799460b286ff5e741d8baf54695e8abd
|
||||
RNExitApp: c4e052df2568b43bec8a37c7cd61194d4cfee2c3
|
||||
RNFileViewer: ce7ca3ac370e18554d35d6355cffd7c30437c592
|
||||
RNFS: 4ac0f0ea233904cb798630b3c077808c06931688
|
||||
RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93
|
||||
RNSecureRandom: 07efbdf2cd99efe13497433668e54acd7df49fef
|
||||
RNShare: 48b3113cd089a2be8ff0515c3ae7a46a4db8a76b
|
||||
RNShare: b089c33619bbfb0a32bc4069c858b9274e694187
|
||||
RNVectorIcons: fcc2f6cb32f5735b586e66d14103a74ce6ad61f8
|
||||
SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608
|
||||
Yoga: 99caf8d5ab45e9d637ee6e0174ec16fbbb01bcfc
|
||||
|
@@ -23,7 +23,7 @@
|
||||
"@joplin/react-native-saf-x": "~2.10",
|
||||
"@joplin/renderer": "~2.10",
|
||||
"@react-native-community/clipboard": "1.5.1",
|
||||
"@react-native-community/datetimepicker": "6.7.4",
|
||||
"@react-native-community/datetimepicker": "6.7.5",
|
||||
"@react-native-community/geolocation": "2.1.0",
|
||||
"@react-native-community/netinfo": "9.3.7",
|
||||
"@react-native-community/push-notification-ios": "1.10.1",
|
||||
@@ -51,16 +51,16 @@
|
||||
"react-native-fingerprint-scanner": "6.0.0",
|
||||
"react-native-fs": "2.20.0",
|
||||
"react-native-get-random-values": "1.8.0",
|
||||
"react-native-image-picker": "5.0.1",
|
||||
"react-native-image-picker": "5.0.2",
|
||||
"react-native-image-resizer": "1.4.5",
|
||||
"react-native-modal-datetime-picker": "14.0.1",
|
||||
"react-native-paper": "5.1.4",
|
||||
"react-native-paper": "5.2.0",
|
||||
"react-native-popup-menu": "0.16.1",
|
||||
"react-native-quick-actions": "0.3.13",
|
||||
"react-native-rsa-native": "2.0.5",
|
||||
"react-native-safe-area-context": "4.5.0",
|
||||
"react-native-securerandom": "1.0.1",
|
||||
"react-native-share": "8.1.0",
|
||||
"react-native-share": "8.2.0",
|
||||
"react-native-side-menu-updated": "1.3.2",
|
||||
"react-native-sqlite-storage": "6.0.1",
|
||||
"react-native-url-polyfill": "1.3.0",
|
||||
@@ -102,8 +102,8 @@
|
||||
"execa": "4.1.0",
|
||||
"fs-extra": "11.1.0",
|
||||
"gulp": "4.0.2",
|
||||
"jest": "29.4.2",
|
||||
"jest-environment-jsdom": "29.4.2",
|
||||
"jest": "29.4.3",
|
||||
"jest-environment-jsdom": "29.4.3",
|
||||
"jetifier": "2.0.0",
|
||||
"jsdom": "21.0.0",
|
||||
"md5-file": "5.0.0",
|
||||
|
@@ -8,7 +8,7 @@ export default class AlarmServiceDriver {
|
||||
private inAppNotificationHandler_: any = null;
|
||||
private logger_: Logger;
|
||||
|
||||
constructor(logger: Logger) {
|
||||
public constructor(logger: Logger) {
|
||||
this.logger_ = logger;
|
||||
PushNotificationIOS.addEventListener('localNotification', (instance: any) => {
|
||||
if (!this.inAppNotificationHandler_) return;
|
||||
@@ -23,19 +23,19 @@ export default class AlarmServiceDriver {
|
||||
});
|
||||
}
|
||||
|
||||
hasPersistentNotifications() {
|
||||
public hasPersistentNotifications() {
|
||||
return true;
|
||||
}
|
||||
|
||||
notificationIsSet() {
|
||||
public notificationIsSet() {
|
||||
throw new Error('Available only for non-persistent alarms');
|
||||
}
|
||||
|
||||
setInAppNotificationHandler(v: any) {
|
||||
public setInAppNotificationHandler(v: any) {
|
||||
this.inAppNotificationHandler_ = v;
|
||||
}
|
||||
|
||||
async hasPermissions(perm: any = null) {
|
||||
public async hasPermissions(perm: any = null) {
|
||||
if (perm !== null) return perm.alert && perm.badge && perm.sound;
|
||||
|
||||
if (this.hasPermission_ !== null) return this.hasPermission_;
|
||||
@@ -49,7 +49,7 @@ export default class AlarmServiceDriver {
|
||||
});
|
||||
}
|
||||
|
||||
async requestPermissions() {
|
||||
public async requestPermissions() {
|
||||
const options: any = {
|
||||
alert: 1,
|
||||
badge: 1,
|
||||
@@ -60,11 +60,11 @@ export default class AlarmServiceDriver {
|
||||
return this.hasPermissions(newPerm);
|
||||
}
|
||||
|
||||
async clearNotification(id: number) {
|
||||
public async clearNotification(id: number) {
|
||||
PushNotificationIOS.cancelLocalNotifications({ id: `${id}` });
|
||||
}
|
||||
|
||||
async scheduleNotification(notification: Notification) {
|
||||
public async scheduleNotification(notification: Notification) {
|
||||
if (!(await this.hasPermissions())) {
|
||||
const ok = await this.requestPermissions();
|
||||
if (!ok) return;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@joplin/fork-htmlparser2",
|
||||
"description": "Fast & forgiving HTML/XML/RSS parser",
|
||||
"version": "4.1.41",
|
||||
"version": "4.1.43",
|
||||
"author": "Felix Boehm <me@feedic.com>",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -52,7 +52,7 @@
|
||||
"coveralls": "3.1.1",
|
||||
"eslint": "8.31.0",
|
||||
"eslint-config-prettier": "8.6.0",
|
||||
"jest": "29.4.2",
|
||||
"jest": "29.4.3",
|
||||
"prettier": "2.8.3",
|
||||
"ts-jest": "29.0.5",
|
||||
"typescript": "4.9.4"
|
||||
|
@@ -2,7 +2,7 @@
|
||||
"name": "@joplin/fork-sax",
|
||||
"description": "An evented streaming XML parser in JavaScript",
|
||||
"author": "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/)",
|
||||
"version": "1.2.45",
|
||||
"version": "1.2.47",
|
||||
"main": "lib/sax.js",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/fork-uslug",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.8",
|
||||
"description": "A permissive slug generator that works with unicode.",
|
||||
"author": "Jeremy Selier <jerem.selier@gmail.com>",
|
||||
"publishConfig": {
|
||||
|
@@ -25,11 +25,11 @@
|
||||
"dependencies": {
|
||||
"chalk": "2.4.2",
|
||||
"slugify": "1.6.5",
|
||||
"yeoman-generator": "5.7.1",
|
||||
"yeoman-generator": "5.8.0",
|
||||
"yosay": "2.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "29.4.2"
|
||||
"jest": "29.4.3"
|
||||
},
|
||||
"repository": "https://github.com/laurent22/generator-joplin",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/htmlpack",
|
||||
"version": "2.10.0",
|
||||
"version": "2.10.2",
|
||||
"description": "Pack an HTML file and all its linked resources into a single HTML file",
|
||||
"main": "dist/index.js",
|
||||
"types": "src/index.ts",
|
||||
@@ -14,7 +14,7 @@
|
||||
"author": "Laurent Cozic",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@joplin/fork-htmlparser2": "^4.1.41",
|
||||
"@joplin/fork-htmlparser2": "^4.1.43",
|
||||
"css": "3.0.0",
|
||||
"datauri": "4.1.0",
|
||||
"fs-extra": "11.1.0",
|
||||
|
@@ -418,7 +418,7 @@ export default class BaseApplication {
|
||||
ResourceFetcher.instance().scheduleAutoAddResources();
|
||||
}
|
||||
|
||||
reducerActionToString(action: any) {
|
||||
public reducerActionToString(action: any) {
|
||||
const o = [action.type];
|
||||
if ('id' in action) o.push(action.id);
|
||||
if ('noteId' in action) o.push(action.noteId);
|
||||
|
@@ -85,19 +85,19 @@ class BaseModel {
|
||||
|
||||
private static db_: JoplinDatabase;
|
||||
|
||||
static modelType(): ModelType {
|
||||
public static modelType(): ModelType {
|
||||
throw new Error('Must be overriden');
|
||||
}
|
||||
|
||||
static tableName(): string {
|
||||
public static tableName(): string {
|
||||
throw new Error('Must be overriden');
|
||||
}
|
||||
|
||||
static setDb(db: any) {
|
||||
public static setDb(db: any) {
|
||||
this.db_ = db;
|
||||
}
|
||||
|
||||
static addModelMd(model: any): any {
|
||||
public static addModelMd(model: any): any {
|
||||
if (!model) return model;
|
||||
|
||||
if (Array.isArray(model)) {
|
||||
@@ -113,22 +113,22 @@ class BaseModel {
|
||||
}
|
||||
}
|
||||
|
||||
static logger() {
|
||||
public static logger() {
|
||||
return this.db().logger();
|
||||
}
|
||||
|
||||
static useUuid() {
|
||||
public static useUuid() {
|
||||
return false;
|
||||
}
|
||||
|
||||
static byId(items: any[], id: string) {
|
||||
public static byId(items: any[], id: string) {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (items[i].id === id) return items[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static defaultValues(fieldNames: string[]) {
|
||||
public static defaultValues(fieldNames: string[]) {
|
||||
const output: any = {};
|
||||
for (const n of fieldNames) {
|
||||
output[n] = this.db().fieldDefaultValue(this.tableName(), n);
|
||||
@@ -136,14 +136,14 @@ class BaseModel {
|
||||
return output;
|
||||
}
|
||||
|
||||
static modelIndexById(items: any[], id: string) {
|
||||
public static modelIndexById(items: any[], id: string) {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (items[i].id === id) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static modelsByIds(items: any[], ids: string[]) {
|
||||
public static modelsByIds(items: any[], ids: string[]) {
|
||||
const output = [];
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (ids.indexOf(items[i].id) >= 0) {
|
||||
@@ -155,14 +155,14 @@ class BaseModel {
|
||||
|
||||
// Prefer the use of this function to compare IDs as it handles the case where
|
||||
// one ID is null and the other is "", in which case they are actually considered to be the same.
|
||||
static idsEqual(id1: string, id2: string) {
|
||||
public static idsEqual(id1: string, id2: string) {
|
||||
if (!id1 && !id2) return true;
|
||||
if (!id1 && !!id2) return false;
|
||||
if (!!id1 && !id2) return false;
|
||||
return id1 === id2;
|
||||
}
|
||||
|
||||
static modelTypeToName(type: number) {
|
||||
public static modelTypeToName(type: number) {
|
||||
for (let i = 0; i < BaseModel.typeEnum_.length; i++) {
|
||||
const e = BaseModel.typeEnum_[i];
|
||||
if (e[1] === type) return e[0].substr(5).toLowerCase();
|
||||
@@ -170,7 +170,7 @@ class BaseModel {
|
||||
throw new Error(`Unknown model type: ${type}`);
|
||||
}
|
||||
|
||||
static modelNameToType(name: string) {
|
||||
public static modelNameToType(name: string) {
|
||||
for (let i = 0; i < BaseModel.typeEnum_.length; i++) {
|
||||
const e = BaseModel.typeEnum_[i];
|
||||
const eName = e[0].substr(5).toLowerCase();
|
||||
@@ -179,12 +179,12 @@ class BaseModel {
|
||||
throw new Error(`Unknown model name: ${name}`);
|
||||
}
|
||||
|
||||
static hasField(name: string) {
|
||||
public static hasField(name: string) {
|
||||
const fields = this.fieldNames();
|
||||
return fields.indexOf(name) >= 0;
|
||||
}
|
||||
|
||||
static fieldNames(withPrefix: boolean = false) {
|
||||
public static fieldNames(withPrefix: boolean = false) {
|
||||
const output = this.db().tableFieldNames(this.tableName());
|
||||
if (!withPrefix) return output;
|
||||
|
||||
@@ -197,7 +197,7 @@ class BaseModel {
|
||||
return temp;
|
||||
}
|
||||
|
||||
static fieldType(name: string, defaultValue: any = null) {
|
||||
public static fieldType(name: string, defaultValue: any = null) {
|
||||
const fields = this.fields();
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
if (fields[i].name === name) return fields[i].type;
|
||||
@@ -206,11 +206,11 @@ class BaseModel {
|
||||
throw new Error(`Unknown field: ${name}`);
|
||||
}
|
||||
|
||||
static fields(): TableField[] {
|
||||
public static fields(): TableField[] {
|
||||
return this.db().tableFields(this.tableName());
|
||||
}
|
||||
|
||||
static removeUnknownFields(model: any) {
|
||||
public static removeUnknownFields(model: any) {
|
||||
const newModel: any = {};
|
||||
for (const n in model) {
|
||||
if (!model.hasOwnProperty(n)) continue;
|
||||
@@ -220,7 +220,7 @@ class BaseModel {
|
||||
return newModel;
|
||||
}
|
||||
|
||||
static new() {
|
||||
public static new() {
|
||||
const fields = this.fields();
|
||||
const output: any = {};
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
@@ -230,7 +230,7 @@ class BaseModel {
|
||||
return output;
|
||||
}
|
||||
|
||||
static modOptions(options: any) {
|
||||
public static modOptions(options: any) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
} else {
|
||||
@@ -242,7 +242,7 @@ class BaseModel {
|
||||
return options;
|
||||
}
|
||||
|
||||
static count(options: any = null) {
|
||||
public static count(options: any = null) {
|
||||
if (!options) options = {};
|
||||
let sql = `SELECT count(*) as total FROM \`${this.tableName()}\``;
|
||||
if (options.where) sql += ` WHERE ${options.where}`;
|
||||
@@ -254,19 +254,19 @@ class BaseModel {
|
||||
});
|
||||
}
|
||||
|
||||
static load(id: string, options: any = null) {
|
||||
public static load(id: string, options: any = null) {
|
||||
return this.loadByField('id', id, options);
|
||||
}
|
||||
|
||||
static shortId(id: string) {
|
||||
public static shortId(id: string) {
|
||||
return id.substr(0, 5);
|
||||
}
|
||||
|
||||
static loadByPartialId(partialId: string) {
|
||||
public static loadByPartialId(partialId: string) {
|
||||
return this.modelSelectAll(`SELECT * FROM \`${this.tableName()}\` WHERE \`id\` LIKE ?`, [`${partialId}%`]);
|
||||
}
|
||||
|
||||
static applySqlOptions(options: any, sql: string, params: any[] = null) {
|
||||
public static applySqlOptions(options: any, sql: string, params: any[] = null) {
|
||||
if (!options) options = {};
|
||||
|
||||
if (options.order && options.order.length) {
|
||||
@@ -278,13 +278,13 @@ class BaseModel {
|
||||
return { sql: sql, params: params };
|
||||
}
|
||||
|
||||
static async allIds(options: any = null) {
|
||||
public static async allIds(options: any = null) {
|
||||
const q = this.applySqlOptions(options, `SELECT id FROM \`${this.tableName()}\``);
|
||||
const rows = await this.db().selectAll(q.sql, q.params);
|
||||
return rows.map((r: any) => r.id);
|
||||
}
|
||||
|
||||
static async all(options: any = null) {
|
||||
public static async all(options: any = null) {
|
||||
if (!options) options = {};
|
||||
if (!options.fields) options.fields = '*';
|
||||
|
||||
@@ -299,7 +299,7 @@ class BaseModel {
|
||||
return this.modelSelectAll(q.sql, q.params);
|
||||
}
|
||||
|
||||
static async byIds(ids: string[], options: any = null) {
|
||||
public static async byIds(ids: string[], options: any = null) {
|
||||
if (!ids.length) return [];
|
||||
if (!options) options = {};
|
||||
if (!options.fields) options.fields = '*';
|
||||
@@ -310,7 +310,7 @@ class BaseModel {
|
||||
return this.modelSelectAll(q.sql);
|
||||
}
|
||||
|
||||
static async search(options: any = null) {
|
||||
public static async search(options: any = null) {
|
||||
if (!options) options = {};
|
||||
if (!options.fields) options.fields = '*';
|
||||
|
||||
@@ -332,7 +332,7 @@ class BaseModel {
|
||||
return this.modelSelectAll(query.sql, query.params);
|
||||
}
|
||||
|
||||
static modelSelectOne(sql: string, params: any[] = null) {
|
||||
public static modelSelectOne(sql: string, params: any[] = null) {
|
||||
if (params === null) params = [];
|
||||
return this.db()
|
||||
.selectOne(sql, params)
|
||||
@@ -342,7 +342,7 @@ class BaseModel {
|
||||
});
|
||||
}
|
||||
|
||||
static modelSelectAll(sql: string, params: any[] = null) {
|
||||
public static modelSelectAll(sql: string, params: any[] = null) {
|
||||
if (params === null) params = [];
|
||||
return this.db()
|
||||
.selectAll(sql, params)
|
||||
@@ -352,7 +352,7 @@ class BaseModel {
|
||||
});
|
||||
}
|
||||
|
||||
static loadByField(fieldName: string, fieldValue: any, options: any = null) {
|
||||
public static loadByField(fieldName: string, fieldValue: any, options: any = null) {
|
||||
if (!options) options = {};
|
||||
if (!('caseInsensitive' in options)) options.caseInsensitive = false;
|
||||
if (!options.fields) options.fields = '*';
|
||||
@@ -361,7 +361,7 @@ class BaseModel {
|
||||
return this.modelSelectOne(sql, [fieldValue]);
|
||||
}
|
||||
|
||||
static loadByFields(fields: any, options: any = null) {
|
||||
public static loadByFields(fields: any, options: any = null) {
|
||||
if (!options) options = {};
|
||||
if (!('caseInsensitive' in options)) options.caseInsensitive = false;
|
||||
if (!options.fields) options.fields = '*';
|
||||
@@ -376,11 +376,11 @@ class BaseModel {
|
||||
return this.modelSelectOne(sql, params);
|
||||
}
|
||||
|
||||
static loadByTitle(fieldValue: any) {
|
||||
public static loadByTitle(fieldValue: any) {
|
||||
return this.modelSelectOne(`SELECT * FROM \`${this.tableName()}\` WHERE \`title\` = ?`, [fieldValue]);
|
||||
}
|
||||
|
||||
static diffObjects(oldModel: any, newModel: any) {
|
||||
public static diffObjects(oldModel: any, newModel: any) {
|
||||
const output: any = {};
|
||||
const fields = this.diffObjectsFields(oldModel, newModel);
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
@@ -390,7 +390,7 @@ class BaseModel {
|
||||
return output;
|
||||
}
|
||||
|
||||
static diffObjectsFields(oldModel: any, newModel: any) {
|
||||
public static diffObjectsFields(oldModel: any, newModel: any) {
|
||||
const output = [];
|
||||
for (const n in newModel) {
|
||||
if (!newModel.hasOwnProperty(n)) continue;
|
||||
@@ -402,13 +402,13 @@ class BaseModel {
|
||||
return output;
|
||||
}
|
||||
|
||||
static modelsAreSame(oldModel: any, newModel: any) {
|
||||
public static modelsAreSame(oldModel: any, newModel: any) {
|
||||
const diff = this.diffObjects(oldModel, newModel);
|
||||
delete diff.type_;
|
||||
return !Object.getOwnPropertyNames(diff).length;
|
||||
}
|
||||
|
||||
static saveMutex(modelOrId: any) {
|
||||
public static saveMutex(modelOrId: any) {
|
||||
const noLockMutex = {
|
||||
acquire: function(): any {
|
||||
return null;
|
||||
@@ -429,7 +429,7 @@ class BaseModel {
|
||||
return mutex;
|
||||
}
|
||||
|
||||
static releaseSaveMutex(modelOrId: any, release: Function) {
|
||||
public static releaseSaveMutex(modelOrId: any, release: Function) {
|
||||
if (!release) return;
|
||||
if (!modelOrId) return release();
|
||||
|
||||
@@ -444,7 +444,7 @@ class BaseModel {
|
||||
release();
|
||||
}
|
||||
|
||||
static saveQuery(o: any, options: any) {
|
||||
public static saveQuery(o: any, options: any) {
|
||||
let temp: any = {};
|
||||
const fieldNames = this.fieldNames();
|
||||
for (let i = 0; i < fieldNames.length; i++) {
|
||||
@@ -521,7 +521,7 @@ class BaseModel {
|
||||
return query;
|
||||
}
|
||||
|
||||
static userSideValidation(o: any) {
|
||||
public static userSideValidation(o: any) {
|
||||
if (o.id && !o.id.match(/^[a-f0-9]{32}$/)) {
|
||||
throw new Error('Validation error: ID must a 32-characters lowercase hexadecimal string');
|
||||
}
|
||||
@@ -532,7 +532,7 @@ class BaseModel {
|
||||
}
|
||||
}
|
||||
|
||||
static async save(o: any, options: any = null) {
|
||||
public static async save(o: any, options: any = null) {
|
||||
// When saving, there's a mutex per model ID. This is because the model returned from this function
|
||||
// is basically its input `o` (instead of being read from the database, for performance reasons).
|
||||
// This works well in general except if that model is saved simultaneously in two places. In that
|
||||
@@ -602,7 +602,7 @@ class BaseModel {
|
||||
return output;
|
||||
}
|
||||
|
||||
static isNew(object: any, options: any) {
|
||||
public static isNew(object: any, options: any) {
|
||||
if (options && 'isNew' in options) {
|
||||
// options.isNew can be "auto" too
|
||||
if (options.isNew === true) return true;
|
||||
@@ -612,7 +612,7 @@ class BaseModel {
|
||||
return !object.id;
|
||||
}
|
||||
|
||||
static filterArray(models: any[]) {
|
||||
public static filterArray(models: any[]) {
|
||||
const output = [];
|
||||
for (let i = 0; i < models.length; i++) {
|
||||
output.push(this.filter(models[i]));
|
||||
@@ -620,7 +620,7 @@ class BaseModel {
|
||||
return output;
|
||||
}
|
||||
|
||||
static filter(model: any) {
|
||||
public static filter(model: any) {
|
||||
if (!model) return model;
|
||||
|
||||
const output = Object.assign({}, model);
|
||||
@@ -643,12 +643,12 @@ class BaseModel {
|
||||
return output;
|
||||
}
|
||||
|
||||
static delete(id: string) {
|
||||
public static delete(id: string) {
|
||||
if (!id) throw new Error('Cannot delete object without an ID');
|
||||
return this.db().exec(`DELETE FROM ${this.tableName()} WHERE id = ?`, [id]);
|
||||
}
|
||||
|
||||
static async batchDelete(ids: string[], options: DeleteOptions = null) {
|
||||
public static async batchDelete(ids: string[], options: DeleteOptions = null) {
|
||||
if (!ids.length) return;
|
||||
options = this.modOptions(options);
|
||||
const idFieldName = options.idFieldName ? options.idFieldName : 'id';
|
||||
@@ -656,7 +656,7 @@ class BaseModel {
|
||||
await this.db().exec(sql);
|
||||
}
|
||||
|
||||
static db() {
|
||||
public static db() {
|
||||
if (!this.db_) throw new Error('Accessing database before it has been initialised');
|
||||
return this.db_;
|
||||
}
|
||||
|
@@ -24,11 +24,11 @@ export default class ClipperServer {
|
||||
|
||||
private static instance_: ClipperServer = null;
|
||||
|
||||
constructor() {
|
||||
public constructor() {
|
||||
this.logger_ = new Logger();
|
||||
}
|
||||
|
||||
static instance() {
|
||||
public static instance() {
|
||||
if (this.instance_) return this.instance_;
|
||||
this.instance_ = new ClipperServer();
|
||||
return this.instance_;
|
||||
@@ -38,30 +38,30 @@ export default class ClipperServer {
|
||||
return this.api_;
|
||||
}
|
||||
|
||||
initialize(actionApi: any = null) {
|
||||
public initialize(actionApi: any = null) {
|
||||
this.api_ = new Api(() => {
|
||||
return Setting.value('api.token');
|
||||
}, (action: any) => { this.dispatch(action); }, actionApi);
|
||||
}
|
||||
|
||||
setLogger(l: Logger) {
|
||||
public setLogger(l: Logger) {
|
||||
this.logger_ = l;
|
||||
}
|
||||
|
||||
logger() {
|
||||
public logger() {
|
||||
return this.logger_;
|
||||
}
|
||||
|
||||
setDispatch(d: Function) {
|
||||
public setDispatch(d: Function) {
|
||||
this.dispatch_ = d;
|
||||
}
|
||||
|
||||
dispatch(action: any) {
|
||||
public dispatch(action: any) {
|
||||
if (!this.dispatch_) throw new Error('dispatch not set!');
|
||||
this.dispatch_(action);
|
||||
}
|
||||
|
||||
setStartState(v: StartState) {
|
||||
public setStartState(v: StartState) {
|
||||
if (this.startState_ === v) return;
|
||||
this.startState_ = v;
|
||||
this.dispatch({
|
||||
@@ -70,7 +70,7 @@ export default class ClipperServer {
|
||||
});
|
||||
}
|
||||
|
||||
setPort(v: number) {
|
||||
public setPort(v: number) {
|
||||
if (this.port_ === v) return;
|
||||
this.port_ = v;
|
||||
this.dispatch({
|
||||
@@ -79,7 +79,7 @@ export default class ClipperServer {
|
||||
});
|
||||
}
|
||||
|
||||
async findAvailablePort() {
|
||||
public async findAvailablePort() {
|
||||
const tcpPortUsed = require('tcp-port-used');
|
||||
|
||||
let state = null;
|
||||
@@ -92,14 +92,14 @@ export default class ClipperServer {
|
||||
throw new Error('All potential ports are in use or not available.');
|
||||
}
|
||||
|
||||
async isRunning() {
|
||||
public async isRunning() {
|
||||
const tcpPortUsed = require('tcp-port-used');
|
||||
const port = Setting.value('api.port') ? Setting.value('api.port') : startPort(Setting.value('env'));
|
||||
const inUse = await tcpPortUsed.check(port);
|
||||
return inUse ? port : 0;
|
||||
}
|
||||
|
||||
async start() {
|
||||
public async start() {
|
||||
this.setPort(null);
|
||||
|
||||
this.setStartState(StartState.Starting);
|
||||
@@ -237,7 +237,7 @@ export default class ClipperServer {
|
||||
return new Promise(() => {});
|
||||
}
|
||||
|
||||
async stop() {
|
||||
public async stop() {
|
||||
this.server_.destroy();
|
||||
this.server_ = null;
|
||||
this.setStartState(StartState.Idle);
|
||||
|
@@ -29,7 +29,7 @@ export default class Cache {
|
||||
private expirableKeys_: ExpirableKeys = {};
|
||||
private recordKeyHistory_: string[] = [];
|
||||
|
||||
constructor(maxRecords: number = 50) {
|
||||
public constructor(maxRecords: number = 50) {
|
||||
this.maxRecords_ = maxRecords;
|
||||
}
|
||||
|
||||
|
@@ -63,7 +63,7 @@ class Logger {
|
||||
private lastDbCleanup_: number = time.unixMs();
|
||||
private enabled_: boolean = true;
|
||||
|
||||
static fsDriver() {
|
||||
public static fsDriver() {
|
||||
if (!Logger.fsDriver_) Logger.fsDriver_ = new FsDriverDummy();
|
||||
return Logger.fsDriver_;
|
||||
}
|
||||
@@ -103,7 +103,7 @@ class Logger {
|
||||
return this.globalLogger_;
|
||||
}
|
||||
|
||||
static create(prefix: string): LoggerWrapper {
|
||||
public static create(prefix: string): LoggerWrapper {
|
||||
return {
|
||||
debug: (...object: any[]) => this.globalLogger.log(LogLevel.Debug, prefix, ...object),
|
||||
info: (...object: any[]) => this.globalLogger.log(LogLevel.Info, prefix, ...object),
|
||||
@@ -118,15 +118,15 @@ class Logger {
|
||||
return previous;
|
||||
}
|
||||
|
||||
level() {
|
||||
public level() {
|
||||
return this.level_;
|
||||
}
|
||||
|
||||
targets() {
|
||||
public targets() {
|
||||
return this.targets_;
|
||||
}
|
||||
|
||||
addTarget(type: TargetType, options: TargetOptions = null) {
|
||||
public addTarget(type: TargetType, options: TargetOptions = null) {
|
||||
const target = { type: type };
|
||||
for (const n in options) {
|
||||
if (!options.hasOwnProperty(n)) continue;
|
||||
@@ -136,7 +136,7 @@ class Logger {
|
||||
this.targets_.push(target);
|
||||
}
|
||||
|
||||
objectToString(object: any) {
|
||||
public objectToString(object: any) {
|
||||
let output = '';
|
||||
|
||||
if (typeof object === 'object') {
|
||||
@@ -157,7 +157,7 @@ class Logger {
|
||||
return output;
|
||||
}
|
||||
|
||||
objectsToString(...object: any[]) {
|
||||
public objectsToString(...object: any[]) {
|
||||
const output = [];
|
||||
for (let i = 0; i < object.length; i++) {
|
||||
output.push(`"${this.objectToString(object[i])}"`);
|
||||
@@ -165,7 +165,7 @@ class Logger {
|
||||
return output.join(', ');
|
||||
}
|
||||
|
||||
static databaseCreateTableSql() {
|
||||
public static databaseCreateTableSql() {
|
||||
const output = `
|
||||
CREATE TABLE IF NOT EXISTS logs (
|
||||
id INTEGER PRIMARY KEY,
|
||||
@@ -179,7 +179,7 @@ class Logger {
|
||||
}
|
||||
|
||||
// Only for database at the moment
|
||||
async lastEntries(limit: number = 100, options: any = null) {
|
||||
public async lastEntries(limit: number = 100, options: any = null) {
|
||||
if (options === null) options = {};
|
||||
if (!options.levels) options.levels = [LogLevel.Debug, LogLevel.Info, LogLevel.Warn, LogLevel.Error];
|
||||
if (!options.levels.length) return [];
|
||||
@@ -195,7 +195,7 @@ class Logger {
|
||||
return [];
|
||||
}
|
||||
|
||||
targetLevel(target: Target) {
|
||||
public targetLevel(target: Target) {
|
||||
if ('level' in target) return target.level;
|
||||
return this.level();
|
||||
}
|
||||
@@ -287,20 +287,20 @@ class Logger {
|
||||
}
|
||||
}
|
||||
|
||||
error(...object: any[]) {
|
||||
public error(...object: any[]) {
|
||||
return this.log(LogLevel.Error, null, ...object);
|
||||
}
|
||||
warn(...object: any[]) {
|
||||
public warn(...object: any[]) {
|
||||
return this.log(LogLevel.Warn, null, ...object);
|
||||
}
|
||||
info(...object: any[]) {
|
||||
public info(...object: any[]) {
|
||||
return this.log(LogLevel.Info, null, ...object);
|
||||
}
|
||||
debug(...object: any[]) {
|
||||
public debug(...object: any[]) {
|
||||
return this.log(LogLevel.Debug, null, ...object);
|
||||
}
|
||||
|
||||
static levelStringToId(s: string) {
|
||||
public static levelStringToId(s: string) {
|
||||
if (s === 'none') return LogLevel.None;
|
||||
if (s === 'error') return LogLevel.Error;
|
||||
if (s === 'warn') return LogLevel.Warn;
|
||||
@@ -309,7 +309,7 @@ class Logger {
|
||||
throw new Error(`Unknown log level: ${s}`);
|
||||
}
|
||||
|
||||
static levelIdToString(id: LogLevel) {
|
||||
public static levelIdToString(id: LogLevel) {
|
||||
if (id === LogLevel.None) return 'none';
|
||||
if (id === LogLevel.Error) return 'error';
|
||||
if (id === LogLevel.Warn) return 'warn';
|
||||
@@ -318,7 +318,7 @@ class Logger {
|
||||
throw new Error(`Unknown level ID: ${id}`);
|
||||
}
|
||||
|
||||
static levelIds() {
|
||||
public static levelIds() {
|
||||
return [LogLevel.None, LogLevel.Error, LogLevel.Warn, LogLevel.Info, LogLevel.Debug];
|
||||
}
|
||||
|
||||
|
@@ -12,20 +12,20 @@ export default class SyncTargetOneDrive extends BaseSyncTarget {
|
||||
|
||||
private api_: any;
|
||||
|
||||
static id() {
|
||||
public static id() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
constructor(db: any, options: any = null) {
|
||||
public constructor(db: any, options: any = null) {
|
||||
super(db, options);
|
||||
this.api_ = null;
|
||||
}
|
||||
|
||||
static targetName() {
|
||||
public static targetName() {
|
||||
return 'onedrive';
|
||||
}
|
||||
|
||||
static label() {
|
||||
public static label() {
|
||||
return _('OneDrive');
|
||||
}
|
||||
|
||||
@@ -37,30 +37,30 @@ export default class SyncTargetOneDrive extends BaseSyncTarget {
|
||||
return false;
|
||||
}
|
||||
|
||||
async isAuthenticated() {
|
||||
public async isAuthenticated() {
|
||||
return !!this.api().auth();
|
||||
}
|
||||
|
||||
syncTargetId() {
|
||||
public syncTargetId() {
|
||||
return SyncTargetOneDrive.id();
|
||||
}
|
||||
|
||||
isTesting() {
|
||||
public isTesting() {
|
||||
const p = parameters();
|
||||
return !!p.oneDriveTest;
|
||||
}
|
||||
|
||||
oneDriveParameters() {
|
||||
public oneDriveParameters() {
|
||||
const p = parameters();
|
||||
if (p.oneDriveTest) return p.oneDriveTest;
|
||||
return p.oneDrive;
|
||||
}
|
||||
|
||||
authRouteName() {
|
||||
public authRouteName() {
|
||||
return 'OneDriveLogin';
|
||||
}
|
||||
|
||||
api() {
|
||||
public api() {
|
||||
if (this.isTesting()) {
|
||||
return this.fileApi_.driver().api();
|
||||
}
|
||||
@@ -92,7 +92,7 @@ export default class SyncTargetOneDrive extends BaseSyncTarget {
|
||||
return this.api_;
|
||||
}
|
||||
|
||||
async initFileApi() {
|
||||
public async initFileApi() {
|
||||
let context = Setting.value(`sync.${this.syncTargetId()}.context`);
|
||||
context = context === '' ? null : JSON.parse(context);
|
||||
let accountProperties = context ? context.accountProperties : null;
|
||||
@@ -109,7 +109,7 @@ export default class SyncTargetOneDrive extends BaseSyncTarget {
|
||||
return fileApi;
|
||||
}
|
||||
|
||||
async initSynchronizer() {
|
||||
public async initSynchronizer() {
|
||||
try {
|
||||
if (!(await this.isAuthenticated())) throw new Error('User is not authentified');
|
||||
return new Synchronizer(this.db(), await this.fileApi(), Setting.value('appType'));
|
||||
|
@@ -91,31 +91,31 @@ export default class Synchronizer {
|
||||
this.apiCall = this.apiCall.bind(this);
|
||||
}
|
||||
|
||||
state() {
|
||||
public state() {
|
||||
return this.state_;
|
||||
}
|
||||
|
||||
db() {
|
||||
public db() {
|
||||
return this.db_;
|
||||
}
|
||||
|
||||
api() {
|
||||
public api() {
|
||||
return this.api_;
|
||||
}
|
||||
|
||||
clientId() {
|
||||
public clientId() {
|
||||
return this.clientId_;
|
||||
}
|
||||
|
||||
setLogger(l: Logger) {
|
||||
public setLogger(l: Logger) {
|
||||
this.logger_ = l;
|
||||
}
|
||||
|
||||
logger() {
|
||||
public logger() {
|
||||
return this.logger_;
|
||||
}
|
||||
|
||||
lockHandler() {
|
||||
public lockHandler() {
|
||||
if (this.lockHandler_) return this.lockHandler_;
|
||||
this.lockHandler_ = new LockHandler(this.api());
|
||||
return this.lockHandler_;
|
||||
@@ -127,13 +127,13 @@ export default class Synchronizer {
|
||||
return this.lockClientType_;
|
||||
}
|
||||
|
||||
migrationHandler() {
|
||||
public migrationHandler() {
|
||||
if (this.migrationHandler_) return this.migrationHandler_;
|
||||
this.migrationHandler_ = new MigrationHandler(this.api(), this.db(), this.lockHandler(), this.lockClientType(), this.clientId_);
|
||||
return this.migrationHandler_;
|
||||
}
|
||||
|
||||
maxResourceSize() {
|
||||
public maxResourceSize() {
|
||||
if (this.maxResourceSize_ !== null) return this.maxResourceSize_;
|
||||
return this.appType_ === AppType.Mobile ? 100 * 1000 * 1000 : Infinity;
|
||||
}
|
||||
@@ -146,7 +146,7 @@ export default class Synchronizer {
|
||||
this.encryptionService_ = v;
|
||||
}
|
||||
|
||||
encryptionService() {
|
||||
public encryptionService() {
|
||||
return this.encryptionService_;
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ export default class Synchronizer {
|
||||
return this.resourceService_;
|
||||
}
|
||||
|
||||
async waitForSyncToFinish() {
|
||||
public async waitForSyncToFinish() {
|
||||
if (this.state() === 'idle') return;
|
||||
|
||||
while (true) {
|
||||
@@ -177,7 +177,7 @@ export default class Synchronizer {
|
||||
return `${duration}ms`;
|
||||
}
|
||||
|
||||
static reportToLines(report: any) {
|
||||
public static reportToLines(report: any) {
|
||||
const lines = [];
|
||||
if (report.createLocal) lines.push(_('Created local items: %d.', report.createLocal));
|
||||
if (report.updateLocal) lines.push(_('Updated local items: %d.', report.updateLocal));
|
||||
@@ -193,7 +193,7 @@ export default class Synchronizer {
|
||||
return lines;
|
||||
}
|
||||
|
||||
logSyncOperation(action: string, local: any = null, remote: RemoteItem = null, message: string = null, actionCount: number = 1) {
|
||||
public logSyncOperation(action: string, local: any = null, remote: RemoteItem = null, message: string = null, actionCount: number = 1) {
|
||||
const line = ['Sync'];
|
||||
line.push(action);
|
||||
if (message) line.push(message);
|
||||
@@ -237,7 +237,7 @@ export default class Synchronizer {
|
||||
this.dispatch({ type: 'SYNC_REPORT_UPDATE', report: reportCopy });
|
||||
}
|
||||
|
||||
async logSyncSummary(report: any) {
|
||||
public async logSyncSummary(report: any) {
|
||||
logger.info('Operations completed: ');
|
||||
for (const n in report) {
|
||||
if (!report.hasOwnProperty(n)) continue;
|
||||
@@ -265,7 +265,7 @@ export default class Synchronizer {
|
||||
}
|
||||
}
|
||||
|
||||
async cancel() {
|
||||
public async cancel() {
|
||||
if (this.cancelling_ || this.state() === 'idle') return;
|
||||
|
||||
// Stop queue but don't set it to null as it may be used to
|
||||
@@ -285,11 +285,11 @@ export default class Synchronizer {
|
||||
});
|
||||
}
|
||||
|
||||
cancelling() {
|
||||
public cancelling() {
|
||||
return this.cancelling_;
|
||||
}
|
||||
|
||||
logLastRequests() {
|
||||
public logLastRequests() {
|
||||
const lastRequests = this.api().lastRequests();
|
||||
if (!lastRequests || !lastRequests.length) return;
|
||||
|
||||
@@ -300,17 +300,17 @@ export default class Synchronizer {
|
||||
}
|
||||
}
|
||||
|
||||
static stateToLabel(state: string) {
|
||||
public static stateToLabel(state: string) {
|
||||
if (state === 'idle') return _('Idle');
|
||||
if (state === 'in_progress') return _('In progress');
|
||||
return state;
|
||||
}
|
||||
|
||||
isFullSync(steps: string[]) {
|
||||
public isFullSync(steps: string[]) {
|
||||
return steps.includes('update_remote') && steps.includes('delete_remote') && steps.includes('delta');
|
||||
}
|
||||
|
||||
async lockErrorStatus_() {
|
||||
private async lockErrorStatus_() {
|
||||
const locks = await this.lockHandler().locks();
|
||||
const currentDate = await this.lockHandler().currentDate();
|
||||
|
||||
|
@@ -23,16 +23,16 @@ export default class TaskQueue {
|
||||
private name_: string;
|
||||
private logger_: Logger;
|
||||
|
||||
constructor(name: string, logger: Logger = null) {
|
||||
public constructor(name: string, logger: Logger = null) {
|
||||
this.name_ = name;
|
||||
this.logger_ = logger ? logger : new Logger();
|
||||
}
|
||||
|
||||
concurrency() {
|
||||
public concurrency() {
|
||||
return Setting.value('sync.maxConcurrentConnections');
|
||||
}
|
||||
|
||||
push(id: string, callback: Function) {
|
||||
public push(id: string, callback: Function) {
|
||||
if (this.stopping_) throw new Error('Cannot push task when queue is stopping');
|
||||
|
||||
this.waitingTasks_.push({
|
||||
@@ -42,7 +42,7 @@ export default class TaskQueue {
|
||||
this.processQueue_();
|
||||
}
|
||||
|
||||
processQueue_() {
|
||||
private processQueue_() {
|
||||
if (this.processingQueue_ || this.stopping_) return;
|
||||
|
||||
this.processingQueue_ = true;
|
||||
@@ -84,19 +84,19 @@ export default class TaskQueue {
|
||||
this.processingQueue_ = false;
|
||||
}
|
||||
|
||||
isWaiting(taskId: string) {
|
||||
public isWaiting(taskId: string) {
|
||||
return this.waitingTasks_.find(task => task.id === taskId);
|
||||
}
|
||||
|
||||
isProcessing(taskId: string) {
|
||||
public isProcessing(taskId: string) {
|
||||
return taskId in this.processingTasks_;
|
||||
}
|
||||
|
||||
isDone(taskId: string) {
|
||||
public isDone(taskId: string) {
|
||||
return taskId in this.results_;
|
||||
}
|
||||
|
||||
async waitForAll() {
|
||||
public async waitForAll() {
|
||||
return new Promise((resolve) => {
|
||||
const checkIID = setInterval(() => {
|
||||
if (this.waitingTasks_.length) return;
|
||||
@@ -107,16 +107,16 @@ export default class TaskQueue {
|
||||
});
|
||||
}
|
||||
|
||||
taskExists(taskId: string) {
|
||||
public taskExists(taskId: string) {
|
||||
return this.isWaiting(taskId) || this.isProcessing(taskId) || this.isDone(taskId);
|
||||
}
|
||||
|
||||
taskResult(taskId: string) {
|
||||
public taskResult(taskId: string) {
|
||||
if (!this.taskExists(taskId)) throw new Error(`No such task: ${taskId}`);
|
||||
return this.results_[taskId];
|
||||
}
|
||||
|
||||
async waitForResult(taskId: string) {
|
||||
public async waitForResult(taskId: string) {
|
||||
if (!this.taskExists(taskId)) throw new Error(`No such task: ${taskId}`);
|
||||
|
||||
while (true) {
|
||||
@@ -126,7 +126,7 @@ export default class TaskQueue {
|
||||
}
|
||||
}
|
||||
|
||||
async stop() {
|
||||
public async stop() {
|
||||
this.stopping_ = true;
|
||||
|
||||
this.logger_.info(`TaskQueue.stop: ${this.name_}: waiting for tasks to complete: ${Object.keys(this.processingTasks_).length}`);
|
||||
@@ -146,7 +146,7 @@ export default class TaskQueue {
|
||||
this.logger_.info(`TaskQueue.stop: ${this.name_}: Done, waited for ${Date.now() - startTime}`);
|
||||
}
|
||||
|
||||
isStopping() {
|
||||
public isStopping() {
|
||||
return this.stopping_;
|
||||
}
|
||||
}
|
||||
|
@@ -46,7 +46,7 @@ export default class DatabaseDriverBetterSqlite {
|
||||
return this.db_.prepare(sql).run(params ? params : []);
|
||||
}
|
||||
|
||||
lastInsertId() {
|
||||
public lastInsertId() {
|
||||
throw new Error('NOT IMPLEMENTED');
|
||||
}
|
||||
}
|
||||
|
@@ -35,30 +35,30 @@ export default class Database {
|
||||
this.driver_ = driver;
|
||||
}
|
||||
|
||||
setLogExcludedQueryTypes(v: string[]) {
|
||||
public setLogExcludedQueryTypes(v: string[]) {
|
||||
this.logExcludedQueryTypes_ = v;
|
||||
}
|
||||
|
||||
// Converts the SQLite error to a regular JS error
|
||||
// so that it prints a stacktrace when passed to
|
||||
// console.error()
|
||||
sqliteErrorToJsError(error: any, sql: string = null, params: SqlParams = null) {
|
||||
public sqliteErrorToJsError(error: any, sql: string = null, params: SqlParams = null) {
|
||||
return this.driver().sqliteErrorToJsError(error, sql, params);
|
||||
}
|
||||
|
||||
setLogger(l: Logger) {
|
||||
public setLogger(l: Logger) {
|
||||
this.logger_ = l;
|
||||
}
|
||||
|
||||
logger() {
|
||||
public logger() {
|
||||
return this.logger_;
|
||||
}
|
||||
|
||||
driver() {
|
||||
public driver() {
|
||||
return this.driver_;
|
||||
}
|
||||
|
||||
async open(options: any) {
|
||||
public async open(options: any) {
|
||||
try {
|
||||
await this.driver().open(options);
|
||||
} catch (error) {
|
||||
@@ -97,7 +97,7 @@ export default class Database {
|
||||
return output.join(',');
|
||||
}
|
||||
|
||||
async tryCall(callName: string, inputSql: StringOrSqlQuery, inputParams: SqlParams) {
|
||||
public async tryCall(callName: string, inputSql: StringOrSqlQuery, inputParams: SqlParams) {
|
||||
let sql: string = null;
|
||||
let params: SqlParams = null;
|
||||
|
||||
@@ -157,11 +157,11 @@ export default class Database {
|
||||
}
|
||||
}
|
||||
|
||||
async selectOne(sql: string, params: SqlParams = null): Promise<Row> {
|
||||
public async selectOne(sql: string, params: SqlParams = null): Promise<Row> {
|
||||
return this.tryCall('selectOne', sql, params);
|
||||
}
|
||||
|
||||
async loadExtension(/* path */) {
|
||||
public async loadExtension(/* path */) {
|
||||
return; // Disabled for now as fuzzy search extension is not in use
|
||||
|
||||
// let result = null;
|
||||
@@ -173,11 +173,11 @@ export default class Database {
|
||||
// }
|
||||
}
|
||||
|
||||
async selectAll(sql: string, params: SqlParams = null): Promise<Row[]> {
|
||||
public async selectAll(sql: string, params: SqlParams = null): Promise<Row[]> {
|
||||
return this.tryCall('selectAll', sql, params);
|
||||
}
|
||||
|
||||
async selectAllFields(sql: string, params: SqlParams, field: string): Promise<any[]> {
|
||||
public async selectAllFields(sql: string, params: SqlParams, field: string): Promise<any[]> {
|
||||
const rows = await this.tryCall('selectAll', sql, params);
|
||||
const output = [];
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
@@ -188,11 +188,11 @@ export default class Database {
|
||||
return output;
|
||||
}
|
||||
|
||||
async exec(sql: StringOrSqlQuery, params: SqlParams = null) {
|
||||
public async exec(sql: StringOrSqlQuery, params: SqlParams = null) {
|
||||
return this.tryCall('exec', sql, params);
|
||||
}
|
||||
|
||||
async transactionExecBatch(queries: StringOrSqlQuery[]) {
|
||||
public async transactionExecBatch(queries: StringOrSqlQuery[]) {
|
||||
if (queries.length <= 0) return;
|
||||
|
||||
if (queries.length === 1) {
|
||||
@@ -221,7 +221,7 @@ export default class Database {
|
||||
}
|
||||
}
|
||||
|
||||
static enumId(type: string, s: string) {
|
||||
public static enumId(type: string, s: string) {
|
||||
if (type === 'settings') {
|
||||
if (s === 'int') return 1;
|
||||
if (s === 'string') return 2;
|
||||
@@ -240,7 +240,7 @@ export default class Database {
|
||||
throw new Error(`Unknown enum type or value: ${type}, ${s}`);
|
||||
}
|
||||
|
||||
static enumName(type: string, id: number) {
|
||||
public static enumName(type: string, id: number) {
|
||||
if (type === 'fieldType') {
|
||||
if (id === Database.TYPE_UNKNOWN) return 'unknown';
|
||||
if (id === Database.TYPE_INT) return 'int';
|
||||
@@ -253,7 +253,7 @@ export default class Database {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
static formatValue(type: number, value: any) {
|
||||
public static formatValue(type: number, value: any) {
|
||||
if (value === null || value === undefined) return null;
|
||||
if (type === this.TYPE_INT) return Number(value);
|
||||
if (type === this.TYPE_TEXT) return value;
|
||||
@@ -261,7 +261,7 @@ export default class Database {
|
||||
throw new Error(`Unknown type: ${type}`);
|
||||
}
|
||||
|
||||
sqlStringToLines(sql: string) {
|
||||
public sqlStringToLines(sql: string) {
|
||||
const output = [];
|
||||
const lines = sql.split('\n');
|
||||
let statement = '';
|
||||
@@ -279,7 +279,7 @@ export default class Database {
|
||||
return output;
|
||||
}
|
||||
|
||||
logQuery(sql: string, params: SqlParams = null) {
|
||||
public logQuery(sql: string, params: SqlParams = null) {
|
||||
if (!this.sqlQueryLogEnabled_) return;
|
||||
|
||||
if (this.logExcludedQueryTypes_.length) {
|
||||
@@ -293,7 +293,7 @@ export default class Database {
|
||||
if (params !== null && params.length) this.logger().debug(JSON.stringify(params));
|
||||
}
|
||||
|
||||
static insertQuery(tableName: string, data: Record<string, any>) {
|
||||
public static insertQuery(tableName: string, data: Record<string, any>) {
|
||||
if (!data || !Object.keys(data).length) throw new Error('Data is empty');
|
||||
|
||||
let keySql = '';
|
||||
@@ -314,7 +314,7 @@ export default class Database {
|
||||
};
|
||||
}
|
||||
|
||||
static updateQuery(tableName: string, data: Record<string, any>, where: string | Record<string, any>) {
|
||||
public static updateQuery(tableName: string, data: Record<string, any>, where: string | Record<string, any>) {
|
||||
if (!data || !Object.keys(data).length) throw new Error('Data is empty');
|
||||
|
||||
let sql = '';
|
||||
@@ -343,7 +343,7 @@ export default class Database {
|
||||
};
|
||||
}
|
||||
|
||||
alterColumnQueries(tableName: string, fields: Record<string, string>) {
|
||||
public alterColumnQueries(tableName: string, fields: Record<string, string>) {
|
||||
const fieldsNoType = [];
|
||||
for (const n in fields) {
|
||||
if (!fields.hasOwnProperty(n)) continue;
|
||||
@@ -373,7 +373,7 @@ export default class Database {
|
||||
return sql.trim().split('\n');
|
||||
}
|
||||
|
||||
wrapQueries(queries: any[]) {
|
||||
public wrapQueries(queries: any[]) {
|
||||
const output = [];
|
||||
for (let i = 0; i < queries.length; i++) {
|
||||
output.push(this.wrapQuery(queries[i]));
|
||||
@@ -381,7 +381,7 @@ export default class Database {
|
||||
return output;
|
||||
}
|
||||
|
||||
wrapQuery(sql: any, params: SqlParams = null): SqlQuery {
|
||||
public wrapQuery(sql: any, params: SqlParams = null): SqlQuery {
|
||||
if (!sql) throw new Error(`Cannot wrap empty string: ${sql}`);
|
||||
|
||||
if (Array.isArray(sql)) {
|
||||
|
@@ -9,11 +9,11 @@ export class EventManager {
|
||||
private appStateWatchedProps_: string[];
|
||||
private appStateListeners_: any;
|
||||
|
||||
constructor() {
|
||||
public constructor() {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
reset() {
|
||||
public reset() {
|
||||
this.emitter_ = new events.EventEmitter();
|
||||
|
||||
this.appStatePrevious_ = {};
|
||||
@@ -21,27 +21,27 @@ export class EventManager {
|
||||
this.appStateListeners_ = {};
|
||||
}
|
||||
|
||||
on(eventName: string, callback: Function) {
|
||||
public on(eventName: string, callback: Function) {
|
||||
return this.emitter_.on(eventName, callback);
|
||||
}
|
||||
|
||||
emit(eventName: string, object: any = null) {
|
||||
public emit(eventName: string, object: any = null) {
|
||||
return this.emitter_.emit(eventName, object);
|
||||
}
|
||||
|
||||
removeListener(eventName: string, callback: Function) {
|
||||
public removeListener(eventName: string, callback: Function) {
|
||||
return this.emitter_.removeListener(eventName, callback);
|
||||
}
|
||||
|
||||
off(eventName: string, callback: Function) {
|
||||
public off(eventName: string, callback: Function) {
|
||||
return this.removeListener(eventName, callback);
|
||||
}
|
||||
|
||||
filterOn(filterName: string, callback: Function) {
|
||||
public filterOn(filterName: string, callback: Function) {
|
||||
return this.emitter_.on(`filter:${filterName}`, callback);
|
||||
}
|
||||
|
||||
filterOff(filterName: string, callback: Function) {
|
||||
public filterOff(filterName: string, callback: Function) {
|
||||
return this.removeListener(`filter:${filterName}`, callback);
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ export class EventManager {
|
||||
return output;
|
||||
}
|
||||
|
||||
appStateOn(propName: string, callback: Function) {
|
||||
public appStateOn(propName: string, callback: Function) {
|
||||
if (!this.appStateListeners_[propName]) {
|
||||
this.appStateListeners_[propName] = [];
|
||||
this.appStateWatchedProps_.push(propName);
|
||||
@@ -76,7 +76,7 @@ export class EventManager {
|
||||
this.appStateListeners_[propName].push(callback);
|
||||
}
|
||||
|
||||
appStateOff(propName: string, callback: Function) {
|
||||
public appStateOff(propName: string, callback: Function) {
|
||||
if (!this.appStateListeners_[propName]) {
|
||||
throw new Error('EventManager: Trying to unregister a state prop watch for a non-watched prop (1)');
|
||||
}
|
||||
@@ -87,7 +87,7 @@ export class EventManager {
|
||||
this.appStateListeners_[propName].splice(idx, 1);
|
||||
}
|
||||
|
||||
stateValue_(state: any, propName: string) {
|
||||
private stateValue_(state: any, propName: string) {
|
||||
const parts = propName.split('.');
|
||||
let s = state;
|
||||
for (const p of parts) {
|
||||
@@ -100,7 +100,7 @@ export class EventManager {
|
||||
// This function works by keeping a copy of the watched props and, whenever this function
|
||||
// is called, comparing the previous and new values and emitting events if they have changed.
|
||||
// The appStateEmit function should be called from a middleware.
|
||||
appStateEmit(state: any) {
|
||||
public appStateEmit(state: any) {
|
||||
if (!this.appStateWatchedProps_.length) return;
|
||||
|
||||
for (const propName of this.appStateWatchedProps_) {
|
||||
|
@@ -7,12 +7,12 @@ export default class FileApiDriverMemory {
|
||||
private items_: any[];
|
||||
private deletedItems_: any[];
|
||||
|
||||
constructor() {
|
||||
public constructor() {
|
||||
this.items_ = [];
|
||||
this.deletedItems_ = [];
|
||||
}
|
||||
|
||||
encodeContent_(content: any) {
|
||||
private encodeContent_(content: any) {
|
||||
if (content instanceof Buffer) {
|
||||
return content.toString('base64');
|
||||
} else {
|
||||
@@ -28,23 +28,23 @@ export default class FileApiDriverMemory {
|
||||
return true;
|
||||
}
|
||||
|
||||
decodeContent_(content: any) {
|
||||
private decodeContent_(content: any) {
|
||||
return Buffer.from(content, 'base64').toString('utf-8');
|
||||
}
|
||||
|
||||
itemIndexByPath(path: string) {
|
||||
public itemIndexByPath(path: string) {
|
||||
for (let i = 0; i < this.items_.length; i++) {
|
||||
if (this.items_[i].path === path) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
itemByPath(path: string) {
|
||||
public itemByPath(path: string) {
|
||||
const index = this.itemIndexByPath(path);
|
||||
return index < 0 ? null : this.items_[index];
|
||||
}
|
||||
|
||||
newItem(path: string, isDir = false) {
|
||||
public newItem(path: string, isDir = false) {
|
||||
const now = time.unixMs();
|
||||
return {
|
||||
path: path,
|
||||
@@ -55,18 +55,18 @@ export default class FileApiDriverMemory {
|
||||
};
|
||||
}
|
||||
|
||||
stat(path: string) {
|
||||
public stat(path: string) {
|
||||
const item = this.itemByPath(path);
|
||||
return Promise.resolve(item ? Object.assign({}, item) : null);
|
||||
}
|
||||
|
||||
async setTimestamp(path: string, timestampMs: number): Promise<any> {
|
||||
public async setTimestamp(path: string, timestampMs: number): Promise<any> {
|
||||
const item = this.itemByPath(path);
|
||||
if (!item) return Promise.reject(new Error(`File not found: ${path}`));
|
||||
item.updated_time = timestampMs;
|
||||
}
|
||||
|
||||
async list(path: string) {
|
||||
public async list(path: string) {
|
||||
const output = [];
|
||||
|
||||
for (let i = 0; i < this.items_.length; i++) {
|
||||
@@ -89,7 +89,7 @@ export default class FileApiDriverMemory {
|
||||
});
|
||||
}
|
||||
|
||||
async get(path: string, options: any) {
|
||||
public async get(path: string, options: any) {
|
||||
const item = this.itemByPath(path);
|
||||
if (!item) return Promise.resolve(null);
|
||||
if (item.isDir) return Promise.reject(new Error(`${path} is a directory, not a file`));
|
||||
@@ -105,13 +105,13 @@ export default class FileApiDriverMemory {
|
||||
return output;
|
||||
}
|
||||
|
||||
async mkdir(path: string) {
|
||||
public async mkdir(path: string) {
|
||||
const index = this.itemIndexByPath(path);
|
||||
if (index >= 0) return;
|
||||
this.items_.push(this.newItem(path, true));
|
||||
}
|
||||
|
||||
async put(path: string, content: any, options: any = null) {
|
||||
public async put(path: string, content: any, options: any = null) {
|
||||
if (!options) options = {};
|
||||
|
||||
if (options.source === 'file') content = await fs.readFile(options.path);
|
||||
@@ -152,7 +152,7 @@ export default class FileApiDriverMemory {
|
||||
return output;
|
||||
}
|
||||
|
||||
async delete(path: string) {
|
||||
public async delete(path: string) {
|
||||
const index = this.itemIndexByPath(path);
|
||||
if (index >= 0) {
|
||||
const item = Object.assign({}, this.items_[index]);
|
||||
@@ -163,18 +163,18 @@ export default class FileApiDriverMemory {
|
||||
}
|
||||
}
|
||||
|
||||
async move(oldPath: string, newPath: string): Promise<any> {
|
||||
public async move(oldPath: string, newPath: string): Promise<any> {
|
||||
const sourceItem = this.itemByPath(oldPath);
|
||||
if (!sourceItem) return Promise.reject(new Error(`Path not found: ${oldPath}`));
|
||||
await this.delete(newPath); // Overwrite if newPath already exists
|
||||
sourceItem.path = newPath;
|
||||
}
|
||||
|
||||
async format() {
|
||||
public async format() {
|
||||
this.items_ = [];
|
||||
}
|
||||
|
||||
async delta(path: string, options: any = null) {
|
||||
public async delta(path: string, options: any = null) {
|
||||
const getStatFn = async (path: string) => {
|
||||
const output = this.items_.slice();
|
||||
for (let i = 0; i < output.length; i++) {
|
||||
@@ -189,7 +189,7 @@ export default class FileApiDriverMemory {
|
||||
return output;
|
||||
}
|
||||
|
||||
async clearRoot() {
|
||||
public async clearRoot() {
|
||||
this.items_ = [];
|
||||
}
|
||||
}
|
||||
|
@@ -99,13 +99,13 @@ class FileApi {
|
||||
private remoteDateMutex_ = new Mutex();
|
||||
private initialized_ = false;
|
||||
|
||||
constructor(baseDir: string | Function, driver: any) {
|
||||
public constructor(baseDir: string | Function, driver: any) {
|
||||
this.baseDir_ = baseDir;
|
||||
this.driver_ = driver;
|
||||
this.driver_.fileApi_ = this;
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
public async initialize() {
|
||||
if (this.initialized_) return;
|
||||
this.initialized_ = true;
|
||||
if (this.driver_.initialize) return this.driver_.initialize(this.fullPath(''));
|
||||
@@ -135,7 +135,7 @@ class FileApi {
|
||||
return !!this.driver().supportsLocks;
|
||||
}
|
||||
|
||||
async fetchRemoteDateOffset_() {
|
||||
private async fetchRemoteDateOffset_() {
|
||||
const tempFile = `${this.tempDirName()}/timeCheck${Math.round(Math.random() * 1000000)}.txt`;
|
||||
const startTime = Date.now();
|
||||
await this.put(tempFile, 'timeCheck');
|
||||
@@ -161,7 +161,7 @@ class FileApi {
|
||||
|
||||
// Approximates the current time on the sync target. It caches the time offset to
|
||||
// improve performance.
|
||||
async remoteDate() {
|
||||
public async remoteDate() {
|
||||
const shouldSyncTime = () => {
|
||||
return !this.remoteDateNextCheckTime_ || Date.now() > this.remoteDateNextCheckTime_;
|
||||
};
|
||||
@@ -193,60 +193,60 @@ class FileApi {
|
||||
// Ideally all requests repeating should be done at the FileApi level to remove duplicate code in the drivers, but
|
||||
// historically some drivers (eg. OneDrive) are already handling request repeating, so this is optional, per driver,
|
||||
// and it defaults to no repeating.
|
||||
requestRepeatCount() {
|
||||
public requestRepeatCount() {
|
||||
if (this.requestRepeatCount_ !== null) return this.requestRepeatCount_;
|
||||
if (this.driver_.requestRepeatCount) return this.driver_.requestRepeatCount();
|
||||
return 0;
|
||||
}
|
||||
|
||||
lastRequests() {
|
||||
public lastRequests() {
|
||||
return this.driver_.lastRequests ? this.driver_.lastRequests() : [];
|
||||
}
|
||||
|
||||
clearLastRequests() {
|
||||
public clearLastRequests() {
|
||||
if (this.driver_.clearLastRequests) this.driver_.clearLastRequests();
|
||||
}
|
||||
|
||||
baseDir() {
|
||||
public baseDir() {
|
||||
return typeof this.baseDir_ === 'function' ? this.baseDir_() : this.baseDir_;
|
||||
}
|
||||
|
||||
tempDirName() {
|
||||
public tempDirName() {
|
||||
if (this.tempDirName_ === null) throw Error('Temp dir not set!');
|
||||
return this.tempDirName_;
|
||||
}
|
||||
|
||||
setTempDirName(v: string) {
|
||||
public setTempDirName(v: string) {
|
||||
this.tempDirName_ = v;
|
||||
}
|
||||
|
||||
fsDriver() {
|
||||
public fsDriver() {
|
||||
return shim.fsDriver();
|
||||
}
|
||||
|
||||
driver() {
|
||||
public driver() {
|
||||
return this.driver_;
|
||||
}
|
||||
|
||||
setSyncTargetId(v: number) {
|
||||
public setSyncTargetId(v: number) {
|
||||
this.syncTargetId_ = v;
|
||||
}
|
||||
|
||||
syncTargetId() {
|
||||
public syncTargetId() {
|
||||
if (this.syncTargetId_ === null) throw new Error('syncTargetId has not been set!!');
|
||||
return this.syncTargetId_;
|
||||
}
|
||||
|
||||
setLogger(l: Logger) {
|
||||
public setLogger(l: Logger) {
|
||||
if (!l) l = new Logger();
|
||||
this.logger_ = l;
|
||||
}
|
||||
|
||||
logger() {
|
||||
public logger() {
|
||||
return this.logger_;
|
||||
}
|
||||
|
||||
fullPath(path: string) {
|
||||
public fullPath(path: string) {
|
||||
const output = [];
|
||||
if (this.baseDir()) output.push(this.baseDir());
|
||||
if (path) output.push(path);
|
||||
@@ -286,18 +286,18 @@ class FileApi {
|
||||
}
|
||||
|
||||
// Deprectated
|
||||
setTimestamp(path: string, timestampMs: number) {
|
||||
public setTimestamp(path: string, timestampMs: number) {
|
||||
logger.debug(`setTimestamp ${this.fullPath(path)}`);
|
||||
return tryAndRepeat(() => this.driver_.setTimestamp(this.fullPath(path), timestampMs), this.requestRepeatCount());
|
||||
// return this.driver_.setTimestamp(this.fullPath(path), timestampMs);
|
||||
}
|
||||
|
||||
mkdir(path: string) {
|
||||
public mkdir(path: string) {
|
||||
logger.debug(`mkdir ${this.fullPath(path)}`);
|
||||
return tryAndRepeat(() => this.driver_.mkdir(this.fullPath(path)), this.requestRepeatCount());
|
||||
}
|
||||
|
||||
async stat(path: string) {
|
||||
public async stat(path: string) {
|
||||
logger.debug(`stat ${this.fullPath(path)}`);
|
||||
|
||||
const output = await tryAndRepeat(() => this.driver_.stat(this.fullPath(path)), this.requestRepeatCount());
|
||||
@@ -308,14 +308,14 @@ class FileApi {
|
||||
}
|
||||
|
||||
// Returns UTF-8 encoded string by default, or a Response if `options.target = 'file'`
|
||||
get(path: string, options: any = null) {
|
||||
public get(path: string, options: any = null) {
|
||||
if (!options) options = {};
|
||||
if (!options.encoding) options.encoding = 'utf8';
|
||||
logger.debug(`get ${this.fullPath(path)}`);
|
||||
return tryAndRepeat(() => this.driver_.get(this.fullPath(path), options), this.requestRepeatCount());
|
||||
}
|
||||
|
||||
async put(path: string, content: any, options: any = null) {
|
||||
public async put(path: string, content: any, options: any = null) {
|
||||
logger.debug(`put ${this.fullPath(path)}`, options);
|
||||
|
||||
if (options && options.source === 'file') {
|
||||
@@ -330,27 +330,27 @@ class FileApi {
|
||||
return tryAndRepeat(() => this.driver_.multiPut(items, options), this.requestRepeatCount());
|
||||
}
|
||||
|
||||
delete(path: string) {
|
||||
public delete(path: string) {
|
||||
logger.debug(`delete ${this.fullPath(path)}`);
|
||||
return tryAndRepeat(() => this.driver_.delete(this.fullPath(path)), this.requestRepeatCount());
|
||||
}
|
||||
|
||||
// Deprectated
|
||||
move(oldPath: string, newPath: string) {
|
||||
public move(oldPath: string, newPath: string) {
|
||||
logger.debug(`move ${this.fullPath(oldPath)} => ${this.fullPath(newPath)}`);
|
||||
return tryAndRepeat(() => this.driver_.move(this.fullPath(oldPath), this.fullPath(newPath)), this.requestRepeatCount());
|
||||
}
|
||||
|
||||
// Deprectated
|
||||
format() {
|
||||
public format() {
|
||||
return tryAndRepeat(() => this.driver_.format(), this.requestRepeatCount());
|
||||
}
|
||||
|
||||
clearRoot() {
|
||||
public clearRoot() {
|
||||
return tryAndRepeat(() => this.driver_.clearRoot(this.baseDir()), this.requestRepeatCount());
|
||||
}
|
||||
|
||||
delta(path: string, options: any = null) {
|
||||
public delta(path: string, options: any = null) {
|
||||
logger.debug(`delta ${this.fullPath(path)}`);
|
||||
return tryAndRepeat(() => this.driver_.delta(this.fullPath(path), options), this.requestRepeatCount());
|
||||
}
|
||||
|
@@ -58,7 +58,7 @@ const geoipServices: Record<string, GeoipService> = {
|
||||
};
|
||||
|
||||
export default class {
|
||||
static async currentPosition(options: CurrentPositionOptions = null) {
|
||||
public static async currentPosition(options: CurrentPositionOptions = null) {
|
||||
if (!options) options = {};
|
||||
|
||||
for (const [serviceName, handler] of Object.entries(geoipServices)) {
|
||||
|
@@ -9,6 +9,7 @@
|
||||
"%d MB per note or attachment": "%d MB pr. note eller vedhæftning",
|
||||
"%d minutes": "%d minutter",
|
||||
"%d notes match this pattern. Delete them?": "%d noter der matcher dette mønster. Slet dem?",
|
||||
"%s": "%s",
|
||||
"%s %s (%s, %s)": "%s %s (%s, %s)",
|
||||
"%s (%s) could not be uploaded: %s": "%s (%s) kunne ikke uploades: %s",
|
||||
"%s (%s) would like to share a notebook with you.": "%s (%s) vil gerne dele en notesbog med dig.",
|
||||
@@ -62,6 +63,7 @@
|
||||
"Admin dashboard": "Kontrolpanel til administration",
|
||||
"Advanced options": "Avancerede indstillinger",
|
||||
"Advanced tools": "Avancerede værktøjer",
|
||||
"All data, including notes, notebooks and tags will be permanently deleted.": "Alle data, herunder noter, notesbøger og etiketter, bliver slettet permanent.",
|
||||
"All notes": "Alle noter",
|
||||
"All potential ports are in use - please report the issue at %s": "Alle mulige porte er i brug - rapportér venligst problemet på %s",
|
||||
"Also displays unset and hidden config variables.": "Viser også tomme eller skjulte konfigurations variabler.",
|
||||
@@ -74,6 +76,7 @@
|
||||
"Application": "Program",
|
||||
"Apply": "Anvend",
|
||||
"Are you sure you want to renew the authorisation token?": "Er du sikker på, at du vil forny autorisationstoken?",
|
||||
"Are you sure you want to return to the default layout? The current layout configuration will be lost.": "Er du sikker på, at du vil vende tilbage til standardlayoutet? Den aktuelle layoutkonfiguration vil gå tabt.",
|
||||
"Arguments:": "Argumenter:",
|
||||
"Aritim Dark": "Aritim Mørk",
|
||||
"Attach": "Vedhæft",
|
||||
@@ -136,6 +139,7 @@
|
||||
"Click to add tags...": "Klik for at tilføje etiketter...",
|
||||
"Client ID: %s": "Klient-ID: %s",
|
||||
"Close": "Luk",
|
||||
"Close dropdown": "Luk rullelisten",
|
||||
"Close Window": "Luk vindue",
|
||||
"Code": "Kode",
|
||||
"Code Block": "Kode blok",
|
||||
@@ -161,6 +165,7 @@
|
||||
"Conflicts (attachments)": "Konflikter (vedhæftninger)",
|
||||
"Consolidated billing": "Konsolideret fakturering",
|
||||
"Content provided by %s": "Indhold leveret af %s",
|
||||
"Continue": "Fortsæt",
|
||||
"Convert to note": "Konverter til note",
|
||||
"Convert to todo": "Konverter til opgave",
|
||||
"Copy": "Kopier",
|
||||
@@ -178,8 +183,10 @@
|
||||
"Could not export notes: %s": "Kunne ikke eksportere noterne: %s",
|
||||
"Could not install plugin: %s": "Kunne ikke installere plugin: %s",
|
||||
"Could not respond to the invitation. Please try again, or check with the notebook owner if they are still sharing it.\n\nThe error was: \"%s\"": "Kunne ikke svare på invitationen. Prøv venligst igen, eller kontakt ejeren af notesbogen, hvis de stadig deler den\n\nFejlen var: \"%s\"",
|
||||
"Could not switch profile: %s": "Kunne ikke skifte profil: %s",
|
||||
"Could not upgrade master key: %s": "Kunne ikke opgradere hovednøgle: %s",
|
||||
"Could not verify the share status of this notebook - aborting. Please try again when you are connected to the internet.": "Kunne ikke verificere denne notesbogs status for deling - afbryder. Prøv venligst igen, når du har forbindelse til internettet.",
|
||||
"Could not verify your identify": "Kunne ikke bekræfte din identitet",
|
||||
"Create": "Opret",
|
||||
"Create a notebook": "Opret en notesbog",
|
||||
"Create new profile...": "Opret ny note...",
|
||||
@@ -228,9 +235,11 @@
|
||||
"Delete notebook \"%s\"?\n\nAll notes and sub-notebooks within this notebook will also be deleted.": "Slet notesbogen \"%s\"?\n\nAlle noter og undernotesbøger i denne notesbog bliver også slettet.",
|
||||
"Delete notebook? All notes and sub-notebooks within this notebook will also be deleted.": "Slet notesbog? Alle noter og undernotesbøger i denne notesbog bliver også slettet.",
|
||||
"Delete plugin \"%s\"?": "Slet udvidelse \"%s\"?",
|
||||
"Delete profile \"%s\"": "Slet profilen \"%s\"",
|
||||
"Delete selected notes": "Slet valgte noter",
|
||||
"Delete these %d notes?": "Slet disse %d noter?",
|
||||
"Delete this invitation? The recipient will no longer have access to this shared notebook.": "Vil du slette denne invitation? Modtageren vil ikke længere have adgang til denne delte notesbog.",
|
||||
"Delete this profile?": "Slet denne profil?",
|
||||
"Deleted local items: %d.": "Slettede lokale emner: %d.",
|
||||
"Deleted remote items: %d.": "Slettede fjern-emner: %d.",
|
||||
"Deletes the given notebook.": "Sletter aktuelle notesbog.",
|
||||
@@ -281,6 +290,7 @@
|
||||
"Edit link": "Rediger link",
|
||||
"Edit note.": "Rediger note.",
|
||||
"Edit notebook": "Rediger notesbog",
|
||||
"Edit profile": "Rediger profil",
|
||||
"Edit profile configuration...": "Rediger profilkonfiguration...",
|
||||
"Editor": "Editor",
|
||||
"Editor font": "Tekstredigeringsskrifttype",
|
||||
@@ -300,6 +310,7 @@
|
||||
"Enable ^sup^ syntax": "Aktivér ^sup^ syntaks",
|
||||
"Enable abbreviation syntax": "Aktivér forkortelsessyntaks",
|
||||
"Enable audio player": "Aktivér lydafspiller",
|
||||
"Enable biometrics authentication?": "Aktivér biometrisk autentificering?",
|
||||
"Enable deflist syntax": "Aktivér deflist syntaks",
|
||||
"Enable encryption": "Aktivér kryptering",
|
||||
"Enable footnotes": "Aktivér fodnoter",
|
||||
@@ -312,6 +323,7 @@
|
||||
"Enable note history": "Slå note-historie til",
|
||||
"Enable PDF viewer": "Aktivér PDF-viser",
|
||||
"Enable soft breaks": "Slå bløde ombrydninger til",
|
||||
"Enable spellcheck in the text editor": "Aktiver stavekontrol i teksteditoren",
|
||||
"Enable table of contents extension": "Aktivér indholdsfortegnelsesudvidelsen",
|
||||
"Enable typographer support": "Slå typografunderstøttelse til",
|
||||
"Enable video player": "Aktivér videoafspiller",
|
||||
@@ -448,6 +460,7 @@
|
||||
"Items": "Emner",
|
||||
"Items that cannot be decrypted": "Emner der ikke kan dekrypteres",
|
||||
"Items that cannot be synchronised": "Emner kan ikke synkroniseres",
|
||||
"Join us on Twitter": "Følg os på Twitter",
|
||||
"Joplin can synchronise your notes using various providers. Select one from the list below.": "Joplin kan synkronisere dine noter ved hjælp af forskellige tjenesteudbydere. Vælg en fra listen nedenfor.",
|
||||
"Joplin Cloud": "Joplin Cloud",
|
||||
"Joplin Cloud email": "Joplin Cloud e-mail",
|
||||
@@ -502,6 +515,7 @@
|
||||
"Manage master password": "Administrer hovedadgangskode",
|
||||
"Manage master password...": "Administrer hovedadgangskode...",
|
||||
"Manage multiple users": "Administrer flere brugere",
|
||||
"Manage profiles": "Administrer profiler",
|
||||
"Manage your plugins": "Administrer dine udvidelser",
|
||||
"Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, `status`, `decrypt-file`, and `target-status`.": "Administrer E2EE-konfiguration. Kommandoer er `enable`(aktiver), `disable`(sluk), `decrypt`(dekrypter), `status`, `decrypt-file`(dekrypter fil) og `target-status` (modtager-status).",
|
||||
"Manual": "Manuel",
|
||||
@@ -520,6 +534,7 @@
|
||||
"Missing keys": "Manglende nøgler",
|
||||
"Missing Master Keys": "Manglende hovednøgler",
|
||||
"Missing required argument: %s": "Mangler krævet argument: %s",
|
||||
"Missing required flag value: %s": "Mangler krævet flag-værdi: %s",
|
||||
"Mobile data - auto-sync disabled": "Mobildata - auto-synk deaktiveret",
|
||||
"More info": "Mere information",
|
||||
"More information": "Mere information",
|
||||
@@ -560,6 +575,7 @@
|
||||
"Not authentified with %s. Please provide any missing credentials.": "Ikke godkendt med %s. Angiv venligst eventuelle manglende legitimationsoplysninger.",
|
||||
"Not downloaded": "Ikke downloadet",
|
||||
"Not generated": "Ikke oprettet",
|
||||
"Not now": "Ikke nu",
|
||||
"note": "note",
|
||||
"Note": "Note",
|
||||
"Note area growth factor": "Noteområde-vækstfaktor",
|
||||
@@ -615,6 +631,7 @@
|
||||
"Password:": "Adgangskode:",
|
||||
"Passwords do not match!": "Adgangskoder stemmer ikke overens!",
|
||||
"Paste": "Indsæt",
|
||||
"Paste as text": "Indsæt som tekst",
|
||||
"Path:": "Sti:",
|
||||
"PDF File": "PDF-fil",
|
||||
"Per user. Minimum of %d users.": "Pr. bruger. Minimum %d brugere.",
|
||||
@@ -656,8 +673,10 @@
|
||||
"Process oversized accounts": "Behandl for store konti",
|
||||
"Process user deletions": "Behandl brugersletninger",
|
||||
"Profile": "Profil",
|
||||
"Profile name": "Profilnavn",
|
||||
"Profile name:": "Profilnavn:",
|
||||
"Profile Version: %s": "Profil-version: %s",
|
||||
"Profiles": "Profiler",
|
||||
"Properties": "Egenskaber",
|
||||
"Proxy enabled": "Proxy aktiveret",
|
||||
"Proxy timeout (seconds)": "Proxy timeout (sekunder)",
|
||||
@@ -692,6 +711,7 @@
|
||||
"Replace all": "Erstat alt",
|
||||
"Replace with...": "Erstat med...",
|
||||
"Replace: ": "Erstat: ",
|
||||
"Reset application layout": "Nulstil applikationslayout",
|
||||
"Reset master password": "Nulstil hovedadgangskode",
|
||||
"Resources: %d.": "Ressourcer: %d.",
|
||||
"Restart and upgrade": "Genstart og upgradér",
|
||||
@@ -792,6 +812,7 @@
|
||||
"Step 2: Install the extension": "Trin 2: Installér udvidelsen",
|
||||
"Stop": "Stop",
|
||||
"Stop external editing": "Stop ekstern redigering",
|
||||
"Storage space": "Lagerplads",
|
||||
"Strikethrough": "Gennemstregning",
|
||||
"strong text": "fed (strong) tekst",
|
||||
"Submit": "Gem",
|
||||
@@ -835,6 +856,7 @@
|
||||
"Teams": "Teams",
|
||||
"Text editor command": "Tekstredigeringskomando",
|
||||
"Thank you! Your Joplin Cloud account is now setup and ready to use.": "Mange tak! Din Joplin Cloud-konto er nu indstillet og klar til brug.",
|
||||
"The active profile cannot be deleted. Switch to a different profile and try again.": "Den aktive profil kan ikke slettes. Skift til en anden profil, og prøv igen.",
|
||||
"The app is now going to close. Please relaunch it to complete the process.": "Appen lukker nu. Genstart den for at fuldføre processen.",
|
||||
"The application did not close properly. Would you like to start in safe mode?": "Programmet blev ikke lukket korrekt. Ønsker du at starte i fejlsikret tilstand?",
|
||||
"The application has been authorised - you may now close this browser tab.": "App'en er blevet godkendt - du kan nu lukke dette faneblad.",
|
||||
@@ -846,6 +868,7 @@
|
||||
"The default admin password is insecure and has not been changed! [Change it now](%s)": "Standard-administratoradgangskoden er svag og er ikke blevet ændret! [Ændr det nu](%s)",
|
||||
"The default encryption method has been changed to a more secure one and it is recommended that you apply it to your data.": "Standard krypteringsmetoden er blevet ændret til en mere sikker metode og det anbefales, at du anvender den på dine data.",
|
||||
"The default encryption method has been changed, you should re-encrypt your data.": "Standard-krypteringsmetoden er blevet ændret, du bør genkryptere dine data.",
|
||||
"The default profile cannot be deleted": "Standardprofilen kan ikke slettes",
|
||||
"The editor command (may include arguments) that will be used to open a note. If none is provided it will try to auto-detect the default editor.": "Editorkommandoen (kan inkludere argumenter) der bruges til at åbne noter. Hvis der ikke er angivet nogen, vil den prøve at autodetektere standardeditoren.",
|
||||
"The factor property sets how the item will grow or shrink to fit the available space in its container with respect to the other items. Thus an item with a factor of 2 will take twice as much space as an item with a factor of 1.Restart app to see changes.": "Faktoren vil bestemme om elementet vil vokse eller skrumpe for at tilpasse den tilgængelig plads i dens beholder med hensyn til andre elementer. Således vil et element med en faktor 2 optage dobbelt så meget plads som et element med en faktor 1. Genstart app'en for at se ændringer.",
|
||||
"The following attachments are being watched for changes:": "Følgende vedhæftninger overvåges for ændringer:",
|
||||
@@ -867,8 +890,10 @@
|
||||
"The Web Clipper needs your authorisation to access your data.": "Web Clipper har brug for din tilladelse til at få adgang til dine data.",
|
||||
"The web clipper service is enabled and set to auto-start.": "Web-clipper tjenesten er slået til og sat til at starte automatisk.",
|
||||
"The web clipper service is not enabled.": "Web-Clipper tjenesten er ikke slået til.",
|
||||
"The WebDAV implementation of %s is incompatible with Joplin, and as such is no longer supported. Please use a different sync method.": "WebDAV-implementeringen af %s er ikke kompatibel med Joplin og understøttes derfor ikke længere. Brug venligst en anden synkroniseringsmetode.",
|
||||
"Theme": "Tema",
|
||||
"There are currently no notes. Create one by clicking on the (+) button.": "Der er ingen noter. Opret note ved at klikke på (+) knappen.",
|
||||
"There are unsaved changes.": "Der er ikke-gemte ændringer.",
|
||||
"There is currently no notebook. Create one by clicking on \"New notebook\".": "Der er ingen notesbog. Opret en ved at klikke på \"Ny Notesbog\".",
|
||||
"There is no data to export.": "Der er ingen data at eksportere.",
|
||||
"There was a [conflict](%s) on the attachment below.\n\n%s": "Der var en [conflict](%s) på vedhæftningen nedenfor.\n\n%s",
|
||||
@@ -902,6 +927,7 @@
|
||||
"To maximise/minimise the console, press \"tc\".": "For at maksimere/minimere konsollen, tryk \"tc\".",
|
||||
"To move from one pane to another, press Tab or Shift+Tab.": "For flytte fra et panel til et andet, tryk Tab eller Skift+Tab.",
|
||||
"To retry decryption of these items. Run `e2ee decrypt --retry-failed-items`": "For at prøve at dekryptere dem igen. Kør `e2ee decrypt --retry-failed-items`",
|
||||
"To switch the profile, the app is going to close and you will need to restart it.": "Hvis du vil skifte profil, lukkes appen, og du skal genstarte den.",
|
||||
"To work correctly, the app needs the following permissions. Please enable them in your phone settings, in Apps > Joplin > Permissions": "For at virke korrekt skal app'en bruge følgende tilladelser. Slå dem til i din telefons indstillinger, i Apps > Joplin > Tilladelser",
|
||||
"to-do": "opgave",
|
||||
"to-do: %s": "opgave: %s",
|
||||
@@ -952,16 +978,19 @@
|
||||
"Upgrade the sync target to the latest version.": "Opgrader synkmålet til den seneste version.",
|
||||
"URL": "URL",
|
||||
"Usage: %s": "Forbrug: %s",
|
||||
"Use biometrics to secure access to the app": "Brug biometri til at sikre adgangen til appen",
|
||||
"Use long list format. Format is ID, NOTE_COUNT (for notebook), DATE, TODO_CHECKED (for to-dos), TITLE": "Brug langt liste format. Formatet er ID,NOTE_TÆLLER (for notesbøger), DATO, MARKEREDE_OPGAVER (for opgaver), TITEL",
|
||||
"Use spell checker": "Brug stavekontrol",
|
||||
"Use the arrows and page up/down to scroll the lists and text areas (including this console).": "Brug pilene og PageUp/PageDown for at rulle lister og tekst-områder (inkl. denne konsol)",
|
||||
"Use the arrows to move the layout items. Press \"Escape\" to exit.": "Brug pilene for at flytte layout-elementerne. Tryk på \"Escape\" for at afslutte.",
|
||||
"Use this to rebuild the search index if there is a problem with search. It may take a long time depending on the number of notes.": "Brug denne funktion til at genopbygge søgeindekset, hvis der er problemer med søgning. Det kan tage lang tid afhængig af antallet af noter.",
|
||||
"Use your biometrics to secure access to your application. You can always set it up later in Settings.": "Brug dine biometriske data til at sikre adgangen til din applikation. Du kan altid konfigurere det senere i Indstillinger.",
|
||||
"Used for most text in the markdown editor. If not found, a generic proportional (variable width) font is used.": "Bruges til det meste tekst i markdown-editoren. Hvis ikke fundet, bruges en proportional (variabel bredde) font.",
|
||||
"Used where a fixed width font is needed to lay out text legibly (e.g. tables, checkboxes, code). If not found, a generic monospace (fixed width) font is used.": "Bruges hvor en font med fast bredde er nødvendig for at vise læsbar tekst (f.eks. tabeller, afkrydsningsfelter, kode). Hvis ikke fundet, bruges en generisk monospatieret (fast bredde) font.",
|
||||
"User deletions": "Brugersletninger",
|
||||
"Users": "Brugere",
|
||||
"Valid": "Gyldig",
|
||||
"Verify your identity": "Bekræft din identitet",
|
||||
"View": "Vis",
|
||||
"View on map": "Vis på kort",
|
||||
"View them now": "Se dem nu",
|
||||
|
@@ -62,6 +62,7 @@
|
||||
"Admin dashboard": "Admin-Übersichtsseite",
|
||||
"Advanced options": "Erweiterte Optionen",
|
||||
"Advanced tools": "Erweiterte Optionen",
|
||||
"All data, including notes, notebooks and tags will be permanently deleted.": "Alle Daten, inklusive Notizen, Notizbücher und Schlagwörter werden permanent gelöscht.",
|
||||
"All notes": "Alle Notizen",
|
||||
"All potential ports are in use - please report the issue at %s": "Alle potentielle Port-Nummern sind in Verwendung - bitte melde dieses Problem auf %s",
|
||||
"Also displays unset and hidden config variables.": "Zeigt auch nicht gesetzte und versteckte Konfigurationsvariablen an.",
|
||||
@@ -136,6 +137,7 @@
|
||||
"Click to add tags...": "Zum Hinzufügen von Schlagwörtern klicken ...",
|
||||
"Client ID: %s": "Client-ID: %s",
|
||||
"Close": "Schließen",
|
||||
"Close dropdown": "Auswahlliste schließen",
|
||||
"Close Window": "Fenster schließen",
|
||||
"Code": "Code",
|
||||
"Code Block": "Code-Block",
|
||||
@@ -161,6 +163,7 @@
|
||||
"Conflicts (attachments)": "Konflikte (Anhänge)",
|
||||
"Consolidated billing": "Konsolidierte Rechnung",
|
||||
"Content provided by %s": "Von %s bereitgestellte Inhalte",
|
||||
"Continue": "Fortfahren",
|
||||
"Convert to note": "In eine Notiz umwandeln",
|
||||
"Convert to todo": "In eine Aufgabe umwandeln",
|
||||
"Copy": "Kopieren",
|
||||
@@ -178,8 +181,10 @@
|
||||
"Could not export notes: %s": "Konnte Notizen nicht exportieren: %s",
|
||||
"Could not install plugin: %s": "Erweiterung konnte nicht installiert werden: %s",
|
||||
"Could not respond to the invitation. Please try again, or check with the notebook owner if they are still sharing it.\n\nThe error was: \"%s\"": "Die Einladung konnte nicht beantwortet werden. Bitte versuche es erneut, oder prüfe, ob der Notizbuch-Besitzer dieses noch freigibt.\n\nDer Fehler war: „%s“",
|
||||
"Could not switch profile: %s": "Konnte das Profil nicht wechseln: %s",
|
||||
"Could not upgrade master key: %s": "Konnte Hauptschlüssel nicht aktualisieren: %s",
|
||||
"Could not verify the share status of this notebook - aborting. Please try again when you are connected to the internet.": "Der Freigabestatus dieses Notizbuchs konnte nicht überprüft werden - Vorgang wird abgebrochen. Bitte versuche es erneut, wenn eine Internetverbindung besteht.",
|
||||
"Could not verify your identify": "Konnte deine Identität nicht verifizieren.",
|
||||
"Create": "Erstellen",
|
||||
"Create a notebook": "Notizbuch erstellen",
|
||||
"Create new profile...": "Neues Profil erstellen...",
|
||||
@@ -228,9 +233,11 @@
|
||||
"Delete notebook \"%s\"?\n\nAll notes and sub-notebooks within this notebook will also be deleted.": "Notizbuch „%s“ löschen?\n\nAlle Notizen und Unter-Notizbücher in diesem Notizbuch werden ebenfalls gelöscht.",
|
||||
"Delete notebook? All notes and sub-notebooks within this notebook will also be deleted.": "Notizbuch löschen? Alle Notizen und Unter-Notizbücher in diesem Notizbuch werden ebenfalls gelöscht.",
|
||||
"Delete plugin \"%s\"?": "Erweiterung „%s“ löschen?",
|
||||
"Delete profile \"%s\"": "Lösche Profil \"%s\"",
|
||||
"Delete selected notes": "Ausgewählte Notizen löschen",
|
||||
"Delete these %d notes?": "Sollen diese %d Notizen gelöscht werden?",
|
||||
"Delete this invitation? The recipient will no longer have access to this shared notebook.": "Einladung löschen? Der Empfänger hat dann keinen Zugriff mehr auf dieses freigegebene Notizbuch.",
|
||||
"Delete this profile?": "Dieses Profil löschen?",
|
||||
"Deleted local items: %d.": "Lokale Elemente gelöscht: %d.",
|
||||
"Deleted remote items: %d.": "Remote-Elemente gelöscht: %d.",
|
||||
"Deletes the given notebook.": "Löscht das ausgewählte Notizbuch.",
|
||||
@@ -281,6 +288,7 @@
|
||||
"Edit link": "Link bearbeiten",
|
||||
"Edit note.": "Notiz bearbeiten.",
|
||||
"Edit notebook": "Notizbuch bearbeiten",
|
||||
"Edit profile": "Profil bearbeiten",
|
||||
"Edit profile configuration...": "Profilkonfiguration bearbeiten...",
|
||||
"Editor": "Editor",
|
||||
"Editor font": "Schriftart im Editor",
|
||||
@@ -300,6 +308,7 @@
|
||||
"Enable ^sup^ syntax": "Syntax ^sup^ aktivieren",
|
||||
"Enable abbreviation syntax": "Abkürzungssyntax aktivieren",
|
||||
"Enable audio player": "Audiospieler aktivieren",
|
||||
"Enable biometrics authentication?": "Biometrische Authentifizierung aktivieren?",
|
||||
"Enable deflist syntax": "Syntax deflist aktivieren",
|
||||
"Enable encryption": "Verschlüsselung aktivieren",
|
||||
"Enable footnotes": "Fußnoten aktivieren",
|
||||
@@ -502,6 +511,7 @@
|
||||
"Manage master password": "Master-Passwort verwalten",
|
||||
"Manage master password...": "Master-Passwort verwalten...",
|
||||
"Manage multiple users": "Verwalte mehrere Nutzer",
|
||||
"Manage profiles": "Profile verwalten",
|
||||
"Manage your plugins": "Erweiterungen verwalten",
|
||||
"Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, `status`, `decrypt-file`, and `target-status`.": "Verwaltet die E2EE-Konfiguration. Die Befehle lauten `enable`, `disable`, `decrypt`, `status`, `decrypt-file` und `target-status`.",
|
||||
"Manual": "Manuell",
|
||||
@@ -560,6 +570,7 @@
|
||||
"Not authentified with %s. Please provide any missing credentials.": "Keine Authentifizierung mit %s. Gib bitte alle fehlenden Zugangsdaten an.",
|
||||
"Not downloaded": "Nicht heruntergeladen",
|
||||
"Not generated": "Nicht erzeugt",
|
||||
"Not now": "Nicht jetzt",
|
||||
"note": "Notiz",
|
||||
"Note": "Notiz",
|
||||
"Note area growth factor": "Notiz-Flächenwachstumsfaktor",
|
||||
@@ -656,8 +667,10 @@
|
||||
"Process oversized accounts": "Verarbeite zu große Konten",
|
||||
"Process user deletions": "Verarbeite Benutzerlöschungen",
|
||||
"Profile": "Profil",
|
||||
"Profile name": "Profilname",
|
||||
"Profile name:": "Profilname:",
|
||||
"Profile Version: %s": "Profil-Version: %s",
|
||||
"Profiles": "Profile",
|
||||
"Properties": "Eigenschaften",
|
||||
"Proxy enabled": "Proxy aktiviert",
|
||||
"Proxy timeout (seconds)": "Proxy Zeitüberschreitung (Sekunden)",
|
||||
@@ -836,6 +849,7 @@
|
||||
"Teams": "Teams",
|
||||
"Text editor command": "Texteditor-Befehl",
|
||||
"Thank you! Your Joplin Cloud account is now setup and ready to use.": "Vielen Dank! Das Joplin Cloud-Konto ist nun eingerichtet und einsatzbereit.",
|
||||
"The active profile cannot be deleted. Switch to a different profile and try again.": "Das aktive Profil kann nicht gelöscht werden. Bitte wechsle in ein anderes Profil und versuche es erneut.",
|
||||
"The app is now going to close. Please relaunch it to complete the process.": "Die App wird nun geschlossen. Bitte starte sie erneut, um den Vorgang abzuschließen.",
|
||||
"The application did not close properly. Would you like to start in safe mode?": "Die Anwendung wurde nicht ordnungsgemäß geschlossen. Im abgesicherten Modus starten?",
|
||||
"The application has been authorised - you may now close this browser tab.": "Die Anwendung wurde autorisiert - du kannst diesen Browsertab nun schließen.",
|
||||
@@ -847,6 +861,7 @@
|
||||
"The default admin password is insecure and has not been changed! [Change it now](%s)": "Das Standard-Administratorpasswort ist unsicher und wurde nicht geändert! [Jetzt ändern](%s)",
|
||||
"The default encryption method has been changed to a more secure one and it is recommended that you apply it to your data.": "Die Standardverschlüsselungsmethode wurde in eine sicherere geändert und es wird empfohlen, sie auf deine Daten anzuwenden.",
|
||||
"The default encryption method has been changed, you should re-encrypt your data.": "Die Standardverschlüsselungsmethode wurde geändert. Du solltest deine Daten erneut verschlüsseln.",
|
||||
"The default profile cannot be deleted": "Das Standardprofil kann nicht gelöscht werden.",
|
||||
"The editor command (may include arguments) that will be used to open a note. If none is provided it will try to auto-detect the default editor.": "Der Editor-Befehl (kann Kommandozeilenargumente enthalten), der zum Öffnen einer Notiz verwendet wird. Wenn keiner angegeben wird, wird versucht, den Standard-Editor automatisch zu erkennen.",
|
||||
"The factor property sets how the item will grow or shrink to fit the available space in its container with respect to the other items. Thus an item with a factor of 2 will take twice as much space as an item with a factor of 1.Restart app to see changes.": "Die Faktor-Eigenschaft legt fest, wie der Artikel wächst oder schrumpft, um dem verfügbaren Platz in seinem Container in Bezug auf die anderen Artikel zu entsprechen. Ein Element mit dem Faktor 2 benötigt also doppelt so viel Platz wie ein Element mit dem Faktor 1. Starte die App neu, um Änderungen zu sehen.",
|
||||
"The following attachments are being watched for changes:": "Die folgenden Anhänge werden auf Änderungen überwacht:",
|
||||
@@ -870,6 +885,7 @@
|
||||
"The web clipper service is not enabled.": "Der Web-Clipper-Dienst ist nicht aktiviert.",
|
||||
"Theme": "Design",
|
||||
"There are currently no notes. Create one by clicking on the (+) button.": "Momentan existieren noch keine Notizen. Erstelle eine, indem du auf den (+)-Knopf drückst.",
|
||||
"There are unsaved changes.": "Es gibt ungespeicherte Änderungen.",
|
||||
"There is currently no notebook. Create one by clicking on \"New notebook\".": "Momentan existieren noch keine Notizbücher. Erstelle eines, indem du auf „Neues Notizbuch“ klickst.",
|
||||
"There is no data to export.": "Keine Daten für den Export vorhanden.",
|
||||
"There was a [conflict](%s) on the attachment below.\n\n%s": "Es gab einen [Konflikt](%s) beim Anhang unten.\n\n%s",
|
||||
@@ -903,6 +919,7 @@
|
||||
"To maximise/minimise the console, press \"tc\".": "Um die Konsole zu maximieren/minimieren, drücke „tc“.",
|
||||
"To move from one pane to another, press Tab or Shift+Tab.": "Um von einem Bereich zu einem anderen zu wechseln, drücke Tab oder Umschalt+Tab.",
|
||||
"To retry decryption of these items. Run `e2ee decrypt --retry-failed-items`": "Um die Entschlüsselung dieser Objekte nochmal zu versuchen, führe `e2ee decrypt --retry-failed-items` aus",
|
||||
"To switch the profile, the app is going to close and you will need to restart it.": "Um das Profil zu wechseln, wird die App beendet und muss von dir neu gestartet werden.",
|
||||
"To work correctly, the app needs the following permissions. Please enable them in your phone settings, in Apps > Joplin > Permissions": "Um korrekt arbeiten zu können, benötigt die App die folgenden Berechtigungen. Bitte aktiviere diese in deinen Telefoneinstellungen, unter Apps > Joplin > Berechtigungen",
|
||||
"to-do": "Aufgabe",
|
||||
"to-do: %s": "To-Do: %s",
|
||||
@@ -953,16 +970,19 @@
|
||||
"Upgrade the sync target to the latest version.": "Aktualisiere das Synchronisationsziel auf die neueste Version.",
|
||||
"URL": "URL",
|
||||
"Usage: %s": "Nutzung: %s",
|
||||
"Use biometrics to secure access to the app": "Verwende Biometrie, um den Zugriff zu der App abzusichern.",
|
||||
"Use long list format. Format is ID, NOTE_COUNT (for notebook), DATE, TODO_CHECKED (for to-dos), TITLE": "Verwende ausführliches Listenformat. Das Format ist ID, NOTIZEN_ANZAHL (für Notizbuch), DATUM, AUFGABE_BEARBEITET (für Aufgaben), TITEL",
|
||||
"Use spell checker": "Rechtschreibprüfung verwenden",
|
||||
"Use the arrows and page up/down to scroll the lists and text areas (including this console).": "Benutze die Pfeiltasten und Bild auf/ab, um durch Listen und Texte zu scrollen (inklusive dieser Konsole).",
|
||||
"Use the arrows to move the layout items. Press \"Escape\" to exit.": "Verwende die Pfeile, um die Layoutelemente zu verschieben. Drücke zum Beenden „Escape“.",
|
||||
"Use this to rebuild the search index if there is a problem with search. It may take a long time depending on the number of notes.": "Verwende dies, um den Suchindex neu aufzubauen, wenn es ein Problem mit der Suche gibt. Dies kann je nach Anzahl der Notizen eine lange Zeit dauern.",
|
||||
"Use your biometrics to secure access to your application. You can always set it up later in Settings.": "Verwende Biometrie, um den Zugriff zu deiner Anwendung abzusichern. Du kannst sie jederzeit in den Einstellungen einrichten.",
|
||||
"Used for most text in the markdown editor. If not found, a generic proportional (variable width) font is used.": "Hauptsächlich für Text im Markdown-Editor verwendet. Falls nicht vorhanden wird eine generische Proportional-Schriftart (mit variabler Breite) verwendet.",
|
||||
"Used where a fixed width font is needed to lay out text legibly (e.g. tables, checkboxes, code). If not found, a generic monospace (fixed width) font is used.": "Verwendete Schriftart mit fester Breite, um Text lesbar zu machen (z.B. in Tabellen, Kontrollkästchen, Code). Falls nicht vorhanden wird eine Monotype-Scrhiftart (mit fester Breite) verwendet.",
|
||||
"User deletions": "Benutzer-Löschungen",
|
||||
"Users": "Benutzer",
|
||||
"Valid": "Gültig",
|
||||
"Verify your identity": "Verifiziere deine Identität",
|
||||
"View": "Ansicht",
|
||||
"View on map": "Auf der Karte anzeigen",
|
||||
"View them now": "Zeige sie jetzt an",
|
||||
|
@@ -1,8 +1,12 @@
|
||||
{
|
||||
"\"%s\" is missing the required \"%s\" property.": "A «%s» le falta la propiedad requerida de «%s».",
|
||||
"%d days": "%d días",
|
||||
"%d GB": "%d GB",
|
||||
"%d GB storage space": "%d GB de almacenamiento",
|
||||
"%d hour": "%d hora",
|
||||
"%d hours": "%d horas",
|
||||
"%d MB": "%d MB",
|
||||
"%d MB per note or attachment": "%d MB por nota o adjunto",
|
||||
"%d minutes": "%d minutos",
|
||||
"%d notes match this pattern. Delete them?": "%d notas coinciden con el patrón. ¿Eliminarlas?",
|
||||
"%s %s (%s, %s)": "%s %s (%s, %s)",
|
||||
@@ -27,6 +31,7 @@
|
||||
"&Tools": "&Herramientas",
|
||||
"&View": "&Ver",
|
||||
"(%s)": "(%s)",
|
||||
"(In plugin: %s)": "(En plugin: %s)",
|
||||
"(None)": "(Ninguno)",
|
||||
"(wysiwyg: %s)": "(wysiwyg: %s)",
|
||||
"- Camera: to allow taking a picture and attaching it to a note.": "- Cámara: Permite tomar fotografías y adjuntarlas a una nota.",
|
||||
@@ -42,11 +47,13 @@
|
||||
"Accelerator \"%s\" is not valid.": "El acelerador «%s» no es válido.",
|
||||
"Accelerator \"%s\" is used for \"%s\" and \"%s\" commands. This may lead to unexpected behaviour.": "El acelerador «%s» se usa para los comandos «%s» y «%s». Esto puede llevar a un comportamiento inesperado.",
|
||||
"Accept": "Aceptar",
|
||||
"Account": "Cuenta",
|
||||
"Action": "Acción",
|
||||
"Actions": "Acciones",
|
||||
"Active": "Activa",
|
||||
"Actual Size": "Tamaño Original",
|
||||
"Add body": "Añadir cuerpo",
|
||||
"Add new": "Añadir nuevo",
|
||||
"Add or remove tags:": "Agregar o eliminar etiquetas:",
|
||||
"Add recipient:": "Agregar destinatario:",
|
||||
"Add title": "Añadir título",
|
||||
@@ -55,10 +62,14 @@
|
||||
"Admin dashboard": "Tablero de Mandos de Administrador",
|
||||
"Advanced options": "Opciones avanzadas",
|
||||
"Advanced tools": "Herramientas avanzadas",
|
||||
"All data, including notes, notebooks and tags will be permanently deleted.": "Todos los datos, incluyendo notas, libretas y etiquetas, serán borrados.",
|
||||
"All notes": "Todas las notas",
|
||||
"All potential ports are in use - please report the issue at %s": "Todos los puertos potenciales están en uso - por favor informe del problema en %s",
|
||||
"Also displays unset and hidden config variables.": "También muestra variables no configuradas u ocultas.",
|
||||
"Also publish linked notes": "También publicar notas enlazadas",
|
||||
"Always": "Siempre",
|
||||
"Ambiguous notebook \"%s\". Please use notebook id instead - press \"ti\" to see the short notebook id or use $b for current selected notebook": "Libreta \"%s\" es ambigua. Utilice el id de la libreta - presione \"ti\" para ver el id abreviado o utilice $b para la libreta seleccionada",
|
||||
"Ambiguous notebook \"%s\". Please use short notebook id instead - press \"ti\" to see the short notebook id": "Libreta \"%s\" es ambigua. Utilice el id abreviado - presione \"ti\" para ver el id abreviado de la libreta",
|
||||
"An update is available, do you want to download it now?": "Está disponible una actualización. ¿Quiere descargarla ahora?",
|
||||
"Appearance": "Apariencia",
|
||||
"Application": "Aplicación",
|
||||
@@ -66,6 +77,7 @@
|
||||
"Are you sure you want to renew the authorisation token?": "¿Está seguro de que desea renovar el token de autorización?",
|
||||
"Arguments:": "Argumentos:",
|
||||
"Aritim Dark": "Aritim Oscuro",
|
||||
"Attach": "Adjuntar",
|
||||
"Attach file": "Adjuntar archivo",
|
||||
"Attach photo": "Adjuntar foto",
|
||||
"Attach...": "Adjuntar...",
|
||||
@@ -84,10 +96,12 @@
|
||||
"Automatically check for updates": "Comprobar actualizaciones",
|
||||
"Automatically switch theme to match system theme": "Cambiar automáticamente el tema para que coincida con el tema del sistema",
|
||||
"Back": "Retroceder",
|
||||
"Basic": "Básico",
|
||||
"Bold": "Negrita",
|
||||
"Browse all plugins": "Explorar todos los plugins",
|
||||
"Browse...": "Explorar...",
|
||||
"Bulleted List": "Lista con Viñetas",
|
||||
"Can Share": "Puede compartir",
|
||||
"Cancel": "Cancelar",
|
||||
"Cancelling background synchronisation... Please wait.": "Cancelando sincronización en segundo plano... Por favor espere.",
|
||||
"Cancelling...": "Cancelando...",
|
||||
@@ -105,6 +119,7 @@
|
||||
"Cannot save %s \"%s\" because it is larger than the allowed limit (%s)": "No se puede guardar %s \"%s\" porque es más pesado que el límite permitido (%s)",
|
||||
"Cannot save %s \"%s\" because it would go over the total allowed size (%s) for this account": "No se puede guardar %s \"%s\" porque es más pesado que el límite permitido (%s)",
|
||||
"Cannot share encrypted notebook with recipient %s because they have not enabled end-to-end encryption. They may do so from the screen Configuration > Encryption.": "No se puede compartir la libreta cifrada con el destinatario %s porque este no ha habilitado el cifrado de extremo a extremo. Puede hacerlo desde la pantalla Configuración > Cifrado.",
|
||||
"Case sensitive": "Distingue mayúsculas",
|
||||
"Change application layout": "Cambiar el diseño de la aplicación",
|
||||
"Change language": "Cambiar idioma",
|
||||
"Characters": "Caracteres",
|
||||
@@ -122,11 +137,13 @@
|
||||
"Click to add tags...": "Haga clic aquí para agregar etiquetas...",
|
||||
"Client ID: %s": "ID del Cliente: %s",
|
||||
"Close": "Cerrar",
|
||||
"Close dropdown": "Cerrar menú",
|
||||
"Close Window": "Cerrar Ventana",
|
||||
"Code": "Código",
|
||||
"Code Block": "Bloque de Código",
|
||||
"Code View": "Código",
|
||||
"Collaborate on notebooks with others": "Colaborar en los cuadernos con otros",
|
||||
"Collapse": "Colapsar",
|
||||
"Coming alarms": "Alarmas próximas",
|
||||
"Comma-separated list of paths to directories to load the certificates from, or path to individual cert files. For example: /my/cert_dir, /other/custom.pem. Note that if you make changes to the TLS settings, you must save your changes before clicking on \"Check synchronisation configuration\".": "Lista de rutas de directorios separados por comas de dónde cargar los certificados, o ruta a certificados individuales. Por ejemplo: /mi/cert_dir, /otro/personalizado.pem. Tenga en cuenta que si realiza cambios en la configuración de los certificados TLS, debe guardar los cambios antes de hacer clic en «Comprobar la configuración de sincronización».",
|
||||
"command": "comando",
|
||||
@@ -144,7 +161,9 @@
|
||||
"Conflicted: %d": "Conflictos: %d",
|
||||
"Conflicts": "Conflictos",
|
||||
"Conflicts (attachments)": "Conflictos (adjuntos)",
|
||||
"Consolidated billing": "Facturación consolidada",
|
||||
"Content provided by %s": "Contenido provisto por %s",
|
||||
"Continue": "Continuar",
|
||||
"Convert to note": "Convertir en nota",
|
||||
"Convert to todo": "Convertir a tarea",
|
||||
"Copy": "Copiar",
|
||||
@@ -162,9 +181,13 @@
|
||||
"Could not export notes: %s": "No se pudo exportar las notas: %s",
|
||||
"Could not install plugin: %s": "No se pudo instalar el plugin: %s",
|
||||
"Could not respond to the invitation. Please try again, or check with the notebook owner if they are still sharing it.\n\nThe error was: \"%s\"": "No se ha podido responder a la invitación. Por favor, inténtelo de nuevo, o compruebe con el propietario de la libreta si todavía la está compartiendo.\n\nEl error fue: \"%s\"",
|
||||
"Could not switch profile: %s": "No se pudo cambiar el perfil: %s",
|
||||
"Could not upgrade master key: %s": "No se pudo actualizar la clave maestra: %s",
|
||||
"Could not verify the share status of this notebook - aborting. Please try again when you are connected to the internet.": "No se ha podido verificar el estado de compartición de esta libreta - abortando. Por favor, inténtelo de nuevo cuando esté conectado a Internet.",
|
||||
"Could not verify your identify": "No se pudo verificar su identidad",
|
||||
"Create": "Crear",
|
||||
"Create a notebook": "Crea una libreta",
|
||||
"Create new profile...": "Crear nuevo perfil...",
|
||||
"Create notebook": "Crea libreta",
|
||||
"Create user": "Crear usuario",
|
||||
"Created": "Creada",
|
||||
@@ -193,6 +216,7 @@
|
||||
"Date": "Fecha",
|
||||
"Date format": "Formato de fecha",
|
||||
"days": "días",
|
||||
"Decrease indent level": "Reducir un nivel de sangría",
|
||||
"Decrypted items: %d": "Elementos descifrados: %d",
|
||||
"Decrypted items: %s / %s": "Elementos descifrados: %s / %s",
|
||||
"Decrypting items: %d/%d": "Descifrando elementos: %d/%d",
|
||||
@@ -209,8 +233,11 @@
|
||||
"Delete notebook \"%s\"?\n\nAll notes and sub-notebooks within this notebook will also be deleted.": "¿Borrar libreta «%s»?\n\nTodas las notas y sublibretas de esta libreta serán borradas.",
|
||||
"Delete notebook? All notes and sub-notebooks within this notebook will also be deleted.": "¿Borrar libreta? Todas las notas y sublibretas dentro de esta libreta también serán eliminadas.",
|
||||
"Delete plugin \"%s\"?": "¿Borrar plugin «%s»?",
|
||||
"Delete profile \"%s\"": "¿Borrar perfil \"%s\"?",
|
||||
"Delete selected notes": "Borrar notas seleccionadas",
|
||||
"Delete these %d notes?": "¿Borrar estas %d notas?",
|
||||
"Delete this invitation? The recipient will no longer have access to this shared notebook.": "¿Eliminar esta invitación? El destinatario ya no tendrá acceso a esta libreta compartida.",
|
||||
"Delete this profile?": "¿Borrar este perfil?",
|
||||
"Deleted local items: %d.": "Elementos locales borrados: %d.",
|
||||
"Deleted remote items: %d.": "Elementos remotos borrados: %d.",
|
||||
"Deletes the given notebook.": "Elimina la libreta dada.",
|
||||
@@ -240,6 +267,7 @@
|
||||
"Do it now": "Hacerlo ahora",
|
||||
"Do not ask for confirmation.": "No pedir confirmación.",
|
||||
"Do not lose the password as, for security purposes, this will be the *only* way to decrypt the data! To enable encryption, please enter your password below.": "No pierda la contraseña, pues, por cuestiones de seguridad, ¡es la *única* forma de descifrar los datos! Para habilitar el cifrado, por favor introduzca su contraseña a continuación.",
|
||||
"Done": "Listo",
|
||||
"Download": "Descargar",
|
||||
"Download and install the relevant extension for your browser:": "Descargue e instale la extensión apropiada para su navegador:",
|
||||
"Downloaded": "Descargado",
|
||||
@@ -253,17 +281,22 @@
|
||||
"Dropbox Login": "Inicio de sesión de Dropbox",
|
||||
"Duplicate": "Duplicar",
|
||||
"Duplicate line": "Duplicar línea",
|
||||
"Duplicate selected notes": "Duplicar notas seleccionadas",
|
||||
"Duplicates the notes matching <note> to [notebook]. If no notebook is specified the note is duplicated in the current notebook.": "Duplica las notas que coincidan con <nota> a [libreta]. Si no se especifica una libreta la nota se duplica en la libreta actual.",
|
||||
"Edit": "Editar",
|
||||
"Edit in external editor": "Editar con un editor externo",
|
||||
"Edit link": "Editar enlace",
|
||||
"Edit note.": "Editar nota.",
|
||||
"Edit notebook": "Editar libreta",
|
||||
"Edit profile": "Editar perfil",
|
||||
"Edit profile configuration...": "Editar configuración de perfil...",
|
||||
"Editor": "Editor",
|
||||
"Editor font": "Fuente del editor",
|
||||
"Editor font family": "Familia de fuente del editor",
|
||||
"Editor font size": "Tamaño de fuente del editor",
|
||||
"Editor maximum width": "Ancho máximo del editor",
|
||||
"Editor monospace font family": "Familia de fuente monoespaciada del editor",
|
||||
"Editor: %s": "Editor: %s",
|
||||
"Either \"text\" or \"json\"": "Puede ser «text» o «json»",
|
||||
"Emacs": "Emacs",
|
||||
"Email": "Email",
|
||||
@@ -275,6 +308,7 @@
|
||||
"Enable ^sup^ syntax": "Activar sintaxis ^sup^",
|
||||
"Enable abbreviation syntax": "Activar sintaxis de abreviaturas",
|
||||
"Enable audio player": "Activar reproductor de audio",
|
||||
"Enable biometrics authentication?": "¿Activar autenticación biométrica?",
|
||||
"Enable deflist syntax": "Activar sintaxis Deflist",
|
||||
"Enable encryption": "Habilitar cifrado",
|
||||
"Enable footnotes": "Activar notas al pie",
|
||||
@@ -315,6 +349,7 @@
|
||||
"Evernote Export File (as HTML)": "Exportar como Archivo de Ever(como HTML)",
|
||||
"Evernote Export File (as Markdown)": "Exportar como Archivo de Ever(como Markdown)",
|
||||
"Exits the application.": "Sale de la aplicación.",
|
||||
"Expand": "Expandir",
|
||||
"Export": "Exportar",
|
||||
"Export all": "Exportar todo",
|
||||
"Export debug report": "Exportar Informe de depuración",
|
||||
@@ -333,6 +368,9 @@
|
||||
"Fetching resources: %d/%d": "Obteniendo recursos: %d/%d",
|
||||
"File": "Archivo",
|
||||
"File system": "Sistema de archivos",
|
||||
"Filter tags": "Filtrar etiquetas",
|
||||
"Find and replace": "Buscar y reemplazar",
|
||||
"Find: ": "Buscar: ",
|
||||
"Firefox Extension": "Extensión de Firefox",
|
||||
"Fix search index": "Reparar índice de búsqueda",
|
||||
"Fixing search index...": "Reparando índice de búsqueda...",
|
||||
@@ -341,14 +379,17 @@
|
||||
"Focus title": "Enfocar título",
|
||||
"Folders": "Carpetas",
|
||||
"For debugging purpose only: export your profile to an external SD card.": "Solo para fines de depuración: exporte su perfil a una tarjeta SD externa.",
|
||||
"For example \"%s\"": "Por ejemplo \"%s\"",
|
||||
"For information on how to customise the shortcuts please visit %s": "Para información de cómo personalizar los atajos, por favor visite %s",
|
||||
"For more information about End-To-End Encryption (E2EE) and advice on how to enable it please check the documentation:": "Para más información acerca del Cifrado de Extremo a Extremo (E2EE) y recomendaciones acerca de como habilitarlo por favor revise la documentación:",
|
||||
"For the list of keyboard shortcuts and config options, type `help keymap`": "Para la lista de los atajos de teclado disponibles, escriba `help keymap`",
|
||||
"Force path style": "Forzar estilo de ruta",
|
||||
"Formatting": "Formatear",
|
||||
"Forward": "Avanzar",
|
||||
"Found: %d.": "Encontrado: %d.",
|
||||
"FTS enabled: %d": "FTS activado: %d",
|
||||
"Full changelog": "Registro de cambios completo",
|
||||
"Full name": "Nombre completo",
|
||||
"General": "General",
|
||||
"Generated": "Generado",
|
||||
"Generating link...": "Creando enlace...",
|
||||
@@ -358,11 +399,17 @@
|
||||
"Go to source URL": "Ir a URL de origen",
|
||||
"Goto Anything...": "Ir a...",
|
||||
"Grant authorisation": "Conceder la autorización",
|
||||
"Header %d": "Encabezado %d",
|
||||
"Headers": "Encabezados",
|
||||
"Heading": "Título",
|
||||
"Help": "Ayuda",
|
||||
"Hide %s": "Ocultar %s",
|
||||
"Hide advanced": "Ocultar avanzadas",
|
||||
"Hide disabled": "Ocultar desactivadas",
|
||||
"Hide disabled keys": "Ocultar las claves desactivadas",
|
||||
"Hide Joplin": "Ocultar Joplin",
|
||||
"Hide keyboard": "Ocultar teclado",
|
||||
"Hide more actions": "Ocultar más acciones",
|
||||
"Highlight": "Resaltado",
|
||||
"Home": "Inicio",
|
||||
"Horizontal Rule": "Regla Horizontal",
|
||||
@@ -387,12 +434,14 @@
|
||||
"In order to use the web clipper, you need to do the following:": "Para utilizar el Web Clipper, necesita hacer lo siguiente:",
|
||||
"In progress": "En progreso",
|
||||
"In: %s": "En: %s",
|
||||
"Increase indent level": "Aumentar un nivel de sangría",
|
||||
"Indent less": "Disminuir sangría",
|
||||
"Indent more": "Aumentar sangría",
|
||||
"Information": "Información",
|
||||
"Inline Code": "Código Integrado",
|
||||
"Insert": "Insertar",
|
||||
"Insert Hyperlink": "Insertar Hipervínculo",
|
||||
"Insert time": "Insertar hora",
|
||||
"Install": "Instalar",
|
||||
"Install from file": "Instalar desde archivo",
|
||||
"Installed": "Instalado",
|
||||
@@ -423,6 +472,7 @@
|
||||
"Joplin Web Clipper allows saving web pages and screenshots from your browser to Joplin.": "El Web Clipper de Joplin le permite guardar páginas web y capturas de pantalla desde su navegador a la aplicación.",
|
||||
"Joplin website": "Sitio web de Joplin",
|
||||
"Joplin's own sync service. Also gives access to Joplin-specific features such as publishing notes or collaborating on notebooks with others.": "El servicio de sincronización propio de Joplin. También da acceso a funciones específicas de Joplin, como la publicación de notas o la colaboración en cuadernos con otras personas.",
|
||||
"KaTeX": "KaTeX",
|
||||
"Keep note history for": "Mantener historial de la nota durante",
|
||||
"Keyboard Mode": "Modo de Teclado",
|
||||
"Keyboard Shortcut": "Atajo de Teclado",
|
||||
@@ -440,9 +490,13 @@
|
||||
"Letter": "Carta",
|
||||
"Light": "Claro",
|
||||
"Lines": "Líneas",
|
||||
"Link": "Enlace",
|
||||
"Link description": "Descripción de enlace",
|
||||
"Link has been copied to clipboard!": "¡El enlace ha sido copiado al portapapeles!",
|
||||
"Link text": "Texto de enlace",
|
||||
"Links with protocol \"%s\" are not supported": "Enlaces con el protocolo «%s» no están soportados",
|
||||
"List item": "Listar elemento",
|
||||
"Lists": "Listas",
|
||||
"Loaded": "Cargado",
|
||||
"Location": "Localización",
|
||||
"Lock file is already being hold. If you know that no synchronisation is taking place, you may delete the lock file at \"%s\" and resume the operation.": "Ya hay un archivo de bloqueo. Si está seguro de que no hay ninguna sincronización en curso, puede eliminar el archivo de bloqueo en «%s» y reanudar la operación.",
|
||||
@@ -456,6 +510,8 @@
|
||||
"Make a donation": "Hacer una donación",
|
||||
"Manage master password": "Administrar contraseña maestra",
|
||||
"Manage master password...": "Administrar contraseña maestra...",
|
||||
"Manage multiple users": "Administrar múltiples usuarios",
|
||||
"Manage profiles": "Administrar perfiles",
|
||||
"Manage your plugins": "Administre sus plugins",
|
||||
"Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, `status`, `decrypt-file`, and `target-status`.": "Administra la configuración E2EE. Los comandos son `enable`, `disable`, `decrypt`, `status`, `decrypt-file` y `target-status`.",
|
||||
"Manual": "Manual",
|
||||
@@ -468,6 +524,9 @@
|
||||
"Master password": "Contraseña maestra",
|
||||
"Master password:": "Contraseña maestra:",
|
||||
"Max concurrent connections": "Conexiones simultáneas máximas",
|
||||
"Max Item Size": "Tamaño máximo de item",
|
||||
"Max note or attachment size": "Tamaño máximo de notas o adjuntos",
|
||||
"Max Total Size": "Tamaño original",
|
||||
"Missing keys": "Claves faltantes",
|
||||
"Missing Master Keys": "Claves Maestras Faltantes",
|
||||
"Missing required argument: %s": "Falta un argumento requerido: %s",
|
||||
@@ -479,6 +538,7 @@
|
||||
"Move to notebook": "Mover a la libreta",
|
||||
"Move to notebook...": "Mover a la libreta...",
|
||||
"Move to notebook:": "Mover a la libreta:",
|
||||
"Moves the given <item> to [notebook]": "Mueve la nota <nota> a [libreta]",
|
||||
"n": "n",
|
||||
"N": "N",
|
||||
"New note": "Nueva nota",
|
||||
@@ -489,6 +549,7 @@
|
||||
"New tags:": "Nuevas etiquetas:",
|
||||
"New to-do": "Nueva tarea",
|
||||
"New version: %s": "Nueva versión: %s",
|
||||
"Next match": "Siguiente coincidencia",
|
||||
"Nextcloud": "Nextcloud",
|
||||
"Nextcloud password": "Contraseña de Nextcloud",
|
||||
"Nextcloud username": "Usuario de Nextcloud",
|
||||
@@ -509,6 +570,7 @@
|
||||
"Not authentified with %s. Please provide any missing credentials.": "No autenticado con %s. Por favor provea las credenciales faltantes.",
|
||||
"Not downloaded": "No descargado",
|
||||
"Not generated": "No generado",
|
||||
"Not now": "Ahora no",
|
||||
"note": "nota",
|
||||
"Note": "Nota",
|
||||
"Note area growth factor": "Factor de crecimiento del área de la nota",
|
||||
@@ -516,6 +578,7 @@
|
||||
"Note attachments...": "Adjuntos de las notas...",
|
||||
"Note body": "Cuerpo de la nota",
|
||||
"Note does not exist: \"%s\". Create it?": "La nota no existe: «%s». ¿Crearla?",
|
||||
"Note editor": "Editor de notas",
|
||||
"Note has been saved.": "La nota ha sido guardada.",
|
||||
"Note History": "Historial",
|
||||
"Note is not a to-do: \"%s\"": "La nota no es una tarea: «%s»",
|
||||
@@ -546,12 +609,14 @@
|
||||
"Only one note can be printed at a time.": "Solo una nota puede ser impresa a la vez.",
|
||||
"Open": "Abrir",
|
||||
"Open %s": "Abrir %s",
|
||||
"Open PDF viewer": "Abrir visor de PDF",
|
||||
"Open profile directory": "Abrir directorio de perfiles",
|
||||
"Open Sync Wizard...": "Abrir el asistente de sincronización...",
|
||||
"Open...": "Abrir...",
|
||||
"Operation cancelled": "Operación cancelada",
|
||||
"Options": "Opciones",
|
||||
"Or create an account.": "Crea una nueva nota.O crea una cuenta.",
|
||||
"Ordered list": "Lista ordenada",
|
||||
"Other applications...": "Otras aplicaciones...",
|
||||
"Output format: %s": "Formato de destino: %s",
|
||||
"Page orientation for PDF export": "Orientación de la página al exportar como PDF",
|
||||
@@ -563,8 +628,10 @@
|
||||
"Paste": "Pegar",
|
||||
"Path:": "Ruta:",
|
||||
"PDF File": "Archivo PDF",
|
||||
"Per user. Minimum of %d users.": "Por usuario. Mínimo %d usuarios.",
|
||||
"Permission needed": "Se necesita permiso",
|
||||
"Permission to use camera": "Permiso para usar la cámara",
|
||||
"Please click on \"%s\" to proceed, or set the passwords in the \"%s\" list below.": "Haga click en \"%s\" para proceder, o indique las contraseñas en la lista \"%s\" debajo.",
|
||||
"Please confirm that you would like to re-encrypt your complete database.": "Por favor, confirme que desea volver a cifrar su base de datos completa.",
|
||||
"Please enter your password in the master key list below before upgrading the key.": "Por favor, ingrese su contraseña en la lista de claves maestras a continuación antes de actualizar la clave.",
|
||||
"Please note that if it is a large notebook, it may take a few minutes for all the notes to show up on the recipient's device.": "Tenga en cuenta de que si se trata de una libreta grande, puede tardar unos minutos para que todas las notas aparezcan en el dispositivo del destinatario.",
|
||||
@@ -590,15 +657,24 @@
|
||||
"Press the shortcut": "Pulse el atajo",
|
||||
"Press the shortcut and then press ENTER. Or, press BACKSPACE to clear the shortcut.": "Pulse el atajo; y luego, ENTER. O bien, pulse RETROCESO para borrar el atajo.",
|
||||
"Press to set the decryption password.": "Pulse para establecer la contraseña de descifrado.",
|
||||
"Previous match": "Anterior coincidencia",
|
||||
"Previous versions of this note": "Versiones anteriores de esta nota",
|
||||
"Print": "Imprimir",
|
||||
"Priority support": "Soporte prioritario",
|
||||
"Privacy Policy": "Política de Privacidad",
|
||||
"Pro": "Pro",
|
||||
"Process failed payment subscriptions": "Procesar las suscripciones de pago fallidas",
|
||||
"Process oversized accounts": "Procesar cuentas de gran tamaño",
|
||||
"Process user deletions": "Procesar eliminaciones de usuarios",
|
||||
"Profile": "Perfil",
|
||||
"Profile name": "Nombre de perfil",
|
||||
"Profile name:": "Nombre de perfil:",
|
||||
"Profile Version: %s": "Versión del Perfil: %s",
|
||||
"Profiles": "Perfiles",
|
||||
"Properties": "Propiedades",
|
||||
"Proxy enabled": "Proxy habilitado",
|
||||
"Proxy timeout (seconds)": "Proxy timeout (segundos)",
|
||||
"Proxy URL": "URL de Proxy",
|
||||
"Public-private key pair:": "Par de claves públicas y privadas:",
|
||||
"Publish note...": "Publicar nota...",
|
||||
"Publish Notes": "Publicar notas",
|
||||
@@ -615,6 +691,7 @@
|
||||
"Recipients:": "Destinatarios:",
|
||||
"Redo": "Rehacer",
|
||||
"Refresh": "Recargar",
|
||||
"Regular expression": "Expresión regular",
|
||||
"Reject": "Rechazar",
|
||||
"Remove": "Eliminar",
|
||||
"Remove tag \"%s\" from all notes?": "¿Desea eliminar la etiqueta «%s» de todas las notas?",
|
||||
@@ -624,6 +701,10 @@
|
||||
"Rename tag:": "Renombrar etiqueta:",
|
||||
"Renames the given <item> (note or notebook) to <name>.": "Renombra el <elemento> dado (nota o libreta) a <nombre>.",
|
||||
"Renew token": "Renovar token",
|
||||
"Replace": "Reemplazar",
|
||||
"Replace all": "Reemplazar todo",
|
||||
"Replace with...": "Reemplazar con...",
|
||||
"Replace: ": "Reemplazar: ",
|
||||
"Reset master password": "Restablecer contraseña maestra",
|
||||
"Resources: %d.": "Recursos: %d.",
|
||||
"Restart and upgrade": "Reiniciar y actualizar",
|
||||
@@ -646,11 +727,13 @@
|
||||
"Safe mode is currently active. Note rendering and all plugins are temporarily disabled.": "El modo seguro está actualmente activo. El renderizado de notas y todos los plugins están temporalmente deshabilitados.",
|
||||
"Save": "Guardar",
|
||||
"Save alarm": "Establecer alarma",
|
||||
"Save as %s": "Guardar como %s",
|
||||
"Save as...": "Guardar como...",
|
||||
"Save changes": "Guardar cambios",
|
||||
"Save geo-location with notes": "Guardar geolocalización en las notas",
|
||||
"Search": "Buscar",
|
||||
"Search for plugins...": "Buscar plugins...",
|
||||
"Search for...": "Buscar...",
|
||||
"Search in all the notes": "Buscar en todas las notas",
|
||||
"Search in current note": "Buscar en la nota actual",
|
||||
"Search...": "Buscar...",
|
||||
@@ -670,17 +753,23 @@
|
||||
"Set the password": "Establecer la contraseña",
|
||||
"Sets the property <name> of the given <note> to the given [value]. Possible properties are:\n\n%s": "Asigna la propiedad <nombre> de la <nota> dada al [valor] dado. Propiedades posibles son:\n\n%s",
|
||||
"Share": "Compartir",
|
||||
"Share and collaborate on a notebook": "Compartir y colaborar en una libreta",
|
||||
"Share Notebook": "Compartir Libreta",
|
||||
"Share notebook...": "Compartir libreta...",
|
||||
"Sharing access control": "Compartiendo control de acceso",
|
||||
"Sharing notebook...": "Compartiendo libreta...",
|
||||
"Shortcuts are not available in CLI mode.": "Los atajos no están disponibles en el modo CLI.",
|
||||
"Show advanced": "Mostrar opciones avanzadas",
|
||||
"Show Advanced Settings": "Mostrar Opciones Avanzadas",
|
||||
"Show all": "Mostrar todo",
|
||||
"Show completed to-dos": "Mostrar tareas completadas",
|
||||
"Show disabled": "Mostrar opciones desactivadas",
|
||||
"Show disabled keys": "Mostrar claves desactivadas",
|
||||
"Show more actions": "Mostrar más acciones",
|
||||
"Show note counts": "Mostrar número de notas",
|
||||
"Show sort order buttons": "Mostrar botones de ordenación",
|
||||
"Show tray icon": "Mostrar icono en la bandeja",
|
||||
"Show/hide the sidebar": "Mostrar/ocultar barra lateral",
|
||||
"Sidebar": "Barra lateral",
|
||||
"Size": "Tamaño",
|
||||
"Skip this version": "Omitir esta versión",
|
||||
@@ -716,6 +805,7 @@
|
||||
"Step 2: Install the extension": "Paso 2: Instale la extensión",
|
||||
"Stop": "Detener",
|
||||
"Stop external editing": "Detener la edición externa",
|
||||
"Storage space": "Espacio de almacenamiento",
|
||||
"Strikethrough": "Tachado",
|
||||
"strong text": "texto en negrita",
|
||||
"Submit": "Enviar",
|
||||
@@ -725,9 +815,12 @@
|
||||
"Swap line down": "Mover línea hacia abajo",
|
||||
"Swap line up": "Mover línea hacia arriba",
|
||||
"Switch between note and to-do type": "Alternar entre nota y tarea",
|
||||
"Switch profile": "Cambiar perfil",
|
||||
"Switch to note type": "Cambiar a nota",
|
||||
"Switch to profile %d": "Cambiar al perfil %d",
|
||||
"Switch to to-do type": "Cambiar a tarea",
|
||||
"Switches to [notebook] - all further operations will happen within this notebook.": "Cambia a [libreta] - todas las demás operaciones se realizarán en esta libreta.",
|
||||
"Sync as many devices as you want": "Sincronice todos los dispositivos que desee",
|
||||
"Sync Status": "Estado de la sincronización",
|
||||
"Sync status (synced items / total items)": "Estado de la sincronización (elementos sincronizados / elementos totales)",
|
||||
"Sync target must be upgraded! Run `%s` to proceed.": "¡El objetivo de sincronización debe ser actualizado! Ejecute `%s` para proceder.",
|
||||
@@ -747,13 +840,18 @@
|
||||
"Synchronising...": "Sincronizando...",
|
||||
"Synchronizing...": "Sincronizando...",
|
||||
"Tabloid": "Tabloide",
|
||||
"tag1, tag2, ...": "etiqueta1, etiqueta2, ...",
|
||||
"Tagged: %d.": "Etiquetado: %d.",
|
||||
"Tags": "Etiquetas",
|
||||
"Take photo": "Tomar una foto",
|
||||
"Task list": "Lista de tareas",
|
||||
"Tasks": "Tareas",
|
||||
"Teams": "Equipos",
|
||||
"Text editor command": "Comando de editor de texto",
|
||||
"Thank you! Your Joplin Cloud account is now setup and ready to use.": "Gracias. Su cuenta de Joplin Cloud ya está configurada y lista para ser utilizada.",
|
||||
"The active profile cannot be deleted. Switch to a different profile and try again.": "El perfil activo no puede eliminarse. Cambie de perfil e intente de nuevo.",
|
||||
"The app is now going to close. Please relaunch it to complete the process.": "La aplicación se va a cerrar. Por favor, vuelva a iniciarla para completar el proceso.",
|
||||
"The application did not close properly. Would you like to start in safe mode?": "La aplicación no cerró correctamente. ¿Quiere iniciarla en modo seguro?",
|
||||
"The application has been authorised - you may now close this browser tab.": "La aplicación ha sido autorizada - ahora puede cerrar la pestaña de su navegador.",
|
||||
"The application has been authorised!": "¡La aplicación ha sido autorizada!",
|
||||
"The application has been successfully authorised.": "La aplicación ha sido autorizada exitosamente.",
|
||||
@@ -763,6 +861,7 @@
|
||||
"The default admin password is insecure and has not been changed! [Change it now](%s)": "La contraseña de administrador por defecto es insegura y no ha sido cambiada! [Cambiarla ahora](%s)",
|
||||
"The default encryption method has been changed to a more secure one and it is recommended that you apply it to your data.": "El método de cifrado predeterminado ha sido cambiado por uno más seguro y se recomienda que lo aplique a sus datos.",
|
||||
"The default encryption method has been changed, you should re-encrypt your data.": "El método de cifrado predeterminado ha sido cambiado, se recomienda recifrar los datos.",
|
||||
"The default profile cannot be deleted": "El perfil por defecto no puede eliminarse",
|
||||
"The editor command (may include arguments) that will be used to open a note. If none is provided it will try to auto-detect the default editor.": "El comando del editor (puede incluir argumentos) que se utilizará para abrir una nota. Si no se provee ninguno se intentará autodetectar el editor por defecto.",
|
||||
"The factor property sets how the item will grow or shrink to fit the available space in its container with respect to the other items. Thus an item with a factor of 2 will take twice as much space as an item with a factor of 1.Restart app to see changes.": "La propiedad factor establece cómo el elemento crecerá o se reducirá para adaptarse al espacio disponible en su contenedor con respecto a los otros elementos. Por lo tanto, un elemento con un factor de 2 ocupará el doble de espacio que un elemento con un factor de 1. Reinicie la aplicación para ver los cambios.",
|
||||
"The following attachments are being watched for changes:": "Los adjuntos siguientes están siendo vigilados en busca de cambios:",
|
||||
@@ -786,6 +885,7 @@
|
||||
"The web clipper service is not enabled.": "El servicio de Web Clipper no está habilitado.",
|
||||
"Theme": "Tema",
|
||||
"There are currently no notes. Create one by clicking on the (+) button.": "Actualmente, no hay notas. Cree una pulsando en el botón (+).",
|
||||
"There are unsaved changes.": "Hay cambios sin guardar.",
|
||||
"There is currently no notebook. Create one by clicking on \"New notebook\".": "No hay ninguna libreta. Cree una haciendo clic en «Libreta nueva».",
|
||||
"There is no data to export.": "No hay datos para exportar.",
|
||||
"There was a [conflict](%s) on the attachment below.\n\n%s": "Ha habido un [conflicto](%s) en el adjunto siguiente:.\n\n%s",
|
||||
@@ -819,8 +919,10 @@
|
||||
"To maximise/minimise the console, press \"tc\".": "Para maximizar/minimizar la consola, pulse «tc».",
|
||||
"To move from one pane to another, press Tab or Shift+Tab.": "Para trasladarse desde un panel a otro, pulse Tab o Mayús+Tab.",
|
||||
"To retry decryption of these items. Run `e2ee decrypt --retry-failed-items`": "Para reintentar el descifrado de estos elementos ejecute 'e2ee decrypt --retry-failed-items'",
|
||||
"To switch the profile, the app is going to close and you will need to restart it.": "Para cambiar el perfil, la aplicación se cerrará y deberás reiniciarla.",
|
||||
"To work correctly, the app needs the following permissions. Please enable them in your phone settings, in Apps > Joplin > Permissions": "Para funcionar correctamente, la aplicación necesita los siguientes permisos. Por favor habilítelos en la configuración de su teléfono, en Aplicaciones > Joplin > Permisos",
|
||||
"to-do": "tarea",
|
||||
"to-do: %s": "tarea: %s",
|
||||
"Toggle comment": "Alternar comentario",
|
||||
"Toggle development tools": "Alternar herramientas de desarrollo",
|
||||
"Toggle editor layout": "Alternar el diseño del editor",
|
||||
@@ -833,8 +935,10 @@
|
||||
"Toggle sort order field": "Alternar el campo de orden",
|
||||
"Token has been copied to the clipboard!": "¡El token ha sido copiado al portapapeles!",
|
||||
"Tools": "Herramientas",
|
||||
"Total Size": "Tamaño total",
|
||||
"Total: %d/%d": "Total: %d/%d",
|
||||
"Try again": "Vuelva a intentarlo",
|
||||
"Try it now": "Intentar ahora",
|
||||
"Type `help [command]` for more information about a command; or type `help all` for the complete usage information.": "Escriba `help [comando]` para obtener más información sobre el comando o escriba `help all` para obtener toda la información acerca del uso del programa.",
|
||||
"Type `joplin help` for usage information.": "Escriba `joplin help` para mostrar información de uso.",
|
||||
"Type a note title or part of its content to jump to it. Or type # followed by a tag name, or @ followed by a notebook name. Or type : to search for commands.": "Escriba el título de una nota o parte de su contenido para abrirla. O escriba # seguido del nombre de una etiqueta, o @ seguido del nombre de una libreta. O escriba : para buscar comandos.",
|
||||
@@ -844,6 +948,7 @@
|
||||
"Undo": "Deshacer",
|
||||
"Unknown flag: %s": "Flag desconocida: %s",
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version": "Se ha descargado un elemento de tipo desconocido - por favor actualice Joplin a la última versión",
|
||||
"Unordered list": "Lista",
|
||||
"Unpublish note": "Despublicar nota",
|
||||
"Unshare": "Dejar de compartir",
|
||||
"Unshare this notebook? The recipients will no longer have access to its content.": "¿Dejar de compartir esta libreta? Los destinatarios ya no tendrán acceso a su contenido.",
|
||||
@@ -865,16 +970,19 @@
|
||||
"Upgrade the sync target to the latest version.": "Actualizar el objetivo de sincronización a la última versión.",
|
||||
"URL": "URL",
|
||||
"Usage: %s": "Uso: %s",
|
||||
"Use biometrics to secure access to the app": "Usar biométricas para asegurar el acceso a la aplicación",
|
||||
"Use long list format. Format is ID, NOTE_COUNT (for notebook), DATE, TODO_CHECKED (for to-dos), TITLE": "Usar formato de lista larga. El formato es ID, NOTE_COUNT (para libretas), DATE,TODO_CHECKED (para tareas), TITLE",
|
||||
"Use spell checker": "Utilizar el corrector ortográfico",
|
||||
"Use the arrows and page up/down to scroll the lists and text areas (including this console).": "Utilice las flechas y re/avpág para desplazar las listas y áreas de textos (incluyendo esta consola).",
|
||||
"Use the arrows to move the layout items. Press \"Escape\" to exit.": "Use las flechas para mover los elementos del diseño. Pulse «Escape» para salir.",
|
||||
"Use this to rebuild the search index if there is a problem with search. It may take a long time depending on the number of notes.": "Use esto para reparar el índice de búsqueda si hay un problema con la búsqueda. Puede tomar un largo tiempo dependiendo del número de notas.",
|
||||
"Use your biometrics to secure access to your application. You can always set it up later in Settings.": "Usa tus biométricas para asegurar el acceso a la aplicación. Siempre puedes configurarlo luego en Ajustes.",
|
||||
"Used for most text in the markdown editor. If not found, a generic proportional (variable width) font is used.": "Se utiliza para la mayor parte del texto en el editor markdown. Si no se encuentra, se utiliza una fuente genérica proporcional (ancho variable).",
|
||||
"Used where a fixed width font is needed to lay out text legibly (e.g. tables, checkboxes, code). If not found, a generic monospace (fixed width) font is used.": "Se utiliza donde se necesita una fuente de ancho fijo para presentar el texto de manera legible (por ejemplo, tablas, casillas de verificación, código). Si no se encuentra, se utiliza una fuente genérica monoespaciada (ancho fijo).",
|
||||
"User deletions": "Eliminaciones de usuarios",
|
||||
"Users": "Usuarios",
|
||||
"Valid": "Válido",
|
||||
"Verify your identity": "Verifica tu identidad",
|
||||
"View": "Ver",
|
||||
"View on map": "Ver en mapa",
|
||||
"View them now": "Verlos ahora",
|
||||
@@ -903,6 +1011,7 @@
|
||||
"You may use the tool below to re-encrypt your data, for example if you know that some of your notes are encrypted with an obsolete encryption method.": "Puede utilizar la siguiente herramienta para volver a cifrar sus datos, por ejemplo, si sabe que algunas de sus notas están cifradas con un método de cifrado obsoleto.",
|
||||
"Your choice: ": "Su elección: ",
|
||||
"Your data is going to be re-encrypted and synced again.": "Sus datos van a ser cifrados y sincronizados nuevamente.",
|
||||
"Your password is needed to decrypt some of your data.": "Su contraseña es necesaria para descifrar algunos de sus datos.",
|
||||
"Your password is needed to decrypt some of your data. Type `:e2ee decrypt` to set it.": "Su contraseña es necesaria para descrifrar algunos de sus datos. Escriba `:e2ee decrypt` para establecerla.",
|
||||
"Your permission to use your camera is required.": "Su permiso para usar su cámara es necesario.",
|
||||
"Your version: %s": "Su versión: %s",
|
||||
|
@@ -9,6 +9,7 @@
|
||||
"%d MB per note or attachment": "%d Mo pour les notes et pièces jointes",
|
||||
"%d minutes": "%d minutes",
|
||||
"%d notes match this pattern. Delete them?": "%d notes correspondent à ce motif. Les supprimer ?",
|
||||
"%s": "%s",
|
||||
"%s %s (%s, %s)": "%s %s (%s, %s)",
|
||||
"%s (%s) could not be uploaded: %s": "%s (%s) n'a pas pu être envoyé : %s",
|
||||
"%s (%s) would like to share a notebook with you.": "%s (%s) souhaiterais partager un carnet avec vous.",
|
||||
@@ -75,6 +76,7 @@
|
||||
"Application": "Application",
|
||||
"Apply": "Appliquer",
|
||||
"Are you sure you want to renew the authorisation token?": "Renouveler le code d'authentification ?",
|
||||
"Are you sure you want to return to the default layout? The current layout configuration will be lost.": "Restaurer la disposition de l'interface ? La disposition en cours sera perdue.",
|
||||
"Arguments:": "Arguments :",
|
||||
"Aritim Dark": "Aritim Sombre",
|
||||
"Attach": "Joindre",
|
||||
@@ -321,6 +323,7 @@
|
||||
"Enable note history": "Activer l'historique des notes",
|
||||
"Enable PDF viewer": "Activer visionneuse PDF",
|
||||
"Enable soft breaks": "Activer retours à la ligne \"doux\"",
|
||||
"Enable spellcheck in the text editor": "Activer le correcteur d'orthographe de l'éditeur de texte",
|
||||
"Enable table of contents extension": "Activer la table des matières",
|
||||
"Enable typographer support": "Activer le typographe",
|
||||
"Enable video player": "Activer lecteur vidéo",
|
||||
@@ -457,6 +460,7 @@
|
||||
"Items": "Objets",
|
||||
"Items that cannot be decrypted": "Objets qui ne peuvent pas être déchiffrés",
|
||||
"Items that cannot be synchronised": "Objets qui ne peuvent pas être synchronisés",
|
||||
"Join us on Twitter": "Nous rejoindre sur Twitter",
|
||||
"Joplin can synchronise your notes using various providers. Select one from the list below.": "Joplin peut synchroniser vos notes grâce à divers fournisseurs. Sélectionnez‑en un dans la liste ci‑dessous.",
|
||||
"Joplin Cloud": "Joplin Cloud",
|
||||
"Joplin Cloud email": "Joplin Cloud : Email",
|
||||
@@ -626,6 +630,7 @@
|
||||
"Password:": "Mot de passe :",
|
||||
"Passwords do not match!": "Les mots de passe ne correspondent pas !",
|
||||
"Paste": "Coller",
|
||||
"Paste as text": "Coller comme texte",
|
||||
"Path:": "Chemin :",
|
||||
"PDF File": "Fichier PDF",
|
||||
"Per user. Minimum of %d users.": "Par utilisateur. Minimum %d utilisateurs.",
|
||||
@@ -705,6 +710,7 @@
|
||||
"Replace all": "Remplacer tout",
|
||||
"Replace with...": "Remplacer avec...",
|
||||
"Replace: ": "Remplacer : ",
|
||||
"Reset application layout": "Restaurer la disposition de l'interface",
|
||||
"Reset master password": "Réinitialiser le mot de passe maître",
|
||||
"Resources: %d.": "Ressources : %d.",
|
||||
"Restart and upgrade": "Redémarrer et mettre à jour",
|
||||
@@ -883,6 +889,7 @@
|
||||
"The Web Clipper needs your authorisation to access your data.": "Le Web Clipper requiert votre autorisation pour accéder à vos données.",
|
||||
"The web clipper service is enabled and set to auto-start.": "Le service du Web Clipper est activé et démarrera automatiquement.",
|
||||
"The web clipper service is not enabled.": "Le service du Web Clipper n'est pas activé.",
|
||||
"The WebDAV implementation of %s is incompatible with Joplin, and as such is no longer supported. Please use a different sync method.": "L'implémentation WebDAV de %s n'est pas compatible avec Joplin, et n'est plus supportée. Veuillez utiliser une autre méthode de synchronisation.",
|
||||
"Theme": "Apparence",
|
||||
"There are currently no notes. Create one by clicking on the (+) button.": "Ce carnet ne contient aucune note. Créez‑en une en appuyant sur le bouton (+).",
|
||||
"There are unsaved changes.": "Il y a des modifications non enregistrées.",
|
||||
@@ -929,7 +936,7 @@
|
||||
"Toggle editors": "Basculer l'éditeur",
|
||||
"Toggle external editing": "Basculer l'édition externe",
|
||||
"Toggle note list": "Basculer liste de notes",
|
||||
"Toggle own sort order": "Utiliser le tri personaliser",
|
||||
"Toggle own sort order": "Utiliser le tri personnalisé",
|
||||
"Toggle safe mode": "Basculer le mode sans échec",
|
||||
"Toggle sidebar": "Basculer barre latérale",
|
||||
"Toggle sort order field": "Basculer le champ d'ordre de tri",
|
||||
|
@@ -41,45 +41,45 @@ locales['uk_UA'] = require('./uk_UA.json');
|
||||
locales['vi'] = require('./vi.json');
|
||||
locales['zh_CN'] = require('./zh_CN.json');
|
||||
locales['zh_TW'] = require('./zh_TW.json');
|
||||
stats['ar'] = {"percentDone":81};
|
||||
stats['ar'] = {"percentDone":80};
|
||||
stats['eu'] = {"percentDone":23};
|
||||
stats['bs_BA'] = {"percentDone":58};
|
||||
stats['bg_BG'] = {"percentDone":45};
|
||||
stats['ca'] = {"percentDone":90};
|
||||
stats['hr_HR'] = {"percentDone":90};
|
||||
stats['cs_CZ'] = {"percentDone":78};
|
||||
stats['da_DK'] = {"percentDone":97};
|
||||
stats['de_DE'] = {"percentDone":98};
|
||||
stats['et_EE'] = {"percentDone":45};
|
||||
stats['ca'] = {"percentDone":89};
|
||||
stats['hr_HR'] = {"percentDone":89};
|
||||
stats['cs_CZ'] = {"percentDone":77};
|
||||
stats['da_DK'] = {"percentDone":99};
|
||||
stats['de_DE'] = {"percentDone":99};
|
||||
stats['et_EE'] = {"percentDone":44};
|
||||
stats['en_GB'] = {"percentDone":100};
|
||||
stats['en_US'] = {"percentDone":100};
|
||||
stats['es_ES'] = {"percentDone":89};
|
||||
stats['eo'] = {"percentDone":26};
|
||||
stats['fi_FI'] = {"percentDone":96};
|
||||
stats['fr_FR'] = {"percentDone":100};
|
||||
stats['es_ES'] = {"percentDone":99};
|
||||
stats['eo'] = {"percentDone":25};
|
||||
stats['fi_FI'] = {"percentDone":95};
|
||||
stats['fr_FR'] = {"percentDone":99};
|
||||
stats['gl_ES'] = {"percentDone":29};
|
||||
stats['id_ID'] = {"percentDone":90};
|
||||
stats['id_ID'] = {"percentDone":89};
|
||||
stats['it_IT'] = {"percentDone":81};
|
||||
stats['hu_HU'] = {"percentDone":78};
|
||||
stats['nl_BE'] = {"percentDone":79};
|
||||
stats['nl_NL'] = {"percentDone":89};
|
||||
stats['nb_NO'] = {"percentDone":89};
|
||||
stats['fa'] = {"percentDone":56};
|
||||
stats['pl_PL'] = {"percentDone":90};
|
||||
stats['pt_BR'] = {"percentDone":89};
|
||||
stats['nl_NL'] = {"percentDone":88};
|
||||
stats['nb_NO'] = {"percentDone":88};
|
||||
stats['fa'] = {"percentDone":55};
|
||||
stats['pl_PL'] = {"percentDone":91};
|
||||
stats['pt_BR'] = {"percentDone":88};
|
||||
stats['pt_PT'] = {"percentDone":73};
|
||||
stats['ro'] = {"percentDone":51};
|
||||
stats['sl_SI'] = {"percentDone":81};
|
||||
stats['sv'] = {"percentDone":97};
|
||||
stats['th_TH'] = {"percentDone":37};
|
||||
stats['vi'] = {"percentDone":79};
|
||||
stats['tr_TR'] = {"percentDone":90};
|
||||
stats['uk_UA'] = {"percentDone":73};
|
||||
stats['el_GR'] = {"percentDone":89};
|
||||
stats['ru_RU'] = {"percentDone":81};
|
||||
stats['sr_RS'] = {"percentDone":66};
|
||||
stats['zh_CN'] = {"percentDone":95};
|
||||
stats['zh_TW'] = {"percentDone":90};
|
||||
stats['ja_JP'] = {"percentDone":90};
|
||||
stats['ko'] = {"percentDone":90};
|
||||
stats['sl_SI'] = {"percentDone":80};
|
||||
stats['sv'] = {"percentDone":96};
|
||||
stats['th_TH'] = {"percentDone":36};
|
||||
stats['vi'] = {"percentDone":78};
|
||||
stats['tr_TR'] = {"percentDone":89};
|
||||
stats['uk_UA'] = {"percentDone":72};
|
||||
stats['el_GR'] = {"percentDone":88};
|
||||
stats['ru_RU'] = {"percentDone":99};
|
||||
stats['sr_RS'] = {"percentDone":65};
|
||||
stats['zh_CN'] = {"percentDone":97};
|
||||
stats['zh_TW'] = {"percentDone":89};
|
||||
stats['ja_JP'] = {"percentDone":89};
|
||||
stats['ko'] = {"percentDone":89};
|
||||
module.exports = { locales: locales, stats: stats };
|
@@ -1,8 +1,11 @@
|
||||
{
|
||||
"\"%s\" is missing the required \"%s\" property.": "\"%s\" nie posiada wymaganej właściwości \"%s\".",
|
||||
"%d days": "%d dni",
|
||||
"%d GB": "%d GB",
|
||||
"%d hour": "%d godzina",
|
||||
"%d hours": "%d godzin",
|
||||
"%d MB": "%d MB",
|
||||
"%d MB per note or attachment": "%d MB na notatkę lub załącznik",
|
||||
"%d minutes": "%d minut",
|
||||
"%d notes match this pattern. Delete them?": "%d notatki spełniają wzór. Usunąć je?",
|
||||
"%s %s (%s, %s)": "%s %s (%s, %s)",
|
||||
@@ -42,11 +45,13 @@
|
||||
"Accelerator \"%s\" is not valid.": "Skrót klawiszowy \"%s\" nie jest prawidłowy.",
|
||||
"Accelerator \"%s\" is used for \"%s\" and \"%s\" commands. This may lead to unexpected behaviour.": "Skrót klawiszowy \"%s\" jest już używany w komendach \"%s\" i \"%s\". Może to powodować nieoczekiwane zachowanie.",
|
||||
"Accept": "Akceptuj",
|
||||
"Account": "Konto",
|
||||
"Action": "Akcja",
|
||||
"Actions": "Akcje",
|
||||
"Active": "Aktywne",
|
||||
"Actual Size": "Rozmiar rzeczywisty",
|
||||
"Add body": "Dodaj tekst",
|
||||
"Add new": "Dodaj nowy",
|
||||
"Add or remove tags:": "Dodaj lub usuń znaczniki:",
|
||||
"Add recipient:": "Dodaj odbiorcę:",
|
||||
"Add title": "Dodaj tytuł",
|
||||
@@ -55,6 +60,7 @@
|
||||
"Admin dashboard": "Panel administracyjny",
|
||||
"Advanced options": "Opcje zaawansowane",
|
||||
"Advanced tools": "Narzędzia zaawansowane",
|
||||
"All data, including notes, notebooks and tags will be permanently deleted.": "Wszystkie dane, włączając notatki, notatniki i tagi zostaną trwale usunięte.",
|
||||
"All notes": "Wszystkie notatki",
|
||||
"All potential ports are in use - please report the issue at %s": "Wszystkie możliwe porty są w użyciu - proszę zgłosić błąd na %s",
|
||||
"Also displays unset and hidden config variables.": "Wyświetla także nieustawione i ukryte zmienne konfiguracyjne.",
|
||||
@@ -67,6 +73,7 @@
|
||||
"Are you sure you want to renew the authorisation token?": "Czy na pewno chcesz odnowić token autoryzacyjny?",
|
||||
"Arguments:": "Argumenty:",
|
||||
"Aritim Dark": "Aritim ciemny",
|
||||
"Attach": "Załącz",
|
||||
"Attach file": "Załącz plik",
|
||||
"Attach photo": "Załącz zdjęcie",
|
||||
"Attach...": "Załącz...",
|
||||
@@ -85,6 +92,7 @@
|
||||
"Automatically check for updates": "Sprawdź dostępność aktualizacji",
|
||||
"Automatically switch theme to match system theme": "Automatycznie dopasuj motyw do motywu systemowego",
|
||||
"Back": "Cofnij",
|
||||
"Basic": "Podstawowy",
|
||||
"Bold": "Pogrubienie",
|
||||
"Browse all plugins": "Przeglądaj wszystkie wtyczki",
|
||||
"Browse...": "Przeglądaj...",
|
||||
@@ -146,6 +154,7 @@
|
||||
"Conflicts": "Konflikty",
|
||||
"Conflicts (attachments)": "Konflikty (załączniki)",
|
||||
"Content provided by %s": "Treść dostarczona przez %s",
|
||||
"Continue": "Kontynuuj",
|
||||
"Convert to note": "Przekonwertuj do notatki",
|
||||
"Convert to todo": "Przekonwertuj do zadania",
|
||||
"Copy": "Kopiuj",
|
||||
@@ -165,6 +174,7 @@
|
||||
"Could not respond to the invitation. Please try again, or check with the notebook owner if they are still sharing it.\n\nThe error was: \"%s\"": "Nie udało się odpowiedzieć na zaproszenie. Proszę spróbować ponownie lub sprawdzić czy właściciel notatnika dalej go udostępnia.\n\nTreść błędu: \"%s\"",
|
||||
"Could not upgrade master key: %s": "Nie udało się zaktualizować klucza głównego: %s",
|
||||
"Could not verify the share status of this notebook - aborting. Please try again when you are connected to the internet.": "Nie udało się zweryfikować statusu udostępnienia tego notatnika - przerywanie. Proszę spróbować ponownie, gdy będziesz podłączony do internetu.",
|
||||
"Could not verify your identify": "Nie można zweryfikować tożsamości",
|
||||
"Create": "Utwórz",
|
||||
"Create a notebook": "Tworzy nowy notatnik",
|
||||
"Create new profile...": "Utwórz nowy profil...",
|
||||
@@ -261,6 +271,7 @@
|
||||
"Edit in external editor": "Edytuj w edytorze zewnętrznym",
|
||||
"Edit note.": "Edytuj notatkę.",
|
||||
"Edit notebook": "Edytuj notatnik",
|
||||
"Edit profile": "Edytuj profil",
|
||||
"Edit profile configuration...": "Edytuj konfigurację profilu...",
|
||||
"Editor": "Edytor",
|
||||
"Editor font": "Rodzaj czcionki edytora",
|
||||
@@ -280,6 +291,7 @@
|
||||
"Enable ^sup^ syntax": "Aktywuj składnię ^sup^",
|
||||
"Enable abbreviation syntax": "Aktywuj składnię skrótów",
|
||||
"Enable audio player": "Aktywuj odtwarzacz muzyki",
|
||||
"Enable biometrics authentication?": "Włączyć uwierzytelnianie biometryczne?",
|
||||
"Enable deflist syntax": "Aktywuj składnię składnię deflist",
|
||||
"Enable encryption": "Aktywuj szyfrowanie",
|
||||
"Enable footnotes": "Aktywuj stopki",
|
||||
@@ -339,6 +351,7 @@
|
||||
"File": "Plik",
|
||||
"File system": "System plików",
|
||||
"Filter tags": "Filtrowanie znaczników",
|
||||
"Find and replace": "Znajdź i zamień",
|
||||
"Firefox Extension": "Rozszerzenie dla Firefoxa",
|
||||
"Fix search index": "Napraw indeks wyszukiwania",
|
||||
"Fixing search index...": "Naprawa indeksu wyszukiwania...",
|
||||
@@ -834,6 +847,7 @@
|
||||
"To retry decryption of these items. Run `e2ee decrypt --retry-failed-items`": "Żeby spróbować ponownie odszyfrować te obiekty, uruchom `e2ee decrypt --retry-failed-items`",
|
||||
"To work correctly, the app needs the following permissions. Please enable them in your phone settings, in Apps > Joplin > Permissions": "Aby pracować prawidłowo, aplikacja potrzebuje następujących uprawnień. Proszę je aktywować w ustawieniach telefonu, w Aplikacje > Joplin > Uprawnienia",
|
||||
"to-do": "zadanie",
|
||||
"to-do: %s": "zadanie: %s",
|
||||
"Toggle comment": "Wstaw komentarz",
|
||||
"Toggle development tools": "Włącz/wyłącz narzędzia programistyczne",
|
||||
"Toggle editor layout": "Przełącz układ edytora",
|
||||
|
@@ -1,8 +1,12 @@
|
||||
{
|
||||
"\"%s\" is missing the required \"%s\" property.": "В \"%s\" отсутствует необходимое свойство \"%s\".",
|
||||
"%d days": "%d дней",
|
||||
"%d GB": "%d ГБ",
|
||||
"%d GB storage space": "storage space %d ГБ",
|
||||
"%d hour": "%d час",
|
||||
"%d hours": "%d часов",
|
||||
"%d MB": "%d МБ",
|
||||
"%d MB per note or attachment": "%d МБ на заметку или вложение",
|
||||
"%d minutes": "%d минут",
|
||||
"%d notes match this pattern. Delete them?": "%d заметок соответствуют указанному выражению. Удалить их?",
|
||||
"%s %s (%s, %s)": "%s %s (%s, %s)",
|
||||
@@ -27,6 +31,7 @@
|
||||
"&Tools": "&Сервис",
|
||||
"&View": "&Вид",
|
||||
"(%s)": "(%s)",
|
||||
"(In plugin: %s)": "(В плагине: %s)",
|
||||
"(None)": "(Нет)",
|
||||
"(wysiwyg: %s)": "(wysiwyg: %s)",
|
||||
"- Camera: to allow taking a picture and attaching it to a note.": "- Камера: позволяет сделать снимок и прикрепить его к заметке.",
|
||||
@@ -42,21 +47,29 @@
|
||||
"Accelerator \"%s\" is not valid.": "Сочетание клавиш \"%s\" недопустимо.",
|
||||
"Accelerator \"%s\" is used for \"%s\" and \"%s\" commands. This may lead to unexpected behaviour.": "Сочетание клавиш \"%s\" используется для команд \"%s\" и \"%s\". Это может привести к неожиданному поведению.",
|
||||
"Accept": "Принять",
|
||||
"Account": "Учетная запись",
|
||||
"Action": "Действие",
|
||||
"Actions": "Действия",
|
||||
"Active": "Активен",
|
||||
"Actual Size": "Фактический размер",
|
||||
"Add body": "Добавить текст заметки",
|
||||
"Add new": "Добавить новый",
|
||||
"Add or remove tags:": "Добавить или удалить метки:",
|
||||
"Add recipient:": "Добавить получателя:",
|
||||
"Add title": "Добавить заголовок",
|
||||
"Add to dictionary": "Добавить в словарь",
|
||||
"Admin": "Амдинистратор",
|
||||
"Admin dashboard": "Панель администратора",
|
||||
"Advanced options": "Расширенные настройки",
|
||||
"Advanced tools": "Расширенные инструменты",
|
||||
"All data, including notes, notebooks and tags will be permanently deleted.": "Все данные, включая заметки, блокноты и теги, будут удалены безвозвратно.",
|
||||
"All notes": "Все заметки",
|
||||
"All potential ports are in use - please report the issue at %s": "Все возможные порты заняты - пожалуйста, сообщите о проблеме в %s",
|
||||
"Also displays unset and hidden config variables.": "Также выводит неустановленные и скрытые параметры конфигурации.",
|
||||
"Also publish linked notes": "Также публикуйте связанные заметки",
|
||||
"Always": "Всегда",
|
||||
"Ambiguous notebook \"%s\". Please use notebook id instead - press \"ti\" to see the short notebook id or use $b for current selected notebook": "Неоднозначный блокнот \"%s\". Вместо этого используйте идентификатор блокнота - нажмите \"ti\", чтобы увидеть короткий идентификатор блокнота, или используйте $b для текущего выбранного блокнота",
|
||||
"Ambiguous notebook \"%s\". Please use short notebook id instead - press \"ti\" to see the short notebook id": "Неоднозначный блокнот \"%s\". Вместо этого используйте короткий идентификатор блокнота - нажмите \"ti\", чтобы увидеть короткий идентификатор блокнота",
|
||||
"An update is available, do you want to download it now?": "Доступно обновление, хотите загрузить его сейчас?",
|
||||
"Appearance": "Внешний вид",
|
||||
"Application": "Приложение",
|
||||
@@ -64,6 +77,7 @@
|
||||
"Are you sure you want to renew the authorisation token?": "Вы точно хотите обновить токен авторизации?",
|
||||
"Arguments:": "Аргументы:",
|
||||
"Aritim Dark": "Aritim тёмная",
|
||||
"Attach": "Прикрепить",
|
||||
"Attach file": "Прикрепить файл",
|
||||
"Attach photo": "Прикрепить фото",
|
||||
"Attach...": "Прикрепить…",
|
||||
@@ -77,13 +91,17 @@
|
||||
"Authentication was not completed (did not receive an authentication token).": "Аутентификация не была завершена (не получен токен аутентификации).",
|
||||
"Authorisation token:": "Токен авторизации:",
|
||||
"Auto": "Автоматически",
|
||||
"Auto-add disabled accounts for deletion": "Автоматическое добавление отключенных учетных записей для удаления",
|
||||
"Auto-pair braces, parenthesis, quotations, etc.": "Автоматическое закрытие скобок, кавычек и т. д.",
|
||||
"Automatically check for updates": "Автоматическая проверка наличия обновлений",
|
||||
"Automatically switch theme to match system theme": "Автоматическая смена темы в соответствии с настройками системы",
|
||||
"Back": "Назад",
|
||||
"Basic": "Основные",
|
||||
"Bold": "Полужирный",
|
||||
"Browse all plugins": "Просмотр всех плагинов",
|
||||
"Browse...": "Обзор...",
|
||||
"Bulleted List": "Маркированный список",
|
||||
"Can Share": "Можно поделиться",
|
||||
"Cancel": "Отмена",
|
||||
"Cancelling background synchronisation... Please wait.": "Отмена фоновой синхронизации... Пожалуйста, подождите.",
|
||||
"Cancelling...": "Отмена...",
|
||||
@@ -100,6 +118,8 @@
|
||||
"Cannot refresh token: authentication data is missing. Starting the synchronisation again may fix the problem.": "Невозможно обновить токен: отсутствуют данные аутентификации. Повторный запуск синхронизации может решить эту проблему.",
|
||||
"Cannot save %s \"%s\" because it is larger than the allowed limit (%s)": "Невозможно сохранить %s \"%s\" из-за превышения допустимого лимита (%s)",
|
||||
"Cannot save %s \"%s\" because it would go over the total allowed size (%s) for this account": "Невозможно сохранить %s \"%s\" из-за превышения допустимого размера (%s) для этой учетной записи",
|
||||
"Cannot share encrypted notebook with recipient %s because they have not enabled end-to-end encryption. They may do so from the screen Configuration > Encryption.": "Невозможно поделиться зашифрованным блокнотом с получателем %s, потому что у него не включено сквозное шифрование. Они могут сделать это на экране Настройки > Шифрование.",
|
||||
"Case sensitive": "С учетом регистра",
|
||||
"Change application layout": "Изменить расположение элементов",
|
||||
"Change language": "Изменить язык",
|
||||
"Characters": "Символы",
|
||||
@@ -117,11 +137,13 @@
|
||||
"Click to add tags...": "Щелкните для добавления тегов...",
|
||||
"Client ID: %s": "Идентификатор клиента: %s",
|
||||
"Close": "Закрыть",
|
||||
"Close dropdown": "Закрыть выпадающий список",
|
||||
"Close Window": "Закрыть окно",
|
||||
"Code": "Код",
|
||||
"Code Block": "Блок кода",
|
||||
"Code View": "Просмотр кода",
|
||||
"Collaborate on notebooks with others": "Делитесь блокнотами с другими",
|
||||
"Collapse": "Коллапс",
|
||||
"Coming alarms": "Ближайшие напоминания",
|
||||
"Comma-separated list of paths to directories to load the certificates from, or path to individual cert files. For example: /my/cert_dir, /other/custom.pem. Note that if you make changes to the TLS settings, you must save your changes before clicking on \"Check synchronisation configuration\".": "Разделенный запятыми список путей к файлам сертификатов (поддерживаются как каталоги, так и абсолютные пути к отдельным файлам). Например: /my/cert_dir, /other/custom.pem. Обратите внимание, что если вы вносите изменения в настройки TLS, вы должны сохранить внесенные изменения перед нажатием на \"Проверить настройки синхронизации\".",
|
||||
"command": "команда",
|
||||
@@ -131,6 +153,7 @@
|
||||
"Completed": "Завершено",
|
||||
"Completed decryption.": "Расшифровка завершена.",
|
||||
"Completed: %s (%s)": "Завершено: %s",
|
||||
"Compress old changes": "Сжатие старых изменений",
|
||||
"Configuration": "Конфигурация",
|
||||
"Confirm password cannot be empty": "Подтверждение пароля не может быть пустым",
|
||||
"Confirm password:": "Подтвердите пароль:",
|
||||
@@ -138,10 +161,15 @@
|
||||
"Conflicted: %d": "Конфликтов: %d",
|
||||
"Conflicts": "Конфликты",
|
||||
"Conflicts (attachments)": "Конфликты (вложения)",
|
||||
"Consolidated billing": "Консолидированный биллинг",
|
||||
"Content provided by %s": "Содержание предоставлено %s",
|
||||
"Continue": "Продолжить",
|
||||
"Convert to note": "Преобразовать в заметку",
|
||||
"Convert to todo": "Преобразовать в задачу",
|
||||
"Copy": "Копировать",
|
||||
"Copy dev mode command to clipboard": "Скопировать команду режима разработчика в буфер обмена",
|
||||
"Copy external link": "Копирование внешней ссылки",
|
||||
"Copy image": "Скопировать изображение",
|
||||
"Copy Link Address": "Скопировать ссылку",
|
||||
"Copy Markdown link": "Копировать ссылку Markdown",
|
||||
"Copy path to clipboard": "Скопировать путь в буфер обмена",
|
||||
@@ -152,14 +180,22 @@
|
||||
"Could not connect to plugin repository.": "Не удалось соединиться с репозиторием плагинов.",
|
||||
"Could not export notes: %s": "Не удалось экспортировать заметки: %s",
|
||||
"Could not install plugin: %s": "Не удалось установить плагин: %s",
|
||||
"Could not respond to the invitation. Please try again, or check with the notebook owner if they are still sharing it.\n\nThe error was: \"%s\"": "Не удалось ответить на приглашение. Пожалуйста, попробуйте еще раз или уточните у владельца блокнота, продолжает ли он его использовать.\n\nОшибка: \"%s\"",
|
||||
"Could not switch profile: %s": "Не удалось переключить профиль: %s",
|
||||
"Could not upgrade master key: %s": "Не удалось обновить мастер-ключ: %s",
|
||||
"Could not verify the share status of this notebook - aborting. Please try again when you are connected to the internet.": "Не удалось проверить состояние общего доступа к этому блокноту - прерывание. Пожалуйста, повторите попытку при подключении к Интернету.",
|
||||
"Could not verify your identify": "Не удалось проверить вашу личность",
|
||||
"Create": "Создать",
|
||||
"Create a notebook": "Создать новый блокнот",
|
||||
"Create new profile...": "Создание нового профиля...",
|
||||
"Create notebook": "Создать блокнот",
|
||||
"Create user": "Создание пользователя",
|
||||
"Created": "Создан",
|
||||
"created date": "дата создания",
|
||||
"Created local items: %d.": "Создано локальных элементов: %d.",
|
||||
"Created locally": "Создано локально",
|
||||
"Created remote items: %d.": "Создано элементов в хранилище: %d.",
|
||||
"Created: ": "Создано: %s",
|
||||
"Created: ": "Создано: ",
|
||||
"Created: %d.": "Создано: %d.",
|
||||
"Created: %s": "Создано: %s",
|
||||
"Creates a new note.": "Создает новую заметку.",
|
||||
@@ -175,10 +211,12 @@
|
||||
"Custom TLS certificates": "Пользовательские сертификаты TLS",
|
||||
"Cut": "Вырезать",
|
||||
"Dark": "Тёмная",
|
||||
"Dashboard": "Панель управления",
|
||||
"Database v%s": "База данных v%s",
|
||||
"Date": "Дата",
|
||||
"Date format": "Формат даты",
|
||||
"days": "дни",
|
||||
"Decrease indent level": "Уменьшить уровень отступа",
|
||||
"Decrypted items: %d": "Расшифровано элементов: %d",
|
||||
"Decrypted items: %s / %s": "Расшифровано элементов: %s / %s",
|
||||
"Decrypting items: %d/%d": "Расшифровка элементов: %d/%d",
|
||||
@@ -186,6 +224,8 @@
|
||||
"Default: %s": "По умолчанию: %s",
|
||||
"Delete": "Удалить",
|
||||
"Delete attachment \"%s\"?": "Удалить вложение \"%s\"?",
|
||||
"Delete expired sessions": "Удаление истекших сессий",
|
||||
"Delete expired tokens": "Удаление просроченных токенов",
|
||||
"Delete line": "Удалить строку",
|
||||
"Delete local data and re-download from sync target": "Удалить локальные данные и перегрузить данные с цели синхронизации",
|
||||
"Delete note \"%s\"?": "Удалить заметку \"%s\"?",
|
||||
@@ -193,8 +233,11 @@
|
||||
"Delete notebook \"%s\"?\n\nAll notes and sub-notebooks within this notebook will also be deleted.": "Удалить блокнот “%s”?\n\nВсе заметки и вложенные блокноты также будут удалены.",
|
||||
"Delete notebook? All notes and sub-notebooks within this notebook will also be deleted.": "Удалить блокнот? Все содержимое блокнота, включая заметки и вложенные блокноты, будет удалено.",
|
||||
"Delete plugin \"%s\"?": "Удалить плагин \"%s\"?",
|
||||
"Delete profile \"%s\"": "Удалить профиль \"%s\"",
|
||||
"Delete selected notes": "Удаление выбранных заметок",
|
||||
"Delete these %d notes?": "Удалить эти %d заметки?",
|
||||
"Delete this invitation? The recipient will no longer have access to this shared notebook.": "Удалить приглашение? Получатель больше не сможет получить доступ к этому общему блокноту.",
|
||||
"Delete this profile?": "Удалить этот профиль?",
|
||||
"Deleted local items: %d.": "Удалено локальных элементов: %d.",
|
||||
"Deleted remote items: %d.": "Удалено удаленных элементов: %d.",
|
||||
"Deletes the given notebook.": "Удаляет заданный блокнот.",
|
||||
@@ -223,6 +266,8 @@
|
||||
"Displays version information": "Отображает информацию о версии",
|
||||
"Do it now": "Загрузить сейчас",
|
||||
"Do not ask for confirmation.": "Не запрашивать подтверждение.",
|
||||
"Do not lose the password as, for security purposes, this will be the *only* way to decrypt the data! To enable encryption, please enter your password below.": "Не теряйте пароль, так как в целях безопасности это будет *единственный* способ расшифровать данные! Чтобы включить шифрование введите свой пароль ниже.",
|
||||
"Done": "Готово",
|
||||
"Download": "Загрузить",
|
||||
"Download and install the relevant extension for your browser:": "Скачайте и установите расширение для вашего браузера:",
|
||||
"Downloaded": "Загружено",
|
||||
@@ -236,19 +281,26 @@
|
||||
"Dropbox Login": "Вход в Dropbox",
|
||||
"Duplicate": "Создать дубликат",
|
||||
"Duplicate line": "Дубликат строки",
|
||||
"Duplicate selected notes": "Дублирование выбранных заметок",
|
||||
"Duplicates the notes matching <note> to [notebook]. If no notebook is specified the note is duplicated in the current notebook.": "Дублирует заметки, соответствующие выражению <note>, в блокнот [notebook]. Если блокнот не указан, заметки дублируются в текущем блокноте.",
|
||||
"Edit": "Правка",
|
||||
"Edit in external editor": "Редактировать во внешнем редакторе",
|
||||
"Edit link": "Редактирование ссылки",
|
||||
"Edit note.": "Редактировать заметку.",
|
||||
"Edit notebook": "Редактировать блокнот",
|
||||
"Edit profile": "Редактировать профиль",
|
||||
"Edit profile configuration...": "Редактирование конфигурации профиля...",
|
||||
"Editor": "Редактор",
|
||||
"Editor font": "Шрифт редактора",
|
||||
"Editor font family": "Семейство шрифтов редактора",
|
||||
"Editor font size": "Размер шрифта редактора",
|
||||
"Editor maximum width": "Максимальная ширина редктора",
|
||||
"Editor monospace font family": "Семейство моноширинных шрифтов редактора",
|
||||
"Editor: %s": "Редактор: %s",
|
||||
"Either \"text\" or \"json\"": "Или \"text\" или \"json\"",
|
||||
"Emacs": "Emacs",
|
||||
"Email": "Email",
|
||||
"Emails": "Email'ы",
|
||||
"emphasised text": "курсивный текст",
|
||||
"Enable": "Включить",
|
||||
"Enable ++insert++ syntax": "Включить синтаксис ++insert++",
|
||||
@@ -256,6 +308,7 @@
|
||||
"Enable ^sup^ syntax": "Включить синтаксис ^sup^",
|
||||
"Enable abbreviation syntax": "Включить синтаксис аббревиатур",
|
||||
"Enable audio player": "Включить аудиоплеер",
|
||||
"Enable biometrics authentication?": "Включить биометрическую аутентификацию?",
|
||||
"Enable deflist syntax": "Включить синтаксис deflist",
|
||||
"Enable encryption": "Включить шифрование",
|
||||
"Enable footnotes": "Включить постраничные сноски",
|
||||
@@ -274,12 +327,16 @@
|
||||
"Enable Web Clipper Service": "Включить службу веб-клиппера",
|
||||
"Enable ~sub~ syntax": "Включить синтаксис ~sub~",
|
||||
"Enabled": "Включено",
|
||||
"Enabling encryption means *all* your notes and attachments are going to be re-synchronised and sent encrypted to the sync target.": "Включение шифрования означает, что *все* ваши заметки и вложения будут повторно синхронизированы и отправлены в зашифрованном виде на сервер синхронизации.",
|
||||
"Encrypted": "Зашифровано",
|
||||
"Encrypted items cannot be modified": "Зашифрованные элементы не могут быть изменены",
|
||||
"Encrypted notebooks cannot be renamed": "Зашифрованные блокноты не могут быть переименованы",
|
||||
"Encryption": "Шифрование",
|
||||
"Encryption Config": "Конфигурация шифрования",
|
||||
"Encryption is: %s": "Шифрование: %s",
|
||||
"Encryption keys": "Ключи шифрования",
|
||||
"Encryption:": "Шифрование:",
|
||||
"End-to-end encryption": "Сквозное шифрование",
|
||||
"Enter code here": "Введите код здесь",
|
||||
"Enter master password:": "Введите мастер-пароль:",
|
||||
"Enter notebook title": "Введите название блокнота",
|
||||
@@ -292,6 +349,7 @@
|
||||
"Evernote Export File (as HTML)": "Файл экспорта Evernote (HTML)",
|
||||
"Evernote Export File (as Markdown)": "Файл экспорта Evernote (Markdown)",
|
||||
"Exits the application.": "Выйти из приложения.",
|
||||
"Expand": "Развернуть",
|
||||
"Export": "Экспорт",
|
||||
"Export all": "Экспорт",
|
||||
"Export debug report": "Экспортировать отладочный отчет",
|
||||
@@ -310,6 +368,9 @@
|
||||
"Fetching resources: %d/%d": "Загрузка ресурсов: %d/%d",
|
||||
"File": "Файл",
|
||||
"File system": "Файловая система",
|
||||
"Filter tags": "Фильтр тегов",
|
||||
"Find and replace": "Найти и заменить",
|
||||
"Find: ": "Найти: ",
|
||||
"Firefox Extension": "Расширение Firefox",
|
||||
"Fix search index": "Исправить индекс поиска",
|
||||
"Fixing search index...": "Исправление индекса поиска...",
|
||||
@@ -318,14 +379,19 @@
|
||||
"Focus title": "Фокус на названии",
|
||||
"Folders": "Каталоги",
|
||||
"For debugging purpose only: export your profile to an external SD card.": "Только для отладки: экспорт вашего профиля на внешнюю SD карту.",
|
||||
"For example \"%s\"": "Например \"%s\"",
|
||||
"For information on how to customise the shortcuts please visit %s": "Информацию по настройке сочетаний клавиш можно получить на странице %s",
|
||||
"For more information about End-To-End Encryption (E2EE) and advice on how to enable it please check the documentation:": "Для получения дополнительной информации о сквозном шифровании (E2EE) и советов о том, как его включить, пожалуйста, обратитесь к документации:",
|
||||
"For the list of keyboard shortcuts and config options, type `help keymap`": "Чтобы получить список сочетаний клавиш и настроек конфигурации, введите `help keymap`",
|
||||
"Force path style": "Принудительный путь стиля",
|
||||
"Formatting": "Форматирование",
|
||||
"Forward": "Вперёд",
|
||||
"Found: %d.": "Найдено: %d.",
|
||||
"FTS enabled: %d": "FTS включен: %d",
|
||||
"Full changelog": "Полный список изменений",
|
||||
"Full name": "Имя и фамилия",
|
||||
"General": "Основные",
|
||||
"Generated": "Создано",
|
||||
"Generating link...": "Создание ссылки...",
|
||||
"Get it now:": "Загрузить сейчас:",
|
||||
"Get pre-releases when checking for updates": "Получать предварительные выпуски при проверке обновлений",
|
||||
@@ -333,11 +399,19 @@
|
||||
"Go to source URL": "Перейти к исходному URL",
|
||||
"Goto Anything...": "Перейти к чему-либо…",
|
||||
"Grant authorisation": "Одобрить авторизацию",
|
||||
"Header %d": "Заголовок %d",
|
||||
"Headers": "Заголовки",
|
||||
"Heading": "Заголовок",
|
||||
"Help": "Помощь",
|
||||
"Hide %s": "Скрыть %s",
|
||||
"Hide advanced": "Скрыть расширенные",
|
||||
"Hide disabled": "Скрыть отключенный",
|
||||
"Hide disabled keys": "Скрыть отключенные ключи",
|
||||
"Hide Joplin": "Скрыть Joplin",
|
||||
"Hide keyboard": "Скрыть клавиатуру",
|
||||
"Hide more actions": "Скрыть дополнительные действия",
|
||||
"Highlight": "Выделение",
|
||||
"Home": "Дом",
|
||||
"Horizontal Rule": "Горизонтальный разделитель",
|
||||
"HTML Directory": "HTML каталог",
|
||||
"HTML File": "HTML файл",
|
||||
@@ -347,6 +421,7 @@
|
||||
"Idle": "Простой",
|
||||
"Ignore": "Игнорировать",
|
||||
"Ignore TLS certificate errors": "Игнорировать ошибки сертификата TLS",
|
||||
"Images": "Изображения",
|
||||
"Import": "Импорт",
|
||||
"Importing from \"%s\" as \"%s\" format. Please wait...": "Импорт из \"%s\" в формате \"%s\". Пожалуйста, подождите...",
|
||||
"Importing notes...": "Импорт заметок...",
|
||||
@@ -359,11 +434,14 @@
|
||||
"In order to use the web clipper, you need to do the following:": "Для использования веб-клиппера сделайте следующее:",
|
||||
"In progress": "Выполнение",
|
||||
"In: %s": "В: %s",
|
||||
"Increase indent level": "Увеличить уровень отступа",
|
||||
"Indent less": "Уменьшить отступ",
|
||||
"Indent more": "Увеличить отступ",
|
||||
"Information": "Информация",
|
||||
"Inline Code": "Инлайн код",
|
||||
"Insert": "Вставка",
|
||||
"Insert Hyperlink": "Вставить гиперссылку",
|
||||
"Insert time": "Вставить время",
|
||||
"Install": "Установить",
|
||||
"Install from file": "Установить из файла",
|
||||
"Installed": "Установлено",
|
||||
@@ -373,8 +451,10 @@
|
||||
"Invalid answer: %s": "Неверный ответ: %s",
|
||||
"Invalid command: \"%s\"": "Неверная команда: \"%s\"",
|
||||
"Invalid option value: \"%s\". Possible values are: %s.": "Неверное значение параметра: \"%s\". Доступные значения: %s.",
|
||||
"Invalid password": "Неверный пароль",
|
||||
"Italic": "Курсивный",
|
||||
"Item \"%s\" could not be downloaded: %s": "Не удается загрузить файл “%s”: %s",
|
||||
"Items": "Пункты",
|
||||
"Items that cannot be decrypted": "Элементы, которые не могут быть расшифрованы",
|
||||
"Items that cannot be synchronised": "Элементы, которые не могут быть синхронизированы",
|
||||
"Joplin can synchronise your notes using various providers. Select one from the list below.": "Joplin может синхронизировать ваши заметки используя разных поставщиков. Выберите одного из них в списке ниже.",
|
||||
@@ -392,43 +472,62 @@
|
||||
"Joplin Web Clipper allows saving web pages and screenshots from your browser to Joplin.": "Веб-клиппер Joplin позволяет сохранять веб-страницы и скриншоты из вашего браузера в Joplin.",
|
||||
"Joplin website": "Сайт Joplin",
|
||||
"Joplin's own sync service. Also gives access to Joplin-specific features such as publishing notes or collaborating on notebooks with others.": "Собственный сервис от Joplin. Кроме того, позволяет поучить доступ к специфичным функциям Joplin, как публикация заметок и общай работа с блокнотами.",
|
||||
"KaTeX": "KaTeX",
|
||||
"Keep note history for": "Хранить историю заметки",
|
||||
"Keyboard Mode": "Режим работы клавиатуры",
|
||||
"Keyboard Shortcut": "Сочетание клавиш",
|
||||
"Keyboard Shortcuts": "Сочетания клавиш",
|
||||
"Keychain Supported: %s": "Поддерживаемая связка ключей: %s",
|
||||
"Keys that need upgrading": "Ключи, которые нуждаются в обновлении",
|
||||
"Landscape": "Горизонтально",
|
||||
"Language": "Язык",
|
||||
"Last error: %s": "Последняя ошибка: %s",
|
||||
"Later": "Позже",
|
||||
"Layout": "Вид редактора",
|
||||
"Layout button sequence": "Порядок переключения вида",
|
||||
"Leave notebook...": "Покинуть блокнот...",
|
||||
"Legal": "Юр. сведения",
|
||||
"Letter": "Письмо",
|
||||
"Light": "Светлая",
|
||||
"Lines": "Строки",
|
||||
"Link": "Ссылка",
|
||||
"Link description": "Описание ссылки",
|
||||
"Link has been copied to clipboard!": "Ссылка скопирована в буфер обмена!",
|
||||
"Link text": "Текст ссылки",
|
||||
"Links with protocol \"%s\" are not supported": "Ссылки с протоколом “%s” не поддерживаются",
|
||||
"List item": "Элемент списка",
|
||||
"Lists": "Списки",
|
||||
"Loaded": "Загружено",
|
||||
"Location": "Местоположение",
|
||||
"Lock file is already being hold. If you know that no synchronisation is taking place, you may delete the lock file at \"%s\" and resume the operation.": "Файл блокировки уже существует. Если вы уверены, что синхронизация не выполняется, вы можете вручную удалить файл блокировки \"%s\" и возобновить операцию.",
|
||||
"Log": "Журнал",
|
||||
"Login": "Вход",
|
||||
"Login below.": "Войдите",
|
||||
"Login below.": "Войдите в систему.",
|
||||
"Login with Dropbox": "Войти с Dropbox",
|
||||
"Login with OneDrive": "Войти с OneDrive",
|
||||
"Logout": "Выйти из системы",
|
||||
"Logs": "Логи",
|
||||
"Make a donation": "Пожертвовать",
|
||||
"Manage master password": "Управление мастер-паролем",
|
||||
"Manage master password...": "Управление мастер-паролем...",
|
||||
"Manage multiple users": "Управление несколькими пользователями",
|
||||
"Manage profiles": "Управление профилями",
|
||||
"Manage your plugins": "Управлять плагинами",
|
||||
"Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, `status`, `decrypt-file`, and `target-status`.": "Управляет конфигурацией E2EE. Команды: `enable`, `disable`, `decrypt`, `status`, `decrypt-file` и `target-status`.",
|
||||
"Manual": "Ручной",
|
||||
"Markdown": "Markdown",
|
||||
"Markdown + Front Matter": "Markdown + Front Matter",
|
||||
"Marks a to-do as done.": "Отмечает задачу как выполненную.",
|
||||
"Marks a to-do as non-completed.": "Помечает задачу как незавершенную.",
|
||||
"Markup": "Разметка",
|
||||
"Master Key %s": "Мастер-ключ %s",
|
||||
"Master password": "Мастер-пароль:",
|
||||
"Master password": "Мастер-пароль",
|
||||
"Master password:": "Главный пароль:",
|
||||
"Max concurrent connections": "Максимальное число одновременных соединений",
|
||||
"Max Item Size": "Максимальный размер пункта",
|
||||
"Max note or attachment size": "Максимальный размер заметки или вложения",
|
||||
"Max Total Size": "Максимальный общий размер",
|
||||
"Missing keys": "Отсутствующие ключи",
|
||||
"Missing Master Keys": "Недостающие мастер-ключи",
|
||||
"Missing required argument: %s": "Отсутствует обязательный аргумент: %s",
|
||||
"Mobile data - auto-sync disabled": "Мобильная сеть - авто-синхронизация отключена",
|
||||
@@ -439,6 +538,7 @@
|
||||
"Move to notebook": "Переместить в блокнот",
|
||||
"Move to notebook...": "Переместить в блокнот...",
|
||||
"Move to notebook:": "Переместить в блокнот:",
|
||||
"Moves the given <item> to [notebook]": "Перемещает заданный <item> в [блокнот].",
|
||||
"n": "n",
|
||||
"N": "N",
|
||||
"New note": "Новая заметка",
|
||||
@@ -449,6 +549,7 @@
|
||||
"New tags:": "Новые метки:",
|
||||
"New to-do": "Новая задача",
|
||||
"New version: %s": "Новая версия: %s",
|
||||
"Next match": "Следующее совпадение",
|
||||
"Nextcloud": "Nextcloud",
|
||||
"Nextcloud password": "Пароль Nextcloud",
|
||||
"Nextcloud username": "Имя пользователя Nextcloud",
|
||||
@@ -468,13 +569,16 @@
|
||||
"Nord": "Север",
|
||||
"Not authentified with %s. Please provide any missing credentials.": "Не удалось аутентифицироваться с %s. Пожалуйста, предоставьте недостающие данные.",
|
||||
"Not downloaded": "Не загружено",
|
||||
"Not generated": "Не создано",
|
||||
"Not now": "Не сейчас",
|
||||
"note": "заметка",
|
||||
"Note": "Заметки",
|
||||
"Note": "Заметка",
|
||||
"Note area growth factor": "Фактор роста поля заметки",
|
||||
"Note attachments": "Вложения заметки",
|
||||
"Note attachments...": "Вложения...",
|
||||
"Note body": "Тело заметки",
|
||||
"Note does not exist: \"%s\". Create it?": "Не существует заметки: \"%s\". Создать?",
|
||||
"Note editor": "Редактор заметок",
|
||||
"Note has been saved.": "Заметка сохранена.",
|
||||
"Note History": "История заметок",
|
||||
"Note is not a to-do: \"%s\"": "Заметка не является задачей: \"%s\"",
|
||||
@@ -485,10 +589,12 @@
|
||||
"Note&book": "Блок&ноты",
|
||||
"Note: Does not work in all desktop environments.": "Примечание: работает не во всех окружениях рабочего стола.",
|
||||
"Note: When a note is shared, it will no longer be encrypted on the server.": "Примечание: если вы поделились заметкой - она будет храниться на сервере в незашифрованном виде.",
|
||||
"Notebook": "Блокнот",
|
||||
"Notebook list growth factor": "Фактор роста списка блокнотов",
|
||||
"Notebook: %s": "Блокнот: %s",
|
||||
"Notebooks": "Блокноты",
|
||||
"Notebooks cannot be named \"%s\", which is a reserved title.": "Блокнот не может быть назван \"%s\", так как это зарезервированное название.",
|
||||
"Notes": "Заметки",
|
||||
"Notes and settings are stored in: %s": "Заметки и настройки сохранены в: %s",
|
||||
"Notes can only be created within a notebook.": "Заметки могут быть созданы только в блокноте.",
|
||||
"Numbered List": "Нумерованный список",
|
||||
@@ -503,11 +609,15 @@
|
||||
"Only one note can be printed at a time.": "Только одна заметка может быть напечатана за раз.",
|
||||
"Open": "Открыть",
|
||||
"Open %s": "Открыть %s",
|
||||
"Open PDF viewer": "Открыть просмотрщик PDF-файлов",
|
||||
"Open profile directory": "Открыть директорию с настройками",
|
||||
"Open Sync Wizard...": "Открытие мастера синхронизации...",
|
||||
"Open...": "Открыть...",
|
||||
"Operation cancelled": "Операция отменена",
|
||||
"Options": "Настройки",
|
||||
"Or create an account.": "Или создайте новую учетную запись.",
|
||||
"Ordered list": "Упорядоченный список",
|
||||
"Other applications...": "Другие приложения...",
|
||||
"Output format: %s": "Выходной формат: %s",
|
||||
"Page orientation for PDF export": "Ориентация страницы при экспорте в PDF",
|
||||
"Page size for PDF export": "Размер страницы при экспорте в PDF",
|
||||
@@ -518,10 +628,13 @@
|
||||
"Paste": "Вставить",
|
||||
"Path:": "Путь:",
|
||||
"PDF File": "Файл PDF",
|
||||
"Per user. Minimum of %d users.": "На каждого пользователя. Минимум %d пользователей.",
|
||||
"Permission needed": "Необходимы разрешения",
|
||||
"Permission to use camera": "Разрешение на использование камеры",
|
||||
"Please click on \"%s\" to proceed, or set the passwords in the \"%s\" list below.": "Пожалуйста, нажмите на \"%s\", чтобы продолжить, или задайте пароли в списке \"%s\" ниже.",
|
||||
"Please confirm that you would like to re-encrypt your complete database.": "Пожалуйста, подтвердите, что вы хотели бы повторно зашифровать вашу полную базу данных.",
|
||||
"Please enter your password in the master key list below before upgrading the key.": "Пожалуйста, введите ваш пароль в список мастер-ключей ниже перед обновлением ключа.",
|
||||
"Please note that if it is a large notebook, it may take a few minutes for all the notes to show up on the recipient's device.": "Обратите внимание, что если это большой блокнот, может потребоваться несколько минут, чтобы все заметки отобразились на устройстве получателя.",
|
||||
"Please open the following URL in your browser to authenticate the application. The application will create a directory in \"Apps/Joplin\" and will only read and write files in this directory. It will have no access to any files outside this directory nor to any other personal data. No data will be shared with any third party.": "Откройте следующую ссылку в вашем браузере для аутентификации приложения. Приложением будет создан отдельный каталог \"Apps/Joplin\", в котором будет происходить работа с файлами. У приложения не будет доступа ни к каким-либо файлам за пределами этого каталога, ни к каким-либо другим личным данным. Никакая информация не будет передана третьим лицам.",
|
||||
"Please select a notebook first.": "Сначала выберите блокнот.",
|
||||
"Please select the note or notebook to be deleted first.": "Пожалуйста, сначала выберите заметку или блокнот для удаления.",
|
||||
@@ -544,11 +657,25 @@
|
||||
"Press the shortcut": "Нажмите нужное сочетание клавиш",
|
||||
"Press the shortcut and then press ENTER. Or, press BACKSPACE to clear the shortcut.": "Нажмите нужное сочетание клавиш и затем нажмите Enter. Для очистки сочетания клавиш нажмите Backspace.",
|
||||
"Press to set the decryption password.": "Нажмите, чтобы установить пароль для расшифровки.",
|
||||
"Previous match": "Предыдущее совпадение",
|
||||
"Previous versions of this note": "Предыдущая версия заметки",
|
||||
"Print": "Печать",
|
||||
"Priority support": "Приоритетная поддержка",
|
||||
"Privacy Policy": "Политика конфиденциальности",
|
||||
"Pro": "Pro",
|
||||
"Process failed payment subscriptions": "Обработка неудачных подписок на платежи",
|
||||
"Process oversized accounts": "Обрабатывать слишком большие учетные записи",
|
||||
"Process user deletions": "Обработка удаления пользователей",
|
||||
"Profile": "Профиль",
|
||||
"Profile name": "Название профиля",
|
||||
"Profile name:": "Название профиля:",
|
||||
"Profile Version: %s": "Версия профиля: %s",
|
||||
"Profiles": "Профили",
|
||||
"Properties": "Свойства",
|
||||
"Proxy enabled": "Прокси включен",
|
||||
"Proxy timeout (seconds)": "Таймаут прокси-сервера (секунды)",
|
||||
"Proxy URL": "Адрес прокси-сервера",
|
||||
"Public-private key pair:": "Пара открытый-закрытый ключ:",
|
||||
"Publish note...": "Опубликовать заметку...",
|
||||
"Publish Notes": "Публиковать заметку",
|
||||
"Publish notes to the internet": "Опубликовать заметки в Интернет",
|
||||
@@ -564,6 +691,7 @@
|
||||
"Recipients:": "Получатели:",
|
||||
"Redo": "Повторить",
|
||||
"Refresh": "Обновить",
|
||||
"Regular expression": "Регулярное выражение",
|
||||
"Reject": "Отказаться",
|
||||
"Remove": "Удалить",
|
||||
"Remove tag \"%s\" from all notes?": "Удалить метку “%s” из всех заметок?",
|
||||
@@ -573,6 +701,11 @@
|
||||
"Rename tag:": "Переименовать метку:",
|
||||
"Renames the given <item> (note or notebook) to <name>.": "Переименовывает элемент <item> (заметку или блокнот) в <name>.",
|
||||
"Renew token": "Обновить токен",
|
||||
"Replace": "Замена",
|
||||
"Replace all": "Заменить все",
|
||||
"Replace with...": "Заменить на...",
|
||||
"Replace: ": "Замена: ",
|
||||
"Reset master password": "Сбросить мастер-пароль",
|
||||
"Resources: %d.": "Ресурсов: %d.",
|
||||
"Restart and upgrade": "Перезапустить и обновить",
|
||||
"Restart now": "Перезагрузить сейчас",
|
||||
@@ -585,14 +718,22 @@
|
||||
"Reverses the sorting order.": "Меняет порядок сортировки.",
|
||||
"Revision: %s (%s)": "Изменения: %s (%s)",
|
||||
"Runs the commands contained in the text file. There should be one command per line.": "Запуск команд из текстового файла. Необходимо указать по команде на строку.",
|
||||
"S3": "S3",
|
||||
"S3 access key": "Ключ доступа S3",
|
||||
"S3 bucket": "S3 bucket",
|
||||
"S3 region": "Регион S3",
|
||||
"S3 secret key": "Секретный ключ S3",
|
||||
"S3 URL": "S3 URL",
|
||||
"Safe mode is currently active. Note rendering and all plugins are temporarily disabled.": "Активен безопасный режим. Отображение заметок и все плагины временно отключены.",
|
||||
"Save": "Сохранить",
|
||||
"Save alarm": "Сохранить напоминание",
|
||||
"Save as %s": "Сохранить как %s",
|
||||
"Save as...": "Сохранить как...",
|
||||
"Save changes": "Сохранить изменения",
|
||||
"Save geo-location with notes": "Сохранять информацию о географическом местоположении в заметках",
|
||||
"Search": "Поиск",
|
||||
"Search for plugins...": "Поиск плагинов...",
|
||||
"Search for...": "Поиск...",
|
||||
"Search in all the notes": "Поиск во всех заметках",
|
||||
"Search in current note": "Поиск в текущей заметке",
|
||||
"Search...": "Поиск...",
|
||||
@@ -601,23 +742,34 @@
|
||||
"See the pre-release page for more details: %s": "См. страницу предварительных выпусков для получения более подробной информации: %s",
|
||||
"Select": "Выбрать",
|
||||
"Select all": "Выбрать все",
|
||||
"Select emoji...": "Поиск эмодзи...",
|
||||
"Select file...": "Выбрать файл...",
|
||||
"Server is already running on port %d": "Сервер уже запущен. Порт: %d",
|
||||
"Server is not running.": "Сервер не запущен.",
|
||||
"Server is running on port %d": "Сервер запущен. Порт: %d",
|
||||
"Set alarm": "Установить напоминание",
|
||||
"Set alarm:": "Установить напоминание:",
|
||||
"Set it to 0 to make it take the complete available space. Recommended width is 600.": "Установите 0, чтобы он занимал все свободное пространство. Рекомендуемая ширина - 600.",
|
||||
"Set the password": "Установить пароль",
|
||||
"Sets the property <name> of the given <note> to the given [value]. Possible properties are:\n\n%s": "Устанавливает значение [value] для свойства <name> заметки <note>. Доступные свойства:\n\n%s",
|
||||
"Share": "Поделиться",
|
||||
"Share and collaborate on a notebook": "Совместное использование блокнота и совместная работа над ним",
|
||||
"Share Notebook": "Поделиться блокнотом",
|
||||
"Share notebook...": "Поделиться блокнотом...",
|
||||
"Sharing access control": "Совместное управление доступом",
|
||||
"Sharing notebook...": "Поделиться блокнотом...",
|
||||
"Shortcuts are not available in CLI mode.": "Сочетания клавиш недоступны в режиме командной строки.",
|
||||
"Show advanced": "Показать расширенные",
|
||||
"Show Advanced Settings": "Показать расширенные настройки",
|
||||
"Show all": "Показать все",
|
||||
"Show completed to-dos": "Показать завершенные задачи",
|
||||
"Show disabled": "Показать отключенные",
|
||||
"Show disabled keys": "Показать отключенные ключи",
|
||||
"Show more actions": "Показать больше действий",
|
||||
"Show note counts": "Показывать число заметок",
|
||||
"Show sort order buttons": "Показать кнопки порядка сортировки",
|
||||
"Show tray icon": "Показывать иконку в трее",
|
||||
"Show/hide the sidebar": "Показать/скрыть боковую панель",
|
||||
"Sidebar": "Боковая панель",
|
||||
"Size": "Размер",
|
||||
"Skip this version": "Пропустить эту версию",
|
||||
@@ -633,7 +785,7 @@
|
||||
"Sort selected lines": "Отсортировать выбранные строки",
|
||||
"Sorts the item by <field> (eg. title, updated_time, created_time).": "Сортирует элементы по полю <field> (напр. title, updated_time, created_time).",
|
||||
"Source format: %s": "Исходный формат: %s",
|
||||
"Source: ": "Источник",
|
||||
"Source: ": "Источник: ",
|
||||
"Specify the port that should be used by the API server. If not set, a default will be used.": "Укажите порт для использования сервером API. Если порт не указан - будет использовано значение по умолчанию.",
|
||||
"Spell checker": "Проверка орфографии",
|
||||
"Split View": "Раздельный вид",
|
||||
@@ -653,16 +805,22 @@
|
||||
"Step 2: Install the extension": "Шаг 2: Установите расширение",
|
||||
"Stop": "Остановить",
|
||||
"Stop external editing": "Выйти из внешнего редактора",
|
||||
"Storage space": "Место для хранения",
|
||||
"Strikethrough": "Зачеркнутый",
|
||||
"strong text": "полужирный текст",
|
||||
"Submit": "Отправить",
|
||||
"Subscript": "Подписка",
|
||||
"Success! Synchronisation configuration appears to be correct.": "Успешно! Синхронизация настроена правильно.",
|
||||
"Superscript": "Надстрочный знак",
|
||||
"Swap line down": "Поменять со строкой снизу",
|
||||
"Swap line up": "Поменять со строкой сверху",
|
||||
"Switch between note and to-do type": "Переключить между заметкой и задачей",
|
||||
"Switch profile": "Переключение профиля",
|
||||
"Switch to note type": "Конвертировать в заметку",
|
||||
"Switch to profile %d": "Переключение на профиль %d",
|
||||
"Switch to to-do type": "Конвертировать в задачу",
|
||||
"Switches to [notebook] - all further operations will happen within this notebook.": "Совершает переход в блокнот [notebook]. Все дальнейшие операции будут совершены в этом блокноте.",
|
||||
"Sync as many devices as you want": "Синхронизируйте столько устройств, сколько хотите",
|
||||
"Sync Status": "Статус синхронизации",
|
||||
"Sync status (synced items / total items)": "Статус синхронизации элементов (синхронизировано / всего)",
|
||||
"Sync target must be upgraded! Run `%s` to proceed.": "Цель синхронизации необходимо обновить! Выполните \"%s\" чтобы продолжить.",
|
||||
@@ -682,12 +840,18 @@
|
||||
"Synchronising...": "Синхронизация...",
|
||||
"Synchronizing...": "Синхронизация...",
|
||||
"Tabloid": "Tabloid",
|
||||
"tag1, tag2, ...": "тег1, тег2, ...",
|
||||
"Tagged: %d.": "С метками: %d.",
|
||||
"Tags": "Метки",
|
||||
"Take photo": "Сделать фото",
|
||||
"Task list": "Список задач",
|
||||
"Tasks": "Задачи",
|
||||
"Teams": "Команды",
|
||||
"Text editor command": "Команда текстового редактора",
|
||||
"Thank you! Your Joplin Cloud account is now setup and ready to use.": "Спасибо! Ваша учетная запись Joplin Cloud настроена и готова к использованию.",
|
||||
"The active profile cannot be deleted. Switch to a different profile and try again.": "Активный профиль не может быть удален. Переключитесь на другой профиль и повторите попытку.",
|
||||
"The app is now going to close. Please relaunch it to complete the process.": "Приложение будет закрыто. Пожалуйста, перезапустите его чтобы завершить установку.",
|
||||
"The application did not close properly. Would you like to start in safe mode?": "Приложение не завершилось должным образом. Хотите запустить в безопасном режиме?",
|
||||
"The application has been authorised - you may now close this browser tab.": "Приложение авторизовано - можно закрыть вкладку браузера.",
|
||||
"The application has been authorised!": "Приложение успешно авторизовано!",
|
||||
"The application has been successfully authorised.": "Приложение успешно авторизовано.",
|
||||
@@ -697,24 +861,31 @@
|
||||
"The default admin password is insecure and has not been changed! [Change it now](%s)": "Пароль администратора по умолчанию небезопасен и не был изменен! [Поменять пароль](%s)",
|
||||
"The default encryption method has been changed to a more secure one and it is recommended that you apply it to your data.": "Метод шифрования по умолчанию был изменен на более безопасный, рекомендуется применять его к своим данным.",
|
||||
"The default encryption method has been changed, you should re-encrypt your data.": "Метод шифрования по умолчанию был изменен, вы должны повторно зашифровать свои данные.",
|
||||
"The default profile cannot be deleted": "Профиль по умолчанию не может быть удален",
|
||||
"The editor command (may include arguments) that will be used to open a note. If none is provided it will try to auto-detect the default editor.": "Команда запуска внешнего текстового редактора (может включать аргументы командной строки). Если команда не задана, будет произведена попытка автоматического определения редактора по умолчанию.",
|
||||
"The factor property sets how the item will grow or shrink to fit the available space in its container with respect to the other items. Thus an item with a factor of 2 will take twice as much space as an item with a factor of 1.Restart app to see changes.": "Параметр \"фактор\" отвечает за то, как элемент будет расти или уменьшаться, чтобы поместиться в свободное пространство контейнера. Элемент с фактором 2 займёт в 2 раза больше места, чем элемент с фактором 1. Перезапустите приложение, чтобы изменения вступили в силу.",
|
||||
"The following attachments are being watched for changes:": "Отслеживаются изменения следующих вложений:",
|
||||
"The following keys use an out-dated encryption algorithm and it is recommended to upgrade them. The upgraded key will still be able to decrypt and encrypt your data as usual.": "Следующие ключи используют устаревший алгоритм шифрования, поэтому рекомендуется их обновить. Обновленный ключ будет по-прежнему способен расшифровывать и шифровать ваши данные, как обычно.",
|
||||
"The Joplin mobile app does not currently support this type of link: %s": "Мобильное приложение Joplin в настоящее время не поддерживает данный тип ссылки: %s",
|
||||
"The Joplin team has vetted this plugin and it meets our standards for security and performance.": "Команда Joplin проверила данный плагин и он соответствует нашим стандартам безопасности и производительности.",
|
||||
"The keys with these IDs are used to encrypt some of your items, however the application does not currently have access to them. It is likely they will eventually be downloaded via synchronisation.": "Ключи с этими идентификаторами используются для шифрования некоторых ваших элементов, однако в настоящее время приложение не имеет к ним доступа. Вероятно, со временем они будут загружены через синхронизацию.",
|
||||
"The master key has been upgraded successfully!": "Мастер-ключ был успешно обновлен!",
|
||||
"The master keys with these IDs are used to encrypt some of your items, however the application does not currently have access to them. It is likely they will eventually be downloaded via synchronisation.": "Мастер-ключи с такими ID используются для шифрования некоторых из ваших элементов, однако у приложения сейчас нет к ним доступа. Скорее всего, они загрузятся при синхронизации.",
|
||||
"The note \"%s\" has been successfully restored to the notebook \"%s\".": "Заметка “%s” была успешно восстановлена в блокнот “%s”.",
|
||||
"The notebook could not be saved: %s": "Блокнот не может быть сохранен: %s",
|
||||
"The notes have been imported: %s": "Импортировано заметок: %s",
|
||||
"The possible commands are:": "Доступные команды:",
|
||||
"The recipient could not be removed from the list. Please try again.\n\nThe error was: \"%s\"": "Получателя не удалось удалить из списка. Пожалуйста, попробуйте еще раз.\n\nОшибка была: \"%s\"",
|
||||
"The sync target needs to be upgraded before Joplin can sync. The operation may take a few minutes to complete and the app needs to be restarted. To proceed please click on the link.": "Нужно обновить версию базы данных перед синхронизацией. Эта операция может потребовать несколько минут для завершения и приложение нужно будет перезапустить. Чтобы продолжить нажмите на ссылку.",
|
||||
"The sync target needs to be upgraded. Press this banner to proceed.": "Необходимо обновить цель синхронизации. Нажмите на этот баннер, чтобы продолжить.",
|
||||
"The tag \"%s\" already exists. Please choose a different name.": "Метка \"%s\" уже существует. Пожалуйста, выберите другое имя.",
|
||||
"The target to synchronise to. Each sync target may have additional parameters which are named as `sync.NUM.NAME` (all documented below).": "Цель синхронизации. Каждая цель синхронизации может иметь дополнительные параметры, именованные как `sync.NUM.NAME` (все документировано ниже).",
|
||||
"The Web Clipper needs your authorisation to access your data.": "Web Clipper требуется ваше разрешение на доступ к вашим данным.",
|
||||
"The web clipper service is enabled and set to auto-start.": "Служба веб-клиппера включена и настроена на автоматический запуск.",
|
||||
"The web clipper service is not enabled.": "Служба веб-клиппера не включена.",
|
||||
"Theme": "Тема",
|
||||
"There are currently no notes. Create one by clicking on the (+) button.": "Заметки отсутствуют. Создайте новую, нажав кнопку (+).",
|
||||
"There are unsaved changes.": "Существуют несохраненные изменения.",
|
||||
"There is currently no notebook. Create one by clicking on \"New notebook\".": "Блокноты отсутствуют. Создайте новый, нажав на \"Новый блокнот\".",
|
||||
"There is no data to export.": "Нет данных для экспорта.",
|
||||
"There was a [conflict](%s) on the attachment below.\n\n%s": "[Конфликт](%s) При загрузке вложения.\n\n%s",
|
||||
@@ -734,10 +905,12 @@
|
||||
"This service allows the browser extension to communicate with Joplin. When enabling it your firewall may ask you to give permission to Joplin to listen to a particular port.": "Эта служба позволяет расширению браузера взаимодействовать с Joplin. После ее включения брандмауэр ОС может запросить разрешение на использование Joplin определенного порта.",
|
||||
"This will allow Joplin to run in the background. It is recommended to enable this setting so that your notes are constantly being synchronised, thus reducing the number of conflicts.": "Это позволит Joplin работать в фоновом режиме. Рекомендуется включить этот параметр, чтобы ваши заметки синхронизировались постоянно, что уменьшит количество возможных конфликтов.",
|
||||
"This will open a new screen. Save your current changes?": "Будет открыто новое окно. Сохранить текущие изменения?",
|
||||
"This will remove the notebook from your collection and you will no longer have access to its content. Do you wish to continue?": "Это удалит блокнот из вашей коллекции, и вы больше не будете иметь доступа к его содержимому. Хотите продолжить?",
|
||||
"Time format": "Формат времени",
|
||||
"title": "заголовок",
|
||||
"Title": "Заглавие",
|
||||
"To allow Joplin to synchronise with Dropbox, please follow the steps below:": "Чтобы разрешить Joplin синхронизироваться с Dropbox, выполните следующие действия:",
|
||||
"To continue, please enter your master password below.": "Для продолжения введите свой мастер-пароль ниже.",
|
||||
"To delete a tag, untag the associated notes.": "Для удаления метки открепите ее от всех связанных с ней заметок.",
|
||||
"To delete: %d": "К удалению: %d",
|
||||
"To enter command line mode, press \":\"": "Для входа в режим командной строки нажмите \":\"",
|
||||
@@ -746,20 +919,26 @@
|
||||
"To maximise/minimise the console, press \"tc\".": "Чтобы развернуть/свернуть консоль, используйте комбинацию \"tc\".",
|
||||
"To move from one pane to another, press Tab or Shift+Tab.": "Для переключения между панелями используйте Tab или Shift+Tab.",
|
||||
"To retry decryption of these items. Run `e2ee decrypt --retry-failed-items`": "Повторить попытку расшифровки элементов: `e2ee decrypt --retry-failed-items`",
|
||||
"To switch the profile, the app is going to close and you will need to restart it.": "Чтобы переключить профиль приложение закроется, и его нужно будет перезапустить.",
|
||||
"To work correctly, the app needs the following permissions. Please enable them in your phone settings, in Apps > Joplin > Permissions": "Для корректной работы приложению необходимы следующие разрешения. Пожалуйста, включите их в настройках телефона в разделе Приложения > Joplin > Разрешения",
|
||||
"to-do": "задача",
|
||||
"to-do: %s": "задание: %s",
|
||||
"Toggle comment": "Переключить комментарий",
|
||||
"Toggle development tools": "Включить инструменты разработки",
|
||||
"Toggle editor layout": "Переключить вид редактора",
|
||||
"Toggle editors": "Переключить редактор",
|
||||
"Toggle external editing": "Открыть / Закрыть внешний редактор",
|
||||
"Toggle note list": "Переключить список заметок",
|
||||
"Toggle own sort order": "Переключение собственного порядка сортировки",
|
||||
"Toggle safe mode": "Включить безопасный режим",
|
||||
"Toggle sidebar": "Переключить боковую панель",
|
||||
"Toggle sort order field": "Переключение поля порядка сортировки",
|
||||
"Token has been copied to the clipboard!": "Токен скопирован в буфер обмена!",
|
||||
"Tools": "Инструменты",
|
||||
"Total Size": "Общий размер",
|
||||
"Total: %d/%d": "Всего: %d/%d",
|
||||
"Try again": "Еще раз",
|
||||
"Try it now": "Попробуйте прямо сейчас",
|
||||
"Type `help [command]` for more information about a command; or type `help all` for the complete usage information.": "Введите `help [command]` для получения справочной информации о команде [command] или `help all` для получения полной справочной информации.",
|
||||
"Type `joplin help` for usage information.": "Введите `joplin help` для получения справочной информации.",
|
||||
"Type a note title or part of its content to jump to it. Or type # followed by a tag name, or @ followed by a notebook name. Or type : to search for commands.": "Введите название заметки, чтобы перейти к ней, либо введите #имя_метки или @имя_блокнота, либо введите : для поиска команд.",
|
||||
@@ -769,6 +948,7 @@
|
||||
"Undo": "Отменить",
|
||||
"Unknown flag: %s": "Неизвестный флаг: %s",
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version": "Обнаружен неизвестный тип файла - пожалуйста, обновите Joplin до последней версии",
|
||||
"Unordered list": "Неупорядоченный список",
|
||||
"Unpublish note": "Снять публикацию",
|
||||
"Unshare": "Отмена доступа",
|
||||
"Unshare this notebook? The recipients will no longer have access to its content.": "Снять общий доступ? Получатели больше не смогут получить доступ к этому содержимому.",
|
||||
@@ -776,11 +956,13 @@
|
||||
"Unsupported link or message: %s": "Неподдерживаемая ссылка или сообщение: %s",
|
||||
"Untitled": "Без имени",
|
||||
"Update": "Обновить",
|
||||
"Update profile": "Обновление профиля",
|
||||
"Update total sizes": "Обновление общих размеров",
|
||||
"Updated": "Обновлен",
|
||||
"updated date": "дата обновления",
|
||||
"Updated local items: %d.": "Обновлено локальных элементов: %d.",
|
||||
"Updated remote items: %d.": "Обновлено элементов в хранилище: %d.",
|
||||
"Updated: ": "Обновлено: %s",
|
||||
"Updated: ": "Обновлено: ",
|
||||
"Updated: %d.": "Обновлено: %d.",
|
||||
"Updated: %s": "Обновлено: %s",
|
||||
"Updating...": "Обновление...",
|
||||
@@ -788,14 +970,19 @@
|
||||
"Upgrade the sync target to the latest version.": "Обновить цель синхронизации до последней версии.",
|
||||
"URL": "URL",
|
||||
"Usage: %s": "Использовано: %s",
|
||||
"Use biometrics to secure access to the app": "Используйте биометрические данные для защиты доступа к приложению",
|
||||
"Use long list format. Format is ID, NOTE_COUNT (for notebook), DATE, TODO_CHECKED (for to-dos), TITLE": "Использовать расширенный формат списка. Поля формата: ID, NOTE_COUNT (для блокнотов), DATE, TODO_CHECKED (для задач), TITLE",
|
||||
"Use spell checker": "Использовать проверку орфографии",
|
||||
"Use the arrows and page up/down to scroll the lists and text areas (including this console).": "Используйте стрелки и клавиши page up/down для прокрутки списков и текстовых областей (включая эту консоль).",
|
||||
"Use the arrows to move the layout items. Press \"Escape\" to exit.": "Используйте стрелки чтобы переместить элементы программы. Нажмите Esc для выхода.",
|
||||
"Use this to rebuild the search index if there is a problem with search. It may take a long time depending on the number of notes.": "Используйте эту опцию для перестроения индекса поиска, если с поиском возникли проблемы. Процесс перестроения индекса может занять много времени в зависимости от количества заметок.",
|
||||
"Use your biometrics to secure access to your application. You can always set it up later in Settings.": "Используйте биометрические данные для защиты доступа к вашему приложению. Вы всегда можете настроить ее позже в Настройках.",
|
||||
"Used for most text in the markdown editor. If not found, a generic proportional (variable width) font is used.": "Используется для большинства текста в редакторе Mardown. Если не найден - используется подменный пропорциональный шрифт.",
|
||||
"Used where a fixed width font is needed to lay out text legibly (e.g. tables, checkboxes, code). If not found, a generic monospace (fixed width) font is used.": "Применяется там, где нужен шрифт фиксированной ширины для лучшего отображения текста (таблицы, отметки, код). Если не найден - используется подменный моноширинный шрифт.",
|
||||
"User deletions": "Удаление пользователей",
|
||||
"Users": "Пользователи",
|
||||
"Valid": "Проверено",
|
||||
"Verify your identity": "Удостоверьте свою личность",
|
||||
"View": "Вид",
|
||||
"View on map": "Посмотреть на карте",
|
||||
"View them now": "Просмотреть сейчас",
|
||||
@@ -824,6 +1011,8 @@
|
||||
"You may use the tool below to re-encrypt your data, for example if you know that some of your notes are encrypted with an obsolete encryption method.": "Вы можете использовать инструмент ниже для повторного шифрования ваших данных, например, если вы знаете, что некоторые из ваших заметок зашифрованы с использованием устаревшего метода шифрования.",
|
||||
"Your choice: ": "Ваш выбор: ",
|
||||
"Your data is going to be re-encrypted and synced again.": "Ваши данные будут повторно зашифрованы и синхронизированы снова.",
|
||||
"Your password is needed to decrypt some of your data.": "Пароль необходим для расшифровки некоторых ваших данных.",
|
||||
"Your password is needed to decrypt some of your data. Type `:e2ee decrypt` to set it.": "Пароль необходим для расшифровки некоторых ваших данных. Введите `:e2ee decrypt`, чтобы установить его.",
|
||||
"Your permission to use your camera is required.": "Необходимо ваше разрешение на использование камеры.",
|
||||
"Your version: %s": "Ваша версия: %s",
|
||||
"Zoom In": "Приблизить",
|
||||
|
@@ -61,6 +61,7 @@
|
||||
"Admin dashboard": "管理员面板",
|
||||
"Advanced options": "高级选项",
|
||||
"Advanced tools": "高级工具",
|
||||
"All data, including notes, notebooks and tags will be permanently deleted.": "包含笔记,笔记本和标签在内的所有数据将会被永久删除。",
|
||||
"All notes": "全部笔记",
|
||||
"All potential ports are in use - please report the issue at %s": "所有默认端口都已经被使用中 - 请在 %s 反馈这个问题",
|
||||
"Also displays unset and hidden config variables.": "同时显示未设置的与隐藏的配置变量。",
|
||||
@@ -159,6 +160,7 @@
|
||||
"Conflicts (attachments)": "冲突(附件)",
|
||||
"Consolidated billing": "合并计费",
|
||||
"Content provided by %s": "内容由 %s 提供",
|
||||
"Continue": "继续",
|
||||
"Convert to note": "转换为笔记",
|
||||
"Convert to todo": "转换为待办事项",
|
||||
"Copy": "复制",
|
||||
@@ -176,8 +178,10 @@
|
||||
"Could not export notes: %s": "无法导出笔记:%s",
|
||||
"Could not install plugin: %s": "无法安装插件:%s",
|
||||
"Could not respond to the invitation. Please try again, or check with the notebook owner if they are still sharing it.\n\nThe error was: \"%s\"": "无法对邀请作出回应。请再试一次,或向笔记本所有者核实他们是否仍在共享它。\n\n错误是:“%s”",
|
||||
"Could not switch profile: %s": "无法切换到配置文件:%s",
|
||||
"Could not upgrade master key: %s": "无法升级主密钥(master key):%s",
|
||||
"Could not verify the share status of this notebook - aborting. Please try again when you are connected to the internet.": "无法验证此笔记的分享状态 — 正在终止。请在连接到互联网后再次尝试。",
|
||||
"Could not verify your identify": "无法验证您的身份",
|
||||
"Create": "创建",
|
||||
"Create a notebook": "新建一个笔记本",
|
||||
"Create new profile...": "创建新的配置文件...",
|
||||
@@ -226,8 +230,11 @@
|
||||
"Delete notebook \"%s\"?\n\nAll notes and sub-notebooks within this notebook will also be deleted.": "是否删除笔记本 “%s”?\n\n所有在该笔记本内的笔记和子笔记本也将同时被删除。",
|
||||
"Delete notebook? All notes and sub-notebooks within this notebook will also be deleted.": "是否删除笔记本?所有在该笔记本内的笔记和子笔记本也将同时被删除。",
|
||||
"Delete plugin \"%s\"?": "是否删除插件 “%s”?",
|
||||
"Delete profile \"%s\"": "删除配置文件 “%s”",
|
||||
"Delete selected notes": "删除选中的笔记",
|
||||
"Delete these %d notes?": "是否删除这 %d 条笔记?",
|
||||
"Delete this invitation? The recipient will no longer have access to this shared notebook.": "是否删除该邀请?收件人将无法再次访问此共享笔记本。",
|
||||
"Delete this profile?": "是否删除此配置文件?",
|
||||
"Deleted local items: %d.": "已删除本地项目:%d。",
|
||||
"Deleted remote items: %d.": "已删除远程项目:%d。",
|
||||
"Deletes the given notebook.": "删除选定的笔记本。",
|
||||
@@ -271,11 +278,14 @@
|
||||
"Dropbox Login": "登录 Dropbox",
|
||||
"Duplicate": "复制",
|
||||
"Duplicate line": "复制行",
|
||||
"Duplicate selected notes": "复制选中的笔记",
|
||||
"Duplicates the notes matching <note> to [notebook]. If no notebook is specified the note is duplicated in the current notebook.": "复制符合 <note> 的笔记至 [notebook]。若无指定的笔记本,该笔记将被复制至当前笔记本。",
|
||||
"Edit": "编辑",
|
||||
"Edit in external editor": "在外部编辑器中编辑",
|
||||
"Edit link": "编辑链接",
|
||||
"Edit note.": "编辑笔记。",
|
||||
"Edit notebook": "编辑笔记本",
|
||||
"Edit profile": "编辑配置文件",
|
||||
"Edit profile configuration...": "编辑配置文件内的配置...",
|
||||
"Editor": "编辑器",
|
||||
"Editor font": "编辑器字体",
|
||||
@@ -356,6 +366,7 @@
|
||||
"File system": "文件系统",
|
||||
"Filter tags": "筛选标签",
|
||||
"Find and replace": "查找和替换",
|
||||
"Find: ": "查找:",
|
||||
"Firefox Extension": "Firefox 扩展插件",
|
||||
"Fix search index": "修复搜索索引",
|
||||
"Fixing search index...": "正在修复搜索索引...",
|
||||
@@ -369,6 +380,7 @@
|
||||
"For more information about End-To-End Encryption (E2EE) and advice on how to enable it please check the documentation:": "若想了解有关端到端加密 (E2EE) 的更多信息,以及如何启用它的建议,请查阅文档:",
|
||||
"For the list of keyboard shortcuts and config options, type `help keymap`": "输入 `help keymap` 来获取完整的键盘快捷键列表",
|
||||
"Force path style": "强制路径风格",
|
||||
"Formatting": "格式",
|
||||
"Forward": "前进",
|
||||
"Found: %d.": "已找到:%d 条。",
|
||||
"FTS enabled: %d": "FTS 已开启:%d",
|
||||
@@ -424,6 +436,7 @@
|
||||
"Inline Code": "内联代码",
|
||||
"Insert": "插入",
|
||||
"Insert Hyperlink": "插入超链接",
|
||||
"Insert time": "插入时间",
|
||||
"Install": "安装",
|
||||
"Install from file": "从文件安装",
|
||||
"Installed": "已安装",
|
||||
@@ -472,7 +485,10 @@
|
||||
"Letter": "信函 (Letter)",
|
||||
"Light": "明亮",
|
||||
"Lines": "行",
|
||||
"Link": "链接",
|
||||
"Link description": "链接描述",
|
||||
"Link has been copied to clipboard!": "链接已复制到剪贴板!",
|
||||
"Link text": "链接文本",
|
||||
"Links with protocol \"%s\" are not supported": "不支持 “%s” 协议的链接",
|
||||
"List item": "列表项",
|
||||
"Lists": "列表",
|
||||
@@ -490,6 +506,7 @@
|
||||
"Manage master password": "管理主密码(master password)",
|
||||
"Manage master password...": "管理主密码(master password)...",
|
||||
"Manage multiple users": "管理多个用户",
|
||||
"Manage profiles": "管理配置文件",
|
||||
"Manage your plugins": "管理您的插件",
|
||||
"Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, `status`, `decrypt-file`, and `target-status`.": "管理 E2EE 配置。命令为:`enable`、`disable`、`decrypt`、`status`、`decrypt-file` 和 `target-status`。",
|
||||
"Manual": "手动",
|
||||
@@ -583,12 +600,14 @@
|
||||
"Only one note can be printed at a time.": "一次只能打印一个笔记。",
|
||||
"Open": "打开",
|
||||
"Open %s": "打开 %s",
|
||||
"Open PDF viewer": "打开 PDF 查看器",
|
||||
"Open profile directory": "打开配置文件目录",
|
||||
"Open Sync Wizard...": "打开同步向导...",
|
||||
"Open...": "打开...",
|
||||
"Operation cancelled": "操作已取消",
|
||||
"Options": "选项",
|
||||
"Or create an account.": "创建帐号。",
|
||||
"Ordered list": "有序列表",
|
||||
"Other applications...": "其他应用...",
|
||||
"Output format: %s": "导出格式:%s",
|
||||
"Page orientation for PDF export": "PDF 导出的页面方向",
|
||||
@@ -638,9 +657,12 @@
|
||||
"Process oversized accounts": "处理超容量的账户",
|
||||
"Process user deletions": "处理用户删除",
|
||||
"Profile": "配置文件",
|
||||
"Profile name": "配置文件名称",
|
||||
"Profile name:": "配置文件:",
|
||||
"Profile Version: %s": "配置文件版本:%s",
|
||||
"Profiles": "配置文件",
|
||||
"Properties": "笔记属性",
|
||||
"Proxy enabled": "使用代理",
|
||||
"Proxy timeout (seconds)": "代理超时(秒)",
|
||||
"Proxy URL": "代理URL",
|
||||
"Public-private key pair:": "公-私钥对:",
|
||||
@@ -659,6 +681,7 @@
|
||||
"Recipients:": "收件人:",
|
||||
"Redo": "恢复",
|
||||
"Refresh": "刷新",
|
||||
"Regular expression": "正则表达式",
|
||||
"Reject": "拒绝",
|
||||
"Remove": "删除",
|
||||
"Remove tag \"%s\" from all notes?": "从所有笔记中删除标签 “%s”?",
|
||||
@@ -669,6 +692,7 @@
|
||||
"Renames the given <item> (note or notebook) to <name>.": "重命名选定的 <item> (笔记或笔记本)到 <name>。",
|
||||
"Renew token": "更新令牌",
|
||||
"Replace": "替换",
|
||||
"Replace all": "全部替换",
|
||||
"Replace with...": "替换为...",
|
||||
"Replace: ": "替换: ",
|
||||
"Reset master password": "重置主密码(master password)",
|
||||
@@ -699,6 +723,7 @@
|
||||
"Save geo-location with notes": "保存地理位置信息到笔记中",
|
||||
"Search": "搜索",
|
||||
"Search for plugins...": "搜索插件...",
|
||||
"Search for...": "搜索...",
|
||||
"Search in all the notes": "在所有笔记内搜索",
|
||||
"Search in current note": "在当前笔记内搜索",
|
||||
"Search...": "搜索...",
|
||||
@@ -728,6 +753,7 @@
|
||||
"Show all": "显示全部",
|
||||
"Show completed to-dos": "显示已完成待办事项",
|
||||
"Show disabled keys": "显示禁用的密钥",
|
||||
"Show more actions": "显示更多操作",
|
||||
"Show note counts": "显示笔记数",
|
||||
"Show sort order buttons": "显示排序方法按钮",
|
||||
"Show tray icon": "显示托盘图标",
|
||||
@@ -810,8 +836,9 @@
|
||||
"Teams": "团队",
|
||||
"Text editor command": "文本编辑器命令",
|
||||
"Thank you! Your Joplin Cloud account is now setup and ready to use.": "感谢!您的 Joplin Cloud 帐号已经设置完毕。",
|
||||
"The active profile cannot be deleted. Switch to a different profile and try again.": "无法删除正在使用的配置文件。请切换到另一配置文件后再次尝试。",
|
||||
"The app is now going to close. Please relaunch it to complete the process.": "应用将要关闭。请重新启动它以完成此过程。",
|
||||
"The application did not close properly. Would you like to start in safe mode?": "该程序没有正确关闭。你想在安全模式下启动吗?",
|
||||
"The application did not close properly. Would you like to start in safe mode?": "该程序没有正确关闭。您想在安全模式下启动吗?",
|
||||
"The application has been authorised - you may now close this browser tab.": "授权成功 - 您可以关闭此页面了。",
|
||||
"The application has been authorised!": "应用已成功授权!",
|
||||
"The application has been successfully authorised.": "已成功授予本应用程序权限。",
|
||||
@@ -821,6 +848,7 @@
|
||||
"The default admin password is insecure and has not been changed! [Change it now](%s)": "默认管理员密码不安全且尚未更改",
|
||||
"The default encryption method has been changed to a more secure one and it is recommended that you apply it to your data.": "默认的加密方法已更改为一种更安全的方法,建议您将其应用于您的数据。",
|
||||
"The default encryption method has been changed, you should re-encrypt your data.": "默认的加密方式已变更,您应当重新加密数据。",
|
||||
"The default profile cannot be deleted": "无法删除默认配置文件",
|
||||
"The editor command (may include arguments) that will be used to open a note. If none is provided it will try to auto-detect the default editor.": "该文本编辑器命令(可包含参数)将会被用于打开笔记。若未提供将尝试自动检测默认编辑器。",
|
||||
"The factor property sets how the item will grow or shrink to fit the available space in its container with respect to the other items. Thus an item with a factor of 2 will take twice as much space as an item with a factor of 1.Restart app to see changes.": "拉伸系数属性用于指定项目之间的容量比例。如,因子为 2 的项目所占容量是因子为 1 的项目的两倍。该更改在软件重启后生效。",
|
||||
"The following attachments are being watched for changes:": "下列附件发生的改动正在被监控:",
|
||||
@@ -844,6 +872,7 @@
|
||||
"The web clipper service is not enabled.": "网页剪藏未启用。",
|
||||
"Theme": "主题",
|
||||
"There are currently no notes. Create one by clicking on the (+) button.": "当前没有任何笔记。点击 (+) 按钮创建。",
|
||||
"There are unsaved changes.": "一些修改尚未保存。",
|
||||
"There is currently no notebook. Create one by clicking on \"New notebook\".": "当前没有笔记本。点击 “新建笔记本” 创建。",
|
||||
"There is no data to export.": "没有可导出的数据。",
|
||||
"There was a [conflict](%s) on the attachment below.\n\n%s": "以下附件存在一个[冲突](%s) \n\n%s",
|
||||
@@ -877,8 +906,10 @@
|
||||
"To maximise/minimise the console, press \"tc\".": "按 “tc” 最大化/最小化控制台。",
|
||||
"To move from one pane to another, press Tab or Shift+Tab.": "按 Tab 键或 Shift+Tab 组合键切换面板。",
|
||||
"To retry decryption of these items. Run `e2ee decrypt --retry-failed-items`": "运行 `e2ee decrypt —retry-failed-items` 来尝试再次解密这些项目",
|
||||
"To switch the profile, the app is going to close and you will need to restart it.": "切换配置文件后,应用程序将会自动关闭,您需要重新启动它。",
|
||||
"To work correctly, the app needs the following permissions. Please enable them in your phone settings, in Apps > Joplin > Permissions": "本应用程序需要下列权限才能正常运作。请在您的手机设置(应用 > Joplin > 权限)中启用它们",
|
||||
"to-do": "待办事项",
|
||||
"to-do: %s": "待办事项:%s",
|
||||
"Toggle comment": "添加/删除注释",
|
||||
"Toggle development tools": "打开/关闭开发者工具",
|
||||
"Toggle editor layout": "切换编辑器布局",
|
||||
@@ -903,6 +934,7 @@
|
||||
"Undo": "撤销",
|
||||
"Unknown flag: %s": "未知标记:%s",
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version": "已下载项目为未知类型,请将 Joplin 升级至最新版本",
|
||||
"Unordered list": "无序列表",
|
||||
"Unpublish note": "取消分享笔记",
|
||||
"Unshare": "取消分享",
|
||||
"Unshare this notebook? The recipients will no longer have access to its content.": "取消分享这个笔记本?收件人将无法再访问到它的内容。",
|
||||
@@ -934,6 +966,7 @@
|
||||
"User deletions": "用户删除",
|
||||
"Users": "用户",
|
||||
"Valid": "有效",
|
||||
"Verify your identity": "验证您的身份",
|
||||
"View": "视图",
|
||||
"View on map": "在地图上查看",
|
||||
"View them now": "立刻查看",
|
||||
|
@@ -10,23 +10,23 @@ export interface Notification {
|
||||
}
|
||||
|
||||
export default class Alarm extends BaseModel {
|
||||
static tableName() {
|
||||
public static tableName() {
|
||||
return 'alarms';
|
||||
}
|
||||
|
||||
static modelType() {
|
||||
public static modelType() {
|
||||
return BaseModel.TYPE_ALARM;
|
||||
}
|
||||
|
||||
static byNoteId(noteId: string) {
|
||||
public static byNoteId(noteId: string) {
|
||||
return this.modelSelectOne('SELECT * FROM alarms WHERE note_id = ?', [noteId]);
|
||||
}
|
||||
|
||||
static async deleteExpiredAlarms() {
|
||||
public static async deleteExpiredAlarms() {
|
||||
return this.db().exec('DELETE FROM alarms WHERE trigger_time <= ?', [Date.now()]);
|
||||
}
|
||||
|
||||
static async alarmIdsWithoutNotes() {
|
||||
public static async alarmIdsWithoutNotes() {
|
||||
// https://stackoverflow.com/a/4967229/561309
|
||||
const alarms = await this.db().selectAll('SELECT alarms.id FROM alarms LEFT JOIN notes ON alarms.note_id = notes.id WHERE notes.id IS NULL');
|
||||
return alarms.map((a: any) => {
|
||||
@@ -34,7 +34,7 @@ export default class Alarm extends BaseModel {
|
||||
});
|
||||
}
|
||||
|
||||
static async makeNotification(alarm: any, note: any = null): Promise<Notification> {
|
||||
public static async makeNotification(alarm: any, note: any = null): Promise<Notification> {
|
||||
if (!note) {
|
||||
note = await Note.load(alarm.note_id);
|
||||
} else if (!note.todo_due) {
|
||||
@@ -55,7 +55,7 @@ export default class Alarm extends BaseModel {
|
||||
return output;
|
||||
}
|
||||
|
||||
static async allDue() {
|
||||
public static async allDue() {
|
||||
return this.modelSelectAll('SELECT * FROM alarms WHERE trigger_time >= ?', [Date.now()]);
|
||||
}
|
||||
}
|
||||
|
@@ -63,15 +63,15 @@ export default class BaseItem extends BaseModel {
|
||||
public static SYNC_ITEM_LOCATION_REMOTE = 2;
|
||||
|
||||
|
||||
static useUuid() {
|
||||
public static useUuid() {
|
||||
return true;
|
||||
}
|
||||
|
||||
static encryptionSupported() {
|
||||
public static encryptionSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
static loadClass(className: string, classRef: any) {
|
||||
public static loadClass(className: string, classRef: any) {
|
||||
for (let i = 0; i < BaseItem.syncItemDefinitions_.length; i++) {
|
||||
if (BaseItem.syncItemDefinitions_[i].className === className) {
|
||||
BaseItem.syncItemDefinitions_[i].classRef = classRef;
|
||||
@@ -82,7 +82,7 @@ export default class BaseItem extends BaseModel {
|
||||
throw new Error(`Invalid class name: ${className}`);
|
||||
}
|
||||
|
||||
static async findUniqueItemTitle(title: string, parentId: string = null) {
|
||||
public static async findUniqueItemTitle(title: string, parentId: string = null) {
|
||||
let counter = 1;
|
||||
let titleToTry = title;
|
||||
while (true) {
|
||||
@@ -106,7 +106,7 @@ export default class BaseItem extends BaseModel {
|
||||
}
|
||||
|
||||
// Need to dynamically load the classes like this to avoid circular dependencies
|
||||
static getClass(name: string) {
|
||||
public static getClass(name: string) {
|
||||
for (let i = 0; i < BaseItem.syncItemDefinitions_.length; i++) {
|
||||
if (BaseItem.syncItemDefinitions_[i].className === name) {
|
||||
const classRef = BaseItem.syncItemDefinitions_[i].classRef;
|
||||
@@ -118,7 +118,7 @@ export default class BaseItem extends BaseModel {
|
||||
throw new Error(`Invalid class name: ${name}`);
|
||||
}
|
||||
|
||||
static getClassByItemType(itemType: ModelType) {
|
||||
public static getClassByItemType(itemType: ModelType) {
|
||||
for (let i = 0; i < BaseItem.syncItemDefinitions_.length; i++) {
|
||||
if (BaseItem.syncItemDefinitions_[i].type === itemType) {
|
||||
return BaseItem.syncItemDefinitions_[i].classRef;
|
||||
@@ -128,7 +128,7 @@ export default class BaseItem extends BaseModel {
|
||||
throw new Error(`Invalid item type: ${itemType}`);
|
||||
}
|
||||
|
||||
static async syncedCount(syncTarget: number) {
|
||||
public static async syncedCount(syncTarget: number) {
|
||||
const ItemClass = this.itemClass(this.modelType());
|
||||
const itemType = ItemClass.modelType();
|
||||
// The fact that we don't check if the item_id still exist in the corresponding item table, means
|
||||
@@ -145,7 +145,7 @@ export default class BaseItem extends BaseModel {
|
||||
else return `${itemOrId.id}.${extension}`;
|
||||
}
|
||||
|
||||
static isSystemPath(path: string) {
|
||||
public static isSystemPath(path: string) {
|
||||
// 1b175bb38bba47baac22b0b47f778113.md
|
||||
if (!path || !path.length) return false;
|
||||
let p: any = path.split('/');
|
||||
@@ -155,7 +155,7 @@ export default class BaseItem extends BaseModel {
|
||||
return p[0].length === 32 && p[1] === 'md';
|
||||
}
|
||||
|
||||
static itemClass(item: any): any {
|
||||
public static itemClass(item: any): any {
|
||||
if (!item) throw new Error('Item cannot be null');
|
||||
|
||||
if (typeof item === 'object') {
|
||||
@@ -171,7 +171,7 @@ export default class BaseItem extends BaseModel {
|
||||
}
|
||||
|
||||
// Returns the IDs of the items that have been synced at least once
|
||||
static async syncedItemIds(syncTarget: number) {
|
||||
public static async syncedItemIds(syncTarget: number) {
|
||||
if (!syncTarget) throw new Error('No syncTarget specified');
|
||||
const temp = await this.db().selectAll('SELECT item_id FROM sync_items WHERE sync_time > 0 AND sync_target = ?', [syncTarget]);
|
||||
const output = [];
|
||||
@@ -181,12 +181,12 @@ export default class BaseItem extends BaseModel {
|
||||
return output;
|
||||
}
|
||||
|
||||
static async allSyncItems(syncTarget: number) {
|
||||
public static async allSyncItems(syncTarget: number) {
|
||||
const output = await this.db().selectAll('SELECT * FROM sync_items WHERE sync_target = ?', [syncTarget]);
|
||||
return output;
|
||||
}
|
||||
|
||||
static pathToId(path: string) {
|
||||
public static pathToId(path: string) {
|
||||
const p = path.split('/');
|
||||
const s = p[p.length - 1].split('.');
|
||||
let name: any = s[0];
|
||||
@@ -195,11 +195,11 @@ export default class BaseItem extends BaseModel {
|
||||
return name[name.length - 1];
|
||||
}
|
||||
|
||||
static loadItemByPath(path: string) {
|
||||
public static loadItemByPath(path: string) {
|
||||
return this.loadItemById(this.pathToId(path));
|
||||
}
|
||||
|
||||
static async loadItemById(id: string) {
|
||||
public static async loadItemById(id: string) {
|
||||
const classes = this.syncItemClassNames();
|
||||
for (let i = 0; i < classes.length; i++) {
|
||||
const item = await this.getClass(classes[i]).load(id);
|
||||
@@ -208,7 +208,7 @@ export default class BaseItem extends BaseModel {
|
||||
return null;
|
||||
}
|
||||
|
||||
static async loadItemsByIds(ids: string[]) {
|
||||
public static async loadItemsByIds(ids: string[]) {
|
||||
if (!ids.length) return [];
|
||||
|
||||
const classes = this.syncItemClassNames();
|
||||
@@ -222,26 +222,26 @@ export default class BaseItem extends BaseModel {
|
||||
return output;
|
||||
}
|
||||
|
||||
static loadItemByField(itemType: number, field: string, value: any) {
|
||||
public static loadItemByField(itemType: number, field: string, value: any) {
|
||||
const ItemClass = this.itemClass(itemType);
|
||||
return ItemClass.loadByField(field, value);
|
||||
}
|
||||
|
||||
static loadItem(itemType: ModelType, id: string) {
|
||||
public static loadItem(itemType: ModelType, id: string) {
|
||||
const ItemClass = this.itemClass(itemType);
|
||||
return ItemClass.load(id);
|
||||
}
|
||||
|
||||
static deleteItem(itemType: ModelType, id: string) {
|
||||
public static deleteItem(itemType: ModelType, id: string) {
|
||||
const ItemClass = this.itemClass(itemType);
|
||||
return ItemClass.delete(id);
|
||||
}
|
||||
|
||||
static async delete(id: string, options: DeleteOptions = null) {
|
||||
public static async delete(id: string, options: DeleteOptions = null) {
|
||||
return this.batchDelete([id], options);
|
||||
}
|
||||
|
||||
static async batchDelete(ids: string[], options: DeleteOptions = null) {
|
||||
public static async batchDelete(ids: string[], options: DeleteOptions = null) {
|
||||
if (!options) options = {};
|
||||
let trackDeleted = true;
|
||||
if (options && options.trackDeleted !== null && options.trackDeleted !== undefined) trackDeleted = options.trackDeleted;
|
||||
@@ -287,20 +287,20 @@ export default class BaseItem extends BaseModel {
|
||||
// - Client 1 syncs with target 2 only => the note is *not* deleted from target 2 because no information
|
||||
// that it was previously deleted exist (deleted_items entry has been deleted).
|
||||
// The solution would be to permanently store the list of deleted items on each client.
|
||||
static deletedItems(syncTarget: number) {
|
||||
public static deletedItems(syncTarget: number) {
|
||||
return this.db().selectAll('SELECT * FROM deleted_items WHERE sync_target = ?', [syncTarget]);
|
||||
}
|
||||
|
||||
static async deletedItemCount(syncTarget: number) {
|
||||
public static async deletedItemCount(syncTarget: number) {
|
||||
const r = await this.db().selectOne('SELECT count(*) as total FROM deleted_items WHERE sync_target = ?', [syncTarget]);
|
||||
return r['total'];
|
||||
}
|
||||
|
||||
static remoteDeletedItem(syncTarget: number, itemId: string) {
|
||||
public static remoteDeletedItem(syncTarget: number, itemId: string) {
|
||||
return this.db().exec('DELETE FROM deleted_items WHERE item_id = ? AND sync_target = ?', [itemId, syncTarget]);
|
||||
}
|
||||
|
||||
static serialize_format(propName: string, propValue: any) {
|
||||
public static serialize_format(propName: string, propValue: any) {
|
||||
if (['created_time', 'updated_time', 'sync_time', 'user_updated_time', 'user_created_time'].indexOf(propName) >= 0) {
|
||||
if (!propValue) return '';
|
||||
propValue = `${moment.unix(propValue / 1000).utc().format('YYYY-MM-DDTHH:mm:ss.SSS')}Z`;
|
||||
@@ -322,7 +322,7 @@ export default class BaseItem extends BaseModel {
|
||||
.replace(/\r/g, '\\r');
|
||||
}
|
||||
|
||||
static unserialize_format(type: ModelType, propName: string, propValue: any) {
|
||||
public static unserialize_format(type: ModelType, propName: string, propValue: any) {
|
||||
if (propName[propName.length - 1] === '_') return propValue; // Private property
|
||||
|
||||
const ItemClass = this.itemClass(type);
|
||||
@@ -350,7 +350,7 @@ export default class BaseItem extends BaseModel {
|
||||
: propValue;
|
||||
}
|
||||
|
||||
static async serialize(item: any, shownKeys: any[] = null) {
|
||||
public static async serialize(item: any, shownKeys: any[] = null) {
|
||||
if (shownKeys === null) {
|
||||
shownKeys = this.itemClass(item).fieldNames();
|
||||
shownKeys.push('type_');
|
||||
@@ -395,12 +395,12 @@ export default class BaseItem extends BaseModel {
|
||||
return temp.join('\n\n');
|
||||
}
|
||||
|
||||
static encryptionService() {
|
||||
public static encryptionService() {
|
||||
if (!this.encryptionService_) throw new Error('BaseItem.encryptionService_ is not set!!');
|
||||
return this.encryptionService_;
|
||||
}
|
||||
|
||||
static revisionService() {
|
||||
public static revisionService() {
|
||||
if (!this.revisionService_) throw new Error('BaseItem.revisionService_ is not set!!');
|
||||
return this.revisionService_;
|
||||
}
|
||||
@@ -460,7 +460,7 @@ export default class BaseItem extends BaseModel {
|
||||
return ItemClass.serialize(reducedItem);
|
||||
}
|
||||
|
||||
static async decrypt(item: any) {
|
||||
public static async decrypt(item: any) {
|
||||
if (!item.encryption_cipher_text) throw new Error(`Item is not encrypted: ${item.id}`);
|
||||
|
||||
const ItemClass = this.itemClass(item);
|
||||
@@ -474,7 +474,7 @@ export default class BaseItem extends BaseModel {
|
||||
return ItemClass.save(plainItem, { autoTimestamp: false, changeSource: ItemChange.SOURCE_DECRYPTION });
|
||||
}
|
||||
|
||||
static async unserialize(content: string) {
|
||||
public static async unserialize(content: string) {
|
||||
const lines = content.split('\n');
|
||||
let output: any = {};
|
||||
let state = 'readingProps';
|
||||
@@ -539,7 +539,7 @@ export default class BaseItem extends BaseModel {
|
||||
};
|
||||
}
|
||||
|
||||
static async encryptedItemsCount() {
|
||||
public static async encryptedItemsCount() {
|
||||
const classNames = this.encryptableItemClassNames();
|
||||
let output = 0;
|
||||
|
||||
@@ -553,7 +553,7 @@ export default class BaseItem extends BaseModel {
|
||||
return output;
|
||||
}
|
||||
|
||||
static async hasEncryptedItems() {
|
||||
public static async hasEncryptedItems() {
|
||||
const classNames = this.encryptableItemClassNames();
|
||||
|
||||
for (let i = 0; i < classNames.length; i++) {
|
||||
@@ -567,7 +567,7 @@ export default class BaseItem extends BaseModel {
|
||||
return false;
|
||||
}
|
||||
|
||||
static async itemsThatNeedDecryption(exclusions: string[] = [], limit = 100): Promise<ItemsThatNeedDecryptionResult> {
|
||||
public static async itemsThatNeedDecryption(exclusions: string[] = [], limit = 100): Promise<ItemsThatNeedDecryptionResult> {
|
||||
const classNames = this.encryptableItemClassNames();
|
||||
|
||||
for (let i = 0; i < classNames.length; i++) {
|
||||
@@ -703,13 +703,13 @@ export default class BaseItem extends BaseModel {
|
||||
throw new Error('Unreachable');
|
||||
}
|
||||
|
||||
static syncItemClassNames(): string[] {
|
||||
public static syncItemClassNames(): string[] {
|
||||
return BaseItem.syncItemDefinitions_.map((def: any) => {
|
||||
return def.className;
|
||||
});
|
||||
}
|
||||
|
||||
static encryptableItemClassNames() {
|
||||
public static encryptableItemClassNames() {
|
||||
const temp = this.syncItemClassNames();
|
||||
const output = [];
|
||||
for (let i = 0; i < temp.length; i++) {
|
||||
@@ -725,14 +725,14 @@ export default class BaseItem extends BaseModel {
|
||||
});
|
||||
}
|
||||
|
||||
static modelTypeToClassName(type: number) {
|
||||
public static modelTypeToClassName(type: number) {
|
||||
for (let i = 0; i < BaseItem.syncItemDefinitions_.length; i++) {
|
||||
if (BaseItem.syncItemDefinitions_[i].type === type) return BaseItem.syncItemDefinitions_[i].className;
|
||||
}
|
||||
throw new Error(`Invalid type: ${type}`);
|
||||
}
|
||||
|
||||
static async syncDisabledItems(syncTargetId: number) {
|
||||
public static async syncDisabledItems(syncTargetId: number) {
|
||||
const rows = await this.db().selectAll('SELECT * FROM sync_items WHERE sync_disabled = 1 AND sync_target = ?', [syncTargetId]);
|
||||
const output = [];
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
@@ -749,7 +749,7 @@ export default class BaseItem extends BaseModel {
|
||||
return output;
|
||||
}
|
||||
|
||||
static updateSyncTimeQueries(syncTarget: number, item: any, syncTime: number, syncDisabled = false, syncDisabledReason = '', itemLocation: number = null) {
|
||||
public static updateSyncTimeQueries(syncTarget: number, item: any, syncTime: number, syncDisabled = false, syncDisabledReason = '', itemLocation: number = null) {
|
||||
const itemType = item.type_;
|
||||
const itemId = item.id;
|
||||
if (!itemType || !itemId || syncTime === undefined) throw new Error(sprintf('Invalid parameters in updateSyncTimeQueries(): %d, %s, %d', syncTarget, JSON.stringify(item), syncTime));
|
||||
@@ -768,12 +768,12 @@ export default class BaseItem extends BaseModel {
|
||||
];
|
||||
}
|
||||
|
||||
static async saveSyncTime(syncTarget: number, item: any, syncTime: number) {
|
||||
public static async saveSyncTime(syncTarget: number, item: any, syncTime: number) {
|
||||
const queries = this.updateSyncTimeQueries(syncTarget, item, syncTime);
|
||||
return this.db().transactionExecBatch(queries);
|
||||
}
|
||||
|
||||
static async saveSyncDisabled(syncTargetId: number, item: any, syncDisabledReason: string, itemLocation: number = null) {
|
||||
public static async saveSyncDisabled(syncTargetId: number, item: any, syncDisabledReason: string, itemLocation: number = null) {
|
||||
const syncTime = 'sync_time' in item ? item.sync_time : 0;
|
||||
const queries = this.updateSyncTimeQueries(syncTargetId, item, syncTime, true, syncDisabledReason, itemLocation);
|
||||
return this.db().transactionExecBatch(queries);
|
||||
@@ -786,7 +786,7 @@ export default class BaseItem extends BaseModel {
|
||||
// When an item is deleted, its associated sync_items data is not immediately deleted for
|
||||
// performance reason. So this function is used to look for these remaining sync_items and
|
||||
// delete them.
|
||||
static async deleteOrphanSyncItems() {
|
||||
public static async deleteOrphanSyncItems() {
|
||||
const classNames = this.syncItemClassNames();
|
||||
|
||||
const queries = [];
|
||||
@@ -803,13 +803,13 @@ export default class BaseItem extends BaseModel {
|
||||
await this.db().transactionExecBatch(queries);
|
||||
}
|
||||
|
||||
static displayTitle(item: any) {
|
||||
public static displayTitle(item: any) {
|
||||
if (!item) return '';
|
||||
if (item.encryption_applied) return `🔑 ${_('Encrypted')}`;
|
||||
return item.title ? item.title : _('Untitled');
|
||||
}
|
||||
|
||||
static async markAllNonEncryptedForSync() {
|
||||
public static async markAllNonEncryptedForSync() {
|
||||
const classNames = this.encryptableItemClassNames();
|
||||
|
||||
for (let i = 0; i < classNames.length; i++) {
|
||||
@@ -834,7 +834,7 @@ export default class BaseItem extends BaseModel {
|
||||
}
|
||||
}
|
||||
|
||||
static async updateShareStatus(item: BaseItemEntity, isShared: boolean) {
|
||||
public static async updateShareStatus(item: BaseItemEntity, isShared: boolean) {
|
||||
if (!item.id || !item.type_) throw new Error('Item must have an ID and a type');
|
||||
if (!!item.is_shared === !!isShared) return false;
|
||||
const ItemClass = this.getClassByItemType(item.type_);
|
||||
@@ -853,15 +853,15 @@ export default class BaseItem extends BaseModel {
|
||||
return true;
|
||||
}
|
||||
|
||||
static async forceSync(itemId: string) {
|
||||
public static async forceSync(itemId: string) {
|
||||
await this.db().exec('UPDATE sync_items SET force_sync = 1 WHERE item_id = ?', [itemId]);
|
||||
}
|
||||
|
||||
static async forceSyncAll() {
|
||||
public static async forceSyncAll() {
|
||||
await this.db().exec('UPDATE sync_items SET force_sync = 1');
|
||||
}
|
||||
|
||||
static async save(o: any, options: any = null) {
|
||||
public static async save(o: any, options: any = null) {
|
||||
if (!options) options = {};
|
||||
|
||||
if (options.userSideValidation === true) {
|
||||
@@ -871,7 +871,7 @@ export default class BaseItem extends BaseModel {
|
||||
return super.save(o, options);
|
||||
}
|
||||
|
||||
static markdownTag(itemOrId: any) {
|
||||
public static markdownTag(itemOrId: any) {
|
||||
const item = typeof itemOrId === 'object' ? itemOrId : {
|
||||
id: itemOrId,
|
||||
title: '',
|
||||
@@ -885,7 +885,7 @@ export default class BaseItem extends BaseModel {
|
||||
return output.join('');
|
||||
}
|
||||
|
||||
static isMarkdownTag(md: any) {
|
||||
public static isMarkdownTag(md: any) {
|
||||
if (!md) return false;
|
||||
return !!md.match(/^\[.*?\]\(:\/[0-9a-zA-Z]{32}\)$/);
|
||||
}
|
||||
|
@@ -19,22 +19,22 @@ export interface FolderEntityWithChildren extends FolderEntity {
|
||||
}
|
||||
|
||||
export default class Folder extends BaseItem {
|
||||
static tableName() {
|
||||
public static tableName() {
|
||||
return 'folders';
|
||||
}
|
||||
|
||||
static modelType() {
|
||||
public static modelType() {
|
||||
return BaseModel.TYPE_FOLDER;
|
||||
}
|
||||
|
||||
static newFolder(): FolderEntity {
|
||||
public static newFolder(): FolderEntity {
|
||||
return {
|
||||
id: null,
|
||||
title: '',
|
||||
};
|
||||
}
|
||||
|
||||
static fieldToLabel(field: string) {
|
||||
public static fieldToLabel(field: string) {
|
||||
const fieldsToLabels: any = {
|
||||
title: _('title'),
|
||||
last_note_user_updated_time: _('updated date'),
|
||||
@@ -43,7 +43,7 @@ export default class Folder extends BaseItem {
|
||||
return field in fieldsToLabels ? fieldsToLabels[field] : field;
|
||||
}
|
||||
|
||||
static noteIds(parentId: string, options: any = null) {
|
||||
public static noteIds(parentId: string, options: any = null) {
|
||||
options = Object.assign({}, {
|
||||
includeConflicts: false,
|
||||
}, options);
|
||||
@@ -66,17 +66,17 @@ export default class Folder extends BaseItem {
|
||||
});
|
||||
}
|
||||
|
||||
static async subFolderIds(parentId: string) {
|
||||
public static async subFolderIds(parentId: string) {
|
||||
const rows = await this.db().selectAll('SELECT id FROM folders WHERE parent_id = ?', [parentId]);
|
||||
return rows.map((r: FolderEntity) => r.id);
|
||||
}
|
||||
|
||||
static async noteCount(parentId: string) {
|
||||
public static async noteCount(parentId: string) {
|
||||
const r = await this.db().selectOne('SELECT count(*) as total FROM notes WHERE is_conflict = 0 AND parent_id = ?', [parentId]);
|
||||
return r ? r.total : 0;
|
||||
}
|
||||
|
||||
static markNotesAsConflict(parentId: string) {
|
||||
public static markNotesAsConflict(parentId: string) {
|
||||
const query = Database.updateQuery('notes', { is_conflict: 1 }, { parent_id: parentId });
|
||||
return this.db().exec(query);
|
||||
}
|
||||
@@ -108,15 +108,15 @@ export default class Folder extends BaseItem {
|
||||
});
|
||||
}
|
||||
|
||||
static conflictFolderTitle() {
|
||||
public static conflictFolderTitle() {
|
||||
return _('Conflicts');
|
||||
}
|
||||
|
||||
static conflictFolderId() {
|
||||
public static conflictFolderId() {
|
||||
return 'c04f1c7c04f1c7c04f1c7c04f1c7c04f';
|
||||
}
|
||||
|
||||
static conflictFolder(): FolderEntity {
|
||||
public static conflictFolder(): FolderEntity {
|
||||
return {
|
||||
type_: this.TYPE_FOLDER,
|
||||
id: this.conflictFolderId(),
|
||||
@@ -129,7 +129,7 @@ export default class Folder extends BaseItem {
|
||||
|
||||
// Calculates note counts for all folders and adds the note_count attribute to each folder
|
||||
// Note: this only calculates the overall number of nodes for this folder and all its descendants
|
||||
static async addNoteCounts(folders: any[], includeCompletedTodos = true) {
|
||||
public static async addNoteCounts(folders: any[], includeCompletedTodos = true) {
|
||||
const foldersById: any = {};
|
||||
for (const f of folders) {
|
||||
foldersById[f.id] = f;
|
||||
@@ -170,7 +170,7 @@ export default class Folder extends BaseItem {
|
||||
|
||||
// Folders that contain notes that have been modified recently go on top.
|
||||
// The remaining folders, that don't contain any notes are sorted by their own user_updated_time
|
||||
static async orderByLastModified(folders: FolderEntity[], dir = 'DESC') {
|
||||
public static async orderByLastModified(folders: FolderEntity[], dir = 'DESC') {
|
||||
dir = dir.toUpperCase();
|
||||
const sql = 'select parent_id, max(user_updated_time) content_updated_time from notes where parent_id != "" group by parent_id';
|
||||
const rows = await this.db().selectAll(sql);
|
||||
@@ -228,7 +228,7 @@ export default class Folder extends BaseItem {
|
||||
return output;
|
||||
}
|
||||
|
||||
static async all(options: any = null) {
|
||||
public static async all(options: any = null) {
|
||||
const output = await super.all(options);
|
||||
if (options && options.includeConflictFolder) {
|
||||
const conflictCount = await Note.conflictedCount();
|
||||
@@ -237,7 +237,7 @@ export default class Folder extends BaseItem {
|
||||
return output;
|
||||
}
|
||||
|
||||
static async childrenIds(folderId: string) {
|
||||
public static async childrenIds(folderId: string) {
|
||||
const folders = await this.db().selectAll('SELECT id FROM folders WHERE parent_id = ?', [folderId]);
|
||||
|
||||
let output: string[] = [];
|
||||
@@ -252,7 +252,7 @@ export default class Folder extends BaseItem {
|
||||
return output;
|
||||
}
|
||||
|
||||
static async expandTree(folders: FolderEntity[], parentId: string) {
|
||||
public static async expandTree(folders: FolderEntity[], parentId: string) {
|
||||
const folderPath = await this.folderPath(folders, parentId);
|
||||
folderPath.pop(); // We don't expand the leaft notebook
|
||||
|
||||
@@ -542,7 +542,7 @@ export default class Folder extends BaseItem {
|
||||
logger.debug('updateNoLongerSharedItems:', report);
|
||||
}
|
||||
|
||||
static async allAsTree(folders: FolderEntity[] = null, options: any = null) {
|
||||
public static async allAsTree(folders: FolderEntity[] = null, options: any = null) {
|
||||
const all = folders ? folders : await this.all(options);
|
||||
|
||||
if (options && options.includeNotes) {
|
||||
@@ -576,7 +576,7 @@ export default class Folder extends BaseItem {
|
||||
return getNestedChildren(all, '');
|
||||
}
|
||||
|
||||
static folderPath(folders: FolderEntity[], folderId: string) {
|
||||
public static folderPath(folders: FolderEntity[], folderId: string) {
|
||||
const idToFolders: Record<string, FolderEntity> = {};
|
||||
for (let i = 0; i < folders.length; i++) {
|
||||
idToFolders[folders[i].id] = folders[i];
|
||||
@@ -595,7 +595,7 @@ export default class Folder extends BaseItem {
|
||||
return path;
|
||||
}
|
||||
|
||||
static folderPathString(folders: FolderEntity[], folderId: string, maxTotalLength = 80) {
|
||||
public static folderPathString(folders: FolderEntity[], folderId: string, maxTotalLength = 80) {
|
||||
const path = this.folderPath(folders, folderId);
|
||||
|
||||
let currentTotalLength = 0;
|
||||
@@ -616,7 +616,7 @@ export default class Folder extends BaseItem {
|
||||
return output.join(' / ');
|
||||
}
|
||||
|
||||
static buildTree(folders: FolderEntity[]): FolderEntityWithChildren[] {
|
||||
public static buildTree(folders: FolderEntity[]): FolderEntityWithChildren[] {
|
||||
const idToFolders: Record<string, any> = {};
|
||||
for (let i = 0; i < folders.length; i++) {
|
||||
idToFolders[folders[i].id] = Object.assign({}, folders[i]);
|
||||
@@ -644,7 +644,7 @@ export default class Folder extends BaseItem {
|
||||
return rootFolders;
|
||||
}
|
||||
|
||||
static async sortFolderTree(folders: FolderEntityWithChildren[] = null) {
|
||||
public static async sortFolderTree(folders: FolderEntityWithChildren[] = null) {
|
||||
const output = folders ? folders : await this.allAsTree();
|
||||
|
||||
const sortFoldersAlphabetically = (folders: FolderEntityWithChildren[]) => {
|
||||
@@ -672,16 +672,16 @@ export default class Folder extends BaseItem {
|
||||
return output;
|
||||
}
|
||||
|
||||
static load(id: string, _options: any = null): Promise<FolderEntity> {
|
||||
public static load(id: string, _options: any = null): Promise<FolderEntity> {
|
||||
if (id === this.conflictFolderId()) return Promise.resolve(this.conflictFolder());
|
||||
return super.load(id);
|
||||
}
|
||||
|
||||
static defaultFolder() {
|
||||
public static defaultFolder() {
|
||||
return this.modelSelectOne('SELECT * FROM folders ORDER BY created_time DESC LIMIT 1');
|
||||
}
|
||||
|
||||
static async canNestUnder(folderId: string, targetFolderId: string) {
|
||||
public static async canNestUnder(folderId: string, targetFolderId: string) {
|
||||
if (folderId === targetFolderId) return false;
|
||||
|
||||
const folder = await Folder.load(folderId);
|
||||
@@ -702,7 +702,7 @@ export default class Folder extends BaseItem {
|
||||
return true;
|
||||
}
|
||||
|
||||
static async moveToFolder(folderId: string, targetFolderId: string) {
|
||||
public static async moveToFolder(folderId: string, targetFolderId: string) {
|
||||
if (!(await this.canNestUnder(folderId, targetFolderId))) throw new Error(_('Cannot move notebook to this location'));
|
||||
|
||||
// When moving a note to a different folder, the user timestamp is not updated.
|
||||
@@ -721,7 +721,7 @@ export default class Folder extends BaseItem {
|
||||
// manually creating a folder. They shouldn't be done for example when the folders
|
||||
// are being synced to avoid any strange side-effects. Technically it's possible to
|
||||
// have folders and notes with duplicate titles (or no title), or with reserved words.
|
||||
static async save(o: FolderEntity, options: any = null) {
|
||||
public static async save(o: FolderEntity, options: any = null) {
|
||||
if (!options) options = {};
|
||||
|
||||
if (options.userSideValidation === true) {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user