diff options
Diffstat (limited to '')
118 files changed, 10952 insertions, 0 deletions
diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 0000000..c070f43 --- /dev/null +++ b/.github/issue_template.md @@ -0,0 +1,16 @@ +## Your environment + +TbSync version: +DAV-4-TbSync version: +Thunderbird version: + +## Expected behavior +... + +## Actual behavior +... + +## Steps to reproduce +... + +To help resolving your issue, enable debug logging (TbSync Account Manager -> Help) and send me the debug.log via e-mail (use the title of your issue as subject of the email). diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 0000000..58cdd82 --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,18 @@ +## Creator +* John Bieling + +## Contributors +* John Bieling +* Brad2014 +* Nathan Gauër +* Alexandr Heymdall (vCard parser) + +## Translators +* Ettore Atalan (de) +* John Bieling (de, en-US) +* Wanderlei Hüttel (pt-BR) +* Alessandro Menti (it) +* Óvári (hu) +* Alexey Sinitsyn (ru) +* Jérémie Parisel (fr) +* Daniel Wróblewski (pl) @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + 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, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/Makebeta b/Makebeta new file mode 100644 index 0000000..24ef73f --- /dev/null +++ b/Makebeta @@ -0,0 +1,34 @@ +#!/bin/bash +# This file is part of DAV-4-TbSync. +# +# 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, You can obtain one at http://mozilla.org/MPL/2.0/. + +# $1 link to base web server : https://tbsync.jobisoft.de/beta +# $2 local path of base web server : /var/www/jobisoft.de/tbsync/beta +# $3 name of XPI : DAV-4-TbSync.xpi + +git clean -df +git checkout -- . +git pull + +version=$(cat manifest.json | jq -r .version) +updatefile=update-dav.json + +sed -i "s/%VERSION%/$version/g" "beta-release-channel-update.json" +sed -i "s|%LINK%|$1/$3|g" "beta-release-channel-update.json" +sed -i "s/static getApiVersion() { return \"/static getApiVersion() { return \"Beta /g" "content/provider.js" + +echo "Releasing version $version via beta release channel (will include updateURL)" +sed -i "s|\"name\": \"__MSG_extensionName__\",|\"name\": \"DAV for TbSync [Beta Release Channel]\",|g" "manifest.json" +sed -i "s|\"gecko\": {|\"gecko\": {\n \"update_url\": \"$1/$updatefile\",|g" "manifest.json" + +cp beta-release-channel-update.json $2/$updatefile + +rm -f $3 +rm -f $3.tar.gz +zip -r $3 content _locales manifest.json background.js LICENSE CONTRIBUTORS.md +tar cfvz $3.tar.gz content _locales manifest.json background.js LICENSE CONTRIBUTORS.md +cp $3 $2/$3 +cp $3.tar.gz $2/$3.tar.gz diff --git a/Makefile.bat b/Makefile.bat new file mode 100644 index 0000000..bde1c1d --- /dev/null +++ b/Makefile.bat @@ -0,0 +1,11 @@ +@echo off +REM This file is part of DAV-4-TbSync. +REM +REM This Source Code Form is subject to the terms of the Mozilla Public +REM License, v. 2.0. If a copy of the MPL was not distributed with this +REM file, You can obtain one at http://mozilla.org/MPL/2.0/. + +del DAV-4-TbSync.xpi +"C:\Program Files\7-Zip\7zG.exe" a -tzip DAV-4-TbSync.xpi content _locales manifest.json background.js LICENSE CONTRIBUTORS.md + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..c5972ed --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# DAV-4-TbSync +This provider add-on adds CalDAV & CardDAV synchronization capabilities to [TbSync](https://github.com/jobisoft/TbSync/). + +More information can be found in the [wiki](https://github.com/jobisoft/DAV-4-TbSync/wiki/About:-Provider-for-CalDAV-&-CardDAV) of this repository + + +## Want to add or fix a localization? +To help translating this project, please visit [crowdin.com](https://crowdin.com/profile/jobisoft), where the localizations are managed. If you want to add a new language, just contact me and I will set it up. + + +## Icon sources and attributions + +#### Public Domain + +* posteo.de icons by [posteo.de](https://commons.wikimedia.org/wiki/File:Posteo.png) +* mailbox.org icons by [mailbox.org](https://commons.wikimedia.org/wiki/File:Logo_mailbox.org_RGB_658x358.jpg) + +#### CC-BY 3.0 +* ics16.png by [FatCow Web Hosting](https://www.iconfinder.com/icons/35803/) +* google icons by [Just UI](https://www.iconfinder.com/icons/1298745/) +* icloud icons by [Five Icons](https://www.iconfinder.com/icons/252111/apple_icon) +* yahoo icons by [Five Icons](https://www.iconfinder.com/icons/252070/yahoo_icon) +* gmx icons by [CloudSponge](https://www.iconfinder.com/icons/1175604/address_book_contact_contacts_email_gmx_square_icon) +* web icons by [CloudSponge](https://www.iconfinder.com/icons/1175616/address_book_contact_contacts_email_mail_square_webde_icon) +* type.pref.png by [Steve Schoger](https://www.iconfinder.com/icons/3671863/) +* type.other.png by [Steve Schoger](https://www.iconfinder.com/icons/3671671/) +* type.work.png by [Steve Schoger](https://www.iconfinder.com/icons/3671695/) +* type.home.png by [Steve Schoger](https://www.iconfinder.com/icons/3671775/) +* type.car.png by [Steve Schoger](https://www.iconfinder.com/icons/3671885/) +* type.cell.png by [Steve Schoger](https://www.iconfinder.com/icons/3671810/) +* type.fax.png by [Steve Schoger](https://www.iconfinder.com/icons/3671840/) +* type.video.png by [Steve Schoger](https://www.iconfinder.com/icons/3671900/) +* type.voice.png by [Steve Schoger](https://www.iconfinder.com/icons/3671831/) +* type.pager.png by [Steve Schoger](https://www.iconfinder.com/icons/3671720/) + diff --git a/_locales/Readme.txt b/_locales/Readme.txt new file mode 100644 index 0000000..10208fa --- /dev/null +++ b/_locales/Readme.txt @@ -0,0 +1,8 @@ +Want to add or fix a localization? + +To help translating this project, please visit + + https://crowdin.com/profile/jobisoft + +where the localizations are managed. If you want to add +a new language, just contact me and I will set it up. diff --git a/_locales/bg/messages.json b/_locales/bg/messages.json new file mode 100644 index 0000000..60d80bb --- /dev/null +++ b/_locales/bg/messages.json @@ -0,0 +1,347 @@ +{ + "abCard.ContactDetails": { + "message": "Свойства на контакта (CardDAV)" + }, + "abCard.EmailAddresses": { + "message": "Електронни адреси:" + }, + "abCard.MiddleName": { + "message": "Бащино име:" + }, + "abCard.Phone": { + "message": "Телефонни номера" + }, + "abCard.PrefixName": { + "message": "Представка:" + }, + "abCard.SuffixName": { + "message": "Наставка:" + }, + "abCard.emailtypes.car": { + "message": "Телефон в колата" + }, + "abCard.emailtypes.cell": { + "message": "Мобилен телефон" + }, + "abCard.emailtypes.description": { + "message": "Първият електронен адрес от списъка ще бъде ползван като основен." + }, + "abCard.emailtypes.fax": { + "message": "Факс" + }, + "abCard.emailtypes.home": { + "message": "Лични" + }, + "abCard.emailtypes.other": { + "message": "Други" + }, + "abCard.emailtypes.pager": { + "message": "Пейджър" + }, + "abCard.emailtypes.video": { + "message": "Видео" + }, + "abCard.emailtypes.voice": { + "message": "Телефон" + }, + "abCard.emailtypes.work": { + "message": "Служебни" + }, + "acl.add": { + "message": "вмъквате" + }, + "acl.delete": { + "message": "изтриване" + }, + "acl.modify": { + "message": "промяна" + }, + "acl.none": { + "message": "никакви" + }, + "acl.readonly": { + "message": "Достъп до сървъра само за четене (изтрива местните промени)" + }, + "acl.readwrite": { + "message": "Права за писане на сървъра: ##replace.1##" + }, + "add.caldavserver": { + "message": "CalDAV сървър:" + }, + "add.carddavserver": { + "message": "CardDAV сървър:" + }, + "add.data.description": { + "message": "Въведете име за новата TbSync регистрация и данните за връзка със сървъра ви:" + }, + "add.data.notes": { + "message": "Бележка:" + }, + "add.data.title": { + "message": "Въвеждане на регистрацията" + }, + "add.finish.description": { + "message": "Следните настройки бяха потвърдени:" + }, + "add.finish.details": { + "message": "Натиснете „Готово“ за да създадете нова TbSync регистрация с тези настройки." + }, + "add.finish.title": { + "message": "Потвърдете създаването на регистрация" + }, + "add.name": { + "message": "Име на регистрация:" + }, + "add.ok": { + "message": "Добавяне на регистрация" + }, + "add.password": { + "message": "Парола:" + }, + "add.server": { + "message": "Адрес на сървър:" + }, + "add.serverprofile.custom": { + "message": "Ръчно настройване" + }, + "add.serverprofile.custom.description": { + "message": "Адресите за CalDAV и CardDAV услугите може да се настроят ръчно." + }, + "add.serverprofile.custom.details1": { + "message": "Необходимите CalDAV и CardDAV адреси, съответно WebDAV адреса на потребителя, ще ви ги даде вашият доставчик." + }, + "add.serverprofile.custom.details2": { + "message": "Ако оставите един от двата адреса празни, съответната услуга ще бъде изключена за вашата регистрация." + }, + "add.serverprofile.description": { + "message": "Изберете един от наличните сървърни профили:" + }, + "add.serverprofile.discovery": { + "message": "Автоматична настройка" + }, + "add.serverprofile.discovery.description": { + "message": "Повечето сървъри поддържат автоматично настойване, за което е нужно да въведете само електронен адрес или потребителско име и сървър." + }, + "add.serverprofile.discovery.details1": { + "message": "Въведете за автоматичното откриване на CalDAV и CardDAV адресите вашите данни за вход и съответния сървър (н.пр. “cloud.example.bg”)" + }, + "add.serverprofile.discovery.details2": { + "message": "Ако потребителското ви име е адрес на електронна поща, указването на сървър не е задължително, тъй като информацията за настройките може да се извлече от доставчика (по RFC 6764)." + }, + "add.serverprofile.discovery.server-optional": { + "message": "по желание" + }, + "add.serverprofile.fruux": { + "message": "fruux" + }, + "add.serverprofile.fruux.description": { + "message": "fruux е услуга, която синхронизира контакти, календари и задачи. Услугата се предлага от фирмата зад sabre/dav и седалището ѝ е в Германия." + }, + "add.serverprofile.gmx.com": { + "message": "GMX.com (USA)" + }, + "add.serverprofile.gmx.com.description": { + "message": "https://www.gmx.com" + }, + "add.serverprofile.gmx.net": { + "message": "GMX.net (Europa)" + }, + "add.serverprofile.gmx.net.description": { + "message": "https://www.gmx.net" + }, + "add.serverprofile.icloud": { + "message": "iCloud" + }, + "add.serverprofile.icloud.description": { + "message": "https://www.icloud.com" + }, + "add.serverprofile.icloud.details1": { + "message": "Потребителското име е вашият Apple ID. Паролата обаче не е паролата за вашия Apple ID! За да синхронизира TbSync вашите контакти и календари е необходимо да включите регистрацията с два фактора (2FA) за вашата iCloud-регистрация и да създадете отделна парола за приложението TbSync." + }, + "add.serverprofile.icloud.details2": { + "message": "Това е допълнително ниво на защита, наложено от Apple, което не дава достъп на приложенията до вашата Apple-регистрация." + }, + "add.serverprofile.mbo": { + "message": "mailbox.org" + }, + "add.serverprofile.mbo.description": { + "message": "mailbox.org е германски доставчик от германия с акцент върху сигурността за частни и бизнес клиенти. Предлага календари, контакти и място в облака." + }, + "add.serverprofile.posteo": { + "message": "Posteo" + }, + "add.serverprofile.posteo.description": { + "message": "https://www.posteo.de" + }, + "add.serverprofile.title": { + "message": "Избор на сървърен профил" + }, + "add.serverprofile.web.de": { + "message": "WEB.de" + }, + "add.serverprofile.web.de.description": { + "message": "https://www.web.de" + }, + "add.serverprofile.yahoo": { + "message": "Yahoo!" + }, + "add.serverprofile.yahoo.description": { + "message": "https://www.yahoo.com" + }, + "add.serverprofile.yahoo.details1": { + "message": "The requested password is an app-specific password for TbSync which you can create in your Yahoo! web portal. It is not your standard Yahoo! password." + }, + "add.spinner.query": { + "message": "Изпращане на RFC 6764 запитване до „##replace.1##“" + }, + "add.spinner.validating": { + "message": "Проверяване на връзката към сървъра" + }, + "add.title": { + "message": "Добавяне на CalDAV и CardDAV регистрация към TbSync" + }, + "add.user": { + "message": "Потребителско име:" + }, + "autocomplete.HOME": { + "message": "личен" + }, + "autocomplete.PREF": { + "message": "предпочитан" + }, + "autocomplete.WORK": { + "message": "служебен" + }, + "config.custom": { + "message": "CalDAV и CardDAV настройки на сървъра" + }, + "defaultname.calendar": { + "message": "Календар" + }, + "defaultname.contacts": { + "message": "Контакти" + }, + "extensionDescription": { + "message": "Разширява TbSync и позволява синхронизацията с CalDAV и CardDAV регистрации (контакти, задачи, календари)." + }, + "extensionName": { + "message": "Доставчик за CalDAV и CardDAV" + }, + "helplink.malformed-xml": { + "message": "https://github.com/jobisoft/DAV-4-TbSync/issues/104" + }, + "manager.tabs.accountsettings": { + "message": "Настройки на регистрацията" + }, + "manager.tabs.syncsettings": { + "message": "Опции" + }, + "menu.name": { + "message": "CalDAV и CardDAV" + }, + "pref.AccountName": { + "message": "Име на регистрация" + }, + "pref.CalDavServer": { + "message": "CalDAV сървър:" + }, + "pref.CardDavServer": { + "message": "CardDAV сървър:" + }, + "pref.UserName": { + "message": "Потребителско име" + }, + "pref.calendaroptions": { + "message": "Настройки за календара" + }, + "pref.contactoptions": { + "message": "Настройки за контакта" + }, + "pref.downloadonly": { + "message": "Пренебрегване на местните промени (еднопосочно синхронизаране)" + }, + "pref.generaloptions": { + "message": "Общи настройки" + }, + "pref.useCalendarCache": { + "message": "Офлайн поддръжка" + }, + "pref.useCardBook": { + "message": "Създай адресен указател в CardBook, вместо в стандартния адресен указател на Thunderbird" + }, + "status.401": { + "message": "Достъпът отказан, проверете потребителското име и паролата." + }, + "status.403": { + "message": "Връзката беше отхвърнена от сървъра (забранена)." + }, + "status.404": { + "message": "HTTP грешка 404 (поисканият ресурс не беше намерен)." + }, + "status.500": { + "message": "Непозната грешка от сървъра (HTTP грешка 500)." + }, + "status.503": { + "message": "Услугата е недостъпна." + }, + "status.caldavservernotfound": { + "message": "Не беше намерен CalDAV сървър." + }, + "status.carddavservernotfound": { + "message": "Не беше намерен CardDAV сървър." + }, + "status.gContactSync": { + "message": "Има несъвместимост с gContactSync при включена синхронизация на групите от контакти. Изключете едно от двете, докато грешката не бъде отстранена." + }, + "status.info.restored": { + "message": "Поради частично недостатъчни права за правене на промени, някои действия бяха отхвърлени от сървъра и местните промени бяха изтрити." + }, + "status.malformed-xml": { + "message": "Отговорът не е правилен XML. Синхронизацията беше прекъсната. Проверете протокола със събитията за подробности." + }, + "status.missing-permission": { + "message": "Недостатъчни права: ##replace.1##" + }, + "status.networkerror": { + "message": "Не можах да се свръжа със сървъра." + }, + "status.rfc6764-lookup-failed": { + "message": "Запитването за „##replace.1##“ не намери адресите за CalDAV и CardDAV услугите. Въведете името на сървъра, за да продължи настройването." + }, + "status.service-discovery-failed": { + "message": "Откриването на настройките по RFC6764 за „##replace.1##“ не проработи. Въведете друг адрес на сървър или направете ръчно настройване, за да въведете сами адресите." + }, + "status.softerror": { + "message": "Пренебрегната грешка (##replace.1##)" + }, + "status.success.managed-by-thunderbird": { + "message": "Ok" + }, + "status.non-carddav-addrbook": { + "message": "This is an older TbSync-CardDAV address book, which cannot be synced anymore. Disable and re-enable the address book, to create a fresh Thunderbird-CardDAV address book." + }, + "helplink.non-carddav-addrbook": { + "message": "https://github.com/jobisoft/DAV-4-TbSync/wiki/Since-Thunderbird-102,-TbSync-DAV-is-using-native-CardDAV-sync" + }, + "syncstate.eval.folders": { + "message": "Обновяване на списъка с ресурсите" + }, + "syncstate.eval.response.localchanges": { + "message": "Обработка на потвърждението за местните промени" + }, + "syncstate.eval.response.remotechanges": { + "message": "Обработка на новите данни от сървъра" + }, + "syncstate.prepare.request.localchanges": { + "message": "Изпращане на местните промени" + }, + "syncstate.send.getfolders": { + "message": "Запитване за списък с ресурсите" + }, + "syncstate.send.request.localchanges": { + "message": "Изчакване за потвърждение, че местните промени са изпратени" + }, + "syncstate.send.request.remotechanges": { + "message": "Изчакване на данни от сървъра за промени" + } +} diff --git a/_locales/cs/messages.json b/_locales/cs/messages.json new file mode 100644 index 0000000..8b838a7 --- /dev/null +++ b/_locales/cs/messages.json @@ -0,0 +1,347 @@ +{ + "abCard.ContactDetails": { + "message": "Vlastnosti kontaktu (CardDAV)" + }, + "abCard.EmailAddresses": { + "message": "E-mailové adresy" + }, + "abCard.MiddleName": { + "message": "Prostřední jméno:" + }, + "abCard.Phone": { + "message": "Telefonní čísla" + }, + "abCard.PrefixName": { + "message": "Titul před jménem:" + }, + "abCard.SuffixName": { + "message": "Titul za jménem:" + }, + "abCard.emailtypes.car": { + "message": "Auto" + }, + "abCard.emailtypes.cell": { + "message": "Mobil" + }, + "abCard.emailtypes.description": { + "message": "První e-mailová adresa v seznamu bude použita jako hlavní e-mailová adresa." + }, + "abCard.emailtypes.fax": { + "message": "Fax" + }, + "abCard.emailtypes.home": { + "message": "Domů" + }, + "abCard.emailtypes.other": { + "message": "Jiný" + }, + "abCard.emailtypes.pager": { + "message": "Pager" + }, + "abCard.emailtypes.video": { + "message": "Video" + }, + "abCard.emailtypes.voice": { + "message": "Telefon" + }, + "abCard.emailtypes.work": { + "message": "Práce" + }, + "acl.add": { + "message": "přidat" + }, + "acl.delete": { + "message": "smazat" + }, + "acl.modify": { + "message": "upravit" + }, + "acl.none": { + "message": "žádný" + }, + "acl.readonly": { + "message": "Přistup na server pouze pro čtení (zrušit místní změny)" + }, + "acl.readwrite": { + "message": "Oprávnění k zápisu na server: ##replace.1##" + }, + "add.caldavserver": { + "message": "Adresa serveru CalDAV:" + }, + "add.carddavserver": { + "message": "Adresa serveru CardDAV:" + }, + "add.data.description": { + "message": "Zadejte prosím přívětivé jméno pro nový účet TbSync a přihlašovací údaje pro váš server:" + }, + "add.data.notes": { + "message": "Poznámky:" + }, + "add.data.title": { + "message": "Zadejte informace o účtu" + }, + "add.finish.description": { + "message": "Následující nastavení byla úspěšně ověřena:" + }, + "add.finish.details": { + "message": "Klikněte na \"Dokončit\" pro vytvoření nového účtu TbSync s těmito nastaveními." + }, + "add.finish.title": { + "message": "Potvrdit vytvoření účtu" + }, + "add.name": { + "message": "Název účtu:" + }, + "add.ok": { + "message": "Přidat účet" + }, + "add.password": { + "message": "Heslo:" + }, + "add.server": { + "message": "URL serveru:" + }, + "add.serverprofile.custom": { + "message": "Ruční nastavení" + }, + "add.serverprofile.custom.description": { + "message": "Koncové body služeb CalDAV a CardDAV mohou být nastaveny ručně." + }, + "add.serverprofile.custom.details1": { + "message": "Požadované koncové body služby CalDAV a CardDAV neboli takzvané hlavní adresy by měl poskytnout váš poskytovatel služeb." + }, + "add.serverprofile.custom.details2": { + "message": "Pokud necháte adresu prázdnou, příslušná služba bude pro tento účet zakázána." + }, + "add.serverprofile.description": { + "message": "Vyberte prosím jeden z dostupných serverových profilů:" + }, + "add.serverprofile.discovery": { + "message": "Automatické nastavení" + }, + "add.serverprofile.discovery.description": { + "message": "Mnoho poskytovatelů služeb a serverů podporuje automatickou konfiguraci, která vyžaduje pouze e-mailovou adresu nebo uživatelské jméno a adresu serveru." + }, + "add.serverprofile.discovery.details1": { + "message": "Pro automatické zjištění koncových bodů služby CalDAV a CardDAV zadejte prosím své přihlašovací údaje a název vašeho serveru (např. 'cloud.myserver.de')." + }, + "add.serverprofile.discovery.details2": { + "message": "Pokud je vaším uživatelským jménem e-mailová adresa, není potřeba uvádět server, jelikož informace o koncových bodech služby lze získat přímo od vašeho poskytovatele služeb prostřednictvím požadavku RFC6764 (je-li podporováno)." + }, + "add.serverprofile.discovery.server-optional": { + "message": "nepovinné" + }, + "add.serverprofile.fruux": { + "message": "fruux" + }, + "add.serverprofile.fruux.description": { + "message": "fruux je služba, která synchronizuje kontakty, kalendáře a úkoly. Je provozována společností stojící za sabre/dav, která sídlí v Německu." + }, + "add.serverprofile.gmx.com": { + "message": "GMX.com (USA)" + }, + "add.serverprofile.gmx.com.description": { + "message": "https://www.gmx.com" + }, + "add.serverprofile.gmx.net": { + "message": "GMX.net (Evropa)" + }, + "add.serverprofile.gmx.net.description": { + "message": "https://www.gmx.net" + }, + "add.serverprofile.icloud": { + "message": "iCloud" + }, + "add.serverprofile.icloud.description": { + "message": "https://www.icloud.com" + }, + "add.serverprofile.icloud.details1": { + "message": "Požadované uživatelské jméno je vaše Apple ID. Všimněte si, že zde nemůžete použít své heslo pro Apple ID. MUSÍTE pro svůj iCloud účet povolit dvoufaktorové ověřování (2FA) a vytvořit samostatné heslo pro TbSync." + }, + "add.serverprofile.icloud.details2": { + "message": "Toto je bezpečnostní vrstva vynucená společností Apple, aby klienti třetích stran nezískali přístup k vašemu účtu Apple." + }, + "add.serverprofile.mbo": { + "message": "mailbox.org" + }, + "add.serverprofile.mbo.description": { + "message": "mailbox.org je bezpečný německý poskytovatel e-mailů pro soukromé a firemní zákazníky, který nabízí také kalendáře, kontakty a cloudové úložiště." + }, + "add.serverprofile.posteo": { + "message": "Posteo" + }, + "add.serverprofile.posteo.description": { + "message": "https://www.posteo.de" + }, + "add.serverprofile.title": { + "message": "Vyberte profil serveru" + }, + "add.serverprofile.web.de": { + "message": "WEB.de" + }, + "add.serverprofile.web.de.description": { + "message": "https://www.web.de" + }, + "add.serverprofile.yahoo": { + "message": "Yahoo!" + }, + "add.serverprofile.yahoo.description": { + "message": "https://www.yahoo.com" + }, + "add.serverprofile.yahoo.details1": { + "message": "Požadované heslo je specifické pro aplikaci TbSync, které můžete vytvořit na webovém portálu Yahoo! Není to vaše standardní Yahoo! heslo." + }, + "add.spinner.query": { + "message": "Odesílání požadavku RFC6764 na „##replace.1##“" + }, + "add.spinner.validating": { + "message": "Ověřování připojení k serveru" + }, + "add.title": { + "message": "Přidání CalDAV a CardDAV účtu do TbSync" + }, + "add.user": { + "message": "Uživatelské jméno:" + }, + "autocomplete.HOME": { + "message": "soukromý" + }, + "autocomplete.PREF": { + "message": "preferovaný" + }, + "autocomplete.WORK": { + "message": "pracovní" + }, + "config.custom": { + "message": "Nastavení CalDAV a CardDAV serveru" + }, + "defaultname.calendar": { + "message": "Kalendář" + }, + "defaultname.contacts": { + "message": "Kontakty" + }, + "extensionDescription": { + "message": "Přidat do TbSync podporu synchronizace pro CalDAV a CardDAV účty." + }, + "extensionName": { + "message": "Poskytovatel CalDAV a CardDAV" + }, + "helplink.malformed-xml": { + "message": "https://github.com/jobisoft/DAV-4-TbSync/issues/104" + }, + "manager.tabs.accountsettings": { + "message": "Nastavení účtu" + }, + "manager.tabs.syncsettings": { + "message": "Nastavení" + }, + "menu.name": { + "message": "CalDAV & CardDAV" + }, + "pref.AccountName": { + "message": "Název účtu" + }, + "pref.CalDavServer": { + "message": "Adresa serveru CalDAV:" + }, + "pref.CardDavServer": { + "message": "Adresa serveru CardDAV:" + }, + "pref.UserName": { + "message": "Uživatelské jméno" + }, + "pref.calendaroptions": { + "message": "Nastavení kalendáře" + }, + "pref.contactoptions": { + "message": "Nastavení kontaktů" + }, + "pref.downloadonly": { + "message": "Vrátit místní změny (jednosměrná synchronizace)" + }, + "pref.generaloptions": { + "message": "Obecné nastavení" + }, + "pref.useCalendarCache": { + "message": "Podpora offline režimu" + }, + "pref.useCardBook": { + "message": "Vytvářet adresáře CardBooku místo výchozích adresářů Thunderbirdu" + }, + "status.401": { + "message": "Nelze se přihlásit, zkontrolujte uživatelské jméno a heslo." + }, + "status.403": { + "message": "Server odmítl připojení (zakázáno)." + }, + "status.404": { + "message": "HTTP chyba 404 (požadovaný zdroj nebyl nalezen)." + }, + "status.500": { + "message": "Neznámá chyba serveru (HTTP chyba 500)." + }, + "status.503": { + "message": "Služba je nedostupná." + }, + "status.caldavservernotfound": { + "message": "Nelze najít CalDAV server." + }, + "status.carddavservernotfound": { + "message": "Nelze najít CardDAV server." + }, + "status.gContactSync": { + "message": "Existuje nekompatibilita s gContactSync s aktivovanou synchronizací skupin kontaktů. Deaktivujte jeden z doplňků, dokud nebude chyba vyřešena." + }, + "status.info.restored": { + "message": "Z důvodu částečně chybějícím oprávněním k zápisu byly některé akce serverem odmítnuty a zrušeny lokálně." + }, + "status.malformed-xml": { + "message": "Nelze zpracovat XML. Podrobnosti naleznete v protokolu událostí." + }, + "status.missing-permission": { + "message": "Chybějící oprávnění: ##replace.1##" + }, + "status.networkerror": { + "message": "Nelze se připojit k serveru." + }, + "status.rfc6764-lookup-failed": { + "message": "Dotaz “##replace.1##“ neposkytl požadované informace ke koncovým bodům služeb CalDAV a CardDAV. Zadejte prosím název serveru, aby mohla pokračovat automatická konfigurace." + }, + "status.service-discovery-failed": { + "message": "Automatické zjištění koncových bodů služby CalDAV a CardDAV “##replace.1##“ selhalo. Zkuste to znovu, určete jinou adresu serveru, nebo přepněte na vlastní nastavení a ručně zadejte koncové body služby." + }, + "status.softerror": { + "message": "Nekritická chyba (##replace.1##)" + }, + "status.success.managed-by-thunderbird": { + "message": "Ok" + }, + "status.non-carddav-addrbook": { + "message": "Toto je starší adresář TbSync-CardDAV, který již nelze synchronizovat. Zakažte a znovu povolte adresář, pro vytvoření nového adresáře Thunderbird-CardDAV." + }, + "helplink.non-carddav-addrbook": { + "message": "https://github.com/jobisoft/DAV-4-TbSync/wiki/Since-Thunderbird-102,-TbSync-DAV-is-using-native-CardDAV-sync" + }, + "syncstate.eval.folders": { + "message": "Aktualizace seznamu složek" + }, + "syncstate.eval.response.localchanges": { + "message": "Zpracovávání potvrzení místních změn" + }, + "syncstate.eval.response.remotechanges": { + "message": "Zpracovávání vzdálených změn" + }, + "syncstate.prepare.request.localchanges": { + "message": "Odesílání místních změn" + }, + "syncstate.send.getfolders": { + "message": "Požadavek na seznam složek" + }, + "syncstate.send.request.localchanges": { + "message": "Čekání na potvrzení lokálních změn" + }, + "syncstate.send.request.remotechanges": { + "message": "Čekám na změny ze serveru" + } +} diff --git a/_locales/de/messages.json b/_locales/de/messages.json new file mode 100644 index 0000000..3a30354 --- /dev/null +++ b/_locales/de/messages.json @@ -0,0 +1,347 @@ +{ + "abCard.ContactDetails": { + "message": "Kontakteigenschaften (CardDAV)" + }, + "abCard.EmailAddresses": { + "message": "E-Mail Adressen" + }, + "abCard.MiddleName": { + "message": "Zweiter Vorname:" + }, + "abCard.Phone": { + "message": "Telefonnummern" + }, + "abCard.PrefixName": { + "message": "Präfix:" + }, + "abCard.SuffixName": { + "message": "Suffix:" + }, + "abCard.emailtypes.car": { + "message": "Autotelefon" + }, + "abCard.emailtypes.cell": { + "message": "Handy" + }, + "abCard.emailtypes.description": { + "message": "Die erste E-Mailadresse in der Liste wird als primäre E-Mailadresse verwendet." + }, + "abCard.emailtypes.fax": { + "message": "Fax" + }, + "abCard.emailtypes.home": { + "message": "Privat" + }, + "abCard.emailtypes.other": { + "message": "Sonstige" + }, + "abCard.emailtypes.pager": { + "message": "Pager" + }, + "abCard.emailtypes.video": { + "message": "Video" + }, + "abCard.emailtypes.voice": { + "message": "Telefon" + }, + "abCard.emailtypes.work": { + "message": "Dienstlich" + }, + "acl.add": { + "message": "hinzufügen" + }, + "acl.delete": { + "message": "löschen" + }, + "acl.modify": { + "message": "bearbeiten" + }, + "acl.none": { + "message": "keine" + }, + "acl.readonly": { + "message": "Serverzugriff nur lesend (verwerfe lokale Änderungen)" + }, + "acl.readwrite": { + "message": "Schreibrechte auf Server: ##replace.1##" + }, + "add.caldavserver": { + "message": "CalDAV Serveradresse:" + }, + "add.carddavserver": { + "message": "CardDAV Serveradresse:" + }, + "add.data.description": { + "message": "Bitte geben Sie einen Namen für das neue TbSync-Konto und die Anmeldeinformationen für Ihren Server an:" + }, + "add.data.notes": { + "message": "Hinweise:" + }, + "add.data.title": { + "message": "Kontoinformationen angeben" + }, + "add.finish.description": { + "message": "Die folgenden Einstellungen wurden erfolgreich verifiziert:" + }, + "add.finish.details": { + "message": "Klicken Sie auf „Fertigstellen“, um ein neues TbSync-Konto mit diesen Einstellungen anzulegen." + }, + "add.finish.title": { + "message": "Kontoerstellung abschließen" + }, + "add.name": { + "message": "Kontoname:" + }, + "add.ok": { + "message": "Konto hinzufügen" + }, + "add.password": { + "message": "Passwort:" + }, + "add.server": { + "message": "Serveradresse:" + }, + "add.serverprofile.custom": { + "message": "Benutzerdefinierte Konfiguration" + }, + "add.serverprofile.custom.description": { + "message": "Die CalDAV und CardDAV Service-Endpunkte können manuell konfiguriert werden." + }, + "add.serverprofile.custom.details1": { + "message": "Die benötigten CalDAV & CardDAV Service-Endpunkte bzw. die Prinzipal-Adressen sollten Sie bei Ihrem Serviceanbieter erfragen können." + }, + "add.serverprofile.custom.details2": { + "message": "Wenn Sie eine der beiden Adressen leer lassen, wird der entsprechende Dienst für dieses Konto deaktiviert." + }, + "add.serverprofile.description": { + "message": "Bitte wählen Sie eines der verfügbaren Serverprofile aus:" + }, + "add.serverprofile.discovery": { + "message": "Automatische Konfiguration" + }, + "add.serverprofile.discovery.description": { + "message": "Viele Dienstanbieter und Server unterstützen eine automatische Konfiguration, bei der nur eine E-Mail Adresse bzw. ein Benutzername und eine Serveradresse angegeben werden müssen." + }, + "add.serverprofile.discovery.details1": { + "message": "Geben Sie für die automatische Erkennung der CalDAV- und CardDAV-Dienstendpunkte Ihre Zugangsdaten und den Hostnamen Ihres Servers ein (z.B. „cloud.myserver.de“)." + }, + "add.serverprofile.discovery.details2": { + "message": "Ist Ihr Benutzername eine E-Mail Adresse, wird die Angabe des Servers optional, da die Informationen bzgl. der Service-Endpunkte evtl. über eine RFC6764-Anfrage direkt von Ihrem Dienstanbieter bezogen werden können (falls dieser das unterstützt)." + }, + "add.serverprofile.discovery.server-optional": { + "message": "optionale Angabe" + }, + "add.serverprofile.fruux": { + "message": "fruux" + }, + "add.serverprofile.fruux.description": { + "message": "fruux ist ein Dienst, der Kontakte, Kalender und Aufgaben synchronisiert. Sie wird von der Firma hinter sabre/dav angetrieben und hat ihren Sitz in Deutschland." + }, + "add.serverprofile.gmx.com": { + "message": "GMX.com (USA)" + }, + "add.serverprofile.gmx.com.description": { + "message": "https://www.gmx.com" + }, + "add.serverprofile.gmx.net": { + "message": "GMX.net (Europa)" + }, + "add.serverprofile.gmx.net.description": { + "message": "https://www.gmx.net" + }, + "add.serverprofile.icloud": { + "message": "iCloud" + }, + "add.serverprofile.icloud.description": { + "message": "https://www.icloud.com" + }, + "add.serverprofile.icloud.details1": { + "message": "Der angeforderte Benutzername ist Ihre Apple ID. Das angeforderte Passwort ist jedoch nicht das Passwort für Ihr Apple ID! Um mit TbSync auf Ihre Kontakte und Kalender zugreifen zu können, müssen Sie zwingend die Zwei-Faktor-Autorisierung für Ihr iCloud-Konto aktivieren und ein separates App-spezifisches Kennwort für TbSync erstellen." + }, + "add.serverprofile.icloud.details2": { + "message": "Dies ist eine von Apple eingeführte zusätzliche Sicherheitsebene, sodass Drittanbieter-Clients keinen Zugriff auf Ihr Apple-Konto erhalten." + }, + "add.serverprofile.mbo": { + "message": "mailbox.org" + }, + "add.serverprofile.mbo.description": { + "message": "mailbox.org ist ein sicherer, deutscher E-Mail-Anbieter für Privat- und Geschäftskunden, der auch Kalender, Kontakte und Cloud-Speicher bietet." + }, + "add.serverprofile.posteo": { + "message": "Posteo" + }, + "add.serverprofile.posteo.description": { + "message": "https://www.posteo.de" + }, + "add.serverprofile.title": { + "message": "Serverprofil auswählen" + }, + "add.serverprofile.web.de": { + "message": "WEB.de" + }, + "add.serverprofile.web.de.description": { + "message": "https://www.web.de" + }, + "add.serverprofile.yahoo": { + "message": "Yahoo!" + }, + "add.serverprofile.yahoo.description": { + "message": "https://www.yahoo.com" + }, + "add.serverprofile.yahoo.details1": { + "message": "The requested password is an app-specific password for TbSync which you can create in your Yahoo! web portal. It is not your standard Yahoo! password." + }, + "add.spinner.query": { + "message": "Sende RFC6764-Anfrage an „##replace.1##“" + }, + "add.spinner.validating": { + "message": "Überprüfe Verbindung zum Server" + }, + "add.title": { + "message": "CalDAV & CardDAV Konto hinzufügen" + }, + "add.user": { + "message": "Benutzername:" + }, + "autocomplete.HOME": { + "message": "privat" + }, + "autocomplete.PREF": { + "message": "bevorzugt" + }, + "autocomplete.WORK": { + "message": "dienstlich" + }, + "config.custom": { + "message": "CalDAV & CardDAV Server Konfiguration" + }, + "defaultname.calendar": { + "message": "Kalender" + }, + "defaultname.contacts": { + "message": "Kontakte" + }, + "extensionDescription": { + "message": "Erweitert TbSync und erlaubt die Synchronisation von CalDAV & CardDAV Konten (Kontakte, Aufgaben und Kalender)." + }, + "extensionName": { + "message": "Provider für CalDAV & CardDAV" + }, + "helplink.malformed-xml": { + "message": "https://github.com/jobisoft/DAV-4-TbSync/issues/104" + }, + "manager.tabs.accountsettings": { + "message": "Kontoeinstellungen" + }, + "manager.tabs.syncsettings": { + "message": "Optionen" + }, + "menu.name": { + "message": "CalDAV & CardDAV" + }, + "pref.AccountName": { + "message": "Kontoname" + }, + "pref.CalDavServer": { + "message": "CalDAV Serveradresse:" + }, + "pref.CardDavServer": { + "message": "CardDAV Serveradresse:" + }, + "pref.UserName": { + "message": "Benutzername" + }, + "pref.calendaroptions": { + "message": "Kalender Optionen" + }, + "pref.contactoptions": { + "message": "Kontakt Optionen" + }, + "pref.downloadonly": { + "message": "Lokale Änderungen verwerfen (one-way sync)" + }, + "pref.generaloptions": { + "message": "Allgemeine Optionen" + }, + "pref.useCalendarCache": { + "message": "Offline-Unterstützung" + }, + "pref.useCardBook": { + "message": "Erstelle CardBook-Adressbücher anstelle der Standard-Adressbücher von Thunderbird" + }, + "status.401": { + "message": "Authentifizierung fehlgeschlagen, überprüfen Sie den Benutzernamen und das Passwort." + }, + "status.403": { + "message": "Verbindung vom Server abgelehnt (nicht erlaubt)." + }, + "status.404": { + "message": "HTTP Fehler 404 (angeforderte Resource nicht gefunden)." + }, + "status.500": { + "message": "Unbekannter Server Fehler (HTTP Fehler 500)." + }, + "status.503": { + "message": "Service nicht erreichbar." + }, + "status.caldavservernotfound": { + "message": "Es wurde kein CalDAV Server gefunden." + }, + "status.carddavservernotfound": { + "message": "Es wurde kein CardDAV Server gefunden." + }, + "status.gContactSync": { + "message": "Es besteht eine Inkompatibilität mit gContactSync bei aktivierter Synchronisation der Kontaktgruppen. Bitte deaktivieren eines von beiden, solange der Fehler nicht behoben ist." + }, + "status.info.restored": { + "message": "Wegen teilweise fehlender Schreibrechte wurden einige Aktionen vom Server zurückgewiesen und lokal rückgängig gemacht." + }, + "status.malformed-xml": { + "message": "Antwort enthält fehlerhaftes XML, Sync abgebrochen. Prüfen Sie bitte das Ereignisprotokoll für weitere Details." + }, + "status.missing-permission": { + "message": "Fehlende Berechtigung: ##replace.1##" + }, + "status.networkerror": { + "message": "Verbindung zum Server fehlgeschlagen." + }, + "status.rfc6764-lookup-failed": { + "message": "Die Abfrage von „##replace.1##“ lieferte nicht die benötigten Informationen bzgl. der CalDAV und CardDAV Service-Endpunkte. Bitte geben den Hostnamen ihres Servers an, um mit der automatischen Konfiguration fortzufahren." + }, + "status.service-discovery-failed": { + "message": "Die automatische Erkennung der CalDAV & CardDAV Service-Endpunkte von „##replace.1##“ war nicht erfolgreich. Versuchen Sie es unter Angabe einer anderen Serveradresse erneut oder wechseln Sie zur benutzerdefinierten Konfiguration, um die Service-Endpunkte selbst anzugeben." + }, + "status.softerror": { + "message": "Ignorierter Fehler (##replace.1##)" + }, + "status.success.managed-by-thunderbird": { + "message": "Ok" + }, + "status.non-carddav-addrbook": { + "message": "Dies ist ein älteres TbSync-CardDAV Adressbuch, welches nicht mehr synchronisiert werden kann. Bitte deaktivieren und reaktivieren Sie dieses Adressbuch, um ein neues Thunderbird-CardDAV Adressbuch anzulegen." + }, + "helplink.non-carddav-addrbook": { + "message": "https://github.com/jobisoft/DAV-4-TbSync/wiki/Since-Thunderbird-102,-TbSync-DAV-is-using-native-CardDAV-sync" + }, + "syncstate.eval.folders": { + "message": "Verarbeite Ordnerliste" + }, + "syncstate.eval.response.localchanges": { + "message": "Verarbeite Bestätigung der lokalen Änderungen" + }, + "syncstate.eval.response.remotechanges": { + "message": "Verarbeite Serverdaten" + }, + "syncstate.prepare.request.localchanges": { + "message": "Sende lokale Änderungen" + }, + "syncstate.send.getfolders": { + "message": "Sende Anfrage bzgl. Ordnerliste" + }, + "syncstate.send.request.localchanges": { + "message": "Warte auf Bestätigung der lokalen Änderungen" + }, + "syncstate.send.request.remotechanges": { + "message": "Warte auf Daten vom Server" + } +} diff --git a/_locales/en-US/messages.json b/_locales/en-US/messages.json new file mode 100644 index 0000000..61ee884 --- /dev/null +++ b/_locales/en-US/messages.json @@ -0,0 +1,347 @@ +{ + "abCard.ContactDetails": { + "message": "Contact properties (CardDAV)" + }, + "abCard.EmailAddresses": { + "message": "E-mail addresses" + }, + "abCard.MiddleName": { + "message": "Middle:" + }, + "abCard.Phone": { + "message": "Phone numbers" + }, + "abCard.PrefixName": { + "message": "Prefix:" + }, + "abCard.SuffixName": { + "message": "Suffix:" + }, + "abCard.emailtypes.car": { + "message": "Car" + }, + "abCard.emailtypes.cell": { + "message": "Mobile" + }, + "abCard.emailtypes.description": { + "message": "The first e-mail address in the list will be used as the primary e-mail address." + }, + "abCard.emailtypes.fax": { + "message": "Fax" + }, + "abCard.emailtypes.home": { + "message": "Home" + }, + "abCard.emailtypes.other": { + "message": "Other" + }, + "abCard.emailtypes.pager": { + "message": "Pager" + }, + "abCard.emailtypes.video": { + "message": "Video" + }, + "abCard.emailtypes.voice": { + "message": "Phone" + }, + "abCard.emailtypes.work": { + "message": "Work" + }, + "acl.add": { + "message": "add" + }, + "acl.delete": { + "message": "delete" + }, + "acl.modify": { + "message": "modify" + }, + "acl.none": { + "message": "none" + }, + "acl.readonly": { + "message": "Read-only server access (revert local changes)" + }, + "acl.readwrite": { + "message": "Server write permissions: ##replace.1##" + }, + "add.caldavserver": { + "message": "CalDAV server address:" + }, + "add.carddavserver": { + "message": "CardDAV server address:" + }, + "add.data.description": { + "message": "Please provide a friendly name for the new TbSync account and the credentials for your server:" + }, + "add.data.notes": { + "message": "Notes:" + }, + "add.data.title": { + "message": "Enter account information" + }, + "add.finish.description": { + "message": "The following settings have been verified successfully:" + }, + "add.finish.details": { + "message": "Click “Finish” to create a new TbSync account with these settings." + }, + "add.finish.title": { + "message": "Confirm account creation" + }, + "add.name": { + "message": "Account name:" + }, + "add.ok": { + "message": "Add account" + }, + "add.password": { + "message": "Password:" + }, + "add.server": { + "message": "Server URL:" + }, + "add.serverprofile.custom": { + "message": "Manual Configuration" + }, + "add.serverprofile.custom.description": { + "message": "The CalDAV and CardDAV service endpoints can be configured manually." + }, + "add.serverprofile.custom.details1": { + "message": "The required CalDAV & CardDAV service endpoints or the so called principal addresses should be provided by your service provider." + }, + "add.serverprofile.custom.details2": { + "message": "If you leave either address empty, the corresponding service will be disabled for this account." + }, + "add.serverprofile.description": { + "message": "Please select one of the available server profiles:" + }, + "add.serverprofile.discovery": { + "message": "Automatic Configuration" + }, + "add.serverprofile.discovery.description": { + "message": "Many service providers and servers support an automatic configuration process, which requires only an e-mail address or a username and a server address." + }, + "add.serverprofile.discovery.details1": { + "message": "For the automatic discovery of the CalDAV & CardDAV service endpoints, please enter your credentials and the host name of your server (e.g. 'cloud.myserver.de')." + }, + "add.serverprofile.discovery.details2": { + "message": "If your username is an e-mail address, specifying the server becomes optional, as the information about the service endpoints may be obtained directly from your service provider via an RFC6764 request (if supported)." + }, + "add.serverprofile.discovery.server-optional": { + "message": "optional" + }, + "add.serverprofile.fruux": { + "message": "fruux" + }, + "add.serverprofile.fruux.description": { + "message": "fruux is a service that syncs contacts, calendars and tasks. It's powered by the company behind sabre/dav and is based in Germany." + }, + "add.serverprofile.gmx.com": { + "message": "GMX.com (USA)" + }, + "add.serverprofile.gmx.com.description": { + "message": "https://www.gmx.com" + }, + "add.serverprofile.gmx.net": { + "message": "GMX.net (Europe)" + }, + "add.serverprofile.gmx.net.description": { + "message": "https://www.gmx.net" + }, + "add.serverprofile.icloud": { + "message": "iCloud" + }, + "add.serverprofile.icloud.description": { + "message": "https://www.icloud.com" + }, + "add.serverprofile.icloud.details1": { + "message": "The requested user name is your Apple ID. Please note, that you may not use your Apple ID password here. You MUST enable two-factor authentication (2FA) for your iCloud account and create a separate app-specific password for TbSync." + }, + "add.serverprofile.icloud.details2": { + "message": "This is a security layer enforced by Apple, so that 3rd party clients do not gain access to your Apple account." + }, + "add.serverprofile.mbo": { + "message": "mailbox.org" + }, + "add.serverprofile.mbo.description": { + "message": "mailbox.org is a secure German e-mail provider for private and business customers, which also offers calendars, contacts and cloud storage." + }, + "add.serverprofile.posteo": { + "message": "Posteo" + }, + "add.serverprofile.posteo.description": { + "message": "https://www.posteo.de" + }, + "add.serverprofile.title": { + "message": "Select a server profile" + }, + "add.serverprofile.web.de": { + "message": "WEB.de" + }, + "add.serverprofile.web.de.description": { + "message": "https://www.web.de" + }, + "add.serverprofile.yahoo": { + "message": "Yahoo!" + }, + "add.serverprofile.yahoo.description": { + "message": "https://www.yahoo.com" + }, + "add.serverprofile.yahoo.details1": { + "message": "The requested password is an app-specific password for TbSync which you can create in your Yahoo! web portal. It is not your standard Yahoo! password." + }, + "add.spinner.query": { + "message": "Sending RFC6764 request to “##replace.1##”" + }, + "add.spinner.validating": { + "message": "Verifying connection to server" + }, + "add.title": { + "message": "Adding a CalDAV & CardDAV account to TbSync" + }, + "add.user": { + "message": "User name:" + }, + "autocomplete.HOME": { + "message": "private" + }, + "autocomplete.PREF": { + "message": "preferred" + }, + "autocomplete.WORK": { + "message": "business" + }, + "config.custom": { + "message": "CalDAV & CardDAV server configuration" + }, + "defaultname.calendar": { + "message": "Calendar" + }, + "defaultname.contacts": { + "message": "Contacts" + }, + "extensionDescription": { + "message": "Add sync support for CalDAV & CardDAV accounts to TbSync." + }, + "extensionName": { + "message": "Provider for CalDAV & CardDAV" + }, + "helplink.malformed-xml": { + "message": "https://github.com/jobisoft/DAV-4-TbSync/issues/104" + }, + "manager.tabs.accountsettings": { + "message": "Account settings" + }, + "manager.tabs.syncsettings": { + "message": "Options" + }, + "menu.name": { + "message": "CalDAV & CardDAV" + }, + "pref.AccountName": { + "message": "Account name" + }, + "pref.CalDavServer": { + "message": "CalDAV server address:" + }, + "pref.CardDavServer": { + "message": "CardDAV server address:" + }, + "pref.UserName": { + "message": "User name" + }, + "pref.calendaroptions": { + "message": "Calendar options" + }, + "pref.contactoptions": { + "message": "Contact options" + }, + "pref.downloadonly": { + "message": "Revert local changes (one-way sync)" + }, + "pref.generaloptions": { + "message": "General options" + }, + "pref.useCalendarCache": { + "message": "Offline Support" + }, + "pref.useCardBook": { + "message": "Create CardBook address books instead of Thunderbird's default address books" + }, + "status.401": { + "message": "Could not authenticate, check username and password." + }, + "status.403": { + "message": "Server rejected connection (forbidden)." + }, + "status.404": { + "message": "HTTP Error 404 (requested resource not found)." + }, + "status.500": { + "message": "Unknown Server Error (HTTP Error 500)." + }, + "status.503": { + "message": "Service unavailable." + }, + "status.caldavservernotfound": { + "message": "Could not find a CalDAV server." + }, + "status.carddavservernotfound": { + "message": "Could not find a CardDAV server." + }, + "status.gContactSync": { + "message": "There is an incompatibility with gContactSync with activated contact group synchronization. Please deactivate one of them as long as the error is not resolved." + }, + "status.info.restored": { + "message": "Due to partially missing write permissions, some actions were rejected by the server and reversed locally." + }, + "status.malformed-xml": { + "message": "Could not parse XML. Check event log for details." + }, + "status.missing-permission": { + "message": "Missing permission: ##replace.1##" + }, + "status.networkerror": { + "message": "Could not connect to server." + }, + "status.rfc6764-lookup-failed": { + "message": "The query of “##replace.1##” did not provide the required information regarding the CalDAV and CardDAV service endpoints. Please enter the hostname of your server to proceed with the automatic configuration." + }, + "status.service-discovery-failed": { + "message": "Automatic discovery of CalDAV & CardDAV service endpoints of “##replace.1##” has failed. Try again, specifying a different server address, or switch to the custom configuration to manually specify the service endpoints." + }, + "status.softerror": { + "message": "Non fatal error (##replace.1##)" + }, + "status.success.managed-by-thunderbird": { + "message": "Ok" + }, + "status.non-carddav-addrbook": { + "message": "This is an older TbSync-CardDAV address book, which cannot be synced anymore. Disable and re-enable the address book, to create a fresh Thunderbird-CardDAV address book." + }, + "helplink.non-carddav-addrbook": { + "message": "https://github.com/jobisoft/DAV-4-TbSync/wiki/Since-Thunderbird-102,-TbSync-DAV-is-using-native-CardDAV-sync" + }, + "syncstate.eval.folders": { + "message": "Updating folder list" + }, + "syncstate.eval.response.localchanges": { + "message": "Processing acknowledgment of local changes" + }, + "syncstate.eval.response.remotechanges": { + "message": "Processing remote changes" + }, + "syncstate.prepare.request.localchanges": { + "message": "Sending local changes" + }, + "syncstate.send.getfolders": { + "message": "Requesting folder list" + }, + "syncstate.send.request.localchanges": { + "message": "Waiting for acknowledgment of local changes" + }, + "syncstate.send.request.remotechanges": { + "message": "Waiting for remote changes" + } +} diff --git a/_locales/es/messages.json b/_locales/es/messages.json new file mode 100644 index 0000000..4a44e37 --- /dev/null +++ b/_locales/es/messages.json @@ -0,0 +1,347 @@ +{ + "abCard.ContactDetails": { + "message": "Propiedades del contacto (CardDAV)" + }, + "abCard.EmailAddresses": { + "message": "Direcciones de correo electrónico" + }, + "abCard.MiddleName": { + "message": "Nombre adicional:" + }, + "abCard.Phone": { + "message": "Números de teléfono" + }, + "abCard.PrefixName": { + "message": "Prefijo:" + }, + "abCard.SuffixName": { + "message": "Sufijo:" + }, + "abCard.emailtypes.car": { + "message": "Coche" + }, + "abCard.emailtypes.cell": { + "message": "Móvil" + }, + "abCard.emailtypes.description": { + "message": "La primera dirección de la lista se utilizará como dirección principal de correo electrónico." + }, + "abCard.emailtypes.fax": { + "message": "Fax" + }, + "abCard.emailtypes.home": { + "message": "Casa" + }, + "abCard.emailtypes.other": { + "message": "Otro" + }, + "abCard.emailtypes.pager": { + "message": "Buscapersonas" + }, + "abCard.emailtypes.video": { + "message": "Vídeo" + }, + "abCard.emailtypes.voice": { + "message": "Teléfono" + }, + "abCard.emailtypes.work": { + "message": "Trabajo" + }, + "acl.add": { + "message": "añadir" + }, + "acl.delete": { + "message": "eliminar" + }, + "acl.modify": { + "message": "modificar" + }, + "acl.none": { + "message": "ninguno" + }, + "acl.readonly": { + "message": "Acceso de solo lectura al servidor (revertir cambios locales)" + }, + "acl.readwrite": { + "message": "Permisos de escritura del servidor: ##replace.1##" + }, + "add.caldavserver": { + "message": "Dirección del servidor CalDAV:" + }, + "add.carddavserver": { + "message": "Dirección del servidor CardDAV:" + }, + "add.data.description": { + "message": "Escriba un nombre reconocible para la nueva cuenta de TbSync y las credenciales de su servidor:" + }, + "add.data.notes": { + "message": "Notas:" + }, + "add.data.title": { + "message": "Datos de la cuenta" + }, + "add.finish.description": { + "message": "Los siguientes ajustes se han verificado correctamente:" + }, + "add.finish.details": { + "message": "Haga clic en «Finalizar» para crear una cuenta de TbSync con estos ajustes." + }, + "add.finish.title": { + "message": "Confirmar la creación de la cuenta" + }, + "add.name": { + "message": "Nombre de cuenta:" + }, + "add.ok": { + "message": "Añadir cuenta" + }, + "add.password": { + "message": "Contraseña:" + }, + "add.server": { + "message": "URL del servidor:" + }, + "add.serverprofile.custom": { + "message": "Configuración manual" + }, + "add.serverprofile.custom.description": { + "message": "Puede configurar manualmente los extremos de los servicios CalDAV y CardDav." + }, + "add.serverprofile.custom.details1": { + "message": "Los extremos de los servicios CalDAV y CardDAV, o direcciones del servidor, debe proporcionárselos su proveedor de servicios." + }, + "add.serverprofile.custom.details2": { + "message": "Si deja vacía alguna de estas direcciones, no se habilitará el servicio correspondiente en esta cuenta." + }, + "add.serverprofile.description": { + "message": "Seleccione uno de los perfiles de servidor disponibles:" + }, + "add.serverprofile.discovery": { + "message": "Configuración automática" + }, + "add.serverprofile.discovery.description": { + "message": "Muchos servidores y proveedores de servicios admiten un proceso de configuración automático que solo requiere una dirección de correo electrónico o nombre de usuario y la dirección del servidor." + }, + "add.serverprofile.discovery.details1": { + "message": "Para detectar automáticamente los extremos de los servicios CalDAV y CardDAV, escriba sus credenciales y el nombre de host de su servidor (por ejemplo, «nube.miservidor.es»)." + }, + "add.serverprofile.discovery.details2": { + "message": "Si su nombre de usuario es una dirección de correo electrónico, quizás no sea necesario especificar el servidor, ya que los extremos de los servicios se podrían obtener directamente del proveedor de servicios mediante una solicitud RFC6764 (si se admite)." + }, + "add.serverprofile.discovery.server-optional": { + "message": "opcional" + }, + "add.serverprofile.fruux": { + "message": "fruux" + }, + "add.serverprofile.fruux.description": { + "message": "fruux es un servicio que sincroniza contactos, calendarios y tareas. Lo ofrece la compañía que desarrolla sabre/dav y tiene su sede en Alemania." + }, + "add.serverprofile.gmx.com": { + "message": "GMX.com (EE. UU.)" + }, + "add.serverprofile.gmx.com.description": { + "message": "https://www.gmx.com" + }, + "add.serverprofile.gmx.net": { + "message": "GMX.net (Europa)" + }, + "add.serverprofile.gmx.net.description": { + "message": "https://www.gmx.net" + }, + "add.serverprofile.icloud": { + "message": "iCloud" + }, + "add.serverprofile.icloud.description": { + "message": "https://www.icloud.com" + }, + "add.serverprofile.icloud.details1": { + "message": "El nombre de usuario que necesita es su ID de Apple. Tenga en cuenta que no puede usar aquí la contraseña de su ID de Apple. DEBE activar la autenticación de dos factores (2FA) en su cuenta de iCloud y crear una contraseña de aplicación específica para TbSync." + }, + "add.serverprofile.icloud.details2": { + "message": "Se trata de una capa de seguridad impuesta por Apple para evitar que las aplicaciones de terceros obtengan acceso a su cuenta de Apple." + }, + "add.serverprofile.mbo": { + "message": "mailbox.org" + }, + "add.serverprofile.mbo.description": { + "message": "mailbox.org es un proveedor alemán de correo electrónico seguro para clientes privados y empresas que también ofrece calendarios, contactos y almacenamiento en la nube." + }, + "add.serverprofile.posteo": { + "message": "Posteo" + }, + "add.serverprofile.posteo.description": { + "message": "https://www.posteo.de" + }, + "add.serverprofile.title": { + "message": "Selección del perfil del servidor" + }, + "add.serverprofile.web.de": { + "message": "WEB.de" + }, + "add.serverprofile.web.de.description": { + "message": "https://www.web.de" + }, + "add.serverprofile.yahoo": { + "message": "Yahoo!" + }, + "add.serverprofile.yahoo.description": { + "message": "https://www.yahoo.com" + }, + "add.serverprofile.yahoo.details1": { + "message": "La contraseña que necesita es una contraseña específica para TbSync que puede crear en el portal web de Yahoo!. Es distinta de su contraseña normal de Yahoo!." + }, + "add.spinner.query": { + "message": "Enviando solicitud RFC6764 a «##replace.1##»" + }, + "add.spinner.validating": { + "message": "Verificando conexión con el servidor" + }, + "add.title": { + "message": "Añadir una cuenta CalDAV y CardDAV a TbSync" + }, + "add.user": { + "message": "Nombre de usuario:" + }, + "autocomplete.HOME": { + "message": "privado" + }, + "autocomplete.PREF": { + "message": "preferido" + }, + "autocomplete.WORK": { + "message": "profesional" + }, + "config.custom": { + "message": "Configuración del servidor CalDAV y CardDAV" + }, + "defaultname.calendar": { + "message": "Calendario" + }, + "defaultname.contacts": { + "message": "Contactos" + }, + "extensionDescription": { + "message": "Añade soporte para sincronización de cuentas CalDAV y CardDAV a TbSync." + }, + "extensionName": { + "message": "Proveedor de CalDAV y CardDAV" + }, + "helplink.malformed-xml": { + "message": "https://github.com/jobisoft/DAV-4-TbSync/issues/104" + }, + "manager.tabs.accountsettings": { + "message": "Ajustes de la cuenta" + }, + "manager.tabs.syncsettings": { + "message": "Opciones" + }, + "menu.name": { + "message": "CalDAV y CardDAV" + }, + "pref.AccountName": { + "message": "Nombre de la cuenta" + }, + "pref.CalDavServer": { + "message": "Dirección del servidor CalDAV:" + }, + "pref.CardDavServer": { + "message": "Dirección del servidor CardDAV:" + }, + "pref.UserName": { + "message": "Nombre de usuario" + }, + "pref.calendaroptions": { + "message": "Opciones de calendario" + }, + "pref.contactoptions": { + "message": "Opciones de contactos" + }, + "pref.downloadonly": { + "message": "Revertir cambios locales (sincronización en una dirección)" + }, + "pref.generaloptions": { + "message": "Opciones generales" + }, + "pref.useCalendarCache": { + "message": "Se puede usar sin conexión" + }, + "pref.useCardBook": { + "message": "Crear libretas de direcciones de CardBook en lugar de las predeterminadas de Thunderbird" + }, + "status.401": { + "message": "No ha sido posible autentificar, compruebe el nombre de usuario y contraseña." + }, + "status.403": { + "message": "El servidor ha rechazado la conexión (prohibida)." + }, + "status.404": { + "message": "Error HTTP 404 (no se encuentra el recurso solicitado)." + }, + "status.500": { + "message": "Error de servidor desconocido (error HTTP 500)." + }, + "status.503": { + "message": "Servicio no disponible." + }, + "status.caldavservernotfound": { + "message": "No se ha encontrado un servidor CalDAV." + }, + "status.carddavservernotfound": { + "message": "No se ha encontrado un servidor CardDAV." + }, + "status.gContactSync": { + "message": "Existe incompatibilidad con el complemento gContactSync cuando se activa la sincronización de grupos de contactos. Desactive uno de los dos hasta que se resuelva el problema." + }, + "status.info.restored": { + "message": "Debido a la ausencia de algunos permisos de escritura, el servidor rechazó algunas acciones, que se han descartado localmente." + }, + "status.malformed-xml": { + "message": "No ha sido posible analizar el XML. Compruebe el registro de eventos para más detalles." + }, + "status.missing-permission": { + "message": "Permiso ausente: ##replace.1##" + }, + "status.networkerror": { + "message": "No ha sido posible conectar al servidor." + }, + "status.rfc6764-lookup-failed": { + "message": "La consulta de «##replace.1##» no proporcionó la información necesaria con respecto a los extremos de los servicios CalDAV y CardDAV. Escriba el nombre de host de su servidor para continuar con la configuración automática." + }, + "status.service-discovery-failed": { + "message": "Ha fallado la detección automática de los extremos de los servicios de «##replace.1##». Inténtelo de nuevo especificando una dirección distinta para el servidor o cambie a la configuración personalizada para especificar manualmente los extremos de los servicios." + }, + "status.softerror": { + "message": "Error no fatal (##replace.1##)" + }, + "status.success.managed-by-thunderbird": { + "message": "OK" + }, + "status.non-carddav-addrbook": { + "message": "Esta es una libreta de direcciones TbSync-CardDAV que ya no se puede sincronizar. Deshabilita y vuelve a habilitar la libreta de direcciones para crear una libreta de direcciones de Thunderbird-CardDAV." + }, + "helplink.non-carddav-addrbook": { + "message": "https://github.com/jobisoft/DAV-4-TbSync/wiki/Since-Thunderbird-102,-TbSync-DAV-is-using-native-CardDAV-sync" + }, + "syncstate.eval.folders": { + "message": "Actualizando la lista de carpetas" + }, + "syncstate.eval.response.localchanges": { + "message": "Procesando el reconocimiento de cambios locales" + }, + "syncstate.eval.response.remotechanges": { + "message": "Procesando los cambios remotos" + }, + "syncstate.prepare.request.localchanges": { + "message": "Enviando los cambios locales" + }, + "syncstate.send.getfolders": { + "message": "Solicitando la lista de carpetas" + }, + "syncstate.send.request.localchanges": { + "message": "Esperando el reconocimiento de cambios locales" + }, + "syncstate.send.request.remotechanges": { + "message": "Esperando los cambios remotos" + } +} diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json new file mode 100644 index 0000000..81a4a6b --- /dev/null +++ b/_locales/fr/messages.json @@ -0,0 +1,347 @@ +{ + "abCard.ContactDetails": { + "message": "Propriété du contact (CardDAV)" + }, + "abCard.EmailAddresses": { + "message": "Adresse de courriel" + }, + "abCard.MiddleName": { + "message": "Second prénom:" + }, + "abCard.Phone": { + "message": "Numéros de téléphone" + }, + "abCard.PrefixName": { + "message": "Préfix:" + }, + "abCard.SuffixName": { + "message": "Suffixe:" + }, + "abCard.emailtypes.car": { + "message": "Voiture" + }, + "abCard.emailtypes.cell": { + "message": "Portable" + }, + "abCard.emailtypes.description": { + "message": "La première adresse de courriel de la liste sera utilisée comme adresse principale." + }, + "abCard.emailtypes.fax": { + "message": "Fax" + }, + "abCard.emailtypes.home": { + "message": "Domicile" + }, + "abCard.emailtypes.other": { + "message": "Autre" + }, + "abCard.emailtypes.pager": { + "message": "Téléavertisseur" + }, + "abCard.emailtypes.video": { + "message": "Vidéoconférence" + }, + "abCard.emailtypes.voice": { + "message": "Téléphone" + }, + "abCard.emailtypes.work": { + "message": "Travail" + }, + "acl.add": { + "message": "ajouter" + }, + "acl.delete": { + "message": "supprimer" + }, + "acl.modify": { + "message": "modifier" + }, + "acl.none": { + "message": "aucun" + }, + "acl.readonly": { + "message": "Accès au serveur en lecture seule (annule les modification locales)" + }, + "acl.readwrite": { + "message": "Droits en écriture sur le serveur: ##replace.1##" + }, + "add.caldavserver": { + "message": "Adresse du serveur CalDAV:" + }, + "add.carddavserver": { + "message": "Adresse du serveur CardDAV:" + }, + "add.data.description": { + "message": "Veuillez fournir un nom convivial pour le nouveau compte TbSync, ainsi que le couple identifiant/mot de passe d'accès à votre serveur:" + }, + "add.data.notes": { + "message": "Notes:" + }, + "add.data.title": { + "message": "Information de compte" + }, + "add.finish.description": { + "message": "Les paramètres suivants ont été vérifiés avec succès :" + }, + "add.finish.details": { + "message": "Cliquez sur « Terminer » pour créer un nouveau compte TbSync avec ces paramètres." + }, + "add.finish.title": { + "message": "Confirmer la création d'un compte" + }, + "add.name": { + "message": "Nom du compte:" + }, + "add.ok": { + "message": "Ajouter un compte" + }, + "add.password": { + "message": "Mot de passe:" + }, + "add.server": { + "message": "URL du serveur:" + }, + "add.serverprofile.custom": { + "message": "Configuration manuelle" + }, + "add.serverprofile.custom.description": { + "message": "Les URL d'accès aux services CalDAV et CardDAV peuvent être configurées manuellement." + }, + "add.serverprofile.custom.details1": { + "message": "Les URL d'accès aux services CalDAV et CardDAV ou ce qu'on appelle les 'adresses principales' devraient vous avoir été fournies par votre fournisseur de services." + }, + "add.serverprofile.custom.details2": { + "message": "Si vous laissez le champ d'adresse vide, le service correspondant sera désactivé pour ce compte." + }, + "add.serverprofile.description": { + "message": "Veuillez sélectionner un des profils de serveur disponibles:" + }, + "add.serverprofile.discovery": { + "message": "Configuration automatique" + }, + "add.serverprofile.discovery.description": { + "message": "De nombreux fournisseurs de services et de serveurs permettent un processus de configuration automatique, ce qui ne nécessite que l'adresse du serveur, ainsi qu'un nom d'utilisateur ou une adresse de courriel." + }, + "add.serverprofile.discovery.details1": { + "message": "Pour la découverte automatique des URL d'accès aux services CalDAV & CardDAV, veuillez entrer vos identifiants et le nom d'hôte de votre serveur (par exemple 'cloud.monserveur.example')." + }, + "add.serverprofile.discovery.details2": { + "message": "Si votre nom d'utilisateur est une adresse e-mail, spécifier le serveur devient optionnel, car les informations sur les URL d'accès au service peuvent être obtenues automatiquement auprès de votre fournisseur de services via une requête RFC6764 (pour autant que votre fournisseur prenne en charge ce type de requêtes)." + }, + "add.serverprofile.discovery.server-optional": { + "message": "facultatif" + }, + "add.serverprofile.fruux": { + "message": "fruux" + }, + "add.serverprofile.fruux.description": { + "message": "fruux est un service de synchronisation de contacts, d'agendas et de tâches. Il est fourni par la société éditrice de sabre/dav et est basé en Allemagne." + }, + "add.serverprofile.gmx.com": { + "message": "GMX.com (USA)" + }, + "add.serverprofile.gmx.com.description": { + "message": "https://www.gmx.com" + }, + "add.serverprofile.gmx.net": { + "message": "GMX.net (Europe)" + }, + "add.serverprofile.gmx.net.description": { + "message": "https://www.gmx.net" + }, + "add.serverprofile.icloud": { + "message": "iCloud" + }, + "add.serverprofile.icloud.description": { + "message": "https://www.icloud.com" + }, + "add.serverprofile.icloud.details1": { + "message": "Le nom d'utilisateur demandé est votre Apple ID. Veuillez noter que vous ne pouvez pas utiliser votre mot de passe Apple ID ici. Vous DEVEZ action l'authentification à deux facteurs (2FA) pour votre compte iCloud et créer un mot de passe d'application spécialement pour TbSync." + }, + "add.serverprofile.icloud.details2": { + "message": "Il s'agit d'une mesure de sécurité imposée par Apple, afin que les clients tiers n'aient pas accès à l'ensemble de votre compte Apple." + }, + "add.serverprofile.mbo": { + "message": "mailbox.org" + }, + "add.serverprofile.mbo.description": { + "message": "mailbox.org est un fournisseur de services de courriels allemand, à destination des particuliers et des entreprises. Ils fournissent également l'hébergement de calendriers, de contacts et des services cloud." + }, + "add.serverprofile.posteo": { + "message": "Posteo" + }, + "add.serverprofile.posteo.description": { + "message": "https://www.posteo.de" + }, + "add.serverprofile.title": { + "message": "Sélection d'un profil de serveur" + }, + "add.serverprofile.web.de": { + "message": "WEB.de" + }, + "add.serverprofile.web.de.description": { + "message": "https://www.web.de" + }, + "add.serverprofile.yahoo": { + "message": "Yahoo!" + }, + "add.serverprofile.yahoo.description": { + "message": "https://www.yahoo.com" + }, + "add.serverprofile.yahoo.details1": { + "message": "Le mot de passe demandé est un mot de passe spécifique à l'application pour TbSync que vous pouvez créer sur votre portail Web Yahoo! Ce n'est pas votre mot de passe standard Yahoo!" + }, + "add.spinner.query": { + "message": "Envoi d'une demande RFC6764 à «##replace.1## »" + }, + "add.spinner.validating": { + "message": "Vérification de la connexion au serveur" + }, + "add.title": { + "message": "Ajout d'un compte CalDAV & CardDAV à TbSync" + }, + "add.user": { + "message": "Nom d'utilisateur:" + }, + "autocomplete.HOME": { + "message": "privé" + }, + "autocomplete.PREF": { + "message": "favori" + }, + "autocomplete.WORK": { + "message": "travail" + }, + "config.custom": { + "message": "Configuration des serveurs CalDAV et CardDAV" + }, + "defaultname.calendar": { + "message": "Agenda" + }, + "defaultname.contacts": { + "message": "Contacts" + }, + "extensionDescription": { + "message": "Ajoute à TbSync la prise en charge des comptes CalDAV & CardDAV." + }, + "extensionName": { + "message": "Provider pour CalDAV & CardDAV" + }, + "helplink.malformed-xml": { + "message": "https://github.com/jobisoft/DAV-4-TbSync/issues/59#issuecomment-459685281" + }, + "manager.tabs.accountsettings": { + "message": "Paramètres du compte" + }, + "manager.tabs.syncsettings": { + "message": "Paramètres" + }, + "menu.name": { + "message": "CalDAV & CardDAV" + }, + "pref.AccountName": { + "message": "Nom du compte" + }, + "pref.CalDavServer": { + "message": "Adresse du serveur CalDAV:" + }, + "pref.CardDavServer": { + "message": "Adresse du serveur CardDAV:" + }, + "pref.UserName": { + "message": "Nom d'utilisateur" + }, + "pref.calendaroptions": { + "message": "Options de calendrier" + }, + "pref.contactoptions": { + "message": "Options des contacts" + }, + "pref.downloadonly": { + "message": "Annuler les modifications locales (synchronisation à sens unique)" + }, + "pref.generaloptions": { + "message": "Options générales" + }, + "pref.useCalendarCache": { + "message": "Prise en charge du mode hors-ligne" + }, + "pref.useCardBook": { + "message": "Créer un carnet d'adresse dans CardBook plutôt que dans le carnet d'adresse standard de Thunderbird" + }, + "status.401": { + "message": "L'authentification a échoué. Veuillez vérifier le couple nom d'utilisateur/mot de passe." + }, + "status.403": { + "message": "Le serveur a refusé la connexion (accès interdit)." + }, + "status.404": { + "message": "HTTP Erreur 404 (la ressource demandée n'a pas été trouvée)." + }, + "status.500": { + "message": "Erreur serveur inconnue(Erreur HTTP 500)." + }, + "status.503": { + "message": "Service indisponible." + }, + "status.caldavservernotfound": { + "message": "Impossible de trouver un serveur CalDAV." + }, + "status.carddavservernotfound": { + "message": "Impossible de trouver un serveur CardDAV." + }, + "status.gContactSync": { + "message": "Il y a une incompatibilité avec gContactSync, lorsque la synchronisation des groupes de contacts est activée. Tant que ce roblème n'est pas résolu, veuillez désactiver le compte ou la synchronisation des groupes." + }, + "status.info.restored": { + "message": "À cause de droits en écriture partiellement insuffisants, certaines actions ont été refusées par le serveur et annulées localement." + }, + "status.malformed-xml": { + "message": "Analyse de l'XML impossible. Veuillez consulter le journal de débogage pour plus de détails. " + }, + "status.missing-permission": { + "message": "Droit manquant: ##replace.1##" + }, + "status.networkerror": { + "message": "La connexion au serveur a échoué." + }, + "status.rfc6764-lookup-failed": { + "message": "L'appel à '##replace.1##” n'a pas permis de déterminer les informations nécessaires relatives aux URL des services CalDAV et CardDAV. Veuillez entrer le nom d'hôte de votre serveur afin de continuer la configuration automatique." + }, + "status.service-discovery-failed": { + "message": "La découverte automatique des URL d'accès aux services CalDAV et CardDAV de “##replace.1###” a échoué. Veuillez réessayer, en spécifiant une adresse de serveur différente, ou passez à la configuration personnalisée, afin de spécifier manuellement les URL en question." + }, + "status.softerror": { + "message": "Erreur non critique (##replace.1##)" + }, + "status.success.managed-by-thunderbird": { + "message": "OK" + }, + "status.non-carddav-addrbook": { + "message": "Ceci est un carnet d'adresses CardDAV issu d'une ancienne version de Tbsync et qui ne peut plus être synchronisé. Désactivez et réactivez le carnet d'adresses pour créer un nouveau carnet d'adresses CardDAV, utilisant la fonctionnalité intégrée dans Thunberbird." + }, + "helplink.non-carddav-addrbook": { + "message": "https://github.com/jobisoft/DAV-4-TbSync/wiki/Since-Thunderbird-102,-TbSync-DAV-is-using-native-CardDAV-sync" + }, + "syncstate.eval.folders": { + "message": "Mise à jour de la liste des dossiers" + }, + "syncstate.eval.response.localchanges": { + "message": "Traitement de la confirmation de réception des modifications locales" + }, + "syncstate.eval.response.remotechanges": { + "message": "En cours de traitement des modifications distantes" + }, + "syncstate.prepare.request.localchanges": { + "message": "Envoi des modifications locales" + }, + "syncstate.send.getfolders": { + "message": "Demande de la liste des dossiers" + }, + "syncstate.send.request.localchanges": { + "message": "En attente de la confirmation de réception des modifications locales" + }, + "syncstate.send.request.remotechanges": { + "message": "En attente des modifications distantes" + } +} diff --git a/_locales/gl/messages.json b/_locales/gl/messages.json new file mode 100644 index 0000000..f9f8a6d --- /dev/null +++ b/_locales/gl/messages.json @@ -0,0 +1,347 @@ +{ + "abCard.ContactDetails": { + "message": "Propiedades do contacto (CardDAV)" + }, + "abCard.EmailAddresses": { + "message": "Enderezos de correo electrónico" + }, + "abCard.MiddleName": { + "message": "Segundo nome:" + }, + "abCard.Phone": { + "message": "Número de teléfono" + }, + "abCard.PrefixName": { + "message": "Prefixo:" + }, + "abCard.SuffixName": { + "message": "Sufixo:" + }, + "abCard.emailtypes.car": { + "message": "Coche" + }, + "abCard.emailtypes.cell": { + "message": "Móbil" + }, + "abCard.emailtypes.description": { + "message": "O primeiro enderezo de correo electrónico da lista usarase como enderezo principal." + }, + "abCard.emailtypes.fax": { + "message": "Fax" + }, + "abCard.emailtypes.home": { + "message": "Casa" + }, + "abCard.emailtypes.other": { + "message": "Outro" + }, + "abCard.emailtypes.pager": { + "message": "Buscapersoas" + }, + "abCard.emailtypes.video": { + "message": "Vídeo" + }, + "abCard.emailtypes.voice": { + "message": "Teléfono" + }, + "abCard.emailtypes.work": { + "message": "Traballo" + }, + "acl.add": { + "message": "engadir" + }, + "acl.delete": { + "message": "eliminar" + }, + "acl.modify": { + "message": "modificar" + }, + "acl.none": { + "message": "ningún" + }, + "acl.readonly": { + "message": "Acceso ao servidor só de lectura (reverter cambios locais)" + }, + "acl.readwrite": { + "message": "Permisos de escritura do servidor: ##replace.1##" + }, + "add.caldavserver": { + "message": "Enderezo do servidor CalDAV:" + }, + "add.carddavserver": { + "message": "Enderezo do servidor CardDAV:" + }, + "add.data.description": { + "message": "Por favor, facilita un nome amigable para a nova conta de TbSync e as credenciais do teu servidor:" + }, + "add.data.notes": { + "message": "Notas:" + }, + "add.data.title": { + "message": "Insire información da conta" + }, + "add.finish.description": { + "message": "Verificáronse correctamente os seguintes axustes:" + }, + "add.finish.details": { + "message": "Preme en \"Rematar\" para crear a nova conta de TbSync con estes axustes." + }, + "add.finish.title": { + "message": "Confirma a creación da conta" + }, + "add.name": { + "message": "Nome da conta:" + }, + "add.ok": { + "message": "Engadir conta" + }, + "add.password": { + "message": "Contrasinal:" + }, + "add.server": { + "message": "URL do servidor:" + }, + "add.serverprofile.custom": { + "message": "Configuración manual" + }, + "add.serverprofile.custom.description": { + "message": "Podes configurar manualmente os extremos dos servizos CalDAV e CardDAV." + }, + "add.serverprofile.custom.details1": { + "message": "Os extremos dos servizos CalDAV e CardDAV, ou os chamados enderezos principais, ten que proporcionalos o teu provedor de servizo." + }, + "add.serverprofile.custom.details2": { + "message": "Se deixas baleiro algún destes enderezos non se activará o servizo correspondente nesta conta." + }, + "add.serverprofile.description": { + "message": "Por favor, selecciona un dos perfís de servidor dispoñibles:" + }, + "add.serverprofile.discovery": { + "message": "Configuración automática" + }, + "add.serverprofile.discovery.description": { + "message": "Moitos servidores e provedores de servizo admiten un proceso automático de configuración que só precisa dun enderezo de correo electrónico ou dun nome de usuario, e do enderezo do servidor." + }, + "add.serverprofile.discovery.details1": { + "message": "Para o descubrimento automático dos extremos dos servizos CalDAV e CardDAV tes que facilitar as túas credenciais e o nome de equipo do teu servidor (por exemplo, nube.dominio.gal)." + }, + "add.serverprofile.discovery.details2": { + "message": "Se o teu nome de usuario é un enderezo de correo é opcional que especifiques o nome do servidor xa que a información sobre os extremos do servizo poden obterse de forma automática do teu provedor de servizo mediante unha solicitude RFC6764 (se é que a admite)." + }, + "add.serverprofile.discovery.server-optional": { + "message": "opcional" + }, + "add.serverprofile.fruux": { + "message": "fruux" + }, + "add.serverprofile.fruux.description": { + "message": "fruux é un servizo que sincroniza contactos, calendarios e tarefas. Ofréceo a mesma compañía que desenvolve sabre/dav e ten sede en Alemaña." + }, + "add.serverprofile.gmx.com": { + "message": "GMX.com (EEUU de América)" + }, + "add.serverprofile.gmx.com.description": { + "message": "https://www.gmx.com" + }, + "add.serverprofile.gmx.net": { + "message": "GMX.net (Europa)" + }, + "add.serverprofile.gmx.net.description": { + "message": "https://www.gmx.net" + }, + "add.serverprofile.icloud": { + "message": "iCloud" + }, + "add.serverprofile.icloud.description": { + "message": "https://www.icloud.com" + }, + "add.serverprofile.icloud.details1": { + "message": "O nome de usuario que precisas é o teu Apple ID. Por favor, ten en conta que non podes usar o contrasinal de Apple ID aquí. Tes que activar a autenticación de doble factor (2FA) na túa conta de iCloud e crear un contrasinal de aplicación específico para usar con TbSync." + }, + "add.serverprofile.icloud.details2": { + "message": "Esta é unha capa de seguridade imposta por Apple para que os clientes de terceiros non teñan acceso á túa conta Apple." + }, + "add.serverprofile.mbo": { + "message": "mailbox.org" + }, + "add.serverprofile.mbo.description": { + "message": "mailbox.org é un provedor alemán de correo electrónico para empresas e clientes privados que ofrece calendarios, contactos e almacenamento na nube." + }, + "add.serverprofile.posteo": { + "message": "Posteo" + }, + "add.serverprofile.posteo.description": { + "message": "https://www.posteo.de" + }, + "add.serverprofile.title": { + "message": "Selecciona un perfil de servidor" + }, + "add.serverprofile.web.de": { + "message": "WEB.de" + }, + "add.serverprofile.web.de.description": { + "message": "https://www.web.de" + }, + "add.serverprofile.yahoo": { + "message": "Yahoo!" + }, + "add.serverprofile.yahoo.description": { + "message": "https://www.yahoo.com" + }, + "add.serverprofile.yahoo.details1": { + "message": "O contrasinal que se solicita é un contrasinal de aplicación para TbSync que podes crear no teu portal web de Yahoo! Non é o teu contrasinal normal de Yahoo!" + }, + "add.spinner.query": { + "message": "Enviando unha solicitude RFC6764 a “##replace.1##”" + }, + "add.spinner.validating": { + "message": "Verificando a conexión ao servidor" + }, + "add.title": { + "message": "Engadindo a conta CalDAV e CardDAV a TbSync" + }, + "add.user": { + "message": "Nome de usuario:" + }, + "autocomplete.HOME": { + "message": "privado" + }, + "autocomplete.PREF": { + "message": "preferido" + }, + "autocomplete.WORK": { + "message": "profesional" + }, + "config.custom": { + "message": "Configuración do servidor CalDAV e CardDAV" + }, + "defaultname.calendar": { + "message": "Calendario" + }, + "defaultname.contacts": { + "message": "Contactos" + }, + "extensionDescription": { + "message": "Engadir a TbSync soporte para a sincronización de contas CalDAV e CardDAV." + }, + "extensionName": { + "message": "Provedor de CalDAV e CardDAV" + }, + "helplink.malformed-xml": { + "message": "https://github.com/jobisoft/DAV-4-TbSync/issues/104" + }, + "manager.tabs.accountsettings": { + "message": "Axustes da conta" + }, + "manager.tabs.syncsettings": { + "message": "Opcións" + }, + "menu.name": { + "message": "CalDAV e CardDAV" + }, + "pref.AccountName": { + "message": "Nome da conta" + }, + "pref.CalDavServer": { + "message": "Enderezo do servidor CalDAV:" + }, + "pref.CardDavServer": { + "message": "Enderezo do servidor CardDAV:" + }, + "pref.UserName": { + "message": "Nome de usuario" + }, + "pref.calendaroptions": { + "message": "Opcións do calendario" + }, + "pref.contactoptions": { + "message": "Opcións do contacto" + }, + "pref.downloadonly": { + "message": "Reverter os cambios locais (sincronización nun sentido)" + }, + "pref.generaloptions": { + "message": "Opcións xerais" + }, + "pref.useCalendarCache": { + "message": "Soporte sen conexión" + }, + "pref.useCardBook": { + "message": "Crear libretas de enderezos de CardBook en vez das predeterminadas de Thunderbird" + }, + "status.401": { + "message": "Non se puido autenticar, comproba o nome de usuario e contrasinal." + }, + "status.403": { + "message": "O servidor rexeitou a conexión (prohibida)." + }, + "status.404": { + "message": "Erro HTTP 404 (non se atopou o recurso solicitado)." + }, + "status.500": { + "message": "Erro descoñecido do servidor (erro HTTP 500)." + }, + "status.503": { + "message": "Servizo non dispoñible." + }, + "status.caldavservernotfound": { + "message": "Non se puido atopar un servidor CalDAV." + }, + "status.carddavservernotfound": { + "message": "Non se puido atopar un servidor CardDAV." + }, + "status.gContactSync": { + "message": "gContactSync é incompatible coa activación da sincronización de grupos de contactos. Por favor, desactiva un deles ata que se solucione este erro." + }, + "status.info.restored": { + "message": "Algunas accións foron rexeitadas polo servidor e revertéronse en local debido á ausencia parcial de permisos de escritura." + }, + "status.malformed-xml": { + "message": "Non se puido analizar o XML. Revisa o rexistro de eventos para dispor de máis detalles." + }, + "status.missing-permission": { + "message": "Falta o permiso: ##replace.1##" + }, + "status.networkerror": { + "message": "Non se puido conectar ao servidor." + }, + "status.rfc6764-lookup-failed": { + "message": "A consulta “##replace.1##” non devolveu a información necesaria sobre os extremos do servizos CalDAV e CardDAV. Por favor, insire o nome do equipo do teu servidor para continuar coa configuración automática." + }, + "status.service-discovery-failed": { + "message": "Fallou o descubrimento automático dos extremos dos servizos CalDAV e CardDAV de “##replace.1##”. Proba de novo especificando un enderezo do servidor diferente ou cambia a configuración personalizada para especificar os extremos do servizo." + }, + "status.softerror": { + "message": "Erro non fatal (##replace.1##)" + }, + "status.success.managed-by-thunderbird": { + "message": "OK" + }, + "status.non-carddav-addrbook": { + "message": "Esta é unha versión vella do caderno de enderezos de TbSync-CardDAV que xa non se pode sincronizar. Desactívao e volve a activalo para crear un novo caderno de enderezos Thunderbird-CardDAV." + }, + "helplink.non-carddav-addrbook": { + "message": "https://github.com/jobisoft/DAV-4-TbSync/wiki/Since-Thunderbird-102,-TbSync-DAV-is-using-native-CardDAV-sync" + }, + "syncstate.eval.folders": { + "message": "Actualizando a lista de cartafois" + }, + "syncstate.eval.response.localchanges": { + "message": "Procesando o recoñecemento de cambios locais" + }, + "syncstate.eval.response.remotechanges": { + "message": "Procesando os cambios remotos" + }, + "syncstate.prepare.request.localchanges": { + "message": "Enviando cambios locais" + }, + "syncstate.send.getfolders": { + "message": "Solicitando a lista de cartafoles" + }, + "syncstate.send.request.localchanges": { + "message": "Esperando polo recoñecemento dos cambios locais" + }, + "syncstate.send.request.remotechanges": { + "message": "Esperando polos cambios remotos" + } +} diff --git a/_locales/hu/messages.json b/_locales/hu/messages.json new file mode 100644 index 0000000..59203e9 --- /dev/null +++ b/_locales/hu/messages.json @@ -0,0 +1,347 @@ +{ + "abCard.ContactDetails": { + "message": "Kapcsolati tulajdonságok (CardDAV)" + }, + "abCard.EmailAddresses": { + "message": "E-mail-címek" + }, + "abCard.MiddleName": { + "message": "Egyéb név:" + }, + "abCard.Phone": { + "message": "Telefonszámok" + }, + "abCard.PrefixName": { + "message": "Előtag:" + }, + "abCard.SuffixName": { + "message": "Utótag:" + }, + "abCard.emailtypes.car": { + "message": "Autó" + }, + "abCard.emailtypes.cell": { + "message": "Mobil" + }, + "abCard.emailtypes.description": { + "message": "A lista első e-mail-címe lesz az elsődleges e-mail-cím." + }, + "abCard.emailtypes.fax": { + "message": "Fax" + }, + "abCard.emailtypes.home": { + "message": "Otthoni" + }, + "abCard.emailtypes.other": { + "message": "Egyéb" + }, + "abCard.emailtypes.pager": { + "message": "Személyhívó" + }, + "abCard.emailtypes.video": { + "message": "Videó" + }, + "abCard.emailtypes.voice": { + "message": "Telefon" + }, + "abCard.emailtypes.work": { + "message": "Munkahelyi" + }, + "acl.add": { + "message": "hozzáadás" + }, + "acl.delete": { + "message": "törlés" + }, + "acl.modify": { + "message": "módosítás" + }, + "acl.none": { + "message": "egyik sem" + }, + "acl.readonly": { + "message": "Csak olvasható hozzáférés (helyi változások visszavonása)" + }, + "acl.readwrite": { + "message": "Írási jogosultságok: ##replace.1##" + }, + "add.caldavserver": { + "message": "CalDAV kiszolgálócím:" + }, + "add.carddavserver": { + "message": "CardDAV kiszolgálócím:" + }, + "add.data.description": { + "message": "Adjon meg egy becenevet az új TbSync-fióknak és adja meg a hitelesítő adatait a kiszolgálóhoz:" + }, + "add.data.notes": { + "message": "Megjegyzések:" + }, + "add.data.title": { + "message": "Fiókadatok megadása" + }, + "add.finish.description": { + "message": "A következő beállítások sikeresen ellenőrzésre kerültek:" + }, + "add.finish.details": { + "message": "Kattintson a „Befejezés“ gombra a TbSync-fiók ezekkel a beállításokkal történő létrehozásához." + }, + "add.finish.title": { + "message": "Fióklétrehozás megerősítése" + }, + "add.name": { + "message": "Fiók neve:" + }, + "add.ok": { + "message": "Fiók hozzáadása" + }, + "add.password": { + "message": "Jelszó:" + }, + "add.server": { + "message": "Kiszolgáló URL:" + }, + "add.serverprofile.custom": { + "message": "Kézi beállítás" + }, + "add.serverprofile.custom.description": { + "message": "A CalDAV és a CardDAV szolgáltatási végpontjok kézzel állíthatók be." + }, + "add.serverprofile.custom.details1": { + "message": "A szükséges CalDAV és CardDAV szolgáltatási végpontokat, vagy az ún. főcímeket a szolgáltatónak kell megadnia." + }, + "add.serverprofile.custom.details2": { + "message": "Ha a két címet üresen hagyja, akkor a megfelelő szolgáltatás le lesz tiltva ennél a fióknál." + }, + "add.serverprofile.description": { + "message": "Válasszon egyet a rendelkezésre álló kiszolgálóprofilok közül:" + }, + "add.serverprofile.discovery": { + "message": "Automatikus beállítás" + }, + "add.serverprofile.discovery.description": { + "message": "Számos szolgáltató és kiszolgáló támogatja az automatikus beállítási folyamatot, amelyhez csak e-mail-cím vagy felhasználónév és kiszolgálócím szükséges." + }, + "add.serverprofile.discovery.details1": { + "message": "A CalDAV és CardDAV szolgáltatási végpontok automatikus felfedezéséhez adja meg a hitelesítő adatait és a kiszolgálója gépnevét (például „cloud.myserver.de”)." + }, + "add.serverprofile.discovery.details2": { + "message": "Ha a felhasználóneve egy e-mail-cím, akkor a kiszolgáló megadása nem kötelező, mivel a szolgáltatási végpontok információi automatikusan lekérhetőek a szolgáltatótól egy RFC6764 kéréssel (ha az támogatott)." + }, + "add.serverprofile.discovery.server-optional": { + "message": "választható" + }, + "add.serverprofile.fruux": { + "message": "fruux" + }, + "add.serverprofile.fruux.description": { + "message": "A fruux egy szolgáltatás, amely szinkronizálja a névjegyeket, naptárakat és feladatokat. A sabre/dav mögött álló németországi központú cég támogatja." + }, + "add.serverprofile.gmx.com": { + "message": "GMX.com (USA)" + }, + "add.serverprofile.gmx.com.description": { + "message": "https://www.gmx.com" + }, + "add.serverprofile.gmx.net": { + "message": "GMX.net (Európa)" + }, + "add.serverprofile.gmx.net.description": { + "message": "https://www.gmx.net" + }, + "add.serverprofile.icloud": { + "message": "iCloud" + }, + "add.serverprofile.icloud.description": { + "message": "https://www.icloud.com" + }, + "add.serverprofile.icloud.details1": { + "message": "A kért felhasználónév az Ön Apple-azonosítója. Vegye figyelembe, hogy itt nem használhatja az Apple-azonosító jelszavát. Engedélyeznie KELL a kétlépcsős hitelesítést (2FA) az iCloud-fiókjában, és különálló alkalmazásjelszót kell létrehoznia a TBSync számára." + }, + "add.serverprofile.icloud.details2": { + "message": "Ez egy az Apple által megkövetel biztonsági réteg, így a harmadik féltől származó kliensek nem férnek hozzá az Ön Apple-fiókjához." + }, + "add.serverprofile.mbo": { + "message": "mailbox.org" + }, + "add.serverprofile.mbo.description": { + "message": "A mailbox.org egy biztonságos német e-mail-szolgáltató magán- és üzleti felhasználók számára, amely naptárakat, névjegyeket és felhőtárhelyet is kínál." + }, + "add.serverprofile.posteo": { + "message": "Posteo" + }, + "add.serverprofile.posteo.description": { + "message": "https://www.posteo.de" + }, + "add.serverprofile.title": { + "message": "Kiszolgálóprofil kiválasztása" + }, + "add.serverprofile.web.de": { + "message": "WEB.de" + }, + "add.serverprofile.web.de.description": { + "message": "https://www.web.de" + }, + "add.serverprofile.yahoo": { + "message": "Yahoo!" + }, + "add.serverprofile.yahoo.description": { + "message": "https://www.yahoo.com" + }, + "add.serverprofile.yahoo.details1": { + "message": "A kért jelszó egy alkalmazásjelszó a TBSynchez, amelyet a Yahoo! webportálon hozhat létre. Ez nem a szokásos Yahoo! jelszava." + }, + "add.spinner.query": { + "message": "RFC6764 kérés küldése a(z) „##replace.1##” címre" + }, + "add.spinner.validating": { + "message": "Kiszolgálókapcsolat ellenőrzése" + }, + "add.title": { + "message": "CalDAV- és CardDAV-fiók hozzáadása a TbSynchez" + }, + "add.user": { + "message": "Felhasználónév:" + }, + "autocomplete.HOME": { + "message": "magán" + }, + "autocomplete.PREF": { + "message": "előnyben részesített" + }, + "autocomplete.WORK": { + "message": "üzleti" + }, + "config.custom": { + "message": "A CalDAV- és CardDAV-kiszolgáló beállításai" + }, + "defaultname.calendar": { + "message": "Naptár" + }, + "defaultname.contacts": { + "message": "Névjegyek" + }, + "extensionDescription": { + "message": "CalDAV- és CardDAV-fiókok szinkronizációjának támogatása a TbSyncben." + }, + "extensionName": { + "message": "CalDAV- és CardDAV-szolgáltató" + }, + "helplink.malformed-xml": { + "message": "https://github.com/jobisoft/DAV-4-TbSync/issues/104" + }, + "manager.tabs.accountsettings": { + "message": "Fiókbeállítások" + }, + "manager.tabs.syncsettings": { + "message": "Beállítások" + }, + "menu.name": { + "message": "CalDAV és CardDAV" + }, + "pref.AccountName": { + "message": "Fiók neve" + }, + "pref.CalDavServer": { + "message": "CalDAV kiszolgálócím:" + }, + "pref.CardDavServer": { + "message": "CardDAV kiszolgálócím:" + }, + "pref.UserName": { + "message": "Felhasználónév" + }, + "pref.calendaroptions": { + "message": "Naptár beállításai" + }, + "pref.contactoptions": { + "message": "Névjegyzék beállításai" + }, + "pref.downloadonly": { + "message": "Helyi változások visszaállítása (egyirányú szinkronizálás)" + }, + "pref.generaloptions": { + "message": "Általános beállítások" + }, + "pref.useCalendarCache": { + "message": "Kapcsolat nélküli munka" + }, + "pref.useCardBook": { + "message": "CardBook címjegyzék létrehozása a Thunderbird alapértelmezett címjegyzéke helyett" + }, + "status.401": { + "message": "A hitelesítés nem sikerült, ellenőrizze a felhasználónevet és a jelszót." + }, + "status.403": { + "message": "A kiszolgáló elutasította a kapcsolatot (hozzáférés megtagadva)." + }, + "status.404": { + "message": "404-es HTTP hiba (a kért erőforrás nem található)." + }, + "status.500": { + "message": "Ismeretlen kiszolgálóhiba (500-as HTTP hiba)." + }, + "status.503": { + "message": "A szolgáltatás nem érhető el." + }, + "status.caldavservernotfound": { + "message": "Nem található CalDAV szerver." + }, + "status.carddavservernotfound": { + "message": "Nem található CardDAV-kiszolgáló." + }, + "status.gContactSync": { + "message": "A gContactSync nem kompatibilis a névjegycsoportok szinkronizálásával. Kapcsolja ki az egyiket, amíg a hiba meg nem oldódik." + }, + "status.info.restored": { + "message": "A részlegesen hiányzó írási jogosultságok miatt egyes műveleteket a kiszolgáló elutasított, és helyileg visszavont." + }, + "status.malformed-xml": { + "message": "Az XML feldolgozása nem sikerült. A részletekért ellenőrizze az eseménynaplót." + }, + "status.missing-permission": { + "message": "Hiányzó jogosultság: ##replace.1##" + }, + "status.networkerror": { + "message": "Nem sikerült kapcsolódni a kiszolgálóhoz." + }, + "status.rfc6764-lookup-failed": { + "message": "A(z) „##replace.1##” lekérdezése nem adta meg a CalDAV és CardDAV szolgáltatási végpontokkal kapcsolatos információkat. Adja meg a kiszolgálója gépnevét, hogy folytassa az automatikus beállítással." + }, + "status.service-discovery-failed": { + "message": "A(z) „##replace.1##” CalDAV és CardDAV szolgáltatási végpontjainak automatikus felfedezése sikertelen. Próbálja újra úgy, hogy egy másik kiszolgálócímet ad meg, vagy váltson az egyéni beállításra, és adja meg kézzel a szolgáltatási végpontokat." + }, + "status.softerror": { + "message": "Nem végzetes hiba (##replace.1##)" + }, + "status.success.managed-by-thunderbird": { + "message": "Ok" + }, + "status.non-carddav-addrbook": { + "message": "This is an older TbSync-CardDAV address book, which cannot be synced anymore. Disable and re-enable the address book, to create a fresh Thunderbird-CardDAV address book." + }, + "helplink.non-carddav-addrbook": { + "message": "https://github.com/jobisoft/DAV-4-TbSync/wiki/Since-Thunderbird-102,-TbSync-DAV-is-using-native-CardDAV-sync" + }, + "syncstate.eval.folders": { + "message": "Mappalista frissítése" + }, + "syncstate.eval.response.localchanges": { + "message": "A helyi változások elismerésének feldolgozása" + }, + "syncstate.eval.response.remotechanges": { + "message": "A távoli változtatások feldolgozása" + }, + "syncstate.prepare.request.localchanges": { + "message": "Helyi változtatások küldése" + }, + "syncstate.send.getfolders": { + "message": "Mappalista lekérése" + }, + "syncstate.send.request.localchanges": { + "message": "Várakozás a helyi változások elismerésére" + }, + "syncstate.send.request.remotechanges": { + "message": "Várakozás a távoli változtatásokra" + } +} diff --git a/_locales/it/messages.json b/_locales/it/messages.json new file mode 100644 index 0000000..7538f9b --- /dev/null +++ b/_locales/it/messages.json @@ -0,0 +1,347 @@ +{ + "abCard.ContactDetails": { + "message": "Proprietà del contatto (CardDAV)" + }, + "abCard.EmailAddresses": { + "message": "Indirizzi email:" + }, + "abCard.MiddleName": { + "message": "Secondo nome:" + }, + "abCard.Phone": { + "message": "Numeri di telefono" + }, + "abCard.PrefixName": { + "message": "Prefisso:" + }, + "abCard.SuffixName": { + "message": "Suffisso:" + }, + "abCard.emailtypes.car": { + "message": "auto" + }, + "abCard.emailtypes.cell": { + "message": "mobile" + }, + "abCard.emailtypes.description": { + "message": "Il primo indirizzo e-mail nell'elenco viene utilizzato come indirizzo di posta elettronica principale." + }, + "abCard.emailtypes.fax": { + "message": "fax" + }, + "abCard.emailtypes.home": { + "message": "privatamente" + }, + "abCard.emailtypes.other": { + "message": "altro" + }, + "abCard.emailtypes.pager": { + "message": "cercapersone" + }, + "abCard.emailtypes.video": { + "message": "video" + }, + "abCard.emailtypes.voice": { + "message": "telefono" + }, + "abCard.emailtypes.work": { + "message": "affari" + }, + "acl.add": { + "message": "inserisci" + }, + "acl.delete": { + "message": "elimina" + }, + "acl.modify": { + "message": "modificare" + }, + "acl.none": { + "message": "nessuna" + }, + "acl.readonly": { + "message": "sola lettura" + }, + "acl.readwrite": { + "message": "Scrivi permesso: ##replace.1##" + }, + "add.caldavserver": { + "message": "Indirizzo server CalDAV:" + }, + "add.carddavserver": { + "message": "Indirizzo server CardDAV:" + }, + "add.data.description": { + "message": "Fornisci un nome descrittivo per il nuovo account TbSync e le credenziali del server:" + }, + "add.data.notes": { + "message": "Note:" + }, + "add.data.title": { + "message": "Immetti informazioni account" + }, + "add.finish.description": { + "message": "?? The following settings have been verified successfully: ??" + }, + "add.finish.details": { + "message": "?? Click „Finish“ to create a new TbSync account with these settings. ??" + }, + "add.finish.title": { + "message": "?? Confirm account creation ??" + }, + "add.name": { + "message": "Nome account:" + }, + "add.ok": { + "message": "Aggiungi account" + }, + "add.password": { + "message": "Password:" + }, + "add.server": { + "message": "Indirizzo server:" + }, + "add.serverprofile.custom": { + "message": "Configurazione personalizzata" + }, + "add.serverprofile.custom.description": { + "message": "Gli endpoint servizio CalDAV e CardDAV possono essere configurati manualmente." + }, + "add.serverprofile.custom.details1": { + "message": "Gli endpoint servizio CalDAV e CardDAV richiesti o i cosiddetti indirizzi principali dovrebbero essere forniti dal tuo provider di servizi." + }, + "add.serverprofile.custom.details2": { + "message": "Se si lascia un indirizzo vuoto, il servizio corrispondente sarà disabilitato per quest'account." + }, + "add.serverprofile.description": { + "message": "Seleziona uno dei profili server disponibili:" + }, + "add.serverprofile.discovery": { + "message": "Configurazione automatica" + }, + "add.serverprofile.discovery.description": { + "message": "Molti provider di servizi e server supportano un processo di configurazione automatico che richiede solamente un indirizzo di posta elettronica o un nome utente e l'indirizzo del server." + }, + "add.serverprofile.discovery.details1": { + "message": "Per rilevare automaticamente gli endpoint servizio CalDAV e CardDAV, immetti le tue credenziali e il nome host del server (ad es. 'cloud.myserver.de')." + }, + "add.serverprofile.discovery.details2": { + "message": "Se il tuo nome utente è un indirizzo di posta elettronica, specificare il server diventa facoltativo in quanto le informazioni sugli endpoint servizio possono essere ottenute direttamente dal provider di servizi tramite una richiesta RFC6764 (se supportata)." + }, + "add.serverprofile.discovery.server-optional": { + "message": "facoltativo" + }, + "add.serverprofile.fruux": { + "message": "fruux" + }, + "add.serverprofile.fruux.description": { + "message": "fruux è un servizio che sincronizza contatti, calendari e attività. È alimentato dalla società dietro sabre/dav e ha sede in Germania." + }, + "add.serverprofile.gmx.com": { + "message": "GMX.com (Stati Uniti d'America)" + }, + "add.serverprofile.gmx.com.description": { + "message": "https://www.gmx.com" + }, + "add.serverprofile.gmx.net": { + "message": "GMX.net (Europa)" + }, + "add.serverprofile.gmx.net.description": { + "message": "https://www.gmx.net" + }, + "add.serverprofile.icloud": { + "message": "iCloud" + }, + "add.serverprofile.icloud.description": { + "message": "https://www.icloud.com" + }, + "add.serverprofile.icloud.details1": { + "message": "Il nome utente richiesto è il tuo ID Apple. Nota che non puoi utilizzare la password del tuo ID Apple qui. DEVI abilitare l'autenticazione a due fattori per il tuo account iCloud e creare una password specifica dell'app per TbSync." + }, + "add.serverprofile.icloud.details2": { + "message": "Questa è una misura di sicurezza imposta da Apple in modo che client di terze parti non ottengano l'accesso al tuo account Apple." + }, + "add.serverprofile.mbo": { + "message": "mailbox.org" + }, + "add.serverprofile.mbo.description": { + "message": "mailbox.org è un provider di posta elettronica tedesco sicuro per i clienti privati e business che offre anche calendari, contatti e archiviazione cloud." + }, + "add.serverprofile.posteo": { + "message": "Posteo" + }, + "add.serverprofile.posteo.description": { + "message": "https://www.posteo.de" + }, + "add.serverprofile.title": { + "message": "Seleziona un profilo server" + }, + "add.serverprofile.web.de": { + "message": "WEB.de" + }, + "add.serverprofile.web.de.description": { + "message": "https://www.web.de" + }, + "add.serverprofile.yahoo": { + "message": "Yahoo!" + }, + "add.serverprofile.yahoo.description": { + "message": "https://www.yahoo.com" + }, + "add.serverprofile.yahoo.details1": { + "message": "La password richiesta è una password specifica per l'app per TbSync che puoi creare nel tuo portale web Yahoo!. Non è la tua password standard di Yahoo." + }, + "add.spinner.query": { + "message": "Invio richiesta RFC6764 a '##replace.1##' in corso" + }, + "add.spinner.validating": { + "message": "Controlla la connessione al server" + }, + "add.title": { + "message": "Aggiunta account CalDAV e CardDAV a TbSync" + }, + "add.user": { + "message": "Nome utente:" + }, + "autocomplete.HOME": { + "message": "personale" + }, + "autocomplete.PREF": { + "message": "preferito" + }, + "autocomplete.WORK": { + "message": "lavoro" + }, + "config.custom": { + "message": "Configurazione server CalDAV & CardDAV" + }, + "defaultname.calendar": { + "message": "calendario" + }, + "defaultname.contacts": { + "message": "contatti" + }, + "extensionDescription": { + "message": "Aggiunge il supporto per la sincronizzazione degli account CalDAV e CardDAV a TbSync." + }, + "extensionName": { + "message": "Provider CalDAV e CardDAV" + }, + "helplink.malformed-xml": { + "message": "https://github.com/jobisoft/DAV-4-TbSync/issues/104" + }, + "manager.tabs.accountsettings": { + "message": "Impostazioni account" + }, + "manager.tabs.syncsettings": { + "message": "Opzioni" + }, + "menu.name": { + "message": "CalDAV e CardDAV" + }, + "pref.AccountName": { + "message": "Nome account" + }, + "pref.CalDavServer": { + "message": "Indirizzo server CalDAV:" + }, + "pref.CardDavServer": { + "message": "Indirizzo server CardDAV:" + }, + "pref.UserName": { + "message": "Nome utente" + }, + "pref.calendaroptions": { + "message": "Opzioni calendario" + }, + "pref.contactoptions": { + "message": "Opzioni contatto" + }, + "pref.downloadonly": { + "message": "Annulla modifiche locali (sincronizzazione a una via)" + }, + "pref.generaloptions": { + "message": "Opzioni generali" + }, + "pref.useCalendarCache": { + "message": "Supporto non in linea" + }, + "pref.useCardBook": { + "message": "Crea rubriche di CardBook al posto delle rubriche di default di Thunderbird" + }, + "status.401": { + "message": "Impossibile autenticarsi, controllare nome utente e password." + }, + "status.403": { + "message": "Il server ha rifiutato la connessione (vietato)." + }, + "status.404": { + "message": "Errore HTTP 404 (risorsa richiesta non trovata)." + }, + "status.500": { + "message": "Errore server sconosciuto (errore HTTP 500)." + }, + "status.503": { + "message": "Servizio non disponibile." + }, + "status.caldavservernotfound": { + "message": "Impossibile trovare un server CalDAV." + }, + "status.carddavservernotfound": { + "message": "Impossibile trovare un server CardDAV." + }, + "status.gContactSync": { + "message": "Il software è incompatibile con gContactSync con la sincronizzazione gruppi contatti attivata. Disattivarne uno finché l'errore non sarà risolto." + }, + "status.info.restored": { + "message": "A causa della mancanza di permessi di scrittura, alcune azioni sono state respinte dal server e annullate localmente." + }, + "status.malformed-xml": { + "message": "Impossibile analizzare XML. Controllare il registro eventi per i dettagli." + }, + "status.missing-permission": { + "message": "Permesso mancante: ##replace.1##" + }, + "status.networkerror": { + "message": "Impossibile connettersi al server." + }, + "status.rfc6764-lookup-failed": { + "message": "L'interrogazione di '##replace.1##' non ha fornito le informazioni richieste relative agli endpoint dei servizi CalDAV e CardDAV. Immettere il nome host del server per procedere con la configurazione automatica." + }, + "status.service-discovery-failed": { + "message": "L'individuazione automatica degli endpoint servizio CalDAV e CardDAV di '##replace.1##' non è riuscita. Riprova specificando un indirizzo server diverso o passa alla modalità configurazione personalizzata per specificare gli endpoint servizio manualmente." + }, + "status.softerror": { + "message": "Errore saltato (##replace.1##)" + }, + "status.success.managed-by-thunderbird": { + "message": "Ok" + }, + "status.non-carddav-addrbook": { + "message": "This is an older TbSync-CardDAV address book, which cannot be synced anymore. Disable and re-enable the address book, to create a fresh Thunderbird-CardDAV address book." + }, + "helplink.non-carddav-addrbook": { + "message": "https://github.com/jobisoft/DAV-4-TbSync/wiki/Since-Thunderbird-102,-TbSync-DAV-is-using-native-CardDAV-sync" + }, + "syncstate.eval.folders": { + "message": "Aggiornamento elenco cartelle in corso" + }, + "syncstate.eval.response.localchanges": { + "message": "Elaborazione riconoscimento modifiche locali in corso" + }, + "syncstate.eval.response.remotechanges": { + "message": "Elaborazione modifiche remote in corso" + }, + "syncstate.prepare.request.localchanges": { + "message": "Invio modifiche locali in corso" + }, + "syncstate.send.getfolders": { + "message": "Richiesta elenco cartelle in corso" + }, + "syncstate.send.request.localchanges": { + "message": "In attesa del riconoscimento delle modifiche locali" + }, + "syncstate.send.request.remotechanges": { + "message": "In attesa delle modifiche remote" + } +} diff --git a/_locales/ja/messages.json b/_locales/ja/messages.json new file mode 100644 index 0000000..61ee884 --- /dev/null +++ b/_locales/ja/messages.json @@ -0,0 +1,347 @@ +{ + "abCard.ContactDetails": { + "message": "Contact properties (CardDAV)" + }, + "abCard.EmailAddresses": { + "message": "E-mail addresses" + }, + "abCard.MiddleName": { + "message": "Middle:" + }, + "abCard.Phone": { + "message": "Phone numbers" + }, + "abCard.PrefixName": { + "message": "Prefix:" + }, + "abCard.SuffixName": { + "message": "Suffix:" + }, + "abCard.emailtypes.car": { + "message": "Car" + }, + "abCard.emailtypes.cell": { + "message": "Mobile" + }, + "abCard.emailtypes.description": { + "message": "The first e-mail address in the list will be used as the primary e-mail address." + }, + "abCard.emailtypes.fax": { + "message": "Fax" + }, + "abCard.emailtypes.home": { + "message": "Home" + }, + "abCard.emailtypes.other": { + "message": "Other" + }, + "abCard.emailtypes.pager": { + "message": "Pager" + }, + "abCard.emailtypes.video": { + "message": "Video" + }, + "abCard.emailtypes.voice": { + "message": "Phone" + }, + "abCard.emailtypes.work": { + "message": "Work" + }, + "acl.add": { + "message": "add" + }, + "acl.delete": { + "message": "delete" + }, + "acl.modify": { + "message": "modify" + }, + "acl.none": { + "message": "none" + }, + "acl.readonly": { + "message": "Read-only server access (revert local changes)" + }, + "acl.readwrite": { + "message": "Server write permissions: ##replace.1##" + }, + "add.caldavserver": { + "message": "CalDAV server address:" + }, + "add.carddavserver": { + "message": "CardDAV server address:" + }, + "add.data.description": { + "message": "Please provide a friendly name for the new TbSync account and the credentials for your server:" + }, + "add.data.notes": { + "message": "Notes:" + }, + "add.data.title": { + "message": "Enter account information" + }, + "add.finish.description": { + "message": "The following settings have been verified successfully:" + }, + "add.finish.details": { + "message": "Click “Finish” to create a new TbSync account with these settings." + }, + "add.finish.title": { + "message": "Confirm account creation" + }, + "add.name": { + "message": "Account name:" + }, + "add.ok": { + "message": "Add account" + }, + "add.password": { + "message": "Password:" + }, + "add.server": { + "message": "Server URL:" + }, + "add.serverprofile.custom": { + "message": "Manual Configuration" + }, + "add.serverprofile.custom.description": { + "message": "The CalDAV and CardDAV service endpoints can be configured manually." + }, + "add.serverprofile.custom.details1": { + "message": "The required CalDAV & CardDAV service endpoints or the so called principal addresses should be provided by your service provider." + }, + "add.serverprofile.custom.details2": { + "message": "If you leave either address empty, the corresponding service will be disabled for this account." + }, + "add.serverprofile.description": { + "message": "Please select one of the available server profiles:" + }, + "add.serverprofile.discovery": { + "message": "Automatic Configuration" + }, + "add.serverprofile.discovery.description": { + "message": "Many service providers and servers support an automatic configuration process, which requires only an e-mail address or a username and a server address." + }, + "add.serverprofile.discovery.details1": { + "message": "For the automatic discovery of the CalDAV & CardDAV service endpoints, please enter your credentials and the host name of your server (e.g. 'cloud.myserver.de')." + }, + "add.serverprofile.discovery.details2": { + "message": "If your username is an e-mail address, specifying the server becomes optional, as the information about the service endpoints may be obtained directly from your service provider via an RFC6764 request (if supported)." + }, + "add.serverprofile.discovery.server-optional": { + "message": "optional" + }, + "add.serverprofile.fruux": { + "message": "fruux" + }, + "add.serverprofile.fruux.description": { + "message": "fruux is a service that syncs contacts, calendars and tasks. It's powered by the company behind sabre/dav and is based in Germany." + }, + "add.serverprofile.gmx.com": { + "message": "GMX.com (USA)" + }, + "add.serverprofile.gmx.com.description": { + "message": "https://www.gmx.com" + }, + "add.serverprofile.gmx.net": { + "message": "GMX.net (Europe)" + }, + "add.serverprofile.gmx.net.description": { + "message": "https://www.gmx.net" + }, + "add.serverprofile.icloud": { + "message": "iCloud" + }, + "add.serverprofile.icloud.description": { + "message": "https://www.icloud.com" + }, + "add.serverprofile.icloud.details1": { + "message": "The requested user name is your Apple ID. Please note, that you may not use your Apple ID password here. You MUST enable two-factor authentication (2FA) for your iCloud account and create a separate app-specific password for TbSync." + }, + "add.serverprofile.icloud.details2": { + "message": "This is a security layer enforced by Apple, so that 3rd party clients do not gain access to your Apple account." + }, + "add.serverprofile.mbo": { + "message": "mailbox.org" + }, + "add.serverprofile.mbo.description": { + "message": "mailbox.org is a secure German e-mail provider for private and business customers, which also offers calendars, contacts and cloud storage." + }, + "add.serverprofile.posteo": { + "message": "Posteo" + }, + "add.serverprofile.posteo.description": { + "message": "https://www.posteo.de" + }, + "add.serverprofile.title": { + "message": "Select a server profile" + }, + "add.serverprofile.web.de": { + "message": "WEB.de" + }, + "add.serverprofile.web.de.description": { + "message": "https://www.web.de" + }, + "add.serverprofile.yahoo": { + "message": "Yahoo!" + }, + "add.serverprofile.yahoo.description": { + "message": "https://www.yahoo.com" + }, + "add.serverprofile.yahoo.details1": { + "message": "The requested password is an app-specific password for TbSync which you can create in your Yahoo! web portal. It is not your standard Yahoo! password." + }, + "add.spinner.query": { + "message": "Sending RFC6764 request to “##replace.1##”" + }, + "add.spinner.validating": { + "message": "Verifying connection to server" + }, + "add.title": { + "message": "Adding a CalDAV & CardDAV account to TbSync" + }, + "add.user": { + "message": "User name:" + }, + "autocomplete.HOME": { + "message": "private" + }, + "autocomplete.PREF": { + "message": "preferred" + }, + "autocomplete.WORK": { + "message": "business" + }, + "config.custom": { + "message": "CalDAV & CardDAV server configuration" + }, + "defaultname.calendar": { + "message": "Calendar" + }, + "defaultname.contacts": { + "message": "Contacts" + }, + "extensionDescription": { + "message": "Add sync support for CalDAV & CardDAV accounts to TbSync." + }, + "extensionName": { + "message": "Provider for CalDAV & CardDAV" + }, + "helplink.malformed-xml": { + "message": "https://github.com/jobisoft/DAV-4-TbSync/issues/104" + }, + "manager.tabs.accountsettings": { + "message": "Account settings" + }, + "manager.tabs.syncsettings": { + "message": "Options" + }, + "menu.name": { + "message": "CalDAV & CardDAV" + }, + "pref.AccountName": { + "message": "Account name" + }, + "pref.CalDavServer": { + "message": "CalDAV server address:" + }, + "pref.CardDavServer": { + "message": "CardDAV server address:" + }, + "pref.UserName": { + "message": "User name" + }, + "pref.calendaroptions": { + "message": "Calendar options" + }, + "pref.contactoptions": { + "message": "Contact options" + }, + "pref.downloadonly": { + "message": "Revert local changes (one-way sync)" + }, + "pref.generaloptions": { + "message": "General options" + }, + "pref.useCalendarCache": { + "message": "Offline Support" + }, + "pref.useCardBook": { + "message": "Create CardBook address books instead of Thunderbird's default address books" + }, + "status.401": { + "message": "Could not authenticate, check username and password." + }, + "status.403": { + "message": "Server rejected connection (forbidden)." + }, + "status.404": { + "message": "HTTP Error 404 (requested resource not found)." + }, + "status.500": { + "message": "Unknown Server Error (HTTP Error 500)." + }, + "status.503": { + "message": "Service unavailable." + }, + "status.caldavservernotfound": { + "message": "Could not find a CalDAV server." + }, + "status.carddavservernotfound": { + "message": "Could not find a CardDAV server." + }, + "status.gContactSync": { + "message": "There is an incompatibility with gContactSync with activated contact group synchronization. Please deactivate one of them as long as the error is not resolved." + }, + "status.info.restored": { + "message": "Due to partially missing write permissions, some actions were rejected by the server and reversed locally." + }, + "status.malformed-xml": { + "message": "Could not parse XML. Check event log for details." + }, + "status.missing-permission": { + "message": "Missing permission: ##replace.1##" + }, + "status.networkerror": { + "message": "Could not connect to server." + }, + "status.rfc6764-lookup-failed": { + "message": "The query of “##replace.1##” did not provide the required information regarding the CalDAV and CardDAV service endpoints. Please enter the hostname of your server to proceed with the automatic configuration." + }, + "status.service-discovery-failed": { + "message": "Automatic discovery of CalDAV & CardDAV service endpoints of “##replace.1##” has failed. Try again, specifying a different server address, or switch to the custom configuration to manually specify the service endpoints." + }, + "status.softerror": { + "message": "Non fatal error (##replace.1##)" + }, + "status.success.managed-by-thunderbird": { + "message": "Ok" + }, + "status.non-carddav-addrbook": { + "message": "This is an older TbSync-CardDAV address book, which cannot be synced anymore. Disable and re-enable the address book, to create a fresh Thunderbird-CardDAV address book." + }, + "helplink.non-carddav-addrbook": { + "message": "https://github.com/jobisoft/DAV-4-TbSync/wiki/Since-Thunderbird-102,-TbSync-DAV-is-using-native-CardDAV-sync" + }, + "syncstate.eval.folders": { + "message": "Updating folder list" + }, + "syncstate.eval.response.localchanges": { + "message": "Processing acknowledgment of local changes" + }, + "syncstate.eval.response.remotechanges": { + "message": "Processing remote changes" + }, + "syncstate.prepare.request.localchanges": { + "message": "Sending local changes" + }, + "syncstate.send.getfolders": { + "message": "Requesting folder list" + }, + "syncstate.send.request.localchanges": { + "message": "Waiting for acknowledgment of local changes" + }, + "syncstate.send.request.remotechanges": { + "message": "Waiting for remote changes" + } +} diff --git a/_locales/pl/messages.json b/_locales/pl/messages.json new file mode 100644 index 0000000..52fe8f0 --- /dev/null +++ b/_locales/pl/messages.json @@ -0,0 +1,347 @@ +{ + "abCard.ContactDetails": { + "message": "Właściwości kontaktu (CardDAV)" + }, + "abCard.EmailAddresses": { + "message": "Adresy e-mail" + }, + "abCard.MiddleName": { + "message": "Drugie imię:" + }, + "abCard.Phone": { + "message": "Numery telefonów" + }, + "abCard.PrefixName": { + "message": "Przedrostek:" + }, + "abCard.SuffixName": { + "message": "Przyrostek:" + }, + "abCard.emailtypes.car": { + "message": "Pojazd" + }, + "abCard.emailtypes.cell": { + "message": "Komórka" + }, + "abCard.emailtypes.description": { + "message": "Pierwszy adres e-mail z listy będzie używany jako podstawowy adres e-mail." + }, + "abCard.emailtypes.fax": { + "message": "Faks" + }, + "abCard.emailtypes.home": { + "message": "Dom" + }, + "abCard.emailtypes.other": { + "message": "Inny" + }, + "abCard.emailtypes.pager": { + "message": "Pager" + }, + "abCard.emailtypes.video": { + "message": "Film" + }, + "abCard.emailtypes.voice": { + "message": "Telefon" + }, + "abCard.emailtypes.work": { + "message": "Praca" + }, + "acl.add": { + "message": "dodaj" + }, + "acl.delete": { + "message": "usuń" + }, + "acl.modify": { + "message": "zmień" + }, + "acl.none": { + "message": "żaden" + }, + "acl.readonly": { + "message": "Dostęp do serwera tylko do odczytu (cofnij zmiany lokalne)" + }, + "acl.readwrite": { + "message": "Uprawnienia do zapisu na serwerze: ##replace.1##" + }, + "add.caldavserver": { + "message": "Adres serwera CalDAV:" + }, + "add.carddavserver": { + "message": "Adres serwera CardDAV:" + }, + "add.data.description": { + "message": "Podaj przyjazną nazwę nowego konta TbSync i dane logowania dla Twojego serwera:" + }, + "add.data.notes": { + "message": "Notatki:" + }, + "add.data.title": { + "message": "Wprowadź informacje konta" + }, + "add.finish.description": { + "message": "Następujące ustawienia zostały pomyślnie zweryfikowane:" + }, + "add.finish.details": { + "message": "Kliknij \"Zakończ\", aby utworzyć nowe konto TbSync z tymi ustawieniami." + }, + "add.finish.title": { + "message": "Potwierdź utworzenie konta" + }, + "add.name": { + "message": "Nazwa konta:" + }, + "add.ok": { + "message": "Dodaj konto" + }, + "add.password": { + "message": "Hasło:" + }, + "add.server": { + "message": "URL serwera:" + }, + "add.serverprofile.custom": { + "message": "Ręczna Konfiguracja" + }, + "add.serverprofile.custom.description": { + "message": "Punkty końcowe usługi CalDAV i CardDAV można skonfigurować ręcznie." + }, + "add.serverprofile.custom.details1": { + "message": "Wymagane punkty końcowe usługi CalDAV i CardDAV lub tak zwane adresy główne powinien podać dostawca usługi." + }, + "add.serverprofile.custom.details2": { + "message": "Jeśli którykolwiek z adresów pozostanie pusty, odpowiednia usługa zostanie wyłączona dla tego konta." + }, + "add.serverprofile.description": { + "message": "Wybierz jeden z dostępnych profili serwera:" + }, + "add.serverprofile.discovery": { + "message": "Automatyczna Konfiguracja" + }, + "add.serverprofile.discovery.description": { + "message": "Wielu dostawców usług i serwerów wspiera proces automatycznej konfiguracji, który wymaga tylko adresu e-mail lub nazwy użytkownika i adresu serwera." + }, + "add.serverprofile.discovery.details1": { + "message": "Dla automatycznego wykrywania punktów końcowych usługi CalDAV i CardDAV, wprowadź swoje poświadczenia i nazwę hosta swojego serwera (np. 'cloud.myserver.de')." + }, + "add.serverprofile.discovery.details2": { + "message": "Jeśli nazwa użytkownika to adres e-mail, określenie serwera staje się opcjonalne, ponieważ informacje o punktach końcowych usługi można uzyskać bezpośrednio od usługodawcy za pośrednictwem żądania RFC6764 (jeśli jest obsługiwane)." + }, + "add.serverprofile.discovery.server-optional": { + "message": "opcjonalnie" + }, + "add.serverprofile.fruux": { + "message": "fruux" + }, + "add.serverprofile.fruux.description": { + "message": "fruux to usługa synchronizująca kontakty, kalendarze i zadania. Jest napędzana przez firmę opartą o sabre/dav i z siedzibą w Niemczech." + }, + "add.serverprofile.gmx.com": { + "message": "GMX.com (USA)" + }, + "add.serverprofile.gmx.com.description": { + "message": "https://www.gmx.com" + }, + "add.serverprofile.gmx.net": { + "message": "GMX.net (Europe)" + }, + "add.serverprofile.gmx.net.description": { + "message": "https://www.gmx.net" + }, + "add.serverprofile.icloud": { + "message": "iCloud" + }, + "add.serverprofile.icloud.description": { + "message": "https://www.icloud.com" + }, + "add.serverprofile.icloud.details1": { + "message": "Żądana nazwa użytkownika to Twój Apple ID. Pamiętaj, że nie możesz tutaj używać hasła Apple ID. MUSISZ włączyć uwierzytelnianie dwuskładnikowe (2FA) dla swojego konta iCloud i utworzyć osobne hasło aplikacji dla TbSync." + }, + "add.serverprofile.icloud.details2": { + "message": "Jest to warstwa bezpieczeństwa wymuszona przez Apple, aby klienci zewnętrzni nie uzyskali dostępu do Twojego konta Apple." + }, + "add.serverprofile.mbo": { + "message": "mailbox.org" + }, + "add.serverprofile.mbo.description": { + "message": "mailbox.org to bezpieczny, niemiecki dostawca poczty elektronicznej dla klientów prywatnych i biznesowych, który oferuje również kalendarze, kontakty i przechowywanie w chmurze." + }, + "add.serverprofile.posteo": { + "message": "Posteo" + }, + "add.serverprofile.posteo.description": { + "message": "https://www.posteo.de" + }, + "add.serverprofile.title": { + "message": "Wybierz profil serwera" + }, + "add.serverprofile.web.de": { + "message": "WEB.de" + }, + "add.serverprofile.web.de.description": { + "message": "https://www.web.de" + }, + "add.serverprofile.yahoo": { + "message": "Yahoo!" + }, + "add.serverprofile.yahoo.description": { + "message": "https://www.yahoo.com" + }, + "add.serverprofile.yahoo.details1": { + "message": "Żądane hasło jest hasłem specyficznym dla aplikacji dla TbSync, które możesz utworzyć w portalu Yahoo! To nie jest Twoje standardowe hasło Yahoo!" + }, + "add.spinner.query": { + "message": "Wysyłanie żądania RFC6764 do “##replace.1##”" + }, + "add.spinner.validating": { + "message": "Weryfikowanie połączenia z serwerem" + }, + "add.title": { + "message": "Dodawanie konta CalDAV i CardDAV do TbSync" + }, + "add.user": { + "message": "Nazwa użytkownika:" + }, + "autocomplete.HOME": { + "message": "prywatny" + }, + "autocomplete.PREF": { + "message": "preferowany" + }, + "autocomplete.WORK": { + "message": "służbowy" + }, + "config.custom": { + "message": "Konfiguracja serwera CalDAV i CardDAV" + }, + "defaultname.calendar": { + "message": "Kalendarz" + }, + "defaultname.contacts": { + "message": "Kontakty" + }, + "extensionDescription": { + "message": "Dodaj do TbSync wsparcie synchronizacji dla CalDAV i CardDAV." + }, + "extensionName": { + "message": "Dostawca dla CalDAV i CardDAV" + }, + "helplink.malformed-xml": { + "message": "https://github.com/jobisoft/DAV-4-TbSync/issues/104" + }, + "manager.tabs.accountsettings": { + "message": "Ustawienia konta" + }, + "manager.tabs.syncsettings": { + "message": "Opcje" + }, + "menu.name": { + "message": "CalDAV i CardDAV" + }, + "pref.AccountName": { + "message": "Nazwa konta" + }, + "pref.CalDavServer": { + "message": "Adres serwera CalDAV:" + }, + "pref.CardDavServer": { + "message": "Adres serwera CardDAV:" + }, + "pref.UserName": { + "message": "Nazwa użytkownika" + }, + "pref.calendaroptions": { + "message": "Opcje kalendarza" + }, + "pref.contactoptions": { + "message": "Opcje kontaktu" + }, + "pref.downloadonly": { + "message": "Cofnij zmiany lokalne (synchronizacja jednokierunkowa)" + }, + "pref.generaloptions": { + "message": "Opcje ogólne" + }, + "pref.useCalendarCache": { + "message": "Wsparcie Offline" + }, + "pref.useCardBook": { + "message": "Utwórz książki adresowe CardBook zamiast domyślnych książek adresowych Thunderbirda" + }, + "status.401": { + "message": "Nie można uwierzytelnić, sprawdź nazwę użytkownika i hasło." + }, + "status.403": { + "message": "Serwer odrzucił połączenie (zabronione)." + }, + "status.404": { + "message": "Błąd HTTP 404 (nie znaleziono żądanego zasobu)." + }, + "status.500": { + "message": "Nieznany błąd serwera (błąd HTTP 500)." + }, + "status.503": { + "message": "Usługa niedostępna." + }, + "status.caldavservernotfound": { + "message": "Nie można znaleźć serwera CalDAV." + }, + "status.carddavservernotfound": { + "message": "Nie można znaleźć serwera CardDAV." + }, + "status.gContactSync": { + "message": "Występuje niezgodność z gContactSync z aktywowaną synchronizacją grupy kontaktów. Dezaktywuj jeden z nich, dopóki błąd nie zostanie rozwiązany." + }, + "status.info.restored": { + "message": "Z powodu częściowo brakujących uprawnień do zapisu, niektóre działania zostały odrzucone przez serwer i cofnięte lokalnie." + }, + "status.malformed-xml": { + "message": "Nie można przeanalizować XML. Sprawdź dziennik zdarzeń, aby uzyskać szczegółowe informacje." + }, + "status.missing-permission": { + "message": "Brak uprawnienia: ##replace.1##" + }, + "status.networkerror": { + "message": "Nie można połączyć z serwerem." + }, + "status.rfc6764-lookup-failed": { + "message": "Zapytanie “##replace.1##” nie dostarczyło wymaganych informacji dotyczących punktów końcowych usługi CalDAV i CardDAV. Wprowadź nazwę hosta swojego serwera, aby kontynuować automatyczną konfigurację." + }, + "status.service-discovery-failed": { + "message": "Automatyczne wykrywanie punktów końcowych usługi CalDAV i CardDAV dla “##replace.1##” nie powiodło się. Spróbuj ponownie, podając inny adres serwera lub przejdź do konfiguracji niestandardowej, aby ręcznie określić punkty końcowe usługi." + }, + "status.softerror": { + "message": "Błąd niekrytyczny (##replace.1##)" + }, + "status.success.managed-by-thunderbird": { + "message": "Ok" + }, + "status.non-carddav-addrbook": { + "message": "To jest starsza książka adresowa TbSync-CardDAV, która nie może być już synchronizowana. Wyłącz i ponownie włącz książkę adresową, aby stworzyć nową książkę adresową Thunderbird-CardDAV." + }, + "helplink.non-carddav-addrbook": { + "message": "https://github.com/jobisoft/DAV-4-TbSync/wiki/Since-Thunderbird-102,-TbSync-DAV-is-using-native-CardDAV-sync" + }, + "syncstate.eval.folders": { + "message": "Aktualizowanie listy folderów" + }, + "syncstate.eval.response.localchanges": { + "message": "Przetwarzanie potwierdzenia lokalnych zmian" + }, + "syncstate.eval.response.remotechanges": { + "message": "Przetwarzanie zdalnych zmian" + }, + "syncstate.prepare.request.localchanges": { + "message": "Wysyłanie zmian lokalnych" + }, + "syncstate.send.getfolders": { + "message": "Żądanie listy folderów" + }, + "syncstate.send.request.localchanges": { + "message": "Oczekiwanie na potwierdzenie lokalnych zmian" + }, + "syncstate.send.request.remotechanges": { + "message": "Oczekiwanie na zdalne zmiany" + } +} diff --git a/_locales/pt_BR/messages.json b/_locales/pt_BR/messages.json new file mode 100644 index 0000000..70a7b9c --- /dev/null +++ b/_locales/pt_BR/messages.json @@ -0,0 +1,347 @@ +{ + "abCard.ContactDetails": { + "message": "Propriedades de contato (CardDAV)" + }, + "abCard.EmailAddresses": { + "message": "Endereço de e-mail:" + }, + "abCard.MiddleName": { + "message": "Nome do meio:" + }, + "abCard.Phone": { + "message": "Números de telefone" + }, + "abCard.PrefixName": { + "message": "Prefixo:" + }, + "abCard.SuffixName": { + "message": "Sufixo:" + }, + "abCard.emailtypes.car": { + "message": "carro" + }, + "abCard.emailtypes.cell": { + "message": "móvel" + }, + "abCard.emailtypes.description": { + "message": "O primeiro endereço de email na lista é usado como o endereço de email principal." + }, + "abCard.emailtypes.fax": { + "message": "fax" + }, + "abCard.emailtypes.home": { + "message": "privadamente" + }, + "abCard.emailtypes.other": { + "message": "outro" + }, + "abCard.emailtypes.pager": { + "message": "pager" + }, + "abCard.emailtypes.video": { + "message": "vídeo" + }, + "abCard.emailtypes.voice": { + "message": "telefone" + }, + "abCard.emailtypes.work": { + "message": "negócio" + }, + "acl.add": { + "message": "adicionar" + }, + "acl.delete": { + "message": "excluir" + }, + "acl.modify": { + "message": "modificar" + }, + "acl.none": { + "message": "nenhum" + }, + "acl.readonly": { + "message": "Acesso ao servidor somente leitura (reverter alterações locais)" + }, + "acl.readwrite": { + "message": "Permissões de gravação do servidor: ##replace.1##" + }, + "add.caldavserver": { + "message": "Endereço servidor CalDAV:" + }, + "add.carddavserver": { + "message": "Endereço servidor CardDAV:" + }, + "add.data.description": { + "message": "Por favor, forneça um nome amigável para a nova conta TbSync e as credenciais para o seu servidor:" + }, + "add.data.notes": { + "message": "Notas:" + }, + "add.data.title": { + "message": "Digite as informações da conta" + }, + "add.finish.description": { + "message": "?? The following settings have been verified successfully: ??" + }, + "add.finish.details": { + "message": "?? Click „Finish“ to create a new TbSync account with these settings. ??" + }, + "add.finish.title": { + "message": "?? Confirm account creation ??" + }, + "add.name": { + "message": "Nome da conta:" + }, + "add.ok": { + "message": "Adicionar conta" + }, + "add.password": { + "message": "Senha:" + }, + "add.server": { + "message": "URL do servidor:" + }, + "add.serverprofile.custom": { + "message": "Configuração manual" + }, + "add.serverprofile.custom.description": { + "message": "Os serviços de CalDAV e CardDAV podem ser configurados manualmente." + }, + "add.serverprofile.custom.details1": { + "message": "Os serviços de CalDAV e CardDAV necessários ou endereços principais devem ser fornecidos pelo seu provedor de serviços." + }, + "add.serverprofile.custom.details2": { + "message": "Se você deixar o endereço vazio, o serviço correspondente será desativado para essa conta." + }, + "add.serverprofile.description": { + "message": "Por favor, selecione um dos perfis de servidor disponíveis:" + }, + "add.serverprofile.discovery": { + "message": "Configuração Automática" + }, + "add.serverprofile.discovery.description": { + "message": "Muitos provedores de serviço e servidores suportam um processo de configuração automática, que requer apenas um endereço de e-mail ou um nome de usuário e um endereço de servidor." + }, + "add.serverprofile.discovery.details1": { + "message": "Para a descoberta automática dos pontos de extremidade do serviço CalDAV & CardDAV, por favor insira suas credenciais e o nome do host do seu servidor (ex.: 'cloud.myserver.de')." + }, + "add.serverprofile.discovery.details2": { + "message": "Se seu nome de usuário é um endereço de e-mail, especificar o servidor torna-se opcional, como as informações sobre os pontos de extremidade do serviço podem ser obtidas diretamente do seu provedor de serviços através de um pedido RFC6764 (se suportado)." + }, + "add.serverprofile.discovery.server-optional": { + "message": "opcional" + }, + "add.serverprofile.fruux": { + "message": "fruux" + }, + "add.serverprofile.fruux.description": { + "message": "fruux é um serviço que sincroniza contatos, calendários e tarefas. É alimentado pela empresa por trás de saber/dav e é baseado na Alemanha." + }, + "add.serverprofile.gmx.com": { + "message": "GMX.com (E.U.A.)" + }, + "add.serverprofile.gmx.com.description": { + "message": "https://www.gmx.com" + }, + "add.serverprofile.gmx.net": { + "message": "GMX.net (Europa)" + }, + "add.serverprofile.gmx.net.description": { + "message": "https://www.gmx.net" + }, + "add.serverprofile.icloud": { + "message": "iCloud" + }, + "add.serverprofile.icloud.description": { + "message": "https://www.icloud.com" + }, + "add.serverprofile.icloud.details1": { + "message": "O nome do usuário solicitado é seu ID da Apple. Por favor, note que você não pode usar sua senha da Apple ID aqui. Você DEVE ativar a autenticação de dois fatores (2FA) para sua conta do iCloud e criar uma senha específica do aplicativo separada para o TbSync." + }, + "add.serverprofile.icloud.details2": { + "message": "Essa é uma camada de segurança imposta pela Apple, portanto, os clientes de terceiros não obtêm acesso à sua conta da Apple." + }, + "add.serverprofile.mbo": { + "message": "mailbox.org" + }, + "add.serverprofile.mbo.description": { + "message": "mailbox.org é um provedor de e-mail alemão seguro para uso pessoal e para negócios, que também oferece calendários, contatos e armazenamento na nuvem." + }, + "add.serverprofile.posteo": { + "message": "Posteo" + }, + "add.serverprofile.posteo.description": { + "message": "https://www.posteo.de" + }, + "add.serverprofile.title": { + "message": "Selecione um perfil do servidor" + }, + "add.serverprofile.web.de": { + "message": "WEB.de" + }, + "add.serverprofile.web.de.description": { + "message": "https://www.web.de" + }, + "add.serverprofile.yahoo": { + "message": "Yahoo!" + }, + "add.serverprofile.yahoo.description": { + "message": "https://www.yahoo.com" + }, + "add.serverprofile.yahoo.details1": { + "message": "A senha solicitada é uma senha específica do aplicativo para TbSync, que você pode criar no seu portal web do Yahoo!. Não é a sua senha padrão do Yahoo!." + }, + "add.spinner.query": { + "message": "Enviando pedido RFC6764 para '##replace.1##'" + }, + "add.spinner.validating": { + "message": "Verifique a conexão com o servidor" + }, + "add.title": { + "message": "Adicionando uma conta CalDAV e CardDAV para o TbSync" + }, + "add.user": { + "message": "Usuário:" + }, + "autocomplete.HOME": { + "message": "particular" + }, + "autocomplete.PREF": { + "message": "preferido" + }, + "autocomplete.WORK": { + "message": "empresa" + }, + "config.custom": { + "message": "Configurações do servidor CalDAV & CardDAV" + }, + "defaultname.calendar": { + "message": "calendário" + }, + "defaultname.contacts": { + "message": "contatos" + }, + "extensionDescription": { + "message": "Adiciona suporte para sincronizar contas CalDAV e CardDAV com o TbSync." + }, + "extensionName": { + "message": "Provedor CalDAV e CardDAV" + }, + "helplink.malformed-xml": { + "message": "https://github.com/jobisoft/DAV-4-TbSync/issues/104" + }, + "manager.tabs.accountsettings": { + "message": "Configurações da conta" + }, + "manager.tabs.syncsettings": { + "message": "Opções" + }, + "menu.name": { + "message": "CalDAV e CardDAV" + }, + "pref.AccountName": { + "message": "Nome da conta" + }, + "pref.CalDavServer": { + "message": "Endereço servidor CalDAV:" + }, + "pref.CardDavServer": { + "message": "Endereço servidor CardDAV:" + }, + "pref.UserName": { + "message": "Usuário" + }, + "pref.calendaroptions": { + "message": "Opções de calendário" + }, + "pref.contactoptions": { + "message": "Opções de contato" + }, + "pref.downloadonly": { + "message": "Reverter alterações locais (sincronização unidirecional)" + }, + "pref.generaloptions": { + "message": "Opções Gerais" + }, + "pref.useCalendarCache": { + "message": "Suporte Offline" + }, + "pref.useCardBook": { + "message": "Criar catálogo de endereços no CardBook em vez do catálogo de endereços padrão do Thunderbird" + }, + "status.401": { + "message": "Não foi possível autenticar, verifique o nome de usuário e a senha." + }, + "status.403": { + "message": "Conexão rejeitada pelo servidor (proibida)." + }, + "status.404": { + "message": "HTTP Erro 404 (recurso solicitado não encontrado)." + }, + "status.500": { + "message": "Erro de servidor desconhecido (HTTP Erro 500)." + }, + "status.503": { + "message": "Serviço indisponível." + }, + "status.caldavservernotfound": { + "message": "Não foi possível encontrar um servidor CalDAV." + }, + "status.carddavservernotfound": { + "message": "Não foi possível encontrar um servidor CardDAV." + }, + "status.gContactSync": { + "message": "Existe uma incompatibilidade com o gContactSync com a sincronização de grupo de contatos ativado. Desative um deles, desde até que o erro não seja resolvido." + }, + "status.info.restored": { + "message": "Devido à falta parcial de permissões de gravação, algumas ações foram rejeitadas pelo servidor e revertidas localmente." + }, + "status.malformed-xml": { + "message": "Não foi possível analisar XML. Verifique o log de eventos para obter detalhes." + }, + "status.missing-permission": { + "message": "Permissão ausente: ##replace.1##" + }, + "status.networkerror": { + "message": "Não foi possível conectar-se ao servidor." + }, + "status.rfc6764-lookup-failed": { + "message": "A consulta de '##replace.1##' não forneceu as informações necessárias sobre os serviços CalDAV e CardDAV. Por favor, digite o nome do host do seu servidor para prosseguir com a configuração automática." + }, + "status.service-discovery-failed": { + "message": "A descoberta automática dos serviços de CalDAV & CardDAV de '##replace.1##' falhou. Tente novamente, especificando um endereço de servidor diferente ou altere para a configuração personalizada para especificar manualmente as configurações." + }, + "status.softerror": { + "message": "Erro não fatal (##replace.1##)" + }, + "status.success.managed-by-thunderbird": { + "message": "Ok" + }, + "status.non-carddav-addrbook": { + "message": "This is an older TbSync-CardDAV address book, which cannot be synced anymore. Disable and re-enable the address book, to create a fresh Thunderbird-CardDAV address book." + }, + "helplink.non-carddav-addrbook": { + "message": "https://github.com/jobisoft/DAV-4-TbSync/wiki/Since-Thunderbird-102,-TbSync-DAV-is-using-native-CardDAV-sync" + }, + "syncstate.eval.folders": { + "message": "Atualizando lista de pastas" + }, + "syncstate.eval.response.localchanges": { + "message": "Processando reconhecimento de alterações locais" + }, + "syncstate.eval.response.remotechanges": { + "message": "Processando alterações remotas" + }, + "syncstate.prepare.request.localchanges": { + "message": "Enviando alterações locais" + }, + "syncstate.send.getfolders": { + "message": "Solicitando lista de pastas" + }, + "syncstate.send.request.localchanges": { + "message": "Aguardando confirmação de alterações locais" + }, + "syncstate.send.request.remotechanges": { + "message": "Esperando por alterações remotas" + } +} diff --git a/_locales/ro/messages.json b/_locales/ro/messages.json new file mode 100644 index 0000000..f9cea8e --- /dev/null +++ b/_locales/ro/messages.json @@ -0,0 +1,347 @@ +{ + "abCard.ContactDetails": { + "message": "Proprietăți de contact (CardDAV)" + }, + "abCard.EmailAddresses": { + "message": "Adresele de poştă electronică" + }, + "abCard.MiddleName": { + "message": "Mijlociu:" + }, + "abCard.Phone": { + "message": "Numere de telefon" + }, + "abCard.PrefixName": { + "message": "Prefix:" + }, + "abCard.SuffixName": { + "message": "Sufix:" + }, + "abCard.emailtypes.car": { + "message": "Maşină" + }, + "abCard.emailtypes.cell": { + "message": "Mobil" + }, + "abCard.emailtypes.description": { + "message": "Prima adresă de e-mail din listă va fi utilizată ca adresă de e-mail principală." + }, + "abCard.emailtypes.fax": { + "message": "Fax" + }, + "abCard.emailtypes.home": { + "message": "Acasă" + }, + "abCard.emailtypes.other": { + "message": "Altele" + }, + "abCard.emailtypes.pager": { + "message": "Pager" + }, + "abCard.emailtypes.video": { + "message": "Videoclip" + }, + "abCard.emailtypes.voice": { + "message": "Telefon" + }, + "abCard.emailtypes.work": { + "message": "Lucru" + }, + "acl.add": { + "message": "adaugă" + }, + "acl.delete": { + "message": "șterge" + }, + "acl.modify": { + "message": "modifică" + }, + "acl.none": { + "message": "niciuna" + }, + "acl.readonly": { + "message": "Acces doar pentru citire pe server (revenire la modificările locale)" + }, + "acl.readwrite": { + "message": "Permisiuni de scriere pe server: ##replace.1##" + }, + "add.caldavserver": { + "message": "Adresa serverului CalDAV:" + }, + "add.carddavserver": { + "message": "Adresa serverului CardDAV:" + }, + "add.data.description": { + "message": "Vă rugăm să furnizați un nume prietenos pentru noul cont TbSync și acreditările pentru serverul dumneavoastră:" + }, + "add.data.notes": { + "message": "Note:" + }, + "add.data.title": { + "message": "Introduceți informații despre cont" + }, + "add.finish.description": { + "message": "Următoarele setări au fost verificate cu succes:" + }, + "add.finish.details": { + "message": "Faceți clic pe \" Terminare \" pentru a crea un nou cont TbSync cu aceste setări." + }, + "add.finish.title": { + "message": "Confirmați crearea contului" + }, + "add.name": { + "message": "Numele contului:" + }, + "add.ok": { + "message": "Adăugaţi un cont" + }, + "add.password": { + "message": "Parolă:" + }, + "add.server": { + "message": "URL-ul serverului:" + }, + "add.serverprofile.custom": { + "message": "Configurație manuală" + }, + "add.serverprofile.custom.description": { + "message": "Punctele finale ale serviciilor CalDAV și CardDAV pot fi configurate manual." + }, + "add.serverprofile.custom.details1": { + "message": "Punctele finale necesare ale serviciilor CalDAV și CardDAV sau așa-numitele adrese principale ar trebui să fie furnizate de furnizorul dumneavoastră de servicii." + }, + "add.serverprofile.custom.details2": { + "message": "Dacă lăsați oricare dintre adrese goală, serviciul corespunzător va fi dezactivat pentru acest cont." + }, + "add.serverprofile.description": { + "message": "Vă rugăm să selectați unul dintre profilurile de server disponibile:" + }, + "add.serverprofile.discovery": { + "message": "Configurație automată" + }, + "add.serverprofile.discovery.description": { + "message": "Mulți furnizori de servicii și servere acceptă un proces de configurare automată, care necesită doar o adresă de e-mail sau un nume de utilizator și o adresă de server." + }, + "add.serverprofile.discovery.details1": { + "message": "Pentru descoperirea automată a punctelor finale ale serviciului CalDAV & CardDAV, vă rugăm să introduceți acreditările dumneavoastră și numele de gazdă al serverului dumneavoastră (de exemplu, \"cloud.myserver.ro\")." + }, + "add.serverprofile.discovery.details2": { + "message": "În cazul în care numele de utilizator este o adresă de e-mail, specificarea serverului devine opțională, deoarece informațiile despre punctele finale ale serviciului pot fi obținute direct de la furnizorul de servicii prin intermediul unei cereri RFC6764 (dacă este acceptată)." + }, + "add.serverprofile.discovery.server-optional": { + "message": "optional" + }, + "add.serverprofile.fruux": { + "message": "fruux" + }, + "add.serverprofile.fruux.description": { + "message": "fruux este un serviciu care sincronizează contactele, calendarele și sarcinile. Este pus la dispoziție de compania din spatele sabre/dav și are sediul în Germania." + }, + "add.serverprofile.gmx.com": { + "message": "GMX.com (SUA)" + }, + "add.serverprofile.gmx.com.description": { + "message": "https://www.gmx.com" + }, + "add.serverprofile.gmx.net": { + "message": "GMX.net (Europa)" + }, + "add.serverprofile.gmx.net.description": { + "message": "https://www.gmx.net" + }, + "add.serverprofile.icloud": { + "message": "iCloud" + }, + "add.serverprofile.icloud.description": { + "message": "https://www.icloud.com" + }, + "add.serverprofile.icloud.details1": { + "message": "Numele de utilizator solicitat este ID-ul dumneavoastră Apple. Vă rugăm să rețineți că nu puteți utiliza aici parola ID-ului Apple. TREBUIE să activați autentificarea cu doi factori (2FA) pentru contul dumneavoastră iCloud și să creați o parolă separată specifică aplicației pentru TbSync." + }, + "add.serverprofile.icloud.details2": { + "message": "Acesta este un nivel de securitate impus de Apple, astfel încât clienții terți să nu obțină acces la contul dumneavoastră Apple." + }, + "add.serverprofile.mbo": { + "message": "mailbox.org" + }, + "add.serverprofile.mbo.description": { + "message": "mailbox.org este un furnizor german de e-mail securizat pentru clienți privați și companii, care oferă, de asemenea, calendare, contacte și stocare în cloud." + }, + "add.serverprofile.posteo": { + "message": "Posteo" + }, + "add.serverprofile.posteo.description": { + "message": "https://www.posteo.de" + }, + "add.serverprofile.title": { + "message": "Selectați un profil de server" + }, + "add.serverprofile.web.de": { + "message": "WEB.de" + }, + "add.serverprofile.web.de.description": { + "message": "https://www.web.de" + }, + "add.serverprofile.yahoo": { + "message": "Yahoo!" + }, + "add.serverprofile.yahoo.description": { + "message": "https://www.yahoo.com" + }, + "add.serverprofile.yahoo.details1": { + "message": "Parola solicitată este o parolă specifică aplicației TbSync, pe care o puteți crea în portalul web Yahoo! Nu este o parolă Yahoo! standard." + }, + "add.spinner.query": { + "message": "Se trimite solicitarea RFC6764 către „##substitui.1##”" + }, + "add.spinner.validating": { + "message": "Verificarea conexiunii la server" + }, + "add.title": { + "message": "Adăugarea unui cont CalDAV și CardDAV la TbSync" + }, + "add.user": { + "message": "Utilizator:" + }, + "autocomplete.HOME": { + "message": "personal" + }, + "autocomplete.PREF": { + "message": "preferat" + }, + "autocomplete.WORK": { + "message": "de afaceri" + }, + "config.custom": { + "message": "Configurarea serverului CalDAV & CardDAV" + }, + "defaultname.calendar": { + "message": "Calendar" + }, + "defaultname.contacts": { + "message": "Contacte" + }, + "extensionDescription": { + "message": "Adăugarea suportului de sincronizare pentru conturile CalDAV și CardDAV în TbSync." + }, + "extensionName": { + "message": "Furnizor pentru CalDAV & CardDAV" + }, + "helplink.malformed-xml": { + "message": "https://github.com/jobisoft/DAV-4-TbSync/issues/104" + }, + "manager.tabs.accountsettings": { + "message": "Setările contului" + }, + "manager.tabs.syncsettings": { + "message": "Opţiuni" + }, + "menu.name": { + "message": "CalDAV & CardDAV" + }, + "pref.AccountName": { + "message": "Denumire cont" + }, + "pref.CalDavServer": { + "message": "Adresa serverului CalDAV:" + }, + "pref.CardDavServer": { + "message": "Adresa serverului CardDAV:" + }, + "pref.UserName": { + "message": "Nume utilizator" + }, + "pref.calendaroptions": { + "message": "Opțiuni calendar" + }, + "pref.contactoptions": { + "message": "Opțiuni contact" + }, + "pref.downloadonly": { + "message": "Anularea modificărilor locale (sincronizare unidirecțională)" + }, + "pref.generaloptions": { + "message": "Opţiuni generale" + }, + "pref.useCalendarCache": { + "message": "Suport offline" + }, + "pref.useCardBook": { + "message": "Creați cărți de adrese CardBook în locul cărților de adrese implicite din Thunderbird" + }, + "status.401": { + "message": "Nu s-a putut autentifica, verificați numele de utilizator și parola." + }, + "status.403": { + "message": "Serverul a respins conexiunea (interzisă)." + }, + "status.404": { + "message": "Eroare HTTP 404 (resursa solicitată nu a fost găsită)." + }, + "status.500": { + "message": "Eroare de server necunoscută (HTTP Error 500)." + }, + "status.503": { + "message": "Serviciu indisponibil." + }, + "status.caldavservernotfound": { + "message": "Nu s-a putut găsi un server CalDAV." + }, + "status.carddavservernotfound": { + "message": "Nu s-a putut găsi un server CardDAV." + }, + "status.gContactSync": { + "message": "Există o incompatibilitate cu gContactSync în cazul sincronizării grupurilor de contacte activate. Vă rugăm să dezactivați unul dintre ele atâta timp cât eroarea nu este rezolvată." + }, + "status.info.restored": { + "message": "Din cauza lipsei parțiale a permisiunilor de scriere, unele acțiuni au fost respinse de server și inversate la nivel local." + }, + "status.malformed-xml": { + "message": "Nu s-a putut analiza XML. Verificați jurnalul de evenimente pentru detalii." + }, + "status.missing-permission": { + "message": "Permisiune lipsă: ##replace.1##" + }, + "status.networkerror": { + "message": "Nu s-a putut conecta la server." + }, + "status.rfc6764-lookup-failed": { + "message": "Interogarea \"##replace.1##\" nu a furnizat informațiile necesare cu privire la punctele finale ale serviciilor CalDAV și CardDAV. Vă rugăm să introduceți numele de gazdă al serverului dumneavoastră pentru a continua cu configurarea automată." + }, + "status.service-discovery-failed": { + "message": "Descoperirea automată a punctelor finale ale serviciului CalDAV & CardDAV din \"##replace.1##\" a eșuat. Încercați din nou, specificând o altă adresă de server, sau treceți la configurația personalizată pentru a specifica manual punctele finale ale serviciului." + }, + "status.softerror": { + "message": "Eroare non fatală (##replace.1##)" + }, + "status.success.managed-by-thunderbird": { + "message": "Ok" + }, + "status.non-carddav-addrbook": { + "message": "Aceasta este o carte de adrese TbSync-CardDAV mai veche, care nu mai poate fi sincronizată. Dezactivați și reactivați cartea de adrese pentru a crea o nouă carte de adrese Thunderbird-CardDAV." + }, + "helplink.non-carddav-addrbook": { + "message": "https://github.com/jobisoft/DAV-4-TbSync/wiki/Since-Thunderbird-102,-TbSync-DAV-is-using-native-CardDAV-sync" + }, + "syncstate.eval.folders": { + "message": "Actualizarea listei de foldere" + }, + "syncstate.eval.response.localchanges": { + "message": "Procesarea confirmării modificărilor locale" + }, + "syncstate.eval.response.remotechanges": { + "message": "Prelucrarea modificărilor la distanță" + }, + "syncstate.prepare.request.localchanges": { + "message": "Trimiterea modificărilor locale" + }, + "syncstate.send.getfolders": { + "message": "Solicitarea listei de foldere" + }, + "syncstate.send.request.localchanges": { + "message": "Așteptarea confirmării modificărilor locale" + }, + "syncstate.send.request.remotechanges": { + "message": "Așteptarea modificărilor la distanță" + } +} diff --git a/_locales/ru/messages.json b/_locales/ru/messages.json new file mode 100644 index 0000000..4279cbc --- /dev/null +++ b/_locales/ru/messages.json @@ -0,0 +1,341 @@ +{ + "abCard.ContactDetails": { + "message": "Контакты свойства (CardDAV)" + }, + "abCard.EmailAddresses": { + "message": "Адреса электронной почты:" + }, + "abCard.MiddleName": { + "message": "Отчество:" + }, + "abCard.Phone": { + "message": "Телефонные номера" + }, + "abCard.PrefixName": { + "message": "Префикс:" + }, + "abCard.SuffixName": { + "message": "Суффикс:" + }, + "abCard.emailtypes.car": { + "message": "автомобиль" + }, + "abCard.emailtypes.cell": { + "message": "мобильный" + }, + "abCard.emailtypes.description": { + "message": "Первый адрес электронной почты в списке используется в качестве основного адреса электронной почты." + }, + "abCard.emailtypes.fax": { + "message": "факс" + }, + "abCard.emailtypes.home": { + "message": "дома" + }, + "abCard.emailtypes.other": { + "message": "другой" + }, + "abCard.emailtypes.pager": { + "message": "пейджер" + }, + "abCard.emailtypes.video": { + "message": "видео" + }, + "abCard.emailtypes.voice": { + "message": "телефон" + }, + "abCard.emailtypes.work": { + "message": "бизнес" + }, + "acl.add": { + "message": "добавлять" + }, + "acl.delete": { + "message": "удалять" + }, + "acl.modify": { + "message": "модифицировать" + }, + "acl.none": { + "message": "никто" + }, + "acl.readonly": { + "message": "только для чтения" + }, + "acl.readwrite": { + "message": "Разрешение на запись: ##replace.1##" + }, + "add.caldavserver": { + "message": "Адрес сервера CalDAV:" + }, + "add.carddavserver": { + "message": "Адрес сервера CardDAV:" + }, + "add.data.description": { + "message": "Пожалуйста укажите понятное имя для нового аккаунта TbSync и учетные данные для вашего сервера:" + }, + "add.data.notes": { + "message": "Заметки:" + }, + "add.data.title": { + "message": "Введите информацию об аккаунте" + }, + "add.finish.description": { + "message": "?? The following settings have been verified successfully: ??" + }, + "add.finish.details": { + "message": "?? Click „Finish“ to create a new TbSync account with these settings. ??" + }, + "add.finish.title": { + "message": "?? Confirm account creation ??" + }, + "add.name": { + "message": "Имя аккаунта:" + }, + "add.ok": { + "message": "Добавить аккаунт" + }, + "add.password": { + "message": "Пароль:" + }, + "add.server": { + "message": "Адрес сервера:" + }, + "add.serverprofile.custom": { + "message": "Пользовательская конфигурация" + }, + "add.serverprofile.custom.description": { + "message": "The CalDAV and CardDAV service endpoints can be configured manually." + }, + "add.serverprofile.custom.details1": { + "message": "The required CalDAV & CardDAV service endpoints or the so called principal addresses should be provided by your service provider." + }, + "add.serverprofile.custom.details2": { + "message": "Если вы оставите любой адрес пустым, соответствующая служба будет отключена для этого аккаунта." + }, + "add.serverprofile.description": { + "message": "Пожалуйста выберите один из доступных профилей сервера:" + }, + "add.serverprofile.discovery": { + "message": "Автоматическая настройка" + }, + "add.serverprofile.discovery.description": { + "message": "Many service providers and servers support an automatic configuration process, which requires only an e-mail address or a username and a server address." + }, + "add.serverprofile.discovery.details1": { + "message": "For the automatic discovery of the CalDAV & CardDAV service endpoints, please enter your credentials and the host name of your server (e.g. 'cloud.myserver.de')." + }, + "add.serverprofile.discovery.details2": { + "message": "If your username is an e-mail address, specifying the server becomes optional, as the information about the service endpoints may be obtained directly from your service provider via an RFC6764 request (if supported)." + }, + "add.serverprofile.discovery.server-optional": { + "message": "optional" + }, + "add.serverprofile.fruux": { + "message": "fruux" + }, + "add.serverprofile.fruux.description": { + "message": "fruux - это сервис, который синхронизирует контакты, календари и задачи. Он работает на базе компании поддерживающей протокол sabre/dav и базируется в Германии." + }, + "add.serverprofile.gmx.com": { + "message": "GMX.com (США)" + }, + "add.serverprofile.gmx.com.description": { + "message": "https://www.gmx.com" + }, + "add.serverprofile.gmx.net": { + "message": "GMX.net (Европа)" + }, + "add.serverprofile.gmx.net.description": { + "message": "https://www.gmx.net" + }, + "add.serverprofile.icloud": { + "message": "iCloud" + }, + "add.serverprofile.icloud.description": { + "message": "https://www.icloud.com" + }, + "add.serverprofile.icloud.details1": { + "message": "Запрашиваемое имя пользователя - ваш Apple-ID. Обратите внимание, что вы не можете использовать свой пароль Apple-ID здесь. Вы ДОЛЖНЫ включить двухфакторную авторизацию для своей учетной записи iCloud и создать отдельный пароль приложения для TbSync." + }, + "add.serverprofile.icloud.details2": { + "message": "Это уровень безопасности, применяемый Apple, поэтому сторонние клиенты не могут получить доступ к вашей учетной записи Apple." + }, + "add.serverprofile.mbo": { + "message": "mailbox.org" + }, + "add.serverprofile.mbo.description": { + "message": "mailbox.org is a secure German e-mail provider for private and business customers, which also offers calendars, contacts and cloud storage." + }, + "add.serverprofile.posteo": { + "message": "Posteo" + }, + "add.serverprofile.posteo.description": { + "message": "https://www.posteo.de" + }, + "add.serverprofile.title": { + "message": "Выберите профиль сервера" + }, + "add.serverprofile.web.de": { + "message": "WEB.de" + }, + "add.serverprofile.web.de.description": { + "message": "https://www.web.de" + }, + "add.serverprofile.yahoo": { + "message": "Yahoo!" + }, + "add.serverprofile.yahoo.description": { + "message": "https://www.yahoo.com" + }, + "add.serverprofile.yahoo.details1": { + "message": "The requested password is an app-specific password for TbSync which you can create in your Yahoo! web portal. It is not your standard Yahoo! password." + }, + "add.spinner.query": { + "message": "Sending RFC6764 request to “##replace.1##”" + }, + "add.spinner.validating": { + "message": "Проверьте соединение с сервером" + }, + "add.title": { + "message": "ДобавитьCalDAV & CardDAV аккаунт для TbSync" + }, + "add.user": { + "message": "Имя пользователя:" + }, + "autocomplete.HOME": { + "message": "private" + }, + "autocomplete.PREF": { + "message": "preferred" + }, + "autocomplete.WORK": { + "message": "business" + }, + "config.custom": { + "message": "CalDAV & CardDAV конфигурация сервера" + }, + "defaultname.calendar": { + "message": "календарь" + }, + "defaultname.contacts": { + "message": "контакты" + }, + "extensionDescription": { + "message": "Добавляет в TbSync поддержку базирующегося на http/https протокола синхронизации для учетных записей CalDAV & CardDAV (контакты, задачи и календари)." + }, + "extensionName": { + "message": "Provider for CalDAV & CardDAV" + }, + "helplink.malformed-xml": { + "message": "https://github.com/jobisoft/DAV-4-TbSync/issues/104" + }, + "manager.tabs.accountsettings": { + "message": "Настройки аккаунта" + }, + "manager.tabs.syncsettings": { + "message": "Настройки синхронизации" + }, + "menu.name": { + "message": "CalDAV & CardDAV" + }, + "pref.AccountName": { + "message": "Имя аккаунта" + }, + "pref.CalDavServer": { + "message": "Адрес сервера CalDAV:" + }, + "pref.CardDavServer": { + "message": "Адрес сервера CardDAV:" + }, + "pref.UserName": { + "message": "Имя пользователя" + }, + "pref.calendaroptions": { + "message": "Настройки календаря" + }, + "pref.contactoptions": { + "message": "Настройки контакта" + }, + "pref.downloadonly": { + "message": "Отменить локальные изменения (односторонняя синхронизация)" + }, + "pref.generaloptions": { + "message": "Общие настройки" + }, + "pref.useCalendarCache": { + "message": "Офлайн поддержка" + }, + "pref.useCardBook": { + "message": "Создавайте адресные книги CardBook вместо стандартных адресных книг Thunderbird" + }, + "status.401": { + "message": "Не удалось аутентифицировать, проверить имя пользователя и пароль. (HTTP Ошибка 401)." + }, + "status.403": { + "message": "Сервер отклонил соединение (запрещено) (HTTP Ошибка 403)." + }, + "status.404": { + "message": "Запрошенный ресурс не найден (HTTP Ошибка 404)." + }, + "status.500": { + "message": "Неизвестная ошибка сервера (HTTP Ошибка 500)." + }, + "status.503": { + "message": "Сервис недоступен (HTTP Ошибка 503)." + }, + "status.caldavservernotfound": { + "message": "Не удалось найти сервер CalDAV." + }, + "status.carddavservernotfound": { + "message": "Не удалось найти сервер CardDAV." + }, + "status.gContactSync": { + "message": "There is an incompatibility with gContactSync with activated contact group synchronization. Please deactivate one of them as long as the error is not resolved." + }, + "status.info.restored": { + "message": "Из-за частично отсутствующих разрешений на запись некоторые действия были отклонены сервером и отменены локально." + }, + "status.malformed-xml": { + "message": "Не удалось разобрать XML. Проверьте журнал событий для деталей." + }, + "status.missing-permission": { + "message": "Отсутствует разрешение: ##replace.1##" + }, + "status.networkerror": { + "message": "Не удалось подключиться к серверу." + }, + "status.rfc6764-lookup-failed": { + "message": "The query of “##replace.1##” did not provide the required information regarding the CalDAV and CardDAV service endpoints. Please enter the hostname of your server to proceed with the automatic configuration." + }, + "status.service-discovery-failed": { + "message": "Automatic discovery of CalDAV & CardDAV service endpoints of “##replace.1##” has failed. Try again, specifying a different server address, or switch to the custom configuration to manually specify the service endpoints." + }, + "status.softerror": { + "message": "пропущенная ошибка (##replace.1##)" + }, + "status.success.managed-by-thunderbird": { + "message": "Ok" + }, + "syncstate.eval.folders": { + "message": "Обновление списка папок" + }, + "syncstate.eval.response.localchanges": { + "message": "Обработка подтверждения локальных изменений" + }, + "syncstate.eval.response.remotechanges": { + "message": "Обработка удаленных изменений" + }, + "syncstate.prepare.request.localchanges": { + "message": "Отправка локальных изменений" + }, + "syncstate.send.getfolders": { + "message": "Запрос списка папок" + }, + "syncstate.send.request.localchanges": { + "message": "Ожидание подтверждения локальных изменений" + }, + "syncstate.send.request.remotechanges": { + "message": "Ожидание удаленных изменений" + } +} diff --git a/background.js b/background.js new file mode 100644 index 0000000..5c793e1 --- /dev/null +++ b/background.js @@ -0,0 +1,26 @@ +function isCompatible(version) { + let [ major, minor , patch ] = version.split(".").map(e => parseInt(e,10)); + return ( + major > 102 || + (major == 102 && minor > 3) || + (major == 102 && minor == 3 && patch > 2) + ); +} + +async function main() { + let { version } = await browser.runtime.getBrowserInfo(); + if (isCompatible(version)) { + await messenger.BootstrapLoader.registerChromeUrl([ ["content", "dav4tbsync", "content/"] ]); + await messenger.BootstrapLoader.registerBootstrapScript("chrome://dav4tbsync/content/bootstrap.js"); + } else { + let manifest = browser.runtime.getManifest(); + browser.notifications.create({ + type: "basic", + iconUrl: browser.runtime.getURL("content/skin/sabredav32.png"), + title: `${manifest.name}`, + message: "Please update Thunderbird to at least 102.3.3 to be able to use this provider.", + }); + } +} + +main(); diff --git a/beta-release-channel-update.json b/beta-release-channel-update.json new file mode 100644 index 0000000..6b172e6 --- /dev/null +++ b/beta-release-channel-update.json @@ -0,0 +1,13 @@ +{ + "addons": { + "dav4tbsync@jobisoft.de": { + "updates": [ + { "version": "%VERSION%", + "update_info_url": "https://github.com/jobisoft/DAV-4-TbSync/releases", + "update_link": "%LINK%", + "applications": { + "gecko": { "strict_min_version": "78.0" } } } + ] + } + } +}
\ No newline at end of file diff --git a/content/api/BootstrapLoader/CHANGELOG.md b/content/api/BootstrapLoader/CHANGELOG.md new file mode 100644 index 0000000..5006ecf --- /dev/null +++ b/content/api/BootstrapLoader/CHANGELOG.md @@ -0,0 +1,75 @@ +Version: 1.21 +------------- +- Explicitly set hasAddonManagerEventListeners flag to false on uninstall + +Version: 1.20 +------------- +- hard fork BootstrapLoader v1.19 implementation and continue to serve it for + Thunderbird 111 and older +- BootstrapLoader v1.20 has removed a lot of unnecessary code used for backward + compatibility + +Version: 1.19 +------------- +- fix race condition which could prevent the AOM tab to be monkey patched correctly + +Version: 1.18 +------------- +- be precise on which revision the wrench symbol should be displayed, instead of + the options button + +Version: 1.17 +------------- +- fix "ownerDoc.getElementById() is undefined" bug + +Version: 1.16 +------------- +- fix "tab.browser is undefined" bug + +Version 1.15 +------------ +- clear cache only if add-on is uninstalled/updated, not on app shutdown + +Version 1.14 +------------ +- fix for TB90 ("view-loaded" event) and TB78.10 (wrench icon for options) + +Version 1.13 +------------ +- removed notifyTools and move it into its own NotifyTools API + +Version 1.12 +------------ +- add support for notifyExperiment and onNotifyBackground + +Version 1.11 +------------ +- add openOptionsDialog() + +Version 1.10 +------------ +- fix for 68 + +Version 1.7 +----------- +- fix for beta 87 + +Version 1.6 +----------- +- add support for options button/menu in add-on manager and fix 68 double menu entry + +Version 1.5 +----------- +- fix for e10s + +Version 1.4 +----------- +- add registerOptionsPage + +Version 1.3 +----------- +- flush cache + +Version 1.2 +----------- +- add support for resource urls diff --git a/content/api/BootstrapLoader/README.md b/content/api/BootstrapLoader/README.md new file mode 100644 index 0000000..7e8fe2a --- /dev/null +++ b/content/api/BootstrapLoader/README.md @@ -0,0 +1 @@ +Usage description can be found in the [wiki](https://github.com/thundernest/addon-developer-support/wiki/Using-the-BootstrapLoader-API-to-convert-a-Legacy-Bootstrap-WebExtension-into-a-MailExtension-for-Thunderbird-78). diff --git a/content/api/BootstrapLoader/implementation.js b/content/api/BootstrapLoader/implementation.js new file mode 100644 index 0000000..03e6b76 --- /dev/null +++ b/content/api/BootstrapLoader/implementation.js @@ -0,0 +1,917 @@ +/* + * This file is provided by the addon-developer-support repository at + * https://github.com/thundernest/addon-developer-support + * + * Version: 1.21 + * + * Author: John Bieling (john@thunderbird.net) + * + * 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, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +// Get various parts of the WebExtension framework that we need. +var { ExtensionCommon } = ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm"); +var { ExtensionSupport } = ChromeUtils.import("resource:///modules/ExtensionSupport.jsm"); +var { AddonManager } = ChromeUtils.import("resource://gre/modules/AddonManager.jsm"); +var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +function getThunderbirdVersion() { + let parts = Services.appinfo.version.split("."); + return { + major: parseInt(parts[0]), + minor: parseInt(parts[1]), + revision: parts.length > 2 ? parseInt(parts[2]) : 0, + } +} + +function getMessenger(context) { + let apis = ["storage", "runtime", "extension", "i18n"]; + + function getStorage() { + let localstorage = null; + try { + localstorage = context.apiCan.findAPIPath("storage"); + localstorage.local.get = (...args) => + localstorage.local.callMethodInParentProcess("get", args); + localstorage.local.set = (...args) => + localstorage.local.callMethodInParentProcess("set", args); + localstorage.local.remove = (...args) => + localstorage.local.callMethodInParentProcess("remove", args); + localstorage.local.clear = (...args) => + localstorage.local.callMethodInParentProcess("clear", args); + } catch (e) { + console.info("Storage permission is missing"); + } + return localstorage; + } + + let messenger = {}; + for (let api of apis) { + switch (api) { + case "storage": + XPCOMUtils.defineLazyGetter(messenger, "storage", () => + getStorage() + ); + break; + + default: + XPCOMUtils.defineLazyGetter(messenger, api, () => + context.apiCan.findAPIPath(api) + ); + } + } + return messenger; +} + +var BootstrapLoader_102 = class extends ExtensionCommon.ExtensionAPI { + getCards(e) { + // This gets triggered by real events but also manually by providing the outer window. + // The event is attached to the outer browser, get the inner one. + let doc; + + // 78,86, and 87+ need special handholding. *Yeah*. + if (getThunderbirdVersion().major < 86) { + let ownerDoc = e.document || e.target.ownerDocument; + doc = ownerDoc.getElementById("html-view-browser").contentDocument; + } else if (getThunderbirdVersion().major < 87) { + let ownerDoc = e.document || e.target; + doc = ownerDoc.getElementById("html-view-browser").contentDocument; + } else { + doc = e.document || e.target; + } + return doc.querySelectorAll("addon-card"); + } + + // Add pref entry to 68 + add68PrefsEntry(event) { + let id = this.menu_addonPrefs_id + "_" + this.uniqueRandomID; + + // Get the best size of the icon (16px or bigger) + let iconSizes = this.extension.manifest.icons + ? Object.keys(this.extension.manifest.icons) + : []; + iconSizes.sort((a, b) => a - b); + let bestSize = iconSizes.filter(e => parseInt(e) >= 16).shift(); + let icon = bestSize ? this.extension.manifest.icons[bestSize] : ""; + + let name = this.extension.manifest.name; + let entry = icon + ? event.target.ownerGlobal.MozXULElement.parseXULToFragment( + `<menuitem class="menuitem-iconic" id="${id}" image="${icon}" label="${name}" />`) + : event.target.ownerGlobal.MozXULElement.parseXULToFragment( + `<menuitem id="${id}" label="${name}" />`); + + event.target.appendChild(entry); + let noPrefsElem = event.target.querySelector('[disabled="true"]'); + // using collapse could be undone by core, so we use display none + // noPrefsElem.setAttribute("collapsed", "true"); + noPrefsElem.style.display = "none"; + event.target.ownerGlobal.document.getElementById(id).addEventListener("command", this); + } + + // Event handler for the addon manager, to update the state of the options button. + handleEvent(e) { + switch (e.type) { + // 68 add-on options menu showing + case "popupshowing": { + this.add68PrefsEntry(e); + } + break; + + // 78/88 add-on options menu/button click + case "click": { + e.preventDefault(); + e.stopPropagation(); + let BL = {} + BL.extension = this.extension; + BL.messenger = getMessenger(this.context); + let w = Services.wm.getMostRecentWindow("mail:3pane"); + w.openDialog(this.pathToOptionsPage, "AddonOptions", "chrome,resizable,centerscreen", BL); + } + break; + + // 68 add-on options menu command + case "command": { + let BL = {} + BL.extension = this.extension; + BL.messenger = getMessenger(this.context); + e.target.ownerGlobal.openDialog(this.pathToOptionsPage, "AddonOptions", "chrome,resizable,centerscreen", BL); + } + break; + + // update, ViewChanged and manual call for add-on manager options overlay + default: { + let cards = this.getCards(e); + for (let card of cards) { + // Setup either the options entry in the menu or the button + if (card.addon.id == this.extension.id) { + let optionsMenu = + (getThunderbirdVersion().major > 78 && getThunderbirdVersion().major < 88) || + (getThunderbirdVersion().major == 78 && getThunderbirdVersion().minor < 10) || + (getThunderbirdVersion().major == 78 && getThunderbirdVersion().minor == 10 && getThunderbirdVersion().revision < 2); + if (optionsMenu) { + // Options menu in 78.0-78.10 and 79-87 + let addonOptionsLegacyEntry = card.querySelector(".extension-options-legacy"); + if (card.addon.isActive && !addonOptionsLegacyEntry) { + let addonOptionsEntry = card.querySelector("addon-options panel-list panel-item[action='preferences']"); + addonOptionsLegacyEntry = card.ownerDocument.createElement("panel-item"); + addonOptionsLegacyEntry.setAttribute("data-l10n-id", "preferences-addon-button"); + addonOptionsLegacyEntry.classList.add("extension-options-legacy"); + addonOptionsEntry.parentNode.insertBefore( + addonOptionsLegacyEntry, + addonOptionsEntry + ); + card.querySelector(".extension-options-legacy").addEventListener("click", this); + } else if (!card.addon.isActive && addonOptionsLegacyEntry) { + addonOptionsLegacyEntry.remove(); + } + } else { + // Add-on button in 88 + let addonOptionsButton = card.querySelector(".extension-options-button2"); + if (card.addon.isActive && !addonOptionsButton) { + addonOptionsButton = card.ownerDocument.createElement("button"); + addonOptionsButton.classList.add("extension-options-button2"); + addonOptionsButton.style["min-width"] = "auto"; + addonOptionsButton.style["min-height"] = "auto"; + addonOptionsButton.style["width"] = "24px"; + addonOptionsButton.style["height"] = "24px"; + addonOptionsButton.style["margin"] = "0"; + addonOptionsButton.style["margin-inline-start"] = "8px"; + addonOptionsButton.style["-moz-context-properties"] = "fill"; + addonOptionsButton.style["fill"] = "currentColor"; + addonOptionsButton.style["background-image"] = "url('chrome://messenger/skin/icons/developer.svg')"; + addonOptionsButton.style["background-repeat"] = "no-repeat"; + addonOptionsButton.style["background-position"] = "center center"; + addonOptionsButton.style["padding"] = "1px"; + addonOptionsButton.style["display"] = "flex"; + addonOptionsButton.style["justify-content"] = "flex-end"; + card.optionsButton.parentNode.insertBefore( + addonOptionsButton, + card.optionsButton + ); + card.querySelector(".extension-options-button2").addEventListener("click", this); + } else if (!card.addon.isActive && addonOptionsButton) { + addonOptionsButton.remove(); + } + } + } + } + } + } + } + + // Some tab/add-on-manager related functions + getTabMail(window) { + return window.document.getElementById("tabmail"); + } + + // returns the outer browser, not the nested browser of the add-on manager + // events must be attached to the outer browser + getAddonManagerFromTab(tab) { + if (tab.browser && tab.mode.name == "contentTab") { + let win = tab.browser.contentWindow; + if (win && win.location.href == "about:addons") { + return win; + } + } + } + + getAddonManagerFromWindow(window) { + let tabMail = this.getTabMail(window); + for (let tab of tabMail.tabInfo) { + let managerWindow = this.getAddonManagerFromTab(tab); + if (managerWindow) { + return managerWindow; + } + } + } + + async getAddonManagerFromWindowWaitForLoad(window) { + let { setTimeout } = Services.wm.getMostRecentWindow("mail:3pane"); + + let tabMail = this.getTabMail(window); + for (let tab of tabMail.tabInfo) { + if (tab.browser && tab.mode.name == "contentTab") { + // Instead of registering a load observer, wait until its loaded. Not nice, + // but gets aroud a lot of edge cases. + while (!tab.pageLoaded) { + await new Promise(r => setTimeout(r, 150)); + } + let managerWindow = this.getAddonManagerFromTab(tab); + if (managerWindow) { + return managerWindow; + } + } + } + } + + setupAddonManager(managerWindow, forceLoad = false) { + if (!managerWindow) { + return; + } + if ( + managerWindow && + managerWindow[this.uniqueRandomID] && + managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners + ) { + return; + } + managerWindow.document.addEventListener("ViewChanged", this); + managerWindow.document.addEventListener("update", this); + managerWindow.document.addEventListener("view-loaded", this); + managerWindow[this.uniqueRandomID] = {}; + managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners = true; + if (forceLoad) { + this.handleEvent(managerWindow); + } + } + + getAPI(context) { + this.uniqueRandomID = "AddOnNS" + context.extension.instanceId; + this.menu_addonPrefs_id = "addonPrefs"; + + + this.pathToBootstrapScript = null; + this.pathToOptionsPage = null; + this.chromeHandle = null; + this.chromeData = null; + this.resourceData = null; + this.bootstrappedObj = {}; + + // make the extension object and the messenger object available inside + // the bootstrapped scope + this.bootstrappedObj.extension = context.extension; + this.bootstrappedObj.messenger = getMessenger(this.context); + + this.BOOTSTRAP_REASONS = { + APP_STARTUP: 1, + APP_SHUTDOWN: 2, + ADDON_ENABLE: 3, + ADDON_DISABLE: 4, + ADDON_INSTALL: 5, + ADDON_UNINSTALL: 6, // not supported + ADDON_UPGRADE: 7, + ADDON_DOWNGRADE: 8, + }; + + const aomStartup = Cc["@mozilla.org/addons/addon-manager-startup;1"].getService(Ci.amIAddonManagerStartup); + const resProto = Cc["@mozilla.org/network/protocol;1?name=resource"].getService(Ci.nsISubstitutingProtocolHandler); + + let self = this; + + // TabMonitor to detect opening of tabs, to setup the options button in the add-on manager. + this.tabMonitor = { + onTabTitleChanged(tab) { }, + onTabClosing(tab) { }, + onTabPersist(tab) { }, + onTabRestored(tab) { }, + onTabSwitched(aNewTab, aOldTab) { }, + async onTabOpened(tab) { + if (tab.browser && tab.mode.name == "contentTab") { + let { setTimeout } = Services.wm.getMostRecentWindow("mail:3pane"); + // Instead of registering a load observer, wait until its loaded. Not nice, + // but gets aroud a lot of edge cases. + while (!tab.pageLoaded) { + await new Promise(r => setTimeout(r, 150)); + } + self.setupAddonManager(self.getAddonManagerFromTab(tab)); + } + }, + }; + + return { + BootstrapLoader: { + + registerOptionsPage(optionsUrl) { + self.pathToOptionsPage = optionsUrl.startsWith("chrome://") + ? optionsUrl + : context.extension.rootURI.resolve(optionsUrl); + }, + + openOptionsDialog(windowId) { + let window = context.extension.windowManager.get(windowId, context).window + let BL = {} + BL.extension = self.extension; + BL.messenger = getMessenger(self.context); + window.openDialog(self.pathToOptionsPage, "AddonOptions", "chrome,resizable,centerscreen", BL); + }, + + registerChromeUrl(data) { + let chromeData = []; + let resourceData = []; + for (let entry of data) { + if (entry[0] == "resource") resourceData.push(entry); + else chromeData.push(entry) + } + + if (chromeData.length > 0) { + const manifestURI = Services.io.newURI( + "manifest.json", + null, + context.extension.rootURI + ); + self.chromeHandle = aomStartup.registerChrome(manifestURI, chromeData); + } + + for (let res of resourceData) { + // [ "resource", "shortname" , "path" ] + let uri = Services.io.newURI( + res[2], + null, + context.extension.rootURI + ); + resProto.setSubstitutionWithFlags( + res[1], + uri, + resProto.ALLOW_CONTENT_ACCESS + ); + } + + self.chromeData = chromeData; + self.resourceData = resourceData; + }, + + registerBootstrapScript: async function (aPath) { + self.pathToBootstrapScript = aPath.startsWith("chrome://") + ? aPath + : context.extension.rootURI.resolve(aPath); + + // Get the addon object belonging to this extension. + let addon = await AddonManager.getAddonByID(context.extension.id); + //make the addon globally available in the bootstrapped scope + self.bootstrappedObj.addon = addon; + + // add BOOTSTRAP_REASONS to scope + for (let reason of Object.keys(self.BOOTSTRAP_REASONS)) { + self.bootstrappedObj[reason] = self.BOOTSTRAP_REASONS[reason]; + } + + // Load registered bootstrap scripts and execute its startup() function. + try { + if (self.pathToBootstrapScript) Services.scriptloader.loadSubScript(self.pathToBootstrapScript, self.bootstrappedObj, "UTF-8"); + if (self.bootstrappedObj.startup) self.bootstrappedObj.startup.call(self.bootstrappedObj, self.extension.addonData, self.BOOTSTRAP_REASONS[self.extension.startupReason]); + } catch (e) { + Components.utils.reportError(e) + } + + // Register window listener for main TB window + if (self.pathToOptionsPage) { + ExtensionSupport.registerWindowListener("injectListener_" + self.uniqueRandomID, { + chromeURLs: [ + "chrome://messenger/content/messenger.xul", + "chrome://messenger/content/messenger.xhtml", + ], + async onLoadWindow(window) { + if (getThunderbirdVersion().major < 78) { + let element_addonPrefs = window.document.getElementById(self.menu_addonPrefs_id); + element_addonPrefs.addEventListener("popupshowing", self); + } else { + // Add a tabmonitor, to be able to setup the options button/menu in the add-on manager. + self.getTabMail(window).registerTabMonitor(self.tabMonitor); + window[self.uniqueRandomID] = {}; + window[self.uniqueRandomID].hasTabMonitor = true; + // Setup the options button/menu in the add-on manager, if it is already open. + let managerWindow = await self.getAddonManagerFromWindowWaitForLoad(window); + self.setupAddonManager(managerWindow, true); + } + }, + + onUnloadWindow(window) { + } + }); + } + } + } + }; + } + + onShutdown(isAppShutdown) { + if (isAppShutdown) { + return; // the application gets unloaded anyway + } + + //remove our entry in the add-on options menu + if (this.pathToOptionsPage) { + for (let window of Services.wm.getEnumerator("mail:3pane")) { + if (getThunderbirdVersion().major < 78) { + let element_addonPrefs = window.document.getElementById(this.menu_addonPrefs_id); + element_addonPrefs.removeEventListener("popupshowing", this); + // Remove our entry. + let entry = window.document.getElementById(this.menu_addonPrefs_id + "_" + this.uniqueRandomID); + if (entry) entry.remove(); + // Do we have to unhide the noPrefsElement? + if (element_addonPrefs.children.length == 1) { + let noPrefsElem = element_addonPrefs.querySelector('[disabled="true"]'); + noPrefsElem.style.display = "inline"; + } + } else { + // Remove event listener for addon manager view changes + let managerWindow = this.getAddonManagerFromWindow(window); + if ( + managerWindow && + managerWindow[this.uniqueRandomID] && + managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners + ) { + managerWindow.document.removeEventListener("ViewChanged", this); + managerWindow.document.removeEventListener("update", this); + managerWindow.document.removeEventListener("view-loaded", this); + managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners = false; + + let cards = this.getCards(managerWindow); + if (getThunderbirdVersion().major < 88) { + // Remove options menu in 78-87 + for (let card of cards) { + let addonOptionsLegacyEntry = card.querySelector(".extension-options-legacy"); + if (addonOptionsLegacyEntry) addonOptionsLegacyEntry.remove(); + } + } else { + // Remove options button in 88 + for (let card of cards) { + if (card.addon.id == this.extension.id) { + let addonOptionsButton = card.querySelector(".extension-options-button2"); + if (addonOptionsButton) addonOptionsButton.remove(); + break; + } + } + } + } + + // Remove tabmonitor + if (window[this.uniqueRandomID].hasTabMonitor) { + this.getTabMail(window).unregisterTabMonitor(this.tabMonitor); + window[this.uniqueRandomID].hasTabMonitor = false; + } + + } + } + // Stop listening for new windows. + ExtensionSupport.unregisterWindowListener("injectListener_" + this.uniqueRandomID); + } + + // Execute registered shutdown() + try { + if (this.bootstrappedObj.shutdown) { + this.bootstrappedObj.shutdown( + this.extension.addonData, + isAppShutdown + ? this.BOOTSTRAP_REASONS.APP_SHUTDOWN + : this.BOOTSTRAP_REASONS.ADDON_DISABLE); + } + } catch (e) { + Components.utils.reportError(e) + } + + if (this.resourceData) { + const resProto = Cc["@mozilla.org/network/protocol;1?name=resource"].getService(Ci.nsISubstitutingProtocolHandler); + for (let res of this.resourceData) { + // [ "resource", "shortname" , "path" ] + resProto.setSubstitution( + res[1], + null, + ); + } + } + + if (this.chromeHandle) { + this.chromeHandle.destruct(); + this.chromeHandle = null; + } + // Flush all caches + Services.obs.notifyObservers(null, "startupcache-invalidate"); + console.log("BootstrapLoader for " + this.extension.id + " unloaded!"); + } +}; + +// Removed all extra code for backward compatibility for better maintainability. +var BootstrapLoader_115 = class extends ExtensionCommon.ExtensionAPI { + getCards(e) { + // This gets triggered by real events but also manually by providing the outer window. + // The event is attached to the outer browser, get the inner one. + let doc = e.document || e.target; + return doc.querySelectorAll("addon-card"); + } + + // Event handler for the addon manager, to update the state of the options button. + handleEvent(e) { + switch (e.type) { + case "click": { + e.preventDefault(); + e.stopPropagation(); + let BL = {} + BL.extension = this.extension; + BL.messenger = getMessenger(this.context); + let w = Services.wm.getMostRecentWindow("mail:3pane"); + w.openDialog( + this.pathToOptionsPage, + "AddonOptions", + "chrome,resizable,centerscreen", + BL + ); + } + break; + + + // update, ViewChanged and manual call for add-on manager options overlay + default: { + let cards = this.getCards(e); + for (let card of cards) { + // Setup either the options entry in the menu or the button + if (card.addon.id == this.extension.id) { + // Add-on button + let addonOptionsButton = card.querySelector( + ".windowlistener-options-button" + ); + if (card.addon.isActive && !addonOptionsButton) { + let origAddonOptionsButton = card.querySelector(".extension-options-button") + origAddonOptionsButton.setAttribute("hidden", "true"); + + addonOptionsButton = card.ownerDocument.createElement("button"); + addonOptionsButton.classList.add("windowlistener-options-button"); + addonOptionsButton.classList.add("extension-options-button"); + card.optionsButton.parentNode.insertBefore( + addonOptionsButton, + card.optionsButton + ); + card + .querySelector(".windowlistener-options-button") + .addEventListener("click", this); + } else if (!card.addon.isActive && addonOptionsButton) { + addonOptionsButton.remove(); + } + } + } + } + } + } + + // Some tab/add-on-manager related functions + getTabMail(window) { + return window.document.getElementById("tabmail"); + } + + // returns the outer browser, not the nested browser of the add-on manager + // events must be attached to the outer browser + getAddonManagerFromTab(tab) { + if (tab.browser && tab.mode.name == "contentTab") { + let win = tab.browser.contentWindow; + if (win && win.location.href == "about:addons") { + return win; + } + } + } + + getAddonManagerFromWindow(window) { + let tabMail = this.getTabMail(window); + for (let tab of tabMail.tabInfo) { + let managerWindow = this.getAddonManagerFromTab(tab); + if (managerWindow) { + return managerWindow; + } + } + } + + async getAddonManagerFromWindowWaitForLoad(window) { + let { setTimeout } = Services.wm.getMostRecentWindow("mail:3pane"); + + let tabMail = this.getTabMail(window); + for (let tab of tabMail.tabInfo) { + if (tab.browser && tab.mode.name == "contentTab") { + // Instead of registering a load observer, wait until its loaded. Not nice, + // but gets aroud a lot of edge cases. + while (!tab.pageLoaded) { + await new Promise(r => setTimeout(r, 150)); + } + let managerWindow = this.getAddonManagerFromTab(tab); + if (managerWindow) { + return managerWindow; + } + } + } + } + + setupAddonManager(managerWindow, forceLoad = false) { + if (!managerWindow) { + return; + } + if (!this.pathToOptionsPage) { + return; + } + if ( + managerWindow && + managerWindow[this.uniqueRandomID] && + managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners + ) { + return; + } + + managerWindow.document.addEventListener("ViewChanged", this); + managerWindow.document.addEventListener("update", this); + managerWindow.document.addEventListener("view-loaded", this); + managerWindow[this.uniqueRandomID] = {}; + managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners = true; + if (forceLoad) { + this.handleEvent(managerWindow); + } + } + + getAPI(context) { + this.uniqueRandomID = "AddOnNS" + context.extension.instanceId; + this.menu_addonPrefs_id = "addonPrefs"; + + + this.pathToBootstrapScript = null; + this.pathToOptionsPage = null; + this.chromeHandle = null; + this.chromeData = null; + this.resourceData = null; + this.bootstrappedObj = {}; + + // make the extension object and the messenger object available inside + // the bootstrapped scope + this.bootstrappedObj.extension = context.extension; + this.bootstrappedObj.messenger = getMessenger(this.context); + + this.BOOTSTRAP_REASONS = { + APP_STARTUP: 1, + APP_SHUTDOWN: 2, + ADDON_ENABLE: 3, + ADDON_DISABLE: 4, + ADDON_INSTALL: 5, + ADDON_UNINSTALL: 6, // not supported + ADDON_UPGRADE: 7, + ADDON_DOWNGRADE: 8, + }; + + const aomStartup = Cc["@mozilla.org/addons/addon-manager-startup;1"].getService(Ci.amIAddonManagerStartup); + const resProto = Cc["@mozilla.org/network/protocol;1?name=resource"].getService(Ci.nsISubstitutingProtocolHandler); + + let self = this; + + // TabMonitor to detect opening of tabs, to setup the options button in the add-on manager. + this.tabMonitor = { + onTabTitleChanged(tab) { }, + onTabClosing(tab) { }, + onTabPersist(tab) { }, + onTabRestored(tab) { }, + onTabSwitched(aNewTab, aOldTab) { }, + async onTabOpened(tab) { + if (tab.browser && tab.mode.name == "contentTab") { + let { setTimeout } = Services.wm.getMostRecentWindow("mail:3pane"); + // Instead of registering a load observer, wait until its loaded. Not nice, + // but gets aroud a lot of edge cases. + while (!tab.pageLoaded) { + await new Promise(r => setTimeout(r, 150)); + } + self.setupAddonManager(self.getAddonManagerFromTab(tab)); + } + }, + }; + + return { + BootstrapLoader: { + + registerOptionsPage(optionsUrl) { + self.pathToOptionsPage = optionsUrl.startsWith("chrome://") + ? optionsUrl + : context.extension.rootURI.resolve(optionsUrl); + }, + + openOptionsDialog(windowId) { + let window = context.extension.windowManager.get(windowId, context).window + let BL = {} + BL.extension = self.extension; + BL.messenger = getMessenger(self.context); + window.openDialog(self.pathToOptionsPage, "AddonOptions", "chrome,resizable,centerscreen", BL); + }, + + registerChromeUrl(data) { + let chromeData = []; + let resourceData = []; + for (let entry of data) { + if (entry[0] == "resource") resourceData.push(entry); + else chromeData.push(entry) + } + + if (chromeData.length > 0) { + const manifestURI = Services.io.newURI( + "manifest.json", + null, + context.extension.rootURI + ); + self.chromeHandle = aomStartup.registerChrome(manifestURI, chromeData); + } + + for (let res of resourceData) { + // [ "resource", "shortname" , "path" ] + let uri = Services.io.newURI( + res[2], + null, + context.extension.rootURI + ); + resProto.setSubstitutionWithFlags( + res[1], + uri, + resProto.ALLOW_CONTENT_ACCESS + ); + } + + self.chromeData = chromeData; + self.resourceData = resourceData; + }, + + registerBootstrapScript: async function (aPath) { + self.pathToBootstrapScript = aPath.startsWith("chrome://") + ? aPath + : context.extension.rootURI.resolve(aPath); + + // Get the addon object belonging to this extension. + let addon = await AddonManager.getAddonByID(context.extension.id); + //make the addon globally available in the bootstrapped scope + self.bootstrappedObj.addon = addon; + + // add BOOTSTRAP_REASONS to scope + for (let reason of Object.keys(self.BOOTSTRAP_REASONS)) { + self.bootstrappedObj[reason] = self.BOOTSTRAP_REASONS[reason]; + } + + // Load registered bootstrap scripts and execute its startup() function. + try { + if (self.pathToBootstrapScript) Services.scriptloader.loadSubScript(self.pathToBootstrapScript, self.bootstrappedObj, "UTF-8"); + if (self.bootstrappedObj.startup) self.bootstrappedObj.startup.call(self.bootstrappedObj, self.extension.addonData, self.BOOTSTRAP_REASONS[self.extension.startupReason]); + } catch (e) { + Components.utils.reportError(e) + } + + // Register window listener for main TB window + if (self.pathToOptionsPage) { + ExtensionSupport.registerWindowListener("injectListener_" + self.uniqueRandomID, { + chromeURLs: [ + "chrome://messenger/content/messenger.xul", + "chrome://messenger/content/messenger.xhtml", + ], + async onLoadWindow(window) { + if (getThunderbirdVersion().major < 78) { + let element_addonPrefs = window.document.getElementById(self.menu_addonPrefs_id); + element_addonPrefs.addEventListener("popupshowing", self); + } else { + // Add a tabmonitor, to be able to setup the options button/menu in the add-on manager. + self.getTabMail(window).registerTabMonitor(self.tabMonitor); + window[self.uniqueRandomID] = {}; + window[self.uniqueRandomID].hasTabMonitor = true; + // Setup the options button/menu in the add-on manager, if it is already open. + let managerWindow = await self.getAddonManagerFromWindowWaitForLoad(window); + self.setupAddonManager(managerWindow, true); + } + }, + + onUnloadWindow(window) { + } + }); + } + } + } + }; + } + + onShutdown(isAppShutdown) { + if (isAppShutdown) { + return; // the application gets unloaded anyway + } + + //remove our entry in the add-on options menu + if (this.pathToOptionsPage) { + for (let window of Services.wm.getEnumerator("mail:3pane")) { + if (getThunderbirdVersion().major < 78) { + let element_addonPrefs = window.document.getElementById(this.menu_addonPrefs_id); + element_addonPrefs.removeEventListener("popupshowing", this); + // Remove our entry. + let entry = window.document.getElementById(this.menu_addonPrefs_id + "_" + this.uniqueRandomID); + if (entry) entry.remove(); + // Do we have to unhide the noPrefsElement? + if (element_addonPrefs.children.length == 1) { + let noPrefsElem = element_addonPrefs.querySelector('[disabled="true"]'); + noPrefsElem.style.display = "inline"; + } + } else { + // Remove event listener for addon manager view changes + let managerWindow = this.getAddonManagerFromWindow(window); + if ( + managerWindow && + managerWindow[this.uniqueRandomID] && + managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners + ) { + managerWindow.document.removeEventListener("ViewChanged", this); + managerWindow.document.removeEventListener("update", this); + managerWindow.document.removeEventListener("view-loaded", this); + managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners = false; + + let cards = this.getCards(managerWindow); + if (getThunderbirdVersion().major < 88) { + // Remove options menu in 78-87 + for (let card of cards) { + let addonOptionsLegacyEntry = card.querySelector(".extension-options-legacy"); + if (addonOptionsLegacyEntry) addonOptionsLegacyEntry.remove(); + } + } else { + // Remove options button in 88 + for (let card of cards) { + if (card.addon.id == this.extension.id) { + let addonOptionsButton = card.querySelector(".extension-options-button2"); + if (addonOptionsButton) addonOptionsButton.remove(); + break; + } + } + } + } + + // Remove tabmonitor + if (window[this.uniqueRandomID].hasTabMonitor) { + this.getTabMail(window).unregisterTabMonitor(this.tabMonitor); + window[this.uniqueRandomID].hasTabMonitor = false; + } + + } + } + // Stop listening for new windows. + ExtensionSupport.unregisterWindowListener("injectListener_" + this.uniqueRandomID); + } + + // Execute registered shutdown() + try { + if (this.bootstrappedObj.shutdown) { + this.bootstrappedObj.shutdown( + this.extension.addonData, + isAppShutdown + ? this.BOOTSTRAP_REASONS.APP_SHUTDOWN + : this.BOOTSTRAP_REASONS.ADDON_DISABLE); + } + } catch (e) { + Components.utils.reportError(e) + } + + if (this.resourceData) { + const resProto = Cc["@mozilla.org/network/protocol;1?name=resource"].getService(Ci.nsISubstitutingProtocolHandler); + for (let res of this.resourceData) { + // [ "resource", "shortname" , "path" ] + resProto.setSubstitution( + res[1], + null, + ); + } + } + + if (this.chromeHandle) { + this.chromeHandle.destruct(); + this.chromeHandle = null; + } + // Flush all caches + Services.obs.notifyObservers(null, "startupcache-invalidate"); + console.log("BootstrapLoader for " + this.extension.id + " unloaded!"); + } +}; + +var BootstrapLoader = getThunderbirdVersion().major < 111 + ? BootstrapLoader_102 + : BootstrapLoader_115; diff --git a/content/api/BootstrapLoader/schema.json b/content/api/BootstrapLoader/schema.json new file mode 100644 index 0000000..fe48fb6 --- /dev/null +++ b/content/api/BootstrapLoader/schema.json @@ -0,0 +1,61 @@ +[ + { + "namespace": "BootstrapLoader", + "functions": [ + { + "name": "registerOptionsPage", + "type": "function", + "parameters": [ + { + "name": "aPath", + "type": "string", + "description": "Path to the options page, which should be made accessible in the (legacy) Add-On Options menu." + } + ] + }, + { + "name": "openOptionsDialog", + "type": "function", + "parameters": [ + { + "name": "windowId", + "type": "integer", + "description": "Id of the window the dialog should be opened from." + } + ] + }, + { + "name": "registerChromeUrl", + "type": "function", + "description": "Register folders which should be available as chrome:// urls (as defined in the legacy chrome.manifest)", + "async": true, + "parameters": [ + { + "name": "chromeData", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "string" + } + }, + "description": "Array of ChromeData Arrays." + } + ] + }, + { + "name": "registerBootstrapScript", + "type": "function", + "description": "Register a bootstrap.js style script", + "async": true, + "parameters": [ + { + "name": "aPath", + "type": "string", + "description": "Either the chrome:// path to the script or its relative location from the root of the extension," + } + ] + } + ] + } +]
\ No newline at end of file diff --git a/content/bootstrap.js b/content/bootstrap.js new file mode 100644 index 0000000..d4e4d7c --- /dev/null +++ b/content/bootstrap.js @@ -0,0 +1,62 @@ +/* + * This file is part of DAV-4-TbSync. + * + * 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, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +// no need to create namespace, we are in a sandbox + +var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); + +let component = {}; + +let onInitDoneObserver = { + observe: async function (aSubject, aTopic, aData) { + let valid = false; + try { + var { TbSync } = ChromeUtils.import("chrome://tbsync/content/tbsync.jsm"); + valid = TbSync.enabled; + } catch (e) { + // If this fails, TbSync is not loaded yet and we will get the notification later again. + } + + //load this provider add-on into TbSync + if (valid) { + await TbSync.providers.loadProvider(extension, "dav", "chrome://dav4tbsync/content/provider.js"); + } + } +} + + +function startup(data, reason) { + // Possible reasons: APP_STARTUP, ADDON_ENABLE, ADDON_INSTALL, ADDON_UPGRADE, or ADDON_DOWNGRADE. + + Services.obs.addObserver(onInitDoneObserver, "tbsync.observer.initialized", false); + + // Did we miss the observer? + onInitDoneObserver.observe(); +} + +function shutdown(data, reason) { + // Possible reasons: APP_STARTUP, ADDON_ENABLE, ADDON_INSTALL, ADDON_UPGRADE, or ADDON_DOWNGRADE. + + // When the application is shutting down we normally don't have to clean up. + if (reason == APP_SHUTDOWN) { + return; + } + + + Services.obs.removeObserver(onInitDoneObserver, "tbsync.observer.initialized"); + //unload this provider add-on from TbSync + try { + var { TbSync } = ChromeUtils.import("chrome://tbsync/content/tbsync.jsm"); + TbSync.providers.unloadProvider("dav"); + } catch (e) { + //if this fails, TbSync has been unloaded already and has unloaded this addon as well + } + Services.obs.notifyObservers(null, "startupcache-invalidate"); + Services.obs.notifyObservers(null, "chrome-flush-caches"); +} diff --git a/content/includes/network.js b/content/includes/network.js new file mode 100644 index 0000000..3f94c30 --- /dev/null +++ b/content/includes/network.js @@ -0,0 +1,431 @@ +/* + * This file is part of DAV-4-TbSync. + * + * 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, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +"use strict"; + +var { HttpRequest } = ChromeUtils.import("chrome://tbsync/content/HttpRequest.jsm"); +var { OAuth2 } = ChromeUtils.import("resource:///modules/OAuth2.jsm"); +const { DNS } = ChromeUtils.import("resource:///modules/DNS.jsm"); + +var network = { + + getAuthData: function(accountData) { + let connection = { + get host() { + return "TbSync#" + accountData.accountID; + }, + + get username() { + return accountData.getAccountProperty("user"); + }, + + get password() { + // try new host first + let pw = TbSync.passwordManager.getLoginInfo(this.host, "TbSync/DAV", this.username); + if (pw) { + return pw; + } + + // try old host as fallback + let oldHost = accountData.getAccountProperty("calDavHost") ? accountData.getAccountProperty("calDavHost") : accountData.getAccountProperty("cardDavHost"); + if (oldHost.startsWith("http://")) oldHost = oldHost.substr(7); + if (oldHost.startsWith("https://")) oldHost = oldHost.substr(8); + pw = TbSync.passwordManager.getLoginInfo(oldHost, "TbSync/DAV", this.username); + if (pw) { + //migrate + this.updateLoginData(this.username, pw); + } + return pw; + }, + + updateLoginData: function(newUsername, newPassword) { + let oldUsername = this.username; + TbSync.passwordManager.updateLoginInfo(this.host, "TbSync/DAV", oldUsername, newUsername, newPassword); + // Also update the username of this account. + accountData.setAccountProperty("user", newUsername); + }, + + removeLoginData: function() { + TbSync.passwordManager.removeLoginInfos(this.host, "TbSync/DAV"); + } + }; + return connection; + }, + + ConnectionData: class { + constructor(data) { + this._password = ""; + this._username = ""; + this._https = ""; + this._type = ""; + this._fqdn = ""; + this._timeout = dav.Base.getConnectionTimeout(); + + //for error logging + this._eventLogInfo = null; + + //typof syncdata? + let folderData = null; + let accountData = null; + + if (data instanceof TbSync.SyncData) { + folderData = data.currentFolderData; + accountData = data.accountData; + this._eventLogInfo = data.eventLogInfo; + } else if (data instanceof TbSync.FolderData) { + folderData = data; + accountData = data.accountData; + this._eventLogInfo = new TbSync.EventLogInfo( + accountData.getAccountProperty("provider"), + accountData.getAccountProperty("accountname"), + accountData.accountID, + folderData.getFolderProperty("foldername")); + } else if (data instanceof TbSync.AccountData) { + accountData = data; + this._eventLogInfo = new TbSync.EventLogInfo( + accountData.getAccountProperty("provider"), + accountData.getAccountProperty("accountname"), + accountData.accountID, + ""); + } + + if (accountData) { + let authData = dav.network.getAuthData(accountData); + this._password = authData.password; + this._username = authData.username; + + this._accountname = accountData.getAccountProperty("accountname"); + if (folderData) { + this._fqdn = folderData.getFolderProperty("fqdn"); + this._https = folderData.getFolderProperty("https"); + } + this.accountData = accountData; + } + } + + + set password(v) {this._password = v;} + set username(v) {this._username = v;} + set timeout(v) {this._timeout = v;} + set https(v) {this._https = v;} + set fqdn(v) {this._fqdn = v;} + set eventLogInfo(v) {this._eventLogInfo = v;} + + get password() {return this._password;} + get username() {return this._username;} + get timeout() {return this._timeout;} + get https() {return this._https;} + get fqdn() {return this._fqdn;} + get eventLogInfo() {return this._eventLogInfo;} + }, + + + checkForRFC6764Request: async function (path, eventLogInfo) { + function checkDefaultSecPort (sec) { + return sec ? "443" : "80"; + } + + if (!this.isRFC6764Request(path)) { + return path; + } + + let parts = path.toLowerCase().split("6764://"); + let type = parts[0].endsWith("caldav") ? "caldav" : "carddav"; + + // obey preselected security level for DNS lookup + // and only use insecure option if specified + let scheme = parts[0].startsWith("httpca") ? "http" : "https"; //httpcaldav or httpcarddav = httpca = http + let sec = (scheme == "https"); + + let hostPath = parts[1]; + while (hostPath.endsWith("/")) { hostPath = hostPath.slice(0,-1); } + let host = hostPath.split("/")[0]; + + let result = {}; + + //only perform dns lookup, if the provided path does not contain any path information + if (host == hostPath) { + let request = "_" + type + (sec ? "s" : "") + "._tcp." + host; + + // get host from SRV record + let rv = await DNS.srv(request); + if (rv && Array.isArray(rv) && rv.length>0 && rv[0].host) { + result.secure = sec; + result.host = rv[0].host + ((checkDefaultSecPort(sec) == rv[0].port) ? "" : ":" + rv[0].port); + TbSync.eventlog.add("info", eventLogInfo, "RFC6764 DNS request succeeded", "SRV record @ " + request + "\n" + JSON.stringify(rv[0])); + + // Now try to get path from TXT + rv = await DNS.txt(request); + if (rv && Array.isArray(rv) && rv.length>0 && rv[0].data && rv[0].data.toLowerCase().startsWith("path=")) { + result.path = rv[0].data.substring(5); + TbSync.eventlog.add("info", eventLogInfo, "RFC6764 DNS request succeeded", "TXT record @ " + request + "\n" + JSON.stringify(rv[0])); + } else { + result.path = "/.well-known/" + type; + } + + result.url = "http" + (result.secure ? "s" : "") + "://" + result.host + result.path; + return result.url; + } else { + TbSync.eventlog.add("warning", eventLogInfo, "RFC6764 DNS request failed", "SRV record @ " + request); + } + } + + // use the provided hostPath and build standard well-known url + return scheme + "://" + hostPath + "/.well-known/" + type; + }, + + startsWithScheme: function (url) { + return (url.toLowerCase().startsWith("http://") || url.toLowerCase().startsWith("https://") || this.isRFC6764Request(url)); + }, + + isRFC6764Request: function (url) { + let parts = url.split("6764://"); + return (parts.length == 2 && parts[0].endsWith("dav")); + }, + + sendRequest: async function (requestData, path, method, connectionData, headers = {}, options = {}) { + let url = await this.checkForRFC6764Request(path, connectionData.eventLogInfo); + let enforcedPermanentlyRedirectedUrl = (url != path) ? url : null; + + // path could be absolute or relative, we may need to rebuild the full url. + if (url.startsWith("http://") || url.startsWith("https://")) { + // extract segments from url + let uri = Services.io.newURI(url); + connectionData.https = (uri.scheme == "https"); + connectionData.fqdn = uri.hostPort; + } else { + url = "http" + (connectionData.https ? "s" : "") + "://" + connectionData.fqdn + url; + } + + let currentSyncState = connectionData.accountData ? connectionData.accountData.syncData.getSyncState().state : ""; + let accountID = connectionData.accountData ? connectionData.accountData.accountID : ""; + + // Loop: Prompt user for password and retry + const MAX_RETRIES = options.hasOwnProperty("passwordRetries") ? options.passwordRetries+1 : 5; + for (let i=1; i <= MAX_RETRIES; i++) { + TbSync.dump("URL Request #" + i, url); + + connectionData.url = url; + + // Restore original syncstate before open the connection + if (connectionData.accountData && currentSyncState != connectionData.accountData.syncData.getSyncState().state) { + connectionData.accountData.syncData.setSyncState(currentSyncState); + } + + let r = await dav.network.promisifiedHttpRequest(requestData, method, connectionData, headers, options); + if (r && enforcedPermanentlyRedirectedUrl && !r.permanentlyRedirectedUrl) { + r.permanentlyRedirectedUrl = enforcedPermanentlyRedirectedUrl; + } + + if (r && r.passwordPrompt && r.passwordPrompt === true) { + if (i == MAX_RETRIES) { + // If this is the final retry, abort with error. + throw r.passwordError; + } else { + let credentials = null; + let retry = false; + + // Prompt, if connection belongs to an account (and not from the create wizard) + if (connectionData.accountData) { + let promptData = { + windowID: "auth:" + connectionData.accountData.accountID, + accountname: connectionData.accountData.getAccountProperty("accountname"), + usernameLocked: connectionData.accountData.isConnected(), + username: connectionData.username, + } + connectionData.accountData.syncData.setSyncState("passwordprompt"); + + credentials = await TbSync.passwordManager.asyncPasswordPrompt(promptData, dav.openWindows); + if (credentials) { + // update login data + dav.network.getAuthData(connectionData.accountData).updateLoginData(credentials.username, credentials.password); + // update connection data + connectionData.username = credentials.username; + connectionData.password = credentials.password; + retry = true; + } + } + + if (!retry) { + throw r.passwordError; + } + + } + } else { + return r; + } + } + }, + + // Promisified implementation of TbSync's HttpRequest (with XHR interface) + promisifiedHttpRequest: function (requestData, method, connectionData, headers, options) { + let responseData = ""; + + //do not log HEADERS, as it could contain an Authorization header + //TbSync.dump("HEADERS", JSON.stringify(headers)); + if (TbSync.prefs.getIntPref("log.userdatalevel") > 1) TbSync.dump("REQUEST", method + " : " + requestData); + + if (!options.hasOwnProperty("softfail")) { + options.softfail = []; + } + + if (!options.hasOwnProperty("responseType")) { + options.responseType = "xml"; + } + + return new Promise(function(resolve, reject) { + let req = new HttpRequest(); + + req.timeout = connectionData.timeout; + req.mozBackgroundRequest = true; + + req.open(method, connectionData.url, true, connectionData.username, connectionData.password); + + if (options.hasOwnProperty("containerRealm")) req.setContainerRealm(options.containerRealm); + if (options.hasOwnProperty("containerReset") && options.containerReset == true) req.clearContainerCache(); + + if (headers) { + for (let header in headers) { + req.setRequestHeader(header, headers[header]); + } + } + + if (options.responseType == "base64") { + req.responseAsBase64 = true; + } + + req.setRequestHeader("User-Agent", dav.sync.prefSettings.getCharPref("clientID.useragent")); + + req.realmCallback = function(username, realm, host) { + // Store realm, needed later to setup lightning passwords. + TbSync.dump("Found CalDAV authRealm for <"+host+">", realm); + connectionData.realm = realm; + }; + + req.onerror = function () { + let error = TbSync.network.createTCPErrorFromFailedXHR(req); + if (!error) { + return reject(dav.sync.finish("error", "networkerror", "URL:\n" + connectionData.url + " ("+method+")")); //reject/resolve do not terminate control flow + } else { + return reject(dav.sync.finish("error", error, "URL:\n" + connectionData.url + " ("+method+")")); + } + }; + + req.ontimeout = req.onerror; + + req.onredirect = function(flags, uri) { + console.log("Redirect ("+ flags.toString(2) +"): " + uri.spec); + // Update connection settings from current URL + let newHttps = (uri.scheme == "https"); + if (connectionData.https != newHttps) { + TbSync.dump("Updating HTTPS", connectionData.https + " -> " + newHttps); + connectionData.https = newHttps; + } + if (connectionData.fqdn !=uri.hostPort) { + TbSync.dump("Updating FQDN", connectionData.fqdn + " -> " + uri.hostPort); + connectionData.fqdn = uri.hostPort; + } + }; + + req.onload = function() { + if (TbSync.prefs.getIntPref("log.userdatalevel") > 1) TbSync.dump("RESPONSE", req.status + " ("+req.statusText+")" + " : " + req.responseText); + responseData = req.responseText.split("><").join(">\n<"); + + let commLog = "URL:\n" + connectionData.url + " ("+method+")" + "\n\nRequest:\n" + requestData + "\n\nResponse:\n" + responseData; + let aResult = req.responseText; + let responseStatus = req.status; + + switch(responseStatus) { + case 401: //AuthError + { + let response = {}; + response.passwordPrompt = true; + response.passwordError = dav.sync.finish("error", responseStatus, commLog); + return resolve(response); + } + break; + + case 207: //preprocess multiresponse + { + let xml = dav.tools.convertToXML(aResult); + if (xml === null) return reject(dav.sync.finish("warning", "malformed-xml", commLog)); + + let response = {}; + response.davOptions = req.getResponseHeader("dav"); + response.responseURL = req.responseURL; + response.permanentlyRedirectedUrl = req.permanentlyRedirectedUrl; + response.commLog = commLog; + response.node = xml.documentElement; + + let multi = xml.documentElement.getElementsByTagNameNS(dav.sync.ns.d, "response"); + response.multi = []; + for (let i=0; i < multi.length; i++) { + let hrefNode = dav.tools.evaluateNode(multi[i], [["d","href"]]); + let responseStatusNode = dav.tools.evaluateNode(multi[i], [["d", "status"]]); + let propstats = multi[i].getElementsByTagNameNS(dav.sync.ns.d, "propstat"); + if (propstats.length > 0) { + //response contains propstats, push each as single entry + for (let p=0; p < propstats.length; p++) { + let statusNode = dav.tools.evaluateNode(propstats[p], [["d", "status"]]); + + let resp = {}; + resp.node = propstats[p]; + resp.status = statusNode === null ? null : statusNode.textContent.split(" ")[1]; + resp.responsestatus = responseStatusNode === null ? null : responseStatusNode.textContent.split(" ")[1]; + resp.href = hrefNode === null ? null : hrefNode.textContent; + response.multi.push(resp); + } + } else { + //response does not contain any propstats, push raw response + let resp = {}; + resp.node = multi[i]; + resp.status = responseStatusNode === null ? null : responseStatusNode.textContent.split(" ")[1]; + resp.responsestatus = responseStatusNode === null ? null : responseStatusNode.textContent.split(" ")[1]; + resp.href = hrefNode === null ? null : hrefNode.textContent; + response.multi.push(resp); + } + } + + return resolve(response); + } + + + case 200: //returned by DELETE by radicale - watch this !!! + return resolve(aResult); + + case 204: //is returned by DELETE - no data + case 201: //is returned by CREATE - no data + return resolve(null); + break; + + default: + if (options.softfail.includes(responseStatus)) { + let noresponse = {}; + noresponse.softerror = responseStatus; + let xml = dav.tools.convertToXML(aResult); + if (xml !== null) { + let exceptionNode = dav.tools.evaluateNode(xml.documentElement, [["s","exception"]]); + if (exceptionNode !== null) { + noresponse.exception = exceptionNode.textContent; + } + } + //manually log this non-fatal error + TbSync.eventlog.add("info", connectionData.eventLogInfo, "softerror::"+responseStatus, commLog); + return resolve(noresponse); + } else { + return reject(dav.sync.finish("warning", responseStatus, commLog)); + } + break; + + } + }; + + req.send(requestData); + }); + } +} diff --git a/content/includes/sync.js b/content/includes/sync.js new file mode 100644 index 0000000..5a73bb3 --- /dev/null +++ b/content/includes/sync.js @@ -0,0 +1,462 @@ +/* +/* + * This file is part of DAV-4-TbSync. + * + * 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, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +"use strict"; + +const { CardDAVDirectory } = ChromeUtils.import( + "resource:///modules/CardDAVDirectory.jsm" +); + +var sync = { + + finish: function (aStatus = "", msg = "", details = "") { + let status = TbSync.StatusData.SUCCESS + switch (aStatus) { + + case "": + case "ok": + status = TbSync.StatusData.SUCCESS; + break; + + case "info": + status = TbSync.StatusData.INFO; + break; + + case "resyncAccount": + status = TbSync.StatusData.ACCOUNT_RERUN; + break; + + case "resyncFolder": + status = TbSync.StatusData.FOLDER_RERUN; + break; + + case "warning": + status = TbSync.StatusData.WARNING; + break; + + case "error": + status = TbSync.StatusData.ERROR; + break; + + default: + console.log("TbSync/DAV: Unknown status <"+aStatus+">"); + status = TbSync.StatusData.ERROR; + break; + } + + let e = new Error(); + e.name = "dav4tbsync"; + e.message = status.toUpperCase() + ": " + msg.toString() + " (" + details.toString() + ")"; + e.statusData = new TbSync.StatusData(status, msg.toString(), details.toString()); + return e; + }, + + prefSettings: Services.prefs.getBranch("extensions.dav4tbsync."), + + ns: { + d: "DAV:", + cal: "urn:ietf:params:xml:ns:caldav" , + card: "urn:ietf:params:xml:ns:carddav" , + cs: "http://calendarserver.org/ns/", + s: "http://sabredav.org/ns", + apple: "http://apple.com/ns/ical/" + }, + + serviceproviders: { + "fruux" : {revision: 1, icon: "fruux", caldav: "https://dav.fruux.com", carddav: "https://dav.fruux.com"}, + "mbo" : {revision: 1, icon: "mbo", caldav: "caldav6764://mailbox.org", carddav: "carddav6764://mailbox.org"}, + "icloud" : {revision: 1, icon: "icloud", caldav: "https://caldav.icloud.com", carddav: "https://contacts.icloud.com"}, + "gmx.net" : {revision: 1, icon: "gmx", caldav: "caldav6764://gmx.net", carddav: "carddav6764://gmx.net"}, + "gmx.com" : {revision: 1, icon: "gmx", caldav: "caldav6764://gmx.com", carddav: "carddav6764://gmx.com"}, + "posteo" : {revision: 1, icon: "posteo", caldav: "https://posteo.de:8443", carddav: "posteo.de:8843"}, + "web.de" : {revision: 1, icon: "web", caldav: "caldav6764://web.de", carddav: "carddav6764://web.de"}, + "yahoo" : {revision: 1, icon: "yahoo", caldav: "caldav6764://yahoo.com", carddav: "carddav6764://yahoo.com"}, + }, + + resetFolderSyncInfo : function (folderData) { + folderData.resetFolderProperty("ctag"); + folderData.resetFolderProperty("token"); + folderData.setFolderProperty("createdWithProviderVersion", folderData.accountData.providerData.getVersion()); + }, + + folderList: async function (syncData) { + //Method description: http://sabre.io/dav/building-a-caldav-client/ + //get all folders currently known + let folderTypes = ["caldav", "carddav", "ics"]; + let unhandledFolders = {}; + for (let type of folderTypes) { + unhandledFolders[type] = []; + } + + + let folders = syncData.accountData.getAllFolders(); + for (let folder of folders) { + //just in case + if (!unhandledFolders.hasOwnProperty(folder.getFolderProperty("type"))) { + unhandledFolders[folder.getFolderProperty("type")] = []; + } + unhandledFolders[folder.getFolderProperty("type")].push(folder); + } + + // refresh urls of service provider, if they have been updated + let serviceprovider = syncData.accountData.getAccountProperty("serviceprovider"); + let serviceproviderRevision = syncData.accountData.getAccountProperty("serviceproviderRevision"); + if (dav.sync.serviceproviders.hasOwnProperty(serviceprovider) && serviceproviderRevision != dav.sync.serviceproviders[serviceprovider].revision) { + TbSync.eventlog.add("info", syncData.eventLogInfo, "updatingServiceProvider", serviceprovider); + syncData.accountData.setAccountProperty("serviceproviderRevision", dav.sync.serviceproviders[serviceprovider].revision); + syncData.accountData.resetAccountProperty("calDavPrincipal"); + syncData.accountData.resetAccountProperty("cardDavPrincipal"); + syncData.accountData.setAccountProperty("calDavHost", dav.sync.serviceproviders[serviceprovider].caldav); + syncData.accountData.setAccountProperty("cardDavHost", dav.sync.serviceproviders[serviceprovider].carddav); + } + + let davjobs = { + cal : {server: syncData.accountData.getAccountProperty("calDavHost")}, + card : {server: syncData.accountData.getAccountProperty("cardDavHost")}, + }; + + for (let job in davjobs) { + if (!davjobs[job].server) continue; + + // SOGo needs some special handling for shared addressbooks. We detect + // it by having SOGo/dav in the url. + let isSogo = davjobs[job].server.includes("/SOGo/dav"); + + // sync states are only printed while the account state is "syncing" + // to inform user about sync process (it is not stored in DB, just in + // syncData) + // example state "getfolders" to get folder information from server + // if you send a request to a server and thus have to wait for answer, + // use a "send." syncstate, which will give visual feedback to the user, + // that we are waiting for an answer with timeout countdown + + let home = []; + let own = []; + + // migration code for http setting, we might keep it as a fallback, if user removed the http:// scheme from the url in the settings + if (!dav.network.startsWithScheme(davjobs[job].server)) { + davjobs[job].server = "http" + (syncData.accountData.getAccountProperty("https") ? "s" : "") + "://" + davjobs[job].server; + syncData.accountData.setAccountProperty(job + "DavHost", davjobs[job].server); + } + + //add connection to syncData + syncData.connectionData = new dav.network.ConnectionData(syncData); + + //only do that, if a new calendar has been enabled + TbSync.network.resetContainerForUser(syncData.connectionData.username); + + syncData.setSyncState("send.getfolders"); + let principal = syncData.accountData.getAccountProperty(job + "DavPrincipal"); // defaults to null + if (principal === null) { + + let response = await dav.network.sendRequest("<d:propfind "+dav.tools.xmlns(["d"])+"><d:prop><d:current-user-principal /></d:prop></d:propfind>", davjobs[job].server , "PROPFIND", syncData.connectionData, {"Depth": "0", "Prefer": "return=minimal"}); + syncData.setSyncState("eval.folders"); + + // keep track of permanent redirects for the server URL + if (response && response.permanentlyRedirectedUrl) { + syncData.accountData.setAccountProperty(job + "DavHost", response.permanentlyRedirectedUrl) + } + + // store dav options send by server + if (response && response.davOptions) { + syncData.accountData.setAccountProperty(job + "DavOptions", response.davOptions.split(",").map(e => e.trim())); + } + + // allow 404 because iCloud sends it on valid answer (yeah!) + if (response && response.multi) { + principal = dav.tools.getNodeTextContentFromMultiResponse(response, [["d","prop"], ["d","current-user-principal"], ["d","href"]], null, ["200","404"]); + } + } + + //principal now contains something like "/remote.php/carddav/principals/john.bieling/" + //principal can also be an absolute url + // -> get home/root of storage + if (principal !== null) { + syncData.setSyncState("send.getfolders"); + + let options = syncData.accountData.getAccountProperty(job + "DavOptions"); + + let homeset = (job == "cal") + ? "calendar-home-set" + : "addressbook-home-set"; + + let request = "<d:propfind "+dav.tools.xmlns(["d", job, "cs"])+"><d:prop><"+job+":" + homeset + " />" + + (job == "cal" && options.includes("calendar-proxy") ? "<cs:calendar-proxy-write-for /><cs:calendar-proxy-read-for />" : "") + + "<d:group-membership />" + + "</d:prop></d:propfind>"; + + let response = await dav.network.sendRequest(request, principal, "PROPFIND", syncData.connectionData, {"Depth": "0", "Prefer": "return=minimal"}); + syncData.setSyncState("eval.folders"); + + // keep track of permanent redirects for the principal URL + if (response && response.permanentlyRedirectedUrl) { + principal = response.permanentlyRedirectedUrl; + } + + own = dav.tools.getNodesTextContentFromMultiResponse(response, [["d","prop"], [job, homeset ], ["d","href"]], principal); + home = own.concat(dav.tools.getNodesTextContentFromMultiResponse(response, [["d","prop"], ["cs", "calendar-proxy-read-for" ], ["d","href"]], principal)); + home = home.concat(dav.tools.getNodesTextContentFromMultiResponse(response, [["d","prop"], ["cs", "calendar-proxy-write-for" ], ["d","href"]], principal)); + + //Any groups we need to find? Only diving one level at the moment, + let g = dav.tools.getNodesTextContentFromMultiResponse(response, [["d","prop"], ["d", "group-membership" ], ["d","href"]], principal); + for (let gc=0; gc < g.length; gc++) { + //SOGo reports a 403 if I request the provided resource, also since we do not dive, remove the request for group-membership + response = await dav.network.sendRequest(request.replace("<d:group-membership />",""), g[gc], "PROPFIND", syncData.connectionData, {"Depth": "0", "Prefer": "return=minimal"}, {softfail: [403, 404]}); + if (response && response.softerror) { + continue; + } + home = home.concat(dav.tools.getNodesTextContentFromMultiResponse(response, [["d","prop"], [job, homeset ], ["d","href"]], g[gc])); + } + + //calendar-proxy and group-membership could have returned the same values, make the homeset unique + home = home.filter((v,i,a) => a.indexOf(v) == i); + } else { + // do not throw here, but log the error and skip this server + TbSync.eventlog.add("error", syncData.eventLogInfo, job+"davservernotfound", davjobs[job].server); + } + + //home now contains something like /remote.php/caldav/calendars/john.bieling/ + // -> get all resources + if (home.length > 0) { + // the used principal returned valid resources, store/update it + // as the principal is being used as a starting point, it must be stored as absolute url + syncData.accountData.setAccountProperty(job + "DavPrincipal", dav.network.startsWithScheme(principal) + ? principal + : "http" + (syncData.connectionData.https ? "s" : "") + "://" + syncData.connectionData.fqdn + principal); + + for (let h=0; h < home.length; h++) { + syncData.setSyncState("send.getfolders"); + let request = (job == "cal") + ? "<d:propfind "+dav.tools.xmlns(["d","apple","cs","cal"])+"><d:prop><d:current-user-privilege-set/><d:resourcetype /><d:displayname /><apple:calendar-color/><cs:source/><cal:supported-calendar-component-set/></d:prop></d:propfind>" + : "<d:propfind "+dav.tools.xmlns(["d"])+"><d:prop><d:current-user-privilege-set/><d:resourcetype /><d:displayname /></d:prop></d:propfind>"; + + //some servers report to have calendar-proxy-read but return a 404 when that gets actually queried + let response = await dav.network.sendRequest(request, home[h], "PROPFIND", syncData.connectionData, {"Depth": "1", "Prefer": "return=minimal"}, {softfail: [403, 404]}); + if (response && response.softerror) { + continue; + } + + for (let r=0; r < response.multi.length; r++) { + if (response.multi[r].status != "200") continue; + + let resourcetype = null; + //is this a result with a valid recourcetype? (the node must be present) + switch (job) { + case "card": + if (dav.tools.evaluateNode(response.multi[r].node, [["d","prop"], ["d","resourcetype"], ["card", "addressbook"]]) !== null) resourcetype = "carddav"; + break; + + case "cal": + if (dav.tools.evaluateNode(response.multi[r].node, [["d","prop"], ["d","resourcetype"], ["cal", "calendar"]]) !== null) resourcetype = "caldav"; + else if (dav.tools.evaluateNode(response.multi[r].node, [["d","prop"], ["d","resourcetype"], ["cs", "subscribed"]]) !== null) resourcetype = "ics"; + break; + } + if (resourcetype === null) continue; + + //get ACL (grant read rights per default, if it is SOGo, as they do not send that permission) + let acl = isSogo ? 0x1 : 0; + + let privilegNode = dav.tools.evaluateNode(response.multi[r].node, [["d","prop"], ["d","current-user-privilege-set"]]); + if (privilegNode) { + if (privilegNode.getElementsByTagNameNS(dav.sync.ns.d, "all").length > 0) { + acl = 0xF; //read=1, mod=2, create=4, delete=8 + } else { + // check for individual write permissions + if (privilegNode.getElementsByTagNameNS(dav.sync.ns.d, "write").length > 0) { + acl = 0xF; + } else { + if (privilegNode.getElementsByTagNameNS(dav.sync.ns.d, "write-content").length > 0) acl |= 0x2; + if (privilegNode.getElementsByTagNameNS(dav.sync.ns.d, "bind").length > 0) acl |= 0x4; + if (privilegNode.getElementsByTagNameNS(dav.sync.ns.d, "unbind").length > 0) acl |= 0x8; + } + + // check for read permission (implying read if any write is given) + if (privilegNode.getElementsByTagNameNS(dav.sync.ns.d, "read").length > 0 || acl != 0) acl |= 0x1; + } + } + + //ignore this resource, if no read access + if ((acl & 0x1) == 0) continue; + + let href = response.multi[r].href; + if (resourcetype == "ics") href = dav.tools.evaluateNode(response.multi[r].node, [["d","prop"], ["cs","source"], ["d","href"]]).textContent; + + let name_node = dav.tools.evaluateNode(response.multi[r].node, [["d","prop"], ["d","displayname"]]); + let name = TbSync.getString("defaultname." + ((job == "cal") ? "calendar" : "contacts") , "dav"); + if (name_node != null) { + name = name_node.textContent; + } + let color = dav.tools.evaluateNode(response.multi[r].node, [["d","prop"], ["apple","calendar-color"]]); + let supportedCalComponent = dav.tools.evaluateNode(response.multi[r].node, [["d","prop"], ["cal","supported-calendar-component-set"]]); + if (supportedCalComponent) { + supportedCalComponent = Array.from(supportedCalComponent.children, e => e.getAttribute("name")); + } else { + supportedCalComponent = []; + } + if (job == "cal" && supportedCalComponent.length > 0 && !supportedCalComponent.includes("VTODO") && !supportedCalComponent.includes("VEVENT")) { + // This does not seem to be a valid resource. + continue; + } + + //remove found folder from list of unhandled folders + unhandledFolders[resourcetype] = unhandledFolders[resourcetype].filter(item => item.getFolderProperty("href") !== href); + + + // interaction with TbSync + // do we have a folder for that href? + let folderData = syncData.accountData.getFolder("href", href); + if (!folderData) { + // create a new folder entry + folderData = syncData.accountData.createNewFolder(); + // this MUST be set to either "addressbook" or "calendar" to use the standard target support, or any other value, which + // requires a corresponding targets implementation by this provider + folderData.setFolderProperty("targetType", (job == "card") ? "addressbook" : "calendar"); + + folderData.setFolderProperty("href", href); + folderData.setFolderProperty("foldername", name); + folderData.setFolderProperty("type", resourcetype); + folderData.setFolderProperty("supportedCalComponent", supportedCalComponent); + folderData.setFolderProperty("shared", !own.includes(home[h])); + folderData.setFolderProperty("acl", acl.toString()); + folderData.setFolderProperty("downloadonly", (acl == 0x1)); //if any write access is granted, setup as writeable + + //we assume the folder has the same fqdn as the homeset, otherwise href must contain the full URL and the fqdn is ignored + folderData.setFolderProperty("fqdn", syncData.connectionData.fqdn); + folderData.setFolderProperty("https", syncData.connectionData.https); + + //do we have a cached folder? + let cachedFolderData = syncData.accountData.getFolderFromCache("href", href); + if (cachedFolderData) { + // copy fields from cache which we want to re-use + folderData.setFolderProperty("targetColor", cachedFolderData.getFolderProperty("targetColor")); + folderData.setFolderProperty("targetName", cachedFolderData.getFolderProperty("targetName")); + //if we have only READ access, do not restore cached value for downloadonly + if (acl > 0x1) folderData.setFolderProperty("downloadonly", cachedFolderData.getFolderProperty("downloadonly")); + } + } else { + //Update name & color + folderData.setFolderProperty("foldername", name); + folderData.setFolderProperty("fqdn", syncData.connectionData.fqdn); + folderData.setFolderProperty("https", syncData.connectionData.https); + folderData.setFolderProperty("acl", acl); + //if the acl changed from RW to RO we need to update the downloadonly setting + if (acl == 0x1) { + folderData.setFolderProperty("downloadonly", true); + } + } + + // Update color from server. + if (color && job == "cal") { + color = color.textContent.substring(0,7); + folderData.setFolderProperty("targetColor", color); + + // Do we have to update the calendar? + if (folderData.targetData && folderData.targetData.hasTarget()) { + try { + let targetCal = await folderData.targetData.getTarget(); + targetCal.calendar.setProperty("color", color); + } catch (e) { + Components.utils.reportError(e) + } + } + } + } + } + } else { + //home was not found - connection error? - do not delete unhandled folders + switch (job) { + case "card": + unhandledFolders.carddav = []; + break; + + case "cal": + unhandledFolders.caldav = []; + unhandledFolders.ics = []; + break; + } + //reset stored principal + syncData.accountData.resetAccountProperty(job + "DavPrincipal"); + } + } + + // Remove unhandled old folders, (because they no longer exist on the server). + // Do not delete the targets, but keep them as stale/unconnected elements. + for (let type of folderTypes) { + for (let folder of unhandledFolders[type]) { + folder.remove("[deleted on server]"); + } + } + }, + + + + + + + folder: async function (syncData) { + // add connection data to syncData + syncData.connectionData = new dav.network.ConnectionData(syncData); + + // add target to syncData + let hadTarget; + try { + // accessing the target for the first time will check if it is avail and if not will create it (if possible) + hadTarget = syncData.currentFolderData.targetData.hasTarget(); + syncData.target = await syncData.currentFolderData.targetData.getTarget(); + } catch (e) { + Components.utils.reportError(e); + throw dav.sync.finish("warning", e.message); + } + + switch (syncData.currentFolderData.getFolderProperty("type")) { + case "carddav": + { + // update downloadonly - we do not use AbDirectory (syncData.target) but the underlying thunderbird addressbook obj + if (syncData.currentFolderData.getFolderProperty("downloadonly")) syncData.target.directory.setBoolValue("readOnly", true); + + try { + let davDirectory = CardDAVDirectory.forFile(syncData.target.directory.fileName); + if (!hadTarget) { + davDirectory.fetchAllFromServer(); + } else { + davDirectory.syncWithServer(); + } + } catch (ex) { + throw dav.sync.finish("error", "non-carddav-addrbook"); + } + + throw dav.sync.finish("ok", "managed-by-thunderbird"); + } + break; + + case "caldav": + case "ics": + { + // update downloadonly - we do not use TbCalendar (syncData.target) but the underlying lightning calendar obj + if (syncData.currentFolderData.getFolderProperty("downloadonly")) syncData.target.calendar.setProperty("readOnly", true); + + // update username of calendar + syncData.target.calendar.setProperty("username", syncData.connectionData.username); + + //init sync via lightning + if (hadTarget) syncData.target.calendar.refresh(); + + throw dav.sync.finish("ok", "managed-by-thunderbird"); + } + break; + + default: + { + throw dav.sync.finish("warning", "notsupported"); + } + break; + } + }, + +} diff --git a/content/includes/tools.js b/content/includes/tools.js new file mode 100644 index 0000000..df51d79 --- /dev/null +++ b/content/includes/tools.js @@ -0,0 +1,198 @@ +/* + * This file is part of DAV-4-TbSync. + * + * 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, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +"use strict"; + +var tools = { + + //* * * * * * * * * * * * * + //* UTILS + //* * * * * * * * * * * * * + + /** + * Removes XML-invalid characters from a string. + * @param {string} string - a string potentially containing XML-invalid characters, such as non-UTF8 characters, STX, EOX and so on. + * @param {boolean} removeDiscouragedChars - a string potentially containing XML-invalid characters, such as non-UTF8 characters, STX, EOX and so on. + * @returns : a sanitized string without all the XML-invalid characters. + * + * Source: https://www.ryadel.com/en/javascript-remove-xml-invalid-chars-characters-string-utf8-unicode-regex/ + */ + removeXMLInvalidChars: function (string, removeDiscouragedChars = true) + { + // remove everything forbidden by XML 1.0 specifications, plus the unicode replacement character U+FFFD + var regex = /((?:[\0-\x08\x0B\f\x0E-\x1F\uFFFD\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]))/g; + string = string.replace(regex, ""); + + if (removeDiscouragedChars) { + // remove everything not suggested by XML 1.0 specifications + regex = new RegExp( + "([\\x7F-\\x84]|[\\x86-\\x9F]|[\\uFDD0-\\uFDEF]|(?:\\uD83F[\\uDFFE\\uDFFF])|(?:\\uD87F[\\uDF"+ + "FE\\uDFFF])|(?:\\uD8BF[\\uDFFE\\uDFFF])|(?:\\uD8FF[\\uDFFE\\uDFFF])|(?:\\uD93F[\\uDFFE\\uD"+ + "FFF])|(?:\\uD97F[\\uDFFE\\uDFFF])|(?:\\uD9BF[\\uDFFE\\uDFFF])|(?:\\uD9FF[\\uDFFE\\uDFFF])"+ + "|(?:\\uDA3F[\\uDFFE\\uDFFF])|(?:\\uDA7F[\\uDFFE\\uDFFF])|(?:\\uDABF[\\uDFFE\\uDFFF])|(?:\\"+ + "uDAFF[\\uDFFE\\uDFFF])|(?:\\uDB3F[\\uDFFE\\uDFFF])|(?:\\uDB7F[\\uDFFE\\uDFFF])|(?:\\uDBBF"+ + "[\\uDFFE\\uDFFF])|(?:\\uDBFF[\\uDFFE\\uDFFF])(?:[\\0-\\t\\x0B\\f\\x0E-\\u2027\\u202A-\\uD7FF\\"+ + "uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|"+ + "(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF]))", "g"); + string = string.replace(regex, ""); + } + + return string; + }, + + xmlns: function (ns) { + let _xmlns = []; + for (let i=0; i < ns.length; i++) { + _xmlns.push('xmlns:'+ns[i]+'="'+dav.sync.ns[ns[i]]+'"'); + } + return _xmlns.join(" "); + }, + + parseUri: function (aUri) { + let uri; + try { + // Test if the entered uri can be parsed. + uri = Services.io.newURI(aUri, null, null); + } catch (ex) { + throw new Error("invalid-calendar-url"); + } + return uri; + }, + + parseVcardDateTime: function ( newServerValue, metadata ) { + if (!newServerValue) { + return false; + } + + /* + ** This accepts RFC2426 BDAY values (with/without hyphens), + ** though TB doesn't handle the time part of date-times, so we discard it. + */ + let bday = newServerValue.match( /^(\d{4})-?(\d{2})-?(\d{2})/ ); + if (!bday) { + return false; + } + + /* + ** Apple Contacts shoehorns date with missing year into vcard3 thus: BDAY;X-APPLE-OMIT-YEAR=1604:1604-03-15 + ** Later in vcard4, it will be represented as BDAY:--0315 + */ + if (metadata + && metadata['x-apple-omit-year'] + && metadata['x-apple-omit-year'] == bday[1]) { + bday[1] = ''; + } + return bday; + }, + + //* * * * * * * * * * * * * * + //* EVALUATE XML RESPONSES * + //* * * * * * * * * * * * * * + + convertToXML: function(text) { + //try to convert response body to xml + let xml = null; + let oParser = new DOMParser(); + try { + xml = oParser.parseFromString(dav.tools.removeXMLInvalidChars(text), "application/xml"); + } catch (e) { + //however, domparser does not throw an error, it returns an error document + //https://developer.mozilla.org/de/docs/Web/API/DOMParser + xml = null; + } + //check if xml is error document + if (xml && xml.documentElement.nodeName == "parsererror") { + xml = null; + } + + return xml; + }, + + evaluateNode: function (_node, path) { + let node = _node; + let valid = false; + + for (let i=0; i < path.length; i++) { + + let children = node.children; + valid = false; + + for (let c=0; c < children.length; c++) { + if (children[c].localName == path[i][1] && children[c].namespaceURI == dav.sync.ns[path[i][0]]) { + node = children[c]; + valid = true; + break; + } + } + + if (!valid) { + //none of the children matched the path abort + return null; + } + } + + if (valid) return node; + return null; + }, + + hrefMatch:function (_requestHref, _responseHref) { + if (_requestHref === null) + return true; + + let requestHref = _requestHref; + let responseHref = _responseHref; + while (requestHref.endsWith("/")) { requestHref = requestHref.slice(0,-1); } + while (responseHref.endsWith("/")) { responseHref = responseHref.slice(0,-1); } + if (requestHref.endsWith(responseHref) || decodeURIComponent(requestHref).endsWith(responseHref) || requestHref.endsWith(decodeURIComponent(responseHref))) + return true; + + return false; + }, + + getNodeTextContentFromMultiResponse: function (response, path, href = null, status = ["200"]) { + for (let i=0; i < response.multi.length; i++) { + let node = dav.tools.evaluateNode(response.multi[i].node, path); + if (node !== null && dav.tools.hrefMatch(href, response.multi[i].href) && status.includes(response.multi[i].status)) { + return node.textContent; + } + } + return null; + }, + + getNodesTextContentFromMultiResponse: function (response, path, href = null, status = "200") { + //remove last element from path + let lastPathElement = path.pop(); + let rv = []; + + for (let i=0; i < response.multi.length; i++) { + let node = dav.tools.evaluateNode(response.multi[i].node, path); + if (node !== null && dav.tools.hrefMatch(href, response.multi[i].href) && response.multi[i].status == status) { + + //get all children + let children = node.getElementsByTagNameNS(dav.sync.ns[lastPathElement[0]], lastPathElement[1]); + for (let c=0; c < children.length; c++) { + if (children[c].textContent) rv.push(children[c].textContent); + } + } + } + return rv; + }, + + getMultiGetRequest: function(hrefs) { + let request = "<card:addressbook-multiget "+dav.tools.xmlns(["d", "card"])+"><d:prop><d:getetag /><card:address-data /></d:prop>"; + let counts = 0; + for (let i=0; i < hrefs.length; i++) { + request += "<d:href>"+hrefs[i]+"</d:href>"; + counts++; + } + request += "</card:addressbook-multiget>"; + + if (counts > 0) return request; + else return null; + }, +} diff --git a/content/locales.js b/content/locales.js new file mode 100644 index 0000000..a8beca0 --- /dev/null +++ b/content/locales.js @@ -0,0 +1,3 @@ +var { TbSync } = ChromeUtils.import("chrome://tbsync/content/tbsync.jsm"); + +TbSync.localizeOnLoad(window, "dav"); diff --git a/content/manager/createAccount.js b/content/manager/createAccount.js new file mode 100644 index 0000000..3e331fe --- /dev/null +++ b/content/manager/createAccount.js @@ -0,0 +1,524 @@ +/* + * This file is part of DAV-4-TbSync. + * + * 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, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +"use strict"; + +var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +var { TbSync } = ChromeUtils.import("chrome://tbsync/content/tbsync.jsm"); + +const dav = TbSync.providers.dav; + +var tbSyncDavNewAccount = { + + // standard data fields + get elementName() { return document.getElementById('tbsync.newaccount.name'); }, + get elementUser() { return document.getElementById('tbsync.newaccount.user'); }, + get elementPass() { return document.getElementById('tbsync.newaccount.password'); }, + get elementServer() { return document.getElementById('tbsync.newaccount.server'); }, + get elementCalDavServer() { return document.getElementById('tbsync.newaccount.caldavserver'); }, + get elementCardDavServer() { return document.getElementById('tbsync.newaccount.carddavserver'); }, + get serviceproviderlist() { return document.getElementById('tbsync.newaccount.serviceproviderlist'); }, + + get accountname() { return this.elementName.value.trim(); }, + get username() { return this.elementUser.value.trim(); }, + get password() { return this.elementPass.value.trim(); }, + get server() { return this.elementServer.value.trim(); }, + get calDavServer() { return this.elementCalDavServer.value.trim(); }, + get cardDavServer() { return this.elementCardDavServer.value.trim(); }, + get serviceprovider() { return this.serviceproviderlist.value; }, + get userdomain() { + let parts = this.username.split("@"); + if (parts.length == 2) { + let subparts = parts[1].split("."); + if (subparts.length > 1 && subparts[subparts.length-1].length > 1) return parts[1]; + } + return null; + }, + + set accountname(v) { this.elementName.value = v; }, + set username(v) { this.elementUser.value = v; }, + set password(v) { this.elementPass.value = v; }, + set server(v) { this.elementServer.value = v; }, + set calDavServer(v) { this.elementCalDavServer.value = v; }, + set cardDavServer(v) { this.elementCardDavServer.value = v; }, + + + + // final data fields on final page + get elementFinalName() { return document.getElementById('tbsync.finalaccount.name'); }, + get elementFinalUser() { return document.getElementById('tbsync.finalaccount.user'); }, + get elementFinalCalDavServer() { return document.getElementById('tbsync.finalaccount.caldavserver'); }, + get elementFinalCardDavServer() { return document.getElementById('tbsync.finalaccount.carddavserver'); }, + + get finalAccountname() { return this.elementFinalName.value.trim(); }, + get finalUsername() { return this.elementFinalUser.value.trim(); }, + get finalCalDavServer() { return this.elementFinalCalDavServer.value.trim(); }, + get finalCardDavServer() { return this.elementFinalCardDavServer.value.trim(); }, + + set finalAccountname(v) { this.elementFinalName.value = v;}, + set finalUsername(v) { + this.elementFinalUser.value = v; + this.elementFinalUser.setAttribute("tooltiptext", v); + }, + set finalCalDavServer(v) { + this.elementFinalCalDavServer.value = v; + this.elementFinalCalDavServer.setAttribute("tooltiptext", v); + document.getElementById("tbsyncfinalaccount.caldavserver.row").hidden = (v.trim() == ""); + }, + set finalCardDavServer(v) { + this.elementFinalCardDavServer.value = v; + this.elementFinalCardDavServer.setAttribute("tooltiptext", v); + document.getElementById("tbsyncfinalaccount.carddavserver.row").hidden = (v.trim() == ""); + }, + + get validated() { return this._validated || false; }, + set validated(v) { + this._validated = v; + if (v) { + this.finalAccountname = this.accountname; + } else { + this.finalAccountname = ""; + this.finalUsername = ""; + this.finalCalDavServer = ""; + this.finalCardDavServer = ""; + } + }, + + + showSpinner: function(spinnerText) { + document.getElementById("tbsync.spinner").hidden = false; + document.getElementById("tbsync.spinner.label").value = TbSync.getString("add.spinner." + spinnerText, "dav"); + }, + + hideSpinner: function() { + document.getElementById("tbsync.spinner").hidden = true; + }, + + onLoad: function () { + this.providerData = new TbSync.ProviderData("dav"); + + //init list + this.serviceproviderlist.appendChild(this.addProviderEntry("sabredav32.png", "discovery")); + this.serviceproviderlist.appendChild(this.addProviderEntry("sabredav32.png", "custom")); + for (let p in dav.sync.serviceproviders) { + this.serviceproviderlist.appendChild(this.addProviderEntry(dav.sync.serviceproviders[p].icon +"32.png", p)); + } + + document.addEventListener("wizardfinish", tbSyncDavNewAccount.onFinish.bind(this)); + document.addEventListener("wizardnext", tbSyncDavNewAccount.onAdvance.bind(this)); + document.addEventListener("wizardcancel", tbSyncDavNewAccount.onCancel.bind(this)); + document.getElementById("firstPage").addEventListener("pageshow", tbSyncDavNewAccount.resetFirstPage.bind(this)); + document.getElementById("secondPage").addEventListener("pageshow", tbSyncDavNewAccount.resetSecondPage.bind(this)); + document.getElementById("thirdPage").addEventListener("pageshow", tbSyncDavNewAccount.resetThirdPage.bind(this)); + + this.serviceproviderlist.selectedIndex = 0; + tbSyncDavNewAccount.resetFirstPage(); + }, + + onUnload: function () { + }, + + onClose: function () { + //disallow closing of wizard while isLocked + return !this.isLocked; + }, + + onCancel: function (event) { + //disallow closing of wizard while isLocked + if (this.isLocked) { + event.preventDefault(); + } + }, + + onFinish () { + let newAccountEntry = this.providerData.getDefaultAccountEntries(); + newAccountEntry.createdWithProviderVersion = this.providerData.getVersion(); + newAccountEntry.serviceprovider = this.serviceprovider == "discovery" ? "custom" : this.serviceprovider; + newAccountEntry.serviceproviderRevision = dav.sync.serviceproviders.hasOwnProperty(this.serviceprovider) ? dav.sync.serviceproviders[this.serviceprovider].revision : 0 + newAccountEntry.calDavHost = this.finalCalDavServer; + newAccountEntry.cardDavHost = this.finalCardDavServer; + + // Add the new account. + let newAccountData = this.providerData.addAccount(this.finalAccountname, newAccountEntry); + dav.network.getAuthData(newAccountData).updateLoginData(this.finalUsername, this.password); + }, + + + + + + // HELPER FUNCTIONS + addProviderEntry: function (icon, serviceprovider) { + let name = TbSync.getString("add.serverprofile."+serviceprovider, "dav"); + let description = TbSync.getString("add.serverprofile."+serviceprovider+".description", "dav"); + + //left column + let image = document.createXULElement("image"); + image.setAttribute("src", "chrome://dav4tbsync/content/skin/" + icon); + image.setAttribute("style", "margin:1ex;"); + + let leftColumn = document.createXULElement("vbox"); + leftColumn.appendChild(image); + + //right column + let label = document.createXULElement("label"); + label.setAttribute("class", "header"); + label.setAttribute("value", name); + + let desc = document.createXULElement("description"); + desc.setAttribute("style", "width: 300px"); + desc.textContent = description; + + let rightColumn = document.createXULElement("vbox"); + rightColumn.appendChild(label); + rightColumn.appendChild(desc); + + //columns + let columns = document.createXULElement("hbox"); + columns.appendChild(leftColumn); + columns.appendChild(rightColumn); + + //richlistitem + let richlistitem = document.createXULElement("richlistitem"); + richlistitem.setAttribute("style", "padding:4px"); + richlistitem.setAttribute("value", serviceprovider); + richlistitem.appendChild(columns); + + return richlistitem; + }, + + checkUrlForPrincipal: async function (job) { + // according to RFC6764, we must also try the username with cut-off domain part + // Note: This is never called for OAUTH serves (see onAdvance) + let users = []; + users.push(this.username); + if (this.userdomain) users.push(this.username.split("@")[0]); + + for (let user of users) { + let connectionData = new dav.network.ConnectionData(); + connectionData.password = this.password; + connectionData.username = user; + connectionData.timeout = 5000; + + //only needed for proper error reporting - that dav needs this is beyond API - connectionData is not used by TbSync + //connectionData is a structure which contains all the information needed to establish and evaluate a network connection + connectionData.eventLogInfo = new TbSync.EventLogInfo("dav", this.accountname); + + job.valid = false; + job.error = ""; + + try { + let response = await dav.network.sendRequest("<d:propfind "+dav.tools.xmlns(["d"])+"><d:prop><d:current-user-principal /></d:prop></d:propfind>", job.server , "PROPFIND", connectionData, {"Depth": "0", "Prefer": "return=minimal"}, {containerRealm: "setup", containerReset: true, passwordRetries: 0}); + // allow 404 because iCloud sends it on valid answer (yeah!) + let principal = (response && response.multi) ? dav.tools.getNodeTextContentFromMultiResponse(response, [["d","prop"], ["d","current-user-principal"], ["d","href"]], null, ["200","404"]) : null; + job.valid = (principal !== null); + if (!job.valid) { + job.error = job.type + "servernotfound"; + TbSync.eventlog.add("warning", connectionData.eventLogInfo, job.error, response ? response.commLog : ""); + } else { + job.validUser = user; + job.validUrl = (response ? response.permanentlyRedirectedUrl : null) || job.server; + return; + } + } catch (e) { + job.valid = false; + job.error = (e.statusData ? e.statusData.message : e.message); + + if (e.name == "dav4tbsync") { + TbSync.eventlog.add("warning", connectionData.eventLogInfo, e.statusData.message, e.statusData.details); + } else { + Components.utils.reportError(e); + } + } + + // only retry with other user, if 401 + if (!job.error.startsWith("401")) { + break; + } + } + + return; + }, + + advance: function () { + document.getElementById("tbsync.newaccount.wizard").advance(null); + }, + + + + + + // RESET AND INIT FUNCTIONS + clearValues: function () { + //clear fields + this.username = ""; + this.password = ""; + this.server = ""; + this.calDavServer = ""; + this.cardDavServer = ""; + + if (this.serviceprovider == "discovery" || this.serviceprovider == "custom") { + this.accountname = ""; + } else { + this.accountname = TbSync.getString("add.serverprofile." + this.serviceprovider, "dav"); + } + }, + + resetFirstPage: function () { + // RESET / INIT first page + document.getElementById("tbsync.newaccount.wizard").canRewind = false; + document.getElementById("tbsync.newaccount.wizard").canAdvance = true; + // bug https://bugzilla.mozilla.org/show_bug.cgi?id=1618252 + document.getElementById('tbsync.newaccount.wizard')._adjustWizardHeader(); + this.isLocked = false; + this.validated = false; + }, + + resetSecondPage: function () { + // RESET / INIT second page + this.isLocked = false; + this.validated = false; + + document.getElementById("tbsync.newaccount.wizard").canRewind = true; + document.getElementById("tbsync.newaccount.wizard").canAdvance = false; + this.hideSpinner(); + document.getElementById("tbsync.error").hidden = true; + + this.checkUI(); + }, + + resetThirdPage: function () { + // RESET / INIT third page + document.getElementById("tbsync.newaccount.wizard").canRewind = true; + document.getElementById("tbsync.newaccount.wizard").canAdvance = true; + this.isLocked = false; + }, + + + + + + // UI FUNCTIONS + lockUI: function(spinnerText) { + this.showSpinner(spinnerText); + document.getElementById("tbsync.error").hidden = true; + document.getElementById("tbsync.newaccount.wizard").canAdvance = false; + document.getElementById("tbsync.newaccount.wizard").canRewind = false; + this.isLocked = true; + }, + + unlockUI: function() { + this.hideSpinner(); + document.getElementById("tbsync.newaccount.wizard").canRewind = true; + this.isLocked = false; + this.checkUI(); + }, + + checkUI: function (hideError) { + if (hideError) { + document.getElementById("tbsync.error").hidden = true; + } + + // determine, if we can advance or not + if (this.serviceprovider == "discovery") { + document.getElementById("tbsync.newaccount.wizard").canAdvance = !( + (this.accountname == "") || + (this.server == "" && !this.userdomain) || + (this.server == "" && this.username == "")); + } else if (this.serviceprovider == "custom") { + // custom does not need username or password (allow annonymous access) + document.getElementById("tbsync.newaccount.wizard").canAdvance = !( + (this.accountname == "") || + (this.calDavServer + this.cardDavServer == "")); + } else { + // build in service providers do need a username and password + document.getElementById("tbsync.newaccount.wizard").canAdvance = !( + (this.accountname == "") || + (this.password == "") || + (this.username == "")); + } + + // update placeholder attribute of server + this.elementServer.setAttribute("placeholder", this.userdomain ? TbSync.getString("add.serverprofile.discovery.server-optional", "dav") : ""); + + + //show/hide additional descriptions (if avail) + let dFound = 0; + for (let i=1; i < 4; i++) { + let dElement = document.getElementById("tbsync.newaccount.details" + i); + let dLocaleString = "add.serverprofile." + this.serviceprovider + ".details" + i; + let dLocaleValue = TbSync.getString(dLocaleString, "dav"); + + let hide = (dLocaleValue == dLocaleString); + if (this.serviceprovider == "discovery") { + // show them according to UI state + switch (i) { + case 1: + hide = false; + break; + case 2: + hide = !this.userdomain; + break; + } + } + + if (hide) { + dElement.textContent = ""; + dElement.hidden = true; + } else { + dFound++; + dElement.textContent = dLocaleValue + dElement.hidden =false; + } + } + + //hide Notes header, if no descriptions avail + let dLabel = document.getElementById("tbsync.newaccount.details.header"); + dLabel.hidden = (dFound == 0); + + + //which server fields to show? + document.getElementById("tbsync.newaccount.finaluser.row").hidden = false; + document.getElementById("tbsync.newaccount.user.row").hidden = false; + document.getElementById("tbsync.newaccount.password.row").hidden = false; + + if (this.serviceprovider == "discovery") { + document.getElementById("tbsync.newaccount.caldavserver.row").hidden = true; + document.getElementById("tbsync.newaccount.carddavserver.row").hidden = true; + document.getElementById("tbsync.newaccount.server.row").hidden = false; + //this.elementCalDavServer.disabled = false; + //this.elementCardDavServer.disabled = false; + } else if (this.serviceprovider == "custom") { + // custom + document.getElementById("tbsync.newaccount.caldavserver.row").hidden = false; + document.getElementById("tbsync.newaccount.carddavserver.row").hidden = false; + document.getElementById("tbsync.newaccount.server.row").hidden = true; + //this.elementCalDavServer.disabled = false; + //this.elementCardDavServer.disabled = false; + } else { + // build in service provider + document.getElementById("tbsync.newaccount.caldavserver.row").hidden = true; + document.getElementById("tbsync.newaccount.carddavserver.row").hidden = true; + document.getElementById("tbsync.newaccount.server.row").hidden = true; + //this.elementCalDavServer.disabled = true; + //this.elementCardDavServer.disabled = true; + this.calDavServer = dav.sync.serviceproviders[this.serviceprovider].caldav; + this.cardDavServer = dav.sync.serviceproviders[this.serviceprovider].carddav; + } + }, + + + + + + // SETUP LOGIC FUNCTION + onAdvance: function (event) { + // do not prevent advancing if we go from page 1 to page 2, or if validation succeeded + if (document.getElementById("tbsync.newaccount.wizard").currentPage.id == "firstPage" || this.validated) { + return; + } + + // if we reach this, we are on page 2 but may not advance but + // go through the setup steps + + if (this.serviceprovider == "discovery") { + while (this.server.endsWith("/")) { this.server = this.server.slice(0,-1); } + // the user may either specify a server or he could have entered an email with domain + let parts = (this.server || this.userdomain).split("://"); + let scheme = (parts.length > 1) ? parts[0].toLowerCase() : ""; + let host = parts[parts.length-1]; + + this.calDavServer = scheme + "caldav6764://" + host; + this.cardDavServer = scheme + "carddav6764://" + host; + this.validateDavServers(); + } else { + // custom or service provider + this.validateDavServers(); + } + + event.preventDefault(); + }, + + validateDavServers: async function() { + this.lockUI("validating"); + + // Do not manipulate input here. + //while (this.calDavServer.endsWith("/")) { this.calDavServer = this.calDavServer.slice(0,-1); } + //while (this.cardDavServer.endsWith("/")) { this.cardDavServer = this.cardDavServer.slice(0,-1); } + + // Default to https, if http is not explicitly specified + if (this.calDavServer && !dav.network.startsWithScheme(this.calDavServer)) { + this.calDavServer = "https://" + this.calDavServer; + } + if (this.cardDavServer && !dav.network.startsWithScheme(this.cardDavServer)) { + this.cardDavServer = "https://" + this.cardDavServer; + } + + let davJobs = [ + {type: "caldav", server: this.calDavServer}, + {type: "carddav", server: this.cardDavServer}, + ]; + + let failedDavJobs = []; + let validUserFound = ""; + + for (let job = 0; job < davJobs.length; job++) { + if (!davJobs[job].server) { + continue; + } + await this.checkUrlForPrincipal(davJobs[job]); + if (!davJobs[job].valid) { + failedDavJobs.push(job); + } else if (!validUserFound) { + // set the found user + validUserFound = davJobs[job].validUser; + } else if (validUserFound != davJobs[job].validUser) { + // users do not match + failedDavJobs.push(job); + } + } + + if (failedDavJobs.length == 0) { + // boom, setup completed + this.finalCalDavServer = davJobs[0].validUrl || ""; + this.finalCardDavServer = davJobs[1].validUrl || ""; + this.finalUsername = validUserFound; + this.validated = true; + this.unlockUI(); + this.advance(); + return; + } else { + //only display one error + let failedJob = failedDavJobs[0]; + console.log("ERROR ("+davJobs[failedJob].type+"): " + davJobs[failedJob].error.toString()); + switch (davJobs[failedJob].error.toString().split("::")[0]) { + case "401": + case "403": + case "503": + case "security": + document.getElementById("tbsync.error.message").textContent = TbSync.getString("status."+davJobs[failedJob].error, "dav"); + break; + default: + if (this.serviceprovider == "discovery" && this.userdomain && !this.server) { + // the discovery mode has a special error msg, in case a userdomain was used as server, but failed and we need the user to provide the server + document.getElementById("tbsync.error.message").textContent = TbSync.getString("status.rfc6764-lookup-failed::" +this.userdomain, "dav"); + } else if (this.serviceprovider != "discovery" && this.serviceprovider != "custom") { + // error msg, that the serviceprovider setup seems wrong + document.getElementById("tbsync.error.message").textContent = TbSync.getString("status.service-provider-setup-failed", "dav"); + } else if (dav.network.isRFC6764Request(davJobs[failedJob].server)) { + // error msg, that discovery mode failed + document.getElementById("tbsync.error.message").textContent = TbSync.getString("status.service-discovery-failed::" +davJobs[failedJob].server.split("://")[1], "dav"); + } else { + document.getElementById("tbsync.error.message").textContent = TbSync.getString("status." + davJobs[failedJob].type + "servernotfound", "dav"); + } + } + document.getElementById("tbsync.error").hidden = false; + this.unlockUI(); + } + }, +}; diff --git a/content/manager/createAccount.xhtml b/content/manager/createAccount.xhtml new file mode 100644 index 0000000..7bdc84a --- /dev/null +++ b/content/manager/createAccount.xhtml @@ -0,0 +1,158 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?> + +<window onload="tbSyncDavNewAccount.onLoad();" onunload="tbSyncDavNewAccount.onUnload();" + onclose="return tbSyncDavNewAccount.onClose()" xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <linkset> + <html:link rel="localization" href="toolkit/global/wizard.ftl" /> + </linkset> + + <script type="application/javascript" src="chrome://dav4tbsync/content/manager/createAccount.js" /> + <script type="application/javascript" src="chrome://dav4tbsync/content/locales.js" /> + + <wizard title="__DAV4TBSYNCMSG_add.title__" id="tbsync.newaccount.wizard" > + + <wizardpage id="firstPage" onFirstPage="true" label="__DAV4TBSYNCMSG_add.serverprofile.title__"> + <vbox flex="1"> + <description style="width: auto; margin-top:1em;">__DAV4TBSYNCMSG_add.serverprofile.description__ + </description> + <richlistbox id="tbsync.newaccount.serviceproviderlist" seltype="single" + style="width: auto; height: 400px; margin-top:1ex" onselect="tbSyncDavNewAccount.clearValues();" + ondblclick="tbSyncDavNewAccount.advance()" /> + </vbox> + </wizardpage> + + <wizardpage id="secondPage" label="__DAV4TBSYNCMSG_add.data.title__"> + <vbox flex="1"> + <description style="width: auto; margin-top:1em;">__DAV4TBSYNCMSG_add.data.description__</description> + <html:table style="margin-top:1ex"> + <html:tr id="tbsync.newaccount.name.row" width="100%"> + <html:td width="33%"> + <vbox pack="center"><label value="__DAV4TBSYNCMSG_add.name__" /></vbox> + </html:td> + <html:td width="67%"> + <html:input style="width: 90%" id="tbsync.newaccount.name" + oninput="tbSyncDavNewAccount.checkUI();" /> + </html:td> + </html:tr> + <html:tr id="tbsync.newaccount.user.row"> + <html:td> + <vbox pack="center"><label value="__DAV4TBSYNCMSG_add.user__" /></vbox> + </html:td> + <html:td> + <html:input style="width: 90%" id="tbsync.newaccount.user" + oninput="tbSyncDavNewAccount.checkUI(true);" /> + </html:td> + </html:tr> + <html:tr id="tbsync.newaccount.password.row"> + <html:td> + <vbox pack="center"><label value="__DAV4TBSYNCMSG_add.password__" /></vbox> + </html:td> + <html:td> + <html:input style="width: 90%" id="tbsync.newaccount.password" type="password" + oninput="tbSyncDavNewAccount.checkUI(true);" /> + </html:td> + </html:tr> + <html:tr id="tbsync.newaccount.server.row" style="margin-top:2em;"> + <html:td> + <vbox pack="center"><label value="__DAV4TBSYNCMSG_add.server__" /></vbox> + </html:td> + <html:td> + <html:input style="width: 90%" id="tbsync.newaccount.server" + oninput="tbSyncDavNewAccount.checkUI(true);" /> + </html:td> + </html:tr> + <html:tr id="tbsync.newaccount.caldavserver.row" style="margin-top:2em;"> + <html:td> + <vbox pack="center"><label value="__DAV4TBSYNCMSG_add.caldavserver__" /></vbox> + </html:td> + <html:td> + <html:input style="width: 90%" id="tbsync.newaccount.caldavserver" + oninput="tbSyncDavNewAccount.checkUI(true);" /> + </html:td> + </html:tr> + <html:tr id="tbsync.newaccount.carddavserver.row"> + <html:td> + <vbox pack="center"><label value="__DAV4TBSYNCMSG_add.carddavserver__" /></vbox> + </html:td> + <html:td> + <html:input style="width: 90%" id="tbsync.newaccount.carddavserver" + oninput="tbSyncDavNewAccount.checkUI(true);" /> + </html:td> + </html:tr> + </html:table> + <label class="header" id="tbsync.newaccount.details.header" value="__DAV4TBSYNCMSG_add.data.notes__" + style="margin-top:2em" /> + <description id="tbsync.newaccount.details1" style="width: auto; margin-top:1ex;"></description> + <description id="tbsync.newaccount.details2" style="width: auto; margin-top:1ex;"></description> + <description id="tbsync.newaccount.details3" style="width: auto; margin-top:1ex;"></description> + <vbox flex="1"> + </vbox> + <hbox id="tbsync.spinner"> + <label id="tbsync.spinner.label" value="" /> + <image src="chrome://tbsync/content/skin/spinner.gif" style="margin-left:1em" width="16" + height="16" /> + </hbox> + <vbox id="tbsync.error" style="width: auto;"> + <description id="tbsync.error.message" flex="1" style="font-weight: bold;"></description> + <vbox> + <button id="tbsync.error.link" label="__DAV4TBSYNCMSG_manager.ShowEventLog__" + oncommand="TbSync.eventlog.open();" /> + </vbox> + </vbox> + </vbox> + </wizardpage> + + <wizardpage id="thirdPage" label="__DAV4TBSYNCMSG_add.finish.title__"> + <vbox flex="1"> + <description style="width: auto; margin-top:1em;">__DAV4TBSYNCMSG_add.finish.description__ + </description> + <html:table style="margin-top:1ex"> + <html:tr flex="1"> + <html:td width="33%"> + <vbox pack="center"><label value="__DAV4TBSYNCMSG_add.name__" /></vbox> + </html:td> + <html:td width="67%"> + <html:input style="width: 90%; margin:2px" id="tbsync.finalaccount.name" /> + </html:td> + </html:tr> + <html:tr id="tbsync.newaccount.finaluser.row"> + <html:td> + <vbox pack="center"><label value="__DAV4TBSYNCMSG_add.user__" /></vbox> + </html:td> + <html:td> + <html:input style="width: 90%; margin:2px; background-color:silver;" type="text" + id="tbsync.finalaccount.user" readonly="true" /> + </html:td> + </html:tr> + <html:tr style="margin-bottom:2em;"> + </html:tr> + <html:tr id="tbsyncfinalaccount.caldavserver.row"> + <html:td> + <vbox pack="center"><label value="__DAV4TBSYNCMSG_add.caldavserver__" /></vbox> + </html:td> + <html:td> + <html:input style="width: 90%; margin:2px; background-color:silver;" + id="tbsync.finalaccount.caldavserver" readonly="true" /> + </html:td> + </html:tr> + <html:tr id="tbsyncfinalaccount.carddavserver.row"> + <html:td> + <vbox pack="center"><label value="__DAV4TBSYNCMSG_add.carddavserver__" /></vbox> + </html:td> + <html:td> + <html:input style="width: 90%; margin:2px; background-color:silver;" + id="tbsync.finalaccount.carddavserver" readonly="true" /> + </html:td> + </html:tr> + </html:table> + <description id="tbsync.finalaccount.details1" style="width: auto; margin-top:2em;"> + __DAV4TBSYNCMSG_add.finish.details__</description> + </vbox> + </wizardpage> + + </wizard> + +</window> diff --git a/content/manager/editAccountOverlay.js b/content/manager/editAccountOverlay.js new file mode 100644 index 0000000..8f32af8 --- /dev/null +++ b/content/manager/editAccountOverlay.js @@ -0,0 +1,46 @@ +/* + * This file is part of DAV-4-TbSync. + * + * 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, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +"use strict"; + +const dav = TbSync.providers.dav; + +var tbSyncEditAccountOverlay = { + + onload: function (window, accountData) { + this.accountData = accountData; + + let serviceprovider = this.accountData.getAccountProperty("serviceprovider"); + let isServiceProvider = dav.sync.serviceproviders.hasOwnProperty(serviceprovider); + + // special treatment for configuration label, which is a permanent setting and will not change by switching modes + let configlabel = window.document.getElementById("tbsync.accountsettings.label.config"); + if (configlabel) { + let extra = ""; + if (isServiceProvider) { + extra = " [" + TbSync.getString("add.serverprofile." + serviceprovider, "dav") + "]"; + } + configlabel.setAttribute("value", TbSync.getString("config.custom", "dav") + extra); + } + + //set certain elements as "alwaysDisable", if locked by service provider + if (isServiceProvider) { + let items = window.document.getElementsByClassName("lockIfServiceProvider"); + for (let i=0; i < items.length; i++) { + items[i].setAttribute("alwaysDisabled", "true"); + } + } + }, + + stripHost: function (document, field) { + let host = document.getElementById('tbsync.accountsettings.pref.' + field).value; + while (host.endsWith("/")) { host = host.slice(0,-1); } + document.getElementById('tbsync.accountsettings.pref.' + field).value = host + this.accountData.setAccountProperty(field, host); + } +}; diff --git a/content/manager/editAccountOverlay.xhtml b/content/manager/editAccountOverlay.xhtml new file mode 100644 index 0000000..bee8d72 --- /dev/null +++ b/content/manager/editAccountOverlay.xhtml @@ -0,0 +1,93 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://tbsync/content/skin/fix_dropdown_1534697.css" type="text/css"?> +<overlay + id="tbSyncAccountConfig" + xmlns:html="http://www.w3.org/1999/xhtml"> + + <script type="application/javascript" src="chrome://dav4tbsync/content/manager/editAccountOverlay.js"/> + <script type="application/javascript" src="chrome://dav4tbsync/content/locales.js"/> + + <tab id="manager.tabs.accountsettings" label="__DAV4TBSYNCMSG_manager.tabs.accountsettings__" appendto="manager.tabs" /> + <tab id="manager.tabs.syncsettings" label="__DAV4TBSYNCMSG_manager.tabs.syncsettings__" appendto="manager.tabs" /> + + <tabpanel id="manager.tabpanels.accountsettings" appendto="manager.tabpanels" flex="1" orient="vertical"><!-- ACCOUNT SETTINGS --> + <vbox flex="1"> + <label class="header lockIfConnected" style="margin-left:0; margin-bottom:1ex;" value="" id="tbsync.accountsettings.label.config" /> + <html:table> + + <html:tr> + <html:td> + <vbox pack="center"> + <label style="text-align:left" control="tbsync.accountsettings.pref.accountname" value="__DAV4TBSYNCMSG_pref.AccountName__" /> + </vbox> + </html:td> + <html:td width="100%"> + <html:input id="tbsync.accountsettings.pref.accountname" /> + </html:td> + </html:tr> + + <html:tr> + <html:td> + <vbox pack="center"> + <label class="lockIfConnected" style="text-align:left" control="tbsync.accountsettings.pref.user" value="__DAV4TBSYNCMSG_pref.UserName__" /> + </vbox> + </html:td> + <html:td> + <html:input class="lockIfConnected" id="tbsync.accountsettings.pref.user" /> + </html:td> + </html:tr> + + <html:tr> + <html:td> + <vbox pack="center"> + <label class="lockIfConnected lockIfServiceProvider" style="text-align:left" control="tbsync.accountsettings.pref.calDavHost" value="__DAV4TBSYNCMSG_pref.CalDavServer__" /> + </vbox> + </html:td> + <html:td> + <html:input class="lockIfConnected lockIfServiceProvider" id="tbsync.accountsettings.pref.calDavHost" onblur="tbSyncEditAccountOverlay.stripHost(document, 'calDavHost');"/> + </html:td> + </html:tr> + <html:tr> + <html:td> + <vbox pack="center"> + <label class="lockIfConnected lockIfServiceProvider" style="text-align:left" control="tbsync.accountsettings.pref.cardDavHost" value="__DAV4TBSYNCMSG_pref.CardDavServer__" /> + </vbox> + </html:td> + <html:td> + <html:input class="lockIfConnected lockIfServiceProvider" id="tbsync.accountsettings.pref.cardDavHost" onblur="tbSyncEditAccountOverlay.stripHost(document, 'cardDavHost');" /> + </html:td> + </html:tr> + + </html:table> + + <vbox flex="1" /> + <vbox class="showIfConnected"> + <hbox> + <vbox pack="center"><image src="chrome://tbsync/content/skin/info16.png" /></vbox> + <description flex="1">__TBSYNCMSG_manager.lockedsettings.description__</description> + </hbox> + </vbox> + + </vbox> + </tabpanel> + + <tabpanel id="manager.tabpanels.syncsettings" appendto="manager.tabpanels" flex="1" orient="vertical"><!-- SYNC SETTINGS --> + <vbox flex="1"> + <!--label style="margin-left:0; margin-bottom: 1ex;" class="header lockIfConnected" value="__DAV4TBSYNCMSG_pref.generaloptions__"/--> + + <label style="margin-left:0; margin-bottom: 1ex; margin-top: 3ex" class="header lockIfConnected" value="__DAV4TBSYNCMSG_pref.calendaroptions__"/> + <vbox> + <checkbox class="lockIfConnected" id="tbsync.accountsettings.pref.useCalendarCache" label="__DAV4TBSYNCMSG_pref.useCalendarCache__" /> + </vbox> + + <vbox flex="1" /> + <vbox class="showIfConnected"> + <hbox> + <vbox pack="center"><image src="chrome://tbsync/content/skin/info16.png" /></vbox> + <description flex="1">__TBSYNCMSG_manager.lockedsettings.description__</description> + </hbox> + </vbox> + </vbox> + </tabpanel> + +</overlay> diff --git a/content/provider.js b/content/provider.js new file mode 100644 index 0000000..dfd8906 --- /dev/null +++ b/content/provider.js @@ -0,0 +1,691 @@ +/* + * This file is part of DAV-4-TbSync. + * + * 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, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +"use strict"; +// check if getItem returns an array because of recursions! + +// Every object in here will be loaded into TbSync.providers.<providername>. +const dav = TbSync.providers.dav; + +/** + * Implementing the TbSync interface for external provider extensions. + */ + +var Base = class { + /** + * Called during load of external provider extension to init provider. + */ + static async load() { + // Set default prefs + let branch = Services.prefs.getDefaultBranch("extensions.dav4tbsync."); + branch.setIntPref("maxitems", 50); + branch.setIntPref("timeout", 90000); + branch.setCharPref("clientID.type", "TbSync"); + branch.setCharPref("clientID.useragent", "Thunderbird CalDAV/CardDAV"); + branch.setBoolPref("enforceUniqueCalendarUrls", false); + + dav.openWindows = {}; + } + + + /** + * Called during unload of external provider extension to unload provider. + */ + static async unload() { + // Close all open windows of this provider. + for (let id in dav.openWindows) { + if (dav.openWindows.hasOwnProperty(id)) { + try { + dav.openWindows[id].close(); + } catch (e) { + //NOOP + } + } + } + } + + + /** + * Returns string for the name of provider for the add account menu. + */ + static getProviderName() { + return TbSync.getString("menu.name", "dav"); + } + + + /** + * Returns version of the TbSync API this provider is using + */ + static getApiVersion() { return "2.5"; } + + + + /** + * Returns location of a provider icon. + */ + static getProviderIcon(size, accountData = null) { + let root = "sabredav"; + if (accountData) { + let serviceprovider = accountData.getAccountProperty("serviceprovider"); + if (dav.sync.serviceproviders.hasOwnProperty(serviceprovider)) { + root = dav.sync.serviceproviders[serviceprovider].icon; + } + } + + switch (size) { + case 16: + return "chrome://dav4tbsync/content/skin/"+root+"16.png"; + case 32: + return "chrome://dav4tbsync/content/skin/"+root+"32.png"; + default : + return "chrome://dav4tbsync/content/skin/"+root+"48.png"; + } + } + + + /** + * Returns a list of sponsors, they will be sorted by the index + */ + static getSponsors() { + return { + "Thoben, Marc" : {name: "Marc Thoben", description: "Zimbra", icon: "", link: "" }, + "Biebl, Michael" : {name: "Michael Biebl", description: "Nextcloud", icon: "", link: "" }, + "László, Kovács" : {name: "Kovács László", description : "Radicale", icon: "", link: "" }, + "Lütticke, David" : {name: "David Lütticke", description : "", icon: "", link: "" }, + }; + } + + + /** + * Returns the url of a page with details about contributors (used in the manager UI) + */ + static getContributorsUrl() { + return "https://github.com/jobisoft/DAV-4-TbSync/blob/master/CONTRIBUTORS.md"; + } + + + /** + * Returns the email address of the maintainer (used for bug reports). + */ + static getMaintainerEmail() { + return "john.bieling@gmx.de"; + } + + + /** + * Returns URL of the new account window. + * + * The URL will be opened via openDialog(), when the user wants to create a + * new account of this provider. + */ + static getCreateAccountWindowUrl() { + return "chrome://dav4tbsync/content/manager/createAccount.xhtml"; + } + + + /** + * Returns overlay XUL URL of the edit account dialog + * (chrome://tbsync/content/manager/editAccount.xhtml) + */ + static getEditAccountOverlayUrl() { + return "chrome://dav4tbsync/content/manager/editAccountOverlay.xhtml"; + } + + + /** + * Return object which contains all possible fields of a row in the + * accounts database with the default value if not yet stored in the + * database. + */ + static getDefaultAccountEntries() { + let row = { + "useCalendarCache" : true, + "calDavHost" : "", + "cardDavHost" : "", + // these must return null if not defined + "calDavPrincipal" : null, + "cardDavPrincipal" : null, + + "calDavOptions" : [], + "cardDavOptions" : [], + + "serviceprovider" : "", + "serviceproviderRevision" : 0, + + "user" : "", + "https" : true, //deprecated, because this is part of the URL now + "createdWithProviderVersion" : "0", + }; + return row; + } + + + /** + * Return object which contains all possible fields of a row in the folder + * database with the default value if not yet stored in the database. + */ + static getDefaultFolderEntries() { + let folder = { + // different folders (caldav/carddav) can be stored on different + // servers (as with yahoo, icloud, gmx, ...), so we need to store + // the fqdn information per folders + "href" : "", + "https" : true, + "fqdn" : "", + + "url" : "", // used by calendar to store the full url of this cal + + "type" : "", //caldav, carddav or ics + "shared": false, //identify shared resources + "acl": "", //acl send from server + "target" : "", + "targetColor" : "", + "targetName" : "", + "ctag" : "", + "token" : "", + "createdWithProviderVersion" : "0", + "supportedCalComponent" : [] + }; + return folder; + } + + + /** + * Is called everytime an account of this provider is enabled in the + * manager UI. + */ + static onEnableAccount(accountData) { + accountData.resetAccountProperty("calDavPrincipal"); + accountData.resetAccountProperty("cardDavPrincipal"); + } + + + /** + * Is called everytime an account of this provider is disabled in the + * manager UI. + */ + static onDisableAccount(accountData) { + } + + + /** + * Is called everytime an account of this provider is deleted in the + * manager UI. + */ + static onDeleteAccount(accountData) { + dav.network.getAuthData(accountData).removeLoginData(); + } + + + /** + * Returns all folders of the account, sorted in the desired order. + * The most simple implementation is to return accountData.getAllFolders(); + */ + static getSortedFolders(accountData) { + let folders = accountData.getAllFolders(); + + // we can only sort arrays, so we create an array of objects which must + // contain the sort key and the associated folder + let toBeSorted = []; + for (let folder of folders) { + let t = 100; + let comp = folder.getFolderProperty("supportedCalComponent"); + switch (folder.getFolderProperty("type")) { + case "carddav": + t+=0; + break; + case "caldav": + t+=10; + if (comp.length > 0 && !comp.includes("VEVENT") && comp.includes("VTODO")) t+=5; + break; + case "ics": + t+=20; + break; + default: + t+=90; + break; + } + + if (folder.getFolderProperty("shared")) { + t+=100; + } + + toBeSorted.push({"key": t.toString() + folder.getFolderProperty("foldername"), "folder": folder}); + } + + //sort + toBeSorted.sort(function(a,b) { + return a.key > b.key; + }); + + let sortedFolders = []; + for (let sortObj of toBeSorted) { + sortedFolders.push(sortObj.folder); + } + return sortedFolders; + } + + + /** + * Return the connection timeout for an active sync, so TbSync can append + * a countdown to the connection timeout, while waiting for an answer from + * the server. Only syncstates which start with "send." will trigger this. + */ + static getConnectionTimeout(accountData) { + return dav.sync.prefSettings.getIntPref("timeout"); + } + + + /** + * Is called if TbSync needs to synchronize the folder list. + */ + static async syncFolderList(syncData, syncJob, syncRunNr) { + // Recommendation: Put the actual function call inside a try catch, to + // ensure returning a proper StatusData object, regardless of what + // happens inside that function. You may also throw custom errors + // in that function, which have the StatusData obj attached, which + // should be returned. + + try { + await dav.sync.folderList(syncData); + } catch (e) { + if (e.name == "dav4tbsync") { + return e.statusData; + } else { + Components.utils.reportError(e); + // re-throw any other error and let TbSync handle it + throw (e); + } + } + + // we fall through, if there was no error + return new TbSync.StatusData(); + } + + + /** + * Is called if TbSync needs to synchronize a folder. + */ + static async syncFolder(syncData, syncJob, syncRunNr) { + // Recommendation: Put the actual function call inside a try catch, to + // ensure returning a proper StatusData object, regardless of what + // happens inside that function. You may also throw custom errors + // in that function, which have the StatusData obj attached, which + // should be returned. + + // Process a single folder. + try { + await dav.sync.folder(syncData); + } catch (e) { + if (e.name == "dav4tbsync") { + return e.statusData; + } else { + Components.utils.reportError(e); + // re-throw any other error and let TbSync handle it + throw (e); + } + } + + // we fall through, if there was no error + return new TbSync.StatusData(); + } +} + + + + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// * TargetData implementation +// * Using TbSyncs advanced address book TargetData +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var TargetData_addressbook = class extends TbSync.addressbook.AdvancedTargetData { + constructor(folderData) { + super(folderData); + } + + // enable or disable changelog + get logUserChanges() { + return false; + } + + directoryObserver(aTopic) { + switch (aTopic) { + case "addrbook-directory-deleted": + case "addrbook-directory-updated": + //Services.console.logStringMessage("["+ aTopic + "] " + this.folderData.getFolderProperty("foldername")); + break; + } + } + + cardObserver(aTopic, abCardItem) { + switch (aTopic) { + case "addrbook-contact-updated": + case "addrbook-contact-deleted": + case "addrbook-contact-created": + //Services.console.logStringMessage("["+ aTopic + "] " + abCardItem.getProperty("DisplayName")); + break; + } + } + + listObserver(aTopic, abListItem, abListMember) { + switch (aTopic) { + case "addrbook-list-member-added": + case "addrbook-list-member-removed": + //Services.console.logStringMessage("["+ aTopic + "] MemberName: " + abListMember.getProperty("DisplayName")); + break; + + case "addrbook-list-deleted": + case "addrbook-list-updated": + //Services.console.logStringMessage("["+ aTopic + "] ListName: " + abListItem.getProperty("ListName")); + break; + + case "addrbook-list-created": + //Services.console.logStringMessage("["+ aTopic + "] Created new X-DAV-UID for List <"+abListItem.getProperty("ListName")+">"); + break; + } + } + + async createAddressbook(newname) { + let authData = dav.network.getAuthData(this.folderData.accountData); + + let baseUrl = "http" + (this.folderData.getFolderProperty("https") ? "s" : "") + "://" + this.folderData.getFolderProperty("fqdn"); + let url = dav.tools.parseUri(baseUrl + this.folderData.getFolderProperty("href") + (dav.sync.prefSettings.getBoolPref("enforceUniqueCalendarUrls") ? "?" + this.folderData.accountID : "")); + this.folderData.setFolderProperty("url", url.spec); + + const getDirectory = (url) => { + // Check if that directory exists already. + for (let ab of MailServices.ab.directories) { + if (ab.dirType == Ci.nsIAbManager.CARDDAV_DIRECTORY_TYPE && ab.getStringValue("carddav.url","") == url.spec) { + return ab; + } + } + let dirPrefId = MailServices.ab.newAddressBook( + newname, + null, + Ci.nsIAbManager.CARDDAV_DIRECTORY_TYPE, + null + ); + let directory = MailServices.ab.getDirectoryFromId(dirPrefId); + directory.setStringValue("carddav.url", url.spec); + return directory; + } + + let directory = getDirectory(url); + if (!directory || !(directory instanceof Components.interfaces.nsIAbDirectory)) { + return null; + } + + // Setup password for CardDAV address book, so users do not get prompted. + directory.setStringValue("carddav.username", authData.username); + if (this.folderData.getFolderProperty("downloadonly")) { + directory.setBoolValue("readOnly", true); + } + TbSync.dump("Searching CardDAV authRealm for", url.host); + let connectionData = new dav.network.ConnectionData(this.folderData); + await dav.network.sendRequest("<d:propfind "+dav.tools.xmlns(["d"])+"><d:prop><d:resourcetype /><d:displayname /></d:prop></d:propfind>", url.spec , "PROPFIND", connectionData, {"Depth": "0", "Prefer": "return=minimal"}, {containerRealm: "setup", containerReset: true, passwordRetries: 0}); + + let realm = connectionData.realm || ""; + if (realm !== "") { + TbSync.dump("Adding CardDAV password", "User <"+authData.username+">, Realm <"+realm+">"); + // Manually create a CardDAV style entry in the password manager. + TbSync.passwordManager.updateLoginInfo( + url.prePath, realm, + /* old */ authData.username, + /* new */ authData.username, + authData.password + ); + } + + dav.sync.resetFolderSyncInfo(this.folderData); + + /* + // Since icons are no longer supported, lets disable this for 102. + let serviceprovider = this.folderData.accountData.getAccountProperty("serviceprovider"); + let icon = "custom"; + if (dav.sync.serviceproviders.hasOwnProperty(serviceprovider)) { + icon = dav.sync.serviceproviders[serviceprovider].icon; + } + directory.setStringValue("tbSyncIcon", "dav" + icon); + */ + + return directory; + } +} + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// * TargetData implementation +// * Using TbSyncs advanced calendar TargetData +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var TargetData_calendar = class extends TbSync.lightning.AdvancedTargetData { + constructor(folderData) { + super(folderData); + } + // The calendar target does not support a custom primaryKeyField, because + // the lightning implementation only allows to search for items via UID. + // Like the addressbook target, the calendar target item element has a + // primaryKey getter/setter which - however - only works on the UID. + + // enable or disable changelog + get logUserChanges(){ + return false; + } + + calendarObserver(aTopic, tbCalendar, aPropertyName, aPropertyValue, aOldPropertyValue) { + switch (aTopic) { + case "onCalendarPropertyChanged": + { + //Services.console.logStringMessage("["+ aTopic + "] " + tbCalendar.calendar.name + " : " + aPropertyName); + switch (aPropertyName) { + case "color": + if (aOldPropertyValue.toString().toUpperCase() != aPropertyValue.toString().toUpperCase()) { + //prepare connection data + let connection = new dav.network.ConnectionData(this.folderData); + //update color on server + dav.network.sendRequest("<d:propertyupdate "+dav.tools.xmlns(["d","apple"])+"><d:set><d:prop><apple:calendar-color>"+(aPropertyValue + "FFFFFFFF").slice(0,9)+"</apple:calendar-color></d:prop></d:set></d:propertyupdate>", this.folderData.getFolderProperty("href"), "PROPPATCH", connection); + } + break; + } + } + break; + + case "onCalendarDeleted": + case "onCalendarPropertyDeleted": + //Services.console.logStringMessage("["+ aTopic + "] " +tbCalendar.calendar.name); + break; + } + } + + itemObserver(aTopic, tbItem, tbOldItem) { + switch (aTopic) { + case "onAddItem": + case "onModifyItem": + case "onDeleteItem": + //Services.console.logStringMessage("["+ aTopic + "] " + tbItem.nativeItem.title); + break; + } + } + + async createCalendar(newname) { + let calManager = TbSync.lightning.cal.manager; + let authData = dav.network.getAuthData(this.folderData.accountData); + + let caltype = this.folderData.getFolderProperty("type"); + + let baseUrl = ""; + if (caltype == "caldav") { + baseUrl = "http" + (this.folderData.getFolderProperty("https") ? "s" : "") + "://" + this.folderData.getFolderProperty("fqdn"); + } + + let url = dav.tools.parseUri(baseUrl + this.folderData.getFolderProperty("href") + (dav.sync.prefSettings.getBoolPref("enforceUniqueCalendarUrls") ? "?" + this.folderData.accountID : "")); + this.folderData.setFolderProperty("url", url.spec); + + // Check if that calendar already exists. + let cals = calManager.getCalendars({}); + let newCalendar = null; + let found = false; + for (let calendar of calManager.getCalendars({})) { + if (calendar.uri.spec == url.spec) { + newCalendar = calendar; + found = true; + break; + } + } + + + if (found) { + newCalendar.setProperty("username", authData.username); + newCalendar.setProperty("color", this.folderData.getFolderProperty("targetColor")); + newCalendar.name = newname; + } else { + newCalendar = calManager.createCalendar(caltype, url); // caldav or ics + newCalendar.id = TbSync.lightning.cal.getUUID(); + newCalendar.name = newname; + + newCalendar.setProperty("username", authData.username); + newCalendar.setProperty("color", this.folderData.getFolderProperty("targetColor")); + // removed in TB78, as it seems to not fully enable the calendar, if present before registering + // https://searchfox.org/comm-central/source/calendar/base/content/calendar-management.js#385 + //newCalendar.setProperty("calendar-main-in-composite",true); + newCalendar.setProperty("cache.enabled", this.folderData.accountData.getAccountProperty("useCalendarCache")); + } + + if (this.folderData.getFolderProperty("downloadonly")) newCalendar.setProperty("readOnly", true); + + let comp = this.folderData.getFolderProperty("supportedCalComponent"); + if (comp.length > 0 && !comp.includes("VTODO")) { + newCalendar.setProperty("capabilities.tasks.supported", false); + } + if (comp.length > 0 && !comp.includes("VEVENT")) { + newCalendar.setProperty("capabilities.events.supported", false); + } + + // Setup password for CalDAV calendar, so users do not get prompted (ICS urls do not need a password). + if (caltype == "caldav") { + TbSync.dump("Searching CalDAV authRealm for", url.host); + let connectionData = new dav.network.ConnectionData(this.folderData); + await dav.network.sendRequest("<d:propfind "+dav.tools.xmlns(["d"])+"><d:prop><d:resourcetype /><d:displayname /></d:prop></d:propfind>", url.spec , "PROPFIND", connectionData, {"Depth": "0", "Prefer": "return=minimal"}, {containerRealm: "setup", containerReset: true, passwordRetries: 0}); + + let realm = connectionData.realm || ""; + if (realm !== "") { + TbSync.dump("Adding CalDAV password", "User <"+authData.username+">, Realm <"+realm+">"); + // Manually create a CalDAV style entry in the password manager. + TbSync.passwordManager.updateLoginInfo( + url.prePath, realm, + /* old */ authData.username, + /* new */ authData.username, + authData.password + ); + } + } + + if (!found) { + calManager.registerCalendar(newCalendar); + } + return newCalendar; + } +} + + + + + +/** + * This provider is implementing the StandardFolderList class instead of + * the FolderList class. + */ +var StandardFolderList = class { + /** + * Is called before the context menu of the folderlist is shown, allows to + * show/hide custom menu options based on selected folder. During an active + * sync, folderData will be null. + */ + static onContextMenuShowing(window, folderData) { + } + + + /** + * Return the icon used in the folderlist to represent the different folder + * types. + */ + static getTypeImage(folderData) { + let src = ""; + switch (folderData.getFolderProperty("type")) { + case "carddav": + if (folderData.getFolderProperty("shared")) { + return "chrome://tbsync/content/skin/contacts16_shared.png"; + } else { + return "chrome://tbsync/content/skin/contacts16.png"; + } + case "caldav": + let comp = folderData.getFolderProperty("supportedCalComponent"); + if (folderData.getFolderProperty("shared")) { + return (comp.length > 0 && comp.includes("VTODO") && !comp.includes("VEVENT")) + ? "chrome://tbsync/content/skin/todo16_shared.png" + : "chrome://tbsync/content/skin/calendar16_shared.png" + } else { + return (comp.length > 0 && comp.includes("VTODO") && !comp.includes("VEVENT")) + ? "chrome://tbsync/content/skin/todo16.png" + : "chrome://tbsync/content/skin/calendar16.png" + } + case "ics": + return "chrome://dav4tbsync/content/skin/ics16.png"; + } + } + + + /** + * Return the name of the folder shown in the folderlist. + */ + static getFolderDisplayName(folderData) { + return folderData.getFolderProperty("foldername"); + } + + + /** + * Return the attributes for the ACL RO (readonly) menu element per folder. + * (label, disabled, hidden, style, ...) + * + * Return a list of attributes and their values. If both (RO+RW) do + * not return any attributes, the ACL menu is not displayed at all. + */ + static getAttributesRoAcl(folderData) { + return { + label: TbSync.getString("acl.readonly", "dav"), + }; + } + + + /** + * Return the attributes for the ACL RW (readwrite) menu element per folder. + * (label, disabled, hidden, style, ...) + * + * Return a list of attributes and their values. If both (RO+RW) do + * not return any attributes, the ACL menu is not displayed at all. + */ + static getAttributesRwAcl(folderData) { + let acl = parseInt(folderData.getFolderProperty("acl")); + let acls = []; + if (acl & 0x2) acls.push(TbSync.getString("acl.modify", "dav")); + if (acl & 0x4) acls.push(TbSync.getString("acl.add", "dav")); + if (acl & 0x8) acls.push(TbSync.getString("acl.delete", "dav")); + if (acls.length == 0) acls.push(TbSync.getString("acl.none", "dav")); + + return { + label: TbSync.getString("acl.readwrite::"+acls.join(", "), "dav"), + disabled: (acl & 0x7) != 0x7, + } + } +} + +Services.scriptloader.loadSubScript("chrome://dav4tbsync/content/includes/sync.js", this, "UTF-8"); +Services.scriptloader.loadSubScript("chrome://dav4tbsync/content/includes/tools.js", this, "UTF-8"); +Services.scriptloader.loadSubScript("chrome://dav4tbsync/content/includes/network.js", this, "UTF-8"); diff --git a/content/skin/ab.css b/content/skin/ab.css new file mode 100644 index 0000000..a1398da --- /dev/null +++ b/content/skin/ab.css @@ -0,0 +1,82 @@ +treechildren::-moz-tree-image(DirCol, davgoogle) { + margin-inline-end: 2px; + list-style-image: url("chrome://dav4tbsync/content/skin/google16.png"); +} + +treechildren::-moz-tree-image(DirCol, davweb) { + margin-inline-end: 2px; + list-style-image: url("chrome://dav4tbsync/content/skin/web16.png"); +} + +treechildren::-moz-tree-image(DirCol, davfruux) { + margin-inline-end: 2px; + list-style-image: url("chrome://dav4tbsync/content/skin/fruux16.png"); +} + +treechildren::-moz-tree-image(DirCol, davposteo) { + margin-inline-end: 2px; + list-style-image: url("chrome://dav4tbsync/content/skin/posteo16.png"); +} + +treechildren::-moz-tree-image(DirCol, davmbo) { + margin-inline-end: 2px; + list-style-image: url("chrome://dav4tbsync/content/skin/mbo16.png"); +} + +treechildren::-moz-tree-image(DirCol, davicloud) { + margin-inline-end: 2px; + list-style-image: url("chrome://dav4tbsync/content/skin/icloud16.png"); +} + +treechildren::-moz-tree-image(DirCol, davyahoo) { + margin-inline-end: 2px; + list-style-image: url("chrome://dav4tbsync/content/skin/yahoo16.png"); +} + +treechildren::-moz-tree-image(DirCol, davgmx) { + margin-inline-end: 2px; + list-style-image: url("chrome://dav4tbsync/content/skin/gmx16.png"); +} + +treechildren::-moz-tree-image(DirCol, davcustom) { + margin-inline-end: 2px; + list-style-image: url("chrome://dav4tbsync/content/skin/sabredav16.png"); +} + + +.abMenuItem[AddrBook="true"][TbSyncIcon="davgoogle"] { + list-style-image: url("chrome://dav4tbsync/content/skin/google16.png"); +} + +.abMenuItem[AddrBook="true"][TbSyncIcon="davweb"] { + list-style-image: url("chrome://dav4tbsync/content/skin/web16.png"); +} + +.abMenuItem[AddrBook="true"][TbSyncIcon="davfruux"] { + list-style-image: url("chrome://dav4tbsync/content/skin/fruux16.png"); +} + +.abMenuItem[AddrBook="true"][TbSyncIcon="davposteo"] { + list-style-image: url("chrome://dav4tbsync/content/skin/posteo16.png"); +} + +.abMenuItem[AddrBook="true"][TbSyncIcon="davmbo"] { + list-style-image: url("chrome://dav4tbsync/content/skin/mbo16.png"); +} + +.abMenuItem[AddrBook="true"][TbSyncIcon="davicloud"] { + list-style-image: url("chrome://dav4tbsync/content/skin/icloud16.png"); +} + +.abMenuItem[AddrBook="true"][TbSyncIcon="davyahoo"] { + list-style-image: url("chrome://dav4tbsync/content/skin/yahoo16.png"); +} + +.abMenuItem[AddrBook="true"][TbSyncIcon="davgmx"] { + list-style-image: url("chrome://dav4tbsync/content/skin/gmx16.png"); +} + + +.abMenuItem[AddrBook="true"][TbSyncIcon="davcustom"] { + list-style-image: url("chrome://dav4tbsync/content/skin/sabredav16.png"); +} diff --git a/content/skin/arrow.down10.png b/content/skin/arrow.down10.png Binary files differnew file mode 100644 index 0000000..82eccb5 --- /dev/null +++ b/content/skin/arrow.down10.png diff --git a/content/skin/arrow.up10.png b/content/skin/arrow.up10.png Binary files differnew file mode 100644 index 0000000..db87a3d --- /dev/null +++ b/content/skin/arrow.up10.png diff --git a/content/skin/dragdrop.png b/content/skin/dragdrop.png Binary files differnew file mode 100644 index 0000000..a3797da --- /dev/null +++ b/content/skin/dragdrop.png diff --git a/content/skin/fruux16.png b/content/skin/fruux16.png Binary files differnew file mode 100644 index 0000000..61d25f2 --- /dev/null +++ b/content/skin/fruux16.png diff --git a/content/skin/fruux32.png b/content/skin/fruux32.png Binary files differnew file mode 100644 index 0000000..2d37e6b --- /dev/null +++ b/content/skin/fruux32.png diff --git a/content/skin/fruux48.png b/content/skin/fruux48.png Binary files differnew file mode 100644 index 0000000..c2b9aca --- /dev/null +++ b/content/skin/fruux48.png diff --git a/content/skin/gmx16.png b/content/skin/gmx16.png Binary files differnew file mode 100644 index 0000000..1672e4a --- /dev/null +++ b/content/skin/gmx16.png diff --git a/content/skin/gmx32.png b/content/skin/gmx32.png Binary files differnew file mode 100644 index 0000000..bb2dd43 --- /dev/null +++ b/content/skin/gmx32.png diff --git a/content/skin/gmx48.png b/content/skin/gmx48.png Binary files differnew file mode 100644 index 0000000..d23c125 --- /dev/null +++ b/content/skin/gmx48.png diff --git a/content/skin/icloud16.png b/content/skin/icloud16.png Binary files differnew file mode 100644 index 0000000..4399f6a --- /dev/null +++ b/content/skin/icloud16.png diff --git a/content/skin/icloud32.png b/content/skin/icloud32.png Binary files differnew file mode 100644 index 0000000..6691ebc --- /dev/null +++ b/content/skin/icloud32.png diff --git a/content/skin/icloud48.png b/content/skin/icloud48.png Binary files differnew file mode 100644 index 0000000..2171c1d --- /dev/null +++ b/content/skin/icloud48.png diff --git a/content/skin/ics16.png b/content/skin/ics16.png Binary files differnew file mode 100644 index 0000000..7fc8ab6 --- /dev/null +++ b/content/skin/ics16.png diff --git a/content/skin/mbo16.png b/content/skin/mbo16.png Binary files differnew file mode 100644 index 0000000..bbf127f --- /dev/null +++ b/content/skin/mbo16.png diff --git a/content/skin/mbo32.png b/content/skin/mbo32.png Binary files differnew file mode 100644 index 0000000..ee86331 --- /dev/null +++ b/content/skin/mbo32.png diff --git a/content/skin/mbo48.png b/content/skin/mbo48.png Binary files differnew file mode 100644 index 0000000..90c62dc --- /dev/null +++ b/content/skin/mbo48.png diff --git a/content/skin/posteo16.png b/content/skin/posteo16.png Binary files differnew file mode 100644 index 0000000..922078b --- /dev/null +++ b/content/skin/posteo16.png diff --git a/content/skin/posteo32.png b/content/skin/posteo32.png Binary files differnew file mode 100644 index 0000000..b296393 --- /dev/null +++ b/content/skin/posteo32.png diff --git a/content/skin/posteo48.png b/content/skin/posteo48.png Binary files differnew file mode 100644 index 0000000..a1f015d --- /dev/null +++ b/content/skin/posteo48.png diff --git a/content/skin/sabredav16.png b/content/skin/sabredav16.png Binary files differnew file mode 100644 index 0000000..fda1f68 --- /dev/null +++ b/content/skin/sabredav16.png diff --git a/content/skin/sabredav32.png b/content/skin/sabredav32.png Binary files differnew file mode 100644 index 0000000..b0e80d6 --- /dev/null +++ b/content/skin/sabredav32.png diff --git a/content/skin/sabredav48.png b/content/skin/sabredav48.png Binary files differnew file mode 100644 index 0000000..48a9739 --- /dev/null +++ b/content/skin/sabredav48.png diff --git a/content/skin/type.car10.png b/content/skin/type.car10.png Binary files differnew file mode 100644 index 0000000..ca12b71 --- /dev/null +++ b/content/skin/type.car10.png diff --git a/content/skin/type.car16.png b/content/skin/type.car16.png Binary files differnew file mode 100644 index 0000000..db6fb9d --- /dev/null +++ b/content/skin/type.car16.png diff --git a/content/skin/type.cell10.png b/content/skin/type.cell10.png Binary files differnew file mode 100644 index 0000000..ef5ebac --- /dev/null +++ b/content/skin/type.cell10.png diff --git a/content/skin/type.cell16.png b/content/skin/type.cell16.png Binary files differnew file mode 100644 index 0000000..0716347 --- /dev/null +++ b/content/skin/type.cell16.png diff --git a/content/skin/type.fax10.png b/content/skin/type.fax10.png Binary files differnew file mode 100644 index 0000000..276f0d1 --- /dev/null +++ b/content/skin/type.fax10.png diff --git a/content/skin/type.fax16.png b/content/skin/type.fax16.png Binary files differnew file mode 100644 index 0000000..4b9a92c --- /dev/null +++ b/content/skin/type.fax16.png diff --git a/content/skin/type.home10.png b/content/skin/type.home10.png Binary files differnew file mode 100644 index 0000000..8a72f0f --- /dev/null +++ b/content/skin/type.home10.png diff --git a/content/skin/type.home16.png b/content/skin/type.home16.png Binary files differnew file mode 100644 index 0000000..84d829b --- /dev/null +++ b/content/skin/type.home16.png diff --git a/content/skin/type.nopref.png b/content/skin/type.nopref.png Binary files differnew file mode 100644 index 0000000..27caa40 --- /dev/null +++ b/content/skin/type.nopref.png diff --git a/content/skin/type.other10.png b/content/skin/type.other10.png Binary files differnew file mode 100644 index 0000000..d05fc5f --- /dev/null +++ b/content/skin/type.other10.png diff --git a/content/skin/type.other16.png b/content/skin/type.other16.png Binary files differnew file mode 100644 index 0000000..60a169b --- /dev/null +++ b/content/skin/type.other16.png diff --git a/content/skin/type.pager10.png b/content/skin/type.pager10.png Binary files differnew file mode 100644 index 0000000..5c6fbe7 --- /dev/null +++ b/content/skin/type.pager10.png diff --git a/content/skin/type.pager16.png b/content/skin/type.pager16.png Binary files differnew file mode 100644 index 0000000..6a0f2b1 --- /dev/null +++ b/content/skin/type.pager16.png diff --git a/content/skin/type.pref.png b/content/skin/type.pref.png Binary files differnew file mode 100644 index 0000000..409a30c --- /dev/null +++ b/content/skin/type.pref.png diff --git a/content/skin/type.video10.png b/content/skin/type.video10.png Binary files differnew file mode 100644 index 0000000..188cabc --- /dev/null +++ b/content/skin/type.video10.png diff --git a/content/skin/type.video16.png b/content/skin/type.video16.png Binary files differnew file mode 100644 index 0000000..afb8c7c --- /dev/null +++ b/content/skin/type.video16.png diff --git a/content/skin/type.voice10.png b/content/skin/type.voice10.png Binary files differnew file mode 100644 index 0000000..3ed48b6 --- /dev/null +++ b/content/skin/type.voice10.png diff --git a/content/skin/type.voice16.png b/content/skin/type.voice16.png Binary files differnew file mode 100644 index 0000000..debb016 --- /dev/null +++ b/content/skin/type.voice16.png diff --git a/content/skin/type.work10.png b/content/skin/type.work10.png Binary files differnew file mode 100644 index 0000000..10db4e7 --- /dev/null +++ b/content/skin/type.work10.png diff --git a/content/skin/type.work16.png b/content/skin/type.work16.png Binary files differnew file mode 100644 index 0000000..de3036f --- /dev/null +++ b/content/skin/type.work16.png diff --git a/content/skin/web16.png b/content/skin/web16.png Binary files differnew file mode 100644 index 0000000..3a738a5 --- /dev/null +++ b/content/skin/web16.png diff --git a/content/skin/web32.png b/content/skin/web32.png Binary files differnew file mode 100644 index 0000000..234e2f8 --- /dev/null +++ b/content/skin/web32.png diff --git a/content/skin/web48.png b/content/skin/web48.png Binary files differnew file mode 100644 index 0000000..6909c9e --- /dev/null +++ b/content/skin/web48.png diff --git a/content/skin/yahoo16.png b/content/skin/yahoo16.png Binary files differnew file mode 100644 index 0000000..7225a3a --- /dev/null +++ b/content/skin/yahoo16.png diff --git a/content/skin/yahoo32.png b/content/skin/yahoo32.png Binary files differnew file mode 100644 index 0000000..054ec55 --- /dev/null +++ b/content/skin/yahoo32.png diff --git a/content/skin/yahoo48.png b/content/skin/yahoo48.png Binary files differnew file mode 100644 index 0000000..bfd7d31 --- /dev/null +++ b/content/skin/yahoo48.png diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 0000000..c3c6508 --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,5 @@ +commit_message: https://crowdin.com/project/dav-4-tbsync +files: + - source: /_locales/en-US/* + translation: /_locales/%osx_locale%/%original_file_name% + escape_special_characters: 0 diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..4b28ce1 --- /dev/null +++ b/manifest.json @@ -0,0 +1,35 @@ +{ + "applications": { + "gecko": { + "id": "dav4tbsync@jobisoft.de", + "strict_min_version": "102.3.0", + "strict_max_version": "115.*" + } + }, + "manifest_version": 2, + "name": "__MSG_extensionName__", + "version": "4.7", + "author": "John Bieling", + "homepage_url": "https://github.com/jobisoft/DAV-4-TbSync/", + "default_locale": "en-US", + "description": "__MSG_extensionDescription__", + "icons": { + "32": "content/skin/sabredav32.png" + }, + "background": { + "scripts": ["background.js"] + }, + "permissions": [ + "notifications" + ], + "experiment_apis": { + "BootstrapLoader": { + "schema": "content/api/BootstrapLoader/schema.json", + "parent": { + "scopes": ["addon_parent"], + "paths": [["BootstrapLoader"]], + "script": "content/api/BootstrapLoader/implementation.js" + } + } + } +} diff --git a/screenshots/1.png b/screenshots/1.png Binary files differnew file mode 100644 index 0000000..58b98b2 --- /dev/null +++ b/screenshots/1.png diff --git a/screenshots/2.png b/screenshots/2.png Binary files differnew file mode 100644 index 0000000..81d1c04 --- /dev/null +++ b/screenshots/2.png diff --git a/screenshots/3.png b/screenshots/3.png Binary files differnew file mode 100644 index 0000000..9a4f1e9 --- /dev/null +++ b/screenshots/3.png diff --git a/screenshots/4.png b/screenshots/4.png Binary files differnew file mode 100644 index 0000000..63730a5 --- /dev/null +++ b/screenshots/4.png diff --git a/screenshots/5.png b/screenshots/5.png Binary files differnew file mode 100644 index 0000000..e5a32fb --- /dev/null +++ b/screenshots/5.png diff --git a/screenshots/6.png b/screenshots/6.png Binary files differnew file mode 100644 index 0000000..e15f2a9 --- /dev/null +++ b/screenshots/6.png diff --git a/screenshots/AddAccount.png b/screenshots/AddAccount.png Binary files differnew file mode 100644 index 0000000..cb0c493 --- /dev/null +++ b/screenshots/AddAccount.png diff --git a/screenshots/custom-service.PNG b/screenshots/custom-service.PNG Binary files differnew file mode 100644 index 0000000..374f0cc --- /dev/null +++ b/screenshots/custom-service.PNG diff --git a/screenshots/discoveryservice.PNG b/screenshots/discoveryservice.PNG Binary files differnew file mode 100644 index 0000000..e81e433 --- /dev/null +++ b/screenshots/discoveryservice.PNG diff --git a/screenshots/icloud.png b/screenshots/icloud.png Binary files differnew file mode 100644 index 0000000..8228d24 --- /dev/null +++ b/screenshots/icloud.png diff --git a/screenshots/icloudlist.png b/screenshots/icloudlist.png Binary files differnew file mode 100644 index 0000000..37d7310 --- /dev/null +++ b/screenshots/icloudlist.png diff --git a/screenshots/install.png b/screenshots/install.png Binary files differnew file mode 100644 index 0000000..6f5bfd7 --- /dev/null +++ b/screenshots/install.png diff --git a/screenshots/options.png b/screenshots/options.png Binary files differnew file mode 100644 index 0000000..3e9cab8 --- /dev/null +++ b/screenshots/options.png diff --git a/screenshots/posteo.PNG b/screenshots/posteo.PNG Binary files differnew file mode 100644 index 0000000..775d024 --- /dev/null +++ b/screenshots/posteo.PNG diff --git a/screenshots/sercviceselection.png b/screenshots/sercviceselection.png Binary files differnew file mode 100644 index 0000000..8bdb5e5 --- /dev/null +++ b/screenshots/sercviceselection.png diff --git a/unused/_mbo16.png b/unused/_mbo16.png Binary files differnew file mode 100644 index 0000000..a0ecec5 --- /dev/null +++ b/unused/_mbo16.png diff --git a/unused/_mbo32.png b/unused/_mbo32.png Binary files differnew file mode 100644 index 0000000..a08c4b4 --- /dev/null +++ b/unused/_mbo32.png diff --git a/unused/_mbo48.png b/unused/_mbo48.png Binary files differnew file mode 100644 index 0000000..128f729 --- /dev/null +++ b/unused/_mbo48.png diff --git a/unused/abUI.js b/unused/abUI.js new file mode 100644 index 0000000..22008bc --- /dev/null +++ b/unused/abUI.js @@ -0,0 +1,487 @@ +/* + * This file is part of DAV-4-TbSync. + * + * 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, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +"use strict"; + +var ui = { + + getUriFromDirectoryId: function(ownerId) { + let directories = MailServices.ab.directories; + for (let directory of directories) { + if (directory instanceof Components.interfaces.nsIAbDirectory) { + if (ownerId.startsWith(directory.dirPrefId)) return directory.URI; + } + } + return null; + }, + + + //function to get correct uri of current card for global book as well for mailLists + getSelectedUri : function(aUri, aCard) { + if (aUri == "moz-abdirectory://?") { + //get parent via card owner + let ownerId = aCard.directoryId; + return dav.ui.getUriFromDirectoryId(ownerId); + } else if (MailServices.ab.getDirectory(aUri).isMailList) { + //MailList suck, we have to cut the url to get the parent + return aUri.substring(0, aUri.lastIndexOf("/")) + } else { + return aUri; + } + }, + + + + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + //* Functions to handle advanced UI elements of AB + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + updatePref: function(aDocument, icon, toggle = false) { + if (toggle) { + if (icon.parentNode.meta.includes("PREF")) icon.parentNode.meta = icon.parentNode.meta.filter(e => e != "PREF"); + else icon.parentNode.meta.push("PREF"); + + icon.parentNode.updateFunction (aDocument); + } + + if (icon.parentNode.meta.includes("PREF")) { + icon.setAttribute("src", "chrome://dav4tbsync/content/skin/type.pref.png"); + } else { + icon.setAttribute("src", "chrome://dav4tbsync/content/skin/type.nopref.png"); + } + }, + + updateType: function(aDocument, button, newvalue = null) { + if (newvalue) { + //we declare allowedValues to be non-overlapping -> remove all allowed values and just add the newvalue + button.parentNode.meta = button.parentNode.meta.filter(value => -1 == button.allowedValues.indexOf(value)); + if (button.allowedValues.includes(newvalue)) { + //hardcoded sort order: HOME/WORK always before other types + if (["HOME","WORK"].includes(newvalue)) button.parentNode.meta.unshift(newvalue); + else button.parentNode.meta.push(newvalue); + } + + button.parentNode.updateFunction (aDocument); + } + + let intersection = button.parentNode.meta.filter(value => -1 !== button.allowedValues.indexOf(value)); + let buttonType = (intersection.length > 0) ? intersection[0].toLowerCase() : button.otherIcon; + button.setAttribute("image","chrome://dav4tbsync/content/skin/type."+buttonType+"10.png"); + }, + + dragdrop: { + handleEvent(event) { + //only allow to drag the elements which are valid drag targets + if (event.target.getAttribute("dragtarget") != "true") { + event.stopPropagation(); + return; + } + + let outerbox = event.currentTarget; + let richlistitem = outerbox.parentNode; + + switch (event.type) { + case "dragenter": + case "dragover": + let dropIndex = richlistitem.parentNode.getIndexOfItem(richlistitem); + let dragIndex = richlistitem.parentNode.getIndexOfItem(richlistitem.ownerDocument.getElementById(event.dataTransfer.getData("id"))); + + let centerY = event.currentTarget.clientHeight / 2; + let insertBefore = (event.offsetY < centerY); + let moveNeeded = !(dropIndex == dragIndex || (dropIndex+1 == dragIndex && !insertBefore) || (dropIndex-1 == dragIndex && insertBefore)); + + if (moveNeeded) { + if (insertBefore) { + richlistitem.parentNode.insertBefore(richlistitem.parentNode.getItemAtIndex(dragIndex), richlistitem); + } else { + richlistitem.parentNode.insertBefore(richlistitem.parentNode.getItemAtIndex(dragIndex), richlistitem.nextSibling); + } + } + + event.preventDefault(); + break; + + case "drop": + event.preventDefault(); + case "dragleave": + break; + + case "dragstart": + event.currentTarget.style["background-color"] = "#eeeeee"; + event.dataTransfer.setData("id", richlistitem.id); + break; + + case "dragend": + event.currentTarget.style["background-color"] = "transparent"; + outerbox.updateFunction(outerbox.ownerDocument); + break; + + default: + return undefined; + } + }, + }, + + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + //* Functions to handle multiple email addresses in AB (UI) + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + getNewEmailDetailsRow: function (aWindow, aItemData) { + let emailType = "other"; + if (aItemData.meta.includes("HOME")) emailType = "home"; + else if (aItemData.meta.includes("WORK")) emailType = "work"; + + //first column + let vbox = aWindow.document.createXULElement("vbox"); + vbox.setAttribute("class","CardViewText"); + vbox.setAttribute("style","margin-right:1ex; margin-bottom:0px;"); + let image = aWindow.document.createXULElement("image"); + image.setAttribute("width","10"); + image.setAttribute("height","10"); + image.setAttribute("src", "chrome://dav4tbsync/content/skin/type."+emailType+"10.png"); + vbox.appendChild(image); + + //second column + let description = aWindow.document.createXULElement("description"); + description.setAttribute("class","plain"); + let namespace = aWindow.document.lookupNamespaceURI("html"); + let a = aWindow.document.createElementNS(namespace, "a"); + a.setAttribute("href", "mailto:" + aItemData.value); + a.textContent = aItemData.value; + description.appendChild(a); + + if (aItemData.meta.includes("PREF")) { + let pref = aWindow.document.createXULElement("image"); + pref.setAttribute("style", "margin-left:1ex;"); + pref.setAttribute("width", "11"); + pref.setAttribute("height", "10"); + pref.setAttribute("src", "chrome://dav4tbsync/content/skin/type.nopref.png"); + description.appendChild(pref); + } + + //row + let row = aWindow.document.createElement("tr"); + let cell1 = aWindow.document.createElement("td"); + let cell2 = aWindow.document.createElement("td"); + cell2.setAttribute("width","100%"); + + cell1.appendChild(vbox); + cell2.appendChild(description); + + row.appendChild(cell1); + row.appendChild(cell2); + return row; + }, + + getNewEmailListItem: function (aDocument, aItemData) { + //hbox + let outerhbox = aDocument.createXULElement("hbox"); + outerhbox.setAttribute("dragtarget", "true"); + outerhbox.setAttribute("flex", "1"); + outerhbox.setAttribute("align", "center"); + outerhbox.setAttribute("style", "padding:0; margin:0"); + outerhbox.updateFunction = dav.ui.updateEmails; + outerhbox.meta = aItemData.meta; + + outerhbox.addEventListener("dragenter", dav.ui.dragdrop); + outerhbox.addEventListener("dragover", dav.ui.dragdrop); + outerhbox.addEventListener("dragleave", dav.ui.dragdrop); + outerhbox.addEventListener("dragstart", dav.ui.dragdrop); + outerhbox.addEventListener("dragend", dav.ui.dragdrop); + outerhbox.addEventListener("drop", dav.ui.dragdrop); + + outerhbox.style["background-image"] = "url('chrome://dav4tbsync/content/skin/dragdrop.png')"; + outerhbox.style["background-position"] = "right"; + outerhbox.style["background-repeat"] = "no-repeat"; + + //button + let button = aDocument.createXULElement("button"); + button.allowedValues = ["HOME", "WORK"]; + button.otherIcon = "other"; + button.setAttribute("type", "menu"); + button.setAttribute("style", "width: 35px; min-width: 35px; margin: 0; padding:0"); + button.appendChild(aDocument.getElementById("DavMenuTemplate").children[0].cloneNode(true)); + outerhbox.appendChild(button); + + //email box + let emailbox = aDocument.createXULElement("hbox"); + emailbox.setAttribute("flex", "1"); + let email = aDocument.createElement("input"); + email.setAttribute("style", "width: 240px"); + email.setAttribute("value", aItemData.value); + email.addEventListener("change", function(e) {dav.ui.updateEmails(aDocument)}); + email.addEventListener("keydown", function(e) {if (e.key == "Enter") {e.stopPropagation(); e.preventDefault(); if (e.target.value != "") { dav.ui.addEmailEntry(e.target.ownerDocument); }}}); + emailbox.appendChild(email); + outerhbox.appendChild(emailbox); + + //image + let image = aDocument.createXULElement("image"); + image.setAttribute("width", "11"); + image.setAttribute("height", "10"); + image.setAttribute("style", "margin:2px 20px 2px 1ex"); + image.addEventListener("click", function(e) { dav.ui.updatePref(aDocument, e.target, true); }); + outerhbox.appendChild(image); + + //richlistitem + let richlistitem = aDocument.createXULElement("richlistitem"); + richlistitem.setAttribute("id", "entry_" + TbSync.generateUUID()); + richlistitem.setAttribute("style", "padding:0;margin:0;"); + richlistitem.appendChild(outerhbox); + + return richlistitem; + }, + + getEmailListItemElement: function(item, element) { + switch (element) { + case "dataContainer": + return item.children[0]; + case "button": + return item.children[0].children[0]; + case "email": + return item.children[0].children[1].children[0]; + case "pref": + return item.children[0].children[2]; + default: + return null; + } + }, + + addEmailEntry: function(aDocument) { + let list = aDocument.getElementById("X-DAV-EmailAddressList"); + let data = {value: "", meta: ["HOME"]}; + let item = list.appendChild(dav.ui.getNewEmailListItem(aDocument, data)); + list.ensureElementIsVisible(item); + + dav.ui.updateType(aDocument, dav.ui.getEmailListItemElement(item, "button")); + dav.ui.updatePref(aDocument, dav.ui.getEmailListItemElement(item, "pref")); + + dav.ui.getEmailListItemElement(item, "email").focus(); + }, + + + //if any setting changed, we need to update Primary and Secondary Email Fields + updateEmails: function(aDocument) { + let list = aDocument.getElementById("X-DAV-EmailAddressList"); + + let emails = []; + for (let i=0; i < list.children.length; i++) { + let item = list.children[i]; + let email = dav.ui.getEmailListItemElement(item, "email").value.trim(); + if (email != "") { + let json = {}; + json.meta = dav.ui.getEmailListItemElement(item, "dataContainer").meta; + json.value = email; + emails.push(json); + } + } + aDocument.getElementById("X-DAV-JSON-Emails").value = JSON.stringify(emails); + + //now update all other TB email fields based on the new JSON data + let emailData = dav.tools.getEmailsFromJSON(aDocument.getElementById("X-DAV-JSON-Emails").value); + for (let field in emailData) { + if (emailData.hasOwnProperty(field)) { + aDocument.getElementById(field).value = emailData[field].join(", "); + } + } + }, + + + + + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * + //* Functions to handle multiple phone numbers in AB (UI) + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + getNewPhoneDetailsRow: function (aWindow, aItemData) { + let phoneType1 = ""; + if (aItemData.meta.includes("HOME")) phoneType1 = "home"; + else if (aItemData.meta.includes("WORK")) phoneType1 = "work"; + + let phoneType2 = ""; + if (aItemData.meta.includes("CELL")) phoneType2 = "cell"; + else if (aItemData.meta.includes("FAX")) phoneType2 = "fax"; + else if (aItemData.meta.includes("PAGER")) phoneType2 = "pager"; + else if (aItemData.meta.includes("CAR")) phoneType2 = "car"; + else if (aItemData.meta.includes("VIDEO")) phoneType2 = "video"; + else if (aItemData.meta.includes("VOICE")) phoneType2 = "voice"; + + //first column + let vbox = aWindow.document.createXULElement("hbox"); + vbox.setAttribute("pack","end"); + vbox.setAttribute("class","CardViewText"); + vbox.setAttribute("style","margin-bottom:0px;"); + if (phoneType1) { + let image = aWindow.document.createXULElement("image"); + image.setAttribute("style","margin-right:1ex;"); + image.setAttribute("width","10"); + image.setAttribute("height","10"); + image.setAttribute("src", "chrome://dav4tbsync/content/skin/type."+phoneType1+"10.png"); + vbox.appendChild(image); + } + if (phoneType2) { + let image = aWindow.document.createXULElement("image"); + image.setAttribute("style","margin-right:1ex;"); + image.setAttribute("width","10"); + image.setAttribute("height","10"); + image.setAttribute("src", "chrome://dav4tbsync/content/skin/type."+phoneType2+"10.png"); + vbox.appendChild(image); + } + + //second column + let description = aWindow.document.createXULElement("description"); + description.setAttribute("class","plain"); + description.setAttribute("style","-moz-user-select: text;"); + description.textContent = aItemData.value; + + if (aItemData.meta.includes("PREF")) { + let pref = aWindow.document.createXULElement("image"); + pref.setAttribute("style", "margin-left:1ex;"); + pref.setAttribute("width", "11"); + pref.setAttribute("height", "10"); + pref.setAttribute("src", "chrome://dav4tbsync/content/skin/type.nopref.png"); + description.appendChild(pref); + } + + //row + let row = aWindow.document.createElement("tr"); + let cell1 = aWindow.document.createElement("td"); + let cell2 = aWindow.document.createElement("td"); + cell2.setAttribute("width","100%"); + + cell1.appendChild(vbox); + cell2.appendChild(description); + + row.appendChild(cell1); + row.appendChild(cell2); + return row; + }, + + getNewPhoneListItem: function (aDocument, aItemData) { + //hbox + let outerhbox = aDocument.createXULElement("hbox"); + outerhbox.setAttribute("dragtarget", "true"); + outerhbox.setAttribute("flex", "1"); + outerhbox.setAttribute("align", "center"); + outerhbox.setAttribute("style", "padding:0; margin:0"); + outerhbox.updateFunction = dav.ui.updatePhoneNumbers; + outerhbox.meta = aItemData.meta; + + outerhbox.addEventListener("dragenter", dav.ui.dragdrop); + outerhbox.addEventListener("dragover", dav.ui.dragdrop); + outerhbox.addEventListener("dragleave", dav.ui.dragdrop); + outerhbox.addEventListener("dragstart", dav.ui.dragdrop); + outerhbox.addEventListener("dragend", dav.ui.dragdrop); + outerhbox.addEventListener("drop", dav.ui.dragdrop); + + outerhbox.style["background-image"] = "url('chrome://dav4tbsync/content/skin/dragdrop.png')"; + outerhbox.style["background-position"] = "right"; + outerhbox.style["background-repeat"] = "no-repeat"; + + //button1 + let button1 = aDocument.createXULElement("button"); + button1.allowedValues = ["HOME", "WORK"]; + button1.otherIcon = "none"; + button1.setAttribute("type", "menu"); + button1.setAttribute("style", "width: 35px; min-width: 35px; margin: 0; padding:0"); + button1.appendChild(aDocument.getElementById("DavMenuTemplate").children[1].cloneNode(true)); + outerhbox.appendChild(button1); + + //button2 + let button2 = aDocument.createXULElement("button"); + button2.allowedValues = ["CELL", "FAX", "PAGER", "CAR", "VIDEO", "VOICE"] ; //same order as in getNewPhoneDetailsRow + button2.otherIcon = "none"; + button2.setAttribute("type", "menu"); + button2.setAttribute("class", "plain"); + button2.setAttribute("style", "width: 35px; min-width: 35px; margin: 0;"); + button2.appendChild(aDocument.getElementById("DavMenuTemplate").children[2].cloneNode(true)); + outerhbox.appendChild(button2); + + //phone box + let phonebox = aDocument.createXULElement("hbox"); + phonebox.setAttribute("flex", "1"); + let phone = aDocument.createElement("input"); + phone.setAttribute("style", "width: 205px"); + phone.setAttribute("value", aItemData.value); + phone.addEventListener("change", function(e) {dav.ui.updatePhoneNumbers(aDocument)}); + phone.addEventListener("keydown", function(e) {if (e.key == "Enter") {e.stopPropagation(); e.preventDefault(); if (e.target.value != "") { dav.ui.addPhoneEntry(e.target.ownerDocument); }}}); + phonebox.appendChild(phone); + outerhbox.appendChild(phonebox); + + //image + let image = aDocument.createXULElement("image"); + image.setAttribute("width", "11"); + image.setAttribute("height", "10"); + image.setAttribute("style", "margin:2px 20px 2px 1ex"); + image.addEventListener("click", function(e) { dav.ui.updatePref(aDocument, e.target, true); }); + outerhbox.appendChild(image); + + //richlistitem + let richlistitem = aDocument.createXULElement("richlistitem"); + richlistitem.setAttribute("id", "entry_" + TbSync.generateUUID()); + richlistitem.setAttribute("style", "padding:0;margin:0;"); + richlistitem.appendChild(outerhbox); + + return richlistitem; + }, + + updatePhoneNumbers: function(aDocument) { + let list = aDocument.getElementById("X-DAV-PhoneNumberList"); + + let phones = []; + for (let i=0; i < list.children.length; i++) { + let item = list.children[i]; + let phone = dav.ui.getPhoneListItemElement(item, "phone").value.trim(); + if (phone != "") { + let json = {}; + json.meta = dav.ui.getPhoneListItemElement(item, "dataContainer").meta; + json.value = phone; + phones.push(json); + } + } + aDocument.getElementById("X-DAV-JSON-Phones").value = JSON.stringify(phones); + + //now update all other TB number fields based on the new JSON data + let phoneData = dav.tools.getPhoneNumbersFromJSON(aDocument.getElementById("X-DAV-JSON-Phones").value); + for (let field in phoneData) { + if (phoneData.hasOwnProperty(field)) { + aDocument.getElementById(field).value = phoneData[field].join(", "); + } + } + }, + + addPhoneEntry: function(aDocument) { + let list = aDocument.getElementById("X-DAV-PhoneNumberList"); + let data = {value: "", meta: ["VOICE"]}; + let item = list.appendChild(dav.ui.getNewPhoneListItem(aDocument, data)); + list.ensureElementIsVisible(item); + + dav.ui.updateType(aDocument, dav.ui.getPhoneListItemElement(item, "button1")); + dav.ui.updateType(aDocument, dav.ui.getPhoneListItemElement(item, "button2")); + dav.ui.updatePref(aDocument, dav.ui.getPhoneListItemElement(item, "pref")); + + dav.ui.getPhoneListItemElement(item, "phone").focus(); + }, + + getPhoneListItemElement: function(item, element) { + switch (element) { + case "dataContainer": + return item.children[0]; + case "button1": + return item.children[0].children[0]; + case "button2": + return item.children[0].children[1]; + case "phone": + return item.children[0].children[2].children[0]; + case "pref": + return item.children[0].children[3]; + default: + return null; + } + }, + +} diff --git a/unused/iconfinder_17_939744_16.png b/unused/iconfinder_17_939744_16.png Binary files differnew file mode 100644 index 0000000..00eab4b --- /dev/null +++ b/unused/iconfinder_17_939744_16.png diff --git a/unused/iconfinder_17_939744_32.png b/unused/iconfinder_17_939744_32.png Binary files differnew file mode 100644 index 0000000..baacddc --- /dev/null +++ b/unused/iconfinder_17_939744_32.png diff --git a/unused/iconfinder_17_939744_64.png b/unused/iconfinder_17_939744_64.png Binary files differnew file mode 100644 index 0000000..b6a227b --- /dev/null +++ b/unused/iconfinder_17_939744_64.png diff --git a/unused/iconfinder_Apple_1298725_16.png b/unused/iconfinder_Apple_1298725_16.png Binary files differnew file mode 100644 index 0000000..fd5fa79 --- /dev/null +++ b/unused/iconfinder_Apple_1298725_16.png diff --git a/unused/iconfinder_Apple_1298725_32.png b/unused/iconfinder_Apple_1298725_32.png Binary files differnew file mode 100644 index 0000000..0815a20 --- /dev/null +++ b/unused/iconfinder_Apple_1298725_32.png diff --git a/unused/iconfinder_Apple_1298725_64.png b/unused/iconfinder_Apple_1298725_64.png Binary files differnew file mode 100644 index 0000000..2d05773 --- /dev/null +++ b/unused/iconfinder_Apple_1298725_64.png diff --git a/unused/mbo48_2.png b/unused/mbo48_2.png Binary files differnew file mode 100644 index 0000000..0379947 --- /dev/null +++ b/unused/mbo48_2.png diff --git a/unused/orig_sync.js b/unused/orig_sync.js new file mode 100644 index 0000000..ac654f8 --- /dev/null +++ b/unused/orig_sync.js @@ -0,0 +1,908 @@ +/* +/* + * This file is part of DAV-4-TbSync. + * + * 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, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +"use strict"; + +var sync = { + + finish: function (aStatus = "", msg = "", details = "") { + let status = TbSync.StatusData.SUCCESS + switch (aStatus) { + + case "": + case "ok": + status = TbSync.StatusData.SUCCESS; + break; + + case "info": + status = TbSync.StatusData.INFO; + break; + + case "resyncAccount": + status = TbSync.StatusData.ACCOUNT_RERUN; + break; + + case "resyncFolder": + status = TbSync.StatusData.FOLDER_RERUN; + break; + + case "warning": + status = TbSync.StatusData.WARNING; + break; + + case "error": + status = TbSync.StatusData.ERROR; + break; + + default: + console.log("TbSync/DAV: Unknown status <"+aStatus+">"); + status = TbSync.StatusData.ERROR; + break; + } + + let e = new Error(); + e.name = "dav4tbsync"; + e.message = status.toUpperCase() + ": " + msg.toString() + " (" + details.toString() + ")"; + e.statusData = new TbSync.StatusData(status, msg.toString(), details.toString()); + return e; + }, + + prefSettings: Services.prefs.getBranch("extensions.dav4tbsync."), + + ns: { + d: "DAV:", + cal: "urn:ietf:params:xml:ns:caldav" , + card: "urn:ietf:params:xml:ns:carddav" , + cs: "http://calendarserver.org/ns/", + s: "http://sabredav.org/ns", + apple: "http://apple.com/ns/ical/" + }, + + serviceproviders: { + "fruux" : {revision: 1, icon: "fruux", caldav: "https://dav.fruux.com", carddav: "https://dav.fruux.com"}, + "mbo" : {revision: 1, icon: "mbo", caldav: "caldav6764://mailbox.org", carddav: "carddav6764://mailbox.org"}, + "icloud" : {revision: 1, icon: "icloud", caldav: "https://caldav.icloud.com", carddav: "https://contacts.icloud.com"}, + "gmx.net" : {revision: 1, icon: "gmx", caldav: "caldav6764://gmx.net", carddav: "carddav6764://gmx.net"}, + "gmx.com" : {revision: 1, icon: "gmx", caldav: "caldav6764://gmx.com", carddav: "carddav6764://gmx.com"}, + "posteo" : {revision: 1, icon: "posteo", caldav: "https://posteo.de:8443", carddav: "posteo.de:8843"}, + "web.de" : {revision: 1, icon: "web", caldav: "caldav6764://web.de", carddav: "carddav6764://web.de"}, + "yahoo" : {revision: 1, icon: "yahoo", caldav: "caldav6764://yahoo.com", carddav: "carddav6764://yahoo.com"}, + }, + + onChange(abItem) { + if (!this._syncOnChangeTimers) + this._syncOnChangeTimers = {}; + + this._syncOnChangeTimers[abItem.abDirectory.UID] = {}; + this._syncOnChangeTimers[abItem.abDirectory.UID].timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer); + this._syncOnChangeTimers[abItem.abDirectory.UID].event = { + notify: function(timer) { + // if account is syncing, re-schedule + // if folder got synced after the start time (due to re-scheduling) abort + console.log("DONE: "+ abItem.abDirectory.UID); + } + } + + this._syncOnChangeTimers[abItem.abDirectory.UID].timer.initWithCallback( + this._syncOnChangeTimers[abItem.abDirectory.UID].event, + 2000, + Components.interfaces.nsITimer.TYPE_ONE_SHOT); + }, + + resetFolderSyncInfo : function (folderData) { + folderData.resetFolderProperty("ctag"); + folderData.resetFolderProperty("token"); + folderData.setFolderProperty("createdWithProviderVersion", folderData.accountData.providerData.getVersion()); + }, + + folderList: async function (syncData) { + //Method description: http://sabre.io/dav/building-a-caldav-client/ + //get all folders currently known + let folderTypes = ["caldav", "carddav", "ics"]; + let unhandledFolders = {}; + for (let type of folderTypes) { + unhandledFolders[type] = []; + } + + + let folders = syncData.accountData.getAllFolders(); + for (let folder of folders) { + //just in case + if (!unhandledFolders.hasOwnProperty(folder.getFolderProperty("type"))) { + unhandledFolders[folder.getFolderProperty("type")] = []; + } + unhandledFolders[folder.getFolderProperty("type")].push(folder); + } + + // refresh urls of service provider, if they have been updated + let serviceprovider = syncData.accountData.getAccountProperty("serviceprovider"); + let serviceproviderRevision = syncData.accountData.getAccountProperty("serviceproviderRevision"); + if (dav.sync.serviceproviders.hasOwnProperty(serviceprovider) && serviceproviderRevision != dav.sync.serviceproviders[serviceprovider].revision) { + TbSync.eventlog.add("info", syncData.eventLogInfo, "updatingServiceProvider", serviceprovider); + syncData.accountData.setAccountProperty("serviceproviderRevision", dav.sync.serviceproviders[serviceprovider].revision); + syncData.accountData.resetAccountProperty("calDavPrincipal"); + syncData.accountData.resetAccountProperty("cardDavPrincipal"); + syncData.accountData.setAccountProperty("calDavHost", dav.sync.serviceproviders[serviceprovider].caldav); + syncData.accountData.setAccountProperty("cardDavHost", dav.sync.serviceproviders[serviceprovider].carddav); + } + + let davjobs = { + cal : {server: syncData.accountData.getAccountProperty("calDavHost")}, + card : {server: syncData.accountData.getAccountProperty("cardDavHost")}, + }; + + for (let job in davjobs) { + if (!davjobs[job].server) continue; + + // SOGo needs some special handling for shared addressbooks. We detect it by having SOGo/dav in the url. + let isSogo = davjobs[job].server.includes("/SOGo/dav"); + + //sync states are only printed while the account state is "syncing" to inform user about sync process (it is not stored in DB, just in syncData) + //example state "getfolders" to get folder information from server + //if you send a request to a server and thus have to wait for answer, use a "send." syncstate, which will give visual feedback to the user, + //that we are waiting for an answer with timeout countdown + + let home = []; + let own = []; + + // migration code for http setting, we might keep it as a fallback, if user removed the http:// scheme from the url in the settings + if (!dav.network.startsWithScheme(davjobs[job].server)) { + davjobs[job].server = "http" + (syncData.accountData.getAccountProperty("https") ? "s" : "") + "://" + davjobs[job].server; + syncData.accountData.setAccountProperty(job + "DavHost", davjobs[job].server); + } + + //add connection to syncData + syncData.connectionData = new dav.network.ConnectionData(syncData); + + //only do that, if a new calendar has been enabled + TbSync.network.resetContainerForUser(syncData.connectionData.username); + + syncData.setSyncState("send.getfolders"); + let principal = syncData.accountData.getAccountProperty(job + "DavPrincipal"); // defaults to null + if (principal === null) { + + let response = await dav.network.sendRequest("<d:propfind "+dav.tools.xmlns(["d"])+"><d:prop><d:current-user-principal /></d:prop></d:propfind>", davjobs[job].server , "PROPFIND", syncData.connectionData, {"Depth": "0", "Prefer": "return=minimal"}); + syncData.setSyncState("eval.folders"); + + // keep track of permanent redirects for the server URL + if (response && response.permanentlyRedirectedUrl) { + syncData.accountData.setAccountProperty(job + "DavHost", response.permanentlyRedirectedUrl) + } + + // store dav options send by server + if (response && response.davOptions) { + syncData.accountData.setAccountProperty(job + "DavOptions", response.davOptions.split(",").map(e => e.trim())); + } + + // allow 404 because iCloud sends it on valid answer (yeah!) + if (response && response.multi) { + principal = dav.tools.getNodeTextContentFromMultiResponse(response, [["d","prop"], ["d","current-user-principal"], ["d","href"]], null, ["200","404"]); + } + } + + //principal now contains something like "/remote.php/carddav/principals/john.bieling/" + //principal can also be an absolute url + // -> get home/root of storage + if (principal !== null) { + syncData.setSyncState("send.getfolders"); + + let options = syncData.accountData.getAccountProperty(job + "DavOptions"); + + let homeset = (job == "cal") + ? "calendar-home-set" + : "addressbook-home-set"; + + let request = "<d:propfind "+dav.tools.xmlns(["d", job, "cs"])+"><d:prop><"+job+":" + homeset + " />" + + (job == "cal" && options.includes("calendar-proxy") ? "<cs:calendar-proxy-write-for /><cs:calendar-proxy-read-for />" : "") + + "<d:group-membership />" + + "</d:prop></d:propfind>"; + + let response = await dav.network.sendRequest(request, principal, "PROPFIND", syncData.connectionData, {"Depth": "0", "Prefer": "return=minimal"}); + syncData.setSyncState("eval.folders"); + + // keep track of permanent redirects for the principal URL + if (response && response.permanentlyRedirectedUrl) { + principal = response.permanentlyRedirectedUrl; + } + + own = dav.tools.getNodesTextContentFromMultiResponse(response, [["d","prop"], [job, homeset ], ["d","href"]], principal); + home = own.concat(dav.tools.getNodesTextContentFromMultiResponse(response, [["d","prop"], ["cs", "calendar-proxy-read-for" ], ["d","href"]], principal)); + home = home.concat(dav.tools.getNodesTextContentFromMultiResponse(response, [["d","prop"], ["cs", "calendar-proxy-write-for" ], ["d","href"]], principal)); + + //Any groups we need to find? Only diving one level at the moment, + let g = dav.tools.getNodesTextContentFromMultiResponse(response, [["d","prop"], ["d", "group-membership" ], ["d","href"]], principal); + for (let gc=0; gc < g.length; gc++) { + //SOGo reports a 403 if I request the provided resource, also since we do not dive, remove the request for group-membership + response = await dav.network.sendRequest(request.replace("<d:group-membership />",""), g[gc], "PROPFIND", syncData.connectionData, {"Depth": "0", "Prefer": "return=minimal"}, {softfail: [403, 404]}); + if (response && response.softerror) { + continue; + } + home = home.concat(dav.tools.getNodesTextContentFromMultiResponse(response, [["d","prop"], [job, homeset ], ["d","href"]], g[gc])); + } + + //calendar-proxy and group-membership could have returned the same values, make the homeset unique + home = home.filter((v,i,a) => a.indexOf(v) == i); + } else { + // do not throw here, but log the error and skip this server + TbSync.eventlog.add("error", syncData.eventLogInfo, job+"davservernotfound", davjobs[job].server); + } + + //home now contains something like /remote.php/caldav/calendars/john.bieling/ + // -> get all resources + if (home.length > 0) { + // the used principal returned valid resources, store/update it + // as the principal is being used as a starting point, it must be stored as absolute url + syncData.accountData.setAccountProperty(job + "DavPrincipal", dav.network.startsWithScheme(principal) + ? principal + : "http" + (syncData.connectionData.https ? "s" : "") + "://" + syncData.connectionData.fqdn + principal); + + for (let h=0; h < home.length; h++) { + syncData.setSyncState("send.getfolders"); + let request = (job == "cal") + ? "<d:propfind "+dav.tools.xmlns(["d","apple","cs"])+"><d:prop><d:current-user-privilege-set/><d:resourcetype /><d:displayname /><apple:calendar-color/><cs:source/></d:prop></d:propfind>" + : "<d:propfind "+dav.tools.xmlns(["d"])+"><d:prop><d:current-user-privilege-set/><d:resourcetype /><d:displayname /></d:prop></d:propfind>"; + + //some servers report to have calendar-proxy-read but return a 404 when that gets actually queried + let response = await dav.network.sendRequest(request, home[h], "PROPFIND", syncData.connectionData, {"Depth": "1", "Prefer": "return=minimal"}, {softfail: [403, 404]}); + if (response && response.softerror) { + continue; + } + + for (let r=0; r < response.multi.length; r++) { + if (response.multi[r].status != "200") continue; + + let resourcetype = null; + //is this a result with a valid recourcetype? (the node must be present) + switch (job) { + case "card": + if (dav.tools.evaluateNode(response.multi[r].node, [["d","prop"], ["d","resourcetype"], ["card", "addressbook"]]) !== null) resourcetype = "carddav"; + break; + + case "cal": + if (dav.tools.evaluateNode(response.multi[r].node, [["d","prop"], ["d","resourcetype"], ["cal", "calendar"]]) !== null) resourcetype = "caldav"; + else if (dav.tools.evaluateNode(response.multi[r].node, [["d","prop"], ["d","resourcetype"], ["cs", "subscribed"]]) !== null) resourcetype = "ics"; + break; + } + if (resourcetype === null) continue; + + //get ACL (grant read rights per default, if it is SOGo, as they do not send that permission) + let acl = isSogo ? 0x1 : 0; + + let privilegNode = dav.tools.evaluateNode(response.multi[r].node, [["d","prop"], ["d","current-user-privilege-set"]]); + if (privilegNode) { + if (privilegNode.getElementsByTagNameNS(dav.sync.ns.d, "all").length > 0) { + acl = 0xF; //read=1, mod=2, create=4, delete=8 + } else { + // check for individual write permissions + if (privilegNode.getElementsByTagNameNS(dav.sync.ns.d, "write").length > 0) { + acl = 0xF; + } else { + if (privilegNode.getElementsByTagNameNS(dav.sync.ns.d, "write-content").length > 0) acl |= 0x2; + if (privilegNode.getElementsByTagNameNS(dav.sync.ns.d, "bind").length > 0) acl |= 0x4; + if (privilegNode.getElementsByTagNameNS(dav.sync.ns.d, "unbind").length > 0) acl |= 0x8; + } + + // check for read permission (implying read if any write is given) + if (privilegNode.getElementsByTagNameNS(dav.sync.ns.d, "read").length > 0 || acl != 0) acl |= 0x1; + } + } + + //ignore this resource, if no read access + if ((acl & 0x1) == 0) continue; + + let href = response.multi[r].href; + if (resourcetype == "ics") href = dav.tools.evaluateNode(response.multi[r].node, [["d","prop"], ["cs","source"], ["d","href"]]).textContent; + + let name_node = dav.tools.evaluateNode(response.multi[r].node, [["d","prop"], ["d","displayname"]]); + let name = TbSync.getString("defaultname." + ((job == "cal") ? "calendar" : "contacts") , "dav"); + if (name_node != null) { + name = name_node.textContent; + } + let color = dav.tools.evaluateNode(response.multi[r].node, [["d","prop"], ["apple","calendar-color"]]); + + //remove found folder from list of unhandled folders + unhandledFolders[resourcetype] = unhandledFolders[resourcetype].filter(item => item.getFolderProperty("href") !== href); + + + // interaction with TbSync + // do we have a folder for that href? + let folderData = syncData.accountData.getFolder("href", href); + if (!folderData) { + // create a new folder entry + folderData = syncData.accountData.createNewFolder(); + // this MUST be set to either "addressbook" or "calendar" to use the standard target support, or any other value, which + // requires a corresponding targets implementation by this provider + folderData.setFolderProperty("targetType", (job == "card") ? "addressbook" : "calendar"); + + folderData.setFolderProperty("href", href); + folderData.setFolderProperty("foldername", name); + folderData.setFolderProperty("type", resourcetype); + folderData.setFolderProperty("shared", !own.includes(home[h])); + folderData.setFolderProperty("acl", acl.toString()); + folderData.setFolderProperty("downloadonly", (acl == 0x1)); //if any write access is granted, setup as writeable + + //we assume the folder has the same fqdn as the homeset, otherwise href must contain the full URL and the fqdn is ignored + folderData.setFolderProperty("fqdn", syncData.connectionData.fqdn); + folderData.setFolderProperty("https", syncData.connectionData.https); + + //do we have a cached folder? + let cachedFolderData = syncData.accountData.getFolderFromCache("href", href); + if (cachedFolderData) { + // copy fields from cache which we want to re-use + folderData.setFolderProperty("targetColor", cachedFolderData.getFolderProperty("targetColor")); + folderData.setFolderProperty("targetName", cachedFolderData.getFolderProperty("targetName")); + //if we have only READ access, do not restore cached value for downloadonly + if (acl > 0x1) folderData.setFolderProperty("downloadonly", cachedFolderData.getFolderProperty("downloadonly")); + } + } else { + //Update name & color + folderData.setFolderProperty("foldername", name); + folderData.setFolderProperty("fqdn", syncData.connectionData.fqdn); + folderData.setFolderProperty("https", syncData.connectionData.https); + folderData.setFolderProperty("acl", acl); + //if the acl changed from RW to RO we need to update the downloadonly setting + if (acl == 0x1) { + folderData.setFolderProperty("downloadonly", true); + } + } + + // Update color from server. + if (color && job == "cal") { + color = color.textContent.substring(0,7); + folderData.setFolderProperty("targetColor", color); + + // Do we have to update the calendar? + if (folderData.targetData && folderData.targetData.hasTarget()) { + try { + let targetCal = await folderData.targetData.getTarget(); + targetCal.calendar.setProperty("color", color); + } catch (e) { + Components.utils.reportError(e) + } + } + } + } + } + } else { + //home was not found - connection error? - do not delete unhandled folders + switch (job) { + case "card": + unhandledFolders.carddav = []; + break; + + case "cal": + unhandledFolders.caldav = []; + unhandledFolders.ics = []; + break; + } + //reset stored principal + syncData.accountData.resetAccountProperty(job + "DavPrincipal"); + } + } + + // Remove unhandled old folders, (because they no longer exist on the server). + // Do not delete the targets, but keep them as stale/unconnected elements. + for (let type of folderTypes) { + for (let folder of unhandledFolders[type]) { + folder.remove("[deleted on server]"); + } + } + }, + + + + + + + folder: async function (syncData) { + // add connection data to syncData + syncData.connectionData = new dav.network.ConnectionData(syncData); + + // add target to syncData + let hadTarget; + try { + // accessing the target for the first time will check if it is avail and if not will create it (if possible) + hadTarget = syncData.currentFolderData.targetData.hasTarget(); + syncData.target = await syncData.currentFolderData.targetData.getTarget(); + } catch (e) { + Components.utils.reportError(e); + throw dav.sync.finish("warning", e.message); + } + + switch (syncData.currentFolderData.getFolderProperty("type")) { + case "carddav": + { + await dav.sync.singleFolder(syncData); + } + break; + + case "caldav": + case "ics": + { + // update downloadonly - we do not use TbCalendar (syncData.target) but the underlying lightning calendar obj + if (syncData.currentFolderData.getFolderProperty("downloadonly")) syncData.target.calendar.setProperty("readOnly", true); + + // update username of calendar + syncData.target.calendar.setProperty("username", syncData.connectionData.username); + + //init sync via lightning + if (hadTarget) syncData.target.calendar.refresh(); + + throw dav.sync.finish("ok", "managed-by-thunderbird"); + } + break; + + default: + { + throw dav.sync.finish("warning", "notsupported"); + } + break; + } + }, + + + singleFolder: async function (syncData) { + let downloadonly = syncData.currentFolderData.getFolderProperty("downloadonly"); + + // we have to abort sync of this folder, if it is contact, has groupSync enabled and gContactSync is enabled + let syncGroups = syncData.accountData.getAccountProperty("syncGroups"); + let gContactSync = await AddonManager.getAddonByID("gContactSync@pirules.net") ; + let contactSync = (syncData.currentFolderData.getFolderProperty("type") == "carddav"); + if (syncGroups && contactSync && gContactSync && gContactSync.isActive) { + throw dav.sync.finish("warning", "gContactSync"); + } + + await dav.sync.remoteChanges(syncData); + let numOfLocalChanges = await dav.sync.localChanges(syncData); + + //revert all local changes on permission error by doing a clean sync + if (numOfLocalChanges < 0) { + dav.sync.resetFolderSyncInfo(syncData.currentFolderData); + await dav.sync.remoteChanges(syncData); + + if (!downloadonly) throw dav.sync.finish("info", "info.restored"); + } else if (numOfLocalChanges > 0){ + //we will get back our own changes and can store etags and vcards and also get a clean ctag/token + await dav.sync.remoteChanges(syncData); + } + }, + + + + + + + + + + + remoteChanges: async function (syncData) { + //Do we have a sync token? No? -> Initial Sync (or WebDAV sync not supported) / Yes? -> Get updates only (token only present if WebDAV sync is suported) + let token = syncData.currentFolderData.getFolderProperty("token"); + if (token) { + //update via token sync + let tokenSyncSucceeded = await dav.sync.remoteChangesByTOKEN(syncData); + if (tokenSyncSucceeded) return; + + //token sync failed, reset ctag and token and do a full sync + dav.sync.resetFolderSyncInfo(syncData.currentFolderData); + } + + //Either token sync did not work or there is no token (initial sync) + //loop until ctag is the same before and after polling data (sane start condition) + let maxloops = 20; + for (let i=0; i <= maxloops; i++) { + if (i == maxloops) + throw dav.sync.finish("warning", "could-not-get-stable-ctag"); + + let ctagChanged = await dav.sync.remoteChangesByCTAG(syncData); + if (!ctagChanged) break; + } + }, + + remoteChangesByTOKEN: async function (syncData) { + syncData.progressData.reset(); + + let token = syncData.currentFolderData.getFolderProperty("token"); + syncData.setSyncState("send.request.remotechanges"); + let cards = await dav.network.sendRequest("<d:sync-collection "+dav.tools.xmlns(["d"])+"><d:sync-token>"+token+"</d:sync-token><d:sync-level>1</d:sync-level><d:prop><d:getetag/></d:prop></d:sync-collection>", syncData.currentFolderData.getFolderProperty("href"), "REPORT", syncData.connectionData, {}, {softfail: [415,403,409]}); + + //EteSync throws 409 because it does not support sync-token + //Sabre\DAV\Exception\ReportNotSupported - Unsupported media type - returned by fruux if synctoken is 0 (empty book), 415 & 403 + //https://github.com/sabre-io/dav/issues/1075 + //Sabre\DAV\Exception\InvalidSyncToken (403) + if (cards && cards.softerror) { + //token sync failed, reset ctag and do a full sync + return false; + } + + let tokenNode = dav.tools.evaluateNode(cards.node, [["d", "sync-token"]]); + if (tokenNode === null) { + //token sync failed, reset ctag and do a full sync + return false; + } + + let vCardsDeletedOnServer = []; + let vCardsChangedOnServer = {}; + + let localDeletes = syncData.target.getDeletedItemsFromChangeLog(); + + let cardsFound = 0; + for (let c=0; c < cards.multi.length; c++) { + let id = cards.multi[c].href; + if (id !==null) { + //valid + let card = await syncData.target.getItemFromProperty("X-DAV-HREF", id); + if (cards.multi[c].status == "200") { + //MOD or ADD + let etag = dav.tools.evaluateNode(cards.multi[c].node, [["d","prop"], ["d","getetag"]]); + if (!card) { + //if the user deleted this card (not yet send to server), do not add it again + if (!localDeletes.includes(id)) { + cardsFound++; + vCardsChangedOnServer[id] = "ADD"; + } + } else if (etag.textContent != card.getProperty("X-DAV-ETAG")) { + cardsFound++; + vCardsChangedOnServer[id] = "MOD"; + } + } else if (cards.multi[c].responsestatus == "404" && card) { + //DEL + cardsFound++; + vCardsDeletedOnServer.push(card); + } else { + //We received something, that is not a DEL, MOD or ADD + TbSync.eventlog.add("warning", syncData.eventLogInfo, "Unknown XML", JSON.stringify(cards.multi[c])); + } + } + } + + // reset sync process + syncData.progressData.reset(0, cardsFound); + + //download all cards added to vCardsChangedOnServer and process changes + await dav.sync.multiget(syncData, vCardsChangedOnServer); + + //delete all contacts added to vCardsDeletedOnServer + await dav.sync.deleteContacts (syncData, vCardsDeletedOnServer); + + //update token + syncData.currentFolderData.setFolderProperty("token", tokenNode.textContent); + + return true; + }, + + remoteChangesByCTAG: async function (syncData) { + syncData.progressData.reset(); + + //Request ctag and token + syncData.setSyncState("send.request.remotechanges"); + let response = await dav.network.sendRequest("<d:propfind "+dav.tools.xmlns(["d", "cs"])+"><d:prop><cs:getctag /><d:sync-token /></d:prop></d:propfind>", syncData.currentFolderData.getFolderProperty("href"), "PROPFIND", syncData.connectionData, {"Depth": "0"}); + + syncData.setSyncState("eval.response.remotechanges"); + let ctag = dav.tools.getNodeTextContentFromMultiResponse(response, [["d","prop"], ["cs", "getctag"]], syncData.currentFolderData.getFolderProperty("href")); + let token = dav.tools.getNodeTextContentFromMultiResponse(response, [["d","prop"], ["d", "sync-token"]], syncData.currentFolderData.getFolderProperty("href")); + + let localDeletes = syncData.target.getDeletedItemsFromChangeLog(); + + //if CTAG changed, we need to sync everything and compare + if (ctag === null || ctag != syncData.currentFolderData.getFolderProperty("ctag")) { + let vCardsFoundOnServer = []; + let vCardsChangedOnServer = {}; + + //get etags of all cards on server and find the changed cards + syncData.setSyncState("send.request.remotechanges"); + let cards = await dav.network.sendRequest("<d:propfind "+dav.tools.xmlns(["d"])+"><d:prop><d:getetag /></d:prop></d:propfind>", syncData.currentFolderData.getFolderProperty("href"), "PROPFIND", syncData.connectionData, {"Depth": "1", "Prefer": "return=minimal"}); + + //to test other impl + //let cards = await dav.network.sendRequest("<d:propfind "+dav.tools.xmlns(["d"])+"><d:prop><d:getetag /></d:prop></d:propfind>", syncData.currentFolderData.getFolderProperty("href"), "PROPFIND", syncData.connectionData, {"Depth": "1", "Prefer": "return=minimal"}, {softfail: []}, false); + + //this is the same request, but includes getcontenttype, do we need it? icloud send contacts without + //let cards = await dav.network.sendRequest("<d:propfind "+dav.tools.xmlns(["d"])+"><d:prop><d:getetag /><d:getcontenttype /></d:prop></d:propfind>", syncData.currentFolderData.getFolderProperty("href"), "PROPFIND", syncData.connectionData, {"Depth": "1", "Prefer": "return=minimal"}); + + //play with filters and limits for synology + /* + let additional = "<card:limit><card:nresults>10</card:nresults></card:limit>"; + additional += "<card:filter test='anyof'>"; + additional += "<card:prop-filter name='FN'>"; + additional += "<card:text-match negate-condition='yes' match-type='equals'>bogusxy</card:text-match>"; + additional += "</card:prop-filter>"; + additional += "</card:filter>";*/ + + //addressbook-query does not work on older servers (zimbra) + //let cards = await dav.network.sendRequest("<card:addressbook-query "+dav.tools.xmlns(["d", "card"])+"><d:prop><d:getetag /></d:prop></card:addressbook-query>", syncData.currentFolderData.getFolderProperty("href"), "REPORT", syncData.connectionData, {"Depth": "1", "Prefer": "return=minimal"}); + + syncData.setSyncState("eval.response.remotechanges"); + let cardsFound = 0; + for (let c=0; cards.multi && c < cards.multi.length; c++) { + let id = cards.multi[c].href; + if (id == syncData.currentFolderData.getFolderProperty("href")) { + //some servers (Radicale) report the folder itself and a querry to that would return everything again + continue; + } + let etag = dav.tools.evaluateNode(cards.multi[c].node, [["d","prop"], ["d","getetag"]]); + + //ctype is currently not used, because iCloud does not send one and sabre/dav documentation is not checking ctype + //let ctype = dav.tools.evaluateNode(cards.multi[c].node, [["d","prop"], ["d","getcontenttype"]]); + + if (cards.multi[c].status == "200" && etag !== null && id !== null /* && ctype !== null */) { //we do not actually check the content of ctype - but why do we request it? iCloud seems to send cards without ctype + vCardsFoundOnServer.push(id); + let card = await syncData.target.getItemFromProperty("X-DAV-HREF", id); + if (!card) { + //if the user deleted this card (not yet send to server), do not add it again + if (!localDeletes.includes(id)) { + cardsFound++; + vCardsChangedOnServer[id] = "ADD"; + } + } else if (etag.textContent != card.getProperty("X-DAV-ETAG")) { + cardsFound++; + vCardsChangedOnServer[id] = "MOD"; + } + } + } + + //FIND DELETES: loop over current addressbook and check each local card if it still exists on the server + let vCardsDeletedOnServer = []; + let localAdditions = syncData.target.getAddedItemsFromChangeLog(); + let allItems = syncData.target.getAllItems() + for (let card of allItems) { + let id = card.getProperty("X-DAV-HREF"); + if (id && !vCardsFoundOnServer.includes(id) && !localAdditions.includes(id)) { + //delete request from server + cardsFound++; + vCardsDeletedOnServer.push(card); + } + } + + // reset sync process + syncData.progressData.reset(0, cardsFound); + + //download all cards added to vCardsChangedOnServer and process changes + await dav.sync.multiget(syncData, vCardsChangedOnServer); + + //delete all contacts added to vCardsDeletedOnServer + await dav.sync.deleteContacts (syncData, vCardsDeletedOnServer); + + //update ctag and token (if there is one) + if (ctag === null) return false; //if server does not support ctag, "it did not change" + syncData.currentFolderData.setFolderProperty("ctag", ctag); + if (token) syncData.currentFolderData.setFolderProperty("token", token); + + //ctag did change + return true; + } else { + + //ctag did not change + return false; + } + + }, + + + + multiget: async function (syncData, vCardsChangedOnServer) { + //keep track of found mailing lists and its members + syncData.foundMailingListsDuringDownSync = {}; + + //download all changed cards and process changes + let cards2catch = Object.keys(vCardsChangedOnServer); + let maxitems = dav.sync.prefSettings.getIntPref("maxitems"); + + for (let i=0; i < cards2catch.length; i+=maxitems) { + let request = dav.tools.getMultiGetRequest(cards2catch.slice(i, i+maxitems)); + if (request) { + syncData.setSyncState("send.request.remotechanges"); + let cards = await dav.network.sendRequest(request, syncData.currentFolderData.getFolderProperty("href"), "REPORT", syncData.connectionData, {"Depth": "1"}); + + syncData.setSyncState("eval.response.remotechanges"); + for (let c=0; c < cards.multi.length; c++) { + syncData.progressData.inc(); + let id = cards.multi[c].href; + let etag = dav.tools.evaluateNode(cards.multi[c].node, [["d","prop"], ["d","getetag"]]); + let data = dav.tools.evaluateNode(cards.multi[c].node, [["d","prop"], ["card","address-data"]]); + + if (cards.multi[c].status == "200" && etag !== null && data !== null && id !== null && vCardsChangedOnServer.hasOwnProperty(id)) { + switch (vCardsChangedOnServer[id]) { + case "ADD": + await dav.tools.addContact (syncData, id, data, etag); + break; + + case "MOD": + await dav.tools.modifyContact (syncData, id, data, etag); + break; + } + //Feedback from users: They want to see the individual count + syncData.setSyncState("eval.response.remotechanges"); + await TbSync.tools.sleep(100); + } else { + TbSync.dump("Skipped Card", [id, cards.multi[c].status == "200", etag !== null, data !== null, id !== null, vCardsChangedOnServer.hasOwnProperty(id)].join(", ")); + } + } + } + } + // Feedback from users: They want to see the final count. + syncData.setSyncState("eval.response.remotechanges"); + await TbSync.tools.sleep(200); + + // On down sync, mailinglists need to be done at the very end so all member data is avail. + if (syncData.accountData.getAccountProperty("syncGroups")) { + let l=0; + for (let listID in syncData.foundMailingListsDuringDownSync) { + if (syncData.foundMailingListsDuringDownSync.hasOwnProperty(listID)) { + l++; + + let list = await syncData.target.getItemFromProperty("X-DAV-HREF", listID); + if (!list.isMailList) + continue; + + //CardInfo contains the name and the X-DAV-UID list of the members + let vCardInfo = dav.tools.getGroupInfoFromCardData(syncData.foundMailingListsDuringDownSync[listID].vCardData, syncData.target); + let oCardInfo = dav.tools.getGroupInfoFromCardData(syncData.foundMailingListsDuringDownSync[listID].oCardData, syncData.target); + + // Smart merge: oCardInfo contains the state during last sync, vCardInfo is the current state. + // By comparing we can learn, which member was deleted by the server (in old but not in new), + // and which one was added (in new but not in old) + let removedMembers = oCardInfo.members.filter(e => !vCardInfo.members.includes(e)); + let newMembers = vCardInfo.members.filter(e => !oCardInfo.members.includes(e)); + + // Check that all new members have an email address (fix for bug 1522453) + let m=0; + for (let member of newMembers) { + let card = await syncData.target.getItemFromProperty("X-DAV-UID", member); + if (card) { + let email = card.getProperty("PrimaryEmail"); + if (!email) { + let email = Date.now() + "." + l + "." + m + "@bug1522453"; + card.setProperty("PrimaryEmail", email); + syncData.target.modifyItem(card); + } + } else { + TbSync.dump("Member not found: " + member); + } + m++; + } + + // if any of the to-be-removed members are not members of the local list, they are skipt + // if any of the to-be-added members are already members of the local list, they are skipt + list.removeListMembers("X-DAV-UID", removedMembers); + list.addListMembers("X-DAV-UID", newMembers); + syncData.target.modifyItem(list); + } + } + } + }, + + deleteContacts: async function (syncData, cards2delete) { + let maxitems = dav.sync.prefSettings.getIntPref("maxitems"); + + // try to show a progress based on maxitens during delete and not delete all at once + for (let i=0; i < cards2delete.length; i+=maxitems) { + //get size of next block + let remain = (cards2delete.length - i); + let chunk = Math.min(remain, maxitems); + + syncData.progressData.inc(chunk); + syncData.setSyncState("eval.response.remotechanges"); + await TbSync.tools.sleep(200); //we want the user to see, that deletes are happening + + for (let j=0; j < chunk; j++) { + syncData.target.deleteItem(cards2delete[i+j]); + } + } + }, + + + + + localChanges: async function (syncData) { + //define how many entries can be send in one request + let maxitems = dav.sync.prefSettings.getIntPref("maxitems"); + + let downloadonly = syncData.currentFolderData.getFolderProperty("downloadonly"); + + let permissionErrors = 0; + let permissionError = { //keep track of permission errors - preset with downloadonly status to skip sync in that case + "added_by_user": downloadonly, + "modified_by_user": downloadonly, + "deleted_by_user": downloadonly + }; + + let syncGroups = syncData.accountData.getAccountProperty("syncGroups"); + + //access changelog to get local modifications (done and todo are used for UI to display progress) + syncData.progressData.reset(0, syncData.target.getItemsFromChangeLog().length); + + do { + syncData.setSyncState("prepare.request.localchanges"); + + //get changed items from ChangeLog + let changes = syncData.target.getItemsFromChangeLog(maxitems); + if (changes.length == 0) + break; + + for (let i=0; i < changes.length; i++) { + switch (changes[i].status) { + case "added_by_user": + case "modified_by_user": + { + let isAdding = (changes[i].status == "added_by_user"); + if (!permissionError[changes[i].status]) { //if this operation failed already, do not retry + + let card = await syncData.target.getItem(changes[i].itemId); + if (card) { + if (card.isMailList && !syncGroups) { + // Conditionally break out of the switch early, but do + // execute the cleanup code below the switch. A continue would + // miss that. + break; + } + + let vcard = card.isMailList + ? dav.tools.getVCardFromThunderbirdListCard(syncData, card, isAdding) + : dav.tools.getVCardFromThunderbirdContactCard(syncData, card, isAdding); + let headers = {"Content-Type": "text/vcard; charset=utf-8"}; + //if (!isAdding) headers["If-Match"] = vcard.etag; + + syncData.setSyncState("send.request.localchanges"); + if (isAdding || vcard.modified) { + let response = await dav.network.sendRequest(vcard.data, card.getProperty("X-DAV-HREF"), "PUT", syncData.connectionData, headers, {softfail: [403,405]}); + + syncData.setSyncState("eval.response.localchanges"); + if (response && response.softerror) { + permissionError[changes[i].status] = true; + TbSync.eventlog.add("warning", syncData.eventLogInfo, "missing-permission::" + TbSync.getString(isAdding ? "acl.add" : "acl.modify", "dav")); + } + } + } else { + TbSync.eventlog.add("warning", syncData.eventLogInfo, "cardnotfoundbutinchangelog::" + changes[i].itemId + "/" + changes[i].status); + } + } + + if (permissionError[changes[i].status]) { + //we where not allowed to add or modify that card, remove it, we will get a fresh copy on the following revert + let card = await syncData.target.getItem(changes[i].itemId); + if (card) syncData.target.deleteItem(card); + permissionErrors++; + } + } + break; + + case "deleted_by_user": + { + if (!permissionError[changes[i].status]) { //if this operation failed already, do not retry + syncData.setSyncState("send.request.localchanges"); + let response = await dav.network.sendRequest("", changes[i].itemId , "DELETE", syncData.connectionData, {}, {softfail: [403, 404, 405]}); + + syncData.setSyncState("eval.response.localchanges"); + if (response && response.softerror) { + if (response.softerror != 404) { //we cannot do anything about a 404 on delete, the card has been deleted here and is not avail on server + permissionError[changes[i].status] = true; + TbSync.eventlog.add("warning", syncData.eventLogInfo, "missing-permission::" + TbSync.getString("acl.delete", "dav")); + } + } + } + + if (permissionError[changes[i].status]) { + permissionErrors++; + } + } + break; + } + + syncData.target.removeItemFromChangeLog(changes[i].itemId); + syncData.progressData.inc(); //UI feedback + } + + + } while (true); + + //return number of modified cards or the number of permission errors (negativ) + return (permissionErrors > 0 ? 0 - permissionErrors : syncData.progressData.done); + }, +} diff --git a/unused/vcard/LICENSE b/unused/vcard/LICENSE new file mode 100644 index 0000000..fa9dba1 --- /dev/null +++ b/unused/vcard/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Aleksandr Kitov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/unused/vcard/SOURCE b/unused/vcard/SOURCE new file mode 100644 index 0000000..21d88d2 --- /dev/null +++ b/unused/vcard/SOURCE @@ -0,0 +1 @@ +https://github.com/Heymdall/vcard/releases/tag/v0.2.7
\ No newline at end of file diff --git a/unused/vcard/vcard.js b/unused/vcard/vcard.js new file mode 100644 index 0000000..d1b9757 --- /dev/null +++ b/unused/vcard/vcard.js @@ -0,0 +1,305 @@ +(function (namespace) { + var PREFIX = 'BEGIN:VCARD', + POSTFIX = 'END:VCARD'; + + /** + * Return json representation of vCard + * @param {string} string raw vCard + * @returns {*} + */ + function parse(string) { + var result = {}, + lines = string.split(/\r\n|\r|\n/), + count = lines.length, + pieces, + key, + value, + meta, + namespace; + + for (var i = 0; i < count; i++) { + if (lines[i] == '') { + continue; + } + if (lines[i].toUpperCase() == PREFIX || lines[i].toUpperCase() == POSTFIX) { + continue; + } + var data = lines[i]; + + /** + * Check that next line continues current + * @param {number} i + * @returns {boolean} + */ + var isValueContinued = function (i) { + return i + 1 < count && (lines[i + 1][0] == ' ' || lines[i + 1][0] == '\t'); + }; + // handle multiline properties (i.e. photo). + // next line should start with space or tab character + if (isValueContinued(i)) { + while (isValueContinued(i)) { + data += lines[i + 1].trim(); + i++; + } + } + + pieces = data.split(':'); + key = pieces.shift(); + value = pieces.join(':'); + namespace = false; + meta = {}; + + // meta fields in property + if (key.match(/;/)) { + key = key + .replace(/\\;/g, 'ΩΩΩ') + .replace(/\\,/, ','); + var metaArr = key.split(';').map(function (item) { + return item.replace(/ΩΩΩ/g, ';'); + }); + key = metaArr.shift(); + metaArr.forEach(function (item) { + var arr = item.split('='); + arr[0] = arr[0].toLowerCase(); + if (arr[0].length === 0) { + return; + } + if (arr.length>1) { + //removing boundary quotes and splitting up values, if send as list - upperCase for hitory reasons + let metavalue = arr[1].replace (/(^")|("$)/g, '').toUpperCase().split(","); + if (meta[arr[0]]) { + meta[arr[0]].push(...metavalue); + } else { + meta[arr[0]] = metavalue; + } + } + }); + } + + // values with \n + value = value + .replace(/\\r/g, '') + .replace(/\\n/g, '\n'); + + value = tryToSplit(value); + + // Grouped properties + if (key.match(/\./)) { + var arr = key.split('.'); + key = arr[1]; + namespace = arr[0]; + } + + var newValue = { + value: value + }; + if (Object.keys(meta).length) { + newValue.meta = meta; + } + if (namespace) { + newValue.namespace = namespace; + } + + if (key.indexOf('X-') !== 0) { + key = key.toLowerCase(); + } + + if (typeof result[key] === 'undefined') { + result[key] = [newValue]; + } else { + result[key].push(newValue); + } + + } + + return result; + } + + var HAS_SEMICOLON_SEPARATOR = /[^\\];|^;/, + HAS_COMMA_SEPARATOR = /[^\\],|^,/; + /** + * Split value by "," or ";" and remove escape sequences for this separators + * @param {string} value + * @returns {string|string[] + */ + function tryToSplit(value) { + if (value.match(HAS_SEMICOLON_SEPARATOR)) { + value = value.replace(/\\,/g, ','); + return splitValue(value, ';'); + } else if (value.match(HAS_COMMA_SEPARATOR)) { + value = value.replace(/\\;/g, ';'); + return splitValue(value, ','); + } else { + return value + .replace(/\\,/g, ',') + .replace(/\\;/g, ';'); + } + } + /** + * Split vcard field value by separator + * @param {string|string[]} value + * @param {string} separator + * @returns {string|string[]} + */ + function splitValue(value, separator) { + var separatorRegexp = new RegExp(separator); + var escapedSeparatorRegexp = new RegExp('\\\\' + separator, 'g'); + // easiest way, replace it with really rare character sequence + value = value.replace(escapedSeparatorRegexp, 'ΩΩΩ'); + if (value.match(separatorRegexp)) { + value = value.split(separator); + + value = value.map(function (item) { + return item.replace(/ΩΩΩ/g, separator); + }); + } else { + value = value.replace(/ΩΩΩ/g, separator); + } + return value; + } + + var guid = (function() { + function s4() { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + } + return function() { + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + + s4() + '-' + s4() + s4() + s4(); + }; + })(); + + var COMMA_SEPARATED_FIELDS = ['nickname', 'related', 'categories', 'pid']; + + /** + * Generate vCard representation af object + * @param {*} data + * @param {obj} + * member "simpleType" returns all types joined by , instead of multiple TYPE= entries + * member "addRequired" determine if generator should add required properties (version and uid) + * @returns {string} + */ + function generate(data, options = {simpleType: true}) { + var lines = [PREFIX], + line = ''; + + if (options.addRequired && !data.version) { + data.version = [{value: '3.0'}]; + } + if (options.addRequired && !data.uid) { + data.uid = [{value: guid()}]; + } + + var escapeCharacters = function (v) { + if (typeof v === 'undefined') { + return ''; + } + return v + .replace(/\r\n|\r|\n/g, '\\n') + .replace(/;/g, '\\;') + .replace(/,/g, '\\,') + }; + + var escapeTypeCharacters = function(v) { + if (typeof v === 'undefined') { + return ''; + } + return v + .replace(/\r\n|\r|\n/g, '\\n') + .replace(/;/g, '\\;') + }; + + Object.keys(data).forEach(function (key) { + if (!data[key] || typeof data[key].forEach !== 'function') { + return; + } + data[key].forEach(function (value) { + // ignore empty values + if (typeof value.value === 'undefined' || value.value == '') { + return; + } + // ignore empty array values + if (value.value instanceof Array) { + var empty = true; + for (var i = 0; i < value.value.length; i++) { + if (typeof value.value[i] !== 'undefined' && value.value[i] != '') { + empty = false; + break; + } + } + if (empty) { + return; + } + } + line = ''; + + // add namespace if exists + if (value.namespace) { + line += value.namespace + '.'; + } + line += key.indexOf('X-') === 0 ? key : key.toUpperCase(); + + // add meta properties + if (typeof value.meta === 'object') { + Object.keys(value.meta).forEach(function (metaKey) { + // values of meta tags must be an array + if (typeof value.meta[metaKey].forEach !== 'function') { + return; + } + //join meta types so we get TYPE=a,b,c instead of TYPE=a;TYPE=b;TYPE=c + let metaArr = (options.simpleType && metaKey.toUpperCase() === 'TYPE') ? [value.meta[metaKey].join(",")] : value.meta[metaKey]; + metaArr.forEach(function (metaValue) { + if (metaKey.length > 0) { + if (metaKey.toUpperCase() === 'TYPE') { + // Do not escape the comma when it is the type property. This breaks a lot. + line += ';' + escapeCharacters(metaKey.toUpperCase()) + '=' + escapeTypeCharacters(metaValue); + } else { + line += ';' + escapeCharacters(metaKey.toUpperCase()) + '=' + escapeCharacters(metaValue); + } + } + }); + }); + } + + line += ':'; + + + + if (typeof value.value === 'string') { + line += escapeCharacters(value.value); + } else { + // list-values + var separator = COMMA_SEPARATED_FIELDS.indexOf(key) !== -1 + ? ',' + : ';'; + line += value.value.map(function (item) { + return escapeCharacters(item); + }).join(separator); + } + + // line-length limit. Content lines + // SHOULD be folded to a maximum width of 75 octets, excluding the line break. + if (line.length > 75) { + var firstChunk = line.substr(0, 75), + least = line.substr(75); + var splitted = least.match(/.{1,74}/g); + lines.push(firstChunk); + splitted.forEach(function (chunk) { + lines.push(' ' + chunk); + }); + } else { + lines.push(line); + } + }); + }); + + lines.push(POSTFIX); + return lines.join('\r\n'); + } + + namespace.vCard = { + parse: parse, + generate: generate + }; +})(this); |