1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-08-27 20:29:45 +02:00

Compare commits

...

26 Commits

Author SHA1 Message Date
Laurent Cozic
19a126ebfe Android release v1.0.94 2018-02-11 17:29:09 +00:00
Laurent Cozic
7e56e5b587 Mobile: Fixes #207: Crash when changing dropdown value in config screen 2018-02-11 17:26:20 +00:00
Laurent Cozic
acf0c79341 Graduated E2EE and WebDAV from beta, and moved to v1.0 2018-02-11 13:50:59 +00:00
Laurent Cozic
9fe7e23ffe Android release v0.10.92 2018-02-11 13:20:32 +00:00
Laurent Cozic
c94cc93971 Fixed translator names 2018-02-10 13:03:01 +00:00
Laurent Cozic
b26094eba8 Fixed Basque flag 2018-02-10 12:52:57 +00:00
Laurent Cozic
89a5ccdf93 Added Basque translation, fixed issue with handling invalid translations. Updated translation FR. 2018-02-10 12:43:45 +00:00
Laurent Cozic
ce2da0e6dc Update readme 2018-02-09 16:03:43 +00:00
Laurent Cozic
f49d644b6a Electron release v0.10.61 2018-02-08 18:15:49 +00:00
Laurent Cozic
02ac0b8593 Removed uneeded created_time property 2018-02-07 20:42:52 +00:00
Laurent Cozic
78e5eaf1e2 Electron: Toolbar button to set tags 2018-02-07 20:35:11 +00:00
Laurent Cozic
fc0d227396 Electron: Allowing opening and saving resource images 2018-02-07 20:23:17 +00:00
Laurent Cozic
f91c52cdf7 Mobile: Update time when app is activated 2018-02-07 19:51:58 +00:00
Laurent Cozic
3f14878d0f All: Improved request repeating mechanism 2018-02-07 19:46:07 +00:00
Laurent Cozic
69fd32e7c6 All: Use mutex when saving model to avoid race conditions when decrypting and syncing at the same time 2018-02-07 19:02:07 +00:00
Laurent Cozic
80801cedf0 Android release v0.10.91 2018-02-07 17:57:08 +00:00
Laurent Cozic
480e4fa94b Merge branch 'master' of github.com:laurent22/joplin 2018-02-07 17:56:36 +00:00
Laurent Cozic
717c789836 Android release v0.10.90 2018-02-07 17:55:54 +00:00
Laurent Cozic
f099376446 Update README.md 2018-02-07 17:55:17 +00:00
Laurent Cozic
41fa9d093e Android release v0.10.89 2018-02-07 17:54:06 +00:00
Laurent Cozic
e2f3f81eb6 Android release v0.10.88 2018-02-07 17:51:31 +00:00
Laurent Cozic
5cab7aeb55 Fixed: Make sure alarms and resources are attached to right note when creating new note 2018-02-06 19:31:22 +00:00
Laurent Cozic
fa5f418c22 All: Added sync config check to config screens 2018-02-06 18:59:36 +00:00
Laurent Cozic
a25fcacace Electron: Display message when creating new note or to-do 2018-02-06 18:12:43 +00:00
Laurent Cozic
727ba7300e All: Also support $ as delimiter for Katex expressoins 2018-02-06 17:58:54 +00:00
Laurent Cozic
d25d9b3f44 CLI v0.10.93 2018-02-06 13:17:10 +00:00
84 changed files with 2509 additions and 432 deletions

View File

@@ -707,7 +707,14 @@ msgstr "Abbrechen"
msgid "Error"
msgstr ""
msgid "An update is available, do you want to update now?"
#, fuzzy, javascript-format
msgid ""
"Release notes:\n"
"\n"
"%s"
msgstr "Notizen löschen?"
msgid "An update is available, do you want to download it now?"
msgstr ""
msgid "Yes"
@@ -717,19 +724,12 @@ msgstr ""
msgid "No"
msgstr "N"
#, javascript-format
msgid "Could not download the update: %s"
msgstr ""
msgid "Current version is up-to-date."
msgstr ""
msgid "New version downloaded - application will quit now and update..."
msgstr ""
#, javascript-format
msgid "Could not install the update: %s"
msgstr ""
#, fuzzy
msgid "Check synchronisation configuration"
msgstr "Synchronisation abbrechen"
#, javascript-format
msgid "Notes and settings are stored in: %s"
@@ -884,6 +884,13 @@ msgstr ""
"Momentan existieren noch keine Notizbücher. Erstelle eines, indem du auf den "
"(+) Knopf drückst."
msgid "Open..."
msgstr ""
#, fuzzy
msgid "Save as..."
msgstr "Änderungen speichern"
#, javascript-format
msgid "Unsupported link or message: %s"
msgstr "Nicht unterstützter Link oder Nachricht: %s"
@@ -891,9 +898,24 @@ msgstr "Nicht unterstützter Link oder Nachricht: %s"
msgid "Attach file"
msgstr "Datei anhängen"
msgid "Tags"
msgstr "Markierungen"
msgid "Set alarm"
msgstr "Alarm erstellen"
#, fuzzy
msgid "to-do"
msgstr "Neues To-Do"
#, fuzzy
msgid "note"
msgstr "Neue Notiz"
#, fuzzy, javascript-format
msgid "Creating new %s..."
msgstr "Importiere Notizen..."
msgid "Refresh"
msgstr "Aktualisieren"
@@ -930,9 +952,6 @@ msgstr "Synchronisieren"
msgid "Notebooks"
msgstr "Notizbücher"
msgid "Tags"
msgstr "Markierungen"
msgid "Searches"
msgstr "Suchen"

View File

@@ -615,7 +615,14 @@ msgstr ""
msgid "Error"
msgstr ""
msgid "An update is available, do you want to update now?"
#, javascript-format
msgid ""
"Release notes:\n"
"\n"
"%s"
msgstr ""
msgid "An update is available, do you want to download it now?"
msgstr ""
msgid "Yes"
@@ -624,18 +631,10 @@ msgstr ""
msgid "No"
msgstr ""
#, javascript-format
msgid "Could not download the update: %s"
msgstr ""
msgid "Current version is up-to-date."
msgstr ""
msgid "New version downloaded - application will quit now and update..."
msgstr ""
#, javascript-format
msgid "Could not install the update: %s"
msgid "Check synchronisation configuration"
msgstr ""
#, javascript-format
@@ -772,6 +771,12 @@ msgid ""
"There is currently no notebook. Create one by clicking on \"New notebook\"."
msgstr ""
msgid "Open..."
msgstr ""
msgid "Save as..."
msgstr ""
#, javascript-format
msgid "Unsupported link or message: %s"
msgstr ""
@@ -779,9 +784,22 @@ msgstr ""
msgid "Attach file"
msgstr ""
msgid "Tags"
msgstr ""
msgid "Set alarm"
msgstr ""
msgid "to-do"
msgstr ""
msgid "note"
msgstr ""
#, javascript-format
msgid "Creating new %s..."
msgstr ""
msgid "Refresh"
msgstr ""
@@ -818,9 +836,6 @@ msgstr ""
msgid "Notebooks"
msgstr ""
msgid "Tags"
msgstr ""
msgid "Searches"
msgstr ""

View File

@@ -673,7 +673,14 @@ msgstr "Cancelar"
msgid "Error"
msgstr ""
msgid "An update is available, do you want to update now?"
#, fuzzy, javascript-format
msgid ""
"Release notes:\n"
"\n"
"%s"
msgstr "Eliminar notas?"
msgid "An update is available, do you want to download it now?"
msgstr ""
msgid "Yes"
@@ -683,19 +690,12 @@ msgstr ""
msgid "No"
msgstr "N"
#, javascript-format
msgid "Could not download the update: %s"
msgstr ""
msgid "Current version is up-to-date."
msgstr ""
msgid "New version downloaded - application will quit now and update..."
msgstr ""
#, javascript-format
msgid "Could not install the update: %s"
msgstr ""
#, fuzzy
msgid "Check synchronisation configuration"
msgstr "Sincronizacion cancelada"
#, javascript-format
msgid "Notes and settings are stored in: %s"
@@ -840,6 +840,13 @@ msgid ""
msgstr ""
"Actualmente no hay notas. Crea una nueva nota dando client en el boton (+)."
msgid "Open..."
msgstr ""
#, fuzzy
msgid "Save as..."
msgstr "Guardar cambios"
#, javascript-format
msgid "Unsupported link or message: %s"
msgstr "Enlace o mensaje sin soporte: %s"
@@ -847,9 +854,24 @@ msgstr "Enlace o mensaje sin soporte: %s"
msgid "Attach file"
msgstr "Adjuntar archivo"
msgid "Tags"
msgstr "Etiquetas"
msgid "Set alarm"
msgstr "Ajustar alarma"
#, fuzzy
msgid "to-do"
msgstr "Nueva lista de tareas"
#, fuzzy
msgid "note"
msgstr "Nueva nota"
#, fuzzy, javascript-format
msgid "Creating new %s..."
msgstr "Importando notas..."
msgid "Refresh"
msgstr "Refrescar"
@@ -887,9 +909,6 @@ msgstr "Sincronizar"
msgid "Notebooks"
msgstr "Libretas"
msgid "Tags"
msgstr "Etiquetas"
msgid "Searches"
msgstr "Busquedas"

View File

@@ -687,7 +687,14 @@ msgstr "Cancelar"
msgid "Error"
msgstr ""
msgid "An update is available, do you want to update now?"
#, fuzzy, javascript-format
msgid ""
"Release notes:\n"
"\n"
"%s"
msgstr "¿Desea eliminar notas?"
msgid "An update is available, do you want to download it now?"
msgstr ""
msgid "Yes"
@@ -697,19 +704,12 @@ msgstr ""
msgid "No"
msgstr "N"
#, javascript-format
msgid "Could not download the update: %s"
msgstr ""
msgid "Current version is up-to-date."
msgstr ""
msgid "New version downloaded - application will quit now and update..."
msgstr ""
#, javascript-format
msgid "Could not install the update: %s"
msgstr ""
#, fuzzy
msgid "Check synchronisation configuration"
msgstr "Cancelar sincronización"
#, javascript-format
msgid "Notes and settings are stored in: %s"
@@ -846,6 +846,13 @@ msgid ""
"There is currently no notebook. Create one by clicking on \"New notebook\"."
msgstr "No hay ninguna libreta. Cree una pulsando en «Libreta nueva»."
msgid "Open..."
msgstr ""
#, fuzzy
msgid "Save as..."
msgstr "Guardar cambios"
#, javascript-format
msgid "Unsupported link or message: %s"
msgstr "Enlace o mensaje no soportado: %s"
@@ -853,9 +860,24 @@ msgstr "Enlace o mensaje no soportado: %s"
msgid "Attach file"
msgstr "Adjuntar archivo"
msgid "Tags"
msgstr "Etiquetas"
msgid "Set alarm"
msgstr "Fijar alarma"
#, fuzzy
msgid "to-do"
msgstr "Lista de tareas nueva"
#, fuzzy
msgid "note"
msgstr "Nota nueva"
#, fuzzy, javascript-format
msgid "Creating new %s..."
msgstr "Importando notas..."
msgid "Refresh"
msgstr "Refrescar"
@@ -892,9 +914,6 @@ msgstr "Sincronizar"
msgid "Notebooks"
msgstr "Libretas"
msgid "Tags"
msgstr "Etiquetas"
msgid "Searches"
msgstr "Búsquedas"

1356
CliClient/locales/eu.po Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -14,8 +14,6 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.0.3\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
msgid "Give focus to next pane"
msgstr "Activer le volet suivant"
@@ -692,7 +690,17 @@ msgstr "Annuler"
msgid "Error"
msgstr "Erreur"
msgid "An update is available, do you want to update now?"
#, javascript-format
msgid ""
"Release notes:\n"
"\n"
"%s"
msgstr ""
"Notes de version :\n"
"\n"
"%s"
msgid "An update is available, do you want to download it now?"
msgstr ""
"Une mise à jour est disponible, souhaitez vous la télécharger maintenant ?"
@@ -702,21 +710,11 @@ msgstr "Oui"
msgid "No"
msgstr "Non"
#, javascript-format
msgid "Could not download the update: %s"
msgstr "Impossible de télécharger la mise à jour : %s"
msgid "Current version is up-to-date."
msgstr "La version actuelle est à jour."
msgid "New version downloaded - application will quit now and update..."
msgstr ""
"La nouvelle version a été téléchargée - le programme va se fermer et se "
"mettre à jour..."
#, javascript-format
msgid "Could not install the update: %s"
msgstr "Impossible d'installer la mise à jour : %s"
msgid "Check synchronisation configuration"
msgstr "Vérifier config synchronisation"
#, javascript-format
msgid "Notes and settings are stored in: %s"
@@ -872,6 +870,12 @@ msgstr ""
"Il n'y a pour l'instant aucun carnet. Créez-en un en cliquant sur \"Nouveau "
"carnet\"."
msgid "Open..."
msgstr "Ouvrir..."
msgid "Save as..."
msgstr "Enregistrer sous..."
#, javascript-format
msgid "Unsupported link or message: %s"
msgstr "Lien ou message non géré : %s"
@@ -879,9 +883,22 @@ msgstr "Lien ou message non géré : %s"
msgid "Attach file"
msgstr "Attacher un fichier"
msgid "Tags"
msgstr "Étiquettes"
msgid "Set alarm"
msgstr "Régler alarme"
msgid "to-do"
msgstr "tâche"
msgid "note"
msgstr "note"
#, javascript-format
msgid "Creating new %s..."
msgstr "Création de %s..."
msgid "Refresh"
msgstr "Rafraîchir"
@@ -918,9 +935,6 @@ msgstr "Synchroniser"
msgid "Notebooks"
msgstr "Carnets"
msgid "Tags"
msgstr "Étiquettes"
msgid "Searches"
msgstr "Recherches"
@@ -1348,6 +1362,17 @@ msgstr ""
msgid "Welcome"
msgstr "Bienvenue"
#~ msgid "Could not download the update: %s"
#~ msgstr "Impossible de télécharger la mise à jour : %s"
#~ msgid "New version downloaded - application will quit now and update..."
#~ msgstr ""
#~ "La nouvelle version a été téléchargée - le programme va se fermer et se "
#~ "mettre à jour..."
#~ msgid "Could not install the update: %s"
#~ msgstr "Impossible d'installer la mise à jour : %s"
#~ msgid ""
#~ "The target to synchonise to. If synchronising with the file system, set "
#~ "`sync.2.path` to specify the target directory."

View File

@@ -695,7 +695,14 @@ msgstr "Odustani"
msgid "Error"
msgstr ""
msgid "An update is available, do you want to update now?"
#, fuzzy, javascript-format
msgid ""
"Release notes:\n"
"\n"
"%s"
msgstr "Obriši bilješke?"
msgid "An update is available, do you want to download it now?"
msgstr ""
msgid "Yes"
@@ -705,19 +712,12 @@ msgstr ""
msgid "No"
msgstr "N"
#, javascript-format
msgid "Could not download the update: %s"
msgstr ""
msgid "Current version is up-to-date."
msgstr ""
msgid "New version downloaded - application will quit now and update..."
msgstr ""
#, javascript-format
msgid "Could not install the update: %s"
msgstr ""
#, fuzzy
msgid "Check synchronisation configuration"
msgstr "Prekini sinkronizaciju"
#, javascript-format
msgid "Notes and settings are stored in: %s"
@@ -856,6 +856,13 @@ msgid ""
"There is currently no notebook. Create one by clicking on \"New notebook\"."
msgstr "Ovdje nema bilježnica. Stvori novu pritiskom na \"Nova bilježnica\"."
msgid "Open..."
msgstr ""
#, fuzzy
msgid "Save as..."
msgstr "Spremi promjene"
#, javascript-format
msgid "Unsupported link or message: %s"
msgstr "Nepodržana poveznica ili poruka: %s"
@@ -863,9 +870,24 @@ msgstr "Nepodržana poveznica ili poruka: %s"
msgid "Attach file"
msgstr "Priloži datoteku"
msgid "Tags"
msgstr "Oznake"
msgid "Set alarm"
msgstr "Postavi upozorenje"
#, fuzzy
msgid "to-do"
msgstr "Novi zadatak"
#, fuzzy
msgid "note"
msgstr "Nova bilješka"
#, fuzzy, javascript-format
msgid "Creating new %s..."
msgstr "Uvozim bilješke..."
msgid "Refresh"
msgstr "Osvježi"
@@ -902,9 +924,6 @@ msgstr "Sinkroniziraj"
msgid "Notebooks"
msgstr "Bilježnice"
msgid "Tags"
msgstr "Oznake"
msgid "Searches"
msgstr "Pretraživanja"

View File

@@ -673,7 +673,14 @@ msgstr "Cancella"
msgid "Error"
msgstr ""
msgid "An update is available, do you want to update now?"
#, fuzzy, javascript-format
msgid ""
"Release notes:\n"
"\n"
"%s"
msgstr "Eliminare le note?"
msgid "An update is available, do you want to download it now?"
msgstr ""
msgid "Yes"
@@ -683,19 +690,12 @@ msgstr ""
msgid "No"
msgstr "N"
#, javascript-format
msgid "Could not download the update: %s"
msgstr ""
msgid "Current version is up-to-date."
msgstr ""
msgid "New version downloaded - application will quit now and update..."
msgstr ""
#, javascript-format
msgid "Could not install the update: %s"
msgstr ""
#, fuzzy
msgid "Check synchronisation configuration"
msgstr "Cancella la sincronizzazione"
#, javascript-format
msgid "Notes and settings are stored in: %s"
@@ -835,6 +835,13 @@ msgid ""
"There is currently no notebook. Create one by clicking on \"New notebook\"."
msgstr "Al momento non ci sono note. Creane una cliccando sul bottone (+)."
msgid "Open..."
msgstr ""
#, fuzzy
msgid "Save as..."
msgstr "Salva i cambiamenti"
#, javascript-format
msgid "Unsupported link or message: %s"
msgstr "Collegamento o messaggio non supportato: %s"
@@ -842,9 +849,24 @@ msgstr "Collegamento o messaggio non supportato: %s"
msgid "Attach file"
msgstr "Allega file"
msgid "Tags"
msgstr "Etichette"
msgid "Set alarm"
msgstr "Imposta allarme"
#, fuzzy
msgid "to-do"
msgstr "Nuova attività"
#, fuzzy
msgid "note"
msgstr "Nuova nota"
#, fuzzy, javascript-format
msgid "Creating new %s..."
msgstr "Importazione delle note..."
msgid "Refresh"
msgstr "Aggiorna"
@@ -881,9 +903,6 @@ msgstr "Sincronizza"
msgid "Notebooks"
msgstr "Blocchi note"
msgid "Tags"
msgstr "Etichette"
msgid "Searches"
msgstr "Ricerche"

View File

@@ -672,7 +672,14 @@ msgstr "キャンセル"
msgid "Error"
msgstr ""
msgid "An update is available, do you want to update now?"
#, fuzzy, javascript-format
msgid ""
"Release notes:\n"
"\n"
"%s"
msgstr "ノートを削除しますか?"
msgid "An update is available, do you want to download it now?"
msgstr ""
msgid "Yes"
@@ -681,19 +688,12 @@ msgstr ""
msgid "No"
msgstr ""
#, javascript-format
msgid "Could not download the update: %s"
msgstr ""
msgid "Current version is up-to-date."
msgstr ""
msgid "New version downloaded - application will quit now and update..."
msgstr ""
#, javascript-format
msgid "Could not install the update: %s"
msgstr ""
#, fuzzy
msgid "Check synchronisation configuration"
msgstr "同期の中止"
#, javascript-format
msgid "Notes and settings are stored in: %s"
@@ -836,6 +836,13 @@ msgid ""
"There is currently no notebook. Create one by clicking on \"New notebook\"."
msgstr "ノートブックがありません。新しいノートブックを作成してください。"
msgid "Open..."
msgstr ""
#, fuzzy
msgid "Save as..."
msgstr "変更を保存"
#, javascript-format
msgid "Unsupported link or message: %s"
msgstr ""
@@ -843,9 +850,24 @@ msgstr ""
msgid "Attach file"
msgstr "ファイルを添付"
msgid "Tags"
msgstr "タグ"
msgid "Set alarm"
msgstr "アラームをセット"
#, fuzzy
msgid "to-do"
msgstr "新しいToDo"
#, fuzzy
msgid "note"
msgstr "新しいノート"
#, fuzzy, javascript-format
msgid "Creating new %s..."
msgstr "ノートのインポート…"
msgid "Refresh"
msgstr "更新"
@@ -882,9 +904,6 @@ msgstr "同期"
msgid "Notebooks"
msgstr "ノートブック"
msgid "Tags"
msgstr "タグ"
msgid "Searches"
msgstr "検索"

View File

@@ -615,7 +615,14 @@ msgstr ""
msgid "Error"
msgstr ""
msgid "An update is available, do you want to update now?"
#, javascript-format
msgid ""
"Release notes:\n"
"\n"
"%s"
msgstr ""
msgid "An update is available, do you want to download it now?"
msgstr ""
msgid "Yes"
@@ -624,18 +631,10 @@ msgstr ""
msgid "No"
msgstr ""
#, javascript-format
msgid "Could not download the update: %s"
msgstr ""
msgid "Current version is up-to-date."
msgstr ""
msgid "New version downloaded - application will quit now and update..."
msgstr ""
#, javascript-format
msgid "Could not install the update: %s"
msgid "Check synchronisation configuration"
msgstr ""
#, javascript-format
@@ -772,6 +771,12 @@ msgid ""
"There is currently no notebook. Create one by clicking on \"New notebook\"."
msgstr ""
msgid "Open..."
msgstr ""
msgid "Save as..."
msgstr ""
#, javascript-format
msgid "Unsupported link or message: %s"
msgstr ""
@@ -779,9 +784,22 @@ msgstr ""
msgid "Attach file"
msgstr ""
msgid "Tags"
msgstr ""
msgid "Set alarm"
msgstr ""
msgid "to-do"
msgstr ""
msgid "note"
msgstr ""
#, javascript-format
msgid "Creating new %s..."
msgstr ""
msgid "Refresh"
msgstr ""
@@ -818,9 +836,6 @@ msgstr ""
msgid "Notebooks"
msgstr ""
msgid "Tags"
msgstr ""
msgid "Searches"
msgstr ""

View File

@@ -690,7 +690,14 @@ msgstr "Annuleer"
msgid "Error"
msgstr ""
msgid "An update is available, do you want to update now?"
#, fuzzy, javascript-format
msgid ""
"Release notes:\n"
"\n"
"%s"
msgstr "Notities verwijderen?"
msgid "An update is available, do you want to download it now?"
msgstr ""
msgid "Yes"
@@ -700,19 +707,12 @@ msgstr ""
msgid "No"
msgstr "N"
#, javascript-format
msgid "Could not download the update: %s"
msgstr ""
msgid "Current version is up-to-date."
msgstr ""
msgid "New version downloaded - application will quit now and update..."
msgstr ""
#, javascript-format
msgid "Could not install the update: %s"
msgstr ""
#, fuzzy
msgid "Check synchronisation configuration"
msgstr "Annuleer synchronisatie"
#, javascript-format
msgid "Notes and settings are stored in: %s"
@@ -864,6 +864,13 @@ msgstr ""
"U heeft momenteel geen notitieboek. Maak een notitieboek door op \"Nieuw "
"notitieboek\" te klikken."
msgid "Open..."
msgstr ""
#, fuzzy
msgid "Save as..."
msgstr "Sla wijzigingen op"
#, javascript-format
msgid "Unsupported link or message: %s"
msgstr "Link of bericht \"%s\" wordt niet ondersteund"
@@ -871,9 +878,24 @@ msgstr "Link of bericht \"%s\" wordt niet ondersteund"
msgid "Attach file"
msgstr "Voeg bestand toe"
msgid "Tags"
msgstr "Tags"
msgid "Set alarm"
msgstr "Zet melding"
#, fuzzy
msgid "to-do"
msgstr "Nieuwe to-do"
#, fuzzy
msgid "note"
msgstr "Nieuwe notitie"
#, fuzzy, javascript-format
msgid "Creating new %s..."
msgstr "Notities importeren..."
msgid "Refresh"
msgstr "Vernieuwen"
@@ -910,9 +932,6 @@ msgstr "Synchroniseer"
msgid "Notebooks"
msgstr "Notitieboeken"
msgid "Tags"
msgstr "Tags"
msgid "Searches"
msgstr "Zoekopdrachten"

View File

@@ -668,7 +668,14 @@ msgstr "Cancelar"
msgid "Error"
msgstr ""
msgid "An update is available, do you want to update now?"
#, fuzzy, javascript-format
msgid ""
"Release notes:\n"
"\n"
"%s"
msgstr "Excluir notas?"
msgid "An update is available, do you want to download it now?"
msgstr ""
msgid "Yes"
@@ -678,19 +685,12 @@ msgstr ""
msgid "No"
msgstr "N"
#, javascript-format
msgid "Could not download the update: %s"
msgstr ""
msgid "Current version is up-to-date."
msgstr ""
msgid "New version downloaded - application will quit now and update..."
msgstr ""
#, javascript-format
msgid "Could not install the update: %s"
msgstr ""
#, fuzzy
msgid "Check synchronisation configuration"
msgstr "Cancelar sincronização"
#, javascript-format
msgid "Notes and settings are stored in: %s"
@@ -832,6 +832,13 @@ msgid ""
"There is currently no notebook. Create one by clicking on \"New notebook\"."
msgstr "Atualmente, não há notas. Crie uma, clicando no botão (+)."
msgid "Open..."
msgstr ""
#, fuzzy
msgid "Save as..."
msgstr "Gravar alterações"
#, javascript-format
msgid "Unsupported link or message: %s"
msgstr "Link ou mensagem não suportada: %s"
@@ -839,9 +846,24 @@ msgstr "Link ou mensagem não suportada: %s"
msgid "Attach file"
msgstr "Anexar arquivo"
msgid "Tags"
msgstr "Tags"
msgid "Set alarm"
msgstr "Definir alarme"
#, fuzzy
msgid "to-do"
msgstr "Nova tarefa"
#, fuzzy
msgid "note"
msgstr "Nova nota"
#, fuzzy, javascript-format
msgid "Creating new %s..."
msgstr "Importando notas ..."
msgid "Refresh"
msgstr "Atualizar"
@@ -879,9 +901,6 @@ msgstr "Sincronizar"
msgid "Notebooks"
msgstr "Cadernos"
msgid "Tags"
msgstr "Tags"
msgid "Searches"
msgstr "Pesquisas"

View File

@@ -689,7 +689,15 @@ msgstr "Отмена"
msgid "Error"
msgstr "Ошибка"
msgid "An update is available, do you want to update now?"
#, fuzzy, javascript-format
msgid ""
"Release notes:\n"
"\n"
"%s"
msgstr "Удалить заметки?"
#, fuzzy
msgid "An update is available, do you want to download it now?"
msgstr "Доступно обновление. Обновить сейчас?"
msgid "Yes"
@@ -699,20 +707,12 @@ msgstr ""
msgid "No"
msgstr "N"
#, javascript-format
msgid "Could not download the update: %s"
msgstr "Не удалось загрузить обновление: %s"
msgid "Current version is up-to-date."
msgstr "Вы используете самую свежую версию."
msgid "New version downloaded - application will quit now and update..."
msgstr ""
"Новая версия загружена — приложение сейчас будет закрыто и обновлено..."
#, javascript-format
msgid "Could not install the update: %s"
msgstr "Не удалось установить обновление: %s"
#, fuzzy
msgid "Check synchronisation configuration"
msgstr "Отменить синхронизацию"
#, javascript-format
msgid "Notes and settings are stored in: %s"
@@ -861,6 +861,13 @@ msgid ""
"There is currently no notebook. Create one by clicking on \"New notebook\"."
msgstr "Сейчас здесь нет блокнотов. Создайте новый нажав «Новый блокнот»."
msgid "Open..."
msgstr ""
#, fuzzy
msgid "Save as..."
msgstr "Сохранить изменения"
#, javascript-format
msgid "Unsupported link or message: %s"
msgstr "Неподдерживаемая ссыка или сообщение: %s"
@@ -868,9 +875,24 @@ msgstr "Неподдерживаемая ссыка или сообщение: %
msgid "Attach file"
msgstr "Прикрепить файл"
msgid "Tags"
msgstr "Теги"
msgid "Set alarm"
msgstr "Установить напоминание"
#, fuzzy
msgid "to-do"
msgstr "Новая задача"
#, fuzzy
msgid "note"
msgstr "Новая заметка"
#, fuzzy, javascript-format
msgid "Creating new %s..."
msgstr "Импорт заметок..."
msgid "Refresh"
msgstr "Обновить"
@@ -907,9 +929,6 @@ msgstr "Синхронизировать"
msgid "Notebooks"
msgstr "Блокноты"
msgid "Tags"
msgstr "Теги"
msgid "Searches"
msgstr "Запросы"
@@ -1335,6 +1354,16 @@ msgstr "У вас сейчас нет блокнота. Создайте его
msgid "Welcome"
msgstr "Добро пожаловать"
#~ msgid "Could not download the update: %s"
#~ msgstr "Не удалось загрузить обновление: %s"
#~ msgid "New version downloaded - application will quit now and update..."
#~ msgstr ""
#~ "Новая версия загружена — приложение сейчас будет закрыто и обновлено..."
#~ msgid "Could not install the update: %s"
#~ msgstr "Не удалось установить обновление: %s"
#~ msgid ""
#~ "The target to synchonise to. If synchronising with the file system, set "
#~ "`sync.2.path` to specify the target directory."

View File

@@ -639,7 +639,14 @@ msgstr "取消"
msgid "Error"
msgstr ""
msgid "An update is available, do you want to update now?"
#, fuzzy, javascript-format
msgid ""
"Release notes:\n"
"\n"
"%s"
msgstr "是否删除笔记?"
msgid "An update is available, do you want to download it now?"
msgstr ""
msgid "Yes"
@@ -649,19 +656,12 @@ msgstr ""
msgid "No"
msgstr "否"
#, javascript-format
msgid "Could not download the update: %s"
msgstr ""
msgid "Current version is up-to-date."
msgstr ""
msgid "New version downloaded - application will quit now and update..."
msgstr ""
#, javascript-format
msgid "Could not install the update: %s"
msgstr ""
#, fuzzy
msgid "Check synchronisation configuration"
msgstr "取消同步"
#, javascript-format
msgid "Notes and settings are stored in: %s"
@@ -801,6 +801,13 @@ msgid ""
"There is currently no notebook. Create one by clicking on \"New notebook\"."
msgstr "当前无笔记。点击(+)创建新笔记。"
msgid "Open..."
msgstr ""
#, fuzzy
msgid "Save as..."
msgstr "保存更改"
#, javascript-format
msgid "Unsupported link or message: %s"
msgstr "不支持的链接或信息:%s"
@@ -808,9 +815,24 @@ msgstr "不支持的链接或信息:%s"
msgid "Attach file"
msgstr "附加文件"
msgid "Tags"
msgstr "标签"
msgid "Set alarm"
msgstr "设置提醒"
#, fuzzy
msgid "to-do"
msgstr "新待办事项"
#, fuzzy
msgid "note"
msgstr "新笔记"
#, fuzzy, javascript-format
msgid "Creating new %s..."
msgstr "正在导入笔记..."
msgid "Refresh"
msgstr "刷新"
@@ -847,9 +869,6 @@ msgstr "同步"
msgid "Notebooks"
msgstr "笔记本"
msgid "Tags"
msgstr "标签"
msgid "Searches"
msgstr "搜索历史"

View File

@@ -1,6 +1,6 @@
{
"name": "joplin",
"version": "0.10.92",
"version": "0.10.93",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -64,6 +64,11 @@
}
}
},
"async-mutex": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.1.3.tgz",
"integrity": "sha1-Cq0hEjaXlas/F+M3RFVtLs9UdWY="
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",

View File

@@ -19,7 +19,7 @@
],
"owner": "Laurent Cozic"
},
"version": "0.10.92",
"version": "1.0.93",
"bin": {
"joplin": "./main.js"
},
@@ -28,6 +28,7 @@
},
"dependencies": {
"app-module-path": "^2.2.0",
"async-mutex": "^0.1.3",
"base-64": "^0.1.0",
"compare-version": "^0.1.2",
"follow-redirects": "^1.2.4",

View File

@@ -9,9 +9,7 @@ rsync -a "$ROOT_DIR/build/locales/" "$BUILD_DIR/locales/"
mkdir -p "$BUILD_DIR/data"
if [[ $TEST_FILE == "" ]]; then
(cd "$ROOT_DIR" && npm test tests-build/synchronizer.js)
(cd "$ROOT_DIR" && npm test tests-build/encryption.js)
(cd "$ROOT_DIR" && npm test tests-build/ArrayUtils.js)
(cd "$ROOT_DIR" && npm test tests-build/synchronizer.js tests-build/encryption.js tests-build/ArrayUtils.js tests-build/models_Setting.js)
else
(cd "$ROOT_DIR" && npm test tests-build/$TEST_FILE.js)
fi

View File

@@ -0,0 +1,32 @@
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const Setting = require('lib/models/Setting.js');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
describe('models_Setting', function() {
beforeEach(async (done) => {
done();
});
it('should return only sub-values', asyncTest(async () => {
const settings = {
'sync.5.path': 'http://example.com',
'sync.5.username': 'testing',
}
let output = Setting.subValues('sync.5', settings);
expect(output['path']).toBe('http://example.com');
expect(output['username']).toBe('testing');
output = Setting.subValues('sync.4', settings);
expect('path' in output).toBe(false);
expect('username' in output).toBe(false);
}));
});

View File

@@ -19,7 +19,7 @@ process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
jasmine.DEFAULT_TIMEOUT_INTERVAL = 35000; // The first test is slow because the database needs to be built
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; // The first test is slow because the database needs to be built
async function allItems() {
let folders = await Folder.all();

View File

@@ -7,6 +7,8 @@ const { Header } = require('./Header.min.js');
const { themeStyle } = require('../theme.js');
const pathUtils = require('lib/path-utils.js');
const { _ } = require('lib/locale.js');
const SyncTargetRegistry = require('lib/SyncTargetRegistry');
const shared = require('lib/components/shared/config-shared.js');
class ConfigScreenComponent extends React.Component {
@@ -16,6 +18,16 @@ class ConfigScreenComponent extends React.Component {
this.state = {
settings: {},
};
shared.init(this);
this.checkSyncConfig_ = async () => {
await shared.checkSyncConfig(this, this.state.settings);
}
this.rowStyle_ = {
marginBottom: 10,
};
}
componentWillMount() {
@@ -44,9 +56,7 @@ class ConfigScreenComponent extends React.Component {
let output = null;
const rowStyle = {
marginBottom: 10,
};
const rowStyle = this.rowStyle_;
const labelStyle = Object.assign({}, theme.textStyle, {
display: 'inline-block',
@@ -59,7 +69,7 @@ class ConfigScreenComponent extends React.Component {
const updateSettingValue = (key, value) => {
const settings = Object.assign({}, this.state.settings);
settings[key] = value;
settings[key] = Setting.formatValue(key, value);
this.setState({ settings: settings });
}
@@ -104,12 +114,13 @@ class ConfigScreenComponent extends React.Component {
updateSettingValue(key, event.target.value);
}
const inputStyle = Object.assign({}, controlStyle, { width: '50%', minWidth: '20em' });
const inputType = md.secure === true ? 'password' : 'text';
return (
<div key={key} style={rowStyle}>
<div style={labelStyle}><label>{md.label()}</label></div>
<input type={inputType} style={controlStyle} value={this.state.settings[key]} onChange={(event) => {onTextChange(event)}} />
<input type={inputType} style={inputStyle} value={this.state.settings[key]} onChange={(event) => {onTextChange(event)}} />
</div>
);
} else if (md.type === Setting.TYPE_INT) {
@@ -144,7 +155,7 @@ class ConfigScreenComponent extends React.Component {
render() {
const theme = themeStyle(this.props.theme);
const style = this.props.style;
const style = Object.assign({}, this.props.style, { overflow: 'auto' });
const settings = this.state.settings;
const headerStyle = {
@@ -175,6 +186,24 @@ class ConfigScreenComponent extends React.Component {
settingComps.push(comp);
}
const syncTargetMd = SyncTargetRegistry.idToMetadata(settings['sync.target']);
if (syncTargetMd.supportsConfigCheck) {
const messages = shared.checkSyncConfigMessages(this);
const statusStyle = Object.assign({}, theme.textStyle, { marginTop: 10 });
const statusComp = !messages.length ? null : (
<div style={statusStyle}>
{messages[0]}
{messages.length >= 1 ? (<p>{messages[1]}</p>) : null}
</div>);
settingComps.push(
<div key="check_sync_config_button" style={this.rowStyle_}>
<button disabled={this.state.checkSyncConfigResult === 'checking'} onClick={this.checkSyncConfig_}>{_('Check synchronisation configuration')}</button>
{ statusComp }
</div>);
}
return (
<div style={style}>
<Header style={headerStyle} />

View File

@@ -182,7 +182,7 @@ class EncryptionConfigScreenComponent extends React.Component {
<div>
<Header style={headerStyle} />
<div style={containerStyle}>
<div style={{backgroundColor: theme.warningBackgroundColor, paddingLeft: 10, paddingRight: 10, paddingTop: 2, paddingBottom: 2 }}>
{/*<div style={{backgroundColor: theme.warningBackgroundColor, paddingLeft: 10, paddingRight: 10, paddingTop: 2, paddingBottom: 2 }}>
<p style={theme.textStyle}>
Important: This is a <b>beta</b> feature. It has been extensively tested and is already in use by some users, but it is possible that some bugs remain.
</p>
@@ -192,7 +192,7 @@ class EncryptionConfigScreenComponent extends React.Component {
<p style={theme.textStyle}>
For more information about End-To-End Encryption (E2EE) and how it is going to work, please check the documentation: <a onClick={() => {bridge().openExternal('http://joplin.cozic.net/help/e2ee.html')}} href="#">http://joplin.cozic.net/help/e2ee.html</a>
</p>
</div>
</div>*/}
<h1 style={theme.h1Style}>{_('Status')}</h1>
<p style={theme.textStyle}>{_('Encryption is:')} <strong>{this.props.encryptionEnabled ? _('Enabled') : _('Disabled')}</strong></p>
{decryptedItemsInfo}

View File

@@ -16,6 +16,7 @@ const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem;
const { shim } = require('lib/shim.js');
const eventManager = require('../eventManager');
const fs = require('fs-extra');
require('brace/mode/markdown');
// https://ace.c9.io/build/kitchen-sink.html
@@ -115,10 +116,14 @@ class NoteTextComponent extends React.Component {
eventManager.removeListener('todoToggle', this.onTodoToggle_);
}
async saveIfNeeded() {
async saveIfNeeded(saveIfNewNote = false) {
const forceSave = saveIfNewNote && (this.state.note && !this.state.note.id);
if (this.scheduleSaveTimeout_) clearTimeout(this.scheduleSaveTimeout_);
this.scheduleSaveTimeout_ = null;
if (!shared.isModified(this)) return;
if (!forceSave) {
if (!shared.isModified(this)) return;
}
await shared.saveNoteButton_press(this);
}
@@ -260,7 +265,7 @@ class NoteTextComponent extends React.Component {
shared.showMetadata_onPress(this);
}
webview_ipcMessage(event) {
async webview_ipcMessage(event) {
const msg = event.channel ? event.channel : '';
const args = event.args;
const arg0 = args && args.length >= 1 ? args[0] : null;
@@ -282,6 +287,32 @@ class NoteTextComponent extends React.Component {
} else if (msg === 'percentScroll') {
this.ignoreNextEditorScroll_ = true;
this.setEditorPercentScroll(arg0);
} else if (msg === 'contextMenu') {
const itemType = arg0 && arg0.type;
const menu = new Menu()
if (itemType === 'image') {
const resource = await Resource.load(arg0.resourceId);
const resourcePath = Resource.fullPath(resource);
menu.append(new MenuItem({label: _('Open...'), click: async () => {
bridge().openExternal(resourcePath);
}}));
menu.append(new MenuItem({label: _('Save as...'), click: async () => {
const filePath = bridge().showSaveDialog({
defaultPath: resource.filename ? resource.filename : resource.title,
});
if (!filePath) return;
await fs.copy(resourcePath, filePath);
}}));
} else {
reg.logger().error('Unhandled item type: ' + itemType);
return;
}
menu.popup(bridge().window());
} else if (msg.indexOf('joplin://') === 0) {
const resourceId = msg.substr('joplin://'.length);
Resource.load(resourceId).then((resource) => {
@@ -400,16 +431,13 @@ class NoteTextComponent extends React.Component {
async commandAttachFile() {
const noteId = this.props.noteId;
if (!noteId) return;
const filePaths = bridge().showOpenDialog({
properties: ['openFile', 'createDirectory', 'multiSelections'],
});
if (!filePaths || !filePaths.length) return;
await this.saveIfNeeded();
let note = await Note.load(noteId);
await this.saveIfNeeded(true);
let note = await Note.load(this.state.note.id);
for (let i = 0; i < filePaths.length; i++) {
const filePath = filePaths[i];
@@ -427,20 +455,29 @@ class NoteTextComponent extends React.Component {
}
}
commandSetAlarm() {
const noteId = this.props.noteId;
if (!noteId) return;
async commandSetAlarm() {
await this.saveIfNeeded(true);
this.props.dispatch({
type: 'WINDOW_COMMAND',
name: 'editAlarm',
noteId: noteId,
noteId: this.state.note.id,
});
}
async commandSetTags() {
await this.saveIfNeeded(true);
this.props.dispatch({
type: 'WINDOW_COMMAND',
name: 'setTags',
noteId: this.state.note.id,
});
}
itemContextMenu(event) {
const noteId = this.props.noteId;
if (!noteId) return;
const note = this.state.note;
if (!note) return;
const menu = new Menu()
@@ -448,10 +485,16 @@ class NoteTextComponent extends React.Component {
return this.commandAttachFile();
}}));
menu.append(new MenuItem({label: _('Set alarm'), click: async () => {
return this.commandSetAlarm();
menu.append(new MenuItem({label: _('Tags'), click: async () => {
return this.commandSetTags();
}}));
if (!!note.is_todo) {
menu.append(new MenuItem({label: _('Set alarm'), click: async () => {
return this.commandSetAlarm();
}}));
}
menu.popup(bridge().window());
}
@@ -461,6 +504,7 @@ class NoteTextComponent extends React.Component {
const body = note && note.body ? note.body : '';
const theme = themeStyle(this.props.theme);
const visiblePanes = this.props.visiblePanes || ['editor', 'viewer'];
const isTodo = note && !!note.is_todo;
const borderWidth = 1;
@@ -568,6 +612,12 @@ class NoteTextComponent extends React.Component {
onClick: () => { return this.commandAttachFile(); },
});
toolbarItems.push({
title: _('Tags'),
iconName: 'fa-tags',
onClick: () => { return this.commandSetTags(); },
});
if (note.is_todo) {
toolbarItems.push({
title: Note.needAlarm(note) ? time.formatMsToLocal(note.todo_due) : _('Set alarm'),
@@ -588,6 +638,7 @@ class NoteTextComponent extends React.Component {
style={titleEditorStyle}
value={note && note.title ? note.title : ''}
onChange={(event) => { this.title_changeText(event); }}
placeholder={ this.props.newNote ? _('Creating new %s...', isTodo ? _('to-do') : _('note')) : '' }
/>
const titleBarMenuButton = <IconButton style={{

View File

@@ -180,6 +180,16 @@
ipcRenderer.sendToHost('percentScroll', percent);
});
document.addEventListener('contextmenu', function(event) {
const element = event.target;
if (element && element.getAttribute('data-resource-id')) {
ipcRenderer.sendToHost('contextMenu', {
type: element.getAttribute('src') ? 'image' : 'link',
resourceId: element.getAttribute('data-resource-id'),
});
}
});
// Disable drag and drop otherwise it's possible to drop a URL
// on it and it will open in the view as a website.
document.addEventListener('drop', function(e) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -3,6 +3,7 @@ locales['en_GB'] = require('./en_GB.json');
locales['de_DE'] = require('./de_DE.json');
locales['es_CR'] = require('./es_CR.json');
locales['es_ES'] = require('./es_ES.json');
locales['eu'] = require('./eu.json');
locales['fr_FR'] = require('./fr_FR.json');
locales['hr_HR'] = require('./hr_HR.json');
locales['it_IT'] = require('./it_IT.json');

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
{
"name": "Joplin",
"version": "0.10.60",
"version": "0.10.61",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -237,6 +237,11 @@
"integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==",
"dev": true
},
"async-mutex": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.1.3.tgz",
"integrity": "sha1-Cq0hEjaXlas/F+M3RFVtLs9UdWY="
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -2852,6 +2857,24 @@
"uc.micro": "1.0.3"
}
},
"markdown-it-katex": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/markdown-it-katex/-/markdown-it-katex-2.0.3.tgz",
"integrity": "sha1-17hqGuoLnWSW+rTnkZoY/e9YnDk=",
"requires": {
"katex": "0.6.0"
},
"dependencies": {
"katex": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.6.0.tgz",
"integrity": "sha1-EkGOCRIcBckgQbazuftrqyE8tvM=",
"requires": {
"match-at": "0.1.1"
}
}
}
},
"match-at": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/match-at/-/match-at-0.1.1.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "Joplin",
"version": "0.10.60",
"version": "1.0.61",
"description": "Joplin for Desktop",
"main": "main.js",
"scripts": {
@@ -55,6 +55,7 @@
},
"dependencies": {
"app-module-path": "^2.2.0",
"async-mutex": "^0.1.3",
"base-64": "^0.1.0",
"electron-context-menu": "^0.9.1",
"electron-log": "^2.2.11",
@@ -70,6 +71,7 @@
"levenshtein": "^1.0.5",
"lodash": "^4.17.4",
"markdown-it": "^8.4.0",
"markdown-it-katex": "^2.0.3",
"md5": "^2.2.1",
"mime": "^2.0.3",
"moment": "^2.19.1",

View File

@@ -6,7 +6,7 @@ Notes exported from Evernote via .enex files [can be imported](#importing-notes-
The notes can be [synchronised](#synchronisation) with various targets including [Nextcloud](https://nextcloud.com/), the file system (for example with a network directory) or with Microsoft OneDrive. When synchronising the notes, notebooks, tags and other metadata are saved to plain text files which can be easily inspected, backed up and moved around.
Joplin is still under development but is out of Beta and should be suitable for every day use. The UI of the terminal client is built on top of the great [terminal-kit](https://github.com/cronvel/terminal-kit) library, the desktop client using [Electron](https://electronjs.org/), and the Android client front end is done using [React Native](https://facebook.github.io/react-native/).
The UI of the terminal client is built on top of the great [terminal-kit](https://github.com/cronvel/terminal-kit) library, the desktop client using [Electron](https://electronjs.org/), and the Android client front end is done using [React Native](https://facebook.github.io/react-native/).
<div class="top-screenshot"><img src="https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/AllClients.jpg" style="max-width: 100%; max-height: 35em;"></div>
@@ -18,15 +18,15 @@ Three types of applications are available: for the **desktop** (Windows, macOS a
Operating System | Download
-----------------|--------
Windows | <a href='https://github.com/laurent22/joplin/releases/download/v0.10.59/Joplin-Setup-0.10.59.exe'><img alt='Get it on Windows' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeWindows.png'/></a>
macOS | <a href='https://github.com/laurent22/joplin/releases/download/v0.10.59/Joplin-0.10.59.dmg'><img alt='Get it on macOS' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeMacOS.png'/></a>
Linux | <a href='https://github.com/laurent22/joplin/releases/download/v0.10.59/Joplin-0.10.59-x86_64.AppImage'><img alt='Get it on macOS' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeLinux.png'/></a>
Windows | <a href='https://github.com/laurent22/joplin/releases/download/v0.10.61/Joplin-Setup-0.10.61.exe'><img alt='Get it on Windows' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeWindows.png'/></a>
macOS | <a href='https://github.com/laurent22/joplin/releases/download/v0.10.61/Joplin-0.10.61.dmg'><img alt='Get it on macOS' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeMacOS.png'/></a>
Linux | <a href='https://github.com/laurent22/joplin/releases/download/v0.10.61/Joplin-0.10.61-x86_64.AppImage'><img alt='Get it on macOS' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeLinux.png'/></a>
## Mobile applications
Operating System | Download | Alt. Download
-----------------|----------|----------------
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeAndroid.png'/></a> | or [Download APK File](https://github.com/laurent22/joplin/releases/download/android-v0.10.86/joplin-v0.10.86.apk)
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeAndroid.png'/></a> | or [Download APK File](https://github.com/laurent22/joplin-android/releases/download/android-v1.0.94/joplin-v1.0.94.apk)
iOS | <a href='https://itunes.apple.com/us/app/joplin/id1315599797'><img alt='Get it on the App Store' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeIOS.png'/></a> | -
## Terminal application
@@ -91,8 +91,6 @@ Currently, synchronisation is possible with Nextcloud and OneDrive (by default)
## Nextcloud synchronisation
**Important: This is a beta feature. It has been extensively tested and is already in use by some users, but it is possible that some bugs remain. If you wish to you use it, it is recommended that you keep a backup of your data. The simplest way is to regularly backup the profile directory of the desktop or terminal application.**
On the **desktop application** or **mobile application**, go to the config screen and select Nextcloud as the synchronisation target. Then input [the WebDAV URL](https://docs.nextcloud.com/server/9/user_manual/files/access_webdav.html), this is normally `https://example.com/nextcloud/remote.php/dav/files/USERNAME/Joplin` (make sure to create the "Joplin" directory in Nextcloud and to replace USERNAME by your Nextcloud username), and set the username and password.
On the **terminal application**, you will need to set the `sync.target` config variable and all the `sync.5.path`, `sync.5.username` and `sync.5.password` config variables to, respectively the Nextcloud WebDAV URL, your username and your password. This can be done from the command line mode using:
@@ -106,8 +104,6 @@ If synchronisation does not work, please consult the logs in the app profile dir
## WebDAV synchronisation
**Important: This is a beta feature. It has been extensively tested and is already in use by some users, but it is possible that some bugs remain. If you wish to you use it, it is recommended that you keep a backup of your data. The simplest way is to regularly backup the profile directory of the desktop or terminal application.**
Select the "WebDAV" synchronisation target and follow the same instructions as for Nextcloud above.
## OneDrive synchronisation
@@ -150,19 +146,19 @@ Joplin uses and renders [Github-flavoured Markdown](https://github.com/adam-p/ma
## Math notation
Math expressions can be added using the [Katex notation](https://khan.github.io/KaTeX/). To add an inline equation, wrap the expression in `` `{.katex}EXPRESSION` ``, eg. `` `{.katex}\sqrt{3x-1}+(1+x)^2` ``. To create an expression block, wrap it as follow:
Math expressions can be added using the [Katex notation](https://khan.github.io/KaTeX/). To add an inline equation, wrap the expression in `$EXPRESSION$`, eg. `$\sqrt{3x-1}+(1+x)^2$`. To create an expression block, wrap it as follow:
```katex
$$
EXPRESSION
```
$$
For example:
```katex
$$
f(x) = \int_{-\infty}^\infty
\hat f(\xi)\,e^{2 \pi i \xi x}
\,d\xi
```
$$
Here is an example with the Markdown and rendered result side by side:
@@ -200,28 +196,21 @@ Current translations:
<!-- LOCALE-TABLE-AUTO-GENERATED -->
&nbsp; | Language | Code | Last translator | Percent done
---|---|---|---|---
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/es/basque_country.png) | Basque | eu | juan.abasolo@ehu.eus | 89%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/hr.png) | Croatian | hr_HR | Hrvoje Mandić <trbuhom@net.hr> | 72%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/de.png) | Deutsch | de_DE | Tobias Strobel <git@strobeltobias.de> | 92%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/de.png) | Deutsch | de_DE | Tobias Strobel <git@strobeltobias.de> | 91%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/gb.png) | English | en_GB | | 100%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/es.png) | Español | es_ES | Lucas Vieites | 80%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/es.png) | Español | es_ES | Lucas Vieites | 79%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/cr.png) | Español (Costa Rica) | es_CR | | 68%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/fr.png) | Français | fr_FR | Laurent Cozic | 100%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/it.png) | Italiano | it_IT | | 76%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/be.png) | Nederlands | nl_BE | | 90%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/it.png) | Italiano | it_IT | | 75%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/be.png) | Nederlands | nl_BE | | 89%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/br.png) | Português (Brasil) | pt_BR | | 74%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/ru.png) | Русский | ru_RU | Artyom Karlov <artyom.karlov@gmail.com> | 96%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/cn.png) | 中文 (简体) | zh_CN | RCJacH <RCJacH@outlook.com> | 76%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/jp.png) | 日本語 | ja_JP | | 74%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/ru.png) | Русский | ru_RU | Artyom Karlov <artyom.karlov@gmail.com> | 94%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/cn.png) | 中文 (简体) | zh_CN | RCJacH <RCJacH@outlook.com> | 75%
![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/jp.png) | 日本語 | ja_JP | | 73%
<!-- LOCALE-TABLE-AUTO-GENERATED -->
# Coming features
- Mobile: manage tags
- Windows: Tray icon
- Desktop apps: Tag auto-complete
- Desktop apps: Dark theme
- Linux: Enable auto-update for desktop app
# Known bugs
- Non-alphabetical characters such as Chinese or Arabic might create glitches in the terminal on Windows. This is a limitation of the current Windows console.

View File

@@ -90,8 +90,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion 16
targetSdkVersion 22
versionCode 2097264
versionName "0.10.86"
versionCode 2097272
versionName "1.0.94"
ndk {
abiFilters "armeabi-v7a", "x86"
}

View File

@@ -2,6 +2,7 @@ const { Log } = require('lib/log.js');
const { Database } = require('lib/database.js');
const { uuid } = require('lib/uuid.js');
const { time } = require('lib/time-utils.js');
const Mutex = require('async-mutex').Mutex;
class BaseModel {
@@ -247,6 +248,40 @@ class BaseModel {
return !Object.getOwnPropertyNames(diff).length;
}
static saveMutex(modelOrId) {
const noLockMutex = {
acquire: function() { return null; }
};
if (!modelOrId) return noLockMutex;
let modelId = typeof modelOrId === 'string' ? modelOrId : modelOrId.id;
if (!modelId) return noLockMutex;
let mutex = BaseModel.saveMutexes_[modelId];
if (mutex) return mutex;
mutex = new Mutex();
BaseModel.saveMutexes_[modelId] = mutex;
return mutex;
}
static releaseSaveMutex(modelOrId, release) {
if (!release) return;
if (!modelOrId) return release();
let modelId = typeof modelOrId === 'string' ? modelOrId : modelOrId.id;
if (!modelId) return release();
let mutex = BaseModel.saveMutexes_[modelId];
if (!mutex) return release();
delete BaseModel.saveMutexes_[modelId];
release();
}
static saveQuery(o, options) {
let temp = {}
let fieldNames = this.fieldNames();
@@ -320,7 +355,16 @@ class BaseModel {
return query;
}
static save(o, options = null) {
static async save(o, options = null) {
// When saving, there's a mutex per model ID. This is because the model returned from this function
// is basically its input `o` (instead of being read from the database, for performance reasons).
// This works well in general except if that model is saved simultaneously in two places. In that
// case, the output won't be up-to-date and would cause for example display issues with out-dated
// notes being displayed. This was an issue when notes were being synchronised while being decrypted
// at the same time.
const mutexRelease = await this.saveMutex(o).acquire();
options = this.modOptions(options);
options.isNew = this.isNew(o, options);
@@ -348,7 +392,11 @@ class BaseModel {
queries = queries.concat(options.nextQueries);
}
return this.db().transactionExecBatch(queries).then(() => {
let output = null;
try {
await this.db().transactionExecBatch(queries);
o = Object.assign({}, o);
if (modelId) o.id = modelId;
if ('updated_time' in saveQuery.modObject) o.updated_time = saveQuery.modObject.updated_time;
@@ -365,10 +413,14 @@ class BaseModel {
}
}
return this.filter(o);
}).catch((error) => {
output = this.filter(o);
} catch (error) {
Log.error('Cannot save model', error);
});
}
this.releaseSaveMutex(o, mutexRelease);
return output;
}
static isNew(object, options) {
@@ -447,5 +499,6 @@ BaseModel.TYPE_MASTER_KEY = 9;
BaseModel.db_ = null;
BaseModel.dispatch = function(o) {};
BaseModel.saveMutexes_ = {};
module.exports = BaseModel;

View File

@@ -10,6 +10,10 @@ class BaseSyncTarget {
this.options_ = options;
}
static supportsConfigCheck() {
return false;
}
option(name, defaultValue = null) {
return this.options_ && (name in this.options_) ? this.options_[name] : defaultValue;
}

View File

@@ -117,7 +117,7 @@ class MdToHtml {
if (mime == 'image/png' || mime == 'image/jpg' || mime == 'image/jpeg' || mime == 'image/gif') {
let src = './' + Resource.filename(resource);
if (this.resourceBaseUrl_ !== null) src = this.resourceBaseUrl_ + src;
let output = '<img title="' + htmlentities(title) + '" src="' + src + '"/>';
let output = '<img data-resource-id="' + resource.id + '" title="' + htmlentities(title) + '" src="' + src + '"/>';
return output;
}
@@ -157,7 +157,7 @@ class MdToHtml {
}
}
customCodeHandler_(language) {
rendererPlugin_(language) {
if (!language) return null;
const handlers = {};
@@ -196,9 +196,10 @@ class MdToHtml {
const isCodeBlock = tag === 'code' && t.block;
const isInlineCode = t.type === 'code_inline';
const codeBlockLanguage = t && t.info ? t.info : null;
let codeBlockHandler = null;
let rendererPlugin = null;
let rendererPluginOptions = { tagType: 'inline' };
if (isCodeBlock) codeBlockHandler = this.customCodeHandler_(codeBlockLanguage);
if (isCodeBlock) rendererPlugin = this.rendererPlugin_(codeBlockLanguage);
if (previousToken && previousToken.tag === 'li' && tag === 'p') {
// Markdown-it render list items as <li><p>Text<p></li> which makes it
@@ -216,7 +217,7 @@ class MdToHtml {
} else if (t.type === 'link_open') {
openTag = 'a';
} else if (isCodeBlock) {
if (codeBlockHandler) {
if (rendererPlugin) {
openTag = null;
} else {
openTag = 'pre';
@@ -235,25 +236,30 @@ class MdToHtml {
if (isCodeBlock) {
const codeAttrs = ['code'];
if (!codeBlockHandler) {
if (!rendererPlugin) {
if (codeBlockLanguage) codeAttrs.push(t.info); // t.info contains the language when the token is a codeblock
output.push('<code class="' + codeAttrs.join(' ') + '">');
}
} else if (isInlineCode) {
const result = this.parseInlineCodeLanguage_(tokenContent);
if (result) {
codeBlockHandler = this.customCodeHandler_(result.language);
rendererPlugin = this.rendererPlugin_(result.language);
tokenContent = result.newContent;
}
if (!codeBlockHandler) {
if (!rendererPlugin) {
output.push('<code>');
}
}
if (codeBlockHandler) {
codeBlockHandler.loadAssets().catch((error) => {
console.warn('MdToHtml: Error loading assets for ' + codeBlockHandler.name() + ': ', error.message);
if (t.type === 'math_inline' || t.type === 'math_block') {
rendererPlugin = this.rendererPlugin_('katex');
rendererPluginOptions = { tagType: t.type === 'math_block' ? 'block' : 'inline' };
}
if (rendererPlugin) {
rendererPlugin.loadAssets().catch((error) => {
console.warn('MdToHtml: Error loading assets for ' + rendererPlugin.name() + ': ', error.message);
});
}
@@ -270,8 +276,10 @@ class MdToHtml {
output = output.concat(parsedChildren);
} else {
if (tokenContent) {
if ((isCodeBlock || isInlineCode) && codeBlockHandler) {
output = codeBlockHandler.processContent(output, tokenContent, isCodeBlock ? 'block' : 'inline');
if ((isCodeBlock || isInlineCode) && rendererPlugin) {
output = rendererPlugin.processContent(output, tokenContent, isCodeBlock ? 'block' : 'inline');
} else if (rendererPlugin) {
output = rendererPlugin.processContent(output, tokenContent, rendererPluginOptions.tagType);
} else {
output.push(htmlentities(tokenContent));
}
@@ -286,15 +294,15 @@ class MdToHtml {
} else if (tag && t.type.indexOf('inline') >= 0) {
closeTag = openTag;
} else if (isCodeBlock) {
if (!codeBlockHandler) closeTag = openTag;
if (!rendererPlugin) closeTag = openTag;
}
if (isCodeBlock) {
if (!codeBlockHandler) {
if (!rendererPlugin) {
output.push('</code>');
}
} else if (isInlineCode) {
if (!codeBlockHandler) {
if (!rendererPlugin) {
output.push('</code>');
}
}
@@ -307,9 +315,9 @@ class MdToHtml {
}
}
if (codeBlockHandler) {
const extraCss = codeBlockHandler.extraCss();
const name = codeBlockHandler.name();
if (rendererPlugin) {
const extraCss = rendererPlugin.extraCss();
const name = rendererPlugin.name();
if (extraCss && !(name in extraCssBlocks)) {
extraCssBlocks[name] = extraCss;
}
@@ -345,6 +353,13 @@ class MdToHtml {
linkify: true,
});
// This is currently used only so that the $expression$ and $$\nexpression\n$$ blocks are translated
// to math_inline and math_block blocks. These blocks are then processed directly with the Katex
// library. It is better this way as then it is possible to conditionally load the CSS required by
// Katex and use an up-to-date version of Katex (as of 2018, the plugin is still using 0.6, which is
// buggy instead of 0.9).
md.use(require('markdown-it-katex'));
// Hack to make checkboxes clickable. Ideally, checkboxes should be parsed properly in
// renderTokens_(), but for now this hack works. Marking it with HORRIBLE_HACK so
// that it can be removed and replaced later on.

View File

@@ -1,9 +1,13 @@
// The Nextcloud sync target is essentially a wrapper over the WebDAV sync target,
// thus all the calls to SyncTargetWebDAV to avoid duplicate code.
const BaseSyncTarget = require('lib/BaseSyncTarget.js');
const { _ } = require('lib/locale.js');
const Setting = require('lib/models/Setting.js');
const { FileApi } = require('lib/file-api.js');
const { Synchronizer } = require('lib/synchronizer.js');
const WebDavApi = require('lib/WebDavApi');
const SyncTargetWebDAV = require('lib/SyncTargetWebDAV');
const { FileApiDriverWebDav } = require('lib/file-api-driver-webdav');
class SyncTargetNextcloud extends BaseSyncTarget {
@@ -12,9 +16,8 @@ class SyncTargetNextcloud extends BaseSyncTarget {
return 5;
}
constructor(db, options = null) {
super(db, options);
// this.authenticated_ = false;
static supportsConfigCheck() {
return true;
}
static targetName() {
@@ -22,26 +25,26 @@ class SyncTargetNextcloud extends BaseSyncTarget {
}
static label() {
return _('Nextcloud (Beta)');
return _('Nextcloud');
}
isAuthenticated() {
return true;
//return this.authenticated_;
}
static async checkConfig(options) {
return SyncTargetWebDAV.checkConfig(options);
}
async initFileApi() {
const options = {
baseUrl: () => Setting.value('sync.5.path'),
username: () => Setting.value('sync.5.username'),
password: () => Setting.value('sync.5.password'),
};
const fileApi = await SyncTargetWebDAV.initFileApi_({
path: Setting.value('sync.5.path'),
username: Setting.value('sync.5.username'),
password: Setting.value('sync.5.password'),
});
const api = new WebDavApi(options);
const driver = new FileApiDriverWebDav(api);
const fileApi = new FileApi('', driver);
fileApi.setSyncTargetId(SyncTargetNextcloud.id());
fileApi.setLogger(this.logger());
return fileApi;
}

View File

@@ -12,6 +12,7 @@ class SyncTargetRegistry {
name: SyncTargetClass.targetName(),
label: SyncTargetClass.label(),
classRef: SyncTargetClass,
supportsConfigCheck: SyncTargetClass.supportsConfigCheck(),
};
}

View File

@@ -12,8 +12,8 @@ class SyncTargetWebDAV extends BaseSyncTarget {
return 6;
}
constructor(db, options = null) {
super(db, options);
static supportsConfigCheck() {
return true;
}
static targetName() {
@@ -21,25 +21,56 @@ class SyncTargetWebDAV extends BaseSyncTarget {
}
static label() {
return _('WebDAV (Beta)');
return _('WebDAV');
}
isAuthenticated() {
return true;
}
async initFileApi() {
const options = {
baseUrl: () => Setting.value('sync.6.path'),
username: () => Setting.value('sync.6.username'),
password: () => Setting.value('sync.6.password'),
static async initFileApi_(options) {
const apiOptions = {
baseUrl: () => options.path,
username: () => options.username,
password: () => options.password,
};
const api = new WebDavApi(options);
const api = new WebDavApi(apiOptions);
const driver = new FileApiDriverWebDav(api);
const fileApi = new FileApi('', driver);
fileApi.setSyncTargetId(SyncTargetWebDAV.id());
fileApi.setSyncTargetId(this.id());
return fileApi;
}
static async checkConfig(options) {
const fileApi = await SyncTargetWebDAV.initFileApi_(options);
const output = {
ok: false,
errorMessage: '',
};
try {
const result = await fileApi.stat('');
if (!result) throw new Error('Could not access WebDAV directory');
output.ok = true;
} catch (error) {
output.errorMessage = error.message;
if (error.code) output.errorMessage += ' (Code ' + error.code + ')';
}
return output;
}
async initFileApi() {
const fileApi = await SyncTargetWebDAV.initFileApi_({
path: Setting.value('sync.6.path'),
username: Setting.value('sync.6.username'),
password: Setting.value('sync.6.password'),
});
fileApi.setLogger(this.logger());
return fileApi;
}

View File

@@ -187,7 +187,7 @@ class WebDavApi {
let response = null;
// console.info('WebDAV', method + ' ' + url, headers, options);
// console.info('WebDAV Call', method + ' ' + url, headers, options);
if (options.source == 'file' && (method == 'POST' || method == 'PUT')) {
response = await shim.uploadBlob(url, fetchOptions);
@@ -199,6 +199,8 @@ class WebDavApi {
const responseText = await response.text();
// console.info('WebDAV Response', responseText);
// Gives a shorter response for error messages. Useful for cases where a full HTML page is accidentally loaded instead of
// JSON. That way the error message will still show there's a problem but without filling up the log or screen.
const shortResponseText = () => {

View File

@@ -7,6 +7,8 @@ const { BaseScreenComponent } = require('lib/components/base-screen.js');
const { Dropdown } = require('lib/components/Dropdown.js');
const { themeStyle } = require('lib/components/global-style.js');
const Setting = require('lib/models/Setting.js');
const shared = require('lib/components/shared/config-shared.js');
const SyncTargetRegistry = require('lib/SyncTargetRegistry');
class ConfigScreenComponent extends BaseScreenComponent {
@@ -23,6 +25,12 @@ class ConfigScreenComponent extends BaseScreenComponent {
settingsChanged: false,
};
shared.init(this);
this.checkSyncConfig_ = async () => {
await shared.checkSyncConfig(this, this.state.settings);
}
this.saveButton_press = () => {
for (let n in this.state.settings) {
if (!this.state.settings.hasOwnProperty(n)) continue;
@@ -106,7 +114,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
const updateSettingValue = (key, value) => {
const settings = Object.assign({}, this.state.settings);
settings[key] = value;
settings[key] = Setting.formatValue(key, value);
this.setState({
settings: settings,
settingsChanged: true,
@@ -193,6 +201,27 @@ class ConfigScreenComponent extends BaseScreenComponent {
if (!comp) continue;
settingComps.push(comp);
}
const syncTargetMd = SyncTargetRegistry.idToMetadata(settings['sync.target']);
if (syncTargetMd.supportsConfigCheck) {
const messages = shared.checkSyncConfigMessages(this);
const statusComp = !messages.length ? null : (
<View style={{flex:1, marginTop: 10}}>
<Text>{messages[0]}</Text>
{messages.length >= 1 ? (<Text style={{marginTop:10}}>{messages[1]}</Text>) : null}
</View>);
settingComps.push(
<View key="check_sync_config_button" style={this.styles().settingContainer}>
<View style={{flex:1, flexDirection: 'column'}}>
<View style={{flex:1}}>
<Button title={_('Check synchronisation configuration')} onPress={this.checkSyncConfig_}/>
</View>
{ statusComp }
</View>
</View>);
}
settingComps.push(
<View key="website_link" style={this.styles().settingContainer}>

View File

@@ -215,12 +215,12 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent {
<ScreenHeader title={_('Encryption Config')}/>
<ScrollView style={this.styles().container}>
<View style={{backgroundColor: theme.warningBackgroundColor, padding: 5}}>
{/*<View style={{backgroundColor: theme.warningBackgroundColor, padding: 5}}>
<Text>Important: This is a *beta* feature. It has been extensively tested and is already in use by some users, but it is possible that some bugs remain.</Text>
<Text>If you wish to you use it, it is recommended that you keep a backup of your data. The simplest way is to regularly backup your notes from the desktop or terminal application.</Text>
<Text>For more information about End-To-End Encryption (E2EE) and how it is going to work, please check the documentation:</Text>
<TouchableOpacity onPress={() => { Linking.openURL('http://joplin.cozic.net/help/e2ee.html') }}><Text>http://joplin.cozic.net/help/e2ee.html</Text></TouchableOpacity>
</View>
</View>*/}
<Text style={this.styles().titleText}>{_('Status')}</Text>
<Text style={this.styles().normalText}>{_('Encryption is: %s', this.props.encryptionEnabled ? _('Enabled') : _('Disabled'))}</Text>

View File

@@ -0,0 +1,38 @@
const Setting = require('lib/models/Setting.js');
const SyncTargetRegistry = require('lib/SyncTargetRegistry');
const { _ } = require('lib/locale.js');
const shared = {}
shared.init = function(comp) {
if (!comp.state) comp.state = {};
comp.state.checkSyncConfigResult = null;
}
shared.checkSyncConfig = async function(comp, settings) {
const syncTargetId = settings['sync.target'];
const SyncTargetClass = SyncTargetRegistry.classById(syncTargetId);
const options = Setting.subValues('sync.' + syncTargetId, settings);
comp.setState({ checkSyncConfigResult: 'checking' });
const result = await SyncTargetClass.checkConfig(options);
console.info(result);
comp.setState({ checkSyncConfigResult: result });
}
shared.checkSyncConfigMessages = function(comp) {
const result = comp.state.checkSyncConfigResult;
const output = [];
if (result === 'checking') {
output.push(_('Checking... Please wait.'));
} else if (result && result.ok) {
output.push(_('Success! Synchronisation configuration appears to be correct.'));
} else if (result && !result.ok) {
output.push(_('Error. Please check that URL, username, password, etc. are correct and that the sync target is accessible. The reported error was:'));
output.push(result.errorMessage);
}
return output;
}
module.exports = shared;

View File

@@ -30,7 +30,6 @@ shared.saveNoteButton_press = async function(comp) {
options.fields = BaseModel.diffObjectsFields(comp.state.lastSavedNote, note);
}
const hasAutoTitle = comp.state.newAndNoTitleChangeNoteId || (isNew && !note.title);
if (hasAutoTitle) {
note.title = Note.defaultTitle(note);

View File

@@ -42,7 +42,7 @@ class FileApiDriverLocal {
metadataFromStat_(stat) {
return {
path: stat.path,
created_time: stat.birthtime.getTime(),
// created_time: stat.birthtime.getTime(),
updated_time: stat.mtime.getTime(),
isDir: stat.isDirectory(),
};

View File

@@ -39,7 +39,7 @@ class FileApiDriverMemory {
path: path,
isDir: isDir,
updated_time: now, // In milliseconds!!
created_time: now, // In milliseconds!!
// created_time: now, // In milliseconds!!
content: '',
};
}

View File

@@ -41,7 +41,7 @@ class FileApiDriverOneDrive {
if ('deleted' in odItem) {
output.isDeleted = true;
} else {
output.created_time = moment(odItem.fileSystemInfo.createdDateTime, 'YYYY-MM-DDTHH:mm:ss.SSSZ').format('x');
// output.created_time = moment(odItem.fileSystemInfo.createdDateTime, 'YYYY-MM-DDTHH:mm:ss.SSSZ').format('x');
output.updated_time = moment(odItem.fileSystemInfo.lastModifiedDateTime, 'YYYY-MM-DDTHH:mm:ss.SSSZ').format('x');
}

View File

@@ -18,6 +18,10 @@ class FileApiDriverWebDav {
return this.api_;
}
requestRepeatCount() {
return 3;
}
async stat(path) {
try {
const result = await this.api().execPropFind(path, 0, [
@@ -48,7 +52,7 @@ class FileApiDriverWebDav {
return {
path: path,
created_time: lastModifiedDate.getTime(),
// created_time: lastModifiedDate.getTime(),
updated_time: lastModifiedDate.getTime(),
isDir: isCollection === '',
sizeDONOTUSE: sizeDONOTUSE, // This property is used only for the WebDAV PUT hack (see below) so mark it as such so that it can be removed with the hack later on.

View File

@@ -4,6 +4,31 @@ const { shim } = require('lib/shim');
const BaseItem = require('lib/models/BaseItem.js');
const JoplinError = require('lib/JoplinError');
const ArrayUtils = require('lib/ArrayUtils');
const { time } = require('lib/time-utils.js');
function requestCanBeRepeated(error) {
const errorCode = typeof error === 'object' && error.code ? error.code : null;
if (errorCode === 'rejectedByTarget') return false;
return true;
}
async function tryAndRepeat(fn, count) {
let retryCount = 0;
while (true) {
try {
const result = await fn();
return result;
} catch (error) {
if (retryCount >= count) throw error;
if (!requestCanBeRepeated(error)) throw error;
retryCount++;
await time.sleep(1 + retryCount * 3);
}
}
}
class FileApi {
@@ -16,6 +41,14 @@ class FileApi {
this.driver_.fileApi_ = this;
}
// Ideally all requests repeating should be done at the FileApi level to remove duplicate code in the drivers, but
// historically some drivers (eg. OneDrive) are already handling request repeating, so this is optional, per driver,
// and it defaults to no repeating.
requestRepeatCount() {
if (this.driver_.requestRepeatCount) return this.driver_.requestRepeatCount();
return 0;
}
tempDirName() {
if (this.tempDirName_ === null) throw Error('Temp dir not set!');
return this.tempDirName_;
@@ -43,6 +76,7 @@ class FileApi {
}
setLogger(l) {
if (!l) l = new Logger();
this.logger_ = l;
}
@@ -58,50 +92,70 @@ class FileApi {
}
// DRIVER MUST RETURN PATHS RELATIVE TO `path`
list(path = '', options = null) {
async list(path = '', options = null) {
if (!options) options = {};
if (!('includeHidden' in options)) options.includeHidden = false;
if (!('context' in options)) options.context = null;
this.logger().debug('list ' + this.baseDir_);
return this.driver_.list(this.baseDir_, options).then((result) => {
if (!options.includeHidden) {
let temp = [];
for (let i = 0; i < result.items.length; i++) {
if (!isHidden(result.items[i].path)) temp.push(result.items[i]);
}
result.items = temp;
const result = await tryAndRepeat(() => this.driver_.list(this.baseDir_, options), this.requestRepeatCount());
if (!options.includeHidden) {
let temp = [];
for (let i = 0; i < result.items.length; i++) {
if (!isHidden(result.items[i].path)) temp.push(result.items[i]);
}
return result;
});
result.items = temp;
}
return result;
// return this.driver_.list(this.baseDir_, options).then((result) => {
// if (!options.includeHidden) {
// let temp = [];
// for (let i = 0; i < result.items.length; i++) {
// if (!isHidden(result.items[i].path)) temp.push(result.items[i]);
// }
// result.items = temp;
// }
// return result;
// });
}
// Deprectated
setTimestamp(path, timestampMs) {
this.logger().debug('setTimestamp ' + this.fullPath_(path));
return this.driver_.setTimestamp(this.fullPath_(path), timestampMs);
return tryAndRepeat(() => this.driver_.setTimestamp(this.fullPath_(path), timestampMs), this.requestRepeatCount());
//return this.driver_.setTimestamp(this.fullPath_(path), timestampMs);
}
mkdir(path) {
this.logger().debug('mkdir ' + this.fullPath_(path));
return this.driver_.mkdir(this.fullPath_(path));
return tryAndRepeat(() => this.driver_.mkdir(this.fullPath_(path)), this.requestRepeatCount());
}
stat(path) {
async stat(path) {
this.logger().debug('stat ' + this.fullPath_(path));
return this.driver_.stat(this.fullPath_(path)).then((output) => {
if (!output) return output;
output.path = path;
return output;
});
const output = await tryAndRepeat(() => this.driver_.stat(this.fullPath_(path)), this.requestRepeatCount());
if (!output) return output;
output.path = path;
return output;
// return this.driver_.stat(this.fullPath_(path)).then((output) => {
// if (!output) return output;
// output.path = path;
// return output;
// });
}
get(path, options = null) {
if (!options) options = {};
if (!options.encoding) options.encoding = 'utf8';
this.logger().debug('get ' + this.fullPath_(path));
return this.driver_.get(this.fullPath_(path), options);
return tryAndRepeat(() => this.driver_.get(this.fullPath_(path), options), this.requestRepeatCount());
}
async put(path, content, options = null) {
@@ -111,32 +165,32 @@ class FileApi {
if (!await this.fsDriver().exists(options.path)) throw new JoplinError('File not found: ' + options.path, 'fileNotFound');
}
return this.driver_.put(this.fullPath_(path), content, options);
return tryAndRepeat(() => this.driver_.put(this.fullPath_(path), content, options), this.requestRepeatCount());
}
delete(path) {
this.logger().debug('delete ' + this.fullPath_(path));
return this.driver_.delete(this.fullPath_(path));
return tryAndRepeat(() => this.driver_.delete(this.fullPath_(path)), this.requestRepeatCount());
}
// Deprectated
move(oldPath, newPath) {
this.logger().debug('move ' + this.fullPath_(oldPath) + ' => ' + this.fullPath_(newPath));
return this.driver_.move(this.fullPath_(oldPath), this.fullPath_(newPath));
return tryAndRepeat(() => this.driver_.move(this.fullPath_(oldPath), this.fullPath_(newPath)), this.requestRepeatCount());
}
// Deprectated
format() {
return this.driver_.format();
return tryAndRepeat(() => this.driver_.format(), this.requestRepeatCount());
}
clearRoot() {
return this.driver_.clearRoot(this.baseDir_);
return tryAndRepeat(() => this.driver_.clearRoot(this.baseDir_), this.requestRepeatCount());
}
delta(path, options = null) {
this.logger().debug('delta ' + this.fullPath_(path));
return this.driver_.delta(this.fullPath_(path), options);
return tryAndRepeat(() => this.driver_.delta(this.fullPath_(path), options), this.requestRepeatCount());
}
}

View File

@@ -256,7 +256,7 @@ function countryDisplayName(canonicalName) {
let extraString;
if (countryCode != "") {
if (countryCode) {
if (languageCode == "zh" && countryCode == "CN") {
extraString = "简体"; // "Simplified" in "Simplified Chinese"
} else {
@@ -266,7 +266,7 @@ function countryDisplayName(canonicalName) {
if (languageCode == "zh" && (countryCode == "" || countryCode == "TW")) extraString = "繁體"; // "Traditional" in "Traditional Chinese"
if (extraString != "") output += " (" + extraString + ")";
if (extraString) output += " (" + extraString + ")";
return output;
}
@@ -294,7 +294,11 @@ function _(s, ...args) {
let strings = localeStrings(currentLocale_);
let result = strings[s];
if (result === '' || result === undefined) result = s;
return sprintf(result, ...args);
try {
return sprintf(result, ...args);
} catch (error) {
return result + ' ' + args.join(', ') + ' (Translation error: ' + error.message + ')';
}
}
module.exports = { _, supportedLocales, countryDisplayName, localeStrings, setLocale, supportedLocalesToLanguages, defaultLocale, closestSupportedLocale, languageCode, countryCodeOnly };

View File

@@ -381,26 +381,26 @@ class Note extends BaseItem {
return this.save(newNote);
}
static save(o, options = null) {
static async save(o, options = null) {
let isNew = this.isNew(o, options);
if (isNew && !o.source) o.source = Setting.value('appName');
if (isNew && !o.source_application) o.source_application = Setting.value('appId');
return super.save(o, options).then((note) => {
this.dispatch({
type: 'NOTE_UPDATE_ONE',
note: note,
});
const note = await super.save(o, options);
if ('todo_due' in o || 'todo_completed' in o || 'is_todo' in o || 'is_conflict' in o) {
this.dispatch({
type: 'EVENT_NOTE_ALARM_FIELD_CHANGE',
id: note.id,
});
}
return note;
this.dispatch({
type: 'NOTE_UPDATE_ONE',
note: note,
});
if ('todo_due' in o || 'todo_completed' in o || 'is_todo' in o || 'is_conflict' in o) {
this.dispatch({
type: 'EVENT_NOTE_ALARM_FIELD_CHANGE',
id: note.id,
});
}
return note;
}
static async delete(id, options = null) {

View File

@@ -392,26 +392,42 @@ class Setting extends BaseModel {
return !!options[value];
}
// Currently only supports objects with properties one level deep
static object(key) {
// For example, if settings is:
// { sync.5.path: 'http://example', sync.5.username: 'testing' }
// and baseKey is 'sync.5', the function will return
// { path: 'http://example', username: 'testing' }
static subValues(baseKey, settings) {
let output = {};
let keys = this.keys();
for (let i = 0; i < keys.length; i++) {
let k = keys[i].split('.');
if (k[0] == key) {
output[k[1]] = this.value(keys[i]);
for (let key in settings) {
if (!settings.hasOwnProperty(key)) continue;
if (key.indexOf(baseKey) === 0) {
const subKey = key.substr(baseKey.length + 1);
output[subKey] = settings[key];
}
}
return output;
}
// Currently only supports objects with properties one level deep
static setObject(key, object) {
for (let n in object) {
if (!object.hasOwnProperty(n)) continue;
this.setValue(key + '.' + n, object[n]);
}
}
// static object(key) {
// let output = {};
// let keys = this.keys();
// for (let i = 0; i < keys.length; i++) {
// let k = keys[i].split('.');
// if (k[0] == key) {
// output[k[1]] = this.value(keys[i]);
// }
// }
// return output;
// }
// Currently only supports objects with properties one level deep
// static setObject(key, object) {
// for (let n in object) {
// if (!object.hasOwnProperty(n)) continue;
// this.setValue(key + '.' + n, object[n]);
// }
// }
static saveAll() {
if (!this.saveTimeoutId_) return Promise.resolve();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -3,6 +3,7 @@ locales['en_GB'] = require('./en_GB.json');
locales['de_DE'] = require('./de_DE.json');
locales['es_CR'] = require('./es_CR.json');
locales['es_ES'] = require('./es_ES.json');
locales['eu'] = require('./eu.json');
locales['fr_FR'] = require('./fr_FR.json');
locales['hr_HR'] = require('./hr_HR.json');
locales['it_IT'] = require('./it_IT.json');

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -228,6 +228,11 @@
"lodash": "4.17.4"
}
},
"async-mutex": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.1.3.tgz",
"integrity": "sha1-Cq0hEjaXlas/F+M3RFVtLs9UdWY="
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -3666,6 +3671,24 @@
"uc.micro": "1.0.3"
}
},
"markdown-it-katex": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/markdown-it-katex/-/markdown-it-katex-2.0.3.tgz",
"integrity": "sha1-17hqGuoLnWSW+rTnkZoY/e9YnDk=",
"requires": {
"katex": "0.6.0"
},
"dependencies": {
"katex": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.6.0.tgz",
"integrity": "sha1-EkGOCRIcBckgQbazuftrqyE8tvM=",
"requires": {
"match-at": "0.1.1"
}
}
}
},
"match-at": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/match-at/-/match-at-0.1.1.tgz",

View File

@@ -10,6 +10,7 @@
"postinstall": "node ../Tools/copycss.js"
},
"dependencies": {
"async-mutex": "^0.1.3",
"base-64": "^0.1.0",
"buffer": "^5.0.8",
"events": "^1.1.1",
@@ -17,6 +18,7 @@
"html-entities": "^1.2.1",
"katex": "^0.9.0-beta1",
"markdown-it": "^8.4.0",
"markdown-it-katex": "^2.0.3",
"md5": "^2.2.1",
"moment": "^2.18.1",
"prop-types": "^15.6.0",

View File

@@ -1,5 +1,5 @@
const React = require('react'); const Component = React.Component;
const { Keyboard, NativeModules, BackHandler } = require('react-native');
const { AppState, Keyboard, NativeModules, BackHandler } = require('react-native');
const { SafeAreaView } = require('react-navigation');
const { connect, Provider } = require('react-redux');
const { BackButtonService } = require('lib/services/back-button.js');
@@ -469,6 +469,10 @@ class AppComponent extends React.Component {
this.backButtonHandler_ = () => {
return this.backButtonHandler();
}
this.onAppStateChange_ = () => {
PoorManIntervals.update();
}
}
async componentDidMount() {
@@ -493,6 +497,12 @@ class AppComponent extends React.Component {
const notification = await Alarm.makeNotification(alarm);
this.dropdownAlert_.alertWithType('info', notification.title, notification.body ? notification.body : '');
});
AppState.addEventListener('change', this.onAppStateChange_);
}
componentWillUnmount() {
AppState.removeEventListener('change', this.onAppStateChange_);
}
async backButtonHandler() {

View File

@@ -108,6 +108,23 @@ function availableLocales(defaultLocale) {
return output;
}
function extractTranslator(regex, poContent) {
const translatorMatch = poContent.match(regex);
let translatorName = '';
if (translatorMatch && translatorMatch.length >= 1) {
translatorName = translatorMatch[1];
translatorName = translatorName.replace(/["\s]+$/, '');
translatorName = translatorName.replace(/\\n$/, '');
translatorName = translatorName.replace(/^\s*/, '');
}
if (translatorName.indexOf('FULL NAME') >= 0) return '';
if (translatorName.indexOf('LL@li.org') >= 0) return '';
return translatorName;
}
async function translationStatus(isDefault, poFile) {
// "apt install translate-toolkit" to have pocount
const command = 'pocount "' + poFile + '"';
@@ -120,17 +137,26 @@ async function translationStatus(isDefault, poFile) {
let translatorName = '';
const content = await fs.readFile(poFile, 'utf-8');
// "Last-Translator: Hrvoje Mandić <trbuhom@net.hr>\n"
const translatorMatch = content.match(/Last-Translator:\s*?(.*)/);
if (translatorMatch.length >= 1) {
translatorName = translatorMatch[1];
translatorName = translatorName.replace(/["\s]+$/, '');
translatorName = translatorName.replace(/\\n$/, '');
translatorName = translatorName.replace(/^\s*/, '');
translatorName = extractTranslator(/Last-Translator:\s*?(.*)/, content);
if (!translatorName) {
translatorName = extractTranslator(/Language-Team:\s*?(.*)/, content);
}
if (translatorName.indexOf('FULL NAME') >= 0) translatorName = '';
// "Last-Translator: Hrvoje Mandić <trbuhom@net.hr>\n"
// let translatorMatch = content.match(/Last-Translator:\s*?(.*)/);
// if (translatorMatch.length < 1) {
// translatorMatch = content.match(/Last-Team:\s*?(.*)/);
// }
// if (translatorMatch.length >= 1) {
// translatorName = translatorMatch[1];
// translatorName = translatorName.replace(/["\s]+$/, '');
// translatorName = translatorName.replace(/\\n$/, '');
// translatorName = translatorName.replace(/^\s*/, '');
// }
// if (translatorName.indexOf('FULL NAME') >= 0) translatorName = '';
return {
percentDone: isDefault ? 100 : percentDone,
@@ -138,13 +164,21 @@ async function translationStatus(isDefault, poFile) {
};
}
function flagImageUrl(locale) {
if (locale === 'eu') {
return 'https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/es/basque_country.png';
} else {
return 'https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/' + countryCodeOnly(locale).toLowerCase() + '.png'
}
}
function translationStatusToMdTable(status) {
let output = [];
output.push(['&nbsp;', 'Language', 'Code', 'Last translator', 'Percent done'].join(' | '));
output.push(['---', '---', '---', '---', '---'].join('|'));
for (let i = 0; i < status.length; i++) {
const stat = status[i];
const flagUrl = 'https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/' + countryCodeOnly(stat.locale).toLowerCase() + '.png';
const flagUrl = flagImageUrl(stat.locale); //'https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/' + countryCodeOnly(stat.locale).toLowerCase() + '.png';
output.push(['![](' + flagUrl + ')', stat.languageName, stat.locale, stat.translatorName, stat.percentDone + '%'].join(' | '));
}
return output.join('\n');

View File

@@ -47,12 +47,13 @@ function gradleVersionName(content) {
}
async function main() {
const projectName = 'joplin-android';
const newContent = updateGradleConfig();
const version = gradleVersionName(newContent);
const tagName = 'android-v' + version;
const apkFilename = 'joplin-v' + version + '.apk';
const apkFilePath = releaseDir + '/' + apkFilename;
const downloadUrl = 'https://github.com/laurent22/joplin/releases/download/' + tagName + '/' + apkFilename;
const downloadUrl = 'https://github.com/laurent22/' + projectName + '/releases/download/' + tagName + '/' + apkFilename;
process.chdir(rootDir);
@@ -70,9 +71,10 @@ async function main() {
console.info('Updating Readme URL...');
let readmeContent = await fs.readFile('README.md', 'utf8');
readmeContent = readmeContent.replace(/(https:\/\/github.com\/laurent22\/joplin\/releases\/download\/.*?\.apk)/, downloadUrl);
readmeContent = readmeContent.replace(/(https:\/\/github.com\/laurent22\/joplin-android\/releases\/download\/.*?\.apk)/, downloadUrl);
await fs.writeFile('README.md', readmeContent);
console.info(await execCommand('git pull'));
console.info(await execCommand('git add -A'));
console.info(await execCommand('git commit -m "Android release v' + version + '"'));
console.info(await execCommand('git tag ' + tagName));
@@ -81,7 +83,7 @@ async function main() {
console.info('Creating GitHub release ' + tagName + '...');
const release = await githubRelease(tagName, false);
const release = await githubRelease(projectName, tagName, false);
const uploadUrlTemplate = uriTemplate.parse(release.upload_url);
const uploadUrl = uploadUrlTemplate.expand({ name: apkFilename });

View File

@@ -20,7 +20,7 @@ async function main() {
console.info(await execCommand('git tag ' + tagName));
console.info(await execCommand('git push && git push --tags'));
const release = await githubRelease(tagName, true);
const release = await githubRelease('joplin', tagName, true);
console.info('Created GitHub release: ' + release.html_url);
}

View File

@@ -87,12 +87,12 @@ toolUtils.githubOauthToken = async function() {
return r.toString();
}
toolUtils.githubRelease = async function(tagName, isDraft) {
toolUtils.githubRelease = async function(project, tagName, isDraft) {
const fetch = require('node-fetch');
const oauthToken = await toolUtils.githubOauthToken();
const response = await fetch('https://api.github.com/repos/laurent22/joplin/releases', {
const response = await fetch('https://api.github.com/repos/laurent22/' + project + '/releases', {
method: 'POST',
body: JSON.stringify({
tag_name: tagName,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -202,7 +202,7 @@
<p>Joplin is a free, open source note taking and to-do application, which can handle a large number of notes organised into notebooks. The notes are searchable, can be copied, tagged and modified either from the applications directly or from your own text editor. The notes are in <a href="https://daringfireball.net/projects/markdown/basics">Markdown format</a>.</p>
<p>Notes exported from Evernote via .enex files <a href="#importing-notes-from-evernote">can be imported</a> into Joplin, including the formatted content (which is converted to Markdown), resources (images, attachments, etc.) and complete metadata (geolocation, updated time, created time, etc.).</p>
<p>The notes can be <a href="#synchronisation">synchronised</a> with various targets including <a href="https://nextcloud.com/">Nextcloud</a>, the file system (for example with a network directory) or with Microsoft OneDrive. When synchronising the notes, notebooks, tags and other metadata are saved to plain text files which can be easily inspected, backed up and moved around.</p>
<p>Joplin is still under development but is out of Beta and should be suitable for every day use. The UI of the terminal client is built on top of the great <a href="https://github.com/cronvel/terminal-kit">terminal-kit</a> library, the desktop client using <a href="https://electronjs.org/">Electron</a>, and the Android client front end is done using <a href="https://facebook.github.io/react-native/">React Native</a>.</p>
<p>The UI of the terminal client is built on top of the great <a href="https://github.com/cronvel/terminal-kit">terminal-kit</a> library, the desktop client using <a href="https://electronjs.org/">Electron</a>, and the Android client front end is done using <a href="https://facebook.github.io/react-native/">React Native</a>.</p>
<div class="top-screenshot"><img src="https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/AllClients.jpg" style="max-width: 100%; max-height: 35em;"></div>
<h1 id="installation">Installation</h1>
@@ -218,15 +218,15 @@
<tbody>
<tr>
<td>Windows</td>
<td><a href='https://github.com/laurent22/joplin/releases/download/v0.10.59/Joplin-Setup-0.10.59.exe'><img alt='Get it on Windows' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeWindows.png'/></a></td>
<td><a href='https://github.com/laurent22/joplin/releases/download/v0.10.61/Joplin-Setup-0.10.61.exe'><img alt='Get it on Windows' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeWindows.png'/></a></td>
</tr>
<tr>
<td>macOS</td>
<td><a href='https://github.com/laurent22/joplin/releases/download/v0.10.59/Joplin-0.10.59.dmg'><img alt='Get it on macOS' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeMacOS.png'/></a></td>
<td><a href='https://github.com/laurent22/joplin/releases/download/v0.10.61/Joplin-0.10.61.dmg'><img alt='Get it on macOS' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeMacOS.png'/></a></td>
</tr>
<tr>
<td>Linux</td>
<td><a href='https://github.com/laurent22/joplin/releases/download/v0.10.59/Joplin-0.10.59-x86_64.AppImage'><img alt='Get it on macOS' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeLinux.png'/></a></td>
<td><a href='https://github.com/laurent22/joplin/releases/download/v0.10.61/Joplin-0.10.61-x86_64.AppImage'><img alt='Get it on macOS' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeLinux.png'/></a></td>
</tr>
</tbody>
</table>
@@ -243,7 +243,7 @@
<tr>
<td>Android</td>
<td><a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeAndroid.png'/></a></td>
<td>or <a href="https://github.com/laurent22/joplin/releases/download/android-v0.10.86/joplin-v0.10.86.apk">Download APK File</a></td>
<td>or <a href="https://github.com/laurent22/joplin-android/releases/download/android-v1.0.94/joplin-v1.0.94.apk">Download APK File</a></td>
</tr>
<tr>
<td>iOS</td>
@@ -297,7 +297,6 @@ sudo ln -s ~/.joplin-bin/bin/joplin /usr/bin/joplin
<p>One of the goals of Joplin was to avoid being tied to any particular company or service, whether it is Evernote, Google or Microsoft. As such the synchronisation is designed without any hard dependency to any particular service. Most of the synchronisation process is done at an abstract level and access to external services, such as Nextcloud or OneDrive, is done via lightweight drivers. It is easy to support new services by creating simple drivers that provide a filesystem-like interface, i.e. the ability to read, write, delete and list items. It is also simple to switch from one service to another or to even sync to multiple services at once. Each note, notebook, tags, as well as the relation between items is transmitted as plain text files during synchronisation, which means the data can also be moved to a different application, can be easily backed up, inspected, etc.</p>
<p>Currently, synchronisation is possible with Nextcloud and OneDrive (by default) or the local filesystem. A Dropbox one will also be available once <a href="https://github.com/facebook/react-native/issues/14445">this React Native bug</a> is fixed. To setup synchronisation please follow the instructions below. After that, the application will synchronise in the background whenever it is running, or you can click on &quot;Synchronise&quot; to start a synchronisation manually.</p>
<h2 id="nextcloud-synchronisation">Nextcloud synchronisation</h2>
<p><strong>Important: This is a beta feature. It has been extensively tested and is already in use by some users, but it is possible that some bugs remain. If you wish to you use it, it is recommended that you keep a backup of your data. The simplest way is to regularly backup the profile directory of the desktop or terminal application.</strong></p>
<p>On the <strong>desktop application</strong> or <strong>mobile application</strong>, go to the config screen and select Nextcloud as the synchronisation target. Then input <a href="https://docs.nextcloud.com/server/9/user_manual/files/access_webdav.html">the WebDAV URL</a>, this is normally <code>https://example.com/nextcloud/remote.php/dav/files/USERNAME/Joplin</code> (make sure to create the &quot;Joplin&quot; directory in Nextcloud and to replace USERNAME by your Nextcloud username), and set the username and password.</p>
<p>On the <strong>terminal application</strong>, you will need to set the <code>sync.target</code> config variable and all the <code>sync.5.path</code>, <code>sync.5.username</code> and <code>sync.5.password</code> config variables to, respectively the Nextcloud WebDAV URL, your username and your password. This can be done from the command line mode using:</p>
<pre><code>:config sync.5.path https://example.com/nextcloud/remote.php/dav/files/USERNAME/Joplin
@@ -306,7 +305,6 @@ sudo ln -s ~/.joplin-bin/bin/joplin /usr/bin/joplin
:config sync.target 5
</code></pre><p>If synchronisation does not work, please consult the logs in the app profile directory - it is often due to a misconfigured URL or password. The log should indicate what the exact issue is.</p>
<h2 id="webdav-synchronisation">WebDAV synchronisation</h2>
<p><strong>Important: This is a beta feature. It has been extensively tested and is already in use by some users, but it is possible that some bugs remain. If you wish to you use it, it is recommended that you keep a backup of your data. The simplest way is to regularly backup the profile directory of the desktop or terminal application.</strong></p>
<p>Select the &quot;WebDAV&quot; synchronisation target and follow the same instructions as for Nextcloud above.</p>
<h2 id="onedrive-synchronisation">OneDrive synchronisation</h2>
<p>When syncing with OneDrive, Joplin creates a sub-directory in OneDrive, in /Apps/Joplin and read/write the notes and notebooks from it. The application does not have access to anything outside this directory.</p>
@@ -331,16 +329,16 @@ sudo ln -s ~/.joplin-bin/bin/joplin /usr/bin/joplin
<h1 id="markdown">Markdown</h1>
<p>Joplin uses and renders <a href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet">Github-flavoured Markdown</a> with a few variations and additions. In particular:</p>
<h2 id="math-notation">Math notation</h2>
<p>Math expressions can be added using the <a href="https://khan.github.io/KaTeX/">Katex notation</a>. To add an inline equation, wrap the expression in <code>`{.katex}EXPRESSION` </code>, eg. <code>`{.katex}\sqrt{3x-1}+(1+x)^2` </code>. To create an expression block, wrap it as follow:</p>
<pre><code>```katex
<p>Math expressions can be added using the <a href="https://khan.github.io/KaTeX/">Katex notation</a>. To add an inline equation, wrap the expression in <code>$EXPRESSION$</code>, eg. <code>$\sqrt{3x-1}+(1+x)^2$</code>. To create an expression block, wrap it as follow:</p>
<pre><code>$$
EXPRESSION
```
$$
</code></pre><p>For example:</p>
<pre><code>```katex
<pre><code>$$
f(x) = \int_{-\infty}^\infty
\hat f(\xi)\,e^{2 \pi i \xi x}
\,d\xi
```
$$
</code></pre><p>Here is an example with the Markdown and rendered result side by side:</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/Katex.png" style="max-width: 100%; max-height: 35em;"></p>
<h2 id="checkboxes">Checkboxes</h2>
@@ -375,18 +373,25 @@ f(x) = \int_{-\infty}^\infty
</thead>
<tbody>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/es/basque_country.png" alt=""></td>
<td>Basque</td>
<td>eu</td>
<td>juan.abasolo@ehu.eus</td>
<td>89%</td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/hr.png" alt=""></td>
<td>Croatian</td>
<td>hr_HR</td>
<td>Hrvoje Mandić <a href="&#109;&#97;&#x69;&#x6c;&#116;&#111;&#x3a;&#x74;&#114;&#98;&#117;&#104;&#x6f;&#x6d;&#x40;&#110;&#101;&#x74;&#46;&#104;&#114;">&#x74;&#114;&#98;&#117;&#104;&#x6f;&#x6d;&#x40;&#110;&#101;&#x74;&#46;&#104;&#114;</a></td>
<td>Hrvoje Mandić <a href="&#x6d;&#x61;&#x69;&#x6c;&#x74;&#111;&#x3a;&#x74;&#114;&#98;&#117;&#104;&#x6f;&#x6d;&#64;&#110;&#101;&#116;&#46;&#x68;&#x72;">&#x74;&#114;&#98;&#117;&#104;&#x6f;&#x6d;&#64;&#110;&#101;&#116;&#46;&#x68;&#x72;</a></td>
<td>72%</td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/de.png" alt=""></td>
<td>Deutsch</td>
<td>de_DE</td>
<td>Tobias Strobel <a href="&#109;&#97;&#105;&#108;&#x74;&#111;&#58;&#x67;&#x69;&#x74;&#64;&#x73;&#x74;&#x72;&#x6f;&#x62;&#x65;&#108;&#x74;&#111;&#98;&#x69;&#97;&#x73;&#x2e;&#x64;&#x65;">&#x67;&#x69;&#x74;&#64;&#x73;&#x74;&#x72;&#x6f;&#x62;&#x65;&#108;&#x74;&#111;&#98;&#x69;&#97;&#x73;&#x2e;&#x64;&#x65;</a></td>
<td>92%</td>
<td>Tobias Strobel <a href="&#109;&#97;&#105;&#108;&#x74;&#x6f;&#x3a;&#x67;&#x69;&#x74;&#x40;&#115;&#116;&#x72;&#111;&#x62;&#101;&#108;&#x74;&#x6f;&#98;&#x69;&#97;&#x73;&#x2e;&#x64;&#x65;">&#x67;&#x69;&#x74;&#x40;&#115;&#116;&#x72;&#111;&#x62;&#101;&#108;&#x74;&#x6f;&#98;&#x69;&#97;&#x73;&#x2e;&#x64;&#x65;</a></td>
<td>91%</td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/gb.png" alt=""></td>
@@ -400,7 +405,7 @@ f(x) = \int_{-\infty}^\infty
<td>Español</td>
<td>es_ES</td>
<td>Lucas Vieites</td>
<td>80%</td>
<td>79%</td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/cr.png" alt=""></td>
@@ -421,14 +426,14 @@ f(x) = \int_{-\infty}^\infty
<td>Italiano</td>
<td>it_IT</td>
<td></td>
<td>76%</td>
<td>75%</td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/be.png" alt=""></td>
<td>Nederlands</td>
<td>nl_BE</td>
<td></td>
<td>90%</td>
<td>89%</td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/br.png" alt=""></td>
@@ -441,34 +446,26 @@ f(x) = \int_{-\infty}^\infty
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/ru.png" alt=""></td>
<td>Русский</td>
<td>ru_RU</td>
<td>Artyom Karlov <a href="&#x6d;&#97;&#105;&#x6c;&#116;&#x6f;&#58;&#x61;&#114;&#116;&#x79;&#111;&#109;&#46;&#107;&#x61;&#114;&#x6c;&#x6f;&#118;&#64;&#x67;&#109;&#x61;&#105;&#x6c;&#x2e;&#x63;&#x6f;&#x6d;">&#x61;&#114;&#116;&#x79;&#111;&#109;&#46;&#107;&#x61;&#114;&#x6c;&#x6f;&#118;&#64;&#x67;&#109;&#x61;&#105;&#x6c;&#x2e;&#x63;&#x6f;&#x6d;</a></td>
<td>96%</td>
<td>Artyom Karlov <a href="&#x6d;&#97;&#x69;&#108;&#x74;&#x6f;&#x3a;&#x61;&#114;&#116;&#121;&#x6f;&#109;&#46;&#107;&#x61;&#x72;&#x6c;&#111;&#118;&#x40;&#x67;&#109;&#97;&#105;&#108;&#46;&#x63;&#111;&#109;">&#x61;&#114;&#116;&#121;&#x6f;&#109;&#46;&#107;&#x61;&#x72;&#x6c;&#111;&#118;&#x40;&#x67;&#109;&#97;&#105;&#108;&#46;&#x63;&#111;&#109;</a></td>
<td>94%</td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/cn.png" alt=""></td>
<td>中文 (简体)</td>
<td>zh_CN</td>
<td>RCJacH <a href="&#x6d;&#97;&#x69;&#108;&#116;&#111;&#58;&#x52;&#67;&#x4a;&#97;&#x63;&#72;&#x40;&#x6f;&#x75;&#116;&#108;&#111;&#x6f;&#107;&#x2e;&#x63;&#111;&#109;">&#x52;&#67;&#x4a;&#97;&#x63;&#72;&#x40;&#x6f;&#x75;&#116;&#108;&#111;&#x6f;&#107;&#x2e;&#x63;&#111;&#109;</a></td>
<td>76%</td>
<td>RCJacH <a href="&#109;&#x61;&#105;&#x6c;&#116;&#x6f;&#x3a;&#x52;&#67;&#x4a;&#97;&#99;&#72;&#x40;&#x6f;&#117;&#x74;&#108;&#111;&#111;&#107;&#x2e;&#99;&#111;&#x6d;">&#x52;&#67;&#x4a;&#97;&#99;&#72;&#x40;&#x6f;&#117;&#x74;&#108;&#111;&#111;&#107;&#x2e;&#99;&#111;&#x6d;</a></td>
<td>75%</td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/jp.png" alt=""></td>
<td>日本語</td>
<td>ja_JP</td>
<td></td>
<td>74%</td>
<td>73%</td>
</tr>
</tbody>
</table>
<!-- LOCALE-TABLE-AUTO-GENERATED -->
<h1 id="coming-features">Coming features</h1>
<ul>
<li>Mobile: manage tags</li>
<li>Windows: Tray icon</li>
<li>Desktop apps: Tag auto-complete</li>
<li>Desktop apps: Dark theme</li>
<li>Linux: Enable auto-update for desktop app</li>
</ul>
<h1 id="known-bugs">Known bugs</h1>
<ul>
<li>Non-alphabetical characters such as Chinese or Arabic might create glitches in the terminal on Windows. This is a limitation of the current Windows console.</li>

View File

@@ -50,6 +50,7 @@
"tests/logs",
"ReactNativeClient/ios/build",
"ElectronClient/app/dist",
"_releases"
],
"path": "."
},