You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-08-24 20:19:10 +02:00
Compare commits
193 Commits
android-v1
...
android-v1
Author | SHA1 | Date | |
---|---|---|---|
|
b3475ae195 | ||
|
a72ab67473 | ||
|
90fbfec914 | ||
|
49ef023a90 | ||
|
64bfd74f18 | ||
|
655e35056e | ||
|
a13ba63ab8 | ||
|
e2e00d4c87 | ||
|
159dc44f6c | ||
|
8fe2091926 | ||
|
c362c38dc0 | ||
|
316a52bbc2 | ||
|
0de9f6f944 | ||
|
3ba021fdd9 | ||
|
04c6579f2c | ||
|
bc7bd456a7 | ||
|
6d7511efbb | ||
|
a0fb99d78f | ||
|
685a52c2c5 | ||
|
fc77419ca1 | ||
|
bab3a12e92 | ||
|
21f0b90f48 | ||
|
83682ab513 | ||
|
7b987b5a8f | ||
|
cb5aa425c8 | ||
|
dd222381dd | ||
|
d7210811f6 | ||
|
3a43cfeebf | ||
|
31e33c6628 | ||
|
875f8d6997 | ||
|
aaaf27af6e | ||
|
43624ffa75 | ||
|
996b6623f1 | ||
|
613041b806 | ||
|
ff1d01a864 | ||
|
bcbbe10bf8 | ||
|
3de0abfc84 | ||
|
133fd03469 | ||
|
4f97c5c017 | ||
|
2d8fbac58c | ||
|
95f7ac4a4a | ||
|
6a56a6ccf0 | ||
|
1eb8df9fa6 | ||
|
485b4baebb | ||
|
5590d887c9 | ||
|
d00bfa997e | ||
|
1a8590e9b9 | ||
|
5a978977df | ||
|
5d763c7e6c | ||
|
050b089e72 | ||
|
733ea4027c | ||
|
10500c78b1 | ||
|
0040cc02a2 | ||
|
74afd20f0c | ||
|
dc9bde2184 | ||
|
5243ea7eb2 | ||
|
7d93492658 | ||
|
c6b56345f5 | ||
|
8a6fe20a69 | ||
|
6bcbedd6a4 | ||
|
4c935b78f9 | ||
|
94cddda6d0 | ||
|
1924ea062c | ||
|
07e88b2eeb | ||
|
e4a08c29d7 | ||
|
d60afcaabe | ||
|
1a091460ca | ||
|
8ebaa7f6eb | ||
|
e2a64e21a2 | ||
|
78ddd22f09 | ||
|
c546b7076a | ||
|
0e2bb5d784 | ||
|
5c069c38f5 | ||
|
451b9c0ae9 | ||
|
047897621a | ||
|
52e5cec585 | ||
|
bc98b65efa | ||
|
9250e77862 | ||
|
cd69e71945 | ||
|
e705e6e990 | ||
|
4638f11c5e | ||
|
9de7c15e93 | ||
|
61736546b4 | ||
|
82b6dd23a7 | ||
|
64427f0160 | ||
|
cbf47cb9ee | ||
|
dba3e4202d | ||
|
173cd6de4d | ||
|
3d333bd8f2 | ||
|
a82f8c7dd0 | ||
|
cde079e44e | ||
|
f8d20b61ea | ||
|
d279435502 | ||
|
eb2065128e | ||
|
10e81aa476 | ||
|
3e808f05fd | ||
|
e57bfad9b1 | ||
|
5f344f07d4 | ||
|
e1b7b64e1b | ||
|
ed3970be81 | ||
|
c27861d40f | ||
|
565dfba8c9 | ||
|
553a26eb63 | ||
|
9c85bc2cd1 | ||
|
e96bc9c48a | ||
|
0d036d8183 | ||
|
e5f2a7f2f5 | ||
|
016ce3dd61 | ||
|
afb375955e | ||
|
b702b0b40c | ||
|
91ecab51c5 | ||
|
7628506926 | ||
|
4e7f7c0c9c | ||
|
863f5bcf18 | ||
|
dccd489fcc | ||
|
333c3f6369 | ||
|
808413d0bf | ||
|
440be3d920 | ||
|
9b27a4f601 | ||
|
6e36ca32b4 | ||
|
85a9c303f2 | ||
|
7e9972d99f | ||
|
771975cd35 | ||
|
356f8e580b | ||
|
68268cb35d | ||
|
8e58ed12af | ||
|
beb428b246 | ||
|
4d81caff0b | ||
|
78372c9bac | ||
|
a4db1bc671 | ||
|
8ea1c373ed | ||
|
8ef27dfcdc | ||
|
23e43c7bc1 | ||
|
edb8f4c79f | ||
|
e115fa4bb3 | ||
|
52a2daddbf | ||
|
c400142996 | ||
|
219171a18c | ||
|
d7d573d9dd | ||
|
c4b17f8919 | ||
|
da2f4b96c7 | ||
|
08af9de190 | ||
|
9e2982992a | ||
|
c03ac5c5f1 | ||
|
5934f2f08e | ||
|
f136f40fdc | ||
|
d213e4ab57 | ||
|
782aae4ddf | ||
|
4f47bd7bcd | ||
|
4f76946140 | ||
|
aa60923cbd | ||
|
7670ce32b1 | ||
|
0b98632336 | ||
|
49c998de83 | ||
|
da69d6b2c9 | ||
|
a757aefce0 | ||
|
b2129cb8c4 | ||
|
27f14c175f | ||
|
aad49c520b | ||
|
af794a16d6 | ||
|
ca7266cd69 | ||
|
fb758afc81 | ||
|
b806f0da49 | ||
|
4e3b1f3e13 | ||
|
475467c41c | ||
|
28e5039873 | ||
|
1efc6e6151 | ||
|
e280a02643 | ||
|
788dc42684 | ||
|
01f2759a62 | ||
|
9419e3af9c | ||
|
6d220005cc | ||
|
6d68e61bbd | ||
|
29582623b0 | ||
|
4571e7853a | ||
|
d6e59c5238 | ||
|
6335cbedb8 | ||
|
f3344ce05d | ||
|
155d38d24a | ||
|
412f6d8316 | ||
|
d0f3ed80e0 | ||
|
b86f3b74bd | ||
|
60054d1d8b | ||
|
c40c6428d7 | ||
|
e708ecccee | ||
|
04f991d3bf | ||
|
d5d7368ba0 | ||
|
abff929d4e | ||
|
49edc82594 | ||
|
7dd7d0ec17 | ||
|
61aaf64f95 | ||
|
da35785951 | ||
|
e394034678 |
5
.github/FUNDING.yml
vendored
Normal file
5
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: laurent22
|
||||
patreon: joplin
|
||||
custom: https://joplinapp.org/donate/
|
Binary file not shown.
Before Width: | Height: | Size: 335 KiB After Width: | Height: | Size: 336 KiB |
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 254 KiB After Width: | Height: | Size: 244 KiB |
Binary file not shown.
BIN
Assets/Screenshots/iOS/Screenshot_iPhone_Portrait_X.png
Normal file
BIN
Assets/Screenshots/iOS/Screenshot_iPhone_Portrait_X.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 249 KiB |
BIN
Assets/Screenshots/iOS/Screenshot_iPhone_Portrait_X.psd
Normal file
BIN
Assets/Screenshots/iOS/Screenshot_iPhone_Portrait_X.psd
Normal file
Binary file not shown.
5
BUILD.md
5
BUILD.md
@@ -1,7 +1,8 @@
|
||||
[](https://travis-ci.org/laurent22/joplin) [](https://ci.appveyor.com/project/laurent22/joplin)
|
||||
|
||||
# General information
|
||||
|
||||
- All the applications share the same library, which, for historical reasons, is in ReactNativeClient/lib. This library is copied to the relevant directories when building each app.
|
||||
- The translations are built by running CliClient/build-translation.sh. You normally don't need to run this if you haven't updated the translation since the compiled files are on the repository.
|
||||
|
||||
## macOS dependencies
|
||||
|
||||
@@ -58,6 +59,8 @@ If node-gyp does not works (MSBUILD: error MSB3428: Could not load the Visual C+
|
||||
|
||||
If `yarn dist` fails, it may need administrative rights.
|
||||
|
||||
If you get an `error MSB8020: The build tools for v140 cannot be found.` try to run with a different toolset version, eg `npm install --toolset=v141` (See [here](https://github.com/mapbox/node-sqlite3/issues/1124) for more info).
|
||||
|
||||
The [building\_win32\_tips on this page](./readme/building_win32_tips.md) might be helpful.
|
||||
|
||||
# Building the Mobile application
|
||||
|
@@ -1,32 +1,42 @@
|
||||
**IMPORTANT:** At the moment pull requests for new features are no longer being accepted. More info there: https://github.com/laurent22/joplin/issues/1112
|
||||
|
||||
* * *
|
||||
|
||||
# User support
|
||||
|
||||
For general discussion about Joplin, user support, software development questions, and to discuss new features, please go to the [Joplin Forum](https://discourse.joplin.cozic.net/). It is possible to login with your GitHub account.
|
||||
The [Joplin Forum](https://discourse.joplinapp.org/) is the community driven place for user support, general discussion about Joplin, problems with installation, new features and software development questions. It is possible to login with your GitHub account. Don't use the issue tracker for support questions.
|
||||
|
||||
# Reporting a bug
|
||||
|
||||
Please check first that it [has not already been reported](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is%3Aissue). Also consider [enabling debug mode](https://github.com/laurent22/joplin/blob/master/readme/debugging.md) before reporting the issue so that you can provide as much details as possible to help fix it.
|
||||
File bugs in the [Github Issue Tracker](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is%3Aissue). Please follow these guidelines:
|
||||
|
||||
If possible, **please provide a screenshot**. A screenshot showing the problem is often more useful than a paragraph describing it as it can make it immediately clear what the issue is.
|
||||
- Search existing issues first, make sure yours hasn't already been reported.
|
||||
- Consider [enabling debug mode](https://joplinapp.org/debugging/) so that you can provide as much details as possible when reporting the issue.
|
||||
- Stay on topic, but describe the issue in detail so that others can reproduce it.
|
||||
- **Provide a screenshot** if possible. A screenshot showing the problem is often more useful than a paragraph describing it.
|
||||
- For web clipper bugs, **please provide the URL causing the issue**. Sometimes the clipper works in one page but not in another so it is important to know what URL has a problem.
|
||||
|
||||
# Feature requests
|
||||
|
||||
Again, please check that it has not already been requested. If it has, simply **up-vote the issue** - the ones with the most up-votes are likely to be implemented. "+1" comments are not tracked.
|
||||
Please check that your request has not already been posted in the [Github Issue Tracker](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is%3Aissue). If it has, **up-voting the issue** increases the chances it'll be noticed and implemented in the future. "+1" comments are not tracked.
|
||||
|
||||
# Creating a pull request
|
||||
As a general rule, suggestions to _improve Joplin_ should be posted first in the [Joplin Forum](https://discourse.joplinapp.org/) for discussion.
|
||||
|
||||
- If you want to add a new feature, consider asking about it before implementing it or checking existing discussions to make sure it is within the scope of the project. That scope, due to limited resources, might be narrower than you think. As a rule of thumb **if your change is likely to involve more than 50 lines of code, you should discuss it in the forum**, just so that you don't waste your time implementing something that might not be accepted.
|
||||
Avoid listing multiple requests in one report in the [Github Issue Tracker](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is%3Aissue). One issue per request makes it easier to track and discuss it.
|
||||
|
||||
- Bug fixes have a very high chance of being accepted.
|
||||
# Contribute to the project
|
||||
|
||||
- A pull request that is relevant to the current roadmap has a very high chance of being accepted.
|
||||
## Contributing to Joplin's translation
|
||||
|
||||
Joplin is available in multiple languages thanks to the help of its users. You can help translate Joplin to your language or keep it up to date. Please read the documentation about [Localisation](https://joplinapp.org/#localisation).
|
||||
|
||||
## Contributing to Joplin's code
|
||||
|
||||
If you want to start contributing to the project's code, please follow these guidelines before creating a pull request:
|
||||
|
||||
- Bug fixes are always welcome. Start by reviewing the list of [essential issues](https://github.com/laurent22/joplin/issues?q=is%3Aissue+is%3Aopen+label%3Aessential)
|
||||
- Before adding a new feature, ask about it in the [Github Issue Tracker](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is%3Aissue) or the [Joplin Forum](https://discourse.joplinapp.org/), or check if existing discussions exist to make sure the new functionality is desired.
|
||||
- **Changes that will consist in more than 50 lines of code should be discussed the [Joplin Forum](https://discourse.joplinapp.org/)**, so that you don't spend too much time implementing something that might not be accepted.
|
||||
|
||||
Building the apps is relatively easy - please [see the build instructions](https://github.com/laurent22/joplin/blob/master/BUILD.md) for more details.
|
||||
|
||||
# Coding style
|
||||
## Coding style
|
||||
|
||||
There are only two rules, but not following them means the pull request will not be accepted (it can be accepted once the issues are fixed):
|
||||
|
||||
|
@@ -23,6 +23,7 @@ const fs = require('fs-extra');
|
||||
const { cliUtils } = require('./cli-utils.js');
|
||||
const Cache = require('lib/Cache');
|
||||
const WelcomeUtils = require('lib/WelcomeUtils');
|
||||
const RevisionService = require('lib/services/RevisionService');
|
||||
|
||||
class Application extends BaseApplication {
|
||||
|
||||
@@ -422,6 +423,8 @@ class Application extends BaseApplication {
|
||||
const tags = await Tag.allWithNotes();
|
||||
|
||||
ResourceService.runInBackground();
|
||||
|
||||
RevisionService.instance().runInBackground();
|
||||
|
||||
this.dispatch({
|
||||
type: 'TAG_UPDATE_ALL',
|
||||
|
@@ -22,6 +22,7 @@ const Tag = require('lib/models/Tag.js');
|
||||
const NoteTag = require('lib/models/NoteTag.js');
|
||||
const MasterKey = require('lib/models/MasterKey');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const Revision = require('lib/models/Revision.js');
|
||||
const { Logger } = require('lib/logger.js');
|
||||
const { FsDriverNode } = require('lib/fs-driver-node.js');
|
||||
const { shimInit } = require('lib/shim-init-node.js');
|
||||
@@ -43,6 +44,7 @@ BaseItem.loadClass('Resource', Resource);
|
||||
BaseItem.loadClass('Tag', Tag);
|
||||
BaseItem.loadClass('NoteTag', NoteTag);
|
||||
BaseItem.loadClass('MasterKey', MasterKey);
|
||||
BaseItem.loadClass('Revision', Revision);
|
||||
|
||||
Setting.setConstant('appId', 'net.cozic.joplin-cli');
|
||||
Setting.setConstant('appType', 'cli');
|
||||
|
@@ -643,6 +643,9 @@ msgstr "أخف %s"
|
||||
msgid "Quit"
|
||||
msgstr "إغلاق"
|
||||
|
||||
msgid "Close Window"
|
||||
msgstr ""
|
||||
|
||||
msgid "&Edit"
|
||||
msgstr "ت&حرير"
|
||||
|
||||
@@ -703,6 +706,9 @@ msgstr "موقع الويب و التوثيق"
|
||||
msgid "Make a donation"
|
||||
msgstr "تبرَّع"
|
||||
|
||||
msgid "Toggle development tools"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr "افتح %s"
|
||||
@@ -809,12 +815,12 @@ msgstr ""
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr "الملاحظات و الإعدادات مخزّنة في: %s"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr "استعراض..."
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr "فحص ضبط المزامنة"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr "استعراض..."
|
||||
|
||||
msgid "Apply"
|
||||
msgstr "تطبيق"
|
||||
|
||||
@@ -954,8 +960,9 @@ msgstr "لا يمكن مزامنة بعض العناصر."
|
||||
msgid "View them now"
|
||||
msgstr "عرضها الآن"
|
||||
|
||||
msgid "Some items cannot be decrypted."
|
||||
msgstr "لا يمكن فك تشفير بعض العناصر."
|
||||
#, fuzzy
|
||||
msgid "One or more master keys need a password."
|
||||
msgstr "أدخل كلمة المرور الرئيسة:"
|
||||
|
||||
msgid "Set the password"
|
||||
msgstr "ضبط كلمة المرور"
|
||||
@@ -974,9 +981,33 @@ msgstr "المكان"
|
||||
msgid "URL"
|
||||
msgstr "عنوان URL"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Note History"
|
||||
msgstr "قائمة ملاحظات"
|
||||
|
||||
msgid "Previous versions of this note"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note properties"
|
||||
msgstr "خصائص الملاحظة"
|
||||
|
||||
#, javascript-format
|
||||
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "This note has no history"
|
||||
msgstr "جرى تعديل هذه الملاحظة:"
|
||||
|
||||
msgid "Restore"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click \"%s\" to restore the note. It will be copied in the notebook named "
|
||||
"\"%s\". The current version of the note will not be replaced or modified."
|
||||
msgstr ""
|
||||
|
||||
msgid "Open..."
|
||||
msgstr "فتح..."
|
||||
|
||||
@@ -1127,8 +1158,8 @@ msgid "Decrypting items: %d/%d"
|
||||
msgstr "فك تشفير العناصر: %d/%d"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Fetching resources: %d"
|
||||
msgstr "جلب الموارد: %d"
|
||||
msgid "Fetching resources: %d/%d"
|
||||
msgstr "جلب الموارد: %d/%d"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr "فضلاً اختر أين سيتم تصدير حالة المزامنة"
|
||||
@@ -1283,6 +1314,10 @@ msgstr "قيد التقدم"
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr "المزامنة قيد التقدم بالفعل. الحال: %s"
|
||||
|
||||
msgid ""
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encrypted"
|
||||
msgstr "مشفّر"
|
||||
|
||||
@@ -1495,6 +1530,24 @@ msgstr ""
|
||||
"الوجهة المستهدفة المزامنة إليها. كل وجهة مزامنة مستهدفة قد يكون لها معلمات "
|
||||
"إضافية تكون مسماة بـ `sync.NUM.NAME` (جميعها موثقة أدناه)."
|
||||
|
||||
msgid "Attachment download behaviour"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
|
||||
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
|
||||
"the attachments are downloaded whether you open the note or not."
|
||||
msgstr ""
|
||||
|
||||
msgid "Always"
|
||||
msgstr ""
|
||||
|
||||
msgid "Manual"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto"
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr "الدليل الذي تتم المزامنة معه (المسار المطلق)"
|
||||
|
||||
@@ -1533,6 +1586,20 @@ msgstr ""
|
||||
msgid "Ignore TLS certificate errors"
|
||||
msgstr "تجاهل أخطاء شهادات TLS"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable note history"
|
||||
msgstr "تفعيل التشفير"
|
||||
|
||||
msgid "days"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%d days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Keep note history for"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr "قيمة خيار غير صحيحة: \"%s\". القيم الممكنة هي: %s."
|
||||
@@ -1602,12 +1669,19 @@ msgstr "لا يوجد بيانات للتصدير."
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr "فضلاً حدّد دفتر الملاحظات الذي ترغب استيراد الملاحظات إليه."
|
||||
|
||||
msgid "Restored Notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "العناصر التي لا يمكن مزامنتها"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s): %s"
|
||||
msgstr "%s (%s): %s"
|
||||
#, fuzzy, javascript-format
|
||||
msgid "%s (%s) could not be uploaded: %s"
|
||||
msgstr "لم نتمكن من فتح هذا الملف: %s"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Item \"%s\" could not be downloaded: %s"
|
||||
msgstr "لم نتمكن من فتح هذا الملف: %s"
|
||||
|
||||
msgid ""
|
||||
"These items will remain on the device but will not be uploaded to the sync "
|
||||
@@ -1880,9 +1954,11 @@ msgstr "ليس لديك دفاتر ملاحظات حالياً. أنشئ واح
|
||||
msgid "Welcome"
|
||||
msgstr "مرحباً"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "This note has no history"
|
||||
#~ msgstr "جرى تعديل هذه الملاحظة:"
|
||||
#~ msgid "Some items cannot be decrypted."
|
||||
#~ msgstr "لا يمكن فك تشفير بعض العناصر."
|
||||
|
||||
#~ msgid "%s (%s): %s"
|
||||
#~ msgstr "%s (%s): %s"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "The path to synchronise with when file system synchronisation is enabled. "
|
||||
|
@@ -668,6 +668,9 @@ msgstr "Amaga %s"
|
||||
msgid "Quit"
|
||||
msgstr "Surt"
|
||||
|
||||
msgid "Close Window"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "&Edit"
|
||||
msgstr "Edita"
|
||||
@@ -735,6 +738,9 @@ msgstr "Lloc web i documentació"
|
||||
msgid "Make a donation"
|
||||
msgstr "Donatius"
|
||||
|
||||
msgid "Toggle development tools"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr "Obre %s"
|
||||
@@ -844,12 +850,12 @@ msgstr ""
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr "Les notes i la configuració es desen a: %s"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr "Comprova la configuració de la sincronització"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
|
||||
@@ -992,8 +998,9 @@ msgstr "Alguns elements no s'han pogut sincronitzar."
|
||||
msgid "View them now"
|
||||
msgstr "Mostra'ls ara"
|
||||
|
||||
msgid "Some items cannot be decrypted."
|
||||
msgstr "Alguns elements no s'han pogut desxifrar."
|
||||
#, fuzzy
|
||||
msgid "One or more master keys need a password."
|
||||
msgstr "Introduïu una contrasenya mestra:"
|
||||
|
||||
msgid "Set the password"
|
||||
msgstr "Establiu la contrasenya"
|
||||
@@ -1013,9 +1020,33 @@ msgstr ""
|
||||
msgid "URL"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Note History"
|
||||
msgstr "Blocs de notes"
|
||||
|
||||
msgid "Previous versions of this note"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note properties"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "This note has no history"
|
||||
msgstr "Aquesta nota s'ha modificat:"
|
||||
|
||||
msgid "Restore"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click \"%s\" to restore the note. It will be copied in the notebook named "
|
||||
"\"%s\". The current version of the note will not be replaced or modified."
|
||||
msgstr ""
|
||||
|
||||
msgid "Open..."
|
||||
msgstr "Obre..."
|
||||
|
||||
@@ -1164,11 +1195,11 @@ msgstr "Blocs de notes"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Decrypting items: %d/%d"
|
||||
msgstr "Elements obtinguts: %d/%d."
|
||||
msgstr "Elements obtinguts: %d/%d"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Fetching resources: %d"
|
||||
msgstr "Recursos: %d."
|
||||
msgid "Fetching resources: %d/%d"
|
||||
msgstr "Recursos: %d/%d"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr "Seleccioneu on s'hauria d'exportar l'estat de la sincronització"
|
||||
@@ -1326,6 +1357,10 @@ msgstr "En progés"
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr "La sincronització ja és en procés. Estat: %s"
|
||||
|
||||
msgid ""
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encrypted"
|
||||
msgstr "Xifrat"
|
||||
|
||||
@@ -1538,6 +1573,24 @@ msgstr ""
|
||||
"L'objectiu on se sincronitzarà. Cada objectiu pot tenir paràmetres "
|
||||
"addicionals que s'anomenen com a «sync.NUM.NAME» (es documenten a sota)."
|
||||
|
||||
msgid "Attachment download behaviour"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
|
||||
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
|
||||
"the attachments are downloaded whether you open the note or not."
|
||||
msgstr ""
|
||||
|
||||
msgid "Always"
|
||||
msgstr ""
|
||||
|
||||
msgid "Manual"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto"
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr "Directori on es farà la sincronització (camí absolut)"
|
||||
|
||||
@@ -1572,6 +1625,20 @@ msgstr ""
|
||||
msgid "Ignore TLS certificate errors"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable note history"
|
||||
msgstr "Activa el xifratge"
|
||||
|
||||
msgid "days"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%d days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Keep note history for"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr "El valor de l'opció no és vàlid: «%s». Els valors possibles són: %s."
|
||||
@@ -1646,12 +1713,19 @@ msgstr "No hi ha dades per exportar."
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr "Indiqueu el bloc de notes on s'haurien d'importar les notes."
|
||||
|
||||
msgid "Restored Notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "Elements que no s'han pogut sincronitzar"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s): %s"
|
||||
msgstr "%s (%s): %s"
|
||||
#, fuzzy, javascript-format
|
||||
msgid "%s (%s) could not be uploaded: %s"
|
||||
msgstr "No s'ha pogut desar el bloc de notes: %s"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Item \"%s\" could not be downloaded: %s"
|
||||
msgstr "No s'ha pogut desar el bloc de notes: %s"
|
||||
|
||||
msgid ""
|
||||
"These items will remain on the device but will not be uploaded to the sync "
|
||||
@@ -1930,9 +2004,11 @@ msgstr ""
|
||||
msgid "Welcome"
|
||||
msgstr "Benvingut"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "This note has no history"
|
||||
#~ msgstr "Aquesta nota s'ha modificat:"
|
||||
#~ msgid "Some items cannot be decrypted."
|
||||
#~ msgstr "Alguns elements no s'han pogut desxifrar."
|
||||
|
||||
#~ msgid "%s (%s): %s"
|
||||
#~ msgstr "%s (%s): %s"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "The path to synchronise with when file system synchronisation is enabled. "
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -655,6 +655,9 @@ msgstr "Skjul %s"
|
||||
msgid "Quit"
|
||||
msgstr "Afslut"
|
||||
|
||||
msgid "Close Window"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "&Edit"
|
||||
msgstr "Ret"
|
||||
@@ -722,6 +725,9 @@ msgstr "Joplins hjemmeside og dokumentation"
|
||||
msgid "Make a donation"
|
||||
msgstr "Giv en donation"
|
||||
|
||||
msgid "Toggle development tools"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr "Åben %s"
|
||||
@@ -824,12 +830,12 @@ msgstr ""
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr "Noter og indstillinger er gemt i: %s"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr "Check synkroniserings Indstillinger"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
|
||||
@@ -973,8 +979,9 @@ msgstr "Nogle emner kan ikke synkroniseres."
|
||||
msgid "View them now"
|
||||
msgstr "Vis dem nu"
|
||||
|
||||
msgid "Some items cannot be decrypted."
|
||||
msgstr "Nogle emner kan ikke krypteres."
|
||||
#, fuzzy
|
||||
msgid "One or more master keys need a password."
|
||||
msgstr "Indtast Hoved kodeord:"
|
||||
|
||||
msgid "Set the password"
|
||||
msgstr "Indstil kodeord"
|
||||
@@ -992,9 +999,33 @@ msgstr ""
|
||||
msgid "URL"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Note History"
|
||||
msgstr "Notesbøger"
|
||||
|
||||
msgid "Previous versions of this note"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note properties"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "This note has no history"
|
||||
msgstr "Denne note er ændret:"
|
||||
|
||||
msgid "Restore"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click \"%s\" to restore the note. It will be copied in the notebook named "
|
||||
"\"%s\". The current version of the note will not be replaced or modified."
|
||||
msgstr ""
|
||||
|
||||
msgid "Open..."
|
||||
msgstr "Åben..."
|
||||
|
||||
@@ -1140,11 +1171,11 @@ msgstr "Notesbøger"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Decrypting items: %d/%d"
|
||||
msgstr "Hentede emner: %d/%d."
|
||||
msgstr "Hentede emner: %d/%d"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Fetching resources: %d"
|
||||
msgstr "Ressourcer: %d."
|
||||
msgid "Fetching resources: %d/%d"
|
||||
msgstr "Ressourcer: %d/%d"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr "Vælg hvor sync status skal eksporteres til"
|
||||
@@ -1303,6 +1334,10 @@ msgstr "I gang"
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr "Synkronisering er allerede i gang: Tilstand: %s"
|
||||
|
||||
msgid ""
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encrypted"
|
||||
msgstr "Krypteret"
|
||||
|
||||
@@ -1520,6 +1555,24 @@ msgstr ""
|
||||
"Synkroniserings mål. Hver synk. mål kan have ekstra parametre som navngives "
|
||||
"som `sync.NUM.NAME` (se dokumentation herunder)."
|
||||
|
||||
msgid "Attachment download behaviour"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
|
||||
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
|
||||
"the attachments are downloaded whether you open the note or not."
|
||||
msgstr ""
|
||||
|
||||
msgid "Always"
|
||||
msgstr ""
|
||||
|
||||
msgid "Manual"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto"
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr "Mappe der skal synkroniseres med (absolut sti)"
|
||||
|
||||
@@ -1554,6 +1607,20 @@ msgstr ""
|
||||
msgid "Ignore TLS certificate errors"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable note history"
|
||||
msgstr "Start kryptering"
|
||||
|
||||
msgid "days"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%d days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Keep note history for"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr "Ulovlig værdi: \"%s\". Mulige valg er: %s."
|
||||
@@ -1628,12 +1695,19 @@ msgstr "Der er ingen data at eksportere."
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr "Angiv hvilken notesbog, noter skal importeres til."
|
||||
|
||||
msgid "Restored Notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "Emner kan ikke synkroniseres"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s): %s"
|
||||
msgstr "%s (%s): %s"
|
||||
#, fuzzy, javascript-format
|
||||
msgid "%s (%s) could not be uploaded: %s"
|
||||
msgstr "Notesbogen kan ikke gemmes: %s"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Item \"%s\" could not be downloaded: %s"
|
||||
msgstr "Notesbogen kan ikke gemmes: %s"
|
||||
|
||||
msgid ""
|
||||
"These items will remain on the device but will not be uploaded to the sync "
|
||||
@@ -1905,9 +1979,11 @@ msgstr "Du har ingen notesbøger. Opret en ved at klikke på (+) knappen."
|
||||
msgid "Welcome"
|
||||
msgstr "Velkommen"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "This note has no history"
|
||||
#~ msgstr "Denne note er ændret:"
|
||||
#~ msgid "Some items cannot be decrypted."
|
||||
#~ msgstr "Nogle emner kan ikke krypteres."
|
||||
|
||||
#~ msgid "%s (%s): %s"
|
||||
#~ msgstr "%s (%s): %s"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "The path to synchronise with when file system synchronisation is enabled. "
|
||||
|
@@ -13,7 +13,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 2.2.1\n"
|
||||
"X-Generator: Poedit 2.2.3\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "To delete a tag, untag the associated notes."
|
||||
@@ -653,7 +653,7 @@ msgid "About Joplin"
|
||||
msgstr "Über Joplin"
|
||||
|
||||
msgid "Preferences..."
|
||||
msgstr ""
|
||||
msgstr "Einstellungen..."
|
||||
|
||||
msgid "Check for updates..."
|
||||
msgstr "Überprüfe auf Aktualisierungen..."
|
||||
@@ -674,6 +674,9 @@ msgstr "%s ausblenden"
|
||||
msgid "Quit"
|
||||
msgstr "Verlassen"
|
||||
|
||||
msgid "Close Window"
|
||||
msgstr "Fenster schließen"
|
||||
|
||||
msgid "&Edit"
|
||||
msgstr "&Bearbeiten"
|
||||
|
||||
@@ -734,6 +737,9 @@ msgstr "Webseite und Dokumentation"
|
||||
msgid "Make a donation"
|
||||
msgstr "Spenden"
|
||||
|
||||
msgid "Toggle development tools"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr "Öffne %s"
|
||||
@@ -846,12 +852,12 @@ msgstr ""
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr "Notizen und Einstellungen werden gespeichert in: %s"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr "Durchsuche..."
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr "Überprüfen der Synchronisationseinstellungen"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr "Durchsuche..."
|
||||
|
||||
msgid "Apply"
|
||||
msgstr "Anwenden"
|
||||
|
||||
@@ -995,8 +1001,9 @@ msgstr "Manche Objekte können nicht synchronisiert werden."
|
||||
msgid "View them now"
|
||||
msgstr "Zeige sie jetzt an"
|
||||
|
||||
msgid "Some items cannot be decrypted."
|
||||
msgstr "Einige Objekte können nicht entschlüsselt werden."
|
||||
#, fuzzy
|
||||
msgid "One or more master keys need a password."
|
||||
msgstr "Master-Passwort eingeben:"
|
||||
|
||||
msgid "Set the password"
|
||||
msgstr "Setze ein Passwort"
|
||||
@@ -1018,9 +1025,35 @@ msgstr "Standort"
|
||||
msgid "URL"
|
||||
msgstr "URL"
|
||||
|
||||
msgid "Note History"
|
||||
msgstr "Notizen-Verlauf"
|
||||
|
||||
msgid "Previous versions of this note"
|
||||
msgstr "Vorherige Version von dieser Notiz"
|
||||
|
||||
msgid "Note properties"
|
||||
msgstr "Notiz-Eigenschaften"
|
||||
|
||||
#, javascript-format
|
||||
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
|
||||
msgstr ""
|
||||
"Die Notiz \"%s\" wurde erfolgreich wiederhergestellt und ist im Notizbuch "
|
||||
"\"%s\" verfügbar."
|
||||
|
||||
msgid "This note has no history"
|
||||
msgstr "Diese Notiz hat keinen Verlauf"
|
||||
|
||||
msgid "Restore"
|
||||
msgstr "Wiederherstellen"
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click \"%s\" to restore the note. It will be copied in the notebook named "
|
||||
"\"%s\". The current version of the note will not be replaced or modified."
|
||||
msgstr ""
|
||||
"Klicke \"%s\" um die Notiz wiederherzustellen. Sie wird in das Notizbuch \"%s"
|
||||
"\" kopiert. Die aktuelle Version der Notiz wird nicht ersetzt oder verändert."
|
||||
|
||||
msgid "Open..."
|
||||
msgstr "Öffne..."
|
||||
|
||||
@@ -1173,8 +1206,8 @@ msgid "Decrypting items: %d/%d"
|
||||
msgstr "Entschlüsselte Objekte: %d/%d"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Fetching resources: %d"
|
||||
msgstr "Ressourcen abrufen: %d"
|
||||
msgid "Fetching resources: %d/%d"
|
||||
msgstr "Ressourcen abrufen: %d/%d"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr ""
|
||||
@@ -1214,9 +1247,11 @@ msgid ""
|
||||
"Type a note title to jump to it. Or type # followed by a tag name, or @ "
|
||||
"followed by a notebook name."
|
||||
msgstr ""
|
||||
"Tippe einen Notiz Titel um hinzuspringen. Oder tippe # gefolgt von einem "
|
||||
"Schlagwort, oder @ gefolgt von einem Notizbuch-Namen."
|
||||
|
||||
msgid "Goto Anything..."
|
||||
msgstr ""
|
||||
msgstr "Gehe zu..."
|
||||
|
||||
#, javascript-format
|
||||
msgid "Usage: %s"
|
||||
@@ -1330,6 +1365,10 @@ msgstr "In Bearbeitung"
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr "Synchronisation ist bereits im Gange. Status: %s"
|
||||
|
||||
msgid ""
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version"
|
||||
msgstr "Unbekannter Eintrags-Typ heruntergeladen - bitte aktualisiere Joplin"
|
||||
|
||||
msgid "Encrypted"
|
||||
msgstr "Verschlüsselt"
|
||||
|
||||
@@ -1425,45 +1464,41 @@ msgstr "Fokussiere Inhalt"
|
||||
msgid "When creating a new note:"
|
||||
msgstr "Wenn eine neue Notiz erstellt wird:"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable soft breaks"
|
||||
msgstr "Inhaltsverzeichnis"
|
||||
msgstr "Aktiviere weiche Zeilenumbrüche"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable math expressions"
|
||||
msgstr "Verschlüsselung aktivieren"
|
||||
msgstr "Aktiviere mathematische Ausdrücke"
|
||||
|
||||
msgid "Enable ==mark== syntax"
|
||||
msgstr ""
|
||||
msgstr "Aktiviere ==mark== Syntax"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable footnotes"
|
||||
msgstr "Inhaltsverzeichnis"
|
||||
msgstr "Aktiviere Fußnoten"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable table of contents extension"
|
||||
msgstr "Inhaltsverzeichnis"
|
||||
msgstr "Aktiviere Inhaltsverzeichnis Erweiterung"
|
||||
|
||||
msgid "Enable ~sub~ syntax"
|
||||
msgstr ""
|
||||
msgstr "Aktiviere ~sub~ Syntax"
|
||||
|
||||
msgid "Enable ^sup^ syntax"
|
||||
msgstr ""
|
||||
msgstr "Aktiviere ^sup^ Syntax"
|
||||
|
||||
msgid "Enable deflist syntax"
|
||||
msgstr ""
|
||||
msgstr "Aktiviere deflist Syntax"
|
||||
|
||||
msgid "Enable abbreviation syntax"
|
||||
msgstr ""
|
||||
msgstr "Aktiviere abbreviation Syntax"
|
||||
|
||||
msgid "Enable markdown emoji"
|
||||
msgstr ""
|
||||
msgstr "Aktiviere markdown emoji"
|
||||
|
||||
msgid "Enable ++insert++ syntax"
|
||||
msgstr ""
|
||||
msgstr "Aktiviere ++insert++ Syntax"
|
||||
|
||||
msgid "Enable multimarkdown table extension"
|
||||
msgstr ""
|
||||
msgstr "Aktiviere multimarkdown Tabellen Erweiterung"
|
||||
|
||||
msgid "Show tray icon"
|
||||
msgstr "Zeige Tray-Icon"
|
||||
@@ -1549,6 +1584,28 @@ msgstr ""
|
||||
"kann zusätzliche Parameter haben, die als `sync.NUM.NAME` (alle unten "
|
||||
"dokumentiert) bezeichnet werden."
|
||||
|
||||
msgid "Attachment download behaviour"
|
||||
msgstr "Verhalten für das Herunterladen von Anhängen"
|
||||
|
||||
msgid ""
|
||||
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
|
||||
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
|
||||
"the attachments are downloaded whether you open the note or not."
|
||||
msgstr ""
|
||||
"Im \"Manuell\" Modus werden die Anhänge nur heruntergeladen wenn Du auf sie "
|
||||
"klickst. Bei \"Automatisch\" werden sie heruntergeladen sobald die Notiz "
|
||||
"geöffnet wird. Bei \"Immer\" werden die Anhänge heruntergeladen egal ob die "
|
||||
"Notiz geöffnet wird oder nicht."
|
||||
|
||||
msgid "Always"
|
||||
msgstr "Immer"
|
||||
|
||||
msgid "Manual"
|
||||
msgstr "Manuell"
|
||||
|
||||
msgid "Auto"
|
||||
msgstr "Automatisch"
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr "Verzeichnis mit dem synchronisiert werden soll (absoluter Pfad)"
|
||||
|
||||
@@ -1588,6 +1645,19 @@ msgstr ""
|
||||
msgid "Ignore TLS certificate errors"
|
||||
msgstr "Ignoriere TLS-Zertifikatfehler"
|
||||
|
||||
msgid "Enable note history"
|
||||
msgstr "Aktiviere Notizen-Verlauf"
|
||||
|
||||
msgid "days"
|
||||
msgstr "Tage"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%d days"
|
||||
msgstr "%d Tage"
|
||||
|
||||
msgid "Keep note history for"
|
||||
msgstr "Speicher Notizen-Verlauf für"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr "Ungültiger Optionswert: \"%s\". Mögliche Werte sind: %s."
|
||||
@@ -1605,7 +1675,7 @@ msgid "Note"
|
||||
msgstr "Notiz"
|
||||
|
||||
msgid "Plugins"
|
||||
msgstr ""
|
||||
msgstr "Zusatzprogramme"
|
||||
|
||||
# 'Applikation' or 'Anwendung' - both translations are correct.
|
||||
msgid "Application"
|
||||
@@ -1660,12 +1730,19 @@ msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr ""
|
||||
"Bitte wähle aus, wohin der Synchronisations-Status exportiert werden soll."
|
||||
|
||||
msgid "Restored Notes"
|
||||
msgstr "Wiederhergestellte Notizen"
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "Objekte können nicht synchronisiert werden"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s): %s"
|
||||
msgstr "%s (%s): %s"
|
||||
msgid "%s (%s) could not be uploaded: %s"
|
||||
msgstr "%s (%s) konnte nicht hochgeladen werden: %s"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Item \"%s\" could not be downloaded: %s"
|
||||
msgstr "Eintrag \"%s\" konnte nicht heruntergeladen werden: %s"
|
||||
|
||||
msgid ""
|
||||
"These items will remain on the device but will not be uploaded to the sync "
|
||||
@@ -1950,9 +2027,11 @@ msgstr ""
|
||||
msgid "Welcome"
|
||||
msgstr "Willkommen"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "This note has no history"
|
||||
#~ msgstr "Diese Notiz wurde verändert:"
|
||||
#~ msgid "Some items cannot be decrypted."
|
||||
#~ msgstr "Einige Objekte können nicht entschlüsselt werden."
|
||||
|
||||
#~ msgid "%s (%s): %s"
|
||||
#~ msgstr "%s (%s): %s"
|
||||
|
||||
#~ msgid "Table of contents"
|
||||
#~ msgstr "Inhaltsverzeichnis"
|
||||
|
@@ -583,6 +583,9 @@ msgstr ""
|
||||
msgid "Quit"
|
||||
msgstr ""
|
||||
|
||||
msgid "Close Window"
|
||||
msgstr ""
|
||||
|
||||
msgid "&Edit"
|
||||
msgstr ""
|
||||
|
||||
@@ -643,6 +646,9 @@ msgstr ""
|
||||
msgid "Make a donation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Toggle development tools"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr ""
|
||||
@@ -744,10 +750,10 @@ msgstr ""
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Browse..."
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr ""
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Apply"
|
||||
@@ -875,7 +881,7 @@ msgstr ""
|
||||
msgid "View them now"
|
||||
msgstr ""
|
||||
|
||||
msgid "Some items cannot be decrypted."
|
||||
msgid "One or more master keys need a password."
|
||||
msgstr ""
|
||||
|
||||
msgid "Set the password"
|
||||
@@ -894,9 +900,31 @@ msgstr ""
|
||||
msgid "URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note History"
|
||||
msgstr ""
|
||||
|
||||
msgid "Previous versions of this note"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note properties"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
|
||||
msgstr ""
|
||||
|
||||
msgid "This note has no history"
|
||||
msgstr ""
|
||||
|
||||
msgid "Restore"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click \"%s\" to restore the note. It will be copied in the notebook named "
|
||||
"\"%s\". The current version of the note will not be replaced or modified."
|
||||
msgstr ""
|
||||
|
||||
msgid "Open..."
|
||||
msgstr ""
|
||||
|
||||
@@ -1043,7 +1071,7 @@ msgid "Decrypting items: %d/%d"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Fetching resources: %d"
|
||||
msgid "Fetching resources: %d/%d"
|
||||
msgstr ""
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
@@ -1191,6 +1219,10 @@ msgstr ""
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encrypted"
|
||||
msgstr ""
|
||||
|
||||
@@ -1391,6 +1423,24 @@ msgid ""
|
||||
"which are named as `sync.NUM.NAME` (all documented below)."
|
||||
msgstr ""
|
||||
|
||||
msgid "Attachment download behaviour"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
|
||||
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
|
||||
"the attachments are downloaded whether you open the note or not."
|
||||
msgstr ""
|
||||
|
||||
msgid "Always"
|
||||
msgstr ""
|
||||
|
||||
msgid "Manual"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto"
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr ""
|
||||
|
||||
@@ -1425,6 +1475,19 @@ msgstr ""
|
||||
msgid "Ignore TLS certificate errors"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable note history"
|
||||
msgstr ""
|
||||
|
||||
msgid "days"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%d days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Keep note history for"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr ""
|
||||
@@ -1492,11 +1555,18 @@ msgstr ""
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr ""
|
||||
|
||||
msgid "Restored Notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s): %s"
|
||||
msgid "%s (%s) could not be uploaded: %s"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Item \"%s\" could not be downloaded: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
|
@@ -589,6 +589,9 @@ msgstr ""
|
||||
msgid "Quit"
|
||||
msgstr ""
|
||||
|
||||
msgid "Close Window"
|
||||
msgstr ""
|
||||
|
||||
msgid "&Edit"
|
||||
msgstr ""
|
||||
|
||||
@@ -649,6 +652,9 @@ msgstr ""
|
||||
msgid "Make a donation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Toggle development tools"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr ""
|
||||
@@ -752,12 +758,12 @@ msgstr ""
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr "Check synchronization configuration"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
|
||||
@@ -893,7 +899,7 @@ msgstr "Some items cannot be synchronized."
|
||||
msgid "View them now"
|
||||
msgstr ""
|
||||
|
||||
msgid "Some items cannot be decrypted."
|
||||
msgid "One or more master keys need a password."
|
||||
msgstr ""
|
||||
|
||||
msgid "Set the password"
|
||||
@@ -912,9 +918,31 @@ msgstr ""
|
||||
msgid "URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note History"
|
||||
msgstr ""
|
||||
|
||||
msgid "Previous versions of this note"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note properties"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
|
||||
msgstr ""
|
||||
|
||||
msgid "This note has no history"
|
||||
msgstr ""
|
||||
|
||||
msgid "Restore"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click \"%s\" to restore the note. It will be copied in the notebook named "
|
||||
"\"%s\". The current version of the note will not be replaced or modified."
|
||||
msgstr ""
|
||||
|
||||
msgid "Open..."
|
||||
msgstr ""
|
||||
|
||||
@@ -1061,7 +1089,7 @@ msgid "Decrypting items: %d/%d"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Fetching resources: %d"
|
||||
msgid "Fetching resources: %d/%d"
|
||||
msgstr ""
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
@@ -1211,6 +1239,10 @@ msgstr ""
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr "Synchronization is already in progress. State: %s"
|
||||
|
||||
msgid ""
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encrypted"
|
||||
msgstr ""
|
||||
|
||||
@@ -1416,6 +1448,24 @@ msgstr ""
|
||||
"The target to synchonize to. Each sync target may have additional parameters "
|
||||
"which are named as `sync.NUM.NAME` (all documented below)."
|
||||
|
||||
msgid "Attachment download behaviour"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
|
||||
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
|
||||
"the attachments are downloaded whether you open the note or not."
|
||||
msgstr ""
|
||||
|
||||
msgid "Always"
|
||||
msgstr ""
|
||||
|
||||
msgid "Manual"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto"
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr "Directory to synchronize with (absolute path)"
|
||||
|
||||
@@ -1450,6 +1500,19 @@ msgstr ""
|
||||
msgid "Ignore TLS certificate errors"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable note history"
|
||||
msgstr ""
|
||||
|
||||
msgid "days"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%d days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Keep note history for"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr ""
|
||||
@@ -1517,11 +1580,18 @@ msgstr ""
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr ""
|
||||
|
||||
msgid "Restored Notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "Items that cannot be synchronized"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s): %s"
|
||||
msgid "%s (%s) could not be uploaded: %s"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Item \"%s\" could not be downloaded: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
|
@@ -1,9 +1,11 @@
|
||||
# Joplin translation to Spanish (Spain)
|
||||
# Copyright (C) 2017 Lucas Vieites
|
||||
# Copyright (C) 2019 Andros Fenollosa
|
||||
# Copyright (C) 2019 Germán Martín
|
||||
# This file is distributed under the same license as the Joplin-CLI package.
|
||||
# Lucas Vieites <lucas.vieites@gmail.com>, 2017.
|
||||
# Andros Fenollosa <andros@fenollosa.email>, 2019.
|
||||
# Germán Martín <gmag11@gmail.com>, 2019.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
@@ -15,7 +17,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 2.2.1\n"
|
||||
"X-Generator: Poedit 2.2.3\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
@@ -132,8 +134,8 @@ msgid ""
|
||||
"Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, "
|
||||
"`status`, `decrypt-file` and `target-status`."
|
||||
msgstr ""
|
||||
"Maneja la configuración E2EE. Comandos disponibles `enable`, `disable`, "
|
||||
"`decrypt`, `status` y `target-status`."
|
||||
"Maneja la configuración E2EE. Los comandos disponibles son: `enable`, "
|
||||
"`disable`, `decrypt`, `status` y `target-status`."
|
||||
|
||||
msgid "Enter master password:"
|
||||
msgstr "Introduzca la contraseña maestra:"
|
||||
@@ -641,7 +643,7 @@ msgid "About Joplin"
|
||||
msgstr "Acerca de Joplin"
|
||||
|
||||
msgid "Preferences..."
|
||||
msgstr ""
|
||||
msgstr "Preferencias..."
|
||||
|
||||
msgid "Check for updates..."
|
||||
msgstr "Comprobar actualizaciones..."
|
||||
@@ -662,6 +664,9 @@ msgstr "Oculta %s"
|
||||
msgid "Quit"
|
||||
msgstr "Salir"
|
||||
|
||||
msgid "Close Window"
|
||||
msgstr "Cerrar Ventana"
|
||||
|
||||
msgid "&Edit"
|
||||
msgstr "&Editar"
|
||||
|
||||
@@ -722,6 +727,9 @@ msgstr "Sitio web y documentación"
|
||||
msgid "Make a donation"
|
||||
msgstr "Hacer una donación"
|
||||
|
||||
msgid "Toggle development tools"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr "Abrir %s"
|
||||
@@ -825,17 +833,19 @@ msgid ""
|
||||
"This authorisation token is only needed to allow third-party applications to "
|
||||
"access Joplin."
|
||||
msgstr ""
|
||||
"Este token de autorización se necesita solamente para permitir a "
|
||||
"aplicaciones de terceros acceder a Joplin."
|
||||
|
||||
#, javascript-format
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr "Las notas y los ajustes se guardan en: %s"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr "Explorar..."
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr "Comprobar sincronización"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr "Explorar..."
|
||||
|
||||
msgid "Apply"
|
||||
msgstr "Aplicar"
|
||||
|
||||
@@ -976,8 +986,9 @@ msgstr "No se han podido sincronizar algunos de los elementos."
|
||||
msgid "View them now"
|
||||
msgstr "Verlos ahora"
|
||||
|
||||
msgid "Some items cannot be decrypted."
|
||||
msgstr "No se han podido descifrar algunos elementos."
|
||||
#, fuzzy
|
||||
msgid "One or more master keys need a password."
|
||||
msgstr "Introduzca la contraseña maestra:"
|
||||
|
||||
msgid "Set the password"
|
||||
msgstr "Establecer la contraseña"
|
||||
@@ -995,9 +1006,35 @@ msgstr "Localización"
|
||||
msgid "URL"
|
||||
msgstr "URL"
|
||||
|
||||
msgid "Note History"
|
||||
msgstr ""
|
||||
"Historial\n"
|
||||
" de notas"
|
||||
|
||||
msgid "Previous versions of this note"
|
||||
msgstr "Versiones anteriores de esta nota"
|
||||
|
||||
msgid "Note properties"
|
||||
msgstr "Propiedades de nota"
|
||||
|
||||
#, javascript-format
|
||||
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
|
||||
msgstr "La nota \"%s\" se ha restaurado correctamente en la libreta \"%s\"."
|
||||
|
||||
msgid "This note has no history"
|
||||
msgstr "Esta nota no dispone de historial"
|
||||
|
||||
msgid "Restore"
|
||||
msgstr "Restaurar"
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click \"%s\" to restore the note. It will be copied in the notebook named "
|
||||
"\"%s\". The current version of the note will not be replaced or modified."
|
||||
msgstr ""
|
||||
"Pulse \"%s\" para restaurar la nota. Se copiará en la libreta \"%s\". La "
|
||||
"versión actual de la nota no se reemplazará ni se modificará."
|
||||
|
||||
msgid "Open..."
|
||||
msgstr "Abrir..."
|
||||
|
||||
@@ -1149,8 +1186,8 @@ msgid "Decrypting items: %d/%d"
|
||||
msgstr "Descifrando elementos: %d/%d"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Fetching resources: %d"
|
||||
msgstr "Obteniendo refuersos: %d"
|
||||
msgid "Fetching resources: %d/%d"
|
||||
msgstr "Obteniendo refuersos: %d/%d"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr "Seleccione a dónde se debería exportar el estado de sincronización"
|
||||
@@ -1189,9 +1226,11 @@ msgid ""
|
||||
"Type a note title to jump to it. Or type # followed by a tag name, or @ "
|
||||
"followed by a notebook name."
|
||||
msgstr ""
|
||||
"Escriba el título de una nota para abrirla. O escriba # seguido de un nombre "
|
||||
"de etiqueta, o @ seguido del nombre de una libreta."
|
||||
|
||||
msgid "Goto Anything..."
|
||||
msgstr ""
|
||||
msgstr "Ir a..."
|
||||
|
||||
#, javascript-format
|
||||
msgid "Usage: %s"
|
||||
@@ -1305,6 +1344,12 @@ msgstr "En progreso"
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr "La sincronización ya está en progreso. Estado: %s"
|
||||
|
||||
msgid ""
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version"
|
||||
msgstr ""
|
||||
"Se ha descargado un elemento de tipo desconocido - actualice Joplin a la "
|
||||
"última versión"
|
||||
|
||||
msgid "Encrypted"
|
||||
msgstr "Cifrado"
|
||||
|
||||
@@ -1382,7 +1427,6 @@ msgstr "Ordenar notas por"
|
||||
msgid "Reverse sort order"
|
||||
msgstr "Invierte el orden"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Sort notebooks by"
|
||||
msgstr "Ordenar notas por"
|
||||
|
||||
@@ -1402,41 +1446,40 @@ msgid "When creating a new note:"
|
||||
msgstr "Cuando se crear una nota nueva:"
|
||||
|
||||
msgid "Enable soft breaks"
|
||||
msgstr ""
|
||||
msgstr "Activar saltos de línea"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable math expressions"
|
||||
msgstr "Habilitar cifrado"
|
||||
msgstr "Habilitar expresiones matemáticas"
|
||||
|
||||
msgid "Enable ==mark== syntax"
|
||||
msgstr ""
|
||||
msgstr "Activar sintaxis ==mark=="
|
||||
|
||||
msgid "Enable footnotes"
|
||||
msgstr ""
|
||||
msgstr "Activar notas al pie"
|
||||
|
||||
msgid "Enable table of contents extension"
|
||||
msgstr ""
|
||||
msgstr "Activar extensión de tabla de contenidos"
|
||||
|
||||
msgid "Enable ~sub~ syntax"
|
||||
msgstr ""
|
||||
msgstr "Activar sintaxis ~sub~"
|
||||
|
||||
msgid "Enable ^sup^ syntax"
|
||||
msgstr ""
|
||||
msgstr "Activar sintaxis ^sup^"
|
||||
|
||||
msgid "Enable deflist syntax"
|
||||
msgstr ""
|
||||
msgstr "Activar sintaxis Deflist"
|
||||
|
||||
msgid "Enable abbreviation syntax"
|
||||
msgstr ""
|
||||
msgstr "Activar sintaxis de abreviaturas"
|
||||
|
||||
msgid "Enable markdown emoji"
|
||||
msgstr ""
|
||||
msgstr "Activa sintaxis de emojis"
|
||||
|
||||
msgid "Enable ++insert++ syntax"
|
||||
msgstr ""
|
||||
msgstr "Activar sintaxis ++insert++"
|
||||
|
||||
msgid "Enable multimarkdown table extension"
|
||||
msgstr ""
|
||||
msgstr "Activar extensión de tablas multimarkdown"
|
||||
|
||||
msgid "Show tray icon"
|
||||
msgstr "Mostrar icono en la bandeja"
|
||||
@@ -1523,6 +1566,28 @@ msgstr ""
|
||||
"tener parámetros adicionales los cuales son llamados como `sync.NUM.NAME` "
|
||||
"(todos abajo documentados)."
|
||||
|
||||
msgid "Attachment download behaviour"
|
||||
msgstr "Comportamiento de descarga de adjuntos"
|
||||
|
||||
msgid ""
|
||||
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
|
||||
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
|
||||
"the attachments are downloaded whether you open the note or not."
|
||||
msgstr ""
|
||||
"En modo \"Manual\", los archivos adjuntos se descargan solamente al pulsar "
|
||||
"sobre ellos. En modo \"Auto\", se descargan cuando se abre la nota. En modo "
|
||||
"\"Always\", se descargan todos los archivos adjuntos aunque la nota no esté "
|
||||
"abierta."
|
||||
|
||||
msgid "Always"
|
||||
msgstr "Siempre"
|
||||
|
||||
msgid "Manual"
|
||||
msgstr "Manual"
|
||||
|
||||
msgid "Auto"
|
||||
msgstr "Automático"
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr "Directorio con el que sincronizarse (ruta completa)"
|
||||
|
||||
@@ -1562,6 +1627,19 @@ msgstr ""
|
||||
msgid "Ignore TLS certificate errors"
|
||||
msgstr "Ignorar errores en certificados TLS"
|
||||
|
||||
msgid "Enable note history"
|
||||
msgstr "Habilitar historial de notas"
|
||||
|
||||
msgid "days"
|
||||
msgstr "días"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%d days"
|
||||
msgstr "%d días"
|
||||
|
||||
msgid "Keep note history for"
|
||||
msgstr "Mantener historial de la nota durante"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr "Opción inválida: «%s». Los valores posibles son: %s."
|
||||
@@ -1579,7 +1657,7 @@ msgid "Note"
|
||||
msgstr "Nota"
|
||||
|
||||
msgid "Plugins"
|
||||
msgstr ""
|
||||
msgstr "Plugins"
|
||||
|
||||
msgid "Application"
|
||||
msgstr "Aplicación"
|
||||
@@ -1631,12 +1709,19 @@ msgstr "No hay datos para exportar."
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr "Por favor especifique la libreta donde las notas deben ser importadas."
|
||||
|
||||
msgid "Restored Notes"
|
||||
msgstr "Notas restauradas"
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "Elementos que no se pueden sincronizar"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s): %s"
|
||||
msgstr "%s (%s): %s"
|
||||
#, fuzzy, javascript-format
|
||||
msgid "%s (%s) could not be uploaded: %s"
|
||||
msgstr "No se ha podido subir el archivo %s (%s): %s"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Item \"%s\" could not be downloaded: %s"
|
||||
msgstr "No se ha podido abrir el archivo %s (%s): %s"
|
||||
|
||||
msgid ""
|
||||
"These items will remain on the device but will not be uploaded to the sync "
|
||||
@@ -1917,9 +2002,11 @@ msgstr ""
|
||||
msgid "Welcome"
|
||||
msgstr "Bienvenido"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "This note has no history"
|
||||
#~ msgstr "Esta nota ha sido modificada:"
|
||||
#~ msgid "Some items cannot be decrypted."
|
||||
#~ msgstr "No se han podido descifrar algunos elementos."
|
||||
|
||||
#~ msgid "%s (%s): %s"
|
||||
#~ msgstr "%s (%s): %s"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "The path to synchronise with when file system synchronisation is enabled. "
|
||||
|
@@ -663,6 +663,9 @@ msgstr ""
|
||||
msgid "Quit"
|
||||
msgstr "Irten"
|
||||
|
||||
msgid "Close Window"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "&Edit"
|
||||
msgstr "Editatu"
|
||||
@@ -729,6 +732,9 @@ msgstr "Web orria eta dokumentazioa (en)"
|
||||
msgid "Make a donation"
|
||||
msgstr "Web orria eta dokumentazioa (en)"
|
||||
|
||||
msgid "Toggle development tools"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr "On %s: %s"
|
||||
@@ -832,13 +838,13 @@ msgstr ""
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr "Oharrak eta ezarpenak hemen daude gordeta: %s"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr "Sinkronizazioa utzi"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
|
||||
@@ -981,8 +987,9 @@ msgstr "Zenbait item ezin dira sinkronizatu."
|
||||
msgid "View them now"
|
||||
msgstr "Ikusi hori orain"
|
||||
|
||||
msgid "Some items cannot be decrypted."
|
||||
msgstr "Zenbait item ezin dira deszifratu."
|
||||
#, fuzzy
|
||||
msgid "One or more master keys need a password."
|
||||
msgstr "Sartu pasahitz nagusia:"
|
||||
|
||||
msgid "Set the password"
|
||||
msgstr "Ezarri pasahitza"
|
||||
@@ -1000,9 +1007,33 @@ msgstr ""
|
||||
msgid "URL"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Note History"
|
||||
msgstr "Koadernoak"
|
||||
|
||||
msgid "Previous versions of this note"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note properties"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "This note has no history"
|
||||
msgstr "Ohar hau mugitua izan da:"
|
||||
|
||||
msgid "Restore"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click \"%s\" to restore the note. It will be copied in the notebook named "
|
||||
"\"%s\". The current version of the note will not be replaced or modified."
|
||||
msgstr ""
|
||||
|
||||
msgid "Open..."
|
||||
msgstr ""
|
||||
|
||||
@@ -1152,11 +1183,11 @@ msgstr "Koadernoak"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Decrypting items: %d/%d"
|
||||
msgstr "Itemak eskuratuta: %d%d."
|
||||
msgstr "Itemak eskuratuta: %d/%d"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Fetching resources: %d"
|
||||
msgstr "Baliabideak: %d."
|
||||
msgid "Fetching resources: %d/%d"
|
||||
msgstr "Baliabideak: %d/%d"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
@@ -1316,6 +1347,10 @@ msgstr ""
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr "Sinkronizazioa hasita dago. Egoera: %s"
|
||||
|
||||
msgid ""
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encrypted"
|
||||
msgstr "Zifratuta"
|
||||
|
||||
@@ -1537,6 +1572,24 @@ msgstr ""
|
||||
"parametro gehigarriak, horrela izendatuta `sync.NUM.NAME` (dena beherago "
|
||||
"dokumentatuta)."
|
||||
|
||||
msgid "Attachment download behaviour"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
|
||||
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
|
||||
"the attachments are downloaded whether you open the note or not."
|
||||
msgstr ""
|
||||
|
||||
msgid "Always"
|
||||
msgstr ""
|
||||
|
||||
msgid "Manual"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto"
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr "Sinkronizatzeko direktorioa (bide-izena osorik)"
|
||||
|
||||
@@ -1574,6 +1627,20 @@ msgstr ""
|
||||
msgid "Ignore TLS certificate errors"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable note history"
|
||||
msgstr "Zifratua gaitu"
|
||||
|
||||
msgid "days"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%d days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Keep note history for"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr "Balio aukera baliogabea: \"%s\". Litezkeen balioak: %s."
|
||||
@@ -1649,12 +1716,19 @@ msgstr ""
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr "Aukeratu nora esportatu sinkronizazioaren egoera, mesedez"
|
||||
|
||||
msgid "Restored Notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "Itemok ezin sinkronizatu"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s): %s"
|
||||
msgstr "%s (%s): %s"
|
||||
#, fuzzy, javascript-format
|
||||
msgid "%s (%s) could not be uploaded: %s"
|
||||
msgstr "Koadernoa ezin gorde daiteke: %s"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Item \"%s\" could not be downloaded: %s"
|
||||
msgstr "Koadernoa ezin gorde daiteke: %s"
|
||||
|
||||
msgid ""
|
||||
"These items will remain on the device but will not be uploaded to the sync "
|
||||
@@ -1926,9 +2000,11 @@ msgstr "Oraindik ez duzu koadernorik. Sortu bat (+) botoian sakatuta."
|
||||
msgid "Welcome"
|
||||
msgstr "Ongi etorri!"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "This note has no history"
|
||||
#~ msgstr "Ohar hau mugitua izan da:"
|
||||
#~ msgid "Some items cannot be decrypted."
|
||||
#~ msgstr "Zenbait item ezin dira deszifratu."
|
||||
|
||||
#~ msgid "%s (%s): %s"
|
||||
#~ msgstr "%s (%s): %s"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "The path to synchronise with when file system synchronisation is enabled. "
|
||||
|
1854
CliClient/locales/fa.po
Normal file
1854
CliClient/locales/fa.po
Normal file
File diff suppressed because it is too large
Load Diff
@@ -659,6 +659,9 @@ msgstr "Cacher %s"
|
||||
msgid "Quit"
|
||||
msgstr "Quitter"
|
||||
|
||||
msgid "Close Window"
|
||||
msgstr "Fermer la fenêtre"
|
||||
|
||||
msgid "&Edit"
|
||||
msgstr "&Édition"
|
||||
|
||||
@@ -719,6 +722,9 @@ msgstr "Documentation en ligne"
|
||||
msgid "Make a donation"
|
||||
msgstr "Faire un don"
|
||||
|
||||
msgid "Toggle development tools"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr "Ouvrir %s"
|
||||
@@ -830,12 +836,12 @@ msgstr ""
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr "Les notes et paramètres se trouve dans : %s"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr "Parcourir…"
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr "Vérifier config synchronisation"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr "Parcourir…"
|
||||
|
||||
msgid "Apply"
|
||||
msgstr "Appliquer"
|
||||
|
||||
@@ -980,8 +986,9 @@ msgstr "Certains objets ne peuvent être synchronisés."
|
||||
msgid "View them now"
|
||||
msgstr "Les voir maintenant"
|
||||
|
||||
msgid "Some items cannot be decrypted."
|
||||
msgstr "Certains objets ne peuvent être déchiffrés."
|
||||
#, fuzzy
|
||||
msgid "One or more master keys need a password."
|
||||
msgstr "Entrer le mot de passe maître :"
|
||||
|
||||
msgid "Set the password"
|
||||
msgstr "Définir le mot de passe"
|
||||
@@ -1002,9 +1009,34 @@ msgstr "Lieu"
|
||||
msgid "URL"
|
||||
msgstr "URL"
|
||||
|
||||
msgid "Note History"
|
||||
msgstr "Historique des notes"
|
||||
|
||||
msgid "Previous versions of this note"
|
||||
msgstr "Versions précédentes de cette note"
|
||||
|
||||
msgid "Note properties"
|
||||
msgstr "Propriétés de la note"
|
||||
|
||||
#, javascript-format
|
||||
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
|
||||
msgstr "La note \"%s\" a été restaurée dans le carnet \"%s\"."
|
||||
|
||||
msgid "This note has no history"
|
||||
msgstr "Cette note n'a pas d'historique"
|
||||
|
||||
msgid "Restore"
|
||||
msgstr "Restaurer"
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click \"%s\" to restore the note. It will be copied in the notebook named "
|
||||
"\"%s\". The current version of the note will not be replaced or modified."
|
||||
msgstr ""
|
||||
"Cliquez sur \"%s\" pour restaurer cette note. Elle sera copiée dans le "
|
||||
"carnet \"%s\". La version actuelle de la note ne sera pas replacée ou "
|
||||
"modifiée."
|
||||
|
||||
msgid "Open..."
|
||||
msgstr "Ouvrir..."
|
||||
|
||||
@@ -1158,8 +1190,8 @@ msgid "Decrypting items: %d/%d"
|
||||
msgstr "Déchiffrement des objets : %d/%d"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Fetching resources: %d"
|
||||
msgstr "Tél. ressources : %d"
|
||||
msgid "Fetching resources: %d/%d"
|
||||
msgstr "Tél. ressources : %d/%d"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr ""
|
||||
@@ -1317,6 +1349,10 @@ msgstr "En cours"
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr "La synchronisation est déjà en cours. État : %s"
|
||||
|
||||
msgid ""
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version"
|
||||
msgstr "Objet inconnu téléchargé - veuillez mettre Joplin à jour"
|
||||
|
||||
msgid "Encrypted"
|
||||
msgstr "Chiffré"
|
||||
|
||||
@@ -1531,6 +1567,28 @@ msgstr ""
|
||||
"avoir des paramètres supplémentaires sous le nom `sync.NUM.NOM` (documentés "
|
||||
"ci-dessous)."
|
||||
|
||||
msgid "Attachment download behaviour"
|
||||
msgstr "Téléchargement des ressources"
|
||||
|
||||
msgid ""
|
||||
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
|
||||
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
|
||||
"the attachments are downloaded whether you open the note or not."
|
||||
msgstr ""
|
||||
"En mode \"manuel\", les ressources sont téléchargées uniquement lorsque vous "
|
||||
"cliquez dessus. En mode \"auto\", elle sont téléchargée lorsque vous ouvrez "
|
||||
"la note. En mode \"toujours\", toutes les ressources sont téléchargées, que "
|
||||
"vous ayez ouvert la note ou pas."
|
||||
|
||||
msgid "Always"
|
||||
msgstr "Toujours"
|
||||
|
||||
msgid "Manual"
|
||||
msgstr "Manuel"
|
||||
|
||||
msgid "Auto"
|
||||
msgstr "Auto"
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr "Répertoire avec lequel synchroniser (chemin absolu)"
|
||||
|
||||
@@ -1570,6 +1628,19 @@ msgstr ""
|
||||
msgid "Ignore TLS certificate errors"
|
||||
msgstr "Ignorer les erreurs de certificats TLS"
|
||||
|
||||
msgid "Enable note history"
|
||||
msgstr "Activer l'historique des notes"
|
||||
|
||||
msgid "days"
|
||||
msgstr "jours"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%d days"
|
||||
msgstr "%d jours"
|
||||
|
||||
msgid "Keep note history for"
|
||||
msgstr "Garder l'historique des notes pour"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr "Option invalide: \"%s\". Les valeurs possibles sont : %s."
|
||||
@@ -1639,12 +1710,19 @@ msgstr "Il n'y a pas de données à exporter."
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr "Veuillez sélectionner le carnet où les notes doivent être importées."
|
||||
|
||||
msgid "Restored Notes"
|
||||
msgstr "Notes restaurées"
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "Objets qui ne peuvent pas être synchronisés"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s): %s"
|
||||
msgstr "%s (%s) : %s"
|
||||
msgid "%s (%s) could not be uploaded: %s"
|
||||
msgstr "%s (%s) n'a pas pu être envoyé : %s"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Item \"%s\" could not be downloaded: %s"
|
||||
msgstr "L'objet \"%s\" n'a pas pu être téléchargé : %s"
|
||||
|
||||
msgid ""
|
||||
"These items will remain on the device but will not be uploaded to the sync "
|
||||
@@ -1927,9 +2005,11 @@ msgstr ""
|
||||
msgid "Welcome"
|
||||
msgstr "Bienvenue"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "This note has no history"
|
||||
#~ msgstr "Cette note a été modifiée :"
|
||||
#~ msgid "Some items cannot be decrypted."
|
||||
#~ msgstr "Certains objets ne peuvent être déchiffrés."
|
||||
|
||||
#~ msgid "%s (%s): %s"
|
||||
#~ msgstr "%s (%s) : %s"
|
||||
|
||||
#~ msgid "Table of contents"
|
||||
#~ msgstr "Table des matières"
|
||||
|
@@ -655,6 +655,9 @@ msgstr "Ocultar %s"
|
||||
msgid "Quit"
|
||||
msgstr "Saír"
|
||||
|
||||
msgid "Close Window"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "&Edit"
|
||||
msgstr "Edtar"
|
||||
@@ -722,6 +725,9 @@ msgstr "Sitio web e documentación"
|
||||
msgid "Make a donation"
|
||||
msgstr "Doar"
|
||||
|
||||
msgid "Toggle development tools"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr "Abrir %s"
|
||||
@@ -824,12 +830,12 @@ msgstr ""
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr "As notas e axustes gardáronse en: %s"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr "Comprobar a configuración da sincronización"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
|
||||
@@ -970,8 +976,9 @@ msgstr "Non é posíbel sincronizar algúns elementos."
|
||||
msgid "View them now"
|
||||
msgstr "Visualizar agora"
|
||||
|
||||
msgid "Some items cannot be decrypted."
|
||||
msgstr "Non é posíbel descifrar algúns elementos."
|
||||
#, fuzzy
|
||||
msgid "One or more master keys need a password."
|
||||
msgstr "Introducir contrasinal mestre:"
|
||||
|
||||
msgid "Set the password"
|
||||
msgstr "Estabelecer un contrasinal"
|
||||
@@ -989,9 +996,33 @@ msgstr ""
|
||||
msgid "URL"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Note History"
|
||||
msgstr "Cadernos"
|
||||
|
||||
msgid "Previous versions of this note"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note properties"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "This note has no history"
|
||||
msgstr "Esta nota foi modificada:"
|
||||
|
||||
msgid "Restore"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click \"%s\" to restore the note. It will be copied in the notebook named "
|
||||
"\"%s\". The current version of the note will not be replaced or modified."
|
||||
msgstr ""
|
||||
|
||||
msgid "Open..."
|
||||
msgstr "Abrir…"
|
||||
|
||||
@@ -1140,11 +1171,11 @@ msgstr "Cadernos"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Decrypting items: %d/%d"
|
||||
msgstr "Elementos obtidos: %d/%d."
|
||||
msgstr "Elementos obtidos: %d/%d"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Fetching resources: %d"
|
||||
msgstr "Recursos: %d."
|
||||
msgid "Fetching resources: %d/%d"
|
||||
msgstr "Recursos: %d/%d"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr "Seleccione onde exportar o estado da sincronización"
|
||||
@@ -1303,6 +1334,10 @@ msgstr "En proceso"
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr "A sincronización xa está en proceso. Estado: %s"
|
||||
|
||||
msgid ""
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encrypted"
|
||||
msgstr "Cifrado"
|
||||
|
||||
@@ -1520,6 +1555,24 @@ msgstr ""
|
||||
"Destino co que sincronizar. Cada destino da sincronización pode ter "
|
||||
"parámetros adicionais que se chaman «sync.NUM.NAME» (documentados arriba)."
|
||||
|
||||
msgid "Attachment download behaviour"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
|
||||
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
|
||||
"the attachments are downloaded whether you open the note or not."
|
||||
msgstr ""
|
||||
|
||||
msgid "Always"
|
||||
msgstr ""
|
||||
|
||||
msgid "Manual"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto"
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr "Cartafol a sincronizar con (ruta absoluta)"
|
||||
|
||||
@@ -1554,6 +1607,20 @@ msgstr ""
|
||||
msgid "Ignore TLS certificate errors"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable note history"
|
||||
msgstr "Activar cifrado"
|
||||
|
||||
msgid "days"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%d days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Keep note history for"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr "Valor incorrecto de opción: «%s». Os valores posíbeis son: %s."
|
||||
@@ -1628,12 +1695,19 @@ msgstr "Non hai datos para exportar."
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr "Indique o caderno ao que importar as notas."
|
||||
|
||||
msgid "Restored Notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "Elementos que non se poden sincronizar"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s): %s"
|
||||
msgstr "%s (%s): %s"
|
||||
#, fuzzy, javascript-format
|
||||
msgid "%s (%s) could not be uploaded: %s"
|
||||
msgstr "Non foi posíbel gardar o caderno: %s"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Item \"%s\" could not be downloaded: %s"
|
||||
msgstr "Non foi posíbel gardar o caderno: %s"
|
||||
|
||||
msgid ""
|
||||
"These items will remain on the device but will not be uploaded to the sync "
|
||||
@@ -1905,9 +1979,11 @@ msgstr "Non ten cadernos actualmente. Cree un premendo no botón (+)."
|
||||
msgid "Welcome"
|
||||
msgstr "Benvido/a"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "This note has no history"
|
||||
#~ msgstr "Esta nota foi modificada:"
|
||||
#~ msgid "Some items cannot be decrypted."
|
||||
#~ msgstr "Non é posíbel descifrar algúns elementos."
|
||||
|
||||
#~ msgid "%s (%s): %s"
|
||||
#~ msgstr "%s (%s): %s"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "The path to synchronise with when file system synchronisation is enabled. "
|
||||
|
@@ -660,6 +660,9 @@ msgstr ""
|
||||
msgid "Quit"
|
||||
msgstr "Izađi"
|
||||
|
||||
msgid "Close Window"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "&Edit"
|
||||
msgstr "Uredi"
|
||||
@@ -727,6 +730,9 @@ msgstr "Website i dokumentacija"
|
||||
msgid "Make a donation"
|
||||
msgstr "Website i dokumentacija"
|
||||
|
||||
msgid "Toggle development tools"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr "On %s: %s"
|
||||
@@ -830,13 +836,13 @@ msgstr ""
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr "Bilješke i postavke su pohranjene u: %s"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr "Prekini sinkronizaciju"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
|
||||
@@ -967,9 +973,8 @@ msgstr "Neke stavke se ne mogu sinkronizirati."
|
||||
msgid "View them now"
|
||||
msgstr "Pogledaj ih sada"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Some items cannot be decrypted."
|
||||
msgstr "Neke stavke se ne mogu sinkronizirati."
|
||||
msgid "One or more master keys need a password."
|
||||
msgstr ""
|
||||
|
||||
msgid "Set the password"
|
||||
msgstr ""
|
||||
@@ -987,9 +992,33 @@ msgstr ""
|
||||
msgid "URL"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Note History"
|
||||
msgstr "Bilježnice"
|
||||
|
||||
msgid "Previous versions of this note"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note properties"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "This note has no history"
|
||||
msgstr "Bilješka je promijenjena:"
|
||||
|
||||
msgid "Restore"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click \"%s\" to restore the note. It will be copied in the notebook named "
|
||||
"\"%s\". The current version of the note will not be replaced or modified."
|
||||
msgstr ""
|
||||
|
||||
msgid "Open..."
|
||||
msgstr ""
|
||||
|
||||
@@ -1139,11 +1168,11 @@ msgstr "Bilježnice"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Decrypting items: %d/%d"
|
||||
msgstr "Stvorene lokalne stavke: %d."
|
||||
msgstr "Stvorene lokalne stavke: %d/%d"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Fetching resources: %d"
|
||||
msgstr "Resursi: %d."
|
||||
msgid "Fetching resources: %d/%d"
|
||||
msgstr "Resursi: %d/%d"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr "Odaberi lokaciju za izvoz statusa sinkronizacije"
|
||||
@@ -1299,6 +1328,10 @@ msgstr ""
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr "Sinkronizacija je već u toku. Stanje: %s"
|
||||
|
||||
msgid ""
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encrypted"
|
||||
msgstr ""
|
||||
|
||||
@@ -1516,6 +1549,24 @@ msgid ""
|
||||
"which are named as `sync.NUM.NAME` (all documented below)."
|
||||
msgstr ""
|
||||
|
||||
msgid "Attachment download behaviour"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
|
||||
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
|
||||
"the attachments are downloaded whether you open the note or not."
|
||||
msgstr ""
|
||||
|
||||
msgid "Always"
|
||||
msgstr ""
|
||||
|
||||
msgid "Manual"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto"
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr "Direktorij za sinkroniziranje (apsolutna putanja)"
|
||||
|
||||
@@ -1550,6 +1601,20 @@ msgstr ""
|
||||
msgid "Ignore TLS certificate errors"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable note history"
|
||||
msgstr "Bilješka je promijenjena:"
|
||||
|
||||
msgid "days"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%d days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Keep note history for"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr "Nevažeća vrijednost: \"%s\". Moguće vrijednosti su: %s."
|
||||
@@ -1625,12 +1690,19 @@ msgstr ""
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr "Odaberi lokaciju za izvoz statusa sinkronizacije"
|
||||
|
||||
msgid "Restored Notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "Stavke koje se ne mogu sinkronizirati"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "%s (%s): %s"
|
||||
msgstr "%s %s (%s)"
|
||||
msgid "%s (%s) could not be uploaded: %s"
|
||||
msgstr "Bilježnicu nije moguće snimiti: %s"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Item \"%s\" could not be downloaded: %s"
|
||||
msgstr "Bilježnicu nije moguće snimiti: %s"
|
||||
|
||||
msgid ""
|
||||
"These items will remain on the device but will not be uploaded to the sync "
|
||||
@@ -1902,8 +1974,12 @@ msgid "Welcome"
|
||||
msgstr "Dobro došli"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "This note has no history"
|
||||
#~ msgstr "Bilješka je promijenjena:"
|
||||
#~ msgid "Some items cannot be decrypted."
|
||||
#~ msgstr "Neke stavke se ne mogu sinkronizirati."
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "%s (%s): %s"
|
||||
#~ msgstr "%s %s (%s)"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "The path to synchronise with when file system synchronisation is enabled. "
|
||||
|
@@ -13,7 +13,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 2.2\n"
|
||||
"X-Generator: Poedit 2.2.3\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "To delete a tag, untag the associated notes."
|
||||
@@ -591,18 +591,16 @@ msgid "Exporting to \"%s\" as \"%s\" format. Please wait..."
|
||||
msgstr "Esportazione da \"%s\" come formato \"%s\". Si prega di attendere..."
|
||||
|
||||
msgid "Sidebar"
|
||||
msgstr ""
|
||||
msgstr "Barra laterale"
|
||||
|
||||
msgid "Note list"
|
||||
msgstr ""
|
||||
msgstr "Lista delle note"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Note title"
|
||||
msgstr "Titolo del Taccuino:"
|
||||
msgstr "Titolo della nota"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Note body"
|
||||
msgstr "Taccuini"
|
||||
msgstr "Corpo della nota"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
|
||||
@@ -639,18 +637,17 @@ msgstr "Opzioni Web Clipper"
|
||||
msgid "%s %s (%s, %s)"
|
||||
msgstr "%s %s (%s, %s)"
|
||||
|
||||
#, fuzzy
|
||||
msgid "&File"
|
||||
msgstr "File"
|
||||
msgstr "&File"
|
||||
|
||||
msgid "About Joplin"
|
||||
msgstr "Informazione su Joplin"
|
||||
|
||||
msgid "Preferences..."
|
||||
msgstr ""
|
||||
msgstr "Preferenze..."
|
||||
|
||||
msgid "Check for updates..."
|
||||
msgstr "Controlla aggiornamenti ..."
|
||||
msgstr "Controlla aggiornamenti..."
|
||||
|
||||
msgid "Import"
|
||||
msgstr "Importa"
|
||||
@@ -668,9 +665,11 @@ msgstr "Nascondi %s"
|
||||
msgid "Quit"
|
||||
msgstr "Esci"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Close Window"
|
||||
msgstr "Chiudi finestra"
|
||||
|
||||
msgid "&Edit"
|
||||
msgstr "Modifica"
|
||||
msgstr "&Modifica"
|
||||
|
||||
msgid "Copy"
|
||||
msgstr "Copia"
|
||||
@@ -691,7 +690,7 @@ msgid "Italic"
|
||||
msgstr "Corsivo"
|
||||
|
||||
msgid "Link"
|
||||
msgstr ""
|
||||
msgstr "Link"
|
||||
|
||||
msgid "Insert Date Time"
|
||||
msgstr "Inserisci data e ora"
|
||||
@@ -705,9 +704,8 @@ msgstr "Cerca in tutte le note"
|
||||
msgid "Search in current note"
|
||||
msgstr "Cerca nella nota corrente"
|
||||
|
||||
#, fuzzy
|
||||
msgid "&View"
|
||||
msgstr "Vista"
|
||||
msgstr "&Vista"
|
||||
|
||||
msgid "Toggle sidebar"
|
||||
msgstr "Attiva / disattiva barra laterale"
|
||||
@@ -715,17 +713,14 @@ msgstr "Attiva / disattiva barra laterale"
|
||||
msgid "Toggle editor layout"
|
||||
msgstr "Attiva / disattiva il layout dell'editor"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Focus"
|
||||
msgstr "Focus sul testo"
|
||||
msgstr "Focus"
|
||||
|
||||
#, fuzzy
|
||||
msgid "&Tools"
|
||||
msgstr "Strumenti"
|
||||
msgstr "&Strumenti"
|
||||
|
||||
#, fuzzy
|
||||
msgid "&Help"
|
||||
msgstr "Aiuto"
|
||||
msgstr "&Aiuto"
|
||||
|
||||
msgid "Website and documentation"
|
||||
msgstr "Sito web e documentazione"
|
||||
@@ -733,6 +728,9 @@ msgstr "Sito web e documentazione"
|
||||
msgid "Make a donation"
|
||||
msgstr "Fai una donazione"
|
||||
|
||||
msgid "Toggle development tools"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr "Aprire %s"
|
||||
@@ -758,11 +756,11 @@ msgstr "È disponibile un aggiornamento, vuoi scaricarlo ora?"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Your version: %s"
|
||||
msgstr ""
|
||||
msgstr "Tua versione: %s"
|
||||
|
||||
#, javascript-format
|
||||
msgid "New version: %s"
|
||||
msgstr ""
|
||||
msgstr "Nuova versione: %s"
|
||||
|
||||
msgid "Yes"
|
||||
msgstr "Sì"
|
||||
@@ -771,7 +769,7 @@ msgid "No"
|
||||
msgstr "No"
|
||||
|
||||
msgid "Token has been copied to the clipboard!"
|
||||
msgstr ""
|
||||
msgstr "Il token è stato copiato negli appunti!"
|
||||
|
||||
msgid "The web clipper service is enabled and set to auto-start."
|
||||
msgstr ""
|
||||
@@ -822,31 +820,32 @@ msgstr "Passaggio 2: installare l'estensione"
|
||||
msgid "Download and install the relevant extension for your browser:"
|
||||
msgstr "Scarica e installa l'estensione adatta per il tuo browser:"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Advanced options"
|
||||
msgstr "Mostra opzioni avanzate"
|
||||
msgstr "Opzioni avanzate"
|
||||
|
||||
msgid "Authorisation token:"
|
||||
msgstr ""
|
||||
msgstr "Token autorizzativo:"
|
||||
|
||||
msgid "Copy token"
|
||||
msgstr ""
|
||||
msgstr "Copia token"
|
||||
|
||||
msgid ""
|
||||
"This authorisation token is only needed to allow third-party applications to "
|
||||
"access Joplin."
|
||||
msgstr ""
|
||||
"Il token autorizzativo è necessario solo per permettere ad applicazioni di "
|
||||
"terze parti di accedere a Joplin."
|
||||
|
||||
#, javascript-format
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr "Le note e le impostazioni sono memorizzate in: %s"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr "Controlla la configurazione della sincronizzazione"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr "Naviga..."
|
||||
|
||||
msgid "Apply"
|
||||
msgstr "Applica"
|
||||
|
||||
@@ -942,9 +941,8 @@ msgstr "Stato"
|
||||
msgid "Encryption is:"
|
||||
msgstr "La crittografia è:"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Usage"
|
||||
msgstr "Uso: %s"
|
||||
msgstr "Uso"
|
||||
|
||||
msgid "Back"
|
||||
msgstr "Indietro"
|
||||
@@ -990,8 +988,9 @@ msgstr "Alcuni elementi non possono essere sincronizzati."
|
||||
msgid "View them now"
|
||||
msgstr "Mostrali ora"
|
||||
|
||||
msgid "Some items cannot be decrypted."
|
||||
msgstr "Alcuni elementi non possono essere decodificati."
|
||||
#, fuzzy
|
||||
msgid "One or more master keys need a password."
|
||||
msgstr "Inserisci password principale:"
|
||||
|
||||
msgid "Set the password"
|
||||
msgstr "Imposta la password"
|
||||
@@ -1006,16 +1005,41 @@ msgstr ""
|
||||
"\"."
|
||||
|
||||
msgid "Location"
|
||||
msgstr ""
|
||||
msgstr "Posizione"
|
||||
|
||||
msgid "URL"
|
||||
msgstr ""
|
||||
msgstr "URL"
|
||||
|
||||
msgid "Note History"
|
||||
msgstr "Cronologia nota"
|
||||
|
||||
msgid "Previous versions of this note"
|
||||
msgstr "Versione precedente di questa nota"
|
||||
|
||||
msgid "Note properties"
|
||||
msgstr "Proprietà della nota"
|
||||
|
||||
#, javascript-format
|
||||
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
|
||||
msgstr "La nota \"%s\" è stata ripristinata con successo nel taccuino \"%s\"."
|
||||
|
||||
msgid "This note has no history"
|
||||
msgstr "Questa nota non ha cronologia"
|
||||
|
||||
msgid "Restore"
|
||||
msgstr "Ripristina"
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click \"%s\" to restore the note. It will be copied in the notebook named "
|
||||
"\"%s\". The current version of the note will not be replaced or modified."
|
||||
msgstr ""
|
||||
"Clicca \"%s\" per ripristinare la nota. Verrà copiata nel taccuino "
|
||||
"denominato \"%s\". La versione corrente della nota non verrà sostituita o "
|
||||
"modificata."
|
||||
|
||||
msgid "Open..."
|
||||
msgstr "Apri ..."
|
||||
msgstr "Apri..."
|
||||
|
||||
#, javascript-format
|
||||
msgid "This file could not be opened: %s"
|
||||
@@ -1046,7 +1070,7 @@ msgstr ""
|
||||
"modificare la nota."
|
||||
|
||||
msgid "Only one note can be printed or exported to PDF at a time."
|
||||
msgstr ""
|
||||
msgstr "Solo una nota alla volta può essere stampata o esportata come PDF."
|
||||
|
||||
msgid "strong text"
|
||||
msgstr "Testo grasseto"
|
||||
@@ -1100,12 +1124,11 @@ msgstr "Fare clic per interrompere le modifiche esterne"
|
||||
msgid "Watching..."
|
||||
msgstr "Osservare..."
|
||||
|
||||
#, fuzzy
|
||||
msgid "to-do"
|
||||
msgstr "Nuovo \"Cose-da-fare\""
|
||||
msgstr "\"Cose-da-fare\""
|
||||
|
||||
msgid "note"
|
||||
msgstr "Nota"
|
||||
msgstr "nota"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Creating new %s..."
|
||||
@@ -1135,18 +1158,19 @@ msgstr "Opzioni di crittografia"
|
||||
msgid "Clipper Options"
|
||||
msgstr "Opzioni Clipper"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Delete notebook \"%s\"?\n"
|
||||
"\n"
|
||||
"All notes and sub-notebooks within this notebook will also be deleted."
|
||||
msgstr ""
|
||||
"Eliminare taccuino? Anche tutte le note e cartelle di questo taccuino "
|
||||
"saranno cancellati."
|
||||
"Eliminare taccuino \"%s\"?\n"
|
||||
"\n"
|
||||
"Anche tutte le note e cartelle di questo taccuino saranno cancellati."
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
#, javascript-format
|
||||
msgid "Remove tag \"%s\" from all notes?"
|
||||
msgstr "Rimuovere questa etichetta da tutte le note?"
|
||||
msgstr "Rimuovere l’etichetta \"%s\" da tutte le note?"
|
||||
|
||||
msgid "Remove this search from the sidebar?"
|
||||
msgstr "Rimuovere questa ricerca dalla barra laterale?"
|
||||
@@ -1165,8 +1189,8 @@ msgid "Decrypting items: %d/%d"
|
||||
msgstr "Decrittografia Elementi: %d/%d"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Fetching resources: %d"
|
||||
msgstr "Risorse: %d."
|
||||
msgid "Fetching resources: %d/%d"
|
||||
msgstr "Risorse: %d/%d"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr ""
|
||||
@@ -1193,13 +1217,13 @@ msgstr "Converti in \"Cose-da-fare\""
|
||||
msgid "Copy Markdown link"
|
||||
msgstr "Copia il link Markdown"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
#, javascript-format
|
||||
msgid "Delete note \"%s\"?"
|
||||
msgstr "Eliminare il taccuino \"%s\"?"
|
||||
msgstr "Eliminare la nota \"%s\"?"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
#, javascript-format
|
||||
msgid "Delete these %d notes?"
|
||||
msgstr "Cancellare queste note?"
|
||||
msgstr "Cancellare queste %d note?"
|
||||
|
||||
msgid ""
|
||||
"Type a note title to jump to it. Or type # followed by a tag name, or @ "
|
||||
@@ -1321,6 +1345,10 @@ msgstr "In corso"
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr "La sincronizzazione è già in corso. Stato: %s"
|
||||
|
||||
msgid ""
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encrypted"
|
||||
msgstr "Crittografato"
|
||||
|
||||
@@ -1328,10 +1356,10 @@ msgid "Encrypted items cannot be modified"
|
||||
msgstr "Gli elementi crittografati non possono essere modificati"
|
||||
|
||||
msgid "title"
|
||||
msgstr "Titolo"
|
||||
msgstr "titolo"
|
||||
|
||||
msgid "updated date"
|
||||
msgstr "Data di aggiornamento"
|
||||
msgstr "data di aggiornamento"
|
||||
|
||||
msgid "Conflicts"
|
||||
msgstr "Conflitti"
|
||||
@@ -1344,7 +1372,7 @@ msgid "Notebooks cannot be named \"%s\", which is a reserved title."
|
||||
msgstr "I blocchi non possono essere chiamati \"%s\". È un titolo riservato."
|
||||
|
||||
msgid "created date"
|
||||
msgstr "Data di creazione"
|
||||
msgstr "data di creazione"
|
||||
|
||||
msgid "This note does not have geolocation information."
|
||||
msgstr "Questa nota non ha informazione sulla geolocalizzazione."
|
||||
@@ -1394,9 +1422,8 @@ msgstr "Ordina le note per"
|
||||
msgid "Reverse sort order"
|
||||
msgstr "Inverti l'ordine"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Sort notebooks by"
|
||||
msgstr "Ordina le note per"
|
||||
msgstr "Ordina i taccuini per"
|
||||
|
||||
msgid "Save geo-location with notes"
|
||||
msgstr "Salva geolocalizzazione con le note"
|
||||
@@ -1416,15 +1443,14 @@ msgstr "Quando si crea una nuova nota:"
|
||||
msgid "Enable soft breaks"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable math expressions"
|
||||
msgstr "Attiva Crittografia"
|
||||
msgstr "Attiva espressioni matematiche"
|
||||
|
||||
msgid "Enable ==mark== syntax"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable footnotes"
|
||||
msgstr ""
|
||||
msgstr "Attiva note a piè pagina"
|
||||
|
||||
msgid "Enable table of contents extension"
|
||||
msgstr ""
|
||||
@@ -1534,6 +1560,24 @@ msgstr ""
|
||||
"Ogni target di sincronizzazione può avere parametri aggiuntivi denominati "
|
||||
"come `sync.NUM.NAME` (tutti documentati di seguito)."
|
||||
|
||||
msgid "Attachment download behaviour"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
|
||||
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
|
||||
"the attachments are downloaded whether you open the note or not."
|
||||
msgstr ""
|
||||
|
||||
msgid "Always"
|
||||
msgstr "Sempre"
|
||||
|
||||
msgid "Manual"
|
||||
msgstr "Manuale"
|
||||
|
||||
msgid "Auto"
|
||||
msgstr "Auto"
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr "Cartella da sincronizzare con (percorso assoluto)"
|
||||
|
||||
@@ -1573,31 +1617,40 @@ msgstr ""
|
||||
msgid "Ignore TLS certificate errors"
|
||||
msgstr "Ignora gli errori del certificato TLS"
|
||||
|
||||
msgid "Enable note history"
|
||||
msgstr "Attiva cronologia della nota"
|
||||
|
||||
msgid "days"
|
||||
msgstr "giorni"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%d days"
|
||||
msgstr "%d giorni"
|
||||
|
||||
msgid "Keep note history for"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr "Opzione non valida: \"%s\". I valori possibili sono: %s."
|
||||
|
||||
#, fuzzy
|
||||
msgid "General"
|
||||
msgstr "Opzioni Generali"
|
||||
msgstr "Generale"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Synchronisation"
|
||||
msgstr "Stato di sincronizzazione"
|
||||
msgstr "Sincronizzazione"
|
||||
|
||||
msgid "Appearance"
|
||||
msgstr ""
|
||||
msgstr "Apparenza"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Note"
|
||||
msgstr "Taccuini"
|
||||
msgstr "Nota"
|
||||
|
||||
msgid "Plugins"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Application"
|
||||
msgstr "Esci dall'applicazione."
|
||||
msgstr "Applicazione"
|
||||
|
||||
#, javascript-format
|
||||
msgid "The tag \"%s\" already exists. Please choose a different name."
|
||||
@@ -1646,12 +1699,19 @@ msgstr "Non ci sono dati da esportare."
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr "Si prega di specificare il Taccuino in cui importare le note."
|
||||
|
||||
msgid "Restored Notes"
|
||||
msgstr "Ripristina note"
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "Elementi che non possono essere sincronizzati"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s): %s"
|
||||
msgstr "%s (%s): %s"
|
||||
msgid "%s (%s) could not be uploaded: %s"
|
||||
msgstr "%s (%s) non può essere caricato: %s"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Item \"%s\" could not be downloaded: %s"
|
||||
msgstr "Elemento \"%s\" non può essere scaricato: %s"
|
||||
|
||||
msgid ""
|
||||
"These items will remain on the device but will not be uploaded to the sync "
|
||||
@@ -1699,7 +1759,7 @@ msgid "Permission to use camera"
|
||||
msgstr "Permesso di usare la fotocamera"
|
||||
|
||||
msgid "Your permission to use your camera is required."
|
||||
msgstr ""
|
||||
msgstr "E’ richiesto il permesso di usare la fotocamera."
|
||||
|
||||
msgid "There are currently no notes. Create one by clicking on the (+) button."
|
||||
msgstr "Al momento non ci sono note. Creane una cliccando sul bottone (+)."
|
||||
@@ -1729,9 +1789,8 @@ msgstr "Spostare le note %d sul Taccuino \"%s\"?"
|
||||
msgid "Press to set the decryption password."
|
||||
msgstr "Premere per impostare la password di decrittografia."
|
||||
|
||||
#, fuzzy
|
||||
msgid "Clear alarm"
|
||||
msgstr "Imposta Allarme"
|
||||
msgstr "Rimuovi Allarme"
|
||||
|
||||
msgid "Save alarm"
|
||||
msgstr "Salva Allarme"
|
||||
@@ -1745,22 +1804,20 @@ msgstr "Conferma"
|
||||
msgid "Cancel synchronisation"
|
||||
msgstr "Cancella la sincronizzazione"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Checking... Please wait."
|
||||
msgstr "Cancellazione... Attendere per favore."
|
||||
msgstr "Controllo... Attendere per favore."
|
||||
|
||||
#, fuzzy
|
||||
msgid "Success! Synchronisation configuration appears to be correct."
|
||||
msgstr "Controlla la configurazione della sincronizzazione"
|
||||
msgstr ""
|
||||
"Successo! La configurazione della sincronizzazione sembra essere corretta."
|
||||
|
||||
msgid ""
|
||||
"Error. Please check that URL, username, password, etc. are correct and that "
|
||||
"the sync target is accessible. The reported error was:"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "The application has been authorised!"
|
||||
msgstr "L'applicazione è stata autorizzata con successo."
|
||||
msgstr "L'applicazione è stata autorizzata con successo!"
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
@@ -1771,9 +1828,9 @@ msgid ""
|
||||
"Please try again."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
#, javascript-format
|
||||
msgid "Decrypted items: %s / %s"
|
||||
msgstr "Decrittografia Elementi: %d/%d"
|
||||
msgstr "Elementi decriptati: %s / %s"
|
||||
|
||||
msgid "New tags:"
|
||||
msgstr "Nuovi tag:"
|
||||
@@ -1930,9 +1987,11 @@ msgstr ""
|
||||
msgid "Welcome"
|
||||
msgstr "Benvenuto"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "This note has no history"
|
||||
#~ msgstr "Questa note è stata modificata:"
|
||||
#~ msgid "Some items cannot be decrypted."
|
||||
#~ msgstr "Alcuni elementi non possono essere decodificati."
|
||||
|
||||
#~ msgid "%s (%s): %s"
|
||||
#~ msgstr "%s (%s): %s"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "The path to synchronise with when file system synchronisation is enabled. "
|
||||
|
@@ -650,6 +650,9 @@ msgstr "%s を隠す"
|
||||
msgid "Quit"
|
||||
msgstr "終了"
|
||||
|
||||
msgid "Close Window"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "&Edit"
|
||||
msgstr "編集"
|
||||
@@ -715,6 +718,9 @@ msgstr "Webサイトとドキュメント"
|
||||
msgid "Make a donation"
|
||||
msgstr "寄付する"
|
||||
|
||||
msgid "Toggle development tools"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr "%s を開く"
|
||||
@@ -823,12 +829,12 @@ msgstr ""
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr "ノートと設定は次の場所に保存されます: %s"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr "同期の設定を確認する"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Apply"
|
||||
msgstr "適用"
|
||||
|
||||
@@ -970,8 +976,9 @@ msgstr "いくつかの項目は同期されませんでした。"
|
||||
msgid "View them now"
|
||||
msgstr "今すぐ表示"
|
||||
|
||||
msgid "Some items cannot be decrypted."
|
||||
msgstr "いくつかの項目は復号されませんでした。"
|
||||
#, fuzzy
|
||||
msgid "One or more master keys need a password."
|
||||
msgstr "マスターパスワードを入力してください:"
|
||||
|
||||
msgid "Set the password"
|
||||
msgstr "パスワードの設定"
|
||||
@@ -989,9 +996,33 @@ msgstr "場所"
|
||||
msgid "URL"
|
||||
msgstr "URL"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Note History"
|
||||
msgstr "ノートブック"
|
||||
|
||||
msgid "Previous versions of this note"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note properties"
|
||||
msgstr "ノートのプロパティ"
|
||||
|
||||
#, javascript-format
|
||||
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "This note has no history"
|
||||
msgstr "ノートは変更されています:"
|
||||
|
||||
msgid "Restore"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click \"%s\" to restore the note. It will be copied in the notebook named "
|
||||
"\"%s\". The current version of the note will not be replaced or modified."
|
||||
msgstr ""
|
||||
|
||||
msgid "Open..."
|
||||
msgstr "開く..."
|
||||
|
||||
@@ -1142,8 +1173,8 @@ msgid "Decrypting items: %d/%d"
|
||||
msgstr "復号中のアイテム: %d/%d"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Fetching resources: %d"
|
||||
msgstr "取得中のリソース: %d"
|
||||
msgid "Fetching resources: %d/%d"
|
||||
msgstr "取得中のリソース: %d/%d"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr "同期状況の出力先を選択してください"
|
||||
@@ -1298,6 +1329,10 @@ msgstr "実行中"
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr "同期作業はすでに実行中です。状態: %s"
|
||||
|
||||
msgid ""
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encrypted"
|
||||
msgstr "暗号化済"
|
||||
|
||||
@@ -1513,6 +1548,24 @@ msgstr ""
|
||||
"同期する先です。いずれの同期先も `sync.NUM.NAME` のように追加のパラメーターを"
|
||||
"持つことができるでしょう(すべてのドキュメントは下にあります)。"
|
||||
|
||||
msgid "Attachment download behaviour"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
|
||||
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
|
||||
"the attachments are downloaded whether you open the note or not."
|
||||
msgstr ""
|
||||
|
||||
msgid "Always"
|
||||
msgstr ""
|
||||
|
||||
msgid "Manual"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto"
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr "同期先のディレクトリ(絶対パス)"
|
||||
|
||||
@@ -1551,6 +1604,20 @@ msgstr ""
|
||||
msgid "Ignore TLS certificate errors"
|
||||
msgstr "TLS証明書のエラーを無視"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable note history"
|
||||
msgstr "暗号化を有効にする"
|
||||
|
||||
msgid "days"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%d days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Keep note history for"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr "無効な設定値: \"%s\"。有効な値は: %sです。"
|
||||
@@ -1624,12 +1691,19 @@ msgstr "エクスポートするデータがありません。"
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr "ノートをどのノートブックにインポートするのか指定してください。"
|
||||
|
||||
msgid "Restored Notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "同期ができなかったアイテム"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s): %s"
|
||||
msgstr "%s (%s): %s"
|
||||
#, fuzzy, javascript-format
|
||||
msgid "%s (%s) could not be uploaded: %s"
|
||||
msgstr "次のファイルは開くことができません:%s"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Item \"%s\" could not be downloaded: %s"
|
||||
msgstr "次のファイルは開くことができません:%s"
|
||||
|
||||
msgid ""
|
||||
"These items will remain on the device but will not be uploaded to the sync "
|
||||
@@ -1908,9 +1982,11 @@ msgstr ""
|
||||
msgid "Welcome"
|
||||
msgstr "ようこそ"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "This note has no history"
|
||||
#~ msgstr "ノートは変更されています:"
|
||||
#~ msgid "Some items cannot be decrypted."
|
||||
#~ msgstr "いくつかの項目は復号されませんでした。"
|
||||
|
||||
#~ msgid "%s (%s): %s"
|
||||
#~ msgstr "%s (%s): %s"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "The path to synchronise with when file system synchronisation is enabled. "
|
||||
|
@@ -583,6 +583,9 @@ msgstr ""
|
||||
msgid "Quit"
|
||||
msgstr ""
|
||||
|
||||
msgid "Close Window"
|
||||
msgstr ""
|
||||
|
||||
msgid "&Edit"
|
||||
msgstr ""
|
||||
|
||||
@@ -643,6 +646,9 @@ msgstr ""
|
||||
msgid "Make a donation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Toggle development tools"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr ""
|
||||
@@ -744,10 +750,10 @@ msgstr ""
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Browse..."
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr ""
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Apply"
|
||||
@@ -875,7 +881,7 @@ msgstr ""
|
||||
msgid "View them now"
|
||||
msgstr ""
|
||||
|
||||
msgid "Some items cannot be decrypted."
|
||||
msgid "One or more master keys need a password."
|
||||
msgstr ""
|
||||
|
||||
msgid "Set the password"
|
||||
@@ -894,9 +900,31 @@ msgstr ""
|
||||
msgid "URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note History"
|
||||
msgstr ""
|
||||
|
||||
msgid "Previous versions of this note"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note properties"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
|
||||
msgstr ""
|
||||
|
||||
msgid "This note has no history"
|
||||
msgstr ""
|
||||
|
||||
msgid "Restore"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click \"%s\" to restore the note. It will be copied in the notebook named "
|
||||
"\"%s\". The current version of the note will not be replaced or modified."
|
||||
msgstr ""
|
||||
|
||||
msgid "Open..."
|
||||
msgstr ""
|
||||
|
||||
@@ -1043,7 +1071,7 @@ msgid "Decrypting items: %d/%d"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Fetching resources: %d"
|
||||
msgid "Fetching resources: %d/%d"
|
||||
msgstr ""
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
@@ -1191,6 +1219,10 @@ msgstr ""
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encrypted"
|
||||
msgstr ""
|
||||
|
||||
@@ -1391,6 +1423,24 @@ msgid ""
|
||||
"which are named as `sync.NUM.NAME` (all documented below)."
|
||||
msgstr ""
|
||||
|
||||
msgid "Attachment download behaviour"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
|
||||
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
|
||||
"the attachments are downloaded whether you open the note or not."
|
||||
msgstr ""
|
||||
|
||||
msgid "Always"
|
||||
msgstr ""
|
||||
|
||||
msgid "Manual"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto"
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr ""
|
||||
|
||||
@@ -1425,6 +1475,19 @@ msgstr ""
|
||||
msgid "Ignore TLS certificate errors"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable note history"
|
||||
msgstr ""
|
||||
|
||||
msgid "days"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%d days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Keep note history for"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr ""
|
||||
@@ -1492,11 +1555,18 @@ msgstr ""
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr ""
|
||||
|
||||
msgid "Restored Notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s): %s"
|
||||
msgid "%s (%s) could not be uploaded: %s"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Item \"%s\" could not be downloaded: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
|
@@ -647,6 +647,9 @@ msgstr "%s 숨기기"
|
||||
msgid "Quit"
|
||||
msgstr "종료"
|
||||
|
||||
msgid "Close Window"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "&Edit"
|
||||
msgstr "편집"
|
||||
@@ -712,6 +715,9 @@ msgstr "웹사이트 및 각종 문서"
|
||||
msgid "Make a donation"
|
||||
msgstr "기부하기"
|
||||
|
||||
msgid "Toggle development tools"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr "%s 열기"
|
||||
@@ -819,12 +825,12 @@ msgstr ""
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr "노트와 설정값이 다음에 저장되었습니다: %s"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr "동기화 설정 확인"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Apply"
|
||||
msgstr "적용"
|
||||
|
||||
@@ -964,8 +970,9 @@ msgstr "일부 항목들은 동기화할 수 없습니다."
|
||||
msgid "View them now"
|
||||
msgstr "지금 보여주기"
|
||||
|
||||
msgid "Some items cannot be decrypted."
|
||||
msgstr "일부 항목들은 암호화할 수 없습니다."
|
||||
#, fuzzy
|
||||
msgid "One or more master keys need a password."
|
||||
msgstr "관리자 비밀번호를 입력하세요:"
|
||||
|
||||
msgid "Set the password"
|
||||
msgstr "암호 설정"
|
||||
@@ -983,9 +990,33 @@ msgstr "위치"
|
||||
msgid "URL"
|
||||
msgstr "URL"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Note History"
|
||||
msgstr "노트북"
|
||||
|
||||
msgid "Previous versions of this note"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note properties"
|
||||
msgstr "노트 속성"
|
||||
|
||||
#, javascript-format
|
||||
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "This note has no history"
|
||||
msgstr "노트가 변경되었습니다:"
|
||||
|
||||
msgid "Restore"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click \"%s\" to restore the note. It will be copied in the notebook named "
|
||||
"\"%s\". The current version of the note will not be replaced or modified."
|
||||
msgstr ""
|
||||
|
||||
msgid "Open..."
|
||||
msgstr "열기..."
|
||||
|
||||
@@ -1136,8 +1167,8 @@ msgid "Decrypting items: %d/%d"
|
||||
msgstr "복호화 항목: %d/%d"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Fetching resources: %d"
|
||||
msgstr "리소스 가져오는 중: %d."
|
||||
msgid "Fetching resources: %d/%d"
|
||||
msgstr "리소스 가져오는 중: %d/%d"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr "동기화 상태를 내보낼 대상을 선택하세요"
|
||||
@@ -1292,6 +1323,10 @@ msgstr "진행 중"
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr "동기화가 이미 진행되고 있습니다. 상태: %s"
|
||||
|
||||
msgid ""
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encrypted"
|
||||
msgstr "암호화됨"
|
||||
|
||||
@@ -1505,6 +1540,24 @@ msgstr ""
|
||||
"동기화를 할 대상을 선택합니다. 각각의 동기화 대상은 `sync.NUM.NAME` 형식으로 "
|
||||
"된 추가적인 매개 변수를 포함할 수 있습니다(아래에 문서화되어 있습니다)."
|
||||
|
||||
msgid "Attachment download behaviour"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
|
||||
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
|
||||
"the attachments are downloaded whether you open the note or not."
|
||||
msgstr ""
|
||||
|
||||
msgid "Always"
|
||||
msgstr ""
|
||||
|
||||
msgid "Manual"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto"
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr "동기화를 할 폴더 (절대적 경로)"
|
||||
|
||||
@@ -1543,6 +1596,20 @@ msgstr ""
|
||||
msgid "Ignore TLS certificate errors"
|
||||
msgstr "TLS 인증서 오류 무시"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable note history"
|
||||
msgstr "암호화 사용"
|
||||
|
||||
msgid "days"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%d days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Keep note history for"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr "올바르지 않은 옵션 값: \"%s\". 가능한 값은 다음과 같습니다: \"%s\"."
|
||||
@@ -1616,12 +1683,19 @@ msgstr "내보낼 데이터가 없습니다."
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr "노트를 가져와서 저장할 노트북을 지정하세요."
|
||||
|
||||
msgid "Restored Notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "동기화를 할 수 없는 항목"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s): %s"
|
||||
msgstr "%s (%s): %s"
|
||||
#, fuzzy, javascript-format
|
||||
msgid "%s (%s) could not be uploaded: %s"
|
||||
msgstr "다음 파일을 열 수 없습니다: %s"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Item \"%s\" could not be downloaded: %s"
|
||||
msgstr "다음 파일을 열 수 없습니다: %s"
|
||||
|
||||
msgid ""
|
||||
"These items will remain on the device but will not be uploaded to the sync "
|
||||
@@ -1897,9 +1971,11 @@ msgstr "노트북이 없습니다. (+) 버튼을 눌러 새로 만드세요."
|
||||
msgid "Welcome"
|
||||
msgstr "환영합니다"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "This note has no history"
|
||||
#~ msgstr "노트가 변경되었습니다:"
|
||||
#~ msgid "Some items cannot be decrypted."
|
||||
#~ msgstr "일부 항목들은 암호화할 수 없습니다."
|
||||
|
||||
#~ msgid "%s (%s): %s"
|
||||
#~ msgstr "%s (%s): %s"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "The path to synchronise with when file system synchronisation is enabled. "
|
||||
|
@@ -650,6 +650,9 @@ msgstr "Skjul %s"
|
||||
msgid "Quit"
|
||||
msgstr "Avslutt"
|
||||
|
||||
msgid "Close Window"
|
||||
msgstr ""
|
||||
|
||||
msgid "&Edit"
|
||||
msgstr "&Rediger"
|
||||
|
||||
@@ -710,6 +713,9 @@ msgstr "Nettsted og dokumentasjon"
|
||||
msgid "Make a donation"
|
||||
msgstr "Gi et bidrag"
|
||||
|
||||
msgid "Toggle development tools"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr "Åpne %s"
|
||||
@@ -818,12 +824,12 @@ msgstr ""
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr "Notater og innstillinger er lagret i: %s"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr "Utforsk..."
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr "Sjekk synkroniseringskonfigurasjon"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr "Utforsk..."
|
||||
|
||||
msgid "Apply"
|
||||
msgstr "Bruk"
|
||||
|
||||
@@ -967,8 +973,9 @@ msgstr "Noen elementer kan ikke synkroniseres."
|
||||
msgid "View them now"
|
||||
msgstr "Vis nå"
|
||||
|
||||
msgid "Some items cannot be decrypted."
|
||||
msgstr "Noen elementer kan ikke dekrypteres."
|
||||
#, fuzzy
|
||||
msgid "One or more master keys need a password."
|
||||
msgstr "Skriv inn masterpassordet:"
|
||||
|
||||
msgid "Set the password"
|
||||
msgstr "Sett passord"
|
||||
@@ -987,9 +994,33 @@ msgstr "Lokasjon"
|
||||
msgid "URL"
|
||||
msgstr "URL"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Note History"
|
||||
msgstr "Notatliste"
|
||||
|
||||
msgid "Previous versions of this note"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note properties"
|
||||
msgstr "Notategenskaper"
|
||||
|
||||
#, javascript-format
|
||||
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "This note has no history"
|
||||
msgstr "Dette notatet har blitt endret:"
|
||||
|
||||
msgid "Restore"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click \"%s\" to restore the note. It will be copied in the notebook named "
|
||||
"\"%s\". The current version of the note will not be replaced or modified."
|
||||
msgstr ""
|
||||
|
||||
msgid "Open..."
|
||||
msgstr "Åpne..."
|
||||
|
||||
@@ -1141,7 +1172,7 @@ msgid "Decrypting items: %d/%d"
|
||||
msgstr "Dekrypterer elementer: %d/%d"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Fetching resources: %d"
|
||||
msgid "Fetching resources: %d/%d"
|
||||
msgstr "Henter ressurser: %d"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
@@ -1297,6 +1328,10 @@ msgstr "Pågår"
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr "Synkronisering pågår allerede. Status: %s"
|
||||
|
||||
msgid ""
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encrypted"
|
||||
msgstr "Kryptert"
|
||||
|
||||
@@ -1514,6 +1549,24 @@ msgstr ""
|
||||
"Målet å synkronisere til. Hvert synkroniseringsmål kan ha tilleggsparametere "
|
||||
"som er navngitt som `sync.NUM.NAME` (dokumentert nedenfor)."
|
||||
|
||||
msgid "Attachment download behaviour"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
|
||||
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
|
||||
"the attachments are downloaded whether you open the note or not."
|
||||
msgstr ""
|
||||
|
||||
msgid "Always"
|
||||
msgstr ""
|
||||
|
||||
msgid "Manual"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto"
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr "Katalog å synkronisere med (absolutt sti)"
|
||||
|
||||
@@ -1552,6 +1605,20 @@ msgstr ""
|
||||
msgid "Ignore TLS certificate errors"
|
||||
msgstr "Ignorer TLS-sertifikatfeil"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable note history"
|
||||
msgstr "Liste over innhold"
|
||||
|
||||
msgid "days"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%d days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Keep note history for"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr "Ugyldig verdi: \"%s\". Mulige verdier er: %s."
|
||||
@@ -1621,12 +1688,19 @@ msgstr "Det er ingen data for eksportering."
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr "Velg notatbok som notatene skal importeres til."
|
||||
|
||||
msgid "Restored Notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "Elementer som ikke vil synkronisere"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s): %s"
|
||||
msgstr "%s (%s): %s"
|
||||
#, fuzzy, javascript-format
|
||||
msgid "%s (%s) could not be uploaded: %s"
|
||||
msgstr "Filen kunne ikke åpnes: %s"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Item \"%s\" could not be downloaded: %s"
|
||||
msgstr "Filen kunne ikke åpnes: %s"
|
||||
|
||||
msgid ""
|
||||
"These items will remain on the device but will not be uploaded to the sync "
|
||||
@@ -1902,9 +1976,11 @@ msgstr "Du har enda ingen notatbok. Opprett en ved å klikke på (+)-knappen."
|
||||
msgid "Welcome"
|
||||
msgstr "Velkommen"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "This note has no history"
|
||||
#~ msgstr "Dette notatet har blitt endret:"
|
||||
#~ msgid "Some items cannot be decrypted."
|
||||
#~ msgstr "Noen elementer kan ikke dekrypteres."
|
||||
|
||||
#~ msgid "%s (%s): %s"
|
||||
#~ msgstr "%s (%s): %s"
|
||||
|
||||
#~ msgid "Table of contents"
|
||||
#~ msgstr "Liste over innhold"
|
||||
|
@@ -665,6 +665,9 @@ msgstr ""
|
||||
msgid "Quit"
|
||||
msgstr "Stop"
|
||||
|
||||
msgid "Close Window"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "&Edit"
|
||||
msgstr "Bewerk"
|
||||
@@ -731,6 +734,9 @@ msgstr "Website en documentatie"
|
||||
msgid "Make a donation"
|
||||
msgstr "Website en documentatie"
|
||||
|
||||
msgid "Toggle development tools"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr "Op %s: %s"
|
||||
@@ -834,13 +840,13 @@ msgstr ""
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr "Notities en instellingen zijn opgeslaan in %s"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr "Annuleer synchronisatie"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
|
||||
@@ -983,8 +989,9 @@ msgstr "Sommige items kunnen niet gesynchroniseerd worden."
|
||||
msgid "View them now"
|
||||
msgstr "Bekijk ze nu"
|
||||
|
||||
msgid "Some items cannot be decrypted."
|
||||
msgstr "Sommige items kunnen niet gedecodeerd worden."
|
||||
#, fuzzy
|
||||
msgid "One or more master keys need a password."
|
||||
msgstr "Voeg hoofdsleutel in:"
|
||||
|
||||
msgid "Set the password"
|
||||
msgstr "Stel wachtwoord in"
|
||||
@@ -1004,9 +1011,33 @@ msgstr ""
|
||||
msgid "URL"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Note History"
|
||||
msgstr "Notitieboeken"
|
||||
|
||||
msgid "Previous versions of this note"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note properties"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "This note has no history"
|
||||
msgstr "Deze notitie werd aangepast:"
|
||||
|
||||
msgid "Restore"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click \"%s\" to restore the note. It will be copied in the notebook named "
|
||||
"\"%s\". The current version of the note will not be replaced or modified."
|
||||
msgstr ""
|
||||
|
||||
msgid "Open..."
|
||||
msgstr ""
|
||||
|
||||
@@ -1157,11 +1188,11 @@ msgstr "Notitieboeken"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Decrypting items: %d/%d"
|
||||
msgstr "Opgehaalde items: %d/%d."
|
||||
msgstr "Opgehaalde items: %d/%d"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Fetching resources: %d"
|
||||
msgstr "Middelen: %d."
|
||||
msgid "Fetching resources: %d/%d"
|
||||
msgstr "Middelen: %d/%d"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr "Selecteer waar de synchronisatie status naar geëxporteerd moet worden"
|
||||
@@ -1320,6 +1351,10 @@ msgstr ""
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr "Synchronisatie is reeds bezig. Status: %s"
|
||||
|
||||
msgid ""
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encrypted"
|
||||
msgstr "Versleuteld"
|
||||
|
||||
@@ -1539,6 +1574,24 @@ msgid ""
|
||||
"which are named as `sync.NUM.NAME` (all documented below)."
|
||||
msgstr ""
|
||||
|
||||
msgid "Attachment download behaviour"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
|
||||
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
|
||||
"the attachments are downloaded whether you open the note or not."
|
||||
msgstr ""
|
||||
|
||||
msgid "Always"
|
||||
msgstr ""
|
||||
|
||||
msgid "Manual"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto"
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr "Folder om mee te synchroniseren (absolute pad)"
|
||||
|
||||
@@ -1575,6 +1628,20 @@ msgstr ""
|
||||
msgid "Ignore TLS certificate errors"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable note history"
|
||||
msgstr "Schakel encryptie in"
|
||||
|
||||
msgid "days"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%d days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Keep note history for"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr "Ongeldige optie: \"%s\". Geldige waarden zijn: %s."
|
||||
@@ -1650,12 +1717,19 @@ msgstr ""
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr "Selecteer waar de synchronisatie status naar geëxporteerd moet worden"
|
||||
|
||||
msgid "Restored Notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "Items die niet gesynchroniseerd kunnen worden"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s): %s"
|
||||
msgstr "%s (%s): %s"
|
||||
#, fuzzy, javascript-format
|
||||
msgid "%s (%s) could not be uploaded: %s"
|
||||
msgstr "Het notitieboek kon niet opgeslaan worden: %s"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Item \"%s\" could not be downloaded: %s"
|
||||
msgstr "Het notitieboek kon niet opgeslaan worden: %s"
|
||||
|
||||
msgid ""
|
||||
"These items will remain on the device but will not be uploaded to the sync "
|
||||
@@ -1931,9 +2005,11 @@ msgstr ""
|
||||
msgid "Welcome"
|
||||
msgstr "Welkom"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "This note has no history"
|
||||
#~ msgstr "Deze notitie werd aangepast:"
|
||||
#~ msgid "Some items cannot be decrypted."
|
||||
#~ msgstr "Sommige items kunnen niet gedecodeerd worden."
|
||||
|
||||
#~ msgid "%s (%s): %s"
|
||||
#~ msgstr "%s (%s): %s"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "The path to synchronise with when file system synchronisation is enabled. "
|
||||
|
@@ -664,6 +664,9 @@ msgstr "%s verbergen"
|
||||
msgid "Quit"
|
||||
msgstr "Afsluiten"
|
||||
|
||||
msgid "Close Window"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "&Edit"
|
||||
msgstr "Bewerken"
|
||||
@@ -731,6 +734,9 @@ msgstr "Website en documentatie"
|
||||
msgid "Make a donation"
|
||||
msgstr "Doneren"
|
||||
|
||||
msgid "Toggle development tools"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr "%s openen"
|
||||
@@ -839,12 +845,12 @@ msgstr ""
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr "Notities en instellingen worden opgeslagen in: %s"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr "Synchronisatieconfiguratie controleren"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Apply"
|
||||
msgstr "Toepassen"
|
||||
|
||||
@@ -989,8 +995,9 @@ msgstr "Sommige items kunnen niet worden gesynchroniseerd."
|
||||
msgid "View them now"
|
||||
msgstr "Items tonen"
|
||||
|
||||
msgid "Some items cannot be decrypted."
|
||||
msgstr "Sommige items kunnen niet worden ontsleuteled."
|
||||
#, fuzzy
|
||||
msgid "One or more master keys need a password."
|
||||
msgstr "Voer het hoofdwachtwoord in:"
|
||||
|
||||
msgid "Set the password"
|
||||
msgstr "Wachtwoord instellen"
|
||||
@@ -1012,9 +1019,33 @@ msgstr ""
|
||||
msgid "URL"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Note History"
|
||||
msgstr "Notitieboeken"
|
||||
|
||||
msgid "Previous versions of this note"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note properties"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "This note has no history"
|
||||
msgstr "Deze notitie is bewerkt:"
|
||||
|
||||
msgid "Restore"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click \"%s\" to restore the note. It will be copied in the notebook named "
|
||||
"\"%s\". The current version of the note will not be replaced or modified."
|
||||
msgstr ""
|
||||
|
||||
msgid "Open..."
|
||||
msgstr "Openen..."
|
||||
|
||||
@@ -1165,8 +1196,8 @@ msgid "Decrypting items: %d/%d"
|
||||
msgstr "Bezig met ontsleutelen van items: %d/%d"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Fetching resources: %d"
|
||||
msgstr "Bronnen: %d."
|
||||
msgid "Fetching resources: %d/%d"
|
||||
msgstr "Bronnen: %d/%d"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr "Kies waar de synchronisatiestatus naar moet worden geëxporteerd"
|
||||
@@ -1323,6 +1354,10 @@ msgstr "Bezig"
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr "De synchronisatie loopt al. Status: %s"
|
||||
|
||||
msgid ""
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encrypted"
|
||||
msgstr "Versleuteld"
|
||||
|
||||
@@ -1535,6 +1570,24 @@ msgstr ""
|
||||
"argumenten bevatten, welke `sync.NUM.NAME` worden genoemd (wordt hieronder "
|
||||
"uitgelegd)."
|
||||
|
||||
msgid "Attachment download behaviour"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
|
||||
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
|
||||
"the attachments are downloaded whether you open the note or not."
|
||||
msgstr ""
|
||||
|
||||
msgid "Always"
|
||||
msgstr ""
|
||||
|
||||
msgid "Manual"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto"
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr "Map waarnaar gesynchroniseerd moet worden (absoluut pad)"
|
||||
|
||||
@@ -1574,6 +1627,20 @@ msgstr ""
|
||||
msgid "Ignore TLS certificate errors"
|
||||
msgstr "TLS-certificaatfouten negeren"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable note history"
|
||||
msgstr "Versleuteling inschakelen"
|
||||
|
||||
msgid "days"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%d days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Keep note history for"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr "Ongeldige optie: \"%s\". Geldige waarden zijn: %s."
|
||||
@@ -1649,12 +1716,19 @@ msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr ""
|
||||
"Geef het notitieboek op waar de notities naar moeten worden geïmporteerd."
|
||||
|
||||
msgid "Restored Notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "Items die niet kunnen worden gesynchroniseerd"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s): %s"
|
||||
msgstr "%s (%s): %s"
|
||||
#, fuzzy, javascript-format
|
||||
msgid "%s (%s) could not be uploaded: %s"
|
||||
msgstr "Dit bestand kan niet worden geopend: %s"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Item \"%s\" could not be downloaded: %s"
|
||||
msgstr "Dit bestand kan niet worden geopend: %s"
|
||||
|
||||
msgid ""
|
||||
"These items will remain on the device but will not be uploaded to the sync "
|
||||
@@ -1934,9 +2008,11 @@ msgstr ""
|
||||
msgid "Welcome"
|
||||
msgstr "Welkom"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "This note has no history"
|
||||
#~ msgstr "Deze notitie is bewerkt:"
|
||||
#~ msgid "Some items cannot be decrypted."
|
||||
#~ msgstr "Sommige items kunnen niet worden ontsleuteled."
|
||||
|
||||
#~ msgid "%s (%s): %s"
|
||||
#~ msgstr "%s (%s): %s"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "The path to synchronise with when file system synchronisation is enabled. "
|
||||
|
2011
CliClient/locales/pl_PL.po
Normal file
2011
CliClient/locales/pl_PL.po
Normal file
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 2.2.1\n"
|
||||
"X-Generator: Poedit 2.0.7\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
msgid "To delete a tag, untag the associated notes."
|
||||
@@ -124,13 +124,12 @@ msgstr "Marca uma tarefa como feita."
|
||||
msgid "Note is not a to-do: \"%s\""
|
||||
msgstr "Nota não é uma tarefa: \"%s\""
|
||||
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, "
|
||||
"`status`, `decrypt-file` and `target-status`."
|
||||
msgstr ""
|
||||
"Gerencia a configuração E2EE. Os comandos são `enable`, `disable`, "
|
||||
"`decrypt`, `status` e `target-status`."
|
||||
"`decrypt`, `status`, `decrypt-file` e `target-status`."
|
||||
|
||||
msgid "Enter master password:"
|
||||
msgstr "Entre a senha master:"
|
||||
@@ -584,18 +583,16 @@ msgid "Exporting to \"%s\" as \"%s\" format. Please wait..."
|
||||
msgstr "Exportando para \"%s\" com o formato \"%s\". Por favor, aguarde..."
|
||||
|
||||
msgid "Sidebar"
|
||||
msgstr ""
|
||||
msgstr "Barra Lateral"
|
||||
|
||||
msgid "Note list"
|
||||
msgstr ""
|
||||
msgstr "Lista de notas"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Note title"
|
||||
msgstr "Título do caderno:"
|
||||
msgstr "Título da Nota:"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Note body"
|
||||
msgstr "Cadernos"
|
||||
msgstr "Corpo da Nota"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
|
||||
@@ -632,15 +629,14 @@ msgstr "Opções do Web clipper"
|
||||
msgid "%s %s (%s, %s)"
|
||||
msgstr "%s %s (%s, %s)"
|
||||
|
||||
#, fuzzy
|
||||
msgid "&File"
|
||||
msgstr "Arquivo"
|
||||
msgstr "&Arquivo"
|
||||
|
||||
msgid "About Joplin"
|
||||
msgstr "Sobre o Joplin"
|
||||
|
||||
msgid "Preferences..."
|
||||
msgstr ""
|
||||
msgstr "Preferências..."
|
||||
|
||||
msgid "Check for updates..."
|
||||
msgstr "Verificar atualizações..."
|
||||
@@ -661,9 +657,11 @@ msgstr "Ocultar %s"
|
||||
msgid "Quit"
|
||||
msgstr "Sair"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Close Window"
|
||||
msgstr "Fechar Janela"
|
||||
|
||||
msgid "&Edit"
|
||||
msgstr "Editar"
|
||||
msgstr "&Editar"
|
||||
|
||||
msgid "Copy"
|
||||
msgstr "Copiar"
|
||||
@@ -684,7 +682,7 @@ msgid "Italic"
|
||||
msgstr "Itálico"
|
||||
|
||||
msgid "Link"
|
||||
msgstr ""
|
||||
msgstr "Link"
|
||||
|
||||
msgid "Insert Date Time"
|
||||
msgstr "Inserir Data e Hora"
|
||||
@@ -698,9 +696,8 @@ msgstr "Pesquisar em todas as notas"
|
||||
msgid "Search in current note"
|
||||
msgstr "Pesquisar na nota atual"
|
||||
|
||||
#, fuzzy
|
||||
msgid "&View"
|
||||
msgstr "Visualizar"
|
||||
msgstr "&Visualizar"
|
||||
|
||||
msgid "Toggle sidebar"
|
||||
msgstr "Alternar barra lateral"
|
||||
@@ -708,17 +705,14 @@ msgstr "Alternar barra lateral"
|
||||
msgid "Toggle editor layout"
|
||||
msgstr "Alternar layout do editor"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Focus"
|
||||
msgstr "Focar no corpo"
|
||||
msgstr "Focar"
|
||||
|
||||
#, fuzzy
|
||||
msgid "&Tools"
|
||||
msgstr "Ferramentas"
|
||||
msgstr "&Ferramentas"
|
||||
|
||||
#, fuzzy
|
||||
msgid "&Help"
|
||||
msgstr "Ajuda"
|
||||
msgstr "&Ajuda"
|
||||
|
||||
msgid "Website and documentation"
|
||||
msgstr "Website e documentação"
|
||||
@@ -726,6 +720,9 @@ msgstr "Website e documentação"
|
||||
msgid "Make a donation"
|
||||
msgstr "Fazer uma doação"
|
||||
|
||||
msgid "Toggle development tools"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr "Abrir %s"
|
||||
@@ -744,18 +741,18 @@ msgstr "A versão atual está atualizada."
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (pre-release)"
|
||||
msgstr ""
|
||||
msgstr "%s (pre-release)"
|
||||
|
||||
msgid "An update is available, do you want to download it now?"
|
||||
msgstr "Uma atualização está disponível, você quer baixar agora?"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Your version: %s"
|
||||
msgstr ""
|
||||
msgstr "Sua versão: %s"
|
||||
|
||||
#, javascript-format
|
||||
msgid "New version: %s"
|
||||
msgstr ""
|
||||
msgstr "Nova versão: %s"
|
||||
|
||||
msgid "Yes"
|
||||
msgstr "Sim"
|
||||
@@ -835,12 +832,12 @@ msgstr ""
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr "Notas e configurações estão armazenadas em: %s"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr "Verificar a configuração da sincronização"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr "Navegar..."
|
||||
|
||||
msgid "Apply"
|
||||
msgstr "Aplicar"
|
||||
|
||||
@@ -934,9 +931,8 @@ msgstr "Status"
|
||||
msgid "Encryption is:"
|
||||
msgstr "Encriptação está:"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Usage"
|
||||
msgstr "Uso: %s"
|
||||
msgstr "Uso"
|
||||
|
||||
msgid "Back"
|
||||
msgstr "Voltar"
|
||||
@@ -983,8 +979,9 @@ msgstr "Alguns itens não podem ser sincronizados."
|
||||
msgid "View them now"
|
||||
msgstr "Visualizar agora"
|
||||
|
||||
msgid "Some items cannot be decrypted."
|
||||
msgstr "Alguns itens não podem ser decriptados."
|
||||
#, fuzzy
|
||||
msgid "One or more master keys need a password."
|
||||
msgstr "Entre a senha master:"
|
||||
|
||||
msgid "Set the password"
|
||||
msgstr "Configurar a senha"
|
||||
@@ -1002,9 +999,33 @@ msgstr "Localização"
|
||||
msgid "URL"
|
||||
msgstr "URL"
|
||||
|
||||
msgid "Note History"
|
||||
msgstr "Histórico da Nota"
|
||||
|
||||
msgid "Previous versions of this note"
|
||||
msgstr "Versões anteriores desta nota"
|
||||
|
||||
msgid "Note properties"
|
||||
msgstr "Propriedades da nota"
|
||||
|
||||
#, javascript-format
|
||||
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
|
||||
msgstr "A nota \"%s\" foi restaurada com sucesso no caderno \"%s\"."
|
||||
|
||||
msgid "This note has no history"
|
||||
msgstr "Esta nota não tem histórico"
|
||||
|
||||
msgid "Restore"
|
||||
msgstr "Restaurar"
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click \"%s\" to restore the note. It will be copied in the notebook named "
|
||||
"\"%s\". The current version of the note will not be replaced or modified."
|
||||
msgstr ""
|
||||
"Clique \"%s\" para restaurar a nota. Ela será copiada no caderno com nomes "
|
||||
"\"%s\". A versão atual da nota não será substituída ou modificada."
|
||||
|
||||
msgid "Open..."
|
||||
msgstr "Abrir..."
|
||||
|
||||
@@ -1037,7 +1058,7 @@ msgstr ""
|
||||
"e edite a nota."
|
||||
|
||||
msgid "Only one note can be printed or exported to PDF at a time."
|
||||
msgstr ""
|
||||
msgstr "Só uma nota pode ser impressa ou exportada para PDF de cada vez."
|
||||
|
||||
msgid "strong text"
|
||||
msgstr "texto forte"
|
||||
@@ -1125,18 +1146,19 @@ msgstr "Opções de Encriptação"
|
||||
msgid "Clipper Options"
|
||||
msgstr "Opções do clipper"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Delete notebook \"%s\"?\n"
|
||||
"\n"
|
||||
"All notes and sub-notebooks within this notebook will also be deleted."
|
||||
msgstr ""
|
||||
"Excluir o caderno? Todas as notas e sub-cadernos dentro deste também serão "
|
||||
"excluídas."
|
||||
"Excluir o caderno\"%s\"?\n"
|
||||
"\n"
|
||||
"Todas as notas e sub-cadernos dentro deste também serão excluídos."
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
#, javascript-format
|
||||
msgid "Remove tag \"%s\" from all notes?"
|
||||
msgstr "Remover esta tag de todas as notas?"
|
||||
msgstr "Remover a tag \"%s\" de todas as notas?"
|
||||
|
||||
msgid "Remove this search from the sidebar?"
|
||||
msgstr "Remover essa pesquisa da barra lateral?"
|
||||
@@ -1154,8 +1176,8 @@ msgstr "Cadernos"
|
||||
msgid "Decrypting items: %d/%d"
|
||||
msgstr "Decriptando itens: %d/%d"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Fetching resources: %d"
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Fetching resources: %d/%d"
|
||||
msgstr "Buscando recursos: %d"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
@@ -1185,21 +1207,23 @@ msgstr "Alternar para o tipo Tarefa"
|
||||
msgid "Copy Markdown link"
|
||||
msgstr "Copiar link de Markdown"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
#, javascript-format
|
||||
msgid "Delete note \"%s\"?"
|
||||
msgstr "Apagar o caderno \"%s\"?"
|
||||
msgstr "Apagar a nota \"%s\"?"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
#, javascript-format
|
||||
msgid "Delete these %d notes?"
|
||||
msgstr "Excluir estas notas?"
|
||||
msgstr "Excluir estas %d notas?"
|
||||
|
||||
msgid ""
|
||||
"Type a note title to jump to it. Or type # followed by a tag name, or @ "
|
||||
"followed by a notebook name."
|
||||
msgstr ""
|
||||
"Digite um título de nota para pular pra ela. Ou digite # seguido por um nome "
|
||||
"de tag, ou @ seguido por um nome de caderno."
|
||||
|
||||
msgid "Goto Anything..."
|
||||
msgstr ""
|
||||
msgstr "Ir para qualquer coisa..."
|
||||
|
||||
#, javascript-format
|
||||
msgid "Usage: %s"
|
||||
@@ -1313,6 +1337,12 @@ msgstr "Em andamento"
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr "Sincronização já em andamento. Estado: %s"
|
||||
|
||||
msgid ""
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version"
|
||||
msgstr ""
|
||||
"Tipo de item desconhecido baixado - por favor, atualize o Joplin para a "
|
||||
"última versão"
|
||||
|
||||
msgid "Encrypted"
|
||||
msgstr "Encriptado"
|
||||
|
||||
@@ -1390,7 +1420,6 @@ msgstr "Ordenar notas por"
|
||||
msgid "Reverse sort order"
|
||||
msgstr "Inverter ordem de classificação"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Sort notebooks by"
|
||||
msgstr "Ordenar notas por"
|
||||
|
||||
@@ -1410,41 +1439,40 @@ msgid "When creating a new note:"
|
||||
msgstr "Quando criar uma nota nova:"
|
||||
|
||||
msgid "Enable soft breaks"
|
||||
msgstr ""
|
||||
msgstr "Habilitar soft breaks"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable math expressions"
|
||||
msgstr "Habilitar encriptação"
|
||||
msgstr "Habilitar expressões matemáticas"
|
||||
|
||||
msgid "Enable ==mark== syntax"
|
||||
msgstr ""
|
||||
msgstr "Habilitar sintaxe ==marcador== "
|
||||
|
||||
msgid "Enable footnotes"
|
||||
msgstr ""
|
||||
msgstr "Habilitar notas de rodapé"
|
||||
|
||||
msgid "Enable table of contents extension"
|
||||
msgstr ""
|
||||
msgstr "Habilitar extensão de tabela de conteúdo"
|
||||
|
||||
msgid "Enable ~sub~ syntax"
|
||||
msgstr ""
|
||||
msgstr "Habilitar sintaxe ~sub~"
|
||||
|
||||
msgid "Enable ^sup^ syntax"
|
||||
msgstr ""
|
||||
msgstr "Habilitar sintaxe ^sup^ "
|
||||
|
||||
msgid "Enable deflist syntax"
|
||||
msgstr ""
|
||||
msgstr "Habilitar sintaxe de deflist "
|
||||
|
||||
msgid "Enable abbreviation syntax"
|
||||
msgstr ""
|
||||
msgstr "Habilitar sintaxe de abreviações"
|
||||
|
||||
msgid "Enable markdown emoji"
|
||||
msgstr ""
|
||||
msgstr "Habilitar emojis em markdown "
|
||||
|
||||
msgid "Enable ++insert++ syntax"
|
||||
msgstr ""
|
||||
msgstr "Habilitar sintaxe ++inserir++ "
|
||||
|
||||
msgid "Enable multimarkdown table extension"
|
||||
msgstr ""
|
||||
msgstr "Habilitar extensão de tabela de multimarkdown "
|
||||
|
||||
msgid "Show tray icon"
|
||||
msgstr "Exibir tray icon"
|
||||
@@ -1484,11 +1512,11 @@ msgid "Automatically update the application"
|
||||
msgstr "Atualizar automaticamente o aplicativo"
|
||||
|
||||
msgid "Get pre-releases when checking for updates"
|
||||
msgstr ""
|
||||
msgstr "Obter pre-releases quando for checar atualizações"
|
||||
|
||||
#, javascript-format
|
||||
msgid "See the pre-release page for more details: %s"
|
||||
msgstr ""
|
||||
msgstr "Ver página de pre-release para mais detalhes: %s"
|
||||
|
||||
msgid "Synchronisation interval"
|
||||
msgstr "Intervalo de sincronização"
|
||||
@@ -1529,6 +1557,24 @@ msgstr ""
|
||||
"O alvo para onde sincronizar. Cada alvo pode ter parâmetros adicionais que "
|
||||
"são nomeados como `sync.NUM.NAME` (todos documentados abaixo)."
|
||||
|
||||
msgid "Attachment download behaviour"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
|
||||
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
|
||||
"the attachments are downloaded whether you open the note or not."
|
||||
msgstr ""
|
||||
|
||||
msgid "Always"
|
||||
msgstr ""
|
||||
|
||||
msgid "Manual"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto"
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr "Diretório para sincronizar (caminho absoluto)"
|
||||
|
||||
@@ -1568,31 +1614,40 @@ msgstr ""
|
||||
msgid "Ignore TLS certificate errors"
|
||||
msgstr "Ignorar erros de certificados TLS"
|
||||
|
||||
msgid "Enable note history"
|
||||
msgstr "Habilitar histórico de notas"
|
||||
|
||||
msgid "days"
|
||||
msgstr "dias"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%d days"
|
||||
msgstr "%d dias"
|
||||
|
||||
msgid "Keep note history for"
|
||||
msgstr "Manter histórico de nota por"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr "Valor da opção inválida: \"%s\". Os valores possíveis são: %s."
|
||||
|
||||
#, fuzzy
|
||||
msgid "General"
|
||||
msgstr "Opções Gerais"
|
||||
msgstr "Geral"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Synchronisation"
|
||||
msgstr "Status de sincronização"
|
||||
msgstr "Sincronização"
|
||||
|
||||
msgid "Appearance"
|
||||
msgstr ""
|
||||
msgstr "Aparência"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Note"
|
||||
msgstr "Cadernos"
|
||||
msgstr "Nota"
|
||||
|
||||
msgid "Plugins"
|
||||
msgstr ""
|
||||
msgstr "Plugins"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Application"
|
||||
msgstr "Localização"
|
||||
msgstr "Aplicação"
|
||||
|
||||
#, javascript-format
|
||||
msgid "The tag \"%s\" already exists. Please choose a different name."
|
||||
@@ -1642,12 +1697,19 @@ msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr ""
|
||||
"Por favor, especifique o caderno para onde as notas deveriam ser importadas."
|
||||
|
||||
msgid "Restored Notes"
|
||||
msgstr "Notas restauradas"
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "Os itens não podem ser sincronizados"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s): %s"
|
||||
msgstr "%s (%s): %s"
|
||||
msgid "%s (%s) could not be uploaded: %s"
|
||||
msgstr "%s (%s) não pôde ser enviado: %s"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Item \"%s\" could not be downloaded: %s"
|
||||
msgstr "Item \"%s\" não pôde ser baixado: %s"
|
||||
|
||||
msgid ""
|
||||
"These items will remain on the device but will not be uploaded to the sync "
|
||||
@@ -1809,18 +1871,17 @@ msgstr "Site do Joplin"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Database v%s"
|
||||
msgstr ""
|
||||
msgstr "Banco de dados v%s"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
#, javascript-format
|
||||
msgid "FTS enabled: %d"
|
||||
msgstr "Para excluir: %d"
|
||||
msgstr "FTS habilitado: %d"
|
||||
|
||||
msgid "Login with Dropbox"
|
||||
msgstr "Login com Dropbox"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enter code here"
|
||||
msgstr "Entrar no modo de linha de comando"
|
||||
msgstr "Entre o código aqui"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Master Key %s"
|
||||
@@ -1928,9 +1989,11 @@ msgstr "Você não possui cadernos. Crie um clicando no botão (+)."
|
||||
msgid "Welcome"
|
||||
msgstr "Bem-vindo"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "This note has no history"
|
||||
#~ msgstr "Esta nota foi modificada:"
|
||||
#~ msgid "Some items cannot be decrypted."
|
||||
#~ msgstr "Alguns itens não podem ser decriptados."
|
||||
|
||||
#~ msgid "%s (%s): %s"
|
||||
#~ msgstr "%s (%s): %s"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "The path to synchronise with when file system synchronisation is enabled. "
|
||||
|
@@ -599,6 +599,9 @@ msgstr "Ascundeți %s"
|
||||
msgid "Quit"
|
||||
msgstr "Ieșiți"
|
||||
|
||||
msgid "Close Window"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "&Edit"
|
||||
msgstr "Editați"
|
||||
@@ -665,6 +668,9 @@ msgstr "Website și documentație"
|
||||
msgid "Make a donation"
|
||||
msgstr "Faceți o donație"
|
||||
|
||||
msgid "Toggle development tools"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr "Deschideți %s"
|
||||
@@ -767,10 +773,10 @@ msgstr ""
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Browse..."
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr ""
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Apply"
|
||||
@@ -899,8 +905,8 @@ msgstr "Câțiva itemi nu pot fi sincronizați."
|
||||
msgid "View them now"
|
||||
msgstr "Vizualizați-le acum"
|
||||
|
||||
msgid "Some items cannot be decrypted."
|
||||
msgstr "Câțiva itemi nu pot fi descriptați."
|
||||
msgid "One or more master keys need a password."
|
||||
msgstr ""
|
||||
|
||||
msgid "Set the password"
|
||||
msgstr "Setați parola"
|
||||
@@ -918,9 +924,33 @@ msgstr ""
|
||||
msgid "URL"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Note History"
|
||||
msgstr "Caiete de notițe"
|
||||
|
||||
msgid "Previous versions of this note"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note properties"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "This note has no history"
|
||||
msgstr "Această notiță a fost modificată:"
|
||||
|
||||
msgid "Restore"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click \"%s\" to restore the note. It will be copied in the notebook named "
|
||||
"\"%s\". The current version of the note will not be replaced or modified."
|
||||
msgstr ""
|
||||
|
||||
msgid "Open..."
|
||||
msgstr "Deschideți..."
|
||||
|
||||
@@ -1067,8 +1097,8 @@ msgid "Decrypting items: %d/%d"
|
||||
msgstr "Se decriptează itemi: %d/%d"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Fetching resources: %d"
|
||||
msgstr "Resurse: %d."
|
||||
msgid "Fetching resources: %d/%d"
|
||||
msgstr "Resurse: %d/%d"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr ""
|
||||
@@ -1218,6 +1248,10 @@ msgstr "În progres"
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encrypted"
|
||||
msgstr "Criptat"
|
||||
|
||||
@@ -1421,6 +1455,24 @@ msgid ""
|
||||
"which are named as `sync.NUM.NAME` (all documented below)."
|
||||
msgstr ""
|
||||
|
||||
msgid "Attachment download behaviour"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
|
||||
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
|
||||
"the attachments are downloaded whether you open the note or not."
|
||||
msgstr ""
|
||||
|
||||
msgid "Always"
|
||||
msgstr ""
|
||||
|
||||
msgid "Manual"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto"
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr ""
|
||||
|
||||
@@ -1455,6 +1507,20 @@ msgstr ""
|
||||
msgid "Ignore TLS certificate errors"
|
||||
msgstr "Ignoră erorile de certificat TLS"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable note history"
|
||||
msgstr "Activați criptarea"
|
||||
|
||||
msgid "days"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%d days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Keep note history for"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr ""
|
||||
@@ -1526,12 +1592,19 @@ msgstr "Nu există date de exportat."
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr ""
|
||||
|
||||
msgid "Restored Notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "Itemii nu pot fi sincronizați"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s): %s"
|
||||
msgstr "%s (%s): %s"
|
||||
#, fuzzy, javascript-format
|
||||
msgid "%s (%s) could not be uploaded: %s"
|
||||
msgstr "Acest fișier nu a putut fi deschis: %s"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Item \"%s\" could not be downloaded: %s"
|
||||
msgstr "Acest fișier nu a putut fi deschis: %s"
|
||||
|
||||
msgid ""
|
||||
"These items will remain on the device but will not be uploaded to the sync "
|
||||
@@ -1797,9 +1870,11 @@ msgstr ""
|
||||
msgid "Welcome"
|
||||
msgstr "Bine ați venit"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "This note has no history"
|
||||
#~ msgstr "Această notiță a fost modificată:"
|
||||
#~ msgid "Some items cannot be decrypted."
|
||||
#~ msgstr "Câțiva itemi nu pot fi descriptați."
|
||||
|
||||
#~ msgid "%s (%s): %s"
|
||||
#~ msgstr "%s (%s): %s"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "Joplin v%s"
|
||||
|
@@ -663,6 +663,9 @@ msgstr "Скрыть %s"
|
||||
msgid "Quit"
|
||||
msgstr "Выход"
|
||||
|
||||
msgid "Close Window"
|
||||
msgstr ""
|
||||
|
||||
msgid "&Edit"
|
||||
msgstr "&Правка"
|
||||
|
||||
@@ -723,6 +726,9 @@ msgstr "Сайт и документация"
|
||||
msgid "Make a donation"
|
||||
msgstr "Сделать пожертвование"
|
||||
|
||||
msgid "Toggle development tools"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr "Открыть %s"
|
||||
@@ -831,12 +837,12 @@ msgstr ""
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr "Заметки и настройки сохранены в: %s"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr "Обзор..."
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr "Проверить настройки синхронизации"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr "Обзор..."
|
||||
|
||||
msgid "Apply"
|
||||
msgstr "Применить"
|
||||
|
||||
@@ -980,8 +986,9 @@ msgstr "Некоторые элементы не могут быть синхр
|
||||
msgid "View them now"
|
||||
msgstr "Просмотреть их сейчас"
|
||||
|
||||
msgid "Some items cannot be decrypted."
|
||||
msgstr "Некоторые элементы не могут быть расшифрованы."
|
||||
#, fuzzy
|
||||
msgid "One or more master keys need a password."
|
||||
msgstr "Введите мастер-пароль:"
|
||||
|
||||
msgid "Set the password"
|
||||
msgstr "Установить пароль"
|
||||
@@ -999,9 +1006,33 @@ msgstr "Местоположение"
|
||||
msgid "URL"
|
||||
msgstr "URL"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Note History"
|
||||
msgstr "Список заметок"
|
||||
|
||||
msgid "Previous versions of this note"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note properties"
|
||||
msgstr "Свойства заметки"
|
||||
|
||||
#, javascript-format
|
||||
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "This note has no history"
|
||||
msgstr "Эта заметка была изменена:"
|
||||
|
||||
msgid "Restore"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click \"%s\" to restore the note. It will be copied in the notebook named "
|
||||
"\"%s\". The current version of the note will not be replaced or modified."
|
||||
msgstr ""
|
||||
|
||||
msgid "Open..."
|
||||
msgstr "Открыть..."
|
||||
|
||||
@@ -1153,8 +1184,8 @@ msgid "Decrypting items: %d/%d"
|
||||
msgstr "Расшифровано элементов: %d/%d"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Fetching resources: %d"
|
||||
msgstr "Получение ресурсов: %d"
|
||||
msgid "Fetching resources: %d/%d"
|
||||
msgstr "Получение ресурсов: %d/%d"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr ""
|
||||
@@ -1310,6 +1341,10 @@ msgstr "Выполнение"
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr "Синхронизация уже выполняется. Состояние: %s"
|
||||
|
||||
msgid ""
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encrypted"
|
||||
msgstr "Зашифровано"
|
||||
|
||||
@@ -1527,6 +1562,24 @@ msgstr ""
|
||||
"Цель синхронизации. Каждая цель синхронизации может иметь дополнительные "
|
||||
"параметры, именованные как `sync.NUM.NAME` (все документировано ниже)."
|
||||
|
||||
msgid "Attachment download behaviour"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
|
||||
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
|
||||
"the attachments are downloaded whether you open the note or not."
|
||||
msgstr ""
|
||||
|
||||
msgid "Always"
|
||||
msgstr ""
|
||||
|
||||
msgid "Manual"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto"
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr "Каталог синхронизации (абсолютный путь)"
|
||||
|
||||
@@ -1566,6 +1619,20 @@ msgstr ""
|
||||
msgid "Ignore TLS certificate errors"
|
||||
msgstr "Игнорировать ошибки сертификата TLS"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable note history"
|
||||
msgstr "Включить шифрование"
|
||||
|
||||
msgid "days"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%d days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Keep note history for"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr "Неверное значение параметра: \"%s\". Доступные значения: %s."
|
||||
@@ -1636,12 +1703,19 @@ msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr ""
|
||||
"Пожалуйста, укажите блокнот, в который должны быть импортированы заметки."
|
||||
|
||||
msgid "Restored Notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "Элементы, которые не могут быть синхронизированы"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s): %s"
|
||||
msgstr "%s (%s): %s"
|
||||
#, fuzzy, javascript-format
|
||||
msgid "%s (%s) could not be uploaded: %s"
|
||||
msgstr "Этот файл не может быть открыт: %s"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Item \"%s\" could not be downloaded: %s"
|
||||
msgstr "Этот файл не может быть открыт: %s"
|
||||
|
||||
msgid ""
|
||||
"These items will remain on the device but will not be uploaded to the sync "
|
||||
@@ -1921,9 +1995,11 @@ msgstr "У вас сейчас нет блокнота. Создайте его
|
||||
msgid "Welcome"
|
||||
msgstr "Добро пожаловать"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "This note has no history"
|
||||
#~ msgstr "Эта заметка была изменена:"
|
||||
#~ msgid "Some items cannot be decrypted."
|
||||
#~ msgstr "Некоторые элементы не могут быть расшифрованы."
|
||||
|
||||
#~ msgid "%s (%s): %s"
|
||||
#~ msgstr "%s (%s): %s"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "The path to synchronise with when file system synchronisation is enabled. "
|
||||
|
@@ -661,6 +661,9 @@ msgstr "Skrij %s"
|
||||
msgid "Quit"
|
||||
msgstr "Izhod"
|
||||
|
||||
msgid "Close Window"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "&Edit"
|
||||
msgstr "Uredi"
|
||||
@@ -728,6 +731,9 @@ msgstr "Spletna stran in dokumentacija"
|
||||
msgid "Make a donation"
|
||||
msgstr "Doniraj"
|
||||
|
||||
msgid "Toggle development tools"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr "Odpri %s"
|
||||
@@ -830,12 +836,12 @@ msgstr ""
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr "Zabeležke in nastavitve so shranjene v: %s"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr "Preveri nastavitve sinhronizacije"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
|
||||
@@ -980,8 +986,9 @@ msgstr "Nekateri premeti ne morejo biti sinhronizirani."
|
||||
msgid "View them now"
|
||||
msgstr "Prikaži jih sedaj"
|
||||
|
||||
msgid "Some items cannot be decrypted."
|
||||
msgstr "Nekateri predmeti ne morejo biti dekriptirani."
|
||||
#, fuzzy
|
||||
msgid "One or more master keys need a password."
|
||||
msgstr "Vnesite glavno geslo:"
|
||||
|
||||
msgid "Set the password"
|
||||
msgstr "Nastavi geslo"
|
||||
@@ -1003,9 +1010,33 @@ msgstr ""
|
||||
msgid "URL"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Note History"
|
||||
msgstr "Beležnice"
|
||||
|
||||
msgid "Previous versions of this note"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note properties"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "This note has no history"
|
||||
msgstr "Ta zabeležka je bila spremenjena:"
|
||||
|
||||
msgid "Restore"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click \"%s\" to restore the note. It will be copied in the notebook named "
|
||||
"\"%s\". The current version of the note will not be replaced or modified."
|
||||
msgstr ""
|
||||
|
||||
msgid "Open..."
|
||||
msgstr "Odpri..."
|
||||
|
||||
@@ -1155,11 +1186,11 @@ msgstr "Beležnice"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Decrypting items: %d/%d"
|
||||
msgstr "Preneseni predmeti: %d/%d."
|
||||
msgstr "Preneseni predmeti: %d/%d"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Fetching resources: %d"
|
||||
msgstr "Viri: %d."
|
||||
msgid "Fetching resources: %d/%d"
|
||||
msgstr "Viri: %d/%d"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr "Prosim izberite, kam želite izvoziti sinhronizacijski status"
|
||||
@@ -1318,6 +1349,10 @@ msgstr "V postopku"
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr "Sinhronizacija je že v postopku. Stanje: %s"
|
||||
|
||||
msgid ""
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encrypted"
|
||||
msgstr "Enkriptirano"
|
||||
|
||||
@@ -1535,6 +1570,24 @@ msgstr ""
|
||||
"Ciljno sinhronizacijsko mesto. Vsak sinhronizacijski cilj ima lahko dodatne "
|
||||
"parametre imenovano kot `sync.NUM.NAME` (vse je dokumentirano spodaj)."
|
||||
|
||||
msgid "Attachment download behaviour"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
|
||||
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
|
||||
"the attachments are downloaded whether you open the note or not."
|
||||
msgstr ""
|
||||
|
||||
msgid "Always"
|
||||
msgstr ""
|
||||
|
||||
msgid "Manual"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto"
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr "Mesto ciljne sinhronizacije (absolutna pot)"
|
||||
|
||||
@@ -1569,6 +1622,20 @@ msgstr ""
|
||||
msgid "Ignore TLS certificate errors"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable note history"
|
||||
msgstr "Omogoči enkripcijo"
|
||||
|
||||
msgid "days"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%d days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Keep note history for"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr "Neveljavna vrednost: \"%s\". Možne vrednosti so : %s."
|
||||
@@ -1643,12 +1710,19 @@ msgstr "Ni datotek za izvoz."
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr "Prosim navedite beležnico, kamor želite uvoziti zabeležke."
|
||||
|
||||
msgid "Restored Notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "Predmeti ne morejo biti sinhronizirani"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s): %s"
|
||||
msgstr "%s (%s): %s"
|
||||
#, fuzzy, javascript-format
|
||||
msgid "%s (%s) could not be uploaded: %s"
|
||||
msgstr "Beležnica ne more biti shranjena: %s"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Item \"%s\" could not be downloaded: %s"
|
||||
msgstr "Beležnica ne more biti shranjena: %s"
|
||||
|
||||
msgid ""
|
||||
"These items will remain on the device but will not be uploaded to the sync "
|
||||
@@ -1920,9 +1994,11 @@ msgstr "Trenutno nimate nobene beležnice. Ustvarite jo s klikom na (+) gumb."
|
||||
msgid "Welcome"
|
||||
msgstr "Dobrodošli"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "This note has no history"
|
||||
#~ msgstr "Ta zabeležka je bila spremenjena:"
|
||||
#~ msgid "Some items cannot be decrypted."
|
||||
#~ msgstr "Nekateri predmeti ne morejo biti dekriptirani."
|
||||
|
||||
#~ msgid "%s (%s): %s"
|
||||
#~ msgstr "%s (%s): %s"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "The path to synchronise with when file system synchronisation is enabled. "
|
||||
|
@@ -665,6 +665,9 @@ msgstr "Dölj %s"
|
||||
msgid "Quit"
|
||||
msgstr "Avsluta"
|
||||
|
||||
msgid "Close Window"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "&Edit"
|
||||
msgstr "Redigera"
|
||||
@@ -729,6 +732,9 @@ msgstr "Webbplats och dokumentation"
|
||||
msgid "Make a donation"
|
||||
msgstr "Gör en donation"
|
||||
|
||||
msgid "Toggle development tools"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr "Öppna %s"
|
||||
@@ -837,12 +843,12 @@ msgstr ""
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr "Anteckningar och inställningar lagras i: %s"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr "Bläddra..."
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr "Kontrollera synkroniseringskonfigurationen"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr "Bläddra..."
|
||||
|
||||
msgid "Apply"
|
||||
msgstr "Tillämpa"
|
||||
|
||||
@@ -986,8 +992,9 @@ msgstr "Några objekt kan inte synkroniseras."
|
||||
msgid "View them now"
|
||||
msgstr "Visa dem nu"
|
||||
|
||||
msgid "Some items cannot be decrypted."
|
||||
msgstr "Några objekt kan inte dekrypteras."
|
||||
#, fuzzy
|
||||
msgid "One or more master keys need a password."
|
||||
msgstr "Ange huvudlösenord:"
|
||||
|
||||
msgid "Set the password"
|
||||
msgstr "Ställ in lösenord"
|
||||
@@ -1007,9 +1014,33 @@ msgstr "Plats"
|
||||
msgid "URL"
|
||||
msgstr "URL"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Note History"
|
||||
msgstr "Anteckningsboken"
|
||||
|
||||
msgid "Previous versions of this note"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note properties"
|
||||
msgstr "Anteckningens egenskaper"
|
||||
|
||||
#, javascript-format
|
||||
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "This note has no history"
|
||||
msgstr "Denna anteckning har ändrats:"
|
||||
|
||||
msgid "Restore"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click \"%s\" to restore the note. It will be copied in the notebook named "
|
||||
"\"%s\". The current version of the note will not be replaced or modified."
|
||||
msgstr ""
|
||||
|
||||
msgid "Open..."
|
||||
msgstr "Öppna..."
|
||||
|
||||
@@ -1161,8 +1192,8 @@ msgid "Decrypting items: %d/%d"
|
||||
msgstr "Dekrypterar objekt: %d/%d"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Fetching resources: %d"
|
||||
msgstr "Hämtar resurser: %d"
|
||||
msgid "Fetching resources: %d/%d"
|
||||
msgstr "Hämtar resurser: %d/%d"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr "Välj vart synkroniseringstillståndet ska exporteras till"
|
||||
@@ -1319,6 +1350,10 @@ msgstr "Pågår"
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr "Synkronisering pågår redan. Tillstånd: %s"
|
||||
|
||||
msgid ""
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encrypted"
|
||||
msgstr "Krypterad"
|
||||
|
||||
@@ -1536,6 +1571,24 @@ msgstr ""
|
||||
"Målet att synkronisera till. Varje synkroniseringsmål kan ha ytterligare "
|
||||
"parametrar som heter `sync.NUM.NAME` (alla dokumenterade nedan)."
|
||||
|
||||
msgid "Attachment download behaviour"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
|
||||
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
|
||||
"the attachments are downloaded whether you open the note or not."
|
||||
msgstr ""
|
||||
|
||||
msgid "Always"
|
||||
msgstr ""
|
||||
|
||||
msgid "Manual"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto"
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr "Katalog för att synkronisera med (absolut sökväg)"
|
||||
|
||||
@@ -1575,6 +1628,20 @@ msgstr ""
|
||||
msgid "Ignore TLS certificate errors"
|
||||
msgstr "Ignorera TLS-certifikatfel"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable note history"
|
||||
msgstr "Aktivera kryptering"
|
||||
|
||||
msgid "days"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%d days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Keep note history for"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr "Ogiltigt inställningsvärde: \"%s\". Möjliga värden är: %s."
|
||||
@@ -1645,12 +1712,19 @@ msgstr "Det finns ingen data att exportera."
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr "Ange anteckningsboken som anteckningarna ska importeras till."
|
||||
|
||||
msgid "Restored Notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "Objekt som inte kan synkroniseras"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s): %s"
|
||||
msgstr "%s (%s): %s"
|
||||
#, fuzzy, javascript-format
|
||||
msgid "%s (%s) could not be uploaded: %s"
|
||||
msgstr "Den här filen kunde inte öppnas: %s"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Item \"%s\" could not be downloaded: %s"
|
||||
msgstr "Den här filen kunde inte öppnas: %s"
|
||||
|
||||
msgid ""
|
||||
"These items will remain on the device but will not be uploaded to the sync "
|
||||
@@ -1936,9 +2010,11 @@ msgstr ""
|
||||
msgid "Welcome"
|
||||
msgstr "Välkommen"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "This note has no history"
|
||||
#~ msgstr "Denna anteckning har ändrats:"
|
||||
#~ msgid "Some items cannot be decrypted."
|
||||
#~ msgstr "Några objekt kan inte dekrypteras."
|
||||
|
||||
#~ msgid "%s (%s): %s"
|
||||
#~ msgstr "%s (%s): %s"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "The path to synchronise with when file system synchronisation is enabled. "
|
||||
|
@@ -632,6 +632,9 @@ msgstr "Gizle %s"
|
||||
msgid "Quit"
|
||||
msgstr "Çıkış"
|
||||
|
||||
msgid "Close Window"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "&Edit"
|
||||
msgstr "Düzenle"
|
||||
@@ -696,6 +699,9 @@ msgstr "Web sitesi ve dökümanlar"
|
||||
msgid "Make a donation"
|
||||
msgstr "Bağış yapın"
|
||||
|
||||
msgid "Toggle development tools"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr "Aç %s"
|
||||
@@ -805,12 +811,12 @@ msgstr ""
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr "Notlar ve ayarlar şu konumda saklanır: %s"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr "Bul..."
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr "Senkronizasyon yapılandırmasını kontrol et"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr "Bul..."
|
||||
|
||||
msgid "Apply"
|
||||
msgstr "Uygula"
|
||||
|
||||
@@ -955,8 +961,9 @@ msgstr "Bazı öğeler senkronize edilemiyor."
|
||||
msgid "View them now"
|
||||
msgstr "Şimdi onları görüntüle"
|
||||
|
||||
msgid "Some items cannot be decrypted."
|
||||
msgstr "Bazı öğelerin şifresi çözülemez."
|
||||
#, fuzzy
|
||||
msgid "One or more master keys need a password."
|
||||
msgstr "Ana şifreyi girin:"
|
||||
|
||||
msgid "Set the password"
|
||||
msgstr "Şifreyi ayarla"
|
||||
@@ -976,9 +983,33 @@ msgstr "Konum"
|
||||
msgid "URL"
|
||||
msgstr "URL"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Note History"
|
||||
msgstr "Not listesi"
|
||||
|
||||
msgid "Previous versions of this note"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note properties"
|
||||
msgstr "Not özellikleri"
|
||||
|
||||
#, javascript-format
|
||||
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "This note has no history"
|
||||
msgstr "Bu not değiştirildi:"
|
||||
|
||||
msgid "Restore"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click \"%s\" to restore the note. It will be copied in the notebook named "
|
||||
"\"%s\". The current version of the note will not be replaced or modified."
|
||||
msgstr ""
|
||||
|
||||
msgid "Open..."
|
||||
msgstr "Aç..."
|
||||
|
||||
@@ -1130,8 +1161,8 @@ msgid "Decrypting items: %d/%d"
|
||||
msgstr "Şifresi çözülenler: %d/%d"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Fetching resources: %d"
|
||||
msgstr "Kaynaklar alınıyor: %d"
|
||||
msgid "Fetching resources: %d/%d"
|
||||
msgstr "Kaynaklar alınıyor: %d/%d"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr "Lütfen senkronizasyon durumunun nereye aktarılacağını seçin"
|
||||
@@ -1288,6 +1319,10 @@ msgstr "Devam etmekte"
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr "Senkronizasyon zaten devam ediyor. Durum: %s"
|
||||
|
||||
msgid ""
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encrypted"
|
||||
msgstr "Şifrelenmiş"
|
||||
|
||||
@@ -1503,6 +1538,24 @@ msgstr ""
|
||||
"Senkronize edilecek hedef. Her senkronizasyon hedefi, `sync.NUM.NAME` olarak "
|
||||
"adlandırılan ek parametrelere sahip olabilir (tümü aşağıda belgelenmiştir)."
|
||||
|
||||
msgid "Attachment download behaviour"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
|
||||
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
|
||||
"the attachments are downloaded whether you open the note or not."
|
||||
msgstr ""
|
||||
|
||||
msgid "Always"
|
||||
msgstr ""
|
||||
|
||||
msgid "Manual"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto"
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr "Eşitlenecek dizin (kesin yol)"
|
||||
|
||||
@@ -1542,6 +1595,20 @@ msgstr ""
|
||||
msgid "Ignore TLS certificate errors"
|
||||
msgstr "TLS sertifikası hatalarını yoksay"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable note history"
|
||||
msgstr "Şifrelemeyi etkinleştir"
|
||||
|
||||
msgid "days"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%d days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Keep note history for"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr "Geçersiz seçenek değeri: \"%s\". Mümkün değerler: %s."
|
||||
@@ -1611,12 +1678,19 @@ msgstr "Çıkartılacak veri bulunmuyor."
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr "Lütfen notların alınacağı not defterini belirtin."
|
||||
|
||||
msgid "Restored Notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "Senkronize edilemeyen öğeler"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s): %s"
|
||||
msgstr "%s (%s): %s"
|
||||
#, fuzzy, javascript-format
|
||||
msgid "%s (%s) could not be uploaded: %s"
|
||||
msgstr "Bu dosya açılamadı: %s"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Item \"%s\" could not be downloaded: %s"
|
||||
msgstr "Bu dosya açılamadı: %s"
|
||||
|
||||
msgid ""
|
||||
"These items will remain on the device but will not be uploaded to the sync "
|
||||
@@ -1895,9 +1969,11 @@ msgstr ""
|
||||
msgid "Welcome"
|
||||
msgstr "Hoşgeldiniz"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "This note has no history"
|
||||
#~ msgstr "Bu not değiştirildi:"
|
||||
#~ msgid "Some items cannot be decrypted."
|
||||
#~ msgstr "Bazı öğelerin şifresi çözülemez."
|
||||
|
||||
#~ msgid "%s (%s): %s"
|
||||
#~ msgstr "%s (%s): %s"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "The path to synchronise with when file system synchronisation is enabled. "
|
||||
|
@@ -47,7 +47,7 @@ msgid "y"
|
||||
msgstr "是"
|
||||
|
||||
msgid "Cancelling background synchronisation... Please wait."
|
||||
msgstr "正在取消后台同步... 请稍候。"
|
||||
msgstr "正在取消后台同步……请稍候。"
|
||||
|
||||
#, javascript-format
|
||||
msgid "No such command: %s"
|
||||
@@ -601,7 +601,7 @@ msgid "About Joplin"
|
||||
msgstr "关于 Joplin"
|
||||
|
||||
msgid "Preferences..."
|
||||
msgstr ""
|
||||
msgstr "偏好……"
|
||||
|
||||
msgid "Check for updates..."
|
||||
msgstr "检查更新..."
|
||||
@@ -622,6 +622,9 @@ msgstr "隐藏 %s"
|
||||
msgid "Quit"
|
||||
msgstr "退出"
|
||||
|
||||
msgid "Close Window"
|
||||
msgstr "关闭窗口"
|
||||
|
||||
msgid "&Edit"
|
||||
msgstr "编辑 (&E)"
|
||||
|
||||
@@ -682,6 +685,9 @@ msgstr "网站与文档"
|
||||
msgid "Make a donation"
|
||||
msgstr "捐赠"
|
||||
|
||||
msgid "Toggle development tools"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr "打开 %s"
|
||||
@@ -785,12 +791,12 @@ msgstr "该授权令牌仅用于允许第三方应用程序访问 Joplin。"
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr "笔记与设置文件储存在:%s"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr "浏览..."
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr "检查同步配置"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr "浏览..."
|
||||
|
||||
msgid "Apply"
|
||||
msgstr "应用"
|
||||
|
||||
@@ -925,8 +931,9 @@ msgstr "一些项目无法被同步。"
|
||||
msgid "View them now"
|
||||
msgstr "立刻查看"
|
||||
|
||||
msgid "Some items cannot be decrypted."
|
||||
msgstr "一些项目无法被解密。"
|
||||
#, fuzzy
|
||||
msgid "One or more master keys need a password."
|
||||
msgstr "输入主密码:"
|
||||
|
||||
msgid "Set the password"
|
||||
msgstr "设置密码"
|
||||
@@ -944,9 +951,33 @@ msgstr "位置"
|
||||
msgid "URL"
|
||||
msgstr "URL"
|
||||
|
||||
msgid "Note History"
|
||||
msgstr "笔记历史"
|
||||
|
||||
msgid "Previous versions of this note"
|
||||
msgstr "此笔记的早期版本"
|
||||
|
||||
msgid "Note properties"
|
||||
msgstr "笔记属性"
|
||||
|
||||
#, javascript-format
|
||||
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
|
||||
msgstr "笔记\"%s\"已成功恢复到笔记本\"%s\"中。"
|
||||
|
||||
msgid "This note has no history"
|
||||
msgstr "此笔记没有历史记录"
|
||||
|
||||
msgid "Restore"
|
||||
msgstr "恢复"
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click \"%s\" to restore the note. It will be copied in the notebook named "
|
||||
"\"%s\". The current version of the note will not be replaced or modified."
|
||||
msgstr ""
|
||||
"单击 \"%s\" 以恢复笔记。它将会被复制到名为 \"%s\" 的笔记本中。笔记的当前版本"
|
||||
"不会被替换或修改。"
|
||||
|
||||
msgid "Open..."
|
||||
msgstr "打开…"
|
||||
|
||||
@@ -1096,8 +1127,8 @@ msgid "Decrypting items: %d/%d"
|
||||
msgstr "正在解密项目:%d/%d"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Fetching resources: %d"
|
||||
msgstr "正在获取资源:%d"
|
||||
msgid "Fetching resources: %d/%d"
|
||||
msgstr "正在获取资源:%d/%d"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr "请选择同步状态的导出位置"
|
||||
@@ -1136,9 +1167,11 @@ msgid ""
|
||||
"Type a note title to jump to it. Or type # followed by a tag name, or @ "
|
||||
"followed by a notebook name."
|
||||
msgstr ""
|
||||
"输入笔记标题以便转跳到它。或者输入 # 跟着一个标签名字,或者输入 @ 跟着一个笔"
|
||||
"记本名字。"
|
||||
|
||||
msgid "Goto Anything..."
|
||||
msgstr ""
|
||||
msgstr "转到某处……"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Usage: %s"
|
||||
@@ -1249,6 +1282,10 @@ msgstr "正在进行"
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr "已经在同步。状态:%s"
|
||||
|
||||
msgid ""
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version"
|
||||
msgstr "已下载项目为未知类型,请将 Joplin 升级到最新版本"
|
||||
|
||||
msgid "Encrypted"
|
||||
msgstr "已加密"
|
||||
|
||||
@@ -1342,45 +1379,41 @@ msgstr "聚焦正文"
|
||||
msgid "When creating a new note:"
|
||||
msgstr "当新建笔记时:"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable soft breaks"
|
||||
msgstr "目录"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable math expressions"
|
||||
msgstr "启用加密"
|
||||
msgstr "启用数学表达式"
|
||||
|
||||
msgid "Enable ==mark== syntax"
|
||||
msgstr ""
|
||||
msgstr "启用 ==mark== 句法"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable footnotes"
|
||||
msgstr "目录"
|
||||
msgstr "启用脚注"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable table of contents extension"
|
||||
msgstr "目录"
|
||||
msgstr "启用目录扩展"
|
||||
|
||||
msgid "Enable ~sub~ syntax"
|
||||
msgstr ""
|
||||
msgstr "启用 ~sub~ 句法"
|
||||
|
||||
msgid "Enable ^sup^ syntax"
|
||||
msgstr ""
|
||||
msgstr "启用 ^sup^ 句法"
|
||||
|
||||
msgid "Enable deflist syntax"
|
||||
msgstr ""
|
||||
msgstr "启用术语表句法"
|
||||
|
||||
msgid "Enable abbreviation syntax"
|
||||
msgstr ""
|
||||
msgstr "启用缩写句法"
|
||||
|
||||
msgid "Enable markdown emoji"
|
||||
msgstr ""
|
||||
msgstr "启用 markdown emoji"
|
||||
|
||||
msgid "Enable ++insert++ syntax"
|
||||
msgstr ""
|
||||
msgstr "启用 ++insert++ 句法"
|
||||
|
||||
msgid "Enable multimarkdown table extension"
|
||||
msgstr ""
|
||||
msgstr "启用 multimarkdown 表格扩展"
|
||||
|
||||
msgid "Show tray icon"
|
||||
msgstr "显示托盘图标"
|
||||
@@ -1462,6 +1495,24 @@ msgid ""
|
||||
msgstr ""
|
||||
"所同步的目标。每个同步目标都可能有名为 `sync.NUM.NAME` 的附加参数(见下文)。"
|
||||
|
||||
msgid "Attachment download behaviour"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
|
||||
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
|
||||
"the attachments are downloaded whether you open the note or not."
|
||||
msgstr ""
|
||||
|
||||
msgid "Always"
|
||||
msgstr ""
|
||||
|
||||
msgid "Manual"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto"
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr "待同步的目录(绝对路径)。"
|
||||
|
||||
@@ -1499,6 +1550,19 @@ msgstr ""
|
||||
msgid "Ignore TLS certificate errors"
|
||||
msgstr "忽略 TLS 证书错误"
|
||||
|
||||
msgid "Enable note history"
|
||||
msgstr "启用笔记历史"
|
||||
|
||||
msgid "days"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%d days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Keep note history for"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr "无效的选项值:\"%s\"。可用值有:%s。"
|
||||
@@ -1516,7 +1580,7 @@ msgid "Note"
|
||||
msgstr "笔记"
|
||||
|
||||
msgid "Plugins"
|
||||
msgstr ""
|
||||
msgstr "插件"
|
||||
|
||||
msgid "Application"
|
||||
msgstr "应用程序"
|
||||
@@ -1566,12 +1630,19 @@ msgstr "没有可导出的数据。"
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr "请指定导入笔记的目标笔记本。"
|
||||
|
||||
msgid "Restored Notes"
|
||||
msgstr "已恢复的笔记"
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "无法同步项目"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s): %s"
|
||||
msgstr "%s (%s): %s"
|
||||
msgid "%s (%s) could not be uploaded: %s"
|
||||
msgstr "%s (%s) 无法上传到:%s"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Item \"%s\" could not be downloaded: %s"
|
||||
msgstr "项目 \"%s\" 无法从 %s 中下载"
|
||||
|
||||
msgid ""
|
||||
"These items will remain on the device but will not be uploaded to the sync "
|
||||
@@ -1842,9 +1913,11 @@ msgstr "您目前未有笔记本。点击 (+) 按钮创建。"
|
||||
msgid "Welcome"
|
||||
msgstr "欢迎"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "This note has no history"
|
||||
#~ msgstr "该笔记已被修改:"
|
||||
#~ msgid "Some items cannot be decrypted."
|
||||
#~ msgstr "一些项目无法被解密。"
|
||||
|
||||
#~ msgid "%s (%s): %s"
|
||||
#~ msgstr "%s (%s): %s"
|
||||
|
||||
#~ msgid "Table of contents"
|
||||
#~ msgstr "目录"
|
||||
|
@@ -627,6 +627,9 @@ msgstr "隱藏 %s"
|
||||
msgid "Quit"
|
||||
msgstr "結束"
|
||||
|
||||
msgid "Close Window"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "&Edit"
|
||||
msgstr "編輯"
|
||||
@@ -694,6 +697,9 @@ msgstr "官方網站及線上說明"
|
||||
msgid "Make a donation"
|
||||
msgstr "捐助"
|
||||
|
||||
msgid "Toggle development tools"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Open %s"
|
||||
msgstr "開啟 %s"
|
||||
@@ -798,12 +804,12 @@ msgstr ""
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr "所有記事和設置均儲存於: %s"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr "檢測同步設置"
|
||||
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Apply"
|
||||
msgstr "套用"
|
||||
|
||||
@@ -939,8 +945,9 @@ msgstr "有些項目不能同步。"
|
||||
msgid "View them now"
|
||||
msgstr "立即檢視"
|
||||
|
||||
msgid "Some items cannot be decrypted."
|
||||
msgstr "有些項目不能解密。"
|
||||
#, fuzzy
|
||||
msgid "One or more master keys need a password."
|
||||
msgstr "輸入主密碼:"
|
||||
|
||||
msgid "Set the password"
|
||||
msgstr "設置密碼"
|
||||
@@ -958,9 +965,33 @@ msgstr ""
|
||||
msgid "URL"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Note History"
|
||||
msgstr "記事本"
|
||||
|
||||
msgid "Previous versions of this note"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note properties"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "This note has no history"
|
||||
msgstr "此記事已被修改:"
|
||||
|
||||
msgid "Restore"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click \"%s\" to restore the note. It will be copied in the notebook named "
|
||||
"\"%s\". The current version of the note will not be replaced or modified."
|
||||
msgstr ""
|
||||
|
||||
msgid "Open..."
|
||||
msgstr "開啟..."
|
||||
|
||||
@@ -1107,8 +1138,8 @@ msgid "Decrypting items: %d/%d"
|
||||
msgstr "正在解密項目: %d/%d 項"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Fetching resources: %d"
|
||||
msgstr "資源: %d。"
|
||||
msgid "Fetching resources: %d/%d"
|
||||
msgstr "資源: %d/%d"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr "請選擇將同步狀態導出到的位置"
|
||||
@@ -1262,6 +1293,10 @@ msgstr "進行中"
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr "同步已在進行中。狀態: %s"
|
||||
|
||||
msgid ""
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encrypted"
|
||||
msgstr "已加密"
|
||||
|
||||
@@ -1471,6 +1506,24 @@ msgstr ""
|
||||
"要同步的目標。每個同步目標可能有附加的參數,它們被命名為 `sync.NUM.NAME` (全"
|
||||
"部記錄如下)。"
|
||||
|
||||
msgid "Attachment download behaviour"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
|
||||
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
|
||||
"the attachments are downloaded whether you open the note or not."
|
||||
msgstr ""
|
||||
|
||||
msgid "Always"
|
||||
msgstr ""
|
||||
|
||||
msgid "Manual"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto"
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr "要同步的目錄 (絕對路徑)"
|
||||
|
||||
@@ -1508,6 +1561,20 @@ msgstr ""
|
||||
msgid "Ignore TLS certificate errors"
|
||||
msgstr "忽略 TLS 證書錯誤"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable note history"
|
||||
msgstr "啟用加密"
|
||||
|
||||
msgid "days"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%d days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Keep note history for"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr "不正確選項值: \"%s\"。可能的值為: %s。"
|
||||
@@ -1580,12 +1647,19 @@ msgstr "沒有資料可匯出。"
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr "請指定將記事匯入到的筆記本。"
|
||||
|
||||
msgid "Restored Notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "無法同步的項目"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s): %s"
|
||||
msgstr "%s (%s): %s"
|
||||
#, fuzzy, javascript-format
|
||||
msgid "%s (%s) could not be uploaded: %s"
|
||||
msgstr "無法開啟檔案: %s"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Item \"%s\" could not be downloaded: %s"
|
||||
msgstr "無法開啟檔案: %s"
|
||||
|
||||
msgid ""
|
||||
"These items will remain on the device but will not be uploaded to the sync "
|
||||
@@ -1854,9 +1928,11 @@ msgstr "您當前沒有任何筆記本。通過按一下 (+) 鍵去建立一本
|
||||
msgid "Welcome"
|
||||
msgstr "歡迎"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "This note has no history"
|
||||
#~ msgstr "此記事已被修改:"
|
||||
#~ msgid "Some items cannot be decrypted."
|
||||
#~ msgstr "有些項目不能解密。"
|
||||
|
||||
#~ msgid "%s (%s): %s"
|
||||
#~ msgstr "%s (%s): %s"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "The path to synchronise with when file system synchronisation is enabled. "
|
||||
|
317
CliClient/package-lock.json
generated
317
CliClient/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "joplin",
|
||||
"version": "1.0.124",
|
||||
"version": "1.0.139",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -346,9 +346,9 @@
|
||||
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
|
||||
},
|
||||
"color": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz",
|
||||
"integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==",
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-3.1.1.tgz",
|
||||
"integrity": "sha512-PvUltIXRjehRKPSy89VnDWFKY58xyhTLyxIg21vwQBI6qLwZNPmC8k3C1uytIgFKEpOIzN4y32iPm8231zFHIg==",
|
||||
"requires": {
|
||||
"color-convert": "^1.9.1",
|
||||
"color-string": "^1.5.2"
|
||||
@@ -560,6 +560,11 @@
|
||||
"resolved": "https://registry.npmjs.org/diacritics/-/diacritics-1.3.0.tgz",
|
||||
"integrity": "sha1-PvqHMj67hj5mls67AILUj/PW96E="
|
||||
},
|
||||
"diff-match-patch": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.4.tgz",
|
||||
"integrity": "sha512-Uv3SW8bmH9nAtHKaKSanOQmj2DnlH65fUpcrMdfdaOxUG02QQ4YGZ8AE7kKOMisF7UqvOlGKVYWRvezdncW9lg=="
|
||||
},
|
||||
"domexception": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz",
|
||||
@@ -697,9 +702,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"expand-template": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-1.1.1.tgz",
|
||||
"integrity": "sha512-cebqLtV8KOZfw0UI8TEFWxtczxxC1jvyUvx6H4fyp1K1FN7A4Q+uggVUlOsI1K8AGU0rwOGqP8nCapdrw8CYQg=="
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
|
||||
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="
|
||||
},
|
||||
"extend": {
|
||||
"version": "3.0.1",
|
||||
@@ -739,6 +744,11 @@
|
||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-4.4.0.tgz",
|
||||
"integrity": "sha1-G2AOX8ofvcboDApwxxyNul95BsU="
|
||||
},
|
||||
"file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
|
||||
},
|
||||
"find-up": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
|
||||
@@ -1435,9 +1445,9 @@
|
||||
}
|
||||
},
|
||||
"joplin-turndown-plugin-gfm": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/joplin-turndown-plugin-gfm/-/joplin-turndown-plugin-gfm-1.0.7.tgz",
|
||||
"integrity": "sha512-z0SveNcchtWwglkO7SgvDzPnVHYk1WumD0QRcWvUchIihqXwDVlve3G8AHkIhM69LY1YdC0HCZJlSMp2spBe/g=="
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/joplin-turndown-plugin-gfm/-/joplin-turndown-plugin-gfm-1.0.8.tgz",
|
||||
"integrity": "sha512-uXgq2zGvjiMl/sXG7946EGhh1pyGbZ0L/6z21LBi8D6BJgHQufmXdve/UP3zpgnhiFhfXvzGY10uNaTuDQ99iQ=="
|
||||
},
|
||||
"jpeg-js": {
|
||||
"version": "0.1.2",
|
||||
@@ -1853,9 +1863,14 @@
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz",
|
||||
"integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw=="
|
||||
"version": "2.13.2",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz",
|
||||
"integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw=="
|
||||
},
|
||||
"napi-build-utils": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.1.tgz",
|
||||
"integrity": "sha512-boQj1WFgQH3v4clhu3mTNfP+vOBxorDlE8EKiMjUlLG3C4qAESnn9AxIOkFgTR2c9LtzNjPrjS60cT27ZKBhaA=="
|
||||
},
|
||||
"ndarray": {
|
||||
"version": "1.0.18",
|
||||
@@ -1876,13 +1891,28 @@
|
||||
}
|
||||
},
|
||||
"needle": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/needle/-/needle-2.2.1.tgz",
|
||||
"integrity": "sha512-t/ZswCM9JTWjAdXS9VpvqhI2Ct2sL2MdY4fUXqGJaGBk13ge99ObqRksRTbBE56K+wxUXwwfZYOuZHifFW9q+Q==",
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/needle/-/needle-2.3.1.tgz",
|
||||
"integrity": "sha512-CaLXV3W8Vnbps8ZANqDGz7j4x7Yj1LW4TWF/TQuDfj7Cfx4nAPTvw98qgTevtto1oHDrh3pQkaODbqupXlsWTg==",
|
||||
"requires": {
|
||||
"debug": "^2.1.2",
|
||||
"debug": "^4.1.0",
|
||||
"iconv-lite": "^0.4.4",
|
||||
"sax": "^1.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"nextgen-events": {
|
||||
@@ -1899,9 +1929,9 @@
|
||||
}
|
||||
},
|
||||
"node-abi": {
|
||||
"version": "2.4.4",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.4.4.tgz",
|
||||
"integrity": "sha512-DQ9Mo2mf/XectC+s6+grPPRQ1Z9gI3ZbrGv6nyXRkjwT3HrE0xvtvrfnH7YHYBLgC/KLadg+h3XHnhZw1sv88A==",
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.8.0.tgz",
|
||||
"integrity": "sha512-1/aa2clS0pue0HjckL62CsbhWWU35HARvBDXcJtYKbYR7LnIutmpxmXbuDMV9kEviD2lP/wACOgWmmwljghHyQ==",
|
||||
"requires": {
|
||||
"semver": "^5.4.1"
|
||||
}
|
||||
@@ -1939,13 +1969,13 @@
|
||||
}
|
||||
},
|
||||
"node-pre-gyp": {
|
||||
"version": "0.10.2",
|
||||
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.10.2.tgz",
|
||||
"integrity": "sha512-16lql9QTqs6KsB9fl3neWyZm02KxIKdI9FlJjrB0y7eMTP5Nyz+xalwPbOlw3iw7EejllJPmlJSnY711PLD1ug==",
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz",
|
||||
"integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==",
|
||||
"requires": {
|
||||
"detect-libc": "^1.0.2",
|
||||
"mkdirp": "^0.5.1",
|
||||
"needle": "^2.2.0",
|
||||
"needle": "^2.2.1",
|
||||
"nopt": "^4.0.1",
|
||||
"npm-packlist": "^1.1.6",
|
||||
"npmlog": "^4.0.2",
|
||||
@@ -1953,13 +1983,6 @@
|
||||
"rimraf": "^2.6.1",
|
||||
"semver": "^5.3.0",
|
||||
"tar": "^4"
|
||||
},
|
||||
"dependencies": {
|
||||
"detect-libc": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
|
||||
}
|
||||
}
|
||||
},
|
||||
"noop-logger": {
|
||||
@@ -1977,14 +2000,14 @@
|
||||
}
|
||||
},
|
||||
"npm-bundled": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.3.tgz",
|
||||
"integrity": "sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow=="
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz",
|
||||
"integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g=="
|
||||
},
|
||||
"npm-packlist": {
|
||||
"version": "1.1.10",
|
||||
"resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.10.tgz",
|
||||
"integrity": "sha512-AQC0Dyhzn4EiYEfIUjCdMl0JJ61I2ER9ukf/sLxJUcZHfo+VyEfz2rMJgLZSS1v30OxPQe1cN0LZA1xbcaVfWA==",
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.1.tgz",
|
||||
"integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==",
|
||||
"requires": {
|
||||
"ignore-walk": "^3.0.1",
|
||||
"npm-bundled": "^1.0.1"
|
||||
@@ -2187,21 +2210,22 @@
|
||||
"integrity": "sha1-EdHhK5y2TWPjDBQ6Mw9MH1Z9qF8="
|
||||
},
|
||||
"prebuild-install": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-4.0.0.tgz",
|
||||
"integrity": "sha512-7tayxeYboJX0RbVzdnKyGl2vhQRWr6qfClEXDhOkXjuaOKCw2q8aiuFhONRYVsG/czia7KhpykIlI2S2VaPunA==",
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.0.tgz",
|
||||
"integrity": "sha512-aaLVANlj4HgZweKttFNUVNRxDukytuIuxeK2boIMHjagNJCiVKWFsKF4tCE3ql3GbrD2tExPQ7/pwtEJcHNZeg==",
|
||||
"requires": {
|
||||
"detect-libc": "^1.0.3",
|
||||
"expand-template": "^1.0.2",
|
||||
"expand-template": "^2.0.3",
|
||||
"github-from-package": "0.0.0",
|
||||
"minimist": "^1.2.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"node-abi": "^2.2.0",
|
||||
"napi-build-utils": "^1.0.1",
|
||||
"node-abi": "^2.7.0",
|
||||
"noop-logger": "^0.1.1",
|
||||
"npmlog": "^4.0.1",
|
||||
"os-homedir": "^1.0.1",
|
||||
"pump": "^2.0.1",
|
||||
"rc": "^1.1.6",
|
||||
"rc": "^1.2.7",
|
||||
"simple-get": "^2.7.0",
|
||||
"tar-fs": "^1.13.0",
|
||||
"tunnel-agent": "^0.6.0",
|
||||
@@ -2212,6 +2236,16 @@
|
||||
"version": "1.2.0",
|
||||
"resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
|
||||
},
|
||||
"simple-get": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.1.tgz",
|
||||
"integrity": "sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw==",
|
||||
"requires": {
|
||||
"decompress-response": "^3.3.0",
|
||||
"once": "^1.3.1",
|
||||
"simple-concat": "^1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2443,50 +2477,63 @@
|
||||
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
|
||||
},
|
||||
"sharp": {
|
||||
"version": "0.20.8",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.20.8.tgz",
|
||||
"integrity": "sha512-A8NaPGWRDKpmHTi8sl2xzozYXhTQWBb/GaJ8ZPU7L/vKW8wVvd4Yq+isJ0c7p9sX5gnjPQcM3eOfHuvvnZ2fOQ==",
|
||||
"version": "0.22.1",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.22.1.tgz",
|
||||
"integrity": "sha512-lXzSk/FL5b/MpWrT1pQZneKe25stVjEbl6uhhJcTULm7PhmJgKKRbTDM/vtjyUuC/RLqL2PRyC4rpKwbv3soEw==",
|
||||
"requires": {
|
||||
"color": "^3.0.0",
|
||||
"color": "^3.1.1",
|
||||
"detect-libc": "^1.0.3",
|
||||
"fs-copy-file-sync": "^1.1.1",
|
||||
"nan": "^2.11.0",
|
||||
"nan": "^2.13.2",
|
||||
"npmlog": "^4.1.2",
|
||||
"prebuild-install": "^4.0.0",
|
||||
"semver": "^5.5.1",
|
||||
"simple-get": "^2.8.1",
|
||||
"tar": "^4.4.6",
|
||||
"prebuild-install": "^5.3.0",
|
||||
"semver": "^6.0.0",
|
||||
"simple-get": "^3.0.3",
|
||||
"tar": "^4.4.8",
|
||||
"tunnel-agent": "^0.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"chownr": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz",
|
||||
"integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g=="
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.3.4",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.4.tgz",
|
||||
"integrity": "sha512-mlouk1OHlaUE8Odt1drMtG1bAJA4ZA6B/ehysgV0LUIrDHdKgo1KorZq3pK0b/7Z7LJIQ12MNM6aC+Tn6lUZ5w==",
|
||||
"version": "2.3.5",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz",
|
||||
"integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==",
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"minizlib": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz",
|
||||
"integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==",
|
||||
"requires": {
|
||||
"minipass": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.5.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz",
|
||||
"integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw=="
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.0.0.tgz",
|
||||
"integrity": "sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ=="
|
||||
},
|
||||
"tar": {
|
||||
"version": "4.4.6",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.6.tgz",
|
||||
"integrity": "sha512-tMkTnh9EdzxyfW+6GK6fCahagXsnYk6kE6S9Gr9pjVdys769+laCTbodXDhPAjzVtEBazRgP0gYqOjnk9dQzLg==",
|
||||
"version": "4.4.8",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz",
|
||||
"integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==",
|
||||
"requires": {
|
||||
"chownr": "^1.0.1",
|
||||
"chownr": "^1.1.1",
|
||||
"fs-minipass": "^1.2.5",
|
||||
"minipass": "^2.3.3",
|
||||
"minizlib": "^1.1.0",
|
||||
"minipass": "^2.3.4",
|
||||
"minizlib": "^1.1.1",
|
||||
"mkdirp": "^0.5.0",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.2"
|
||||
@@ -2505,9 +2552,9 @@
|
||||
"integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY="
|
||||
},
|
||||
"simple-get": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.1.tgz",
|
||||
"integrity": "sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.0.3.tgz",
|
||||
"integrity": "sha512-Wvre/Jq5vgoz31Z9stYWPLn0PqRqmBDpFSdypAnHu5AvRVCYPRYGnvryNLiXu8GOBNDH82J2FRHUGMjjHUpXFw==",
|
||||
"requires": {
|
||||
"decompress-response": "^3.3.0",
|
||||
"once": "^1.3.1",
|
||||
@@ -2554,18 +2601,136 @@
|
||||
"integrity": "sha1-Nr54Mgr+WAH2zqPueLblqrlA6gw="
|
||||
},
|
||||
"sqlite3": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.0.1.tgz",
|
||||
"integrity": "sha512-i8LtU2fdEGFEt4Kcs7eNjYdGmnAQ8zWlaOv6Esbq/jfVfR0Qbn/1dgVyKebrMc2zN7h3oHsqla9zq7AJ0+34ZA==",
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.0.7.tgz",
|
||||
"integrity": "sha512-TGEeSBB8O48bEu8KUUMqzeB22WrfTxzhIf0lFm8wLTo3a6yJBonF2sPKMYrYtOne1F1t9AHAEn+DTISq8WebQg==",
|
||||
"requires": {
|
||||
"nan": "~2.10.0",
|
||||
"node-pre-gyp": "~0.10.1"
|
||||
"nan": "^2.12.1",
|
||||
"node-pre-gyp": "^0.11.0",
|
||||
"request": "^2.87.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"nan": {
|
||||
"version": "2.10.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
|
||||
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA=="
|
||||
"ajv": {
|
||||
"version": "6.10.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz",
|
||||
"integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==",
|
||||
"requires": {
|
||||
"fast-deep-equal": "^2.0.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
}
|
||||
},
|
||||
"aws4": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
|
||||
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
|
||||
},
|
||||
"combined-stream": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz",
|
||||
"integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==",
|
||||
"requires": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"extend": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
|
||||
},
|
||||
"fast-deep-equal": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
|
||||
"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
|
||||
},
|
||||
"form-data": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
|
||||
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.6",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
},
|
||||
"har-validator": {
|
||||
"version": "5.1.3",
|
||||
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
|
||||
"integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
|
||||
"requires": {
|
||||
"ajv": "^6.5.5",
|
||||
"har-schema": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.40.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
|
||||
"integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA=="
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.24",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
|
||||
"integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
|
||||
"requires": {
|
||||
"mime-db": "1.40.0"
|
||||
}
|
||||
},
|
||||
"oauth-sign": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
|
||||
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
|
||||
},
|
||||
"request": {
|
||||
"version": "2.88.0",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
|
||||
"integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
|
||||
"requires": {
|
||||
"aws-sign2": "~0.7.0",
|
||||
"aws4": "^1.8.0",
|
||||
"caseless": "~0.12.0",
|
||||
"combined-stream": "~1.0.6",
|
||||
"extend": "~3.0.2",
|
||||
"forever-agent": "~0.6.1",
|
||||
"form-data": "~2.3.2",
|
||||
"har-validator": "~5.1.0",
|
||||
"http-signature": "~1.2.0",
|
||||
"is-typedarray": "~1.0.0",
|
||||
"isstream": "~0.1.2",
|
||||
"json-stringify-safe": "~5.0.1",
|
||||
"mime-types": "~2.1.19",
|
||||
"oauth-sign": "~0.9.0",
|
||||
"performance-now": "^2.1.0",
|
||||
"qs": "~6.5.2",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"tough-cookie": "~2.4.3",
|
||||
"tunnel-agent": "^0.6.0",
|
||||
"uuid": "^3.3.2"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
|
||||
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
|
||||
"requires": {
|
||||
"psl": "^1.1.24",
|
||||
"punycode": "^1.4.1"
|
||||
}
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
|
||||
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@@ -20,7 +20,7 @@
|
||||
],
|
||||
"owner": "Laurent Cozic"
|
||||
},
|
||||
"version": "1.0.124",
|
||||
"version": "1.0.139",
|
||||
"bin": {
|
||||
"joplin": "./main.js"
|
||||
},
|
||||
@@ -33,7 +33,9 @@
|
||||
"base-64": "^0.1.0",
|
||||
"compare-version": "^0.1.2",
|
||||
"diacritics": "^1.3.0",
|
||||
"diff-match-patch": "^1.0.4",
|
||||
"es6-promise-pool": "^2.5.0",
|
||||
"file-uri-to-path": "^1.0.0",
|
||||
"follow-redirects": "^1.2.4",
|
||||
"form-data": "^2.1.4",
|
||||
"fs-extra": "^5.0.0",
|
||||
@@ -42,7 +44,7 @@
|
||||
"image-data-uri": "^2.0.0",
|
||||
"image-type": "^3.0.0",
|
||||
"joplin-turndown": "^4.0.11",
|
||||
"joplin-turndown-plugin-gfm": "^1.0.7",
|
||||
"joplin-turndown-plugin-gfm": "^1.0.8",
|
||||
"jssha": "^2.3.0",
|
||||
"levenshtein": "^1.0.5",
|
||||
"lodash": "^4.17.4",
|
||||
@@ -60,9 +62,9 @@
|
||||
"redux": "^3.7.2",
|
||||
"sax": "^1.2.2",
|
||||
"server-destroy": "^1.0.1",
|
||||
"sharp": "^0.20.8",
|
||||
"sharp": "^0.22.1",
|
||||
"sprintf-js": "^1.1.1",
|
||||
"sqlite3": "^4.0.1",
|
||||
"sqlite3": "^4.0.7",
|
||||
"string-padding": "^1.0.2",
|
||||
"string-to-stream": "^1.1.0",
|
||||
"strip-ansi": "^4.0.0",
|
||||
|
@@ -31,6 +31,7 @@ npm test tests-build/models_Folder.js
|
||||
npm test tests-build/models_ItemChange.js
|
||||
npm test tests-build/models_Note.js
|
||||
npm test tests-build/models_Resource.js
|
||||
npm test tests-build/models_Revision.js
|
||||
npm test tests-build/models_Setting.js
|
||||
npm test tests-build/models_Tag.js
|
||||
npm test tests-build/pathUtils.js
|
||||
@@ -38,6 +39,7 @@ npm test tests-build/services_InteropService.js
|
||||
npm test tests-build/services_ResourceService.js
|
||||
npm test tests-build/services_rest_Api.js
|
||||
npm test tests-build/services_SearchEngine.js
|
||||
npm test tests-build/services_Revision.js
|
||||
npm test tests-build/StringUtils.js
|
||||
npm test tests-build/synchronizer.js
|
||||
npm test tests-build/urlUtils.js
|
@@ -35,7 +35,7 @@ describe('EnexToMd', function() {
|
||||
const htmlPath = basePath + '/' + htmlFilename;
|
||||
const mdPath = basePath + '/' + filename(htmlFilename) + '.md';
|
||||
|
||||
// if (htmlFilename !== 'text2.html') continue;
|
||||
// if (htmlFilename !== 'list5.html') continue;
|
||||
|
||||
const html = await shim.fsDriver().readFile(htmlPath);
|
||||
let expectedMd = await shim.fsDriver().readFile(mdPath);
|
||||
|
@@ -25,8 +25,7 @@ describe('Encryption', function() {
|
||||
|
||||
beforeEach(async (done) => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
//await setupDatabaseAndSynchronizer(2);
|
||||
//await switchClient(1);
|
||||
await switchClient(1);
|
||||
service = new EncryptionService();
|
||||
BaseItem.encryptionService_ = service;
|
||||
Setting.setValue('encryption.enabled', true);
|
||||
|
16
CliClient/tests/enex_to_md/list5.html
Normal file
16
CliClient/tests/enex_to_md/list5.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<ul>
|
||||
<li lang="en-US">
|
||||
<div>Protocols</div>
|
||||
</li>
|
||||
<ul type="circle">
|
||||
<li lang="en-US">
|
||||
<div>two common network protocols used to send data packets over a network</div>
|
||||
</li>
|
||||
<li lang="en-US">
|
||||
<div>TCP Transmission control protocol</div>
|
||||
</li>
|
||||
</ul>
|
||||
<li lang="en-US">
|
||||
<div>Network port - a network port is a process-specific or an application-specific software construct serving as a communication endpoint, which is used by the Transport Layer protocols of Internet Protocol suite, such as UDP and TCP</div>
|
||||
</li>
|
||||
</ul>
|
7
CliClient/tests/enex_to_md/list5.md
Normal file
7
CliClient/tests/enex_to_md/list5.md
Normal file
@@ -0,0 +1,7 @@
|
||||
- Protocols
|
||||
|
||||
- two common network protocols used to send data packets over a network
|
||||
|
||||
- TCP Transmission control protocol
|
||||
|
||||
- Network port - a network port is a process-specific or an application-specific software construct serving as a communication endpoint, which is used by the Transport Layer protocols of Internet Protocol suite, such as UDP and TCP
|
5
CliClient/tests/enex_to_md/multiline_attribute.html
Normal file
5
CliClient/tests/enex_to_md/multiline_attribute.html
Normal file
@@ -0,0 +1,5 @@
|
||||
<img src="https://joplinapp.org/images/Icon512.png" alt="multiple
|
||||
lines
|
||||
are
|
||||
possible
|
||||
I guess"/><img src="https://joplinapp.org/images/Icon512.png" alt="This should ] be escaped"/>
|
1
CliClient/tests/enex_to_md/multiline_attribute.md
Normal file
1
CliClient/tests/enex_to_md/multiline_attribute.md
Normal file
@@ -0,0 +1 @@
|
||||
![This should \] be escaped](https://joplinapp.org/images/Icon512.png)
|
42
CliClient/tests/html_to_md/table_with_empty_header.html
Normal file
42
CliClient/tests/html_to_md/table_with_empty_header.html
Normal file
@@ -0,0 +1,42 @@
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Official Things</strong></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://nim-lang.org">Web Site</a></td>
|
||||
<td>The project’s entry point</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/nim-lang/nim">Source</a></td>
|
||||
<td>The github project</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/nim-lang/nimble">nimble</a></td>
|
||||
<td>The nim package manager</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/dom96/choosenim">choosenim</a></td>
|
||||
<td>Toolchain installer</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Community</strong></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://forum.nim-lang.org">Forums</a></td>
|
||||
<td>An async discussion board</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
9
CliClient/tests/html_to_md/table_with_empty_header.md
Normal file
9
CliClient/tests/html_to_md/table_with_empty_header.md
Normal file
@@ -0,0 +1,9 @@
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **Official Things** | |
|
||||
| [Web Site](https://nim-lang.org) | The project’s entry point |
|
||||
| [Source](https://github.com/nim-lang/nim) | The github project |
|
||||
| [nimble](https://github.com/nim-lang/nimble) | The nim package manager |
|
||||
| [choosenim](https://github.com/dom96/choosenim) | Toolchain installer |
|
||||
| **Community** | |
|
||||
| [Forums](https://forum.nim-lang.org) | An async discussion board |
|
@@ -47,5 +47,20 @@ describe('models_BaseItem', function() {
|
||||
|
||||
expect('ignore_me' in unserialized).toBe(false);
|
||||
}));
|
||||
|
||||
it('should not modify title when unserializing', asyncTest(async () => {
|
||||
let folder1 = await Folder.save({ title: "" });
|
||||
let folder2 = await Folder.save({ title: "folder1" });
|
||||
|
||||
let serialized1 = await Folder.serialize(folder1);
|
||||
let unserialized1 = await Folder.unserialize(serialized1);
|
||||
|
||||
expect(unserialized1.title).toBe(folder1.title);
|
||||
|
||||
let serialized2 = await Folder.serialize(folder2);
|
||||
let unserialized2 = await Folder.unserialize(serialized2);
|
||||
|
||||
expect(unserialized2.title).toBe(folder2.title);
|
||||
}));
|
||||
|
||||
});
|
@@ -1,7 +1,7 @@
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const { asyncTest, fileContentEqual, revisionService, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const SearchEngine = require('lib/services/SearchEngine');
|
||||
const ResourceService = require('lib/services/ResourceService');
|
||||
const ItemChangeUtils = require('lib/services/ItemChangeUtils');
|
||||
@@ -34,19 +34,17 @@ describe('models_ItemChange', function() {
|
||||
const resourceService = new ResourceService();
|
||||
|
||||
await searchEngine.syncTables();
|
||||
|
||||
// If we run this now, it should not delete any change because
|
||||
// the resource service has not yet processed the change
|
||||
await ItemChangeUtils.deleteProcessedChanges();
|
||||
|
||||
expect(await ItemChange.lastChangeId()).toBe(1);
|
||||
|
||||
await resourceService.indexNoteResources();
|
||||
|
||||
// Now that the resource service has processed the change,
|
||||
// the change can be deleted.
|
||||
await ItemChangeUtils.deleteProcessedChanges();
|
||||
expect(await ItemChange.lastChangeId()).toBe(1);
|
||||
|
||||
await revisionService().collectRevisions();
|
||||
await ItemChangeUtils.deleteProcessedChanges();
|
||||
expect(await ItemChange.lastChangeId()).toBe(0);
|
||||
}));
|
||||
|
||||
|
@@ -86,5 +86,32 @@ describe('models_Note', function() {
|
||||
expect(changedNote === note1).toBe(false);
|
||||
expect(!!changedNote.is_todo).toBe(false);
|
||||
}));
|
||||
|
||||
it('should serialize and unserialize without modifying data', asyncTest(async () => {
|
||||
let folder1 = await Folder.save({ title: "folder1"});
|
||||
const testCases = [
|
||||
[ {title: '', body:'Body and no title\nSecond line\nThird Line', parent_id: folder1.id},
|
||||
'', 'Body and no title\nSecond line\nThird Line'],
|
||||
[ {title: 'Note title', body:'Body and title', parent_id: folder1.id},
|
||||
'Note title', 'Body and title'],
|
||||
[ {title: 'Title and no body', body:'', parent_id: folder1.id},
|
||||
'Title and no body', ''],
|
||||
]
|
||||
|
||||
for (let i = 0; i < testCases.length; i++) {
|
||||
const t = testCases[i];
|
||||
|
||||
const input = t[0];
|
||||
const expectedTitle = t[1];
|
||||
const expectedBody = t[1];
|
||||
|
||||
let note1 = await Note.save(input);
|
||||
let serialized = await Note.serialize(note1);
|
||||
let unserialized = await Note.unserialize( serialized);
|
||||
|
||||
expect(unserialized.title).toBe(input.title);
|
||||
expect(unserialized.body).toBe(input.body);
|
||||
}
|
||||
}));
|
||||
|
||||
});
|
@@ -8,10 +8,14 @@ const Resource = require('lib/models/Resource.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const { shim } = require('lib/shim');
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000; // The first test is slow because the database needs to be built
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
});
|
||||
|
||||
const testImagePath = __dirname + '/../tests/support/photo.jpg';
|
||||
|
||||
describe('models_Resource', function() {
|
||||
|
||||
beforeEach(async (done) => {
|
||||
@@ -23,7 +27,7 @@ describe('models_Resource', function() {
|
||||
it('should have a "done" fetch_status when created locally', asyncTest(async () => {
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
|
||||
await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
|
||||
await shim.attachFileToNote(note1, testImagePath);
|
||||
let resource1 = (await Resource.all())[0];
|
||||
let ls = await Resource.localState(resource1);
|
||||
expect(ls.fetch_status).toBe(Resource.FETCH_STATUS_DONE);
|
||||
@@ -32,7 +36,7 @@ describe('models_Resource', function() {
|
||||
it('should have a default local state', asyncTest(async () => {
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
|
||||
await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
|
||||
await shim.attachFileToNote(note1, testImagePath);
|
||||
let resource1 = (await Resource.all())[0];
|
||||
let ls = await Resource.localState(resource1);
|
||||
expect(!ls.id).toBe(true);
|
||||
@@ -43,7 +47,7 @@ describe('models_Resource', function() {
|
||||
it('should save and delete local state', asyncTest(async () => {
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
|
||||
await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
|
||||
await shim.attachFileToNote(note1, testImagePath);
|
||||
let resource1 = (await Resource.all())[0];
|
||||
await Resource.setLocalState(resource1, { fetch_status: Resource.FETCH_STATUS_IDLE });
|
||||
|
||||
@@ -56,4 +60,31 @@ describe('models_Resource', function() {
|
||||
expect(!ls.id).toBe(true);
|
||||
}));
|
||||
|
||||
it('should resize the resource if the image is below the required dimensions', asyncTest(async () => {
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
|
||||
const previousMax = Resource.IMAGE_MAX_DIMENSION;
|
||||
Resource.IMAGE_MAX_DIMENSION = 5;
|
||||
await shim.attachFileToNote(note1, testImagePath);
|
||||
Resource.IMAGE_MAX_DIMENSION = previousMax;
|
||||
let resource1 = (await Resource.all())[0];
|
||||
|
||||
const originalStat = await shim.fsDriver().stat(testImagePath);
|
||||
const newStat = await shim.fsDriver().stat(Resource.fullPath(resource1));
|
||||
|
||||
expect(newStat.size < originalStat.size).toBe(true);
|
||||
}));
|
||||
|
||||
it('should not resize the resource if the image is below the required dimensions', asyncTest(async () => {
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
|
||||
await shim.attachFileToNote(note1, testImagePath);
|
||||
let resource1 = (await Resource.all())[0];
|
||||
|
||||
const originalStat = await shim.fsDriver().stat(testImagePath);
|
||||
const newStat = await shim.fsDriver().stat(Resource.fullPath(resource1));
|
||||
|
||||
expect(originalStat.size).toBe(newStat.size);
|
||||
}));
|
||||
|
||||
});
|
104
CliClient/tests/models_Revision.js
Normal file
104
CliClient/tests/models_Revision.js
Normal file
@@ -0,0 +1,104 @@
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const NoteTag = require('lib/models/NoteTag.js');
|
||||
const Tag = require('lib/models/Tag.js');
|
||||
const Revision = require('lib/models/Revision.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const { shim } = require('lib/shim');
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
});
|
||||
|
||||
describe('models_Revision', function() {
|
||||
|
||||
beforeEach(async (done) => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
await switchClient(1);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should create patches of text and apply it', asyncTest(async () => {
|
||||
const note1 = await Note.save({ body: 'my note\nsecond line' });
|
||||
|
||||
const patch = Revision.createTextPatch(note1.body, 'my new note\nsecond line');
|
||||
const merged = Revision.applyTextPatch(note1.body, patch);
|
||||
|
||||
expect(merged).toBe('my new note\nsecond line');
|
||||
}));
|
||||
|
||||
it('should create patches of objects and apply it', asyncTest(async () => {
|
||||
const oldObject = {
|
||||
one: '123',
|
||||
two: '456',
|
||||
three: '789',
|
||||
};
|
||||
|
||||
const newObject = {
|
||||
one: '123',
|
||||
three: '999',
|
||||
}
|
||||
|
||||
const patch = Revision.createObjectPatch(oldObject, newObject);
|
||||
const merged = Revision.applyObjectPatch(oldObject, patch);
|
||||
|
||||
expect(JSON.stringify(merged)).toBe(JSON.stringify(newObject));
|
||||
}));
|
||||
|
||||
it('should move target revision to the top', asyncTest(async () => {
|
||||
const revs = [
|
||||
{ id: '123' },
|
||||
{ id: '456' },
|
||||
{ id: '789' },
|
||||
];
|
||||
|
||||
let newRevs;
|
||||
newRevs = Revision.moveRevisionToTop({ id: '456' }, revs);
|
||||
expect(newRevs[0].id).toBe('123');
|
||||
expect(newRevs[1].id).toBe('789');
|
||||
expect(newRevs[2].id).toBe('456');
|
||||
|
||||
newRevs = Revision.moveRevisionToTop({ id: '789' }, revs);
|
||||
expect(newRevs[0].id).toBe('123');
|
||||
expect(newRevs[1].id).toBe('456');
|
||||
expect(newRevs[2].id).toBe('789');
|
||||
}));
|
||||
|
||||
it('should create patch stats', asyncTest(async () => {
|
||||
const tests = [
|
||||
{
|
||||
patch: `@@ -625,16 +625,48 @@
|
||||
rrupted download
|
||||
+%0A- %5B %5D Fix mobile screen options`,
|
||||
expected: [-0, +32],
|
||||
},
|
||||
{
|
||||
patch: `@@ -564,17 +564,17 @@
|
||||
ages%0A- %5B
|
||||
-
|
||||
+x
|
||||
%5D Check `,
|
||||
expected: [-1, +1],
|
||||
},
|
||||
{
|
||||
patch: `@@ -1022,56 +1022,415 @@
|
||||
.%0A%0A#
|
||||
- How to view a note history%0A%0AWhile all the apps
|
||||
+%C2%A0How does it work?%0A%0AAll the apps save a version of the modified notes every 10 minutes.
|
||||
%0A%0A# `,
|
||||
expected: [-(19+27+2), 17+67+4],
|
||||
},
|
||||
];
|
||||
|
||||
for (const test of tests) {
|
||||
const stats = Revision.patchStats(test.patch);
|
||||
expect(stats.removed).toBe(-test.expected[0]);
|
||||
expect(stats.added).toBe(test.expected[1]);
|
||||
}
|
||||
}));
|
||||
|
||||
});
|
420
CliClient/tests/services_Revision.js
Normal file
420
CliClient/tests/services_Revision.js
Normal file
@@ -0,0 +1,420 @@
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const { asyncTest, fileContentEqual, setupDatabase, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const NoteTag = require('lib/models/NoteTag.js');
|
||||
const ItemChange = require('lib/models/ItemChange.js');
|
||||
const Tag = require('lib/models/Tag.js');
|
||||
const Revision = require('lib/models/Revision.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const RevisionService = require('lib/services/RevisionService.js');
|
||||
const { shim } = require('lib/shim');
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
});
|
||||
|
||||
describe('services_Revision', function() {
|
||||
|
||||
beforeEach(async (done) => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
await switchClient(1);
|
||||
Setting.setValue('revisionService.intervalBetweenRevisions', 0)
|
||||
done();
|
||||
});
|
||||
|
||||
it('should create diff and rebuild notes', asyncTest(async () => {
|
||||
const service = new RevisionService();
|
||||
|
||||
const n1_v1 = await Note.save({ title: '', author: 'testing' });
|
||||
await service.collectRevisions();
|
||||
await Note.save({ id: n1_v1.id, title: 'hello', author: 'testing' });
|
||||
await service.collectRevisions();
|
||||
const n1_v2 = await Note.save({ id: n1_v1.id, title: 'hello welcome', author: '' });
|
||||
await service.collectRevisions();
|
||||
|
||||
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, n1_v1.id);
|
||||
expect(revisions.length).toBe(2);
|
||||
expect(revisions[1].parent_id).toBe(revisions[0].id);
|
||||
|
||||
const rev1 = await service.revisionNote(revisions, 0);
|
||||
expect(rev1.title).toBe('hello');
|
||||
expect(rev1.author).toBe('testing');
|
||||
|
||||
const rev2 = await service.revisionNote(revisions, 1);
|
||||
expect(rev2.title).toBe('hello welcome');
|
||||
expect(rev2.author).toBe('');
|
||||
|
||||
await time.sleep(0.5);
|
||||
|
||||
await service.deleteOldRevisions(400);
|
||||
const revisions2 = await Revision.allByType(BaseModel.TYPE_NOTE, n1_v1.id);
|
||||
expect(revisions2.length).toBe(0);
|
||||
}));
|
||||
|
||||
it('should delete old revisions (1 note, 2 rev)', asyncTest(async () => {
|
||||
const service = new RevisionService();
|
||||
|
||||
const n1_v0 = await Note.save({ title: '' });
|
||||
const n1_v1 = await Note.save({ id: n1_v0.id, title: 'hello' });
|
||||
await service.collectRevisions();
|
||||
await time.sleep(1);
|
||||
const n1_v2 = await Note.save({ id: n1_v1.id, title: 'hello welcome' });
|
||||
await service.collectRevisions();
|
||||
expect((await Revision.allByType(BaseModel.TYPE_NOTE, n1_v1.id)).length).toBe(2);
|
||||
|
||||
await service.deleteOldRevisions(1000);
|
||||
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, n1_v1.id);
|
||||
expect(revisions.length).toBe(1);
|
||||
|
||||
const rev1 = await service.revisionNote(revisions, 0);
|
||||
expect(rev1.title).toBe('hello welcome');
|
||||
}));
|
||||
|
||||
it('should delete old revisions (1 note, 3 rev)', asyncTest(async () => {
|
||||
const service = new RevisionService();
|
||||
|
||||
const n1_v0 = await Note.save({ title: '' });
|
||||
const n1_v1 = await Note.save({ id: n1_v0.id, title: 'one' });
|
||||
await service.collectRevisions();
|
||||
await time.sleep(1);
|
||||
const n1_v2 = await Note.save({ id: n1_v1.id, title: 'one two' });
|
||||
await service.collectRevisions();
|
||||
await time.sleep(1);
|
||||
const n1_v3 = await Note.save({ id: n1_v1.id, title: 'one two three' });
|
||||
await service.collectRevisions();
|
||||
|
||||
{
|
||||
await service.deleteOldRevisions(2000);
|
||||
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, n1_v1.id);
|
||||
expect(revisions.length).toBe(2);
|
||||
|
||||
const rev1 = await service.revisionNote(revisions, 0);
|
||||
expect(rev1.title).toBe('one two');
|
||||
|
||||
const rev2 = await service.revisionNote(revisions, 1);
|
||||
expect(rev2.title).toBe('one two three');
|
||||
}
|
||||
|
||||
{
|
||||
await service.deleteOldRevisions(1000);
|
||||
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, n1_v1.id);
|
||||
expect(revisions.length).toBe(1);
|
||||
|
||||
const rev1 = await service.revisionNote(revisions, 0);
|
||||
expect(rev1.title).toBe('one two three');
|
||||
}
|
||||
}));
|
||||
|
||||
it('should delete old revisions (2 notes, 2 rev)', asyncTest(async () => {
|
||||
const service = new RevisionService();
|
||||
|
||||
const n1_v0 = await Note.save({ title: '' });
|
||||
const n1_v1 = await Note.save({ id: n1_v0.id, title: 'note 1' });
|
||||
const n2_v0 = await Note.save({ title: '' });
|
||||
const n2_v1 = await Note.save({ id: n2_v0.id, title: 'note 2' });
|
||||
await service.collectRevisions();
|
||||
await time.sleep(1);
|
||||
const n1_v2 = await Note.save({ id: n1_v1.id, title: 'note 1 (v2)' });
|
||||
const n2_v2 = await Note.save({ id: n2_v1.id, title: 'note 2 (v2)' });
|
||||
await service.collectRevisions();
|
||||
|
||||
expect((await Revision.all()).length).toBe(4);
|
||||
|
||||
await service.deleteOldRevisions(1000);
|
||||
|
||||
{
|
||||
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, n1_v1.id);
|
||||
expect(revisions.length).toBe(1);
|
||||
const rev1 = await service.revisionNote(revisions, 0);
|
||||
expect(rev1.title).toBe('note 1 (v2)');
|
||||
}
|
||||
|
||||
{
|
||||
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, n2_v1.id);
|
||||
expect(revisions.length).toBe(1);
|
||||
const rev1 = await service.revisionNote(revisions, 0);
|
||||
expect(rev1.title).toBe('note 2 (v2)');
|
||||
}
|
||||
}));
|
||||
|
||||
it('should handle conflicts', asyncTest(async () => {
|
||||
const service = new RevisionService();
|
||||
|
||||
// A conflict happens in this case:
|
||||
// - Device 1 creates note1 (rev1)
|
||||
// - Device 2 syncs and get note1
|
||||
// - Device 1 modifies note1 (rev2)
|
||||
// - Device 2 modifies note1 (rev3)
|
||||
// When reconstructing the notes based on the revisions, we need to make sure it follow the right
|
||||
// "path". For example, to reconstruct the note at rev2 it would be:
|
||||
// rev1 => rev2
|
||||
// To reconstruct the note at rev3 it would be:
|
||||
// rev1 => rev3
|
||||
// And not, for example, rev1 => rev2 => rev3
|
||||
|
||||
const n1_v1 = await Note.save({ title: 'hello' });
|
||||
const noteId = n1_v1.id;
|
||||
const rev1 = await service.createNoteRevision_(n1_v1);
|
||||
const n1_v2 = await Note.save({ id: noteId, title: 'hello Paul' });
|
||||
const rev2 = await service.createNoteRevision_(n1_v2, rev1.id);
|
||||
const n1_v3 = await Note.save({ id: noteId, title: 'hello John' });
|
||||
const rev3 = await service.createNoteRevision_(n1_v3, rev1.id);
|
||||
|
||||
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, noteId);
|
||||
expect(revisions.length).toBe(3);
|
||||
expect(revisions[1].parent_id).toBe(rev1.id);
|
||||
expect(revisions[2].parent_id).toBe(rev1.id);
|
||||
|
||||
const revNote1 = await service.revisionNote(revisions, 0);
|
||||
const revNote2 = await service.revisionNote(revisions, 1);
|
||||
const revNote3 = await service.revisionNote(revisions, 2);
|
||||
expect(revNote1.title).toBe('hello');
|
||||
expect(revNote2.title).toBe('hello Paul');
|
||||
expect(revNote3.title).toBe('hello John');
|
||||
}));
|
||||
|
||||
it('should create a revision for notes that are older than a given interval', asyncTest(async () => {
|
||||
const n1 = await Note.save({ title: 'hello' });
|
||||
const noteId = n1.id;
|
||||
|
||||
await sleep(0.1);
|
||||
|
||||
// Set the interval in such a way that the note is considered an old one.
|
||||
Setting.setValue('revisionService.oldNoteInterval', 50);
|
||||
|
||||
// A revision is created the first time a note is overwritten with new content, and
|
||||
// if this note doesn't already have an existing revision.
|
||||
// This is mostly to handle old notes that existed before the revision service. If these
|
||||
// old notes are changed, there's a chance it's accidental or due to some bug, so we
|
||||
// want to preserve a revision just in case.
|
||||
|
||||
{
|
||||
await Note.save({ id: noteId, title: 'hello 2' });
|
||||
await revisionService().collectRevisions(); // Rev for old note created + Rev for new note
|
||||
const all = await Revision.allByType(BaseModel.TYPE_NOTE, noteId);
|
||||
expect(all.length).toBe(2);
|
||||
const revNote1 = await revisionService().revisionNote(all, 0);
|
||||
const revNote2 = await revisionService().revisionNote(all, 1);
|
||||
expect(revNote1.title).toBe('hello');
|
||||
expect(revNote2.title).toBe('hello 2');
|
||||
}
|
||||
|
||||
// If the note is saved a third time, we don't automatically create a revision. One
|
||||
// will be created x minutes later when the service collects revisions.
|
||||
|
||||
{
|
||||
await Note.save({ id: noteId, title: 'hello 3' });
|
||||
const all = await Revision.allByType(BaseModel.TYPE_NOTE, noteId);
|
||||
expect(all.length).toBe(2);
|
||||
}
|
||||
}));
|
||||
|
||||
it('should create a revision for notes that get deleted (recyle bin)', asyncTest(async () => {
|
||||
const n1 = await Note.save({ title: 'hello' });
|
||||
const noteId = n1.id;
|
||||
|
||||
await Note.delete(noteId);
|
||||
|
||||
await revisionService().collectRevisions();
|
||||
|
||||
const all = await Revision.allByType(BaseModel.TYPE_NOTE, noteId);
|
||||
expect(all.length).toBe(1);
|
||||
const rev1 = await revisionService().revisionNote(all, 0);
|
||||
expect(rev1.title).toBe('hello');
|
||||
}));
|
||||
|
||||
it('should not create a revision for notes that get deleted if there is already a revision', asyncTest(async () => {
|
||||
const n1 = await Note.save({ title: 'hello' });
|
||||
await revisionService().collectRevisions();
|
||||
const noteId = n1.id;
|
||||
await Note.save({ id: noteId, title: 'hello Paul' });
|
||||
await revisionService().collectRevisions(); // REV 1
|
||||
|
||||
expect((await Revision.allByType(BaseModel.TYPE_NOTE, n1.id)).length).toBe(1);
|
||||
|
||||
await Note.delete(noteId);
|
||||
|
||||
// At this point there is no need to create a new revision for the deleted note
|
||||
// because we already have the latest version as REV 1
|
||||
await revisionService().collectRevisions();
|
||||
|
||||
expect((await Revision.allByType(BaseModel.TYPE_NOTE, n1.id)).length).toBe(1);
|
||||
}));
|
||||
|
||||
it('should not create a revision for new note the first time they are saved', asyncTest(async () => {
|
||||
const n1 = await Note.save({ title: 'hello' });
|
||||
|
||||
{
|
||||
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, n1.id);
|
||||
expect(revisions.length).toBe(0);
|
||||
}
|
||||
|
||||
await revisionService().collectRevisions();
|
||||
|
||||
{
|
||||
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, n1.id);
|
||||
expect(revisions.length).toBe(0);
|
||||
}
|
||||
}));
|
||||
|
||||
it('should abort collecting revisions when one of them is encrypted', asyncTest(async () => {
|
||||
const n1 = await Note.save({ title: 'hello' }); // CHANGE 1
|
||||
await revisionService().collectRevisions();
|
||||
await Note.save({ id: n1.id, title: 'hello Ringo' }); // CHANGE 2
|
||||
await revisionService().collectRevisions();
|
||||
await Note.save({ id: n1.id, title: 'hello George' }); // CHANGE 3
|
||||
await revisionService().collectRevisions();
|
||||
|
||||
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, n1.id);
|
||||
expect(revisions.length).toBe(2);
|
||||
|
||||
const encryptedRevId = revisions[0].id;
|
||||
|
||||
// Simulate receiving an encrypted revision
|
||||
await Revision.save({ id: encryptedRevId, encryption_applied: 1 });
|
||||
await Note.save({ id: n1.id, title: 'hello Paul' }); // CHANGE 4
|
||||
|
||||
await revisionService().collectRevisions();
|
||||
|
||||
// Although change 4 is a note update, check that it has not been processed
|
||||
// by the collector, due to one of the revisions being encrypted.
|
||||
expect(await ItemChange.lastChangeId()).toBe(4);
|
||||
expect(Setting.value('revisionService.lastProcessedChangeId')).toBe(3);
|
||||
|
||||
// Simulate the revision being decrypted by DecryptionService
|
||||
await Revision.save({ id: encryptedRevId, encryption_applied: 0 });
|
||||
|
||||
await revisionService().collectRevisions();
|
||||
|
||||
// Now that the revision has been decrypted, all the changes can be processed
|
||||
expect(await ItemChange.lastChangeId()).toBe(4);
|
||||
expect(Setting.value('revisionService.lastProcessedChangeId')).toBe(4);
|
||||
}));
|
||||
|
||||
it('should not delete old revisions if one of them is still encrypted (1)', asyncTest(async () => {
|
||||
// Test case 1: Two revisions and the first one is encrypted.
|
||||
// Calling deleteOldRevisions() with low TTL, which means all revisions
|
||||
// should be deleted, but they won't be due to the encrypted one.
|
||||
|
||||
const n1_v0 = await Note.save({ title: '' });
|
||||
const n1_v1 = await Note.save({ id: n1_v0.id, title: 'hello' });
|
||||
await revisionService().collectRevisions(); // REV 1
|
||||
await time.sleep(0.1);
|
||||
const n1_v2 = await Note.save({ id: n1_v1.id, title: 'hello welcome' });
|
||||
await revisionService().collectRevisions(); // REV 2
|
||||
await time.sleep(0.1);
|
||||
|
||||
expect((await Revision.all()).length).toBe(2);
|
||||
|
||||
const revisions = await Revision.all();
|
||||
await Revision.save({ id: revisions[0].id, encryption_applied: 1 });
|
||||
|
||||
await revisionService().deleteOldRevisions(0);
|
||||
expect((await Revision.all()).length).toBe(2);
|
||||
|
||||
await Revision.save({ id: revisions[0].id, encryption_applied: 0 });
|
||||
|
||||
await revisionService().deleteOldRevisions(0);
|
||||
expect((await Revision.all()).length).toBe(0);
|
||||
}));
|
||||
|
||||
it('should not delete old revisions if one of them is still encrypted (2)', asyncTest(async () => {
|
||||
// Test case 2: Two revisions and the first one is encrypted.
|
||||
// Calling deleteOldRevisions() with higher TTL, which means the oldest
|
||||
// revision should be deleted, but it won't be due to the encrypted one.
|
||||
|
||||
const n1_v0 = await Note.save({ title: '' });
|
||||
const n1_v1 = await Note.save({ id: n1_v0.id, title: 'hello' });
|
||||
await revisionService().collectRevisions(); // REV 1
|
||||
await time.sleep(0.5);
|
||||
const n1_v2 = await Note.save({ id: n1_v1.id, title: 'hello welcome' });
|
||||
await revisionService().collectRevisions(); // REV 2
|
||||
|
||||
expect((await Revision.all()).length).toBe(2);
|
||||
|
||||
const revisions = await Revision.all();
|
||||
await Revision.save({ id: revisions[0].id, encryption_applied: 1 });
|
||||
|
||||
await revisionService().deleteOldRevisions(500);
|
||||
expect((await Revision.all()).length).toBe(2);
|
||||
}));
|
||||
|
||||
it('should not delete old revisions if one of them is still encrypted (3)', asyncTest(async () => {
|
||||
// Test case 2: Two revisions and the second one is encrypted.
|
||||
// Calling deleteOldRevisions() with higher TTL, which means the oldest
|
||||
// revision should be deleted, but it won't be due to the encrypted one.
|
||||
|
||||
const n1_v0 = await Note.save({ title: '' });
|
||||
const n1_v1 = await Note.save({ id: n1_v0.id, title: 'hello' });
|
||||
await revisionService().collectRevisions(); // REV 1
|
||||
await time.sleep(0.5);
|
||||
const n1_v2 = await Note.save({ id: n1_v1.id, title: 'hello welcome' });
|
||||
await revisionService().collectRevisions(); // REV 2
|
||||
|
||||
expect((await Revision.all()).length).toBe(2);
|
||||
|
||||
const revisions = await Revision.all();
|
||||
await Revision.save({ id: revisions[1].id, encryption_applied: 1 });
|
||||
|
||||
await revisionService().deleteOldRevisions(500);
|
||||
expect((await Revision.all()).length).toBe(2);
|
||||
|
||||
await Revision.save({ id: revisions[1].id, encryption_applied: 0 });
|
||||
|
||||
await revisionService().deleteOldRevisions(500);
|
||||
expect((await Revision.all()).length).toBe(1);
|
||||
}));
|
||||
|
||||
it('should not create a revision if the note has not changed', asyncTest(async () => {
|
||||
const n1_v0 = await Note.save({ title: '' });
|
||||
const n1_v1 = await Note.save({ id: n1_v0.id, title: 'hello' });
|
||||
await revisionService().collectRevisions(); // REV 1
|
||||
expect((await Revision.all()).length).toBe(1);
|
||||
|
||||
const n1_v2 = await Note.save({ id: n1_v0.id, title: 'hello' });
|
||||
await revisionService().collectRevisions(); // Note has not changed (except its timestamp) so don't create a revision
|
||||
expect((await Revision.all()).length).toBe(1);
|
||||
}));
|
||||
|
||||
it('should preserve user update time', asyncTest(async () => {
|
||||
// user_updated_time is kind of tricky and can be changed automatically in various
|
||||
// places so make sure it is saved correctly with the revision
|
||||
|
||||
const n1_v0 = await Note.save({ title: '' });
|
||||
const n1_v1 = await Note.save({ id: n1_v0.id, title: 'hello' });
|
||||
await revisionService().collectRevisions(); // REV 1
|
||||
expect((await Revision.all()).length).toBe(1);
|
||||
|
||||
const userUpdatedTime = Date.now() - 1000 * 60 * 60;
|
||||
const n1_v2 = await Note.save({ id: n1_v0.id, title: 'hello', updated_time: Date.now(), user_updated_time: userUpdatedTime }, { autoTimestamp: false });
|
||||
await revisionService().collectRevisions(); // Only the user timestamp has changed, but that needs to be saved
|
||||
|
||||
const revisions = await Revision.all();
|
||||
expect(revisions.length).toBe(2);
|
||||
|
||||
const revNote = await revisionService().revisionNote(revisions, 1);
|
||||
expect(revNote.user_updated_time).toBe(userUpdatedTime);
|
||||
}));
|
||||
|
||||
it('should not create a revision if there is already a recent one', asyncTest(async () => {
|
||||
const n1_v0 = await Note.save({ title: '' });
|
||||
const n1_v1 = await Note.save({ id: n1_v0.id, title: 'hello' });
|
||||
await revisionService().collectRevisions(); // REV 1
|
||||
|
||||
const n1_v2 = await Note.save({ id: n1_v0.id, title: 'hello 2' });
|
||||
await revisionService().collectRevisions(); // REV 2
|
||||
expect((await Revision.all()).length).toBe(2);
|
||||
|
||||
Setting.setValue('revisionService.intervalBetweenRevisions', 1000);
|
||||
|
||||
const n1_v3 = await Note.save({ id: n1_v0.id, title: 'hello 3' });
|
||||
await revisionService().collectRevisions(); // No rev because there's already a rev that is less than 1000 ms old
|
||||
|
||||
expect((await Revision.all()).length).toBe(2);
|
||||
}));
|
||||
|
||||
});
|
@@ -1,7 +1,7 @@
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const { setupDatabase, allSyncTargetItemsEncrypted, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, encryptionService, loadEncryptionMasterKey, fileContentEqual, decryptionWorker, checkThrowAsync, asyncTest } = require('test-utils.js');
|
||||
const { setupDatabase, allSyncTargetItemsEncrypted, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, encryptionService, loadEncryptionMasterKey, fileContentEqual, decryptionWorker, checkThrowAsync, asyncTest } = require('test-utils.js');
|
||||
const { shim } = require('lib/shim.js');
|
||||
const fs = require('fs-extra');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
@@ -13,6 +13,7 @@ const { Database } = require('lib/database.js');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const MasterKey = require('lib/models/MasterKey');
|
||||
const BaseItem = require('lib/models/BaseItem.js');
|
||||
const Revision = require('lib/models/Revision.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const SyncTargetRegistry = require('lib/SyncTargetRegistry.js');
|
||||
const WelcomeUtils = require('lib/WelcomeUtils');
|
||||
@@ -23,19 +24,44 @@ process.on('unhandledRejection', (reason, p) => {
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000 + 30000; // The first test is slow because the database needs to be built
|
||||
|
||||
async function allItems() {
|
||||
async function allNotesFolders() {
|
||||
let folders = await Folder.all();
|
||||
let notes = await Note.all();
|
||||
return folders.concat(notes);
|
||||
}
|
||||
|
||||
async function localItemsSameAsRemote(locals, expect) {
|
||||
async function remoteItemsByTypes(types) {
|
||||
const list = await fileApi().list();
|
||||
if (list.has_more) throw new Error('Not implemented!!!');
|
||||
const files = list.items;
|
||||
|
||||
const output = [];
|
||||
for (const file of files) {
|
||||
const remoteContent = await fileApi().get(file.path);
|
||||
const content = await BaseItem.unserialize(remoteContent);
|
||||
if (types.indexOf(content.type_) < 0) continue;
|
||||
output.push(content);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
async function remoteNotesAndFolders() {
|
||||
return remoteItemsByTypes([BaseModel.TYPE_NOTE, BaseModel.TYPE_FOLDER]);
|
||||
}
|
||||
|
||||
async function remoteNotesFoldersResources() {
|
||||
return remoteItemsByTypes([BaseModel.TYPE_NOTE, BaseModel.TYPE_FOLDER, BaseModel.TYPE_RESOURCE]);
|
||||
}
|
||||
|
||||
async function remoteResources() {
|
||||
return remoteItemsByTypes([BaseModel.TYPE_RESOURCE]);
|
||||
}
|
||||
|
||||
async function localNotesFoldersSameAsRemote(locals, expect) {
|
||||
let error = null;
|
||||
try {
|
||||
let files = await fileApi().list();
|
||||
files = files.items;
|
||||
|
||||
expect(locals.length).toBe(files.length);
|
||||
const nf = await remoteNotesAndFolders();
|
||||
expect(locals.length).toBe(nf.length);
|
||||
|
||||
for (let i = 0; i < locals.length; i++) {
|
||||
let dbItem = locals[i];
|
||||
@@ -45,12 +71,6 @@ async function localItemsSameAsRemote(locals, expect) {
|
||||
expect(!!remote).toBe(true);
|
||||
if (!remote) continue;
|
||||
|
||||
// if (syncTargetId() == SyncTargetRegistry.nameToId('filesystem')) {
|
||||
// expect(remote.updated_time).toBe(Math.floor(dbItem.updated_time / 1000) * 1000);
|
||||
// } else {
|
||||
// expect(remote.updated_time).toBe(dbItem.updated_time);
|
||||
// }
|
||||
|
||||
let remoteContent = await fileApi().get(path);
|
||||
|
||||
remoteContent = dbItem.type_ == BaseModel.TYPE_NOTE ? await Note.unserialize(remoteContent) : await Folder.unserialize(remoteContent);
|
||||
@@ -82,11 +102,11 @@ describe('Synchronizer', function() {
|
||||
let folder = await Folder.save({ title: "folder1" });
|
||||
await Note.save({ title: "un", parent_id: folder.id });
|
||||
|
||||
let all = await allItems();
|
||||
let all = await allNotesFolders();
|
||||
|
||||
await synchronizer().start();
|
||||
|
||||
await localItemsSameAsRemote(all, expect);
|
||||
await localNotesFoldersSameAsRemote(all, expect);
|
||||
}));
|
||||
|
||||
it('should update remote items', asyncTest(async () => {
|
||||
@@ -96,10 +116,10 @@ describe('Synchronizer', function() {
|
||||
|
||||
await Note.save({ title: "un UPDATE", id: note.id });
|
||||
|
||||
let all = await allItems();
|
||||
let all = await allNotesFolders();
|
||||
await synchronizer().start();
|
||||
|
||||
await localItemsSameAsRemote(all, expect);
|
||||
await localNotesFoldersSameAsRemote(all, expect);
|
||||
}));
|
||||
|
||||
it('should create local items', asyncTest(async () => {
|
||||
@@ -111,9 +131,9 @@ describe('Synchronizer', function() {
|
||||
|
||||
await synchronizer().start();
|
||||
|
||||
let all = await allItems();
|
||||
let all = await allNotesFolders();
|
||||
|
||||
await localItemsSameAsRemote(all, expect);
|
||||
await localNotesFoldersSameAsRemote(all, expect);
|
||||
}));
|
||||
|
||||
it('should update local items', asyncTest(async () => {
|
||||
@@ -138,9 +158,9 @@ describe('Synchronizer', function() {
|
||||
|
||||
await synchronizer().start();
|
||||
|
||||
let all = await allItems();
|
||||
let all = await allNotesFolders();
|
||||
|
||||
await localItemsSameAsRemote(all, expect);
|
||||
await localNotesFoldersSameAsRemote(all, expect);
|
||||
}));
|
||||
|
||||
it('should resolve note conflicts', asyncTest(async () => {
|
||||
@@ -232,11 +252,9 @@ describe('Synchronizer', function() {
|
||||
|
||||
await synchronizer().start();
|
||||
|
||||
let files = await fileApi().list();
|
||||
files = files.items;
|
||||
|
||||
expect(files.length).toBe(1);
|
||||
expect(files[0].path).toBe(Folder.systemPath(folder1));
|
||||
const remotes = await remoteNotesAndFolders();
|
||||
expect(remotes.length).toBe(1);
|
||||
expect(remotes[0].id).toBe(folder1.id);
|
||||
|
||||
let deletedItems = await BaseItem.deletedItems(syncTargetId());
|
||||
expect(deletedItems.length).toBe(0);
|
||||
@@ -279,7 +297,7 @@ describe('Synchronizer', function() {
|
||||
await switchClient(1);
|
||||
|
||||
context1 = await synchronizer().start({ context: context1 });
|
||||
let items = await allItems();
|
||||
let items = await allNotesFolders();
|
||||
expect(items.length).toBe(2);
|
||||
let deletedItems = await BaseItem.deletedItems(syncTargetId());
|
||||
expect(deletedItems.length).toBe(0);
|
||||
@@ -302,8 +320,8 @@ describe('Synchronizer', function() {
|
||||
|
||||
await synchronizer().start();
|
||||
|
||||
let all = await allItems();
|
||||
await localItemsSameAsRemote(all, expect);
|
||||
let all = await allNotesFolders();
|
||||
await localNotesFoldersSameAsRemote(all, expect);
|
||||
}));
|
||||
|
||||
it('should delete local folder', asyncTest(async () => {
|
||||
@@ -320,8 +338,8 @@ describe('Synchronizer', function() {
|
||||
await switchClient(1);
|
||||
|
||||
await synchronizer().start({ context: context1 });
|
||||
let items = await allItems();
|
||||
await localItemsSameAsRemote(items, expect);
|
||||
let items = await allNotesFolders();
|
||||
await localNotesFoldersSameAsRemote(items, expect);
|
||||
}));
|
||||
|
||||
it('should resolve conflict if remote folder has been deleted, but note has been added to folder locally', asyncTest(async () => {
|
||||
@@ -338,7 +356,7 @@ describe('Synchronizer', function() {
|
||||
|
||||
let note = await Note.save({ title: "note1", parent_id: folder1.id });
|
||||
await synchronizer().start();
|
||||
let items = await allItems();
|
||||
let items = await allNotesFolders();
|
||||
expect(items.length).toBe(1);
|
||||
expect(items[0].title).toBe('note1');
|
||||
expect(items[0].is_conflict).toBe(1);
|
||||
@@ -360,11 +378,11 @@ describe('Synchronizer', function() {
|
||||
await Note.delete(note.id);
|
||||
await synchronizer().start();
|
||||
|
||||
let items = await allItems();
|
||||
let items = await allNotesFolders();
|
||||
expect(items.length).toBe(1);
|
||||
expect(items[0].title).toBe('folder');
|
||||
|
||||
await localItemsSameAsRemote(items, expect);
|
||||
await localNotesFoldersSameAsRemote(items, expect);
|
||||
}));
|
||||
|
||||
it('should cross delete all folders', asyncTest(async () => {
|
||||
@@ -393,13 +411,13 @@ describe('Synchronizer', function() {
|
||||
|
||||
await synchronizer().start();
|
||||
|
||||
let items2 = await allItems();
|
||||
let items2 = await allNotesFolders();
|
||||
|
||||
await switchClient(1);
|
||||
|
||||
await synchronizer().start();
|
||||
|
||||
let items1 = await allItems();
|
||||
let items1 = await allNotesFolders();
|
||||
|
||||
expect(items1.length).toBe(0);
|
||||
expect(items1.length).toBe(items2.length);
|
||||
@@ -462,7 +480,7 @@ describe('Synchronizer', function() {
|
||||
|
||||
await synchronizer().start();
|
||||
|
||||
let items = await allItems();
|
||||
let items = await allNotesFolders();
|
||||
|
||||
expect(items.length).toBe(1);
|
||||
}));
|
||||
@@ -680,7 +698,7 @@ describe('Synchronizer', function() {
|
||||
let disabledItems = await BaseItem.syncDisabledItems(syncTargetId());
|
||||
expect(disabledItems.length).toBe(0);
|
||||
await Note.save({ id: noteId, title: "un mod", });
|
||||
synchronizer().testingHooks_ = ['rejectedByTarget'];
|
||||
synchronizer().testingHooks_ = ['notesRejectedByTarget'];
|
||||
await synchronizer().start();
|
||||
synchronizer().testingHooks_ = [];
|
||||
await synchronizer().start(); // Another sync to check that this item is now excluded from sync
|
||||
@@ -833,8 +851,8 @@ describe('Synchronizer', function() {
|
||||
await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
|
||||
let resource1 = (await Resource.all())[0];
|
||||
let resourcePath1 = Resource.fullPath(resource1);
|
||||
await synchronizer().start();
|
||||
expect((await fileApi().list()).items.length).toBe(3);
|
||||
await synchronizer().start();
|
||||
expect((await remoteNotesFoldersResources()).length).toBe(3);
|
||||
|
||||
await switchClient(2);
|
||||
|
||||
@@ -847,7 +865,7 @@ describe('Synchronizer', function() {
|
||||
expect(ls.fetch_status).toBe(Resource.FETCH_STATUS_IDLE);
|
||||
|
||||
const fetcher = new ResourceFetcher(() => { return synchronizer().api() });
|
||||
fetcher.queueDownload(resource1_2.id);
|
||||
fetcher.queueDownload_(resource1_2.id);
|
||||
await fetcher.waitForAllFinished();
|
||||
|
||||
resource1_2 = await Resource.load(resource1.id);
|
||||
@@ -876,7 +894,7 @@ describe('Synchronizer', function() {
|
||||
// Simulate a failed download
|
||||
get: () => { return new Promise((resolve, reject) => { reject(new Error('did not work')) }); }
|
||||
} });
|
||||
fetcher.queueDownload(resource1.id);
|
||||
fetcher.queueDownload_(resource1.id);
|
||||
await fetcher.waitForAllFinished();
|
||||
|
||||
resource1 = await Resource.load(resource1.id);
|
||||
@@ -885,6 +903,29 @@ describe('Synchronizer', function() {
|
||||
expect(ls.fetch_error).toBe('did not work');
|
||||
}));
|
||||
|
||||
it('should set the resource file size if it is missing', asyncTest(async () => {
|
||||
while (insideBeforeEach) await time.msleep(500);
|
||||
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
|
||||
await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
|
||||
await synchronizer().start();
|
||||
|
||||
await switchClient(2);
|
||||
|
||||
await synchronizer().start();
|
||||
let r1 = (await Resource.all())[0];
|
||||
await Resource.setFileSizeOnly(r1.id, -1);
|
||||
r1 = await Resource.load(r1.id);
|
||||
expect(r1.size).toBe(-1);
|
||||
|
||||
const fetcher = new ResourceFetcher(() => { return synchronizer().api() });
|
||||
fetcher.queueDownload_(r1.id);
|
||||
await fetcher.waitForAllFinished();
|
||||
r1 = await Resource.load(r1.id);
|
||||
expect(r1.size).toBe(2720);
|
||||
}));
|
||||
|
||||
it('should delete resources', asyncTest(async () => {
|
||||
while (insideBeforeEach) await time.msleep(500);
|
||||
|
||||
@@ -901,11 +942,10 @@ describe('Synchronizer', function() {
|
||||
let allResources = await Resource.all();
|
||||
expect(allResources.length).toBe(1);
|
||||
let all = await fileApi().list();
|
||||
expect(all.items.length).toBe(3);
|
||||
expect((await remoteNotesFoldersResources()).length).toBe(3);
|
||||
await Resource.delete(resource1.id);
|
||||
await synchronizer().start();
|
||||
all = await fileApi().list();
|
||||
expect(all.items.length).toBe(2);
|
||||
expect((await remoteNotesFoldersResources()).length).toBe(2);
|
||||
|
||||
await switchClient(1);
|
||||
|
||||
@@ -934,7 +974,7 @@ describe('Synchronizer', function() {
|
||||
await encryptionService().loadMasterKeysFromSettings();
|
||||
|
||||
const fetcher = new ResourceFetcher(() => { return synchronizer().api() });
|
||||
fetcher.queueDownload(resource1.id);
|
||||
fetcher.queueDownload_(resource1.id);
|
||||
await fetcher.waitForAllFinished();
|
||||
|
||||
let resource1_2 = (await Resource.all())[0];
|
||||
@@ -998,6 +1038,33 @@ describe('Synchronizer', function() {
|
||||
expect(allEncrypted).toBe(false);
|
||||
}));
|
||||
|
||||
it('should set the resource file size after decryption', asyncTest(async () => {
|
||||
Setting.setValue('encryption.enabled', true);
|
||||
const masterKey = await loadEncryptionMasterKey();
|
||||
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
|
||||
await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
|
||||
let resource1 = (await Resource.all())[0];
|
||||
await Resource.setFileSizeOnly(resource1.id, -1);
|
||||
let resourcePath1 = Resource.fullPath(resource1);
|
||||
await synchronizer().start();
|
||||
|
||||
await switchClient(2);
|
||||
|
||||
await synchronizer().start();
|
||||
Setting.setObjectKey('encryption.passwordCache', masterKey.id, '123456');
|
||||
await encryptionService().loadMasterKeysFromSettings();
|
||||
|
||||
const fetcher = new ResourceFetcher(() => { return synchronizer().api() });
|
||||
fetcher.queueDownload_(resource1.id);
|
||||
await fetcher.waitForAllFinished();
|
||||
await decryptionWorker().start();
|
||||
|
||||
const resource1_2 = await Resource.load(resource1.id);
|
||||
expect(resource1_2.size).toBe(2720);
|
||||
}));
|
||||
|
||||
it('should encrypt remote resources after encryption has been enabled', asyncTest(async () => {
|
||||
while (insideBeforeEach) await time.msleep(100);
|
||||
|
||||
@@ -1036,11 +1103,11 @@ describe('Synchronizer', function() {
|
||||
it('should create remote items with UTF-8 content', asyncTest(async () => {
|
||||
let folder = await Folder.save({ title: "Fahrräder" });
|
||||
await Note.save({ title: "Fahrräder", body: "Fahrräder", parent_id: folder.id });
|
||||
let all = await allItems();
|
||||
let all = await allNotesFolders();
|
||||
|
||||
await synchronizer().start();
|
||||
|
||||
await localItemsSameAsRemote(all, expect);
|
||||
await localNotesFoldersSameAsRemote(all, expect);
|
||||
}));
|
||||
|
||||
it("should update remote items but not pull remote changes", asyncTest(async () => {
|
||||
@@ -1058,7 +1125,7 @@ describe('Synchronizer', function() {
|
||||
|
||||
await Note.save({ title: "un UPDATE", id: note.id });
|
||||
await synchronizer().start({ syncSteps: ["update_remote"] });
|
||||
let all = await allItems();
|
||||
let all = await allNotesFolders();
|
||||
expect(all.length).toBe(2);
|
||||
|
||||
await switchClient(2);
|
||||
@@ -1103,11 +1170,250 @@ describe('Synchronizer', function() {
|
||||
|
||||
const f1_1 = await Folder.load(f1.id);
|
||||
expect(f1_1.title).toBe('Welcome MOD');
|
||||
}));
|
||||
|
||||
// Now check that it created the duplicate tag
|
||||
it("should not save revisions when updating a note via sync", asyncTest(async () => {
|
||||
// When a note is updated, a revision of the original is created.
|
||||
// Here, on client 1, the note is updated for the first time, however since it is
|
||||
// via sync, we don't create a revision - that revision has already been created on client
|
||||
// 2 and is going to be synced.
|
||||
|
||||
const tags = await Tag.modelSelectAll('SELECT * FROM tags WHERE title = "organising"');
|
||||
expect(tags.length).toBe(2);
|
||||
const n1 = await Note.save({ title: 'testing' });
|
||||
await synchronizer().start();
|
||||
|
||||
await switchClient(2);
|
||||
|
||||
await synchronizer().start();
|
||||
await Note.save({ id: n1.id, title: 'mod from client 2' });
|
||||
await revisionService().collectRevisions();
|
||||
const allRevs1 = await Revision.allByType(BaseModel.TYPE_NOTE, n1.id);
|
||||
expect(allRevs1.length).toBe(1);
|
||||
await synchronizer().start();
|
||||
|
||||
await switchClient(1);
|
||||
|
||||
await synchronizer().start();
|
||||
const allRevs2 = await Revision.allByType(BaseModel.TYPE_NOTE, n1.id);
|
||||
expect(allRevs2.length).toBe(1);
|
||||
expect(allRevs2[0].id).toBe(allRevs1[0].id);
|
||||
}));
|
||||
|
||||
it("should not save revisions when deleting a note via sync", asyncTest(async () => {
|
||||
const n1 = await Note.save({ title: 'testing' });
|
||||
await synchronizer().start();
|
||||
|
||||
await switchClient(2);
|
||||
|
||||
await synchronizer().start();
|
||||
await Note.delete(n1.id);
|
||||
await revisionService().collectRevisions(); // REV 1
|
||||
{
|
||||
const allRevs = await Revision.allByType(BaseModel.TYPE_NOTE, n1.id);
|
||||
expect(allRevs.length).toBe(1);
|
||||
}
|
||||
await synchronizer().start();
|
||||
|
||||
await switchClient(1);
|
||||
|
||||
await synchronizer().start(); // The local note gets deleted here, however a new rev is *not* created
|
||||
{
|
||||
const allRevs = await Revision.allByType(BaseModel.TYPE_NOTE, n1.id);
|
||||
expect(allRevs.length).toBe(1);
|
||||
}
|
||||
|
||||
const notes = await Note.all();
|
||||
expect(notes.length).toBe(0);
|
||||
}));
|
||||
|
||||
it("should not save revisions when an item_change has been generated as a result of a sync", asyncTest(async () => {
|
||||
// When a note is modified an item_change object is going to be created. This
|
||||
// is used for example to tell the search engine, when note should be indexed. It is
|
||||
// also used by the revision service to tell what note should get a new revision.
|
||||
// When a note is modified via sync, this item_change object is also created. The issue
|
||||
// is that we don't want to create revisions for these particular item_changes, because
|
||||
// such revision has already been created on another client (whatever client initially
|
||||
// modified the note), and that rev is going to be synced.
|
||||
//
|
||||
// So in the end we need to make sure that we don't create these unecessary additional revisions.
|
||||
|
||||
const n1 = await Note.save({ title: 'testing' });
|
||||
await synchronizer().start();
|
||||
|
||||
await switchClient(2);
|
||||
|
||||
await synchronizer().start();
|
||||
await Note.save({ id: n1.id, title: 'mod from client 2' });
|
||||
await revisionService().collectRevisions();
|
||||
await synchronizer().start();
|
||||
|
||||
await switchClient(1);
|
||||
|
||||
await synchronizer().start();
|
||||
|
||||
{
|
||||
const allRevs = await Revision.allByType(BaseModel.TYPE_NOTE, n1.id);
|
||||
expect(allRevs.length).toBe(1);
|
||||
}
|
||||
|
||||
await revisionService().collectRevisions();
|
||||
|
||||
{
|
||||
const allRevs = await Revision.allByType(BaseModel.TYPE_NOTE, n1.id);
|
||||
expect(allRevs.length).toBe(1);
|
||||
}
|
||||
}));
|
||||
|
||||
it("should handle case when new rev is created on client, then older rev arrives later via sync", asyncTest(async () => {
|
||||
// - C1 creates note 1
|
||||
// - C1 modifies note 1 - REV1 created
|
||||
// - C1 sync
|
||||
// - C2 sync
|
||||
// - C2 receives note 1
|
||||
// - C2 modifies note 1 - REV2 created (but not based on REV1)
|
||||
// - C2 receives REV1
|
||||
//
|
||||
// In that case, we need to make sure that REV1 and REV2 are both valid and can be retrieved.
|
||||
// Even though REV1 was created before REV2, REV2 is *not* based on REV1. This is not ideal
|
||||
// due to unecessary data being saved, but a possible edge case and we simply need to check
|
||||
// all the data is valid.
|
||||
|
||||
const n1 = await Note.save({ title: 'note' });
|
||||
await Note.save({ id: n1.id, title: 'note REV1' });
|
||||
await revisionService().collectRevisions(); // REV1
|
||||
expect((await Revision.allByType(BaseModel.TYPE_NOTE, n1.id)).length).toBe(1);
|
||||
await synchronizer().start();
|
||||
|
||||
await switchClient(2);
|
||||
|
||||
synchronizer().testingHooks_ = ['skipRevisions'];
|
||||
await synchronizer().start();
|
||||
synchronizer().testingHooks_ = [];
|
||||
|
||||
await Note.save({ id: n1.id, title: 'note REV2' });
|
||||
await revisionService().collectRevisions(); // REV2
|
||||
expect((await Revision.allByType(BaseModel.TYPE_NOTE, n1.id)).length).toBe(1);
|
||||
await synchronizer().start(); // Sync the rev that had been skipped above with skipRevisions
|
||||
|
||||
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, n1.id);
|
||||
expect(revisions.length).toBe(2);
|
||||
|
||||
expect((await revisionService().revisionNote(revisions, 0)).title).toBe('note REV1');
|
||||
expect((await revisionService().revisionNote(revisions, 1)).title).toBe('note REV2');
|
||||
}));
|
||||
|
||||
it("should not download resources over the limit", asyncTest(async () => {
|
||||
const note1 = await Note.save({ title: 'note' });
|
||||
await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
|
||||
await synchronizer().start();
|
||||
|
||||
await switchClient(2);
|
||||
|
||||
const previousMax = synchronizer().maxResourceSize_;
|
||||
synchronizer().maxResourceSize_ = 1;
|
||||
await synchronizer().start();
|
||||
synchronizer().maxResourceSize_ = previousMax;
|
||||
|
||||
const syncItems = await BaseItem.allSyncItems(syncTargetId());
|
||||
expect(syncItems.length).toBe(2);
|
||||
expect(syncItems[1].item_location).toBe(BaseItem.SYNC_ITEM_LOCATION_REMOTE);
|
||||
expect(syncItems[1].sync_disabled).toBe(1);
|
||||
}));
|
||||
|
||||
it("should not upload a resource if it has not been fetched yet", asyncTest(async () => {
|
||||
// In some rare cases, the synchronizer might try to upload a resource even though it
|
||||
// doesn't have the resource file. It can happen in this situation:
|
||||
// - C1 create resource
|
||||
// - C1 sync
|
||||
// - C2 sync
|
||||
// - C2 resource metadata is received but ResourceFetcher hasn't downloaded the file yet
|
||||
// - C2 enables E2EE - all the items are marked for forced sync
|
||||
// - C2 sync
|
||||
// The synchronizer will try to upload the resource, even though it doesn't have the file,
|
||||
// so we need to make sure it doesn't. But also that once it gets the file, the resource
|
||||
// does get uploaded.
|
||||
|
||||
const note1 = await Note.save({ title: 'note' });
|
||||
await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
|
||||
const resource = (await Resource.all())[0];
|
||||
await Resource.setLocalState(resource.id, { fetch_status: Resource.FETCH_STATUS_IDLE });
|
||||
await synchronizer().start();
|
||||
|
||||
expect((await remoteResources()).length).toBe(0);
|
||||
|
||||
await Resource.setLocalState(resource.id, { fetch_status: Resource.FETCH_STATUS_DONE });
|
||||
await synchronizer().start();
|
||||
|
||||
expect((await remoteResources()).length).toBe(1);
|
||||
}));
|
||||
|
||||
it('should decrypt the resource metadata, but not try to decrypt the file, if it is not present', asyncTest(async () => {
|
||||
const note1 = await Note.save({ title: 'note' });
|
||||
await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
|
||||
const masterKey = await loadEncryptionMasterKey();
|
||||
await encryptionService().enableEncryption(masterKey, '123456');
|
||||
await encryptionService().loadMasterKeysFromSettings();
|
||||
await synchronizer().start();
|
||||
expect(await allSyncTargetItemsEncrypted()).toBe(true);
|
||||
|
||||
await switchClient(2);
|
||||
|
||||
await synchronizer().start();
|
||||
Setting.setObjectKey('encryption.passwordCache', masterKey.id, '123456');
|
||||
await encryptionService().loadMasterKeysFromSettings();
|
||||
await decryptionWorker().start();
|
||||
|
||||
let resource = (await Resource.all())[0];
|
||||
|
||||
expect(!!resource.encryption_applied).toBe(false);
|
||||
expect(!!resource.encryption_blob_encrypted).toBe(true);
|
||||
|
||||
const resourceFetcher = new ResourceFetcher(() => { return synchronizer().api() });
|
||||
await resourceFetcher.start();
|
||||
await resourceFetcher.waitForAllFinished();
|
||||
|
||||
const ls = await Resource.localState(resource);
|
||||
expect(ls.fetch_status).toBe(Resource.FETCH_STATUS_DONE);
|
||||
|
||||
await decryptionWorker().start();
|
||||
resource = (await Resource.all())[0];
|
||||
|
||||
expect(!!resource.encryption_blob_encrypted).toBe(false);
|
||||
}));
|
||||
|
||||
it('should not create revisions when item is modified as a result of decryption', asyncTest(async () => {
|
||||
// Handle this scenario:
|
||||
// - C1 creates note
|
||||
// - C1 never changes it
|
||||
// - E2EE is enabled
|
||||
// - C1 sync
|
||||
// - More than one week later (as defined by oldNoteCutOffDate_), C2 sync
|
||||
// - C2 enters master password and note gets decrypted
|
||||
//
|
||||
// Technically at this point the note is modified (from encrypted to non-encrypted) and thus a ItemChange
|
||||
// object is created. The note is also older than oldNoteCutOffDate. However, this should not lead to the
|
||||
// creation of a revision because that change was not the result of a user action.
|
||||
// I guess that's the general rule - changes that come from user actions should result in revisions,
|
||||
// while automated changes (sync, decryption) should not.
|
||||
|
||||
const dateInPast = revisionService().oldNoteCutOffDate_() - 1000;
|
||||
|
||||
const note1 = await Note.save({ title: 'ma note', updated_time: dateInPast, created_time: dateInPast }, { autoTimestamp: false });
|
||||
const masterKey = await loadEncryptionMasterKey();
|
||||
await encryptionService().enableEncryption(masterKey, '123456');
|
||||
await encryptionService().loadMasterKeysFromSettings();
|
||||
await synchronizer().start();
|
||||
|
||||
await switchClient(2);
|
||||
|
||||
await synchronizer().start();
|
||||
|
||||
Setting.setObjectKey('encryption.passwordCache', masterKey.id, '123456');
|
||||
await encryptionService().loadMasterKeysFromSettings();
|
||||
await decryptionWorker().start();
|
||||
|
||||
await revisionService().collectRevisions();
|
||||
|
||||
expect((await Revision.all()).length).toBe(0);
|
||||
}));
|
||||
|
||||
});
|
||||
|
@@ -8,6 +8,7 @@ const ItemChange = require('lib/models/ItemChange.js');
|
||||
const Resource = require('lib/models/Resource.js');
|
||||
const Tag = require('lib/models/Tag.js');
|
||||
const NoteTag = require('lib/models/NoteTag.js');
|
||||
const Revision = require('lib/models/Revision.js');
|
||||
const { Logger } = require('lib/logger.js');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const MasterKey = require('lib/models/MasterKey');
|
||||
@@ -31,12 +32,14 @@ const SyncTargetDropbox = require('lib/SyncTargetDropbox.js');
|
||||
const EncryptionService = require('lib/services/EncryptionService.js');
|
||||
const DecryptionWorker = require('lib/services/DecryptionWorker.js');
|
||||
const ResourceService = require('lib/services/ResourceService.js');
|
||||
const RevisionService = require('lib/services/RevisionService.js');
|
||||
const WebDavApi = require('lib/WebDavApi');
|
||||
const DropboxApi = require('lib/DropboxApi');
|
||||
|
||||
let databases_ = [];
|
||||
let synchronizers_ = [];
|
||||
let encryptionServices_ = [];
|
||||
let revisionServices_ = [];
|
||||
let decryptionWorkers_ = [];
|
||||
let resourceServices_ = [];
|
||||
let fileApi_ = null;
|
||||
@@ -71,6 +74,11 @@ const sleepTime = syncTargetId_ == SyncTargetRegistry.nameToId('filesystem') ? 1
|
||||
|
||||
console.info('Testing with sync target: ' + SyncTargetRegistry.idToName(syncTargetId_));
|
||||
|
||||
const dbLogger = new Logger();
|
||||
dbLogger.addTarget('console');
|
||||
dbLogger.addTarget('file', { path: logDir + '/log.txt' });
|
||||
dbLogger.setLevel(Logger.LEVEL_WARN);
|
||||
|
||||
const logger = new Logger();
|
||||
logger.addTarget('console');
|
||||
logger.addTarget('file', { path: logDir + '/log.txt' });
|
||||
@@ -82,6 +90,7 @@ BaseItem.loadClass('Resource', Resource);
|
||||
BaseItem.loadClass('Tag', Tag);
|
||||
BaseItem.loadClass('NoteTag', NoteTag);
|
||||
BaseItem.loadClass('MasterKey', MasterKey);
|
||||
BaseItem.loadClass('Revision', Revision);
|
||||
|
||||
Setting.setConstant('appId', 'net.cozic.joplin-cli');
|
||||
Setting.setConstant('appType', 'cli');
|
||||
@@ -115,9 +124,11 @@ async function switchClient(id) {
|
||||
Note.db_ = databases_[id];
|
||||
BaseItem.db_ = databases_[id];
|
||||
Setting.db_ = databases_[id];
|
||||
Resource.db_ = databases_[id];
|
||||
|
||||
BaseItem.encryptionService_ = encryptionServices_[id];
|
||||
Resource.encryptionService_ = encryptionServices_[id];
|
||||
BaseItem.revisionService_ = revisionServices_[id];
|
||||
|
||||
Setting.setConstant('resourceDir', resourceDir(id));
|
||||
|
||||
@@ -129,21 +140,28 @@ async function clearDatabase(id = null) {
|
||||
|
||||
await ItemChange.waitForAllSaved();
|
||||
|
||||
let queries = [
|
||||
'DELETE FROM notes',
|
||||
'DELETE FROM folders',
|
||||
'DELETE FROM resources',
|
||||
'DELETE FROM tags',
|
||||
'DELETE FROM note_tags',
|
||||
'DELETE FROM master_keys',
|
||||
'DELETE FROM item_changes',
|
||||
'DELETE FROM note_resources',
|
||||
'DELETE FROM settings',
|
||||
'DELETE FROM deleted_items',
|
||||
'DELETE FROM sync_items',
|
||||
'DELETE FROM notes_normalized',
|
||||
const tableNames = [
|
||||
'notes',
|
||||
'folders',
|
||||
'resources',
|
||||
'tags',
|
||||
'note_tags',
|
||||
'master_keys',
|
||||
'item_changes',
|
||||
'note_resources',
|
||||
'settings',
|
||||
'deleted_items',
|
||||
'sync_items',
|
||||
'notes_normalized',
|
||||
'revisions',
|
||||
];
|
||||
|
||||
const queries = [];
|
||||
for (const n of tableNames) {
|
||||
queries.push('DELETE FROM ' + n);
|
||||
queries.push('DELETE FROM sqlite_sequence WHERE name="' + n + '"'); // Reset autoincremented IDs
|
||||
}
|
||||
|
||||
await databases_[id].transactionExecBatch(queries);
|
||||
}
|
||||
|
||||
@@ -168,6 +186,7 @@ async function setupDatabase(id = null) {
|
||||
};
|
||||
|
||||
databases_[id] = new JoplinDatabase(new DatabaseDriverNode());
|
||||
databases_[id].setLogger(dbLogger);
|
||||
await databases_[id].open({ name: filePath });
|
||||
|
||||
BaseModel.db_ = databases_[id];
|
||||
@@ -200,6 +219,7 @@ async function setupDatabaseAndSynchronizer(id = null) {
|
||||
}
|
||||
|
||||
encryptionServices_[id] = new EncryptionService();
|
||||
revisionServices_[id] = new RevisionService();
|
||||
decryptionWorkers_[id] = new DecryptionWorker();
|
||||
decryptionWorkers_[id].setEncryptionService(encryptionServices_[id]);
|
||||
resourceServices_[id] = new ResourceService();
|
||||
@@ -222,6 +242,11 @@ function encryptionService(id = null) {
|
||||
return encryptionServices_[id];
|
||||
}
|
||||
|
||||
function revisionService(id = null) {
|
||||
if (id === null) id = currentClient_;
|
||||
return revisionServices_[id];
|
||||
}
|
||||
|
||||
function decryptionWorker(id = null) {
|
||||
if (id === null) id = currentClient_;
|
||||
return decryptionWorkers_[id];
|
||||
@@ -354,4 +379,4 @@ async function allSyncTargetItemsEncrypted() {
|
||||
return totalCount === encryptedCount;
|
||||
}
|
||||
|
||||
module.exports = { resourceService, allSyncTargetItemsEncrypted, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync, encryptionService, loadEncryptionMasterKey, fileContentEqual, decryptionWorker, asyncTest };
|
||||
module.exports = { resourceService, allSyncTargetItemsEncrypted, setupDatabase, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync, encryptionService, loadEncryptionMasterKey, fileContentEqual, decryptionWorker, asyncTest };
|
||||
|
@@ -1,3 +1,5 @@
|
||||
// https://github.com/mozilla/readability/tree/814f0a3884350b6f1adfdebb79ca3599e9806605
|
||||
|
||||
/*eslint-env es6:false*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
@@ -496,17 +498,9 @@
|
||||
},
|
||||
setValue: function(newValue) {
|
||||
this._value = newValue;
|
||||
delete this._decodedValue;
|
||||
},
|
||||
setDecodedValue: function(newValue) {
|
||||
this._value = encodeHTML(newValue);
|
||||
this._decodedValue = newValue;
|
||||
},
|
||||
getDecodedValue: function() {
|
||||
if (typeof this._decodedValue === "undefined") {
|
||||
this._decodedValue = (this._value && decodeHTML(this._value)) || "";
|
||||
}
|
||||
return this._decodedValue;
|
||||
getEncodedValue: function() {
|
||||
return encodeHTML(this._value);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -611,6 +605,13 @@
|
||||
};
|
||||
|
||||
var Element = function (tag) {
|
||||
// We use this to find the closing tag.
|
||||
this._matchingTag = tag;
|
||||
// We're explicitly a non-namespace aware parser, we just pretend it's all HTML.
|
||||
var lastColonIndex = tag.lastIndexOf(":");
|
||||
if (lastColonIndex != -1) {
|
||||
tag = tag.substring(lastColonIndex + 1);
|
||||
}
|
||||
this.attributes = [];
|
||||
this.childNodes = [];
|
||||
this.children = [];
|
||||
@@ -659,6 +660,14 @@
|
||||
this.setAttribute("src", str);
|
||||
},
|
||||
|
||||
get srcset() {
|
||||
return this.getAttribute("srcset") || "";
|
||||
},
|
||||
|
||||
set srcset(str) {
|
||||
this.setAttribute("srcset", str);
|
||||
},
|
||||
|
||||
get nodeName() {
|
||||
return this.tagName;
|
||||
},
|
||||
@@ -675,9 +684,9 @@
|
||||
for (var j = 0; j < child.attributes.length; j++) {
|
||||
var attr = child.attributes[j];
|
||||
// the attribute value will be HTML escaped.
|
||||
var val = attr.value;
|
||||
var val = attr.getEncodedValue();
|
||||
var quote = (val.indexOf('"') === -1 ? '"' : "'");
|
||||
arr.push(" " + attr.name + '=' + quote + val + quote);
|
||||
arr.push(" " + attr.name + "=" + quote + val + quote);
|
||||
}
|
||||
|
||||
if (child.localName in voidElems && !child.childNodes.length) {
|
||||
@@ -753,8 +762,9 @@
|
||||
getAttribute: function (name) {
|
||||
for (var i = this.attributes.length; --i >= 0;) {
|
||||
var attr = this.attributes[i];
|
||||
if (attr.name === name)
|
||||
return attr.getDecodedValue();
|
||||
if (attr.name === name) {
|
||||
return attr.value;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
@@ -763,11 +773,11 @@
|
||||
for (var i = this.attributes.length; --i >= 0;) {
|
||||
var attr = this.attributes[i];
|
||||
if (attr.name === name) {
|
||||
attr.setDecodedValue(value);
|
||||
attr.setValue(value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.attributes.push(new Attribute(name, encodeHTML(value)));
|
||||
this.attributes.push(new Attribute(name, value));
|
||||
},
|
||||
|
||||
removeAttribute: function (name) {
|
||||
@@ -778,7 +788,13 @@
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
hasAttribute: function (name) {
|
||||
return this.attributes.some(function (attr) {
|
||||
return attr.name == name;
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
var Style = function (node) {
|
||||
@@ -925,7 +941,7 @@
|
||||
// Read the attribute value (and consume the matching quote)
|
||||
var value = this.readString(c);
|
||||
|
||||
node.attributes.push(new Attribute(name, value));
|
||||
node.attributes.push(new Attribute(name, decodeHTML(value)));
|
||||
|
||||
return;
|
||||
},
|
||||
@@ -950,7 +966,7 @@
|
||||
strBuf.push(c);
|
||||
c = this.nextChar();
|
||||
}
|
||||
var tag = strBuf.join('');
|
||||
var tag = strBuf.join("");
|
||||
|
||||
if (!tag)
|
||||
return false;
|
||||
@@ -961,7 +977,9 @@
|
||||
while (c !== "/" && c !== ">") {
|
||||
if (c === undefined)
|
||||
return false;
|
||||
while (whitespace.indexOf(this.html[this.currentChar++]) != -1);
|
||||
while (whitespace.indexOf(this.html[this.currentChar++]) != -1) {
|
||||
// Advance cursor to first non-whitespace char.
|
||||
}
|
||||
this.currentChar--;
|
||||
c = this.nextChar();
|
||||
if (c !== "/" && c !== ">") {
|
||||
@@ -1055,9 +1073,10 @@
|
||||
return null;
|
||||
|
||||
// Read any text as Text node
|
||||
var textNode;
|
||||
if (c !== "<") {
|
||||
--this.currentChar;
|
||||
var textNode = new Text();
|
||||
textNode = new Text();
|
||||
var n = this.html.indexOf("<", this.currentChar);
|
||||
if (n === -1) {
|
||||
textNode.innerHTML = this.html.substring(this.currentChar, this.html.length);
|
||||
@@ -1069,6 +1088,18 @@
|
||||
return textNode;
|
||||
}
|
||||
|
||||
if (this.match("![CDATA[")) {
|
||||
var endChar = this.html.indexOf("]]>", this.currentChar);
|
||||
if (endChar === -1) {
|
||||
this.error("unclosed CDATA section");
|
||||
return null;
|
||||
}
|
||||
textNode = new Text();
|
||||
textNode.textContent = this.html.substring(this.currentChar, endChar);
|
||||
this.currentChar = endChar + ("]]>").length;
|
||||
return textNode;
|
||||
}
|
||||
|
||||
c = this.peekNext();
|
||||
|
||||
// Read Comment node. Normally, Comment nodes know their inner
|
||||
@@ -1100,7 +1131,7 @@
|
||||
// If this isn't a void Element, read its child nodes
|
||||
if (!closed) {
|
||||
this.readChildren(node);
|
||||
var closingTag = "</" + localName + ">";
|
||||
var closingTag = "</" + node._matchingTag + ">";
|
||||
if (!this.match(closingTag)) {
|
||||
this.error("expected '" + closingTag + "' and got " + this.html.substr(this.currentChar, closingTag.length));
|
||||
return null;
|
||||
|
@@ -0,0 +1,99 @@
|
||||
// https://github.com/mozilla/readability/tree/814f0a3884350b6f1adfdebb79ca3599e9806605
|
||||
|
||||
/* eslint-env es6:false */
|
||||
/* globals exports */
|
||||
/*
|
||||
* Copyright (c) 2010 Arc90 Inc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This code is heavily based on Arc90's readability.js (1.7.1) script
|
||||
* available at: http://code.google.com/p/arc90labs-readability
|
||||
*/
|
||||
|
||||
var REGEXPS = {
|
||||
// NOTE: These two regular expressions are duplicated in
|
||||
// Readability.js. Please keep both copies in sync.
|
||||
unlikelyCandidates: /-ad-|ai2html|banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|foot|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote/i,
|
||||
okMaybeItsACandidate: /and|article|body|column|main|shadow/i,
|
||||
};
|
||||
|
||||
function isNodeVisible(node) {
|
||||
// Have to null-check node.style to deal with SVG and MathML nodes.
|
||||
return (!node.style || node.style.display != "none") && !node.hasAttribute("hidden");
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides whether or not the document is reader-able without parsing the whole thing.
|
||||
*
|
||||
* @return boolean Whether or not we suspect Readability.parse() will suceeed at returning an article object.
|
||||
*/
|
||||
function isProbablyReaderable(doc, isVisible) {
|
||||
if (!isVisible) {
|
||||
isVisible = isNodeVisible;
|
||||
}
|
||||
|
||||
var nodes = doc.querySelectorAll("p, pre");
|
||||
|
||||
// Get <div> nodes which have <br> node(s) and append them into the `nodes` variable.
|
||||
// Some articles' DOM structures might look like
|
||||
// <div>
|
||||
// Sentences<br>
|
||||
// <br>
|
||||
// Sentences<br>
|
||||
// </div>
|
||||
var brNodes = doc.querySelectorAll("div > br");
|
||||
if (brNodes.length) {
|
||||
var set = new Set(nodes);
|
||||
[].forEach.call(brNodes, function(node) {
|
||||
set.add(node.parentNode);
|
||||
});
|
||||
nodes = Array.from(set);
|
||||
}
|
||||
|
||||
var score = 0;
|
||||
// This is a little cheeky, we use the accumulator 'score' to decide what to return from
|
||||
// this callback:
|
||||
return [].some.call(nodes, function(node) {
|
||||
if (!isVisible(node))
|
||||
return false;
|
||||
|
||||
var matchString = node.className + " " + node.id;
|
||||
if (REGEXPS.unlikelyCandidates.test(matchString) &&
|
||||
!REGEXPS.okMaybeItsACandidate.test(matchString)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (node.matches("li p")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var textContentLength = node.textContent.trim().length;
|
||||
if (textContentLength < 140) {
|
||||
return false;
|
||||
}
|
||||
|
||||
score += Math.sqrt(textContentLength - 140);
|
||||
|
||||
if (score > 20) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof exports === "object") {
|
||||
exports.isProbablyReaderable = isProbablyReaderable;
|
||||
}
|
@@ -1,3 +1,5 @@
|
||||
// https://github.com/mozilla/readability/tree/814f0a3884350b6f1adfdebb79ca3599e9806605
|
||||
|
||||
/*eslint-env es6:false*/
|
||||
/*
|
||||
* Copyright (c) 2010 Arc90 Inc
|
||||
@@ -39,6 +41,7 @@ function Readability(doc, options) {
|
||||
this._articleTitle = null;
|
||||
this._articleByline = null;
|
||||
this._articleDir = null;
|
||||
this._articleSiteName = null;
|
||||
this._attempts = [];
|
||||
|
||||
// Configurable options
|
||||
@@ -111,15 +114,18 @@ Readability.prototype = {
|
||||
// All of the regular expressions in use within readability.
|
||||
// Defined up here so we don't instantiate them repeatedly in loops.
|
||||
REGEXPS: {
|
||||
unlikelyCandidates: /banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|foot|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote/i,
|
||||
// NOTE: These two regular expressions are duplicated in
|
||||
// Readability-readerable.js. Please keep both copies in sync.
|
||||
unlikelyCandidates: /-ad-|ai2html|banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|foot|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote/i,
|
||||
okMaybeItsACandidate: /and|article|body|column|main|shadow/i,
|
||||
|
||||
positive: /article|body|content|entry|hentry|h-entry|main|page|pagination|post|text|blog|story/i,
|
||||
negative: /hidden|^hid$| hid$| hid |^hid |banner|combx|comment|com-|contact|foot|footer|footnote|masthead|media|meta|outbrain|promo|related|scroll|share|shoutbox|sidebar|skyscraper|sponsor|shopping|tags|tool|widget/i,
|
||||
negative: /hidden|^hid$| hid$| hid |^hid |banner|combx|comment|com-|contact|foot|footer|footnote|gdpr|masthead|media|meta|outbrain|promo|related|scroll|share|shoutbox|sidebar|skyscraper|sponsor|shopping|tags|tool|widget/i,
|
||||
extraneous: /print|archive|comment|discuss|e[\-]?mail|share|reply|all|login|sign|single|utility/i,
|
||||
byline: /byline|author|dateline|writtenby|p-author/i,
|
||||
replaceFonts: /<(\/?)font[^>]*>/gi,
|
||||
normalize: /\s{2,}/g,
|
||||
videos: /\/\/(www\.)?(dailymotion|youtube|youtube-nocookie|player\.vimeo)\.com/i,
|
||||
videos: /\/\/(www\.)?((dailymotion|youtube|youtube-nocookie|player\.vimeo|v\.qq)\.com|(archive|upload\.wikimedia)\.org|player\.twitch\.tv)/i,
|
||||
nextLink: /(next|weiter|continue|>([^\|]|$)|»([^\|]|$))/i,
|
||||
prevLink: /(prev|earl|old|new|<|«)/i,
|
||||
whitespace: /^\s*$/,
|
||||
@@ -260,7 +266,7 @@ Readability.prototype = {
|
||||
|
||||
_getAllNodesWithTag: function(node, tagNames) {
|
||||
if (node.querySelectorAll) {
|
||||
return node.querySelectorAll(tagNames.join(','));
|
||||
return node.querySelectorAll(tagNames.join(","));
|
||||
}
|
||||
return [].concat.apply([], tagNames.map(function(tag) {
|
||||
var collection = node.getElementsByTagName(tag);
|
||||
@@ -320,7 +326,7 @@ Readability.prototype = {
|
||||
return uri;
|
||||
}
|
||||
|
||||
var links = articleContent.getElementsByTagName("a");
|
||||
var links = this._getAllNodesWithTag(articleContent, ["a"]);
|
||||
this._forEachNode(links, function(link) {
|
||||
var href = link.getAttribute("href");
|
||||
if (href) {
|
||||
@@ -335,7 +341,7 @@ Readability.prototype = {
|
||||
}
|
||||
});
|
||||
|
||||
var imgs = articleContent.getElementsByTagName("img");
|
||||
var imgs = this._getAllNodesWithTag(articleContent, ["img"]);
|
||||
this._forEachNode(imgs, function(img) {
|
||||
var src = img.getAttribute("src");
|
||||
if (src) {
|
||||
@@ -355,11 +361,11 @@ Readability.prototype = {
|
||||
var origTitle = "";
|
||||
|
||||
try {
|
||||
curTitle = origTitle = doc.title;
|
||||
curTitle = origTitle = doc.title.trim();
|
||||
|
||||
// If they had an element with id "title" in their HTML
|
||||
if (typeof curTitle !== "string")
|
||||
curTitle = origTitle = this._getInnerText(doc.getElementsByTagName('title')[0]);
|
||||
curTitle = origTitle = this._getInnerText(doc.getElementsByTagName("title")[0]);
|
||||
} catch (e) {/* ignore exceptions setting the title. */}
|
||||
|
||||
var titleHadHierarchicalSeparators = false;
|
||||
@@ -370,44 +376,45 @@ Readability.prototype = {
|
||||
// If there's a separator in the title, first remove the final part
|
||||
if ((/ [\|\-\\\/>»] /).test(curTitle)) {
|
||||
titleHadHierarchicalSeparators = / [\\\/>»] /.test(curTitle);
|
||||
curTitle = origTitle.replace(/(.*)[\|\-\\\/>»] .*/gi, '$1');
|
||||
curTitle = origTitle.replace(/(.*)[\|\-\\\/>»] .*/gi, "$1");
|
||||
|
||||
// If the resulting title is too short (3 words or fewer), remove
|
||||
// the first part instead:
|
||||
if (wordCount(curTitle) < 3)
|
||||
curTitle = origTitle.replace(/[^\|\-\\\/>»]*[\|\-\\\/>»](.*)/gi, '$1');
|
||||
} else if (curTitle.indexOf(': ') !== -1) {
|
||||
curTitle = origTitle.replace(/[^\|\-\\\/>»]*[\|\-\\\/>»](.*)/gi, "$1");
|
||||
} else if (curTitle.indexOf(": ") !== -1) {
|
||||
// Check if we have an heading containing this exact string, so we
|
||||
// could assume it's the full title.
|
||||
var headings = this._concatNodeLists(
|
||||
doc.getElementsByTagName('h1'),
|
||||
doc.getElementsByTagName('h2')
|
||||
doc.getElementsByTagName("h1"),
|
||||
doc.getElementsByTagName("h2")
|
||||
);
|
||||
var trimmedTitle = curTitle.trim();
|
||||
var match = this._someNode(headings, function(heading) {
|
||||
return heading.textContent === curTitle;
|
||||
return heading.textContent.trim() === trimmedTitle;
|
||||
});
|
||||
|
||||
// If we don't, let's extract the title out of the original title string.
|
||||
if (!match) {
|
||||
curTitle = origTitle.substring(origTitle.lastIndexOf(':') + 1);
|
||||
curTitle = origTitle.substring(origTitle.lastIndexOf(":") + 1);
|
||||
|
||||
// If the title is now too short, try the first colon instead:
|
||||
if (wordCount(curTitle) < 3) {
|
||||
curTitle = origTitle.substring(origTitle.indexOf(':') + 1);
|
||||
curTitle = origTitle.substring(origTitle.indexOf(":") + 1);
|
||||
// But if we have too many words before the colon there's something weird
|
||||
// with the titles and the H tags so let's just use the original title instead
|
||||
} else if (wordCount(origTitle.substr(0, origTitle.indexOf(':'))) > 5) {
|
||||
} else if (wordCount(origTitle.substr(0, origTitle.indexOf(":"))) > 5) {
|
||||
curTitle = origTitle;
|
||||
}
|
||||
}
|
||||
} else if (curTitle.length > 150 || curTitle.length < 15) {
|
||||
var hOnes = doc.getElementsByTagName('h1');
|
||||
var hOnes = doc.getElementsByTagName("h1");
|
||||
|
||||
if (hOnes.length === 1)
|
||||
curTitle = this._getInnerText(hOnes[0]);
|
||||
}
|
||||
|
||||
curTitle = curTitle.trim();
|
||||
curTitle = curTitle.trim().replace(this.REGEXPS.normalize, " ");
|
||||
// If we now have 4 words or fewer as our title, and either no
|
||||
// 'hierarchical' separators (\, /, > or ») were found in the original
|
||||
// title or we decreased the number of words by more than 1 word, use
|
||||
@@ -497,7 +504,8 @@ Readability.prototype = {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!this._isPhrasingContent(next)) break;
|
||||
if (!this._isPhrasingContent(next))
|
||||
break;
|
||||
|
||||
// Otherwise, make this node a child of the new <p>.
|
||||
var sibling = next.nextSibling;
|
||||
@@ -505,7 +513,12 @@ Readability.prototype = {
|
||||
next = sibling;
|
||||
}
|
||||
|
||||
while (p.lastChild && this._isWhitespace(p.lastChild)) p.removeChild(p.lastChild);
|
||||
while (p.lastChild && this._isWhitespace(p.lastChild)) {
|
||||
p.removeChild(p.lastChild);
|
||||
}
|
||||
|
||||
if (p.parentNode.tagName === "P")
|
||||
this._setNodeTag(p.parentNode, "DIV");
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -527,7 +540,16 @@ Readability.prototype = {
|
||||
replacement.readability = node.readability;
|
||||
|
||||
for (var i = 0; i < node.attributes.length; i++) {
|
||||
replacement.setAttribute(node.attributes[i].name, node.attributes[i].value);
|
||||
try {
|
||||
replacement.setAttribute(node.attributes[i].name, node.attributes[i].value);
|
||||
} catch (ex) {
|
||||
/* it's possible for setAttribute() to throw if the attribute name
|
||||
* isn't a valid XML Name. Such attributes can however be parsed from
|
||||
* source in HTML docs, see https://github.com/whatwg/html/issues/4275,
|
||||
* so we can hit them here and then throw. We don't care about such
|
||||
* attributes so we ignore them.
|
||||
*/
|
||||
}
|
||||
}
|
||||
return replacement;
|
||||
},
|
||||
@@ -547,6 +569,8 @@ Readability.prototype = {
|
||||
// visually linked to other content-ful elements (text, images, etc.).
|
||||
this._markDataTables(articleContent);
|
||||
|
||||
this._fixLazyImages(articleContent);
|
||||
|
||||
// Clean out junk from the article content
|
||||
this._cleanConditionally(articleContent, "form");
|
||||
this._cleanConditionally(articleContent, "fieldset");
|
||||
@@ -557,16 +581,21 @@ Readability.prototype = {
|
||||
this._clean(articleContent, "link");
|
||||
this._clean(articleContent, "aside");
|
||||
|
||||
// Clean out elements have "share" in their id/class combinations from final top candidates,
|
||||
// Clean out elements with little content that have "share" in their id/class combinations from final top candidates,
|
||||
// which means we don't remove the top candidates even they have "share".
|
||||
this._forEachNode(articleContent.children, function(topCandidate) {
|
||||
this._cleanMatchedNodes(topCandidate, /share/);
|
||||
|
||||
var shareElementThreshold = this.DEFAULT_CHAR_THRESHOLD;
|
||||
|
||||
this._forEachNode(articleContent.children, function (topCandidate) {
|
||||
this._cleanMatchedNodes(topCandidate, function (node, matchString) {
|
||||
return /share/.test(matchString) && node.textContent.length < shareElementThreshold;
|
||||
});
|
||||
});
|
||||
|
||||
// If there is only one h2 and its text content substantially equals article title,
|
||||
// they are probably using it as a header and not a subheader,
|
||||
// so remove it since we already extract the title separately.
|
||||
var h2 = articleContent.getElementsByTagName('h2');
|
||||
var h2 = articleContent.getElementsByTagName("h2");
|
||||
if (h2.length === 1) {
|
||||
var lengthSimilarRate = (h2[0].textContent.length - this._articleTitle.length) / this._articleTitle.length;
|
||||
if (Math.abs(lengthSimilarRate) < 0.5) {
|
||||
@@ -596,12 +625,12 @@ Readability.prototype = {
|
||||
this._cleanConditionally(articleContent, "div");
|
||||
|
||||
// Remove extra paragraphs
|
||||
this._removeNodes(articleContent.getElementsByTagName('p'), function (paragraph) {
|
||||
var imgCount = paragraph.getElementsByTagName('img').length;
|
||||
var embedCount = paragraph.getElementsByTagName('embed').length;
|
||||
var objectCount = paragraph.getElementsByTagName('object').length;
|
||||
this._removeNodes(articleContent.getElementsByTagName("p"), function (paragraph) {
|
||||
var imgCount = paragraph.getElementsByTagName("img").length;
|
||||
var embedCount = paragraph.getElementsByTagName("embed").length;
|
||||
var objectCount = paragraph.getElementsByTagName("object").length;
|
||||
// At this point, nasty iframes have been removed, only remain embedded video ones.
|
||||
var iframeCount = paragraph.getElementsByTagName('iframe').length;
|
||||
var iframeCount = paragraph.getElementsByTagName("iframe").length;
|
||||
var totalCount = imgCount + embedCount + objectCount + iframeCount;
|
||||
|
||||
return totalCount === 0 && !this._getInnerText(paragraph, false);
|
||||
@@ -612,6 +641,19 @@ Readability.prototype = {
|
||||
if (next && next.tagName == "P")
|
||||
br.parentNode.removeChild(br);
|
||||
});
|
||||
|
||||
// Remove single-cell tables
|
||||
this._forEachNode(this._getAllNodesWithTag(articleContent, ["table"]), function(table) {
|
||||
var tbody = this._hasSingleTagInsideElement(table, "TBODY") ? table.firstElementChild : table;
|
||||
if (this._hasSingleTagInsideElement(tbody, "TR")) {
|
||||
var row = tbody.firstElementChild;
|
||||
if (this._hasSingleTagInsideElement(row, "TD")) {
|
||||
var cell = row.firstElementChild;
|
||||
cell = this._setNodeTag(cell, this._everyNode(cell.childNodes, this._isPhrasingContent) ? "P" : "DIV");
|
||||
table.parentNode.replaceChild(cell, table);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -625,34 +667,34 @@ Readability.prototype = {
|
||||
node.readability = {"contentScore": 0};
|
||||
|
||||
switch (node.tagName) {
|
||||
case 'DIV':
|
||||
case "DIV":
|
||||
node.readability.contentScore += 5;
|
||||
break;
|
||||
|
||||
case 'PRE':
|
||||
case 'TD':
|
||||
case 'BLOCKQUOTE':
|
||||
case "PRE":
|
||||
case "TD":
|
||||
case "BLOCKQUOTE":
|
||||
node.readability.contentScore += 3;
|
||||
break;
|
||||
|
||||
case 'ADDRESS':
|
||||
case 'OL':
|
||||
case 'UL':
|
||||
case 'DL':
|
||||
case 'DD':
|
||||
case 'DT':
|
||||
case 'LI':
|
||||
case 'FORM':
|
||||
case "ADDRESS":
|
||||
case "OL":
|
||||
case "UL":
|
||||
case "DL":
|
||||
case "DD":
|
||||
case "DT":
|
||||
case "LI":
|
||||
case "FORM":
|
||||
node.readability.contentScore -= 3;
|
||||
break;
|
||||
|
||||
case 'H1':
|
||||
case 'H2':
|
||||
case 'H3':
|
||||
case 'H4':
|
||||
case 'H5':
|
||||
case 'H6':
|
||||
case 'TH':
|
||||
case "H1":
|
||||
case "H2":
|
||||
case "H3":
|
||||
case "H4":
|
||||
case "H5":
|
||||
case "H6":
|
||||
case "TH":
|
||||
node.readability.contentScore -= 5;
|
||||
break;
|
||||
}
|
||||
@@ -691,37 +733,6 @@ Readability.prototype = {
|
||||
return node && node.nextElementSibling;
|
||||
},
|
||||
|
||||
/**
|
||||
* Like _getNextNode, but for DOM implementations with no
|
||||
* firstElementChild/nextElementSibling functionality...
|
||||
*/
|
||||
_getNextNodeNoElementProperties: function(node, ignoreSelfAndKids) {
|
||||
function nextSiblingEl(n) {
|
||||
do {
|
||||
n = n.nextSibling;
|
||||
} while (n && n.nodeType !== n.ELEMENT_NODE);
|
||||
return n;
|
||||
}
|
||||
// First check for kids if those aren't being ignored
|
||||
if (!ignoreSelfAndKids && node.children[0]) {
|
||||
return node.children[0];
|
||||
}
|
||||
// Then for siblings...
|
||||
var next = nextSiblingEl(node);
|
||||
if (next) {
|
||||
return next;
|
||||
}
|
||||
// And finally, move up the parent chain *and* find a sibling
|
||||
// (because this is depth-first traversal, we will have already
|
||||
// seen the parent nodes themselves).
|
||||
do {
|
||||
node = node.parentNode;
|
||||
if (node)
|
||||
next = nextSiblingEl(node);
|
||||
} while (node && !next);
|
||||
return node && next;
|
||||
},
|
||||
|
||||
_checkByline: function(node, matchString) {
|
||||
if (this._articleByline) {
|
||||
return false;
|
||||
@@ -729,9 +740,10 @@ Readability.prototype = {
|
||||
|
||||
if (node.getAttribute !== undefined) {
|
||||
var rel = node.getAttribute("rel");
|
||||
var itemprop = node.getAttribute("itemprop");
|
||||
}
|
||||
|
||||
if ((rel === "author" || this.REGEXPS.byline.test(matchString)) && this._isValidByline(node.textContent)) {
|
||||
if ((rel === "author" || (itemprop && itemprop.indexOf("author") !== -1) || this.REGEXPS.byline.test(matchString)) && this._isValidByline(node.textContent)) {
|
||||
this._articleByline = node.textContent.trim();
|
||||
return true;
|
||||
}
|
||||
@@ -784,6 +796,12 @@ Readability.prototype = {
|
||||
while (node) {
|
||||
var matchString = node.className + " " + node.id;
|
||||
|
||||
if (!this._isProbablyVisible(node)) {
|
||||
this.log("Removing hidden node - " + matchString);
|
||||
node = this._removeAndGetNext(node);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check to see if this node is a byline, and remove it if it is.
|
||||
if (this._checkByline(node, matchString)) {
|
||||
node = this._removeAndGetNext(node);
|
||||
@@ -794,6 +812,7 @@ Readability.prototype = {
|
||||
if (stripUnlikelyCandidates) {
|
||||
if (this.REGEXPS.unlikelyCandidates.test(matchString) &&
|
||||
!this.REGEXPS.okMaybeItsACandidate.test(matchString) &&
|
||||
!this._hasAncestorTag(node, "table") &&
|
||||
node.tagName !== "BODY" &&
|
||||
node.tagName !== "A") {
|
||||
this.log("Removing unlikely candidate - " + matchString);
|
||||
@@ -826,12 +845,14 @@ Readability.prototype = {
|
||||
if (p !== null) {
|
||||
p.appendChild(childNode);
|
||||
} else if (!this._isWhitespace(childNode)) {
|
||||
p = doc.createElement('p');
|
||||
p = doc.createElement("p");
|
||||
node.replaceChild(p, childNode);
|
||||
p.appendChild(childNode);
|
||||
}
|
||||
} else if (p !== null) {
|
||||
while (p.lastChild && this._isWhitespace(p.lastChild)) p.removeChild(p.lastChild);
|
||||
while (p.lastChild && this._isWhitespace(p.lastChild)) {
|
||||
p.removeChild(p.lastChild);
|
||||
}
|
||||
p = null;
|
||||
}
|
||||
childNode = nextSibling;
|
||||
@@ -841,7 +862,7 @@ Readability.prototype = {
|
||||
// element. DIVs with only a P element inside and no text content can be
|
||||
// safely converted into plain P elements to avoid confusing the scoring
|
||||
// algorithm with DIVs with are, in practice, paragraphs.
|
||||
if (this._hasSinglePInsideElement(node) && this._getLinkDensity(node) < 0.25) {
|
||||
if (this._hasSingleTagInsideElement(node, "P") && this._getLinkDensity(node) < 0.25) {
|
||||
var newNode = node.children[0];
|
||||
node.parentNode.replaceChild(newNode, node);
|
||||
node = newNode;
|
||||
@@ -862,7 +883,7 @@ Readability.prototype = {
|
||||
**/
|
||||
var candidates = [];
|
||||
this._forEachNode(elementsToScore, function(elementToScore) {
|
||||
if (!elementToScore.parentNode || typeof(elementToScore.parentNode.tagName) === 'undefined')
|
||||
if (!elementToScore.parentNode || typeof(elementToScore.parentNode.tagName) === "undefined")
|
||||
return;
|
||||
|
||||
// If this paragraph is less than 25 characters, don't even count it.
|
||||
@@ -881,17 +902,17 @@ Readability.prototype = {
|
||||
contentScore += 1;
|
||||
|
||||
// Add points for any commas within this paragraph.
|
||||
contentScore += innerText.split(',').length;
|
||||
contentScore += innerText.split(",").length;
|
||||
|
||||
// For every 100 characters in this paragraph, add another point. Up to 3 points.
|
||||
contentScore += Math.min(Math.floor(innerText.length / 100), 3);
|
||||
|
||||
// Initialize and score ancestors.
|
||||
this._forEachNode(ancestors, function(ancestor, level) {
|
||||
if (!ancestor.tagName || !ancestor.parentNode || typeof(ancestor.parentNode.tagName) === 'undefined')
|
||||
if (!ancestor.tagName || !ancestor.parentNode || typeof(ancestor.parentNode.tagName) === "undefined")
|
||||
return;
|
||||
|
||||
if (typeof(ancestor.readability) === 'undefined') {
|
||||
if (typeof(ancestor.readability) === "undefined") {
|
||||
this._initializeNode(ancestor);
|
||||
candidates.push(ancestor);
|
||||
}
|
||||
@@ -922,7 +943,7 @@ Readability.prototype = {
|
||||
var candidateScore = candidate.readability.contentScore * (1 - this._getLinkDensity(candidate));
|
||||
candidate.readability.contentScore = candidateScore;
|
||||
|
||||
this.log('Candidate:', candidate, "with score " + candidateScore);
|
||||
this.log("Candidate:", candidate, "with score " + candidateScore);
|
||||
|
||||
for (var t = 0; t < this._nbTopCandidates; t++) {
|
||||
var aTopCandidate = topCandidates[t];
|
||||
@@ -1041,8 +1062,8 @@ Readability.prototype = {
|
||||
var sibling = siblings[s];
|
||||
var append = false;
|
||||
|
||||
this.log("Looking at sibling node:", sibling, sibling.readability ? ("with score " + sibling.readability.contentScore) : '');
|
||||
this.log("Sibling has score", sibling.readability ? sibling.readability.contentScore : 'Unknown');
|
||||
this.log("Looking at sibling node:", sibling, sibling.readability ? ("with score " + sibling.readability.contentScore) : "");
|
||||
this.log("Sibling has score", sibling.readability ? sibling.readability.contentScore : "Unknown");
|
||||
|
||||
if (sibling === topCandidate) {
|
||||
append = true;
|
||||
@@ -1076,7 +1097,7 @@ Readability.prototype = {
|
||||
if (this.ALTER_TO_DIV_EXCEPTIONS.indexOf(sibling.nodeName) === -1) {
|
||||
// We have a node that isn't a common block level element, like a form or td tag.
|
||||
// Turn it into a div so it doesn't get filtered out later by accident.
|
||||
this.log("Altering sibling:", sibling, 'to div.');
|
||||
this.log("Altering sibling:", sibling, "to div.");
|
||||
|
||||
sibling = this._setNodeTag(sibling, "DIV");
|
||||
}
|
||||
@@ -1144,7 +1165,7 @@ Readability.prototype = {
|
||||
this._attempts.push({articleContent: articleContent, textLength: textLength});
|
||||
// No luck after removing flags, just return the longest text we found during the different loops
|
||||
this._attempts.sort(function (a, b) {
|
||||
return a.textLength < b.textLength;
|
||||
return b.textLength - a.textLength;
|
||||
});
|
||||
|
||||
// But first check if we actually have something
|
||||
@@ -1184,7 +1205,7 @@ Readability.prototype = {
|
||||
* @return Boolean - whether the input string is a byline.
|
||||
*/
|
||||
_isValidByline: function(byline) {
|
||||
if (typeof byline == 'string' || byline instanceof String) {
|
||||
if (typeof byline == "string" || byline instanceof String) {
|
||||
byline = byline.trim();
|
||||
return (byline.length > 0) && (byline.length < 100);
|
||||
}
|
||||
@@ -1201,61 +1222,75 @@ Readability.prototype = {
|
||||
var values = {};
|
||||
var metaElements = this._doc.getElementsByTagName("meta");
|
||||
|
||||
// Match "description", or Twitter's "twitter:description" (Cards)
|
||||
// in name attribute.
|
||||
var namePattern = /^\s*((twitter)\s*:\s*)?(description|title)\s*$/gi;
|
||||
// property is a space-separated list of values
|
||||
var propertyPattern = /\s*(dc|dcterm|og|twitter)\s*:\s*(author|creator|description|title|site_name)\s*/gi;
|
||||
|
||||
// Match Facebook's Open Graph title & description properties.
|
||||
var propertyPattern = /^\s*og\s*:\s*(description|title)\s*$/gi;
|
||||
// name is a single value
|
||||
var namePattern = /^\s*(?:(dc|dcterm|og|twitter|weibo:(article|webpage))\s*[\.:]\s*)?(author|creator|description|title|site_name)\s*$/i;
|
||||
|
||||
// Find description tags.
|
||||
this._forEachNode(metaElements, function(element) {
|
||||
var elementName = element.getAttribute("name");
|
||||
var elementProperty = element.getAttribute("property");
|
||||
|
||||
if ([elementName, elementProperty].indexOf("author") !== -1) {
|
||||
metadata.byline = element.getAttribute("content");
|
||||
var content = element.getAttribute("content");
|
||||
if (!content) {
|
||||
return;
|
||||
}
|
||||
|
||||
var matches = null;
|
||||
var name = null;
|
||||
if (namePattern.test(elementName)) {
|
||||
name = elementName;
|
||||
} else if (propertyPattern.test(elementProperty)) {
|
||||
name = elementProperty;
|
||||
}
|
||||
|
||||
if (name) {
|
||||
var content = element.getAttribute("content");
|
||||
if (elementProperty) {
|
||||
matches = elementProperty.match(propertyPattern);
|
||||
if (matches) {
|
||||
for (var i = matches.length - 1; i >= 0; i--) {
|
||||
// Convert to lowercase, and remove any whitespace
|
||||
// so we can match below.
|
||||
name = matches[i].toLowerCase().replace(/\s/g, "");
|
||||
// multiple authors
|
||||
values[name] = content.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!matches && elementName && namePattern.test(elementName)) {
|
||||
name = elementName;
|
||||
if (content) {
|
||||
// Convert to lowercase and remove any whitespace
|
||||
// so we can match below.
|
||||
name = name.toLowerCase().replace(/\s/g, '');
|
||||
// Convert to lowercase, remove any whitespace, and convert dots
|
||||
// to colons so we can match below.
|
||||
name = name.toLowerCase().replace(/\s/g, "").replace(/\./g, ":");
|
||||
values[name] = content.trim();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if ("description" in values) {
|
||||
metadata.excerpt = values["description"];
|
||||
} else if ("og:description" in values) {
|
||||
// Use facebook open graph description.
|
||||
metadata.excerpt = values["og:description"];
|
||||
} else if ("twitter:description" in values) {
|
||||
// Use twitter cards description.
|
||||
metadata.excerpt = values["twitter:description"];
|
||||
// get title
|
||||
metadata.title = values["dc:title"] ||
|
||||
values["dcterm:title"] ||
|
||||
values["og:title"] ||
|
||||
values["weibo:article:title"] ||
|
||||
values["weibo:webpage:title"] ||
|
||||
values["title"] ||
|
||||
values["twitter:title"];
|
||||
|
||||
if (!metadata.title) {
|
||||
metadata.title = this._getArticleTitle();
|
||||
}
|
||||
|
||||
metadata.title = this._getArticleTitle();
|
||||
if (!metadata.title) {
|
||||
if ("og:title" in values) {
|
||||
// Use facebook open graph title.
|
||||
metadata.title = values["og:title"];
|
||||
} else if ("twitter:title" in values) {
|
||||
// Use twitter cards title.
|
||||
metadata.title = values["twitter:title"];
|
||||
}
|
||||
}
|
||||
// get author
|
||||
metadata.byline = values["dc:creator"] ||
|
||||
values["dcterm:creator"] ||
|
||||
values["author"];
|
||||
|
||||
// get description
|
||||
metadata.excerpt = values["dc:description"] ||
|
||||
values["dcterm:description"] ||
|
||||
values["og:description"] ||
|
||||
values["weibo:article:description"] ||
|
||||
values["weibo:webpage:description"] ||
|
||||
values["description"] ||
|
||||
values["twitter:description"];
|
||||
|
||||
// get site name
|
||||
metadata.siteName = values["og:site_name"];
|
||||
|
||||
return metadata;
|
||||
},
|
||||
@@ -1266,24 +1301,25 @@ Readability.prototype = {
|
||||
* @param Element
|
||||
**/
|
||||
_removeScripts: function(doc) {
|
||||
this._removeNodes(doc.getElementsByTagName('script'), function(scriptNode) {
|
||||
this._removeNodes(doc.getElementsByTagName("script"), function(scriptNode) {
|
||||
scriptNode.nodeValue = "";
|
||||
scriptNode.removeAttribute('src');
|
||||
scriptNode.removeAttribute("src");
|
||||
return true;
|
||||
});
|
||||
this._removeNodes(doc.getElementsByTagName('noscript'));
|
||||
this._removeNodes(doc.getElementsByTagName("noscript"));
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if this node has only whitespace and a single P element
|
||||
* Check if this node has only whitespace and a single element with given tag
|
||||
* Returns false if the DIV node contains non-empty text nodes
|
||||
* or if it contains no P or more than 1 element.
|
||||
* or if it contains no element with given tag or more than 1 element.
|
||||
*
|
||||
* @param Element
|
||||
* @param string tag of child element
|
||||
**/
|
||||
_hasSinglePInsideElement: function(element) {
|
||||
// There should be exactly 1 element child which is a P:
|
||||
if (element.children.length != 1 || element.children[0].tagName !== "P") {
|
||||
_hasSingleTagInsideElement: function(element, tag) {
|
||||
// There should be exactly 1 element child with given tag
|
||||
if (element.children.length != 1 || element.children[0].tagName !== tag) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1337,7 +1373,7 @@ Readability.prototype = {
|
||||
* @return string
|
||||
**/
|
||||
_getInnerText: function(e, normalizeSpaces) {
|
||||
normalizeSpaces = (typeof normalizeSpaces === 'undefined') ? true : normalizeSpaces;
|
||||
normalizeSpaces = (typeof normalizeSpaces === "undefined") ? true : normalizeSpaces;
|
||||
var textContent = e.textContent.trim();
|
||||
|
||||
if (normalizeSpaces) {
|
||||
@@ -1366,7 +1402,7 @@ Readability.prototype = {
|
||||
* @return void
|
||||
**/
|
||||
_cleanStyles: function(e) {
|
||||
if (!e || e.tagName.toLowerCase() === 'svg')
|
||||
if (!e || e.tagName.toLowerCase() === "svg")
|
||||
return;
|
||||
|
||||
// Remove `style` and deprecated presentational attributes
|
||||
@@ -1375,8 +1411,8 @@ Readability.prototype = {
|
||||
}
|
||||
|
||||
if (this.DEPRECATED_SIZE_ATTRIBUTE_ELEMS.indexOf(e.tagName) !== -1) {
|
||||
e.removeAttribute('width');
|
||||
e.removeAttribute('height');
|
||||
e.removeAttribute("width");
|
||||
e.removeAttribute("height");
|
||||
}
|
||||
|
||||
var cur = e.firstElementChild;
|
||||
@@ -1422,7 +1458,7 @@ Readability.prototype = {
|
||||
var weight = 0;
|
||||
|
||||
// Look for a special classname
|
||||
if (typeof(e.className) === 'string' && e.className !== '') {
|
||||
if (typeof(e.className) === "string" && e.className !== "") {
|
||||
if (this.REGEXPS.negative.test(e.className))
|
||||
weight -= 25;
|
||||
|
||||
@@ -1431,7 +1467,7 @@ Readability.prototype = {
|
||||
}
|
||||
|
||||
// Look for a special ID
|
||||
if (typeof(e.id) === 'string' && e.id !== '') {
|
||||
if (typeof(e.id) === "string" && e.id !== "") {
|
||||
if (this.REGEXPS.negative.test(e.id))
|
||||
weight -= 25;
|
||||
|
||||
@@ -1456,17 +1492,17 @@ Readability.prototype = {
|
||||
this._removeNodes(e.getElementsByTagName(tag), function(element) {
|
||||
// Allow youtube and vimeo videos through as people usually want to see those.
|
||||
if (isEmbed) {
|
||||
var attributeValues = [].map.call(element.attributes, function(attr) {
|
||||
return attr.value;
|
||||
}).join("|");
|
||||
|
||||
// First, check the elements attributes to see if any of them contain youtube or vimeo
|
||||
if (this.REGEXPS.videos.test(attributeValues))
|
||||
return false;
|
||||
for (var i = 0; i < element.attributes.length; i++) {
|
||||
if (this.REGEXPS.videos.test(element.attributes[i].value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Then check the elements inside this element for the same.
|
||||
if (this.REGEXPS.videos.test(element.innerHTML))
|
||||
// For embed with <object> tag, check inner HTML as well.
|
||||
if (element.tagName === "object" && this.REGEXPS.videos.test(element.innerHTML)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -1584,6 +1620,39 @@ Readability.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
/* convert images and figures that have properties like data-src into images that can be loaded without JS */
|
||||
_fixLazyImages: function (root) {
|
||||
this._forEachNode(this._getAllNodesWithTag(root, ["img", "picture", "figure"]), function (elem) {
|
||||
// also check for "null" to work around https://github.com/jsdom/jsdom/issues/2580
|
||||
if ((!elem.src && (!elem.srcset || elem.srcset == "null")) || elem.className.toLowerCase().indexOf("lazy") !== -1) {
|
||||
for (var i = 0; i < elem.attributes.length; i++) {
|
||||
var attr = elem.attributes[i];
|
||||
if (attr.name === "src" || attr.name === "srcset") {
|
||||
continue;
|
||||
}
|
||||
var copyTo = null;
|
||||
if (/\.(jpg|jpeg|png|webp)\s+\d/.test(attr.value)) {
|
||||
copyTo = "srcset";
|
||||
} else if (/^\s*\S+\.(jpg|jpeg|png|webp)\S*\s*$/.test(attr.value)) {
|
||||
copyTo = "src";
|
||||
}
|
||||
if (copyTo) {
|
||||
//if this is an img or picture, set the attribute directly
|
||||
if (elem.tagName === "IMG" || elem.tagName === "PICTURE") {
|
||||
elem.setAttribute(copyTo, attr.value);
|
||||
} else if (elem.tagName === "FIGURE" && !this._getAllNodesWithTag(elem, ["img", "picture"]).length) {
|
||||
//if the item is a <figure> that does not contain an image or picture, create one and place it inside the figure
|
||||
//see the nytimes-3 testcase for an example
|
||||
var img = this._doc.createElement("img");
|
||||
img.setAttribute(copyTo, attr.value);
|
||||
elem.appendChild(img);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Clean an element of all tags of type "tag" if they look fishy.
|
||||
* "Fishy" is an algorithm based on content length, classnames, link density, number of images & embeds, etc.
|
||||
@@ -1602,11 +1671,16 @@ Readability.prototype = {
|
||||
//
|
||||
// TODO: Consider taking into account original contentScore here.
|
||||
this._removeNodes(e.getElementsByTagName(tag), function(node) {
|
||||
// First check if we're in a data table, in which case don't remove us.
|
||||
// First check if this node IS data table, in which case don't remove it.
|
||||
var isDataTable = function(t) {
|
||||
return t._readabilityDataTable;
|
||||
};
|
||||
|
||||
if (tag === "table" && isDataTable(node)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Next check if we're inside a data table, in which case don't remove it as well.
|
||||
if (this._hasAncestorTag(node, "table", -1, isDataTable)) {
|
||||
return false;
|
||||
}
|
||||
@@ -1620,7 +1694,7 @@ Readability.prototype = {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this._getCharCount(node, ',') < 10) {
|
||||
if (this._getCharCount(node, ",") < 10) {
|
||||
// If there are not very many commas, and the number of
|
||||
// non-paragraph elements is more than paragraphs or other
|
||||
// ominous signs, remove the element.
|
||||
@@ -1630,10 +1704,25 @@ Readability.prototype = {
|
||||
var input = node.getElementsByTagName("input").length;
|
||||
|
||||
var embedCount = 0;
|
||||
var embeds = node.getElementsByTagName("embed");
|
||||
for (var ei = 0, il = embeds.length; ei < il; ei += 1) {
|
||||
if (!this.REGEXPS.videos.test(embeds[ei].src))
|
||||
embedCount += 1;
|
||||
var embeds = this._concatNodeLists(
|
||||
node.getElementsByTagName("object"),
|
||||
node.getElementsByTagName("embed"),
|
||||
node.getElementsByTagName("iframe"));
|
||||
|
||||
for (var i = 0; i < embeds.length; i++) {
|
||||
// If this embed has attribute that matches video regex, don't delete it.
|
||||
for (var j = 0; j < embeds[i].attributes.length; j++) {
|
||||
if (this.REGEXPS.videos.test(embeds[i].attributes[j].value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// For embed with <object> tag, check inner HTML as well.
|
||||
if (embeds[i].tagName === "object" && this.REGEXPS.videos.test(embeds[i].innerHTML)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
embedCount++;
|
||||
}
|
||||
|
||||
var linkDensity = this._getLinkDensity(node);
|
||||
@@ -1654,17 +1743,17 @@ Readability.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Clean out elements whose id/class combinations match specific string.
|
||||
* Clean out elements that match the specified conditions
|
||||
*
|
||||
* @param Element
|
||||
* @param RegExp match id/class combination.
|
||||
* @param Function determines whether a node should be removed
|
||||
* @return void
|
||||
**/
|
||||
_cleanMatchedNodes: function(e, regex) {
|
||||
_cleanMatchedNodes: function(e, filter) {
|
||||
var endOfSearchMarkerNode = this._getNextNode(e, true);
|
||||
var next = this._getNextNode(e);
|
||||
while (next && next != endOfSearchMarkerNode) {
|
||||
if (regex.test(next.className + " " + next.id)) {
|
||||
if (filter(next, next.className + " " + next.id)) {
|
||||
next = this._removeAndGetNext(next);
|
||||
} else {
|
||||
next = this._getNextNode(next);
|
||||
@@ -1680,7 +1769,7 @@ Readability.prototype = {
|
||||
**/
|
||||
_cleanHeaders: function(e) {
|
||||
for (var headerIndex = 1; headerIndex < 3; headerIndex += 1) {
|
||||
this._removeNodes(e.getElementsByTagName('h' + headerIndex), function (header) {
|
||||
this._removeNodes(e.getElementsByTagName("h" + headerIndex), function (header) {
|
||||
return this._getClassWeight(header) < 0;
|
||||
});
|
||||
}
|
||||
@@ -1694,63 +1783,8 @@ Readability.prototype = {
|
||||
this._flags = this._flags & ~flag;
|
||||
},
|
||||
|
||||
/**
|
||||
* Decides whether or not the document is reader-able without parsing the whole thing.
|
||||
*
|
||||
* @return boolean Whether or not we suspect parse() will suceeed at returning an article object.
|
||||
*/
|
||||
isProbablyReaderable: function(helperIsVisible) {
|
||||
var nodes = this._getAllNodesWithTag(this._doc, ["p", "pre"]);
|
||||
|
||||
// Get <div> nodes which have <br> node(s) and append them into the `nodes` variable.
|
||||
// Some articles' DOM structures might look like
|
||||
// <div>
|
||||
// Sentences<br>
|
||||
// <br>
|
||||
// Sentences<br>
|
||||
// </div>
|
||||
var brNodes = this._getAllNodesWithTag(this._doc, ["div > br"]);
|
||||
if (brNodes.length) {
|
||||
var set = new Set();
|
||||
[].forEach.call(brNodes, function(node) {
|
||||
set.add(node.parentNode);
|
||||
});
|
||||
nodes = [].concat.apply(Array.from(set), nodes);
|
||||
}
|
||||
|
||||
// FIXME we should have a fallback for helperIsVisible, but this is
|
||||
// problematic because of jsdom's elem.style handling - see
|
||||
// https://github.com/mozilla/readability/pull/186 for context.
|
||||
|
||||
var score = 0;
|
||||
// This is a little cheeky, we use the accumulator 'score' to decide what to return from
|
||||
// this callback:
|
||||
return this._someNode(nodes, function(node) {
|
||||
if (helperIsVisible && !helperIsVisible(node))
|
||||
return false;
|
||||
var matchString = node.className + " " + node.id;
|
||||
|
||||
if (this.REGEXPS.unlikelyCandidates.test(matchString) &&
|
||||
!this.REGEXPS.okMaybeItsACandidate.test(matchString)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (node.matches && node.matches("li p")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var textContentLength = node.textContent.trim().length;
|
||||
if (textContentLength < 140) {
|
||||
return false;
|
||||
}
|
||||
|
||||
score += Math.sqrt(textContentLength - 140);
|
||||
|
||||
if (score > 20) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
_isProbablyVisible: function(node) {
|
||||
return (!node.style || node.style.display != "none") && !node.hasAttribute("hidden");
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1774,9 +1808,6 @@ Readability.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof this._doc.documentElement.firstElementChild === "undefined") {
|
||||
this._getNextNode = this._getNextNodeNoElementProperties;
|
||||
}
|
||||
// Remove script tags from the document.
|
||||
this._removeScripts(this._doc);
|
||||
|
||||
@@ -1812,6 +1843,7 @@ Readability.prototype = {
|
||||
textContent: textContent,
|
||||
length: textContent.length,
|
||||
excerpt: metadata.excerpt,
|
||||
siteName: metadata.siteName || this._articleSiteName
|
||||
};
|
||||
}
|
||||
};
|
||||
|
@@ -14,6 +14,20 @@
|
||||
browserSupportsPromises_ = false;
|
||||
}
|
||||
|
||||
function absoluteUrl(url) {
|
||||
if (!url) return url;
|
||||
const protocol = url.toLowerCase().split(':')[0];
|
||||
if (['http', 'https', 'file'].indexOf(protocol) >= 0) return url;
|
||||
|
||||
if (url.indexOf('//')) {
|
||||
return location.protocol + url;
|
||||
} else if (url[0] === '/') {
|
||||
return location.protocol + '//' + location.host + url;
|
||||
} else {
|
||||
return baseUrl() + '/' + url;
|
||||
}
|
||||
}
|
||||
|
||||
function pageTitle() {
|
||||
const titleElements = document.getElementsByTagName("title");
|
||||
if (titleElements.length) return titleElements[0].text.trim();
|
||||
@@ -30,12 +44,13 @@
|
||||
return output;
|
||||
}
|
||||
|
||||
function getImageSizes(element) {
|
||||
function getImageSizes(element, forceAbsoluteUrls = false) {
|
||||
const images = element.getElementsByTagName('img');
|
||||
const output = {};
|
||||
for (let i = 0; i < images.length; i++) {
|
||||
const img = images[i];
|
||||
output[img.src] = {
|
||||
const src = forceAbsoluteUrls ? absoluteUrl(img.src) : img.src;
|
||||
output[src] = {
|
||||
width: img.width,
|
||||
height: img.height,
|
||||
naturalWidth: img.naturalWidth,
|
||||
@@ -46,7 +61,7 @@
|
||||
}
|
||||
|
||||
// Cleans up element by removing all its invisible children (which we don't want to render as Markdown)
|
||||
function cleanUpElement(element) {
|
||||
function cleanUpElement(element, imageSizes) {
|
||||
const childNodes = element.childNodes;
|
||||
|
||||
for (let i = 0; i < childNodes.length; i++) {
|
||||
@@ -58,11 +73,27 @@
|
||||
if (!isVisible) {
|
||||
element.removeChild(node);
|
||||
} else {
|
||||
cleanUpElement(node);
|
||||
|
||||
if (node.nodeName.toLowerCase() === 'img') {
|
||||
node.src = absoluteUrl(node.src);
|
||||
const imageSize = imageSizes[node.src];
|
||||
if (imageSize) {
|
||||
node.width = imageSize.width;
|
||||
node.height = imageSize.height;
|
||||
}
|
||||
}
|
||||
|
||||
cleanUpElement(node, imageSizes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function documentForReadability() {
|
||||
// Readability directly change the passed document so clone it so as
|
||||
// to preserve the original web page.
|
||||
return document.cloneNode(true);
|
||||
}
|
||||
|
||||
function readabilityProcess() {
|
||||
var uri = {
|
||||
spec: location.href,
|
||||
@@ -72,10 +103,7 @@
|
||||
pathBase: location.protocol + "//" + location.host + location.pathname.substr(0, location.pathname.lastIndexOf("/") + 1)
|
||||
};
|
||||
|
||||
// Readability directly change the passed document so clone it so as
|
||||
// to preserve the original web page.
|
||||
const documentClone = document.cloneNode(true);
|
||||
const readability = new Readability(documentClone); // new window.Readability(uri, documentClone);
|
||||
const readability = new Readability(documentForReadability());
|
||||
const article = readability.parse();
|
||||
|
||||
if (!article) throw new Error('Could not parse HTML document with Readability');
|
||||
@@ -117,11 +145,18 @@
|
||||
}
|
||||
return clippedContentResponse(article.title, article.body, getImageSizes(document));
|
||||
|
||||
} else if (command.name === "isProbablyReaderable") {
|
||||
|
||||
const ok = isProbablyReaderable(documentForReadability());
|
||||
console.info('isProbablyReaderable', ok);
|
||||
return { name: 'isProbablyReaderable', value: ok };
|
||||
|
||||
} else if (command.name === "completePageHtml") {
|
||||
|
||||
const cleanDocument = document.body.cloneNode(true);
|
||||
cleanUpElement(cleanDocument);
|
||||
return clippedContentResponse(pageTitle(), cleanDocument.innerHTML, getImageSizes(document));
|
||||
const imageSizes = getImageSizes(document, true);
|
||||
cleanUpElement(cleanDocument, imageSizes);
|
||||
return clippedContentResponse(pageTitle(), cleanDocument.innerHTML, imageSizes);
|
||||
|
||||
} else if (command.name === "selectedHtml") {
|
||||
|
||||
@@ -250,6 +285,7 @@
|
||||
return {};
|
||||
|
||||
} else if (command.name === "pageUrl") {
|
||||
|
||||
let url = location.origin + location.pathname + location.search;
|
||||
return clippedContentResponse(pageTitle(), url, getImageSizes(document));
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"name": "Joplin Web Clipper [DEV]",
|
||||
"version": "1.0.13",
|
||||
"version": "1.0.14",
|
||||
"description": "Capture and save web pages and screenshots from your browser to Joplin.",
|
||||
"homepage_url": "https://joplinapp.org",
|
||||
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
|
||||
|
@@ -7,6 +7,39 @@ import led_orange from './led_orange.png';
|
||||
const { connect } = require('react-redux');
|
||||
const { bridge } = require('./bridge');
|
||||
|
||||
class PreviewComponent extends React.PureComponent {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.bodyRef = React.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// Because the text size is made twice smaller with CSS, we need
|
||||
// to also reduce the size of the images
|
||||
const imgs = this.bodyRef.current.getElementsByTagName('img');
|
||||
for (const img of imgs) {
|
||||
img.width /= 2;
|
||||
img.height /= 2;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="Preview">
|
||||
<a className={"Confirm Button"} onClick={this.props.onConfirmClick}>Confirm</a>
|
||||
<h2>Preview:</h2>
|
||||
<input className={"Title"} value={this.props.title} onChange={this.props.onTitleChange}/>
|
||||
<div className={"BodyWrapper"}>
|
||||
<div className={"Body"} ref={this.bodyRef} dangerouslySetInnerHTML={{__html: this.props.body_html}}></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class AppComponent extends Component {
|
||||
|
||||
constructor() {
|
||||
@@ -123,6 +156,7 @@ class AppComponent extends Component {
|
||||
async loadContentScripts() {
|
||||
await bridge().tabsExecuteScript({file: "/content_scripts/JSDOMParser.js"});
|
||||
await bridge().tabsExecuteScript({file: "/content_scripts/Readability.js"});
|
||||
await bridge().tabsExecuteScript({file: "/content_scripts/Readability-readerable.js"});
|
||||
await bridge().tabsExecuteScript({file: "/content_scripts/index.js"});
|
||||
}
|
||||
|
||||
@@ -158,6 +192,8 @@ class AppComponent extends Component {
|
||||
id: newFolderId,
|
||||
});
|
||||
}
|
||||
|
||||
bridge().sendCommandToActiveTab({ name: 'isProbablyReaderable' });
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
@@ -208,16 +244,12 @@ class AppComponent extends Component {
|
||||
</div>
|
||||
);
|
||||
} else if (hasContent) {
|
||||
previewComponent = (
|
||||
<div className="Preview">
|
||||
<a className={"Confirm Button"} onClick={this.confirm_click}>Confirm</a>
|
||||
<h2>Preview:</h2>
|
||||
<input className={"Title"} value={content.title} onChange={this.contentTitle_change}/>
|
||||
<div className={"BodyWrapper"}>
|
||||
<div className={"Body"} dangerouslySetInnerHTML={{__html: content.body_html}}></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
previewComponent = <PreviewComponent
|
||||
onConfirmClick={this.confirm_click}
|
||||
title={content.title}
|
||||
body_html={content.body_html}
|
||||
onTitleChange={this.contentTitle_change}
|
||||
/>
|
||||
}
|
||||
|
||||
const clipperStatusComp = () => {
|
||||
@@ -278,11 +310,10 @@ class AppComponent extends Component {
|
||||
const tagsComp = () => {
|
||||
const comps = [];
|
||||
for (let i = 0; i < this.state.selectedTags.length; i++) {
|
||||
comps.push(<div>
|
||||
comps.push(<div key={i}>
|
||||
<input
|
||||
ref={'tagSelector' + i}
|
||||
data-index={i}
|
||||
key={i}
|
||||
type="text"
|
||||
list="tags"
|
||||
value={this.state.selectedTags[i]}
|
||||
@@ -306,11 +337,18 @@ class AppComponent extends Component {
|
||||
tagDataListOptions.push(<option key={tag.id}>{tag.title}</option>);
|
||||
}
|
||||
|
||||
let simplifiedPageButtonLabel = 'Clip simplified page';
|
||||
let simplifiedPageButtonTooltip = '';
|
||||
if (!this.props.isProbablyReaderable) {
|
||||
simplifiedPageButtonLabel += ' ⚠️';
|
||||
simplifiedPageButtonTooltip = 'It might not be possible to create a good simplified version of this page.\nYou may want to clip the complete page instead.';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<div className="Controls">
|
||||
<ul>
|
||||
<li><a className="Button" onClick={this.clipSimplified_click}>Clip simplified page</a></li>
|
||||
<li><a className="Button" onClick={this.clipSimplified_click} title={simplifiedPageButtonTooltip}>{simplifiedPageButtonLabel}</a></li>
|
||||
<li><a className="Button" onClick={this.clipComplete_click}>Clip complete page</a></li>
|
||||
<li><a className="Button" onClick={this.clipSelection_click}>Clip selection</a></li>
|
||||
<li><a className="Button" onClick={this.clipScreenshot_click}>Clip screenshot</a></li>
|
||||
@@ -343,6 +381,7 @@ const mapStateToProps = (state) => {
|
||||
folders: state.folders,
|
||||
tags: state.tags,
|
||||
selectedFolderId: state.selectedFolderId,
|
||||
isProbablyReaderable: state.isProbablyReaderable,
|
||||
};
|
||||
};
|
||||
|
||||
|
@@ -2,6 +2,10 @@ const randomClipperPort = require('./randomClipperPort');
|
||||
|
||||
class Bridge {
|
||||
|
||||
constructor() {
|
||||
this.nounce_ = Date.now();
|
||||
}
|
||||
|
||||
async init(browser, browserSupportsPromises, dispatch) {
|
||||
console.info('Popup: Init bridge');
|
||||
|
||||
@@ -34,6 +38,10 @@ class Bridge {
|
||||
|
||||
this.dispatch({ type: 'CLIPPED_CONTENT_SET', content: content });
|
||||
}
|
||||
|
||||
if (command.name === 'isProbablyReaderable') {
|
||||
this.dispatch({ type: 'IS_PROBABLY_READERABLE', value: command.value });
|
||||
}
|
||||
}
|
||||
|
||||
this.browser_.runtime.onMessage.addListener(this.browser_notify);
|
||||
@@ -264,7 +272,7 @@ class Bridge {
|
||||
await this.tabsSendMessage(tabs[0].id, command);
|
||||
}
|
||||
|
||||
async clipperApiExec(method, path, body) {
|
||||
async clipperApiExec(method, path, query, body) {
|
||||
console.info('Popup: ' + method + ' ' + path);
|
||||
|
||||
const baseUrl = await this.clipperServerBaseUrl();
|
||||
@@ -278,7 +286,18 @@ class Bridge {
|
||||
|
||||
if (body) fetchOptions.body = typeof body === 'string' ? body : JSON.stringify(body);
|
||||
|
||||
const response = await fetch(baseUrl + "/" + path, fetchOptions)
|
||||
let queryString = '';
|
||||
if (query) {
|
||||
const s = [];
|
||||
for (const k in query) {
|
||||
if (!query.hasOwnProperty(k)) continue;
|
||||
s.push(encodeURIComponent(k) + '=' + encodeURIComponent(query[k]));
|
||||
}
|
||||
queryString = s.join('&');
|
||||
if (queryString) queryString = '?' + queryString;
|
||||
}
|
||||
|
||||
const response = await fetch(baseUrl + "/" + path + queryString, fetchOptions)
|
||||
if (!response.ok) {
|
||||
const msg = await response.text();
|
||||
throw new Error(msg);
|
||||
@@ -296,11 +315,39 @@ class Bridge {
|
||||
|
||||
if (!content) throw new Error('Cannot send empty content');
|
||||
|
||||
await this.clipperApiExec('POST', 'notes', content);
|
||||
// There is a bug in Chrome that somehow makes the app send the same request twice, which
|
||||
// results in Joplin having the same note twice. There's a 2-3 sec delay between
|
||||
// each request. The bug only happens the first time the extension popup is open and the
|
||||
// Complete button is clicked.
|
||||
//
|
||||
// It's beyond my understanding how it's happening. I don't know how this sendContentToJoplin function
|
||||
// can be called twice. But even if it is, logically, it's impossible that this
|
||||
// call below would be done with twice the same nounce. Even if the function sendContentToJoplin
|
||||
// is called twice in parallel, the increment is atomic and should result in two nounces
|
||||
// being generated. But it's not. Somehow the function below is called twice with the exact same nounce.
|
||||
//
|
||||
// It's also not something internal to Chrome that repeat the request since the error is caught
|
||||
// so it really seems like a double function call.
|
||||
//
|
||||
// So this is why below, when we get the duplicate nounce error, we just ignore it so as not to display
|
||||
// a useless error message. The whole nounce feature is not for security (it's not to prevent replay
|
||||
// attacks), but simply to detect these double-requests and ignore them on Joplin side.
|
||||
//
|
||||
// This nounce feature is optional, it's only active when the nounce query parameter is provided
|
||||
// so it shouldn't affect any other call.
|
||||
//
|
||||
// This is the perfect Heisenbug - it happens always when opening the popup the first time EXCEPT
|
||||
// when the debugger is open. Then everything is working fine and the bug NEVER EVER happens,
|
||||
// so it's impossible to understand what's going on.
|
||||
await this.clipperApiExec('POST', 'notes', { nounce: this.nounce_++ }, content);
|
||||
|
||||
this.dispatch({ type: 'CONTENT_UPLOAD', operation: { uploading: false, success: true } });
|
||||
} catch (error) {
|
||||
this.dispatch({ type: 'CONTENT_UPLOAD', operation: { uploading: false, success: false, errorMessage: error.message } });
|
||||
if (error.message === '{"error":"Duplicate Nounce"}') {
|
||||
this.dispatch({ type: 'CONTENT_UPLOAD', operation: { uploading: false, success: true } });
|
||||
} else {
|
||||
this.dispatch({ type: 'CONTENT_UPLOAD', operation: { uploading: false, success: false, errorMessage: error.message } });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -19,6 +19,7 @@ const defaultState = {
|
||||
tags: [],
|
||||
selectedFolderId: null,
|
||||
env: 'prod',
|
||||
isProbablyReaderable: true,
|
||||
};
|
||||
|
||||
const reduxMiddleware = store => next => async (action) => {
|
||||
@@ -40,6 +41,11 @@ function reducer(state = defaultState, action) {
|
||||
newState = Object.assign({}, state);
|
||||
newState.warning = action.text;
|
||||
|
||||
} else if (action.type === 'IS_PROBABLY_READERABLE') {
|
||||
|
||||
newState = Object.assign({}, state);
|
||||
newState.isProbablyReaderable = action.value;
|
||||
|
||||
} else if (action.type === 'CLIPPED_CONTENT_SET') {
|
||||
|
||||
newState = Object.assign({}, state);
|
||||
|
@@ -81,7 +81,7 @@ class ElectronAppWrapper {
|
||||
}))
|
||||
|
||||
// Uncomment this to view errors if the application does not start
|
||||
// if (this.env_ === 'dev') this.win_.webContents.openDevTools();
|
||||
if (this.env_ === 'dev') this.win_.webContents.openDevTools();
|
||||
|
||||
this.win_.on('close', (event) => {
|
||||
// If it's on macOS, the app is completely closed only if the user chooses to close the app (willQuitApp_ will be true)
|
||||
|
@@ -29,6 +29,8 @@ const { bridge } = require('electron').remote.require('./bridge');
|
||||
const Menu = bridge().Menu;
|
||||
const MenuItem = bridge().MenuItem;
|
||||
const PluginManager = require('lib/services/PluginManager');
|
||||
const RevisionService = require('lib/services/RevisionService');
|
||||
const MigrationService = require('lib/services/MigrationService');
|
||||
|
||||
const pluginClasses = [
|
||||
require('./plugins/GotoAnything.min'),
|
||||
@@ -48,6 +50,7 @@ const appDefaultState = Object.assign({}, defaultState, {
|
||||
windowContentSize: bridge().windowContentSize(),
|
||||
watchedNoteFiles: [],
|
||||
lastEditorScrollPercents: {},
|
||||
noteDevToolsVisible: false,
|
||||
});
|
||||
|
||||
class Application extends BaseApplication {
|
||||
@@ -185,6 +188,12 @@ class Application extends BaseApplication {
|
||||
newState.lastEditorScrollPercents = newPercents;
|
||||
break;
|
||||
|
||||
case 'NOTE_DEVTOOLS_TOGGLE':
|
||||
|
||||
newState = Object.assign({}, state);
|
||||
newState.noteDevToolsVisible = !newState.noteDevToolsVisible;
|
||||
break;
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
error.message = 'In reducer: ' + error.message + ' Action: ' + JSON.stringify(action);
|
||||
@@ -588,6 +597,11 @@ class Application extends BaseApplication {
|
||||
newNoteItem,
|
||||
newTodoItem,
|
||||
newNotebookItem, {
|
||||
label: _('Close Window'),
|
||||
platforms: ['darwin'],
|
||||
accelerator: 'Command+W',
|
||||
selector: 'performClose:',
|
||||
}, {
|
||||
type: 'separator',
|
||||
}, {
|
||||
label: _('Import'),
|
||||
@@ -780,6 +794,17 @@ class Application extends BaseApplication {
|
||||
label: _('Check for updates...'),
|
||||
visible: shim.isMac() ? false : true,
|
||||
click: () => _checkForUpdates(this)
|
||||
}, {
|
||||
type: 'separator',
|
||||
screens: ['Main'],
|
||||
}, {
|
||||
label: _('Toggle development tools'),
|
||||
visible: true,
|
||||
click: () => {
|
||||
this.dispatch({
|
||||
type: 'NOTE_DEVTOOLS_TOGGLE',
|
||||
});
|
||||
},
|
||||
}, {
|
||||
type: 'separator',
|
||||
visible: shim.isMac() ? false : true,
|
||||
@@ -799,6 +824,24 @@ class Application extends BaseApplication {
|
||||
rootMenus.file = rootMenuFile;
|
||||
}
|
||||
|
||||
// It seems the "visible" property of separators is ignored by Electron, making
|
||||
// it display separators that we want hidden. So this function iterates through
|
||||
// them and remove them completely.
|
||||
const cleanUpSeparators = items => {
|
||||
const output = [];
|
||||
for (const item of items) {
|
||||
if ('visible' in item && item.type === 'separator' && !item.visible) continue;
|
||||
output.push(item);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
for (const key in rootMenus) {
|
||||
if (!rootMenus.hasOwnProperty(key)) continue;
|
||||
if (!rootMenus[key].submenu) continue;
|
||||
rootMenus[key].submenu = cleanUpSeparators(rootMenus[key].submenu);
|
||||
}
|
||||
|
||||
const pluginMenuItems = PluginManager.instance().menuItems();
|
||||
for (const item of pluginMenuItems) {
|
||||
let itemParent = rootMenus[item.parent] ? rootMenus[item.parent] : 'tools';
|
||||
@@ -1026,6 +1069,12 @@ class Application extends BaseApplication {
|
||||
|
||||
ExternalEditWatcher.instance().setLogger(reg.logger());
|
||||
ExternalEditWatcher.instance().dispatch = this.store().dispatch;
|
||||
|
||||
RevisionService.instance().runInBackground();
|
||||
|
||||
// Make it available to the console window - useful to call revisionService.collectRevisions()
|
||||
window.revisionService = RevisionService.instance();
|
||||
window.migrationService = MigrationService.instance();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -77,13 +77,16 @@ class Bridge {
|
||||
});
|
||||
}
|
||||
|
||||
showConfirmMessageBox(message) {
|
||||
const result = this.showMessageBox_(this.window(), {
|
||||
showConfirmMessageBox(message, options = null) {
|
||||
if (options === null) options = {};
|
||||
|
||||
const result = this.showMessageBox_(this.window(), Object.assign({}, {
|
||||
type: 'question',
|
||||
message: message,
|
||||
cancelId: 1,
|
||||
buttons: [_('OK'), _('Cancel')],
|
||||
});
|
||||
}, options));
|
||||
|
||||
return result === 0;
|
||||
}
|
||||
|
||||
|
@@ -76,6 +76,26 @@ class ConfigScreenComponent extends React.Component {
|
||||
</div>
|
||||
);
|
||||
|
||||
if (section.name === 'sync') {
|
||||
const syncTargetMd = SyncTargetRegistry.idToMetadata(settings['sync.target']);
|
||||
|
||||
if (syncTargetMd.supportsConfigCheck) {
|
||||
const messages = shared.checkSyncConfigMessages(this);
|
||||
const statusStyle = Object.assign({}, theme.textStyle, { marginTop: 10 });
|
||||
const statusComp = !messages.length ? null : (
|
||||
<div style={statusStyle}>
|
||||
{messages[0]}
|
||||
{messages.length >= 1 ? (<p>{messages[1]}</p>) : null}
|
||||
</div>);
|
||||
|
||||
settingComps.push(
|
||||
<div key="check_sync_config_button" style={this.rowStyle_}>
|
||||
<button disabled={this.state.checkSyncConfigResult === 'checking'} style={theme.buttonStyle} onClick={this.checkSyncConfig_}>{_('Check synchronisation configuration')}</button>
|
||||
{ statusComp }
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={key} style={sectionStyle}>
|
||||
<h2 style={headerStyle}>{Setting.sectionNameToLabel(section.name)}</h2>
|
||||
@@ -265,9 +285,12 @@ class ConfigScreenComponent extends React.Component {
|
||||
updateSettingValue(key, event.target.value);
|
||||
};
|
||||
|
||||
const label = [md.label()];
|
||||
if (md.unitLabel) label.push('(' + md.unitLabel() + ')');
|
||||
|
||||
return (
|
||||
<div key={key} style={rowStyle}>
|
||||
<div style={labelStyle}><label>{md.label()}</label></div>
|
||||
<div style={labelStyle}><label>{label.join(' ')}</label></div>
|
||||
<input type="number" style={controlStyle} value={this.state.settings[key]} onChange={(event) => {onNumChange(event)}} min={md.minimum} max={md.maximum} step={md.step}/>
|
||||
{ descriptionComp }
|
||||
</div>
|
||||
@@ -320,24 +343,6 @@ class ConfigScreenComponent extends React.Component {
|
||||
|
||||
const settingComps = shared.settingsToComponents2(this, 'desktop', settings);
|
||||
|
||||
const syncTargetMd = SyncTargetRegistry.idToMetadata(settings['sync.target']);
|
||||
|
||||
if (syncTargetMd.supportsConfigCheck) {
|
||||
const messages = shared.checkSyncConfigMessages(this);
|
||||
const statusStyle = Object.assign({}, theme.textStyle, { marginTop: 10 });
|
||||
const statusComp = !messages.length ? null : (
|
||||
<div style={statusStyle}>
|
||||
{messages[0]}
|
||||
{messages.length >= 1 ? (<p>{messages[1]}</p>) : null}
|
||||
</div>);
|
||||
|
||||
settingComps.push(
|
||||
<div key="check_sync_config_button" style={this.rowStyle_}>
|
||||
<button disabled={this.state.checkSyncConfigResult === 'checking'} style={buttonStyle} onClick={this.checkSyncConfig_}>{_('Check synchronisation configuration')}</button>
|
||||
{ statusComp }
|
||||
</div>);
|
||||
}
|
||||
|
||||
const buttonBarStyle = {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
|
@@ -35,8 +35,7 @@ class HeaderComponent extends React.Component {
|
||||
};
|
||||
|
||||
this.search_onClear = (event) => {
|
||||
this.setState({ searchQuery: '' });
|
||||
triggerOnQuery('');
|
||||
this.resetSearch();
|
||||
if (this.searchElement_) this.searchElement_.focus();
|
||||
}
|
||||
|
||||
@@ -56,6 +55,17 @@ class HeaderComponent extends React.Component {
|
||||
this.setState({ showSearchUsageLink: false });
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
this.search_keyDown = event => {
|
||||
if (event.keyCode === 27) { // ESCAPE
|
||||
this.resetSearch();
|
||||
}
|
||||
}
|
||||
|
||||
this.resetSearch = () => {
|
||||
this.setState({ searchQuery: '' });
|
||||
triggerOnQuery('');
|
||||
}
|
||||
|
||||
this.searchUsageLink_click = event => {
|
||||
bridge().openExternal('https://joplinapp.org/#searching');
|
||||
@@ -68,6 +78,12 @@ class HeaderComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if(prevProps.notesParentType !== this.props.notesParentType && this.props.notesParentType !== 'Search' && this.state.searchQuery) {
|
||||
this.resetSearch();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.hideSearchUsageLinkIID_) {
|
||||
clearTimeout(this.hideSearchUsageLinkIID_);
|
||||
@@ -102,11 +118,14 @@ class HeaderComponent extends React.Component {
|
||||
let icon = null;
|
||||
if (options.iconName) {
|
||||
const iconStyle = {
|
||||
fontSize: Math.round(style.fontSize * 1.4),
|
||||
fontSize: Math.round(style.fontSize * 1.1),
|
||||
color: style.color,
|
||||
};
|
||||
if (options.title) iconStyle.marginRight = 5;
|
||||
if (options.iconRotation) iconStyle.transform = 'rotate(' + options.iconRotation + 'deg)';
|
||||
if("undefined" != typeof(options.iconRotation)) {
|
||||
iconStyle.transition = "transform 0.15s ease-in-out";
|
||||
iconStyle.transform = 'rotate(' + options.iconRotation + 'deg)';
|
||||
}
|
||||
icon = <i style={iconStyle} className={"fa " + options.iconName}></i>
|
||||
}
|
||||
|
||||
@@ -187,6 +206,7 @@ class HeaderComponent extends React.Component {
|
||||
ref={elem => this.searchElement_ = elem}
|
||||
onFocus={this.search_onFocus}
|
||||
onBlur={this.search_onBlur}
|
||||
onKeyDown={this.search_keyDown}
|
||||
/>
|
||||
<a
|
||||
href="#"
|
||||
@@ -254,6 +274,7 @@ const mapStateToProps = (state) => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
windowCommand: state.windowCommand,
|
||||
notesParentType: state.notesParentType,
|
||||
};
|
||||
};
|
||||
|
||||
|
39
ElectronClient/app/gui/HelpButton.jsx
Normal file
39
ElectronClient/app/gui/HelpButton.jsx
Normal file
@@ -0,0 +1,39 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const { themeStyle } = require('../theme.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
|
||||
class HelpButtonComponent extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.onClick = this.onClick.bind(this);
|
||||
}
|
||||
|
||||
onClick() {
|
||||
if (this.props.onClick) this.props.onClick();
|
||||
}
|
||||
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
let style = Object.assign({}, this.props.style, {color: theme.color, textDecoration: 'none'});
|
||||
const helpIconStyle = {flex:0, width: 16, height: 16, marginLeft: 10};
|
||||
const extraProps = {};
|
||||
if (this.props.tip) extraProps['data-tip'] = this.props.tip;
|
||||
return <a href="#" style={style} onClick={this.onClick} {...extraProps}><i style={helpIconStyle} className={"fa fa-question-circle"}></i></a>
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
};
|
||||
};
|
||||
|
||||
const HelpButton = connect(mapStateToProps)(HelpButtonComponent);
|
||||
|
||||
module.exports = HelpButton;
|
@@ -227,6 +227,7 @@ class MainScreenComponent extends React.Component {
|
||||
notePropertiesDialogOptions: {
|
||||
noteId: command.noteId,
|
||||
visible: true,
|
||||
onRevisionLinkClick: command.onRevisionLinkClick,
|
||||
},
|
||||
});
|
||||
} else if (command.name === 'toggleVisiblePanes') {
|
||||
@@ -447,7 +448,7 @@ class MainScreenComponent extends React.Component {
|
||||
if (this.props.hasDisabledSyncItems) {
|
||||
msg = <span>{_('Some items cannot be synchronised.')} <a href="#" onClick={() => { onViewDisabledItemsClick() }}>{_('View them now')}</a></span>
|
||||
} else if (this.props.showMissingMasterKeyMessage) {
|
||||
msg = <span>{_('Some items cannot be decrypted.')} <a href="#" onClick={() => { onViewMasterKeysClick() }}>{_('Set the password')}</a></span>
|
||||
msg = <span>{_('One or more master keys need a password.')} <a href="#" onClick={() => { onViewMasterKeysClick() }}>{_('Set the password')}</a></span>
|
||||
}
|
||||
|
||||
messageComp = (
|
||||
@@ -474,6 +475,7 @@ class MainScreenComponent extends React.Component {
|
||||
theme={this.props.theme}
|
||||
noteId={notePropertiesDialogOptions.noteId}
|
||||
onClose={this.notePropertiesDialog_close}
|
||||
onRevisionLinkClick={notePropertiesDialogOptions.onRevisionLinkClick}
|
||||
/> }
|
||||
|
||||
<PromptDialog
|
||||
@@ -494,7 +496,7 @@ class MainScreenComponent extends React.Component {
|
||||
<VerticalResizer style={styles.verticalResizer} onDrag={this.sidebar_onDrag}/>
|
||||
<NoteList style={styles.noteList} />
|
||||
<VerticalResizer style={styles.verticalResizer} onDrag={this.noteList_onDrag}/>
|
||||
<NoteText style={styles.noteText} visiblePanes={this.props.noteVisiblePanes} />
|
||||
<NoteText style={styles.noteText} visiblePanes={this.props.noteVisiblePanes} noteDevToolsVisible={this.props.noteDevToolsVisible}/>
|
||||
|
||||
{pluginDialog}
|
||||
</div>
|
||||
@@ -519,6 +521,7 @@ const mapStateToProps = (state) => {
|
||||
noteListWidth: state.settings['style.noteList.width'],
|
||||
selectedNoteId: state.selectedNoteIds.length === 1 ? state.selectedNoteIds[0] : null,
|
||||
plugins: state.plugins,
|
||||
noteDevToolsVisible: state.noteDevToolsVisible,
|
||||
};
|
||||
};
|
||||
|
||||
|
@@ -17,6 +17,7 @@ class NotePropertiesDialog extends React.Component {
|
||||
this.okButton_click = this.okButton_click.bind(this);
|
||||
this.cancelButton_click = this.cancelButton_click.bind(this);
|
||||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
this.revisionsLink_click = this.revisionsLink_click.bind(this);
|
||||
this.okButton = React.createRef();
|
||||
|
||||
this.state = {
|
||||
@@ -31,6 +32,7 @@ class NotePropertiesDialog extends React.Component {
|
||||
user_updated_time: _('Updated'),
|
||||
location: _('Location'),
|
||||
source_url: _('URL'),
|
||||
revisionsLink: _('Note History'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -79,6 +81,7 @@ class NotePropertiesDialog extends React.Component {
|
||||
formNote.location = note.latitude + ', ' + note.longitude;
|
||||
}
|
||||
|
||||
formNote.revisionsLink = note.id;
|
||||
formNote.id = note.id;
|
||||
|
||||
return formNote;
|
||||
@@ -102,26 +105,6 @@ class NotePropertiesDialog extends React.Component {
|
||||
this.styles_ = {};
|
||||
this.styleKey_ = styleKey;
|
||||
|
||||
// this.styles_.modalLayer = {
|
||||
// zIndex: 9999,
|
||||
// display: 'flex',
|
||||
// position: 'absolute',
|
||||
// top: 0,
|
||||
// left: 0,
|
||||
// width: '100%',
|
||||
// height: '100%',
|
||||
// backgroundColor: 'rgba(0,0,0,0.6)',
|
||||
// alignItems: 'flex-start',
|
||||
// justifyContent: 'center',
|
||||
// };
|
||||
|
||||
// this.styles_.dialogBox = {
|
||||
// backgroundColor: theme.backgroundColor,
|
||||
// padding: 16,
|
||||
// boxShadow: '6px 6px 20px rgba(0,0,0,0.5)',
|
||||
// marginTop: 20,
|
||||
// }
|
||||
|
||||
this.styles_.controlBox = {
|
||||
marginBottom: '1em',
|
||||
color: 'black', //This will apply for the calendar
|
||||
@@ -153,8 +136,6 @@ class NotePropertiesDialog extends React.Component {
|
||||
borderColor: theme.dividerColor,
|
||||
};
|
||||
|
||||
// this.styles_.dialogTitle = Object.assign({}, theme.h1Style, { marginBottom: '1.2em' });
|
||||
|
||||
return this.styles_;
|
||||
}
|
||||
|
||||
@@ -181,6 +162,11 @@ class NotePropertiesDialog extends React.Component {
|
||||
this.closeDialog(false);
|
||||
}
|
||||
|
||||
revisionsLink_click() {
|
||||
this.closeDialog(false);
|
||||
if (this.props.onRevisionLinkClick) this.props.onRevisionLinkClick();
|
||||
}
|
||||
|
||||
onKeyDown(event) {
|
||||
if (event.keyCode === 13) {
|
||||
this.closeDialog(true);
|
||||
@@ -300,11 +286,13 @@ class NotePropertiesDialog extends React.Component {
|
||||
url = Note.geoLocationUrlFromLatLong(ll.latitude, ll.longitude);
|
||||
}
|
||||
controlComp = <a href="#" onClick={() => bridge().openExternal(url)} style={theme.urlStyle}>{displayedValue}</a>
|
||||
} else if (key === 'revisionsLink') {
|
||||
controlComp = <a href="#" onClick={this.revisionsLink_click} style={theme.urlStyle}>{_('Previous versions of this note')}</a>
|
||||
} else {
|
||||
controlComp = <div style={Object.assign({}, theme.textStyle, {display: 'inline-block'})}>{displayedValue}</div>
|
||||
}
|
||||
|
||||
if (key !== 'id') {
|
||||
if (key !== 'id' && key !== 'revisionsLink') {
|
||||
editCompHandler = () => {this.editPropertyButtonClick(key, value)};
|
||||
editCompIcon = 'fa-edit';
|
||||
}
|
||||
|
177
ElectronClient/app/gui/NoteRevisionViewer.jsx
Normal file
177
ElectronClient/app/gui/NoteRevisionViewer.jsx
Normal file
@@ -0,0 +1,177 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const { themeStyle } = require('../theme.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const NoteTextViewer = require('./NoteTextViewer.min');
|
||||
const HelpButton = require('./HelpButton.min');
|
||||
const BaseModel = require('lib/BaseModel');
|
||||
const Revision = require('lib/models/Revision');
|
||||
const Setting = require('lib/models/Setting');
|
||||
const RevisionService = require('lib/services/RevisionService');
|
||||
const shared = require('lib/components/shared/note-screen-shared.js');
|
||||
const MdToHtml = require('lib/MdToHtml');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const ReactTooltip = require('react-tooltip');
|
||||
const { substrWithEllipsis } = require('lib/string-utils');
|
||||
|
||||
class NoteRevisionViewerComponent extends React.PureComponent {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
revisions: [],
|
||||
currentRevId: '',
|
||||
note: null,
|
||||
restoring: false,
|
||||
};
|
||||
|
||||
this.viewerRef_ = React.createRef();
|
||||
|
||||
this.viewer_domReady = this.viewer_domReady.bind(this);
|
||||
this.revisionList_onChange = this.revisionList_onChange.bind(this);
|
||||
this.importButton_onClick = this.importButton_onClick.bind(this);
|
||||
this.backButton_click = this.backButton_click.bind(this);
|
||||
}
|
||||
|
||||
style() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
|
||||
let style = {
|
||||
root: {
|
||||
backgroundColor: theme.backgroundColor,
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
},
|
||||
titleInput: Object.assign({}, theme.inputStyle, { flex: 1 }),
|
||||
revisionList: Object.assign({}, theme.dropdownList, { marginLeft: 10, flex: 0.5 }),
|
||||
};
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
async viewer_domReady() {
|
||||
// this.viewerRef_.current.wrappedInstance.openDevTools();
|
||||
|
||||
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, this.props.noteId);
|
||||
|
||||
this.setState({
|
||||
revisions: revisions,
|
||||
currentRevId: revisions.length ? revisions[revisions.length - 1].id : '',
|
||||
}, () => {
|
||||
this.reloadNote();
|
||||
});
|
||||
}
|
||||
|
||||
async importButton_onClick() {
|
||||
if (!this.state.note) return;
|
||||
this.setState({ restoring: true });
|
||||
await RevisionService.instance().importRevisionNote(this.state.note);
|
||||
this.setState({ restoring: false });
|
||||
alert(_('The note "%s" has been successfully restored to the notebook "%s".', substrWithEllipsis(this.state.note.title, 0, 32), RevisionService.instance().restoreFolderTitle()));
|
||||
}
|
||||
|
||||
backButton_click() {
|
||||
if (this.props.onBack) this.props.onBack();
|
||||
}
|
||||
|
||||
revisionList_onChange(event) {
|
||||
const value = event.target.value;
|
||||
|
||||
if (!value) {
|
||||
if (this.props.onBack) this.props.onBack();
|
||||
} else {
|
||||
this.setState({
|
||||
currentRevId: value,
|
||||
}, () => {
|
||||
this.reloadNote();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async reloadNote() {
|
||||
let noteBody = '';
|
||||
if (!this.state.revisions.length || !this.state.currentRevId) {
|
||||
noteBody = _('This note has no history');
|
||||
this.setState({ note: null });
|
||||
} else {
|
||||
const revIndex = BaseModel.modelIndexById(this.state.revisions, this.state.currentRevId);
|
||||
const note = await RevisionService.instance().revisionNote(this.state.revisions, revIndex);
|
||||
if (!note) return;
|
||||
noteBody = note.body;
|
||||
this.setState({ note: note });
|
||||
}
|
||||
|
||||
const theme = themeStyle(this.props.theme);
|
||||
|
||||
const mdToHtml = new MdToHtml({
|
||||
resourceBaseUrl: 'file://' + Setting.value('resourceDir') + '/',
|
||||
});
|
||||
|
||||
const result = mdToHtml.render(noteBody, theme, {
|
||||
codeTheme: theme.codeThemeCss,
|
||||
userCss: this.props.customCss ? this.props.customCss : '',
|
||||
resources: await shared.attachedResources(noteBody),
|
||||
});
|
||||
|
||||
this.viewerRef_.current.wrappedInstance.send('setHtml', result.html, { cssFiles: result.cssFiles });
|
||||
}
|
||||
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const style = this.style();
|
||||
|
||||
const revisionListItems = [];
|
||||
const revs = this.state.revisions.slice().reverse();
|
||||
for (let i = 0; i < revs.length; i++) {
|
||||
const rev = revs[i];
|
||||
const stats = Revision.revisionPatchStatsText(rev);
|
||||
|
||||
revisionListItems.push(<option
|
||||
key={rev.id}
|
||||
value={rev.id}
|
||||
>{time.formatMsToLocal(rev.item_updated_time) + ' (' + stats + ')'}</option>);
|
||||
}
|
||||
|
||||
const restoreButtonTitle = _('Restore');
|
||||
const helpMessage = _('Click "%s" to restore the note. It will be copied in the notebook named "%s". The current version of the note will not be replaced or modified.', restoreButtonTitle, RevisionService.instance().restoreFolderTitle());
|
||||
|
||||
const titleInput = (
|
||||
<div style={{display:'flex', flexDirection: 'row', alignItems:'center', marginBottom: 10, borderWidth: 1, borderBottomStyle: 'solid', borderColor: theme.dividerColor, paddingBottom:10}}>
|
||||
<button onClick={this.backButton_click} style={Object.assign({}, theme.buttonStyle, { marginRight: 10, height: theme.inputStyle.height })}>{'⬅ ' + _('Back')}</button>
|
||||
<input readOnly type="text" style={style.titleInput} value={this.state.note ? this.state.note.title : ''}/>
|
||||
<select disabled={!this.state.revisions.length} value={this.state.currentRevId} style={style.revisionList} onChange={this.revisionList_onChange}>
|
||||
{revisionListItems}
|
||||
</select>
|
||||
<button disabled={!this.state.revisions.length || this.state.restoring} onClick={this.importButton_onClick} style={Object.assign({}, theme.buttonStyle, { marginLeft: 10, height: theme.inputStyle.height })}>{restoreButtonTitle}</button>
|
||||
<HelpButton tip={helpMessage} id="noteRevisionHelpButton" onClick={this.helpButton_onClick}/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const viewer = <NoteTextViewer
|
||||
viewerStyle={{display:'flex', flex:1}}
|
||||
ref={this.viewerRef_}
|
||||
onDomReady={this.viewer_domReady}
|
||||
/>
|
||||
|
||||
return (
|
||||
<div style={style.root}>
|
||||
{titleInput}
|
||||
{viewer}
|
||||
<ReactTooltip place="bottom" delayShow={300} className="help-tooltip"/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
};
|
||||
};
|
||||
|
||||
const NoteRevisionViewer = connect(mapStateToProps)(NoteRevisionViewerComponent);
|
||||
|
||||
module.exports = NoteRevisionViewer;
|
@@ -36,8 +36,10 @@ const ResourceFetcher = require('lib/services/ResourceFetcher');
|
||||
const { toSystemSlashes, safeFilename } = require('lib/path-utils');
|
||||
const { clipboard } = require('electron');
|
||||
const SearchEngine = require('lib/services/SearchEngine');
|
||||
const DecryptionWorker = require('lib/services/DecryptionWorker');
|
||||
const ModelCache = require('lib/services/ModelCache');
|
||||
const NoteTextViewer = require('./NoteTextViewer.min');
|
||||
const NoteRevisionViewer = require('./NoteRevisionViewer.min');
|
||||
|
||||
require('brace/mode/markdown');
|
||||
// https://ace.c9.io/build/kitchen-sink.html
|
||||
@@ -70,6 +72,7 @@ class NoteTextComponent extends React.Component {
|
||||
editorScrollTop: 0,
|
||||
newNote: null,
|
||||
noteTags: [],
|
||||
showRevisions: false,
|
||||
|
||||
// If the current note was just created, and the title has never been
|
||||
// changed by the user, this variable contains that note ID. Used
|
||||
@@ -224,14 +227,13 @@ class NoteTextComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
this.resourceFetcher_downloadComplete = async (resource) => {
|
||||
this.refreshResource = async (event) => {
|
||||
if (!this.state.note || !this.state.note.body) return;
|
||||
const resourceIds = await Note.linkedResourceIds(this.state.note.body);
|
||||
if (resourceIds.indexOf(resource.id) >= 0) {
|
||||
// this.mdToHtml().clearCache();
|
||||
if (resourceIds.indexOf(event.id) >= 0) {
|
||||
shared.clearResourceCache();
|
||||
this.lastSetHtml_ = '';
|
||||
this.scheduleHtmlUpdate();
|
||||
//this.updateHtml(this.state.note.body);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,6 +270,7 @@ class NoteTextComponent extends React.Component {
|
||||
this.titleField_keyDown = this.titleField_keyDown.bind(this);
|
||||
this.webview_ipcMessage = this.webview_ipcMessage.bind(this);
|
||||
this.webview_domReady = this.webview_domReady.bind(this);
|
||||
this.noteRevisionViewer_onBack = this.noteRevisionViewer_onBack.bind(this);
|
||||
}
|
||||
|
||||
// Note:
|
||||
@@ -360,7 +363,8 @@ class NoteTextComponent extends React.Component {
|
||||
eventManager.on('noteTypeToggle', this.onNoteTypeToggle_);
|
||||
eventManager.on('todoToggle', this.onTodoToggle_);
|
||||
|
||||
ResourceFetcher.instance().on('downloadComplete', this.resourceFetcher_downloadComplete);
|
||||
shared.installResourceHandling(this.refreshResource);
|
||||
|
||||
ExternalEditWatcher.instance().on('noteChange', this.externalEditWatcher_noteChange);
|
||||
}
|
||||
|
||||
@@ -373,10 +377,27 @@ class NoteTextComponent extends React.Component {
|
||||
eventManager.removeListener('noteTypeToggle', this.onNoteTypeToggle_);
|
||||
eventManager.removeListener('todoToggle', this.onTodoToggle_);
|
||||
|
||||
ResourceFetcher.instance().off('downloadComplete', this.resourceFetcher_downloadComplete);
|
||||
shared.uninstallResourceHandling(this.refreshResource);
|
||||
|
||||
ExternalEditWatcher.instance().off('noteChange', this.externalEditWatcher_noteChange);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.webviewRef() && this.props.noteDevToolsVisible !== this.webviewRef().isDevToolsOpened()) {
|
||||
if (this.props.noteDevToolsVisible) {
|
||||
this.webviewRef().openDevTools();
|
||||
} else {
|
||||
this.webviewRef().closeDevTools();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
webviewRef() {
|
||||
if (!this.webviewRef_.current || !this.webviewRef_.current.wrappedInstance) return null;
|
||||
if (!this.webviewRef_.current.wrappedInstance.domReady()) return null;
|
||||
return this.webviewRef_.current.wrappedInstance;
|
||||
}
|
||||
|
||||
async saveIfNeeded(saveIfNewNote = false) {
|
||||
const forceSave = saveIfNewNote && (this.state.note && !this.state.note.id);
|
||||
|
||||
@@ -471,6 +492,8 @@ class NoteTextComponent extends React.Component {
|
||||
|
||||
// Scroll back to top when loading new note
|
||||
if (loadingNewNote) {
|
||||
shared.clearResourceCache();
|
||||
|
||||
this.editorMaxScrollTop_ = 0;
|
||||
|
||||
// HACK: To go around a bug in Ace editor, we first set the scroll position to 1
|
||||
@@ -501,7 +524,7 @@ class NoteTextComponent extends React.Component {
|
||||
// 2. It resets the undo manager - fixes https://github.com/laurent22/joplin/issues/355
|
||||
// Note: calling undoManager.reset() doesn't work
|
||||
try {
|
||||
this.editor_.editor.getSession().setValue(note ? note.body : '');
|
||||
this.editor_.editor.getSession().setValue(note && note.body? note.body : '');
|
||||
} catch (error) {
|
||||
if (error.message === "Cannot read property 'match' of undefined") {
|
||||
// The internals of Ace Editor throws an exception when creating a new note,
|
||||
@@ -518,6 +541,11 @@ class NoteTextComponent extends React.Component {
|
||||
this.setViewerPercentScroll(scrollPercent ? scrollPercent : 0);
|
||||
}, 10);
|
||||
}
|
||||
|
||||
if (note && note.body && Setting.value('sync.resourceDownloadMode') === 'auto') {
|
||||
const resourceIds = await Note.linkedResourceIds(note.body);
|
||||
await ResourceFetcher.instance().markForDownload(resourceIds);
|
||||
}
|
||||
}
|
||||
|
||||
if (note) {
|
||||
@@ -530,7 +558,8 @@ class NoteTextComponent extends React.Component {
|
||||
webviewReady: webviewReady,
|
||||
folder: parentFolder,
|
||||
lastKeys: [],
|
||||
noteTags: noteTags
|
||||
noteTags: noteTags,
|
||||
showRevisions: false,
|
||||
};
|
||||
|
||||
if (!note) {
|
||||
@@ -619,6 +648,13 @@ class NoteTextComponent extends React.Component {
|
||||
return shared.refreshNoteMetadata(this, force);
|
||||
}
|
||||
|
||||
async noteRevisionViewer_onBack() {
|
||||
this.setState({ showRevisions: false });
|
||||
|
||||
this.lastSetHtml_ = '';
|
||||
this.scheduleReloadNote(this.props);
|
||||
}
|
||||
|
||||
title_changeText(event) {
|
||||
shared.noteComponent_change(this, 'title', event.target.value);
|
||||
this.setState({ newAndNoTitleChangeNoteId: null });
|
||||
@@ -636,7 +672,7 @@ class NoteTextComponent extends React.Component {
|
||||
|
||||
async webview_ipcMessage(event) {
|
||||
const msg = event.channel ? event.channel : '';
|
||||
const args = event.args;
|
||||
const args = event.args;
|
||||
const arg0 = args && args.length >= 1 ? args[0] : null;
|
||||
const arg1 = args && args.length >= 2 ? args[1] : null;
|
||||
|
||||
@@ -651,10 +687,18 @@ class NoteTextComponent extends React.Component {
|
||||
|
||||
const newBody = shared.toggleCheckbox(msg, this.state.note.body);
|
||||
this.saveOneProperty('body', newBody);
|
||||
} else if (msg.indexOf('error:') === 0) {
|
||||
const s = msg.split(':');
|
||||
s.splice(0, 1);
|
||||
reg.logger().error(s.join(':'));
|
||||
} else if (msg === 'setMarkerCount') {
|
||||
const ls = Object.assign({}, this.state.localSearch);
|
||||
ls.resultCount = arg0;
|
||||
this.setState({ localSearch: ls });
|
||||
} else if (msg.indexOf('markForDownload:') === 0) {
|
||||
const s = msg.split(':');
|
||||
if (s.length < 2) throw new Error('Invalid message: ' + msg);
|
||||
ResourceFetcher.instance().markForDownload(s[1]);
|
||||
} else if (msg === 'percentScroll') {
|
||||
this.ignoreNextEditorScroll_ = true;
|
||||
this.setEditorPercentScroll(arg0);
|
||||
@@ -729,7 +773,6 @@ class NoteTextComponent extends React.Component {
|
||||
// When using the file:// protocol, openExternal doesn't work (does nothing) with URL-encoded paths
|
||||
require('electron').shell.openExternal(urlDecode(msg));
|
||||
} else {
|
||||
console.info('OPEN URL');
|
||||
require('electron').shell.openExternal(msg);
|
||||
}
|
||||
} else if (msg.indexOf('#') === 0) {
|
||||
@@ -853,6 +896,9 @@ class NoteTextComponent extends React.Component {
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
//fixes #1426 but this is an Ace issue, so it can be removed if ace/brace is updated.
|
||||
this.editor_.editor.getSession().getMode().$quotes = {'"': '"', "'": "'", "`": "`"};
|
||||
|
||||
// Disable Markdown auto-completion (eg. auto-adding a dash after a line with a dash.
|
||||
// https://github.com/ajaxorg/ace/issues/2754
|
||||
@@ -1529,6 +1575,7 @@ class NoteTextComponent extends React.Component {
|
||||
type: 'WINDOW_COMMAND',
|
||||
name: 'commandNoteProperties',
|
||||
noteId: n.id,
|
||||
onRevisionLinkClick: () => { this.setState({ showRevisions: true}) },
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1610,6 +1657,18 @@ class NoteTextComponent extends React.Component {
|
||||
|
||||
const innerWidth = rootStyle.width - rootStyle.paddingLeft - rootStyle.paddingRight - borderWidth;
|
||||
|
||||
if (this.state.showRevisions && note && note.id) {
|
||||
rootStyle.paddingRight = rootStyle.paddingLeft;
|
||||
rootStyle.paddingTop = rootStyle.paddingLeft;
|
||||
rootStyle.paddingBottom = rootStyle.paddingLeft;
|
||||
rootStyle.display = 'inline-flex';
|
||||
return (
|
||||
<div style={rootStyle}>
|
||||
<NoteRevisionViewer noteId={note.id} customCss={this.props.customCss} onBack={this.noteRevisionViewer_onBack}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.selectedNoteIds.length > 1) {
|
||||
return this.renderMultiNotes(rootStyle);
|
||||
} else if (!note || !!note.encryption_applied) { //|| (note && !this.props.newNote && this.props.noteId && note.id !== this.props.noteId)) { // note.id !== props.noteId is when the note has not been loaded yet, and the previous one is still in the state
|
||||
@@ -1710,13 +1769,14 @@ class NoteTextComponent extends React.Component {
|
||||
viewerStyle.borderLeft = 'none';
|
||||
}
|
||||
|
||||
if (this.state.webviewReady) {
|
||||
if (this.state.webviewReady && this.webviewRef_.current) {
|
||||
let html = this.state.bodyHtml;
|
||||
|
||||
const htmlHasChanged = this.lastSetHtml_ !== html;
|
||||
if (htmlHasChanged) {
|
||||
let options = {
|
||||
cssFiles: this.state.lastRenderCssFiles,
|
||||
downloadResources: Setting.value('sync.resourceDownloadMode'),
|
||||
};
|
||||
this.webviewRef_.current.wrappedInstance.send('setHtml', html, options);
|
||||
this.lastSetHtml_ = html;
|
||||
|
@@ -9,6 +9,7 @@ class NoteTextViewerComponent extends React.Component {
|
||||
super();
|
||||
|
||||
this.initialized_ = false;
|
||||
this.domReady_ = false;
|
||||
|
||||
this.webviewRef_ = React.createRef();
|
||||
this.webviewListeners_ = null;
|
||||
@@ -18,11 +19,16 @@ class NoteTextViewerComponent extends React.Component {
|
||||
}
|
||||
|
||||
webview_domReady(event) {
|
||||
this.props.onDomReady(event);
|
||||
this.domReady_ = true;
|
||||
if (this.props.onDomReady) this.props.onDomReady(event);
|
||||
}
|
||||
|
||||
webview_ipcMessage(event) {
|
||||
this.props.onIpcMessage(event);
|
||||
if (this.props.onIpcMessage) this.props.onIpcMessage(event);
|
||||
}
|
||||
|
||||
domReady() {
|
||||
return this.domReady_;
|
||||
}
|
||||
|
||||
initWebview() {
|
||||
@@ -65,15 +71,26 @@ class NoteTextViewerComponent extends React.Component {
|
||||
const fn = this.webviewListeners_[n];
|
||||
wv.removeEventListener(n, fn);
|
||||
}
|
||||
|
||||
this.initialized_ = false;
|
||||
this.domReady_ = false;
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
tryInit() {
|
||||
if (!this.initialized_ && this.webviewRef_.current) {
|
||||
this.initWebview();
|
||||
this.initialized_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.tryInit();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.tryInit();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.destroyWebview();
|
||||
}
|
||||
@@ -108,6 +125,14 @@ class NoteTextViewerComponent extends React.Component {
|
||||
return this.webviewRef_.current.openDevTools();
|
||||
}
|
||||
|
||||
closeDevTools() {
|
||||
return this.webviewRef_.current.closeDevTools();
|
||||
}
|
||||
|
||||
isDevToolsOpened() {
|
||||
return this.webviewRef_.current.isDevToolsOpened();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Wrap WebView functions (END)
|
||||
// ----------------------------------------------------------------
|
||||
|
@@ -152,7 +152,7 @@ class SideBarComponent extends React.Component {
|
||||
header: {
|
||||
height: itemHeight * 1.8,
|
||||
fontFamily: theme.fontFamily,
|
||||
fontSize: theme.fontSize * 1.3,
|
||||
fontSize: theme.fontSize * 1.16,
|
||||
textDecoration: "none",
|
||||
boxSizing: "border-box",
|
||||
color: theme.color2,
|
||||
@@ -221,7 +221,7 @@ class SideBarComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
} else if (command.name === 'synchronize') {
|
||||
this.sync_click();
|
||||
if (!this.props.syncStarted) this.sync_click();
|
||||
} else {
|
||||
commandProcessed = false;
|
||||
}
|
||||
@@ -509,7 +509,7 @@ class SideBarComponent extends React.Component {
|
||||
|
||||
makeHeader(key, label, iconName, extraProps = {}) {
|
||||
const style = this.style().header;
|
||||
const icon = <i style={{ fontSize: style.fontSize * 1.2, marginRight: 5 }} className={"fa " + iconName} />;
|
||||
const icon = <i style={{ fontSize: style.fontSize, marginRight: 5 }} className={"fa " + iconName} />;
|
||||
|
||||
if (extraProps.toggleblock || extraProps.onClick) {
|
||||
style.cursor = "pointer";
|
||||
@@ -642,9 +642,15 @@ class SideBarComponent extends React.Component {
|
||||
|
||||
synchronizeButton(type) {
|
||||
const style = Object.assign({}, this.style().button, { marginBottom: 5 });
|
||||
const iconName = type === "sync" ? "fa-refresh" : "fa-times";
|
||||
const iconName = "fa-refresh";
|
||||
const label = type === "sync" ? _("Synchronise") : _("Cancel");
|
||||
const icon = <i style={{ fontSize: style.fontSize, marginRight: 5 }} className={"fa " + iconName} />;
|
||||
let iconStyle = { fontSize: style.fontSize, marginRight: 5 };
|
||||
|
||||
if(type !== 'sync'){
|
||||
iconStyle.animation = 'icon-infinite-rotation 1s linear infinite';
|
||||
}
|
||||
|
||||
const icon = <i style={iconStyle} className={"fa " + iconName} />;
|
||||
return (
|
||||
<a
|
||||
className="synchronize-button"
|
||||
@@ -708,7 +714,7 @@ class SideBarComponent extends React.Component {
|
||||
|
||||
let resourceFetcherText = '';
|
||||
if (this.props.resourceFetcher && this.props.resourceFetcher.toFetchCount) {
|
||||
resourceFetcherText = _('Fetching resources: %d', this.props.resourceFetcher.toFetchCount);
|
||||
resourceFetcherText = _('Fetching resources: %d/%d', this.props.resourceFetcher.fetchingCount, this.props.resourceFetcher.toFetchCount);
|
||||
}
|
||||
|
||||
let lines = Synchronizer.reportToLines(this.props.syncReport);
|
||||
@@ -738,8 +744,8 @@ class SideBarComponent extends React.Component {
|
||||
{items}
|
||||
</div>
|
||||
<div style={{flex:0}}>
|
||||
{syncReportComp}
|
||||
{syncButton}
|
||||
{syncReportComp}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@@ -38,277 +38,286 @@
|
||||
<script src="./lib.js"></script>
|
||||
|
||||
<script>
|
||||
const contentElement = document.getElementById('content');
|
||||
|
||||
const ipc = {};
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
// Here we only deal with messages that are sent from the main Electro process to the webview.
|
||||
if (!event.data || event.data.target !== 'webview') return;
|
||||
|
||||
const callName = event.data.name;
|
||||
const callData = event.data.data;
|
||||
|
||||
if (!ipc[callName]) {
|
||||
console.warn('Missing IPC function:', event.data);
|
||||
} else {
|
||||
ipc[callName](callData);
|
||||
}
|
||||
});
|
||||
|
||||
const loadedCssFiles_ = {};
|
||||
function loadCssFiles(cssFiles) {
|
||||
for (let i = 0; i < cssFiles.length; i++) {
|
||||
const f = cssFiles[i];
|
||||
if (loadedCssFiles_[f]) continue;
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = f;
|
||||
document.getElementById('styleContainer').appendChild(link);
|
||||
}
|
||||
}
|
||||
|
||||
// Note: the scroll position source of truth is "percentScroll_". This is easier to manage than scrollTop because
|
||||
// the scrollTop value depends on the images being loaded or not. For example, if the scrollTop is saved while
|
||||
// images are being displayed then restored while images are being reloaded, the new scrollTop might be changed
|
||||
// so that it is not greater than contentHeight. On the other hand, with percentScroll it is possible to restore
|
||||
// it at any time knowing that it's not going to be changed because the content height has changed.
|
||||
// To restore percentScroll the "checkScrollIID" interval is used. It constantly resets the scroll position during
|
||||
// one second after the content has been updated.
|
||||
//
|
||||
// ignoreNextScroll is used to differentiate between scroll event from the users and those that are the result
|
||||
// of programmatically changing scrollTop. We only want to respond to events initiated by the user.
|
||||
|
||||
let percentScroll_ = 0;
|
||||
let checkScrollIID_ = null;
|
||||
|
||||
function setPercentScroll(percent) {
|
||||
percentScroll_ = percent;
|
||||
contentElement.scrollTop = percentScroll_ * maxScrollTop();
|
||||
}
|
||||
|
||||
function percentScroll() {
|
||||
return percentScroll_;
|
||||
}
|
||||
|
||||
function restorePercentScroll() {
|
||||
setPercentScroll(percentScroll_);
|
||||
}
|
||||
|
||||
ipc.setHtml = (event) => {
|
||||
const html = event.html;
|
||||
|
||||
markJsHackMarkerInserted_ = false;
|
||||
|
||||
updateBodyHeight();
|
||||
|
||||
contentElement.innerHTML = html;
|
||||
|
||||
let previousContentHeight = contentElement.scrollHeight;
|
||||
let startTime = Date.now();
|
||||
restorePercentScroll();
|
||||
|
||||
if (!checkScrollIID_) {
|
||||
checkScrollIID_ = setInterval(() => {
|
||||
const h = contentElement.scrollHeight;
|
||||
if (h !== previousContentHeight) {
|
||||
previousContentHeight = h;
|
||||
restorePercentScroll();
|
||||
}
|
||||
if (Date.now() - startTime >= 1000) {
|
||||
clearInterval(checkScrollIID_);
|
||||
checkScrollIID_ = null;
|
||||
}
|
||||
}, 1);
|
||||
}
|
||||
|
||||
loadCssFiles(event.options.cssFiles);
|
||||
}
|
||||
|
||||
let ignoreNextScrollEvent = false;
|
||||
ipc.setPercentScroll = (event) => {
|
||||
const percent = event.percent;
|
||||
|
||||
if (checkScrollIID_) {
|
||||
clearInterval(checkScrollIID_);
|
||||
checkScrollIID_ = null;
|
||||
}
|
||||
|
||||
ignoreNextScrollEvent = true;
|
||||
setPercentScroll(percent);
|
||||
}
|
||||
|
||||
// HACK for Mark.js bug - https://github.com/julmot/mark.js/issues/127
|
||||
let markJsHackMarkerInserted_ = false;
|
||||
function addMarkJsSpaceHack(document) {
|
||||
if (markJsHackMarkerInserted_) return;
|
||||
|
||||
const prepareElementsForMarkJs = (elements, type) => {
|
||||
// const markJsHackMarker_ = '​ ​'
|
||||
const markJsHackMarker_ = ' ';
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
if (!type) {
|
||||
elements[i].innerHTML = elements[i].innerHTML + markJsHackMarker_;
|
||||
} else if (type === 'insertBefore') {
|
||||
elements[i].insertAdjacentHTML('beforeBegin', markJsHackMarker_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prepareElementsForMarkJs(document.getElementsByTagName('p'));
|
||||
prepareElementsForMarkJs(document.getElementsByTagName('div'));
|
||||
prepareElementsForMarkJs(document.getElementsByTagName('br'), 'insertBefore');
|
||||
markJsHackMarkerInserted_ = true;
|
||||
}
|
||||
|
||||
let mark_ = null;
|
||||
let markSelectedElement_ = null;
|
||||
function setMarkers(keywords, options = null) {
|
||||
if (!options) options = {};
|
||||
|
||||
// TODO: Add support for scriptType on mobile and CLI
|
||||
|
||||
if (!mark_) {
|
||||
mark_ = new Mark(document.getElementById('content'), {
|
||||
exclude: ['img'],
|
||||
acrossElements: true,
|
||||
});
|
||||
}
|
||||
|
||||
addMarkJsSpaceHack(document);
|
||||
|
||||
mark_.unmark()
|
||||
|
||||
if (markSelectedElement_) markSelectedElement_.classList.remove('mark-selected');
|
||||
|
||||
let selectedElement = null;
|
||||
let elementIndex = 0;
|
||||
|
||||
const onEachElement = (element) => {
|
||||
if (!('selectedIndex' in options)) return;
|
||||
|
||||
if (('selectedIndex' in options) && elementIndex === options.selectedIndex) {
|
||||
markSelectedElement_ = element;
|
||||
element.classList.add('mark-selected');
|
||||
selectedElement = element;
|
||||
}
|
||||
|
||||
elementIndex++;
|
||||
}
|
||||
|
||||
for (let i = 0; i < keywords.length; i++) {
|
||||
let keyword = keywords[i];
|
||||
|
||||
markJsUtils.markKeyword(mark_, keyword, {
|
||||
pregQuote: pregQuote,
|
||||
replaceRegexDiacritics: replaceRegexDiacritics,
|
||||
}, {
|
||||
each: onEachElement,
|
||||
});
|
||||
}
|
||||
|
||||
ipcProxySendToHost('setMarkerCount', elementIndex);
|
||||
|
||||
if (selectedElement) selectedElement.scrollIntoView();
|
||||
}
|
||||
|
||||
let markLoaded_ = false;
|
||||
ipc.setMarkers = (event) => {
|
||||
const keywords = event.keywords;
|
||||
const options = event.options;
|
||||
|
||||
if (!keywords.length && !markLoaded_) return;
|
||||
|
||||
if (!markLoaded_) {
|
||||
const script = document.createElement('script');
|
||||
script.onload = function() {
|
||||
setMarkers(keywords, options);
|
||||
};
|
||||
|
||||
script.src = '../../node_modules/mark.js/dist/mark.min.js';
|
||||
document.getElementById('markScriptContainer').appendChild(script);
|
||||
markLoaded_ = true;
|
||||
} else {
|
||||
setMarkers(keywords, options);
|
||||
}
|
||||
}
|
||||
|
||||
function maxScrollTop() {
|
||||
return Math.max(0, contentElement.scrollHeight - contentElement.clientHeight);
|
||||
}
|
||||
|
||||
// The body element needs to have a fixed height for the content to be scrollable
|
||||
function updateBodyHeight() {
|
||||
document.getElementById('body').style.height = window.innerHeight + 'px';
|
||||
}
|
||||
|
||||
const ipcProxySendToHost = (methodName, arg) => {
|
||||
window.postMessage({ target: 'main', name: methodName, args: [ arg ] }, '*');
|
||||
}
|
||||
|
||||
contentElement.addEventListener('scroll', function(e) {
|
||||
if (ignoreNextScrollEvent) {
|
||||
ignoreNextScrollEvent = false;
|
||||
return;
|
||||
}
|
||||
const m = maxScrollTop();
|
||||
const percent = m ? contentElement.scrollTop / m : 0;
|
||||
setPercentScroll(percent);
|
||||
|
||||
ipcProxySendToHost('percentScroll', percent);
|
||||
});
|
||||
try {
|
||||
const contentElement = document.getElementById('content');
|
||||
|
||||
document.addEventListener('contextmenu', function(event) {
|
||||
let element = event.target;
|
||||
const ipc = {};
|
||||
|
||||
// To handle right clicks on resource icons
|
||||
if (element && !element.getAttribute('data-resource-id')) element = element.parentElement;
|
||||
window.addEventListener('message', webviewLib.logEnabledEventHandler(event => {
|
||||
// Here we only deal with messages that are sent from the main Electro process to the webview.
|
||||
if (!event.data || event.data.target !== 'webview') return;
|
||||
|
||||
if (element && element.getAttribute('data-resource-id')) {
|
||||
ipcProxySendToHost('contextMenu', {
|
||||
type: element.getAttribute('src') ? 'image' : 'resource',
|
||||
resourceId: element.getAttribute('data-resource-id'),
|
||||
});
|
||||
} else {
|
||||
const selectedText = window.getSelection().toString();
|
||||
const callName = event.data.name;
|
||||
const callData = event.data.data;
|
||||
|
||||
if (selectedText) {
|
||||
ipcProxySendToHost('contextMenu', {
|
||||
type: 'text',
|
||||
textToCopy: selectedText,
|
||||
});
|
||||
} else if (event.target.getAttribute('href')) {
|
||||
ipcProxySendToHost('contextMenu', {
|
||||
type: 'link',
|
||||
textToCopy: event.target.getAttribute('href'),
|
||||
});
|
||||
if (!ipc[callName]) {
|
||||
console.warn('Missing IPC function:', event.data);
|
||||
} else {
|
||||
ipc[callName](callData);
|
||||
}
|
||||
}));
|
||||
|
||||
const loadedCssFiles_ = {};
|
||||
function loadCssFiles(cssFiles) {
|
||||
for (let i = 0; i < cssFiles.length; i++) {
|
||||
const f = cssFiles[i];
|
||||
if (loadedCssFiles_[f]) continue;
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = f;
|
||||
document.getElementById('styleContainer').appendChild(link);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
webviewLib.initialize({
|
||||
postMessage: ipcProxySendToHost,
|
||||
});
|
||||
// Note: the scroll position source of truth is "percentScroll_". This is easier to manage than scrollTop because
|
||||
// the scrollTop value depends on the images being loaded or not. For example, if the scrollTop is saved while
|
||||
// images are being displayed then restored while images are being reloaded, the new scrollTop might be changed
|
||||
// so that it is not greater than contentHeight. On the other hand, with percentScroll it is possible to restore
|
||||
// it at any time knowing that it's not going to be changed because the content height has changed.
|
||||
// To restore percentScroll the "checkScrollIID" interval is used. It constantly resets the scroll position during
|
||||
// one second after the content has been updated.
|
||||
//
|
||||
// ignoreNextScroll is used to differentiate between scroll event from the users and those that are the result
|
||||
// of programmatically changing scrollTop. We only want to respond to events initiated by the user.
|
||||
|
||||
// Disable drag and drop otherwise it's possible to drop a URL
|
||||
// on it and it will open in the view as a website.
|
||||
document.addEventListener('drop', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
document.addEventListener('dragover', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
document.addEventListener('dragover', function(e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
let percentScroll_ = 0;
|
||||
let checkScrollIID_ = null;
|
||||
|
||||
function setPercentScroll(percent) {
|
||||
percentScroll_ = percent;
|
||||
contentElement.scrollTop = percentScroll_ * maxScrollTop();
|
||||
}
|
||||
|
||||
function percentScroll() {
|
||||
return percentScroll_;
|
||||
}
|
||||
|
||||
function restorePercentScroll() {
|
||||
setPercentScroll(percentScroll_);
|
||||
}
|
||||
|
||||
ipc.setHtml = (event) => {
|
||||
const html = event.html;
|
||||
|
||||
markJsHackMarkerInserted_ = false;
|
||||
|
||||
updateBodyHeight();
|
||||
|
||||
contentElement.innerHTML = html;
|
||||
|
||||
let previousContentHeight = contentElement.scrollHeight;
|
||||
let startTime = Date.now();
|
||||
restorePercentScroll();
|
||||
|
||||
if (!checkScrollIID_) {
|
||||
checkScrollIID_ = setInterval(() => {
|
||||
const h = contentElement.scrollHeight;
|
||||
if (h !== previousContentHeight) {
|
||||
previousContentHeight = h;
|
||||
restorePercentScroll();
|
||||
}
|
||||
if (Date.now() - startTime >= 1000) {
|
||||
clearInterval(checkScrollIID_);
|
||||
checkScrollIID_ = null;
|
||||
}
|
||||
}, 1);
|
||||
}
|
||||
|
||||
loadCssFiles(event.options.cssFiles);
|
||||
|
||||
if (event.options.downloadResources === 'manual') {
|
||||
webviewLib.setupResourceManualDownload();
|
||||
}
|
||||
}
|
||||
|
||||
let ignoreNextScrollEvent = false;
|
||||
ipc.setPercentScroll = (event) => {
|
||||
const percent = event.percent;
|
||||
|
||||
if (checkScrollIID_) {
|
||||
clearInterval(checkScrollIID_);
|
||||
checkScrollIID_ = null;
|
||||
}
|
||||
|
||||
ignoreNextScrollEvent = true;
|
||||
setPercentScroll(percent);
|
||||
}
|
||||
|
||||
// HACK for Mark.js bug - https://github.com/julmot/mark.js/issues/127
|
||||
let markJsHackMarkerInserted_ = false;
|
||||
function addMarkJsSpaceHack(document) {
|
||||
if (markJsHackMarkerInserted_) return;
|
||||
|
||||
const prepareElementsForMarkJs = (elements, type) => {
|
||||
// const markJsHackMarker_ = '​ ​'
|
||||
const markJsHackMarker_ = ' ';
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
if (!type) {
|
||||
elements[i].innerHTML = elements[i].innerHTML + markJsHackMarker_;
|
||||
} else if (type === 'insertBefore') {
|
||||
elements[i].insertAdjacentHTML('beforeBegin', markJsHackMarker_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prepareElementsForMarkJs(document.getElementsByTagName('p'));
|
||||
prepareElementsForMarkJs(document.getElementsByTagName('div'));
|
||||
prepareElementsForMarkJs(document.getElementsByTagName('br'), 'insertBefore');
|
||||
markJsHackMarkerInserted_ = true;
|
||||
}
|
||||
|
||||
let mark_ = null;
|
||||
let markSelectedElement_ = null;
|
||||
function setMarkers(keywords, options = null) {
|
||||
if (!options) options = {};
|
||||
|
||||
// TODO: Add support for scriptType on mobile and CLI
|
||||
|
||||
if (!mark_) {
|
||||
mark_ = new Mark(document.getElementById('content'), {
|
||||
exclude: ['img'],
|
||||
acrossElements: true,
|
||||
});
|
||||
}
|
||||
|
||||
addMarkJsSpaceHack(document);
|
||||
|
||||
mark_.unmark()
|
||||
|
||||
if (markSelectedElement_) markSelectedElement_.classList.remove('mark-selected');
|
||||
|
||||
let selectedElement = null;
|
||||
let elementIndex = 0;
|
||||
|
||||
const onEachElement = (element) => {
|
||||
if (!('selectedIndex' in options)) return;
|
||||
|
||||
if (('selectedIndex' in options) && elementIndex === options.selectedIndex) {
|
||||
markSelectedElement_ = element;
|
||||
element.classList.add('mark-selected');
|
||||
selectedElement = element;
|
||||
}
|
||||
|
||||
elementIndex++;
|
||||
}
|
||||
|
||||
for (let i = 0; i < keywords.length; i++) {
|
||||
let keyword = keywords[i];
|
||||
|
||||
markJsUtils.markKeyword(mark_, keyword, {
|
||||
pregQuote: pregQuote,
|
||||
replaceRegexDiacritics: replaceRegexDiacritics,
|
||||
}, {
|
||||
each: onEachElement,
|
||||
});
|
||||
}
|
||||
|
||||
ipcProxySendToHost('setMarkerCount', elementIndex);
|
||||
|
||||
if (selectedElement) selectedElement.scrollIntoView();
|
||||
}
|
||||
|
||||
let markLoaded_ = false;
|
||||
ipc.setMarkers = (event) => {
|
||||
const keywords = event.keywords;
|
||||
const options = event.options;
|
||||
|
||||
if (!keywords.length && !markLoaded_) return;
|
||||
|
||||
if (!markLoaded_) {
|
||||
const script = document.createElement('script');
|
||||
script.onload = function() {
|
||||
setMarkers(keywords, options);
|
||||
};
|
||||
|
||||
script.src = '../../node_modules/mark.js/dist/mark.min.js';
|
||||
document.getElementById('markScriptContainer').appendChild(script);
|
||||
markLoaded_ = true;
|
||||
} else {
|
||||
setMarkers(keywords, options);
|
||||
}
|
||||
}
|
||||
|
||||
function maxScrollTop() {
|
||||
return Math.max(0, contentElement.scrollHeight - contentElement.clientHeight);
|
||||
}
|
||||
|
||||
// The body element needs to have a fixed height for the content to be scrollable
|
||||
function updateBodyHeight() {
|
||||
document.getElementById('body').style.height = window.innerHeight + 'px';
|
||||
}
|
||||
|
||||
contentElement.addEventListener('scroll', webviewLib.logEnabledEventHandler(e => {
|
||||
if (ignoreNextScrollEvent) {
|
||||
ignoreNextScrollEvent = false;
|
||||
return;
|
||||
}
|
||||
const m = maxScrollTop();
|
||||
const percent = m ? contentElement.scrollTop / m : 0;
|
||||
setPercentScroll(percent);
|
||||
|
||||
ipcProxySendToHost('percentScroll', percent);
|
||||
}));
|
||||
|
||||
document.addEventListener('contextmenu', webviewLib.logEnabledEventHandler(event => {
|
||||
let element = event.target;
|
||||
|
||||
// To handle right clicks on resource icons
|
||||
if (element && !element.getAttribute('data-resource-id')) element = element.parentElement;
|
||||
|
||||
if (element && element.getAttribute('data-resource-id')) {
|
||||
ipcProxySendToHost('contextMenu', {
|
||||
type: element.getAttribute('src') ? 'image' : 'resource',
|
||||
resourceId: element.getAttribute('data-resource-id'),
|
||||
});
|
||||
} else {
|
||||
const selectedText = window.getSelection().toString();
|
||||
|
||||
if (selectedText) {
|
||||
ipcProxySendToHost('contextMenu', {
|
||||
type: 'text',
|
||||
textToCopy: selectedText,
|
||||
});
|
||||
} else if (event.target.getAttribute('href')) {
|
||||
ipcProxySendToHost('contextMenu', {
|
||||
type: 'link',
|
||||
textToCopy: event.target.getAttribute('href'),
|
||||
});
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
webviewLib.initialize({
|
||||
postMessage: ipcProxySendToHost,
|
||||
});
|
||||
|
||||
// Disable drag and drop otherwise it's possible to drop a URL
|
||||
// on it and it will open in the view as a website.
|
||||
document.addEventListener('drop', webviewLib.logEnabledEventHandler(e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}));
|
||||
document.addEventListener('dragover', webviewLib.logEnabledEventHandler(e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}));
|
||||
document.addEventListener('dragover', webviewLib.logEnabledEventHandler(e => {
|
||||
e.preventDefault();
|
||||
}));
|
||||
|
||||
window.addEventListener('resize', webviewLib.logEnabledEventHandler(() => {
|
||||
updateBodyHeight();
|
||||
}));
|
||||
|
||||
window.addEventListener('resize', function() {
|
||||
updateBodyHeight();
|
||||
});
|
||||
|
||||
updateBodyHeight();
|
||||
} catch (error) {
|
||||
ipcProxySendToHost('error:' + JSON.stringify(webviewLib.cloneError(error)));
|
||||
throw error;
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
@@ -127,7 +127,11 @@ class NoteListUtils {
|
||||
msg = _('Delete these %d notes?', noteIds.length);
|
||||
}
|
||||
|
||||
const ok = bridge().showConfirmMessageBox(msg);
|
||||
const ok = bridge().showConfirmMessageBox(msg, {
|
||||
buttons: [_('Delete'), _('Cancel')],
|
||||
defaultId: 1,
|
||||
});
|
||||
|
||||
if (!ok) return;
|
||||
await Note.batchDelete(noteIds);
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
ElectronClient/app/locales/fa.json
Normal file
1
ElectronClient/app/locales/fa.json
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -8,6 +8,7 @@ locales['de_DE'] = require('./de_DE.json');
|
||||
locales['en_US'] = require('./en_US.json');
|
||||
locales['es_ES'] = require('./es_ES.json');
|
||||
locales['eu'] = require('./eu.json');
|
||||
locales['fa'] = require('./fa.json');
|
||||
locales['fr_FR'] = require('./fr_FR.json');
|
||||
locales['gl_ES'] = require('./gl_ES.json');
|
||||
locales['hr_HR'] = require('./hr_HR.json');
|
||||
@@ -17,6 +18,7 @@ locales['ko'] = require('./ko.json');
|
||||
locales['nb_NO'] = require('./nb_NO.json');
|
||||
locales['nl_BE'] = require('./nl_BE.json');
|
||||
locales['nl_NL'] = require('./nl_NL.json');
|
||||
locales['pl_PL'] = require('./pl_PL.json');
|
||||
locales['pt_BR'] = require('./pt_BR.json');
|
||||
locales['ro'] = require('./ro.json');
|
||||
locales['ru_RU'] = require('./ru_RU.json');
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user