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

Compare commits

...

67 Commits

Author SHA1 Message Date
Laurent Cozic
c0d679b6c2 Android release v1.0.178 2018-12-16 18:41:51 +01:00
Laurent Cozic
eb789b9b9a Electron release v1.0.119 2018-12-16 18:35:37 +01:00
Laurent Cozic
b1898141c3 Mobile: Fixes #382: Implemented new search engine for mobile and highlight searched words in notes 2018-12-16 18:32:42 +01:00
Laurent Cozic
3231bfaff0 Mobile: Fixes #1045: Display notebooks as a tree in notebook dropdown 2018-12-16 17:18:24 +01:00
Laurent Cozic
6bb09c9c30 Updated FAQ with info about F-Droid and how to fix Nextcloud issues 2018-12-16 14:30:56 +01:00
Laurent Cozic
35d3fe03ab Android: Fixes #321: Changed notification library to Firebase to get more reliable notifications 2018-12-16 14:11:45 +01:00
Laurent Cozic
f05929cd17 All: Fixes #1033: Handle hard break when rendering Markdown to HTML 2018-12-16 11:41:15 +01:00
Laurent Cozic
982c9828da Desktop: Fixes #1039: Always print or export to PDF using light theme 2018-12-16 02:49:06 +01:00
Laurent Cozic
d6eacb2b33 Android release v1.0.177 2018-12-15 01:51:49 +01:00
Laurent Cozic
0abe213fc2 Merge branch 'master' of github.com:laurent22/joplin 2018-12-15 01:46:16 +01:00
Laurent Cozic
a6716d55c5 Doc: Added search engine doc 2018-12-15 01:46:06 +01:00
Laurent Cozic
fa0572de77 Mobile: Many small fixes and improvements to style and layout to make app more usable 2018-12-15 01:45:35 +01:00
Laurent Cozic
6dca4a0d6b Mobile: Optimised loading of large notes that contain many images. 2018-12-15 01:42:19 +01:00
Helmut K. C. Tessarek
eacfe1a9ac add Apache WebDAV Module to WebDAV-compatible services (#1042) 2018-12-14 22:50:50 +00:00
Laurent Cozic
c223cdf10a Electron release v1.0.118 2018-12-14 19:57:32 +01:00
Laurent Cozic
38c42b7a15 Merge branch 'full_text_search' 2018-12-14 19:56:21 +01:00
Mats Estensen
56432dc773 [Documentation] Add example editor config to FAQ (#1036)
* add external editor example config to faq

* add external editor to feature list
2018-12-14 18:04:53 +00:00
Lucas
d3b4379161 Update to make it works in Kubuntu (#1038)
The directory ~/.local/share/applications/ exists in Kubuntu 18.04 so with this change the script also work in Kubuntu.
2018-12-14 18:03:39 +00:00
Laurent Cozic
8a6fcdbcae Should be commented out by default 2018-12-14 00:00:03 +01:00
Laurent Cozic
061ce646d2 Finished search engine integration with desktop app 2018-12-13 23:57:14 +01:00
Laurent Cozic
5ec7c16e3e Fixed logic to update search engine data 2018-12-12 22:40:05 +01:00
Andros Fenollosa
5d629508c1 Fix name in Linux Desktop (#1034) 2018-12-11 21:51:47 +01:00
Laurent Cozic
0a6f8b0cfe Started integrating search engine to desktop app 2018-12-10 19:58:49 +01:00
Laurent Cozic
460f826672 Nearly finished search engine backend 2018-12-10 18:54:46 +00:00
Laurent Cozic
cb16a10121 Updated the way item changes are recorded so that info can be used by more services (including search engine) 2018-12-10 01:39:31 +01:00
Laurent Cozic
3b6131f1ca Started support for FTS search 2018-12-09 21:45:50 +01:00
Laurent Cozic
57225a36b9 Updated translations 2018-12-09 01:22:16 +01:00
Laurent Cozic
3e313399c2 Desktop: Search within current note 2018-12-09 01:18:10 +01:00
Laurent Cozic
7947e14792 Merge branch 'master' of github.com:laurent22/joplin 2018-12-08 00:42:54 +01:00
Laurent Cozic
71098102c5 Electron: Fixes #476 (maybe): Trying to fix notification flood. Added more log statements in case something goes wrong. 2018-12-08 00:42:29 +01:00
Laurent Cozic
8e601e80df All: Prevent sync infinite loop under some rare conditions 2018-12-08 00:41:39 +01:00
Caleb John
3b14cfcc54 add separate editor font size option (#1027) 2018-12-07 22:26:03 +01:00
Laurent Cozic
61a0e43092 Mobile: Fixes #999: Associate new note with default notebook when creating it from Welcome screen 2018-12-07 01:23:36 +01:00
Laurent Cozic
d08aaffe41 Mobile: Resolves #1015: (Re-)added support for selecting image from camera roll 2018-12-07 01:07:10 +01:00
Laurent Cozic
7d0def30f0 Revert "ReactNativeClient: A better NAV_BACK logic to change folder or tag. (#984)" (Was opening up side menu on first app startup)
This reverts commit fc8f53fd0e.
2018-12-07 00:45:53 +01:00
Laurent Cozic
bb45d72a56 Clipper: Fixes #1026: Handle adding tag to clipped content after content has been clipped 2018-12-06 23:50:02 +01:00
Laurent Cozic
3943192c5d All: Fixes #808 (maybe): Added fix for Nginx 404 error issue. 2018-12-06 23:09:54 +01:00
Laurent Cozic
18d76807f6 Fixing Norwegian locale 2018-12-06 22:59:08 +01:00
Laurent Cozic
01a30a7ccf Fixed renaming 'no' locale to 'nb_NO' 2018-12-05 23:30:36 +01:00
Mats Estensen
3fb35d043b Revise and complete Norwegian translation (no) and rename to correct locale: nb_NO (#1013)
* update norwegian translation and correct locale name to nb_NO

* set no to nb_NO in documentation

* Add name, email and completion to README

Norwegian translation
2018-12-05 23:21:40 +01:00
Alex Devero
9b51bd484d Fix failing Windows build (#997)
* Upgrade sqlite3 to 4.0.4

* Remove rebuild, add install-app-deps
2018-12-05 23:11:40 +01:00
Laurent Cozic
879b556845 Update website 2018-11-24 12:05:49 +00:00
Laurent Cozic
0df2a501dd Electron release v1.0.117 2018-11-24 11:44:38 +00:00
Laurent Cozic
6f64fdffcc Desktop: Fixes #995: Added flag to disable tag bar for now 2018-11-24 11:42:50 +00:00
Laurent Cozic
19252af345 Desktop: Fixes #996: Allow editing multiple notes in external editor 2018-11-21 19:50:50 +00:00
Laurent Cozic
897f53b13e All: Resolves #846: Set resource path to correct relative path so that for example images show up in Markdown viewers 2018-11-21 00:36:23 +00:00
Laurent Cozic
45cd8b7e3c Merge branch 'master' of github.com:laurent22/joplin 2018-11-20 23:19:07 +00:00
Laurent Cozic
922bbdd1b6 All: Fixes #968: Export resources specified with a title 2018-11-20 23:18:56 +00:00
rhtenhove
c24135577c Useless and error prone VERSION file removed & warn use of root (#989)
* Don't create unused VERSION file

It will throw an error if the script is run from a non-writable directory

* Warn user if running as root

This will write files as root:root

* Clearer root warning
2018-11-20 21:54:58 +00:00
Laurent Cozic
3240ff40bc Restored string to avoid invalidating all the translations. 2018-11-20 21:54:40 +00:00
Ben Fisher
58b68cab0c fix for #906, 1) windows paths like C:\a\b weren't accepted because b… (#935)
* fix for #906, 1) windows paths like C:\a\b weren't accepted because backslashes were treated as escape sequences, 2) common paths like C:\Program Files\Foo\Foo.exe weren't accepted because of the space in the path

* Using anothing approach,
a) backslashes are no longer treated as escape characters,
b) string change to remind people to add spaces

* Removing joplin.pot from the patch, it will be updated later.

* Removing unused code.
2018-11-20 21:46:18 +00:00
Helmut K. C. Tessarek
0a0afd7245 CLI v1.0.118 2018-11-20 15:38:32 -05:00
Laurent Cozic
de01606bff Update website 2018-11-20 19:09:47 +00:00
Laurent Cozic
046474b484 Android release v1.0.176 2018-11-20 00:49:27 +00:00
Laurent Cozic
277b2b9298 Electron release v1.0.116 2018-11-20 00:44:25 +00:00
Laurent Cozic
0b7296ae95 Update translations 2018-11-20 00:43:35 +00:00
Laurent Cozic
ce87dd55f0 Merge branch 'master' of github.com:laurent22/joplin 2018-11-20 00:42:33 +00:00
Laurent Cozic
07b724d65b All: Fixes #992: Allow non-ASCII chars when exporting MD and handle duplicate filenames 2018-11-20 00:42:21 +00:00
Caleb John
bc1984298f Add dark theme to note properties dialog (#991) 2018-11-19 22:48:10 +00:00
Ein Verne
9ed0bdfed2 Add more Chinese translation (#986) 2018-11-19 22:44:20 +00:00
Caleb John
57628e8986 Add missing syntax file for dark theme (#985) 2018-11-19 22:43:56 +00:00
Joybin Chen
fc8f53fd0e ReactNativeClient: A better NAV_BACK logic to change folder or tag. (#984) 2018-11-19 22:43:21 +00:00
Renato Rosa
efd7cc6a0c update-locale-pt_BR (#981)
Update missing strings and fixed flagged ones.
2018-11-19 22:40:21 +00:00
Abijeet Patro
7bfc3e1256 Fixes #979 (#980)
* Adds functionality to display tags under the open note.

Towards #469

Signed-off-by: Abijeet <abijeetpatro@gmail.com>

* Ensured tags in the dialog box and under the note appear in the same order.

Few formatting tweaks.

Signed-off-by: Abijeet <abijeetpatro@gmail.com>

* Fixes issues raised during code review.

Signed-off-by: Abijeet <abijeetpatro@gmail.com>

* Refactored code to always display tags in ascending order.

This changes the order of the tags in the dialog box and below the tag title.

Signed-off-by: Abijeet <abijeetpatro@gmail.com>

* Added the new tag height and margin bottom to the bottomRowHeight

Fixes #979

Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-11-17 11:21:57 +00:00
FleischKarussel
7f6ca1e527 fixed typo in Aktualsierungsdatum to Aktualisierungsdatum (#974) 2018-11-17 11:17:47 +00:00
Laurent Cozic
71d9b1d441 Electron: Fixes #933: Handle internal links from HTML and from MD 2018-11-16 18:39:11 +00:00
Laurent Cozic
a3d64d0a90 Update website 2018-11-16 16:53:37 +00:00
160 changed files with 3004 additions and 1229 deletions

View File

@@ -655,6 +655,10 @@ msgstr ""
msgid "Search in all the notes"
msgstr "Cerca a totes les notes"
#, fuzzy
msgid "Search in current note"
msgstr "Cerca a totes les notes"
msgid "View"
msgstr "Visualització"
@@ -1175,6 +1179,9 @@ msgstr ""
"No es pot actualitzar el testimoni: manquen les dades d'autenticació. Si "
"comenceu altre cop la sincronització, potser es corregeixi el problema."
msgid "Untitled"
msgstr "Sense títol"
msgid ""
"Could not synchronize with OneDrive.\n"
"\n"
@@ -1268,9 +1275,6 @@ msgstr "data d'actualització"
msgid "created date"
msgstr "data de creació"
msgid "Untitled"
msgstr "Sense títol"
msgid "This note does not have geolocation information."
msgstr "Aquesta nota no té informació de geolocalització."
@@ -1345,6 +1349,10 @@ msgstr ""
msgid "Global zoom percentage"
msgstr "Percentatge de zoom global"
#, fuzzy
msgid "Editor font size"
msgstr "Tipus de lletra de l'editor"
msgid "Editor font family"
msgstr "Tipus de lletra de l'editor"
@@ -1712,6 +1720,10 @@ msgstr ""
msgid "Unsupported image type: %s"
msgstr "Tipus d'imatge no admesa: %s"
#, fuzzy
msgid "Take photo"
msgstr "Adjunta una imatge"
msgid "Attach photo"
msgstr "Adjunta una imatge"

View File

@@ -637,6 +637,10 @@ msgstr ""
msgid "Search in all the notes"
msgstr "Hledat ve všech poznámkách"
#, fuzzy
msgid "Search in current note"
msgstr "Hledat ve všech poznámkách"
msgid "View"
msgstr "Zobrazit"
@@ -1145,6 +1149,9 @@ msgstr ""
"Nelze obnovit token: chybí autentizační data. Restart synchronizace může "
"tento problém vyřešit. "
msgid "Untitled"
msgstr "Bez názvu"
msgid ""
"Could not synchronize with OneDrive.\n"
"\n"
@@ -1242,9 +1249,6 @@ msgstr "Upraveno: %d."
msgid "created date"
msgstr "Vytvořeno: %d."
msgid "Untitled"
msgstr "Bez názvu"
msgid "This note does not have geolocation information."
msgstr "Tato poznámka nemá informace o poloze."
@@ -1320,6 +1324,10 @@ msgstr ""
msgid "Global zoom percentage"
msgstr "Globální zoom"
#, fuzzy
msgid "Editor font size"
msgstr "Rodina písma v editoru"
msgid "Editor font family"
msgstr "Rodina písma v editoru"
@@ -1681,6 +1689,10 @@ msgstr ""
msgid "Unsupported image type: %s"
msgstr "Nepodporovaný formát obrázku: %s"
#, fuzzy
msgid "Take photo"
msgstr "Přiložit obrázek"
msgid "Attach photo"
msgstr "Přiložit obrázek"

View File

@@ -642,6 +642,10 @@ msgstr ""
msgid "Search in all the notes"
msgstr "Søg i alle noter"
#, fuzzy
msgid "Search in current note"
msgstr "Søg i alle noter"
msgid "View"
msgstr "Vis"
@@ -1154,6 +1158,9 @@ msgstr ""
"Kan ikke opdatere token: Godkendelses data mangler. Prøv at starte "
"synkronisering igen, det kan løse problemet."
msgid "Untitled"
msgstr "Samlet"
msgid ""
"Could not synchronize with OneDrive.\n"
"\n"
@@ -1251,9 +1258,6 @@ msgstr "Opdateret %d."
msgid "created date"
msgstr "Oprettet: %d."
msgid "Untitled"
msgstr "Samlet"
msgid "This note does not have geolocation information."
msgstr "Denne note har ingen geolokations oplysninger."
@@ -1329,6 +1333,10 @@ msgstr ""
msgid "Global zoom percentage"
msgstr "Global zoom procent"
#, fuzzy
msgid "Editor font size"
msgstr "Rediger skrifttype"
msgid "Editor font family"
msgstr "Rediger skrifttype"
@@ -1690,6 +1698,10 @@ msgstr ""
msgid "Unsupported image type: %s"
msgstr "Ulovlig billedtype: %s"
#, fuzzy
msgid "Take photo"
msgstr "Vedhæft foto"
msgid "Attach photo"
msgstr "Vedhæft foto"

View File

@@ -663,6 +663,10 @@ msgstr "Im externen Editor bearbeiten"
msgid "Search in all the notes"
msgstr "Alle Notizen durchsuchen"
#, fuzzy
msgid "Search in current note"
msgstr "Alle Notizen durchsuchen"
msgid "View"
msgstr "Ansicht"
@@ -1186,6 +1190,9 @@ msgstr ""
"Kann Token nicht erneuern: Authentifikationsdaten nicht vorhanden. Ein "
"Neustart der Synchronisation könnte das Problem beheben."
msgid "Untitled"
msgstr "Unbenannt"
msgid ""
"Could not synchronize with OneDrive.\n"
"\n"
@@ -1275,14 +1282,11 @@ msgid "title"
msgstr "Titel"
msgid "updated date"
msgstr "Aktualsierungsdatum"
msgstr "Aktualisierungsdatum"
msgid "created date"
msgstr "Erstelldatum"
msgid "Untitled"
msgstr "Unbenannt"
msgid "This note does not have geolocation information."
msgstr "Diese Notiz hat keine Standort-Informationen."
@@ -1357,6 +1361,10 @@ msgstr "Starte die Anwendung minimiert im Tray"
msgid "Global zoom percentage"
msgstr "Zoomstufe der Benutzeroberfläche"
#, fuzzy
msgid "Editor font size"
msgstr "Editor Schriftenfamilie"
msgid "Editor font family"
msgstr "Editor Schriftenfamilie"
@@ -1738,6 +1746,10 @@ msgstr ""
msgid "Unsupported image type: %s"
msgstr "Nicht unterstütztes Fotoformat: %s"
#, fuzzy
msgid "Take photo"
msgstr "Foto anhängen"
msgid "Attach photo"
msgstr "Foto anhängen"

View File

@@ -573,6 +573,9 @@ msgstr ""
msgid "Search in all the notes"
msgstr ""
msgid "Search in current note"
msgstr ""
msgid "View"
msgstr ""
@@ -1060,6 +1063,9 @@ msgid ""
"synchronisation again may fix the problem."
msgstr ""
msgid "Untitled"
msgstr ""
msgid ""
"Could not synchronize with OneDrive.\n"
"\n"
@@ -1147,9 +1153,6 @@ msgstr ""
msgid "created date"
msgstr ""
msgid "Untitled"
msgstr ""
msgid "This note does not have geolocation information."
msgstr ""
@@ -1224,6 +1227,9 @@ msgstr ""
msgid "Global zoom percentage"
msgstr ""
msgid "Editor font size"
msgstr ""
msgid "Editor font family"
msgstr ""
@@ -1562,6 +1568,9 @@ msgstr ""
msgid "Unsupported image type: %s"
msgstr ""
msgid "Take photo"
msgstr ""
msgid "Attach photo"
msgstr ""

View File

@@ -651,6 +651,10 @@ msgstr "Editar con un editor externo"
msgid "Search in all the notes"
msgstr "Buscar en todas las notas"
#, fuzzy
msgid "Search in current note"
msgstr "Buscar en todas las notas"
msgid "View"
msgstr "Ver"
@@ -1167,6 +1171,9 @@ msgstr ""
"No se ha podido actualizar token: faltan datos de autenticación. Reiniciar "
"la sincronización podría solucionar el problema."
msgid "Untitled"
msgstr "Sin título"
msgid ""
"Could not synchronize with OneDrive.\n"
"\n"
@@ -1261,9 +1268,6 @@ msgstr "fecha de actualización"
msgid "created date"
msgstr "fecha de creación"
msgid "Untitled"
msgstr "Sin título"
msgid "This note does not have geolocation information."
msgstr "Esta nota no tiene informacion de geolocalización."
@@ -1338,6 +1342,10 @@ msgstr ""
msgid "Global zoom percentage"
msgstr "Establecer el porcentaje de aumento de la aplicación"
#, fuzzy
msgid "Editor font size"
msgstr "Fuente del editor"
msgid "Editor font family"
msgstr "Fuente del editor"
@@ -1710,6 +1718,10 @@ msgstr ""
msgid "Unsupported image type: %s"
msgstr "Tipo de imagen no soportado: %s"
#, fuzzy
msgid "Take photo"
msgstr "Adjuntar foto"
msgid "Attach photo"
msgstr "Adjuntar foto"

View File

@@ -650,6 +650,10 @@ msgstr ""
msgid "Search in all the notes"
msgstr "Bilatu ohar guztietan"
#, fuzzy
msgid "Search in current note"
msgstr "Bilatu ohar guztietan"
msgid "View"
msgstr ""
@@ -1170,6 +1174,9 @@ msgstr ""
"Tokena ezin eguneratu daiteke: egiaztatze-datuak desagertuta daude. Agian, "
"berriro sinkronizatzeak arazoa konpon lezake."
msgid "Untitled"
msgstr "Titulu gabekoa"
#, fuzzy
msgid ""
"Could not synchronize with OneDrive.\n"
@@ -1267,9 +1274,6 @@ msgstr "Eguneratuta: %d."
msgid "created date"
msgstr "Sortuta: %d."
msgid "Untitled"
msgstr "Titulu gabekoa"
msgid "This note does not have geolocation information."
msgstr "Ohar honek ez du geokokapen informaziorik."
@@ -1350,6 +1354,10 @@ msgstr ""
msgid "Global zoom percentage"
msgstr "Ezarri aplikazioaren zoomaren ehunekoa"
#, fuzzy
msgid "Editor font size"
msgstr "Oharra editatu."
msgid "Editor font family"
msgstr ""
@@ -1711,6 +1719,10 @@ msgstr ""
msgid "Unsupported image type: %s"
msgstr "Irudi formatua ez onartua: %s"
#, fuzzy
msgid "Take photo"
msgstr "Argazkia erantsi"
msgid "Attach photo"
msgstr "Argazkia erantsi"

View File

@@ -648,6 +648,9 @@ msgstr "Ouvrir dans un éditeur externe"
msgid "Search in all the notes"
msgstr "Chercher dans toutes les notes"
msgid "Search in current note"
msgstr "Chercher dans la note en cours"
msgid "View"
msgstr "Affichage"
@@ -1172,6 +1175,9 @@ msgstr ""
"Impossible de rafraîchir la connexion à OneDrive. Démarrez la "
"synchronisation à nouveau pour corriger le problème."
msgid "Untitled"
msgstr "Sans titre"
msgid ""
"Could not synchronize with OneDrive.\n"
"\n"
@@ -1265,9 +1271,6 @@ msgstr "date de modification"
msgid "created date"
msgstr "date de création"
msgid "Untitled"
msgstr "Sans titre"
msgid "This note does not have geolocation information."
msgstr "Cette note n'a pas d'information d'emplacement."
@@ -1345,6 +1348,9 @@ msgstr "Démarrer minimisé dans la zone de notification"
msgid "Global zoom percentage"
msgstr "Niveau de zoom"
msgid "Editor font size"
msgstr "Taille police éditeur"
msgid "Editor font family"
msgstr "Police de l'éditeur"
@@ -1722,6 +1728,9 @@ msgstr ""
msgid "Unsupported image type: %s"
msgstr "Type d'image non géré : %s"
msgid "Take photo"
msgstr "Prendre une photo"
msgid "Attach photo"
msgstr "Attacher une photo"

View File

@@ -642,6 +642,10 @@ msgstr ""
msgid "Search in all the notes"
msgstr "Buscar en todas as notas"
#, fuzzy
msgid "Search in current note"
msgstr "Buscar en todas as notas"
msgid "View"
msgstr "Vista"
@@ -1153,6 +1157,9 @@ msgstr ""
"Non é posíbel actualizar o «token»: faltan datos da autenticación. Iniciar a "
"sincronización de novo pode arranxar o problema."
msgid "Untitled"
msgstr "Sen título"
msgid ""
"Could not synchronize with OneDrive.\n"
"\n"
@@ -1250,9 +1257,6 @@ msgstr "Actualizado: %d."
msgid "created date"
msgstr "Creado: %d."
msgid "Untitled"
msgstr "Sen título"
msgid "This note does not have geolocation information."
msgstr "Esta nota non ten información de xeolocalización."
@@ -1328,6 +1332,10 @@ msgstr ""
msgid "Global zoom percentage"
msgstr "Porcentaxe de ampliación"
#, fuzzy
msgid "Editor font size"
msgstr "Familia de tipos de letra do editor"
msgid "Editor font family"
msgstr "Familia de tipos de letra do editor"
@@ -1689,6 +1697,10 @@ msgstr ""
msgid "Unsupported image type: %s"
msgstr "Tipo de imaxe incompatíbel: %s"
#, fuzzy
msgid "Take photo"
msgstr "Anexar foto"
msgid "Attach photo"
msgstr "Anexar foto"

View File

@@ -647,6 +647,10 @@ msgstr ""
msgid "Search in all the notes"
msgstr "Pretraži u svim bilješkama"
#, fuzzy
msgid "Search in current note"
msgstr "Pretraži u svim bilješkama"
msgid "View"
msgstr ""
@@ -1151,6 +1155,9 @@ msgid ""
"synchronisation again may fix the problem."
msgstr "Nedostaju podaci za ovjeru. Pokušaj ponovo započeti sinkronizaciju."
msgid "Untitled"
msgstr "Nenaslovljen"
msgid ""
"Could not synchronize with OneDrive.\n"
"\n"
@@ -1249,9 +1256,6 @@ msgstr "Ažurirano: %d."
msgid "created date"
msgstr "Stvoreno: %d."
msgid "Untitled"
msgstr "Nenaslovljen"
msgid "This note does not have geolocation information."
msgstr "Ova bilješka nema geolokacijske informacije."
@@ -1332,6 +1336,10 @@ msgstr ""
msgid "Global zoom percentage"
msgstr ""
#, fuzzy
msgid "Editor font size"
msgstr "Uredi bilješku."
msgid "Editor font family"
msgstr ""
@@ -1687,6 +1695,10 @@ msgstr ""
msgid "Unsupported image type: %s"
msgstr "Nepodržana vrsta slike: %s"
#, fuzzy
msgid "Take photo"
msgstr "Priloži sliku"
msgid "Attach photo"
msgstr "Priloži sliku"

View File

@@ -648,6 +648,10 @@ msgstr "Modifica in un editor esterno"
msgid "Search in all the notes"
msgstr "Cerca in tutte le note"
#, fuzzy
msgid "Search in current note"
msgstr "Cerca in tutte le note"
msgid "View"
msgstr "Vista"
@@ -1168,6 +1172,9 @@ msgstr ""
"Non è possibile aggiornare il token. mancano i dati di autenticazione. "
"Ricominciare la sincronizzazione da capo potrebbe risolvere il problema."
msgid "Untitled"
msgstr "Senza titolo"
msgid ""
"Could not synchronize with OneDrive.\n"
"\n"
@@ -1261,9 +1268,6 @@ msgstr "Data di aggiornamento"
msgid "created date"
msgstr "Data di creazione"
msgid "Untitled"
msgstr "Senza titolo"
msgid "This note does not have geolocation information."
msgstr "Questa nota non ha informazione sulla geolocalizzazione."
@@ -1338,6 +1342,10 @@ msgstr ""
msgid "Global zoom percentage"
msgstr "Percentuale di zoom globale"
#, fuzzy
msgid "Editor font size"
msgstr "Editor Famiglia Caratteri"
msgid "Editor font family"
msgstr "Editor Famiglia Caratteri"
@@ -1710,6 +1718,10 @@ msgstr ""
msgid "Unsupported image type: %s"
msgstr "Tipo di immagine non supportata: %s"
#, fuzzy
msgid "Take photo"
msgstr "Allega foto"
msgid "Attach photo"
msgstr "Allega foto"

View File

@@ -637,6 +637,10 @@ msgstr "外部エディターで編集"
msgid "Search in all the notes"
msgstr "すべてのノートを検索"
#, fuzzy
msgid "Search in current note"
msgstr "すべてのノートを検索"
msgid "View"
msgstr "表示"
@@ -1151,6 +1155,9 @@ msgstr ""
"トークンの更新ができませんでした。認証データがありません。同期を再度行うこと"
"で解決することがあります。"
msgid "Untitled"
msgstr "名称未設定"
msgid ""
"Could not synchronize with OneDrive.\n"
"\n"
@@ -1246,9 +1253,6 @@ msgstr "アップデート日"
msgid "created date"
msgstr "作成日"
msgid "Untitled"
msgstr "名称未設定"
msgid "This note does not have geolocation information."
msgstr "このノートには位置情報がありません。"
@@ -1323,6 +1327,10 @@ msgstr "アプリケーションをトレイアンコンで最小化して起動
msgid "Global zoom percentage"
msgstr "全体ズームの割合"
#, fuzzy
msgid "Editor font size"
msgstr "エディターのフォントファミリー"
msgid "Editor font family"
msgstr "エディターのフォントファミリー"
@@ -1690,6 +1698,10 @@ msgstr "Joplinモバイルアプリは次のタイプのリンクをまだサポ
msgid "Unsupported image type: %s"
msgstr "サポートされていない画像の形式: %s"
#, fuzzy
msgid "Take photo"
msgstr "写真を添付"
msgid "Attach photo"
msgstr "写真を添付"

View File

@@ -573,6 +573,9 @@ msgstr ""
msgid "Search in all the notes"
msgstr ""
msgid "Search in current note"
msgstr ""
msgid "View"
msgstr ""
@@ -1060,6 +1063,9 @@ msgid ""
"synchronisation again may fix the problem."
msgstr ""
msgid "Untitled"
msgstr ""
msgid ""
"Could not synchronize with OneDrive.\n"
"\n"
@@ -1147,9 +1153,6 @@ msgstr ""
msgid "created date"
msgstr ""
msgid "Untitled"
msgstr ""
msgid "This note does not have geolocation information."
msgstr ""
@@ -1224,6 +1227,9 @@ msgstr ""
msgid "Global zoom percentage"
msgstr ""
msgid "Editor font size"
msgstr ""
msgid "Editor font family"
msgstr ""
@@ -1562,6 +1568,9 @@ msgstr ""
msgid "Unsupported image type: %s"
msgstr ""
msgid "Take photo"
msgstr ""
msgid "Attach photo"
msgstr ""

View File

@@ -634,6 +634,10 @@ msgstr "외부 편집기에서 편집하기"
msgid "Search in all the notes"
msgstr "모든 노트에서 검색"
#, fuzzy
msgid "Search in current note"
msgstr "모든 노트에서 검색"
msgid "View"
msgstr "보기"
@@ -1145,6 +1149,9 @@ msgstr ""
"토큰 새로고침 불가: 인증 데이터를 찾을 수 없습니다. 동기화를 다시 시도하면 문"
"제가 해결될수도 있습니다."
msgid "Untitled"
msgstr "제목 없음"
msgid ""
"Could not synchronize with OneDrive.\n"
"\n"
@@ -1238,9 +1245,6 @@ msgstr "업데이트된 날짜"
msgid "created date"
msgstr "만들어진 날짜"
msgid "Untitled"
msgstr "제목 없음"
msgid "This note does not have geolocation information."
msgstr "이 노트는 지리적 위치 정보를 포함하고 있지 않습니다."
@@ -1315,6 +1319,10 @@ msgstr ""
msgid "Global zoom percentage"
msgstr "전체적 확대 비율"
#, fuzzy
msgid "Editor font size"
msgstr "편집기 글꼴 집합"
msgid "Editor font family"
msgstr "편집기 글꼴 집합"
@@ -1680,6 +1688,10 @@ msgstr "조플린 모바일 앱은 현재 해당 형식의 링크를 지원하
msgid "Unsupported image type: %s"
msgstr "지원하지 않는 이미지 형식: %s"
#, fuzzy
msgid "Take photo"
msgstr "사진 첨부"
msgid "Attach photo"
msgstr "사진 첨부"

File diff suppressed because it is too large Load Diff

View File

@@ -652,6 +652,10 @@ msgstr ""
msgid "Search in all the notes"
msgstr "Zoek in alle notities"
#, fuzzy
msgid "Search in current note"
msgstr "Zoek in alle notities"
msgid "View"
msgstr ""
@@ -1171,6 +1175,9 @@ msgstr ""
"Kan token niet vernieuwen: authenticatiedata ontbreekt. Herstarten van de "
"synchronisatie kan het probleem eventueel oplossen. "
msgid "Untitled"
msgstr "Untitled"
msgid ""
"Could not synchronize with OneDrive.\n"
"\n"
@@ -1270,9 +1277,6 @@ msgstr "Bijgewerkt: %d."
msgid "created date"
msgstr "Aangemaakt: %d."
msgid "Untitled"
msgstr "Untitled"
msgid "This note does not have geolocation information."
msgstr "Deze notitie bevat geen geo-locatie informatie."
@@ -1352,6 +1356,10 @@ msgstr ""
msgid "Global zoom percentage"
msgstr ""
#, fuzzy
msgid "Editor font size"
msgstr "Bewerk notitie."
msgid "Editor font family"
msgstr ""
@@ -1713,6 +1721,10 @@ msgstr ""
msgid "Unsupported image type: %s"
msgstr "Afbeeldingstype %s wordt niet ondersteund"
#, fuzzy
msgid "Take photo"
msgstr "Voeg foto toe"
msgid "Attach photo"
msgstr "Voeg foto toe"

View File

@@ -651,6 +651,10 @@ msgstr "Bewerken in externe bewerker"
msgid "Search in all the notes"
msgstr "Alle notities doorzoeken"
#, fuzzy
msgid "Search in current note"
msgstr "Alle notities doorzoeken"
msgid "View"
msgstr "Beeld"
@@ -1173,6 +1177,9 @@ msgstr ""
"Kan toegangssleutel niet verversen: de authenticatiegegevens ontbreken. "
"Probeer om de synchronisatie opnieuw te starten."
msgid "Untitled"
msgstr "Naamloos"
msgid ""
"Could not synchronize with OneDrive.\n"
"\n"
@@ -1266,9 +1273,6 @@ msgstr "bijgewerkt op"
msgid "created date"
msgstr "gecreëerd op"
msgid "Untitled"
msgstr "Naamloos"
msgid "This note does not have geolocation information."
msgstr "Deze notitie bevat geen locatie-informatie."
@@ -1343,6 +1347,10 @@ msgstr ""
msgid "Global zoom percentage"
msgstr "Globaal zoompercentage"
#, fuzzy
msgid "Editor font size"
msgstr "Lettertype van bewerker"
msgid "Editor font family"
msgstr "Lettertype van bewerker"
@@ -1716,6 +1724,10 @@ msgstr "De mobiele Joplin-app ondersteunt momenteel niet dit soort links: %s"
msgid "Unsupported image type: %s"
msgstr "Niet-ondersteunde afbeeldingssoort: %s"
#, fuzzy
msgid "Take photo"
msgstr "Foto bijvoegen"
msgid "Attach photo"
msgstr "Foto bijvoegen"

View File

@@ -1,7 +1,8 @@
# SOME DESCRIPTIVE TITLE.
# pt_BR locale strings - Joplin.
# Copyright (C) YEAR Laurent Cozic
# This file is distributed under the same license as the Joplin-CLI package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# Updated by Renato Xavier da Silveira Rosa <renatoxsr@gmail.com>, 2018.
#
msgid ""
msgstr ""
@@ -13,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.0.7\n"
"X-Generator: Poedit 2.2\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
msgid "To delete a tag, untag the associated notes."
@@ -147,7 +148,7 @@ msgid "Completed decryption."
msgstr "Decriptação completada."
msgid "Enabled"
msgstr "Desabilitado"
msgstr "Habilitado"
msgid "Disabled"
msgstr "Desabilitado"
@@ -455,12 +456,11 @@ msgid "Starting synchronisation..."
msgstr "Iniciando sincronização..."
msgid "Downloading resources..."
msgstr ""
msgstr "Baixando os recursos..."
msgid "Cancelling... Please wait."
msgstr "Cancelando... Aguarde."
#, fuzzy
msgid ""
"<tag-command> can be \"add\", \"remove\" or \"list\" to assign or remove "
"[tag] from [note], or to list the notes associated with [tag]. The command "
@@ -468,7 +468,8 @@ msgid ""
msgstr ""
"<tag-command> pode ser \"add\", \"remove\" ou \"list\" para atribuir ou "
"remover [tag] de [nota], ou para listar as notas associadas a [tag]. O "
"comando `taglist` pode ser usado para listar todas as tags."
"comando `tag list` pode ser usado para listar todas as tags (use -l para "
"opção longa)."
#, javascript-format
msgid "Invalid command: \"%s\""
@@ -628,9 +629,8 @@ msgstr "Cortar"
msgid "Paste"
msgstr "Colar"
#, fuzzy
msgid "Select all"
msgstr "Selecionar data"
msgstr "Selecionar tudo"
msgid "Bold"
msgstr "Negrito"
@@ -647,6 +647,10 @@ msgstr "Editar com editor externo"
msgid "Search in all the notes"
msgstr "Pesquisar em todas as notas"
#, fuzzy
msgid "Search in current note"
msgstr "Pesquisar em todas as notas"
msgid "View"
msgstr "Visualizar"
@@ -716,7 +720,7 @@ msgid "No"
msgstr "Não"
msgid "Token has been copied to the clipboard!"
msgstr ""
msgstr "Token foi copiado para a \\u00e1rea de transfer\\u00eancia!"
msgid "The web clipper service is enabled and set to auto-start."
msgstr ""
@@ -728,7 +732,7 @@ msgstr "Status: Iniciado, na porta %d"
#, javascript-format
msgid "Status: %s"
msgstr "Status: \"%s\"."
msgstr "Status: %s"
msgid "Disable Web Clipper Service"
msgstr "Desabilitar serviço Web Clipper"
@@ -767,20 +771,21 @@ msgstr "Passo 2: Instalar a extensão"
msgid "Download and install the relevant extension for your browser:"
msgstr "Baixe e instale a extensão relevante para seu browser:"
#, fuzzy
msgid "Advanced options"
msgstr "Mostrar opções avançadas"
msgid "Authorisation token:"
msgstr ""
msgstr "Token de autoriza\\u00e7\\u00e3o:"
msgid "Copy token"
msgstr ""
msgstr "Copira token"
msgid ""
"This authorisation token is only needed to allow third-party applications to "
"access Joplin."
msgstr ""
"Esse token de autoriza\\u00e7\\u00e3o só é necess\\u00e1rio para permitir "
"que aplicativos de terceiros acessem o Joplin."
msgid "Check synchronisation configuration"
msgstr "Verificar a configuração da sincronização"
@@ -946,13 +951,11 @@ msgstr "%s - Copiar"
msgid "Switch between note and to-do type"
msgstr "Alternar entre os tipos Nota e Tarefa"
#, fuzzy
msgid "Switch to note type"
msgstr "Alternar entre os tipos Nota e Tarefa"
msgstr "Alternar para o tipo Nota"
#, fuzzy
msgid "Switch to to-do type"
msgstr "Alternar entre os tipos Nota e Tarefa"
msgstr "Alternar para o tipo Tarefa"
msgid "Copy Markdown link"
msgstr "Copiar link de Markdown"
@@ -971,13 +974,13 @@ msgid ""
msgstr "Atualmente, não há cadernos. Crie um, clicando em \"Novo caderno\"."
msgid "Location"
msgstr ""
msgstr "Localização"
msgid "URL"
msgstr ""
msgstr "URL"
msgid "Note properties"
msgstr ""
msgstr "Propriedades da nota"
msgid "Open..."
msgstr "Abrir..."
@@ -996,7 +999,7 @@ msgid "Copy Link Address"
msgstr "Copiar endereço do link"
msgid "This attachment is not downloaded or not decrypted yet."
msgstr ""
msgstr "O anexo ainda não foi baixado ou decriptado."
#, javascript-format
msgid "Unsupported link or message: %s"
@@ -1060,7 +1063,7 @@ msgid "Click to stop external editing"
msgstr "Clique para encerrar edição externa"
msgid "Watching..."
msgstr "Verificando..."
msgstr "Observando alterações..."
msgid "to-do"
msgstr "tarefa"
@@ -1113,11 +1116,11 @@ msgstr "Cadernos"
#, javascript-format
msgid "Decrypting items: %d/%d"
msgstr "Decriptando itens: %d/%d."
msgstr "Decriptando itens: %d/%d"
#, fuzzy, javascript-format
#, javascript-format
msgid "Fetching resources: %d"
msgstr "Recursos: %d."
msgstr "Buscando recursos: %d"
msgid "Please select where the sync status should be exported to"
msgstr ""
@@ -1165,6 +1168,9 @@ msgstr ""
"Não é possível atualizar token: faltam dados de autenticação. Iniciar a "
"sincronização novamente pode corrigir o problema."
msgid "Untitled"
msgstr "Sem título"
msgid ""
"Could not synchronize with OneDrive.\n"
"\n"
@@ -1259,9 +1265,6 @@ msgstr "data de ataualização"
msgid "created date"
msgstr "data de criação"
msgid "Untitled"
msgstr "Sem título"
msgid "This note does not have geolocation information."
msgstr "Esta nota não possui informações de geolocalização."
@@ -1301,7 +1304,7 @@ msgid "Sort notes by"
msgstr "Ordenar notas por"
msgid "Reverse sort order"
msgstr "Inverter ordem de classificação."
msgstr "Inverter ordem de classificação"
msgid "Save geo-location with notes"
msgstr "Salvar geolocalização com notas"
@@ -1322,20 +1325,27 @@ msgid "Show tray icon"
msgstr "Exibir tray icon"
msgid "Note: Does not work in all desktop environments."
msgstr "Nota: não funciona em todos os ambientes de desktop"
msgstr "Nota: não funciona em todos os ambientes de desktop."
msgid ""
"This will allow Joplin to run in the background. It is recommended to enable "
"this setting so that your notes are constantly being synchronised, thus "
"reducing the number of conflicts."
msgstr ""
"Isso irá permitir que o Joplin continue sendo executado em segundo plano. É "
"recomendado que você habilita essa configuração para que suas notas "
"sejamconstantemente sincronizadas, de modo a reduzir as chances de conflitos."
msgid "Start application minimised in the tray icon"
msgstr ""
msgstr "Iniciar aplicativo minimizado na barra de tarefas"
msgid "Global zoom percentage"
msgstr "Porcentagem global do zoom"
#, fuzzy
msgid "Editor font size"
msgstr "Família de fontes do editor"
msgid "Editor font family"
msgstr "Família de fontes do editor"
@@ -1407,6 +1417,9 @@ msgid ""
"to it before syncing, otherwise all files will be removed! See the FAQ for "
"more details: %s"
msgstr ""
"Atenção: Se você modificar esse local, tenha certeza de copiar todo o seu "
"conteúdo para lá antes de sincronizar, do contrário todos os seus arquivos "
"serão removidos! Veja o FAQ para mais detalhes: %s"
msgid "Nextcloud username"
msgstr "Usuário da Nextcloud"
@@ -1436,7 +1449,7 @@ msgstr ""
"os certificados, ou caminhos para arquivos cert. Por exemplo, /my/cert_dir, /"
"other/custom.pem. Note que se você fizer mudanças nas configurações de TLS, "
"você tem que salvar as mudanças antes de clicar em \"Verificar a "
"configuração da sincronização\""
"configuração da sincronização\"."
msgid "Ignore TLS certificate errors"
msgstr "Ignorar erros de certificados TLS"
@@ -1447,7 +1460,7 @@ msgstr "Valor da opção inválida: \"%s\". Os valores possíveis são: %s."
#, javascript-format
msgid "The tag \"%s\" already exists. Please choose a different name."
msgstr ""
msgstr "A tag \"%s\" já existe. Escolha um nome diferente."
msgid "Joplin Export File"
msgstr "Arquivo de Exportação do Joplin"
@@ -1461,12 +1474,11 @@ msgstr "Diretório de Exportação do Joplin"
msgid "Evernote Export File"
msgstr "Arquivo de Exportação do Evernote"
#, fuzzy
msgid "Json Export Directory"
msgstr "Diretório de Exportação do Joplin"
msgstr "Diretório de Exportação JSON"
msgid "Directory"
msgstr "DIretório"
msgstr "Diretório"
#, javascript-format
msgid "Cannot load \"%s\" module for format \"%s\""
@@ -1541,10 +1553,10 @@ msgid "On %s: %s"
msgstr "Em %s: %s"
msgid "Permission to use camera"
msgstr ""
msgstr "Permissão para utilizar sua c\\u00e2mera"
msgid "Your permission to use your camera is required."
msgstr ""
msgstr "É necessária a sua permissão para utilizar sua c\\u00e2mera."
msgid "There are currently no notes. Create one by clicking on the (+) button."
msgstr "Atualmente, não há notas. Crie uma, clicando no botão (+)."
@@ -1574,9 +1586,8 @@ msgstr "Mover %d notas para o caderno \"%s\"?"
msgid "Press to set the decryption password."
msgstr "Pressione para configurar a senha de decriptação."
#, fuzzy
msgid "Clear alarm"
msgstr "Definir alarme"
msgstr "Limpar alarme"
msgid "Save alarm"
msgstr "Salvar alarme"
@@ -1590,22 +1601,22 @@ msgstr "Confirmar"
msgid "Cancel synchronisation"
msgstr "Cancelar sincronização"
#, fuzzy
msgid "Checking... Please wait."
msgstr "Cancelando... Aguarde."
msgstr "Verificando... Por favor aguarde."
#, fuzzy
msgid "Success! Synchronisation configuration appears to be correct."
msgstr "Verificar a configuração da sincronização"
msgstr "Sucesso! A configuração da sincronização parece estar correta."
msgid ""
"Error. Please check that URL, username, password, etc. are correct and that "
"the sync target is accessible. The reported error was:"
msgstr ""
"Erro. Verifique se a URL, nome de usuário, senha, etc., estão corretos e "
"tenha certeza que o destino da sincronização está acessível. O erro "
"reportado foi:"
#, fuzzy
msgid "The application has been authorised!"
msgstr "O aplicativo foi autorizado com sucesso."
msgstr "O aplicativo foi autorizado!"
#, javascript-format
msgid ""
@@ -1615,10 +1626,15 @@ msgid ""
"\n"
"Please try again."
msgstr ""
"Não foi possível autorizar o aplicativo:\n"
"\n"
"%s\n"
"\n"
"Por favor tente novamente."
#, fuzzy, javascript-format
#, javascript-format
msgid "Decrypted items: %s / %s"
msgstr "Decriptando itens: %d/%d."
msgstr "Itens decriptados: %s / %s"
msgid "New tags:"
msgstr "Novas tags:"
@@ -1638,8 +1654,8 @@ msgid ""
"- Storage: to allow attaching files to notes and to enable filesystem "
"synchronisation."
msgstr ""
"- Armazenamento: para permitir anexar arquivos a notas, e para permitir a "
"sincronização do sistema de arquivos "
"- Armazenamento: para permitir anexar arquivos a notas e para permitir a "
"sincronização do sistema de arquivos."
msgid "- Camera: to allow taking a picture and attaching it to a note."
msgstr "- Câmera: para permitir tirar fotos e anexar a uma nota."
@@ -1706,6 +1722,10 @@ msgstr "O app mobile do Joplin não suporta, atualmente, esse tipo de link: %s"
msgid "Unsupported image type: %s"
msgstr "Tipo de imagem não suportada: %s"
#, fuzzy
msgid "Take photo"
msgstr "Anexar foto"
msgid "Attach photo"
msgstr "Anexar foto"
@@ -1731,7 +1751,7 @@ msgid "View on map"
msgstr "Ver no mapa"
msgid "Go to source URL"
msgstr ""
msgstr "Ir para a URL de origem"
msgid "Delete notebook"
msgstr "Excluir caderno"

View File

@@ -587,6 +587,10 @@ msgstr "Editați într-un editor extern"
msgid "Search in all the notes"
msgstr "Căutați în toate notițele"
#, fuzzy
msgid "Search in current note"
msgstr "Căutați în toate notițele"
msgid "View"
msgstr "Vizualizați"
@@ -1077,6 +1081,9 @@ msgid ""
"synchronisation again may fix the problem."
msgstr ""
msgid "Untitled"
msgstr "Fără denumire"
msgid ""
"Could not synchronize with OneDrive.\n"
"\n"
@@ -1164,9 +1171,6 @@ msgstr "data actualizării"
msgid "created date"
msgstr "data creării"
msgid "Untitled"
msgstr "Fără denumire"
msgid "This note does not have geolocation information."
msgstr ""
@@ -1241,6 +1245,10 @@ msgstr ""
msgid "Global zoom percentage"
msgstr ""
#, fuzzy
msgid "Editor font size"
msgstr "Editează notiță."
msgid "Editor font family"
msgstr ""
@@ -1581,6 +1589,10 @@ msgstr ""
msgid "Unsupported image type: %s"
msgstr ""
#, fuzzy
msgid "Take photo"
msgstr "Atașează imagine"
msgid "Attach photo"
msgstr "Atașează imagine"

View File

@@ -649,6 +649,10 @@ msgstr ""
msgid "Search in all the notes"
msgstr "Поиск во всех заметках"
#, fuzzy
msgid "Search in current note"
msgstr "Поиск во всех заметках"
msgid "View"
msgstr "Вид"
@@ -1163,6 +1167,9 @@ msgstr ""
"Не удалось обновить токен: отсутствуют данные аутентификации. Повторный "
"запуск синхронизации может решить проблему."
msgid "Untitled"
msgstr "Без имени"
msgid ""
"Could not synchronize with OneDrive.\n"
"\n"
@@ -1260,9 +1267,6 @@ msgstr "Обновлено: %d."
msgid "created date"
msgstr "Создано: %d."
msgid "Untitled"
msgstr "Без имени"
msgid "This note does not have geolocation information."
msgstr "Эта заметка не содержит информации о геолокации."
@@ -1338,6 +1342,10 @@ msgstr ""
msgid "Global zoom percentage"
msgstr "Глобальный масштаб в процентах"
#, fuzzy
msgid "Editor font size"
msgstr "Семейство шрифтов редактора"
msgid "Editor font family"
msgstr "Семейство шрифтов редактора"
@@ -1700,6 +1708,10 @@ msgstr ""
msgid "Unsupported image type: %s"
msgstr "Неподдерживаемый формат изображения: %s"
#, fuzzy
msgid "Take photo"
msgstr "Прикрепить фото"
msgid "Attach photo"
msgstr "Прикрепить фото"

View File

@@ -648,6 +648,10 @@ msgstr ""
msgid "Search in all the notes"
msgstr "Išči znotraj vseh zabeležk"
#, fuzzy
msgid "Search in current note"
msgstr "Išči znotraj vseh zabeležk"
msgid "View"
msgstr "Pogled"
@@ -1167,6 +1171,9 @@ msgstr ""
"Ne gre osvežiti tokena: manjkajo podatki o avtentikaciji. Ponovno zaženite "
"sinhronizacijo, da morda popravite težavo."
msgid "Untitled"
msgstr "Neimenovano"
msgid ""
"Could not synchronize with OneDrive.\n"
"\n"
@@ -1264,9 +1271,6 @@ msgstr "Posodobljeno: %d."
msgid "created date"
msgstr "Ustvarjeno: %d."
msgid "Untitled"
msgstr "Neimenovano"
msgid "This note does not have geolocation information."
msgstr "Ta zabeležke nima geografske lokacije."
@@ -1342,6 +1346,10 @@ msgstr ""
msgid "Global zoom percentage"
msgstr "Celokupen procent povečave"
#, fuzzy
msgid "Editor font size"
msgstr "Družina urejevalnika besedilnega stila"
msgid "Editor font family"
msgstr "Družina urejevalnika besedilnega stila"
@@ -1703,6 +1711,10 @@ msgstr ""
msgid "Unsupported image type: %s"
msgstr "Nepodprt tip slike: %s"
#, fuzzy
msgid "Take photo"
msgstr "Pripni fotografijo"
msgid "Attach photo"
msgstr "Pripni fotografijo"

View File

@@ -655,6 +655,10 @@ msgstr "Redigera i extern redigerare"
msgid "Search in all the notes"
msgstr "Sök i alla anteckningarna"
#, fuzzy
msgid "Search in current note"
msgstr "Sök i alla anteckningarna"
msgid "View"
msgstr "Visa"
@@ -1174,6 +1178,9 @@ msgstr ""
"Kan inte uppdatera token: autentiseringsdata saknas. Om du startar "
"synkroniseringen igen kan det lösa problemet."
msgid "Untitled"
msgstr "Utan titel"
msgid ""
"Could not synchronize with OneDrive.\n"
"\n"
@@ -1268,9 +1275,6 @@ msgstr "uppdaterad datum"
msgid "created date"
msgstr "Skapad datum"
msgid "Untitled"
msgstr "Utan titel"
msgid "This note does not have geolocation information."
msgstr "Denna anteckning har inte geolokaliseringsinformation."
@@ -1345,6 +1349,10 @@ msgstr ""
msgid "Global zoom percentage"
msgstr "Global zoomprocent"
#, fuzzy
msgid "Editor font size"
msgstr "Redigerarens typsnittsfamilj"
msgid "Editor font family"
msgstr "Redigerarens typsnittsfamilj"
@@ -1719,6 +1727,10 @@ msgstr ""
msgid "Unsupported image type: %s"
msgstr "Bildstorlek som inte stöds: %s"
#, fuzzy
msgid "Take photo"
msgstr "Bifoga foto"
msgid "Attach photo"
msgstr "Bifoga foto"

View File

@@ -605,7 +605,7 @@ msgid "Italic"
msgstr "斜体"
msgid "Insert Date Time"
msgstr ""
msgstr "插入时间"
#, fuzzy
msgid "Edit in external editor"
@@ -614,6 +614,10 @@ msgstr "在外部编辑器中打开"
msgid "Search in all the notes"
msgstr "在所有笔记内搜索"
#, fuzzy
msgid "Search in current note"
msgstr "在所有笔记内搜索"
msgid "View"
msgstr "显示"
@@ -683,7 +687,7 @@ msgid "No"
msgstr "否"
msgid "Token has been copied to the clipboard!"
msgstr ""
msgstr "Token 已被复制到粘贴板"
msgid "The web clipper service is enabled and set to auto-start."
msgstr "网页剪辑服务已启用并设置为自动启动。"
@@ -735,10 +739,10 @@ msgid "Advanced options"
msgstr "显示高级选项"
msgid "Authorisation token:"
msgstr ""
msgstr "授权码:"
msgid "Copy token"
msgstr ""
msgstr "拷贝 token"
msgid ""
"This authorisation token is only needed to allow third-party applications to "
@@ -753,7 +757,7 @@ msgid "Notes and settings are stored in: %s"
msgstr "笔记与设置文件储存目录为:%s"
msgid "Apply"
msgstr ""
msgstr "应用"
msgid "Submit"
msgstr "提交"
@@ -927,13 +931,13 @@ msgid ""
msgstr "此处没有任何笔记本。点击\"新笔记本\"创建。"
msgid "Location"
msgstr ""
msgstr "位置"
msgid "URL"
msgstr ""
msgid "Note properties"
msgstr ""
msgstr "笔记属性"
msgid "Open..."
msgstr "打开…"
@@ -949,10 +953,10 @@ msgid "Copy path to clipboard"
msgstr "复制路径到剪切板"
msgid "Copy Link Address"
msgstr ""
msgstr "拷贝链接地址"
msgid "This attachment is not downloaded or not decrypted yet."
msgstr ""
msgstr "该附件没有下载或者没有解密"
#, javascript-format
msgid "Unsupported link or message: %s"
@@ -1115,6 +1119,9 @@ msgid ""
"synchronisation again may fix the problem."
msgstr "无法刷新令牌:缺失认证数据。重新开始同步可能会修复此错误。"
msgid "Untitled"
msgstr "无标题"
msgid ""
"Could not synchronize with OneDrive.\n"
"\n"
@@ -1208,9 +1215,6 @@ msgstr "更新日期"
msgid "created date"
msgstr "创建日期"
msgid "Untitled"
msgstr "无标题"
msgid "This note does not have geolocation information."
msgstr "此笔记不包含地理定位信息。"
@@ -1278,6 +1282,8 @@ msgid ""
"this setting so that your notes are constantly being synchronised, thus "
"reducing the number of conflicts."
msgstr ""
"该选项允许 Joplin 在后台运行,如果你的笔记时常发生变化,推荐启用该设置来减少"
"可能的冲突"
msgid "Start application minimised in the tray icon"
msgstr "启动应用程序时在托盘中最小化"
@@ -1285,6 +1291,10 @@ msgstr "启动应用程序时在托盘中最小化"
msgid "Global zoom percentage"
msgstr "全局缩放比例"
#, fuzzy
msgid "Editor font size"
msgstr "编辑器字体"
msgid "Editor font family"
msgstr "编辑器字体"
@@ -1483,10 +1493,10 @@ msgid "On %s: %s"
msgstr "%s:%s"
msgid "Permission to use camera"
msgstr ""
msgstr "使用摄像头的权限"
msgid "Your permission to use your camera is required."
msgstr ""
msgstr "使用摄像头的权限是必须的"
msgid "There are currently no notes. Create one by clicking on the (+) button."
msgstr "当前没有任何笔记。点击(+)按钮创建。"
@@ -1544,6 +1554,8 @@ msgid ""
"Error. Please check that URL, username, password, etc. are correct and that "
"the sync target is accessible. The reported error was:"
msgstr ""
"发生错误。请检查 URL,用户名,密码等等是否正确并且确保同步目的地可被访问。报"
"告的错误如下:"
#, fuzzy
msgid "The application has been authorised!"
@@ -1557,6 +1569,11 @@ msgid ""
"\n"
"Please try again."
msgstr ""
"无法授权应用:\n"
"\n"
"%s\n"
"\n"
"请重新尝试"
#, fuzzy, javascript-format
msgid "Decrypted items: %s / %s"
@@ -1643,6 +1660,10 @@ msgstr "Joplin 手机应用目前不支持这种类型的链接:%s"
msgid "Unsupported image type: %s"
msgstr "不支持的图片格式:%s"
#, fuzzy
msgid "Take photo"
msgstr "附加照片"
msgid "Attach photo"
msgstr "附加照片"
@@ -1668,7 +1689,7 @@ msgid "View on map"
msgstr "查看地图"
msgid "Go to source URL"
msgstr ""
msgstr "定位到源 URL"
msgid "Delete notebook"
msgstr "删除笔记本"

View File

@@ -614,6 +614,10 @@ msgstr "使用外部編輯器編輯"
msgid "Search in all the notes"
msgstr "在所有記事中搜尋"
#, fuzzy
msgid "Search in current note"
msgstr "在所有記事中搜尋"
msgid "View"
msgstr "檢視"
@@ -1115,6 +1119,9 @@ msgid ""
"synchronisation again may fix the problem."
msgstr "無法刷新 token: 缺少身份驗證資料。再次啟動同步可能會解決此問題。"
msgid "Untitled"
msgstr "未命名"
msgid ""
"Could not synchronize with OneDrive.\n"
"\n"
@@ -1207,9 +1214,6 @@ msgstr "更新日期"
msgid "created date"
msgstr "建立日期"
msgid "Untitled"
msgstr "未命名"
msgid "This note does not have geolocation information."
msgstr "此記事沒有地理位置定位資訊。"
@@ -1284,6 +1288,10 @@ msgstr ""
msgid "Global zoom percentage"
msgstr "整體縮放比例 (%)"
#, fuzzy
msgid "Editor font size"
msgstr "編輯器字型系列"
msgid "Editor font family"
msgstr "編輯器字型系列"
@@ -1640,6 +1648,10 @@ msgstr "Joplin 移動應用程式暫時不支援此類型的連結: %s"
msgid "Unsupported image type: %s"
msgstr "不支援的圖像類型: %s"
#, fuzzy
msgid "Take photo"
msgstr "附加相片"
msgid "Attach photo"
msgstr "附加相片"

View File

@@ -1,6 +1,6 @@
{
"name": "joplin",
"version": "1.0.117",
"version": "1.0.118",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -2890,11 +2890,6 @@
"resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz",
"integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo="
},
"unidecode": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/unidecode/-/unidecode-0.1.8.tgz",
"integrity": "sha1-77swFTi8RSRqmsjFWdcvAVMFBT4="
},
"uniq": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz",

View File

@@ -19,7 +19,7 @@
],
"owner": "Laurent Cozic"
},
"version": "1.0.117",
"version": "1.0.118",
"bin": {
"joplin": "./main.js"
},
@@ -68,7 +68,6 @@
"tar": "^4.4.0",
"tcp-port-used": "^0.1.2",
"tkwidgets": "^0.5.26",
"unidecode": "^0.1.8",
"url-parse": "^1.2.0",
"uuid": "^3.0.1",
"valid-url": "^1.0.9",

View File

@@ -30,6 +30,7 @@ npm test tests-build/models_Folder.js
npm test tests-build/models_Note.js
npm test tests-build/models_Tag.js
npm test tests-build/models_Setting.js
npm test tests-build/pathUtils.js
npm test tests-build/services_InteropService.js
npm test tests-build/services_ResourceService.js
npm test tests-build/urlUtils.js

View File

@@ -5,6 +5,7 @@ const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const BaseModel = require('lib/BaseModel.js');
const ArrayUtils = require('lib/ArrayUtils.js');
const { shim } = require('lib/shim');
process.on('unhandledRejection', (reason, p) => {
@@ -35,13 +36,36 @@ describe('models_Note', function() {
expect(items[0].type_).toBe(BaseModel.TYPE_NOTE);
expect(items[1].type_).toBe(BaseModel.TYPE_RESOURCE);
const resource = items[1];
note2.body += '<img alt="bla" src=":/' + resource.id + '"/>';
note2.body += '<img src=\':/' + resource.id + '\' />';
const resource2 = await shim.createResourceFromPath(__dirname + '/../tests/support/photo.jpg');
const resource3 = await shim.createResourceFromPath(__dirname + '/../tests/support/photo.jpg');
note2.body += '<img alt="bla" src=":/' + resource2.id + '"/>';
note2.body += '<img src=\':/' + resource3.id + '\' />';
items = await Note.linkedItems(note2.body);
expect(items.length).toBe(4);
}));
it('should find linked items', asyncTest(async () => {
const testCases = [
['[](:/06894e83b8f84d3d8cbe0f1587f9e226)', ['06894e83b8f84d3d8cbe0f1587f9e226']],
['[](:/06894e83b8f84d3d8cbe0f1587f9e226) [](:/06894e83b8f84d3d8cbe0f1587f9e226)', ['06894e83b8f84d3d8cbe0f1587f9e226']],
['[](:/06894e83b8f84d3d8cbe0f1587f9e226) [](:/06894e83b8f84d3d8cbe0f1587f9e227)', ['06894e83b8f84d3d8cbe0f1587f9e226', '06894e83b8f84d3d8cbe0f1587f9e227']],
['[](:/06894e83b8f84d3d8cbe0f1587f9e226 "some title")', ['06894e83b8f84d3d8cbe0f1587f9e226']],
];
for (let i = 0; i < testCases.length; i++) {
const t = testCases[i];
const input = t[0];
const expected = t[1];
const actual = Note.linkedItemIds(input);
const contentEquals = ArrayUtils.contentEquals(actual, expected);
// console.info(contentEquals, input, expected, actual);
expect(contentEquals).toBe(true);
}
}));
it('should change the type of notes', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });

View File

@@ -0,0 +1,39 @@
require('app-module-path').addPath(__dirname);
const { friendlySafeFilename } = require('lib/path-utils.js');
const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
describe('pathUtils', function() {
beforeEach(async (done) => {
done();
});
it('should create friendly safe filename', async (done) => {
const testCases = [
['生活', '生活'],
['not/good', 'not_good'],
['really/not/good', 'really_not_good'],
['con', '___'],
['no space at the end ', 'no space at the end'],
['nor dots...', 'nor dots'],
[' no space before either', 'no space before either'],
['thatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylong', 'thatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylong'],
];
for (let i = 0; i < testCases.length; i++) {
const t = testCases[i];
expect(friendlySafeFilename(t[0])).toBe(t[1]);
}
expect(!!friendlySafeFilename('')).toBe(true);
expect(!!friendlySafeFilename('...')).toBe(true);
done();
});
});

View File

@@ -331,4 +331,30 @@ describe('services_InteropService', function() {
expect(obj.body).toBe(items[i].body);
}
}));
it('should export MD with unicode filenames', asyncTest(async () => {
const service = new InteropService();
let folder1 = await Folder.save({ title: 'folder1' });
let folder2 = await Folder.save({ title: 'ジョプリン' });
let note1 = await Note.save({ title: '生活', parent_id: folder1.id });
let note2 = await Note.save({ title: '生活', parent_id: folder1.id });
let note2b = await Note.save({ title: '生活', parent_id: folder1.id });
let note3 = await Note.save({ title: '', parent_id: folder1.id });
let note4 = await Note.save({ title: '', parent_id: folder1.id });
let note5 = await Note.save({ title: 'salut, ça roule ?', parent_id: folder1.id });
let note6 = await Note.save({ title: 'ジョプリン', parent_id: folder2.id });
const outDir = exportDir();
await service.export({ path: outDir, format: 'md' });
expect(await shim.fsDriver().exists(outDir + '/folder1/生活.md')).toBe(true);
expect(await shim.fsDriver().exists(outDir + '/folder1/生活 (1).md')).toBe(true);
expect(await shim.fsDriver().exists(outDir + '/folder1/生活 (2).md')).toBe(true);
expect(await shim.fsDriver().exists(outDir + '/folder1/Untitled.md')).toBe(true);
expect(await shim.fsDriver().exists(outDir + '/folder1/Untitled (1).md')).toBe(true);
expect(await shim.fsDriver().exists(outDir + '/folder1/salut, ça roule _.md')).toBe(true);
expect(await shim.fsDriver().exists(outDir + '/ジョプリン/ジョプリン.md')).toBe(true);
}));
});

View File

@@ -8,6 +8,7 @@ const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');
const NoteTag = require('lib/models/NoteTag.js');
const Resource = require('lib/models/Resource.js');
const ItemChange = require('lib/models/ItemChange.js');
const NoteResource = require('lib/models/NoteResource.js');
const ResourceService = require('lib/services/ResourceService.js');
const fs = require('fs-extra');
@@ -124,4 +125,25 @@ describe('services_ResourceService', function() {
expect(!!(await Resource.load(resource1.id))).toBe(true);
}));
it('should not process twice the same change', asyncTest(async () => {
const service = new ResourceService();
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
note1 = await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
let resource1 = (await Resource.all())[0];
await service.indexNoteResources();
const before = (await NoteResource.all())[0];
await time.sleep(0.1);
await service.indexNoteResources();
const after = (await NoteResource.all())[0];
expect(before.last_seen_time).toBe(after.last_seen_time);
}));
});

View File

@@ -0,0 +1,188 @@
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const SearchEngine = require('lib/services/SearchEngine');
const Note = require('lib/models/Note');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
let engine = null;
describe('services_SearchEngine', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
engine = new SearchEngine();
engine.setDb(db());
done();
});
it('should keep the content and FTS table in sync', async (done) => {
let rows, n1, n2, n3;
n1 = await Note.save({ title: "a" });
n2 = await Note.save({ title: "b" });
rows = await engine.search('a');
expect(rows.length).toBe(1);
expect(rows[0].title).toBe('a');
await Note.delete(n1.id);
rows = await engine.search('a');
expect(rows.length).toBe(0);
rows = await engine.search('b');
expect(rows[0].title).toBe('b');
await Note.save({ id: n2.id, title: 'c' });
rows = await engine.search('b');
expect(rows.length).toBe(0);
rows = await engine.search('c');
expect(rows[0].title).toBe('c');
await Note.save({ id: n2.id, encryption_applied: 1 });
rows = await engine.search('c');
expect(rows.length).toBe(0);
await Note.save({ id: n2.id, encryption_applied: 0 });
rows = await engine.search('c');
expect(rows.length).toBe(1);
done();
});
it('should order search results by relevance (1)', async (done) => {
const n1 = await Note.save({ title: "abcd efgh" }); // 3
const n2 = await Note.save({ title: "abcd aaaaa abcd abcd" }); // 1
const n3 = await Note.save({ title: "abcd aaaaa bbbb eeee abcd" }); // 2
const rows = await engine.search('abcd');
expect(rows[0].id).toBe(n2.id);
expect(rows[1].id).toBe(n3.id);
expect(rows[2].id).toBe(n1.id);
done();
});
it('should order search results by relevance (2)', async (done) => {
// 1
const n1 = await Note.save({ title: "abcd efgh", body: "XX abcd XX efgh" });
// 4
const n2 = await Note.save({ title: "abcd aaaaa bbbb eeee efgh" });
// 3
const n3 = await Note.save({ title: "abcd aaaaa efgh" });
// 2
const n4 = await Note.save({ title: "blablablabla blabla bla abcd X efgh" });
// 5
const n5 = await Note.save({ title: "occurence many times but very abcd spread appart spread appart spread appart spread appart spread appart efgh occurence many times but very abcd spread appart spread appart spread appart spread appart spread appart efgh occurence many times but very abcd spread appart spread appart spread appart spread appart spread appart efgh occurence many times but very abcd spread appart spread appart spread appart spread appart spread appart efgh occurence many times but very abcd spread appart spread appart spread appart spread appart spread appart efgh" });
const rows = await engine.search('abcd efgh');
expect(rows[0].id).toBe(n1.id);
expect(rows[1].id).toBe(n4.id);
expect(rows[2].id).toBe(n3.id);
expect(rows[3].id).toBe(n2.id);
expect(rows[4].id).toBe(n5.id);
done();
});
it('should supports various query types', async (done) => {
let rows;
const n1 = await Note.save({ title: "abcd efgh ijkl", body: "aaaa bbbb" });
const n2 = await Note.save({ title: "iiii efgh bbbb", body: "aaaa bbbb" });
rows = await engine.search('abcd ijkl');
expect(rows.length).toBe(1);
rows = await engine.search('"abcd ijkl"');
expect(rows.length).toBe(0);
rows = await engine.search('"abcd efgh"');
expect(rows.length).toBe(1);
rows = await engine.search('title:abcd');
expect(rows.length).toBe(1);
rows = await engine.search('title:efgh');
expect(rows.length).toBe(2);
rows = await engine.search('body:abcd');
expect(rows.length).toBe(0);
rows = await engine.search('body:bbbb');
expect(rows.length).toBe(2);
rows = await engine.search('body:bbbb iiii');
expect(rows.length).toBe(1);
done();
});
it('should parse normal query strings', async (done) => {
let rows;
const testCases = [
['abcd efgh', { _: ['abcd', 'efgh'] }],
['abcd efgh', { _: ['abcd', 'efgh'] }],
['title:abcd efgh', { _: ['efgh'], title: ['abcd'] }],
['title:abcd', { title: ['abcd'] }],
['"abcd efgh"', { _: ['abcd efgh'] }],
];
for (let i = 0; i < testCases.length; i++) {
const t = testCases[i];
const input = t[0];
const expected = t[1];
const actual = engine.parseQuery(input);
expect(JSON.stringify(actual.terms._)).toBe(JSON.stringify(expected._));
expect(JSON.stringify(actual.terms.title)).toBe(JSON.stringify(expected.title));
expect(JSON.stringify(actual.terms.body)).toBe(JSON.stringify(expected.body));
}
done();
});
it('should parse query strings with wildcards', async (done) => {
let rows;
const testCases = [
['do*', ['do', 'dog', 'domino'], [] ],
// "*" is a wildcard only when used at the end (to searhc for documents with the specified prefix)
// If it's at the beginning, it's ignored, if it's in the middle, it's treated as a litteral "*".
['*an*', ['an', 'anneau'], ['piano', 'plan'] ],
['no*no', ['no*no'], ['nonono'] ],
];
for (let i = 0; i < testCases.length; i++) {
const t = testCases[i];
const input = t[0];
const shouldMatch = t[1];
const shouldNotMatch = t[2];
const regex = new RegExp(engine.parseQuery(input).terms._[0].value, 'gmi');
for (let j = 0; j < shouldMatch.length; j++) {
const r = shouldMatch[j].match(regex);
expect(!!r).toBe(true, '"' + input + '" should match "' + shouldMatch[j] + '"');
}
// for (let j = 0; j < shouldNotMatch.length; j++) {
// const r = shouldNotMatch[j].match(regex);
// // console.info(input, shouldNotMatch)
// expect(!!r).toBe(false, '"' + input + '" should not match "' + shouldNotMatch[j] + '"');
// }
}
expect(engine.parseQuery('*').termCount).toBe(0);
done();
});
});

View File

@@ -4,6 +4,7 @@ const { DatabaseDriverNode } = require('lib/database-driver-node.js');
const BaseModel = require('lib/BaseModel.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const ItemChange = require('lib/models/ItemChange.js');
const Resource = require('lib/models/Resource.js');
const Tag = require('lib/models/Tag.js');
const NoteTag = require('lib/models/NoteTag.js');
@@ -122,6 +123,8 @@ async function switchClient(id) {
async function clearDatabase(id = null) {
if (id === null) id = currentClient_;
await ItemChange.waitForAllSaved();
let queries = [
'DELETE FROM notes',
'DELETE FROM folders',

View File

@@ -18,7 +18,10 @@ class AppComponent extends Component {
});
this.confirm_click = () => {
bridge().sendContentToJoplin(this.props.clippedContent);
const content = Object.assign({}, this.props.clippedContent);
content.tags = this.state.selectedTags.join(',');
content.parent_id = this.props.selectedFolderId;
bridge().sendContentToJoplin(content);
}
this.contentTitle_change = (event) => {
@@ -31,24 +34,18 @@ class AppComponent extends Component {
this.clipSimplified_click = () => {
bridge().sendCommandToActiveTab({
name: 'simplifiedPageHtml',
parent_id: this.props.selectedFolderId,
tags: this.state.selectedTags.join(','),
});
}
this.clipComplete_click = () => {
bridge().sendCommandToActiveTab({
name: 'completePageHtml',
parent_id: this.props.selectedFolderId,
tags: this.state.selectedTags.join(','),
});
}
this.clipSelection_click = () => {
bridge().sendCommandToActiveTab({
name: 'selectedHtml',
parent_id: this.props.selectedFolderId,
tags: this.state.selectedTags.join(','),
});
}

View File

@@ -23,8 +23,9 @@ const DecryptionWorker = require('lib/services/DecryptionWorker');
const InteropService = require('lib/services/InteropService');
const InteropServiceHelper = require('./InteropServiceHelper.js');
const ResourceService = require('lib/services/ResourceService');
const SearchEngine = require('lib/services/SearchEngine');
const ClipperServer = require('lib/ClipperServer');
const ExternalEditWatcher = require('lib/services/ExternalEditWatcher');
const { bridge } = require('electron').remote.require('./bridge');
const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem;
@@ -459,14 +460,27 @@ class Application extends BaseApplication {
name: 'commandStartExternalEditing',
});
},
}, {
type: 'separator',
screens: ['Main'],
}, {
label: _('Search in all the notes'),
screens: ['Main'],
accelerator: 'F6',
click: () => {
this.dispatch({
type: 'WINDOW_COMMAND',
name: 'focus_search',
});
},
}, {
label: _('Search in current note'),
screens: ['Main'],
accelerator: 'CommandOrControl+F',
click: () => {
this.dispatch({
type: 'WINDOW_COMMAND',
name: 'focus_search',
name: 'showLocalSearch',
});
},
}],
@@ -780,6 +794,9 @@ class Application extends BaseApplication {
ResourceService.runInBackground();
SearchEngine.instance().setDb(reg.db());
SearchEngine.instance().setLogger(reg.logger());
if (Setting.value('env') === 'dev') {
AlarmService.updateAllNotifications();
} else {
@@ -802,6 +819,9 @@ class Application extends BaseApplication {
if (Setting.value('clipperServer.autoStart')) {
ClipperServer.instance().start();
}
ExternalEditWatcher.instance().setLogger(reg.logger());
ExternalEditWatcher.instance().dispatch = this.store().dispatch;
}
}

View File

@@ -13,9 +13,16 @@ const InteropService = require('lib/services/InteropService');
const InteropServiceHelper = require('../InteropServiceHelper.js');
const Search = require('lib/models/Search');
const Mark = require('mark.js/dist/mark.min.js');
const SearchEngine = require('lib/services/SearchEngine');
class NoteListComponent extends React.Component {
constructor() {
super();
this.itemRenderer = this.itemRenderer.bind(this);
}
style() {
const theme = themeStyle(this.props.theme);
@@ -169,7 +176,10 @@ class NoteListComponent extends React.Component {
menu.popup(bridge().window());
}
itemRenderer(item, theme, width) {
itemRenderer(item) {
const theme = themeStyle(this.props.theme);
const width = this.props.style.width;
const onTitleClick = async (event, item) => {
if (event.ctrlKey) {
event.preventDefault();
@@ -225,8 +235,11 @@ class NoteListComponent extends React.Component {
let highlightedWords = [];
if (this.props.notesParentType === 'Search') {
const search = BaseModel.byId(this.props.searches, this.props.selectedSearchId);
highlightedWords = search ? Search.keywords(search.query_pattern) : [];
const query = BaseModel.byId(this.props.searches, this.props.selectedSearchId);
if (query) {
const parsedQuery = SearchEngine.instance().parseQuery(query.query_pattern);
highlightedWords = SearchEngine.instance().allParsedQueryTerms(parsedQuery);
}
}
let style = Object.assign({ width: width }, this.style().listItem);
@@ -257,7 +270,18 @@ class NoteListComponent extends React.Component {
exclude: ['img'],
acrossElements: true,
});
mark.mark(highlightedWords);
mark.unmark();
for (let i = 0; i < highlightedWords.length; i++) {
const w = highlightedWords[i];
if (w.type === 'regex') {
mark.markRegExp(new RegExp(w.value, 'gmi'), { acrossElements: true });
} else {
mark.mark([w]);
}
}
// Note: in this case it is safe to use dangerouslySetInnerHTML because titleElement
// is a span tag that we created and that contains data that's been inserted as plain text
@@ -269,6 +293,14 @@ class NoteListComponent extends React.Component {
titleComp = <span>{displayTitle}</span>
}
const watchedIconStyle = {
paddingRight: 4,
color: theme.color,
};
const watchedIcon = this.props.watchedNoteFiles.indexOf(item.id) < 0 ? null : (
<i style={watchedIconStyle} className={"fa fa-external-link"}></i>
);
// Need to include "todo_completed" in key so that checkbox is updated when
// item is changed via sync.
return <div key={item.id + '_' + item.todo_completed} style={style}>
@@ -283,6 +315,7 @@ class NoteListComponent extends React.Component {
onDragStart={(event) => onDragStart(event) }
data-id={item.id}
>
{watchedIcon}
{titleComp}
</a>
</div>
@@ -313,7 +346,7 @@ class NoteListComponent extends React.Component {
style={style}
className={"note-list"}
items={notes}
itemRenderer={ (item) => { return this.itemRenderer(item, theme, style.width) } }
itemRenderer={this.itemRenderer}
></ItemList>
);
}
@@ -329,6 +362,7 @@ const mapStateToProps = (state) => {
notesParentType: state.notesParentType,
searches: state.searches,
selectedSearchId: state.selectedSearchId,
watchedNoteFiles: state.watchedNoteFiles,
};
};

View File

@@ -110,12 +110,12 @@ class NotePropertiesDialog extends React.Component {
width: '100%',
height: '100%',
backgroundColor: 'rgba(0,0,0,0.6)',
alignItems: 'flex-start',
justifyContent: 'center',
alignItems: 'flex-start',
justifyContent: 'center',
};
this.styles_.dialogBox = {
backgroundColor: 'white',
backgroundColor: theme.backgroundColor,
padding: 16,
boxShadow: '6px 6px 20px rgba(0,0,0,0.5)',
marginTop: 20,
@@ -123,17 +123,33 @@ class NotePropertiesDialog extends React.Component {
this.styles_.controlBox = {
marginBottom: '1em',
color: 'black', //This will apply for the calendar
};
this.styles_.button = {
minWidth: theme.buttonMinWidth,
minHeight: theme.buttonMinHeight,
marginLeft: 5,
color: theme.color,
backgroundColor: theme.backgroundColor,
border: '1px solid',
borderColor: theme.dividerColor,
};
this.styles_.editPropertyButton = {
color: theme.color,
textDecoration: 'none',
backgroundColor: theme.backgroundColor,
border: '1px solid',
borderColor: theme.dividerColor,
};
this.styles_.input = {
display:'inline-block',
color: theme.color,
backgroundColor: theme.backgroundColor,
border: '1px solid',
borderColor: theme.dividerColor,
};
this.styles_.dialogTitle = Object.assign({}, theme.h1Style, { marginBottom: '1.2em' });
@@ -239,7 +255,8 @@ class NotePropertiesDialog extends React.Component {
dateFormat={time.dateFormat()}
timeFormat={time.timeFormat()}
inputProps={{
onKeyDown: (event) => onKeyDown(event, key)
onKeyDown: (event) => onKeyDown(event, key),
style: styles.input
}}
onChange={(momentObject) => {this.setState({ editedValue: momentObject })}}
/>
@@ -254,7 +271,7 @@ class NotePropertiesDialog extends React.Component {
ref="editField"
onChange={(event) => {this.setState({ editedValue: event.target.value })}}
onKeyDown={(event) => onKeyDown(event)}
style={{display:'inline-block'}}
style={styles.input}
/>
}
} else {

View File

@@ -0,0 +1,122 @@
const React = require('react');
const { connect } = require('react-redux');
const { themeStyle } = require('../theme.js');
const { _ } = require('lib/locale.js');
class NoteSearchBarComponent extends React.Component {
constructor() {
super();
this.state = {
query: '',
};
this.searchInput_change = this.searchInput_change.bind(this);
this.previousButton_click = this.previousButton_click.bind(this);
this.nextButton_click = this.nextButton_click.bind(this);
this.closeButton_click = this.closeButton_click.bind(this);
}
style() {
const theme = themeStyle(this.props.theme);
let style = {
root: Object.assign({}, theme.textStyle, {
backgroundColor: theme.backgroundColor,
color: theme.colorFaded,
}),
};
return style;
}
componentDidMount() {
this.refs.searchInput.focus();
}
buttonIconComponent(iconName, clickHandler) {
const theme = themeStyle(this.props.theme);
const searchButton = {
paddingLeft: 4,
paddingRight: 4,
paddingTop: 2,
paddingBottom: 2,
textDecoration: 'none',
marginRight: 5,
};
const iconStyle = {
display: 'flex',
fontSize: Math.round(theme.fontSize) * 1.2,
color: theme.color,
};
const icon = <i style={iconStyle} className={"fa " + iconName}></i>
return (
<a
href="#"
style={searchButton}
onClick={clickHandler}
>{icon}</a>
);
}
searchInput_change(event) {
const query = event.currentTarget.value;
this.setState({ query: query });
this.triggerOnChange(query);
}
previousButton_click(event) {
if (this.props.onPrevious) this.props.onPrevious();
}
nextButton_click(event) {
if (this.props.onNext) this.props.onNext();
}
closeButton_click(event) {
if (this.props.onClose) this.props.onClose();
}
triggerOnChange(query) {
if (this.props.onChange) this.props.onChange(query);
}
focus() {
this.refs.searchInput.focus();
}
render() {
const theme = themeStyle(this.props.theme);
const closeButton = this.buttonIconComponent('fa-times', this.closeButton_click);
const previousButton = this.buttonIconComponent('fa-chevron-up', this.previousButton_click);
const nextButton = this.buttonIconComponent('fa-chevron-down', this.nextButton_click);
return (
<div style={this.props.style}>
<div style={{display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
{ closeButton }
<input placeholder={_('Search...')} value={this.state.query} onChange={this.searchInput_change} ref="searchInput" type="text" style={{width: 200, marginRight: 5}}></input>
{ nextButton }
{ previousButton }
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
theme: state.settings.theme,
};
};
const NoteSearchBar = connect(mapStateToProps, null, null, { withRef: true })(NoteSearchBarComponent);
module.exports = NoteSearchBar;

View File

@@ -25,13 +25,16 @@ const fs = require('fs-extra');
const md5 = require('md5');
const mimeUtils = require('lib/mime-utils.js').mime;
const ArrayUtils = require('lib/ArrayUtils');
const ObjectUtils = require('lib/ObjectUtils');
const urlUtils = require('lib/urlUtils');
const dialogs = require('./dialogs');
const NoteSearchBar = require('./NoteSearchBar.min.js');
const markdownUtils = require('lib/markdownUtils');
const ExternalEditWatcher = require('lib/services/ExternalEditWatcher');
const ResourceFetcher = require('lib/services/ResourceFetcher');
const { toSystemSlashes, safeFilename } = require('lib/path-utils');
const { clipboard } = require('electron');
const SearchEngine = require('lib/services/SearchEngine');
require('brace/mode/markdown');
// https://ace.c9.io/build/kitchen-sink.html
@@ -39,11 +42,19 @@ require('brace/mode/markdown');
require('brace/theme/chrome');
require('brace/theme/twilight');
const NOTE_TAG_BAR_FEATURE_ENABLED = false;
class NoteTextComponent extends React.Component {
constructor() {
super();
this.localSearchDefaultState = {
query: '',
selectedIndex: 0,
resultCount: 0,
};
this.state = {
note: null,
noteMetadata: '',
@@ -63,6 +74,8 @@ class NoteTextComponent extends React.Component {
newAndNoTitleChangeNoteId: null,
bodyHtml: '',
lastKeys: [],
showLocalSearch: false,
localSearch: Object.assign({}, this.localSearchDefaultState),
};
this.lastLoadedNoteId_ = null;
@@ -72,8 +85,10 @@ class NoteTextComponent extends React.Component {
this.scheduleSaveTimeout_ = null;
this.restoreScrollTop_ = null;
this.lastSetHtml_ = '';
this.lastSetMarkers_ = [];
this.lastSetMarkers_ = '';
this.lastSetMarkersOptions_ = {};
this.selectionRange_ = null;
this.noteSearchBar_ = React.createRef();
// Complicated but reliable method to get editor content height
// https://github.com/ajaxorg/ace/issues/2046
@@ -212,6 +227,36 @@ class NoteTextComponent extends React.Component {
this.updateHtml(this.state.note.body);
}
}
this.noteSearchBar_change = (query) => {
this.setState({ localSearch: {
query: query,
selectedIndex: 0,
}});
}
const noteSearchBarNextPrevious = (inc) => {
const ls = Object.assign({}, this.state.localSearch);
ls.selectedIndex += inc;
if (ls.selectedIndex < 0) ls.selectedIndex = ls.resultCount - 1;
if (ls.selectedIndex >= ls.resultCount) ls.selectedIndex = 0;
this.setState({ localSearch: ls });
}
this.noteSearchBar_next = () => {
noteSearchBarNextPrevious(+1);
}
this.noteSearchBar_previous = () => {
noteSearchBarNextPrevious(-1);
}
this.noteSearchBar_close = () => {
this.setState({
showLocalSearch: false,
});
}
}
// Note:
@@ -305,6 +350,7 @@ class NoteTextComponent extends React.Component {
eventManager.on('todoToggle', this.onTodoToggle_);
ResourceFetcher.instance().on('downloadComplete', this.resourceFetcher_downloadComplete);
ExternalEditWatcher.instance().on('noteChange', this.externalEditWatcher_noteChange);
}
componentWillUnmount() {
@@ -318,8 +364,7 @@ class NoteTextComponent extends React.Component {
eventManager.removeListener('todoToggle', this.onTodoToggle_);
ResourceFetcher.instance().off('downloadComplete', this.resourceFetcher_downloadComplete);
this.destroyExternalEditWatcher();
ExternalEditWatcher.instance().off('noteChange', this.externalEditWatcher_noteChange);
}
async saveIfNeeded(saveIfNewNote = false) {
@@ -332,7 +377,7 @@ class NoteTextComponent extends React.Component {
}
await shared.saveNoteButton_press(this);
this.externalEditWatcherUpdateNoteFile(this.state.note);
ExternalEditWatcher.instance().updateNoteFile(this.state.note);
}
async saveOneProperty(name, value) {
@@ -371,7 +416,6 @@ class NoteTextComponent extends React.Component {
if (props.newNote) {
note = Object.assign({}, props.newNote);
this.lastLoadedNoteId_ = null;
this.externalEditWatcherStopWatchingAll();
} else {
noteId = props.noteId;
loadingNewNote = stateNoteId !== noteId;
@@ -398,8 +442,6 @@ class NoteTextComponent extends React.Component {
// Scroll back to top when loading new note
if (loadingNewNote) {
this.externalEditWatcherStopWatchingAll();
this.editorMaxScrollTop_ = 0;
// HACK: To go around a bug in Ace editor, we first set the scroll position to 1
@@ -442,8 +484,7 @@ class NoteTextComponent extends React.Component {
}
}
if (note)
{
if (note) {
parentFolder = Folder.byId(props.folders, note.parent_id);
}
@@ -462,8 +503,14 @@ class NoteTextComponent extends React.Component {
newState.newAndNoTitleChangeNoteId = null;
}
if (!note || loadingNewNote) {
newState.showLocalSearch = false;
newState.localSearch = Object.assign({}, this.localSearchDefaultState);
}
this.lastSetHtml_ = '';
this.lastSetMarkers_ = [];
this.lastSetMarkers_ = '';
this.lastSetMarkersOptions_ = {};
this.setState(newState);
@@ -472,13 +519,15 @@ class NoteTextComponent extends React.Component {
// Since I'm updating the state, the componentWillReceiveProps was getting triggered again, where nextProps.newNote was still true, causing reloadNote to trigger again and again.
// Notes from Laurent: The selected note tags are part of the global Redux state because they need to be updated whenever tags are changed or deleted
// anywhere in the app. Thus it's not possible simple to load the tags here (as we won't have a way to know if they're updated afterwards).
// Perhaps a better way would be to move that code in the middleware, check for TAGS_DELETE, TAGS_UPDATE, etc. actions and update the
// Perhaps a better way would be to move that code in the middleware, check for TAGS_DELETE, TAGS_UPDATE, etc. actions and update the
// selected note tags accordingly.
if (!this.props.newNote) {
this.props.dispatch({
type: "SET_NOTE_TAGS",
items: noteTags,
});
if (NOTE_TAG_BAR_FEATURE_ENABLED) {
if (!this.props.newNote) {
this.props.dispatch({
type: "SET_NOTE_TAGS",
items: noteTags,
});
}
}
this.updateHtml(newState.note ? newState.note.body : '');
@@ -509,6 +558,8 @@ class NoteTextComponent extends React.Component {
}
areNoteTagsModified(newTags, oldTags) {
if (!NOTE_TAG_BAR_FEATURE_ENABLED) return false;
if (!oldTags) return true;
if (newTags.length !== oldTags.length) return true;
@@ -562,6 +613,10 @@ class NoteTextComponent extends React.Component {
const newBody = this.mdToHtml_.handleCheckboxClick(msg, this.state.note.body);
this.saveOneProperty('body', newBody);
} else if (msg === 'setMarkerCount') {
const ls = Object.assign({}, this.state.localSearch);
ls.resultCount = arg0;
this.setState({ localSearch: ls });
} else if (msg === 'percentScroll') {
this.ignoreNextEditorScroll_ = true;
this.setEditorPercentScroll(arg0);
@@ -856,30 +911,48 @@ class NoteTextComponent extends React.Component {
async doCommand(command) {
if (!command || !this.state.note) return;
let commandProcessed = true;
let fn = null;
if (command.name === 'exportPdf' && this.webview_) {
this.commandSavePdf();
fn = this.commandSavePdf;
} else if (command.name === 'print' && this.webview_) {
this.webview_.print();
fn = this.commandPrint;
} else if (command.name === 'textBold') {
this.commandTextBold();
fn = this.commandTextBold;
} else if (command.name === 'textItalic') {
this.commandTextItalic();
fn = this.commandTextItalic;
} else if (command.name === 'insertDateTime' ) {
this.commandDateTime();
fn = this.commandDateTime;
} else if (command.name === 'commandStartExternalEditing') {
this.commandStartExternalEditing();
} else {
commandProcessed = false;
fn = this.commandStartExternalEditing;
} else if (command.name === 'showLocalSearch') {
fn = this.commandShowLocalSearch;
}
if (commandProcessed) {
this.props.dispatch({
type: 'WINDOW_COMMAND',
name: null,
});
if (!fn) return;
this.props.dispatch({
type: 'WINDOW_COMMAND',
name: null,
});
requestAnimationFrame(() => {
fn = fn.bind(this);
fn();
});
}
commandShowLocalSearch() {
if (this.state.showLocalSearch) {
this.noteSearchBar_.current.wrappedInstance.focus();
} else {
this.setState({ showLocalSearch: true });
}
this.props.dispatch({
type: 'NOTE_VISIBLE_PANES_SET',
panes: ['editor', 'viewer'],
});
}
async commandAttachFile(filePaths = null) {
@@ -924,6 +997,41 @@ class NoteTextComponent extends React.Component {
});
}
printTo_(target, options) {
const previousBody = this.state.note.body;
const tempBody = "# " + this.state.note.title + "\n\n" + previousBody;
const previousTheme = Setting.value('theme');
Setting.setValue('theme', Setting.THEME_LIGHT);
this.lastSetHtml_ = '';
this.updateHtml(tempBody);
this.forceUpdate();
const restoreSettings = () => {
Setting.setValue('theme', previousTheme);
this.lastSetHtml_ = '';
this.updateHtml(previousBody);
this.forceUpdate();
}
setTimeout(() => {
if (target === 'pdf') {
this.webview_.printToPDF({}, (error, data) => {
restoreSettings();
if (error) {
bridge().showErrorMessageBox(error.message);
} else {
shim.fsDriver().writeFile(options.path, data, 'buffer');
}
});
} else if (target === 'printer') {
this.webview_.print();
restoreSettings();
}
}, 100);
}
commandSavePdf() {
const path = bridge().showSaveDialog({
filters: [{ name: _('PDF File'), extensions: ['pdf']}],
@@ -931,73 +1039,24 @@ class NoteTextComponent extends React.Component {
});
if (path) {
// Temporarily add a <h2> title in the webview
const newHtml = this.insertHtmlHeading_(this.lastSetHtml_, this.state.note.title);
this.webview_.send('setHtml', newHtml);
setTimeout(() => {
this.webview_.printToPDF({}, (error, data) => {
if (error) {
bridge().showErrorMessageBox(error.message);
} else {
shim.fsDriver().writeFile(path, data, 'buffer');
}
// Refresh the webview, restoring the previous content
this.lastSetHtml_ = '';
this.forceUpdate();
});
}, 100);
this.printTo_('pdf', { path: path });
}
}
insertHtmlHeading_(s, heading) {
const tag = 'h2';
const marker = '<!-- START_OF_DOCUMENT -->'
let splitStyle = s.split(marker);
const index = splitStyle.length > 1 ? 1 : 0;
let toInsert = escapeHtml(heading);
toInsert = '<' + tag + '>' + toInsert + '</' + tag + '>';
splitStyle[index] = toInsert + splitStyle[index];
return splitStyle.join(marker);
}
externalEditWatcher() {
if (!this.externalEditWatcher_) {
this.externalEditWatcher_ = new ExternalEditWatcher((action) => { return this.props.dispatch(action) });
this.externalEditWatcher_.setLogger(reg.logger());
this.externalEditWatcher_.on('noteChange', this.externalEditWatcher_noteChange);
}
return this.externalEditWatcher_;
}
externalEditWatcherUpdateNoteFile(note) {
if (this.externalEditWatcher_) this.externalEditWatcher().updateNoteFile(note);
}
externalEditWatcherStopWatchingAll() {
if (this.externalEditWatcher_) this.externalEditWatcher().stopWatchingAll();
}
destroyExternalEditWatcher() {
if (!this.externalEditWatcher_) return;
this.externalEditWatcher_.off('noteChange', this.externalEditWatcher_noteChange);
this.externalEditWatcher_.stopWatchingAll();
this.externalEditWatcher_ = null;
commandPrint() {
this.printTo_('printer');
}
async commandStartExternalEditing() {
try {
await this.externalEditWatcher().openAndWatch(this.state.note);
await ExternalEditWatcher.instance().openAndWatch(this.state.note);
} catch (error) {
bridge().showErrorMessageBox(_('Error opening note in editor: %s', error.message));
}
}
async commandStopExternalEditing() {
this.externalEditWatcherStopWatchingAll();
ExternalEditWatcher.instance().stopWatching(this.state.note.id);
}
async commandSetTags() {
@@ -1434,10 +1493,21 @@ class NoteTextComponent extends React.Component {
const tagStyle = {
marginBottom: 10,
height: 30
};
const bottomRowHeight = rootStyle.height - titleBarStyle.height - titleBarStyle.marginBottom - titleBarStyle.marginTop - theme.toolbarHeight;
const searchBarHeight = this.state.showLocalSearch ? 35 : 0;
let bottomRowHeight = 0;
if (NOTE_TAG_BAR_FEATURE_ENABLED) {
bottomRowHeight = rootStyle.height - titleBarStyle.height - titleBarStyle.marginBottom - titleBarStyle.marginTop - theme.toolbarHeight - tagStyle.height - tagStyle.marginBottom;
} else {
toolbarStyle.marginBottom = 10;
bottomRowHeight = rootStyle.height - titleBarStyle.height - titleBarStyle.marginBottom - titleBarStyle.marginTop - theme.toolbarHeight - toolbarStyle.marginBottom;
}
bottomRowHeight -= searchBarHeight;
const viewerStyle = {
width: Math.floor(innerWidth / 2),
height: bottomRowHeight,
@@ -1457,7 +1527,7 @@ class NoteTextComponent extends React.Component {
verticalAlign: 'top',
paddingTop: paddingTop + 'px',
lineHeight: theme.textAreaLineHeight + 'px',
fontSize: theme.fontSize + 'px',
fontSize: theme.editorFontSize + 'px',
color: theme.color,
backgroundColor: theme.backgroundColor,
editorTheme: theme.editorTheme,
@@ -1496,12 +1566,25 @@ class NoteTextComponent extends React.Component {
this.lastSetHtml_ = html;
}
const search = BaseModel.byId(this.props.searches, this.props.selectedSearchId);
const keywords = search ? Search.keywords(search.query_pattern) : [];
let keywords = [];
const markerOptions = {};
if (htmlHasChanged || !ArrayUtils.contentEquals(this.lastSetMarkers_, keywords)) {
this.lastSetMarkers_ = [];
this.webview_.send('setMarkers', keywords);
if (this.state.showLocalSearch) {
keywords = [this.state.localSearch.query];
markerOptions.selectedIndex = this.state.localSearch.selectedIndex;
} else {
const search = BaseModel.byId(this.props.searches, this.props.selectedSearchId);
if (search) {
const parsedQuery = SearchEngine.instance().parseQuery(search.query_pattern);
keywords = SearchEngine.instance().allParsedQueryTerms(parsedQuery);
}
}
const keywordHash = JSON.stringify(keywords);
if (htmlHasChanged || keywordHash !== this.lastSetMarkers_ || !ObjectUtils.fieldsEqual(this.lastSetMarkersOptions_, markerOptions)) {
this.lastSetMarkers_ = keywordHash;
this.lastSetMarkersOptions_ = Object.assign({}, markerOptions);
this.webview_.send('setMarkers', keywords, markerOptions);
}
}
@@ -1521,7 +1604,7 @@ class NoteTextComponent extends React.Component {
placeholder={ this.props.newNote ? _('Creating new %s...', isTodo ? _('to-do') : _('note')) : '' }
/>
const tagList = <TagList
const tagList = !NOTE_TAG_BAR_FEATURE_ENABLED ? null : <TagList
style={tagStyle}
items={this.state.noteTags}
/>;
@@ -1589,6 +1672,17 @@ class NoteTextComponent extends React.Component {
highlightActiveLine={false}
/>
const noteSearchBarComp = !this.state.showLocalSearch ? null : (
<NoteSearchBar
ref={this.noteSearchBar_}
style={{display: 'flex', height:searchBarHeight,width:innerWidth, borderTop: '1px solid ' + theme.dividerColor}}
onChange={this.noteSearchBar_change}
onNext={this.noteSearchBar_next}
onPrevious={this.noteSearchBar_previous}
onClose={this.noteSearchBar_close}
/>
);
return (
<div style={rootStyle} onDrop={this.onDrop_}>
<div style={titleBarStyle}>
@@ -1600,6 +1694,8 @@ class NoteTextComponent extends React.Component {
{ tagList }
{ editor }
{ viewer }
<div style={{clear:'both'}}/>
{ noteSearchBarComp }
</div>
);
}

View File

@@ -0,0 +1,77 @@
/*
Atom One Dark With support for ReasonML by Gidi Morris, based off work by Daniel Gamage
Original One Dark Syntax theme from https://github.com/atom/one-dark-syntax
*/
.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
line-height: 1.3em;
color: #abb2bf;
background: #282c34;
border-radius: 5px;
}
.hljs-keyword, .hljs-operator {
color: #F92672;
}
.hljs-pattern-match {
color: #F92672;
}
.hljs-pattern-match .hljs-constructor {
color: #61aeee;
}
.hljs-function {
color: #61aeee;
}
.hljs-function .hljs-params {
color: #A6E22E;
}
.hljs-function .hljs-params .hljs-typing {
color: #FD971F;
}
.hljs-module-access .hljs-module {
color: #7e57c2;
}
.hljs-constructor {
color: #e2b93d;
}
.hljs-constructor .hljs-string {
color: #9CCC65;
}
.hljs-comment, .hljs-quote {
color: #b18eb1;
font-style: italic;
}
.hljs-doctag, .hljs-formula {
color: #c678dd;
}
.hljs-section, .hljs-name, .hljs-selector-tag, .hljs-deletion, .hljs-subst {
color: #e06c75;
}
.hljs-literal {
color: #56b6c2;
}
.hljs-string, .hljs-regexp, .hljs-addition, .hljs-attribute, .hljs-meta-string {
color: #98c379;
}
.hljs-built_in, .hljs-class .hljs-title {
color: #e6c07b;
}
.hljs-attr, .hljs-variable, .hljs-template-variable, .hljs-type, .hljs-selector-class, .hljs-selector-attr, .hljs-selector-pseudo, .hljs-number {
color: #d19a66;
}
.hljs-symbol, .hljs-bullet, .hljs-link, .hljs-meta, .hljs-selector-id, .hljs-title {
color: #61aeee;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}
.hljs-link {
text-decoration: underline;
}

View File

@@ -16,6 +16,11 @@
}
mark {
background: #F3B717;
color: black;
}
.mark-selected {
background: #CF3F00;
color: white;
}
@@ -138,6 +143,8 @@
ipc.setHtml = (event) => {
const html = event.html;
markJsHackMarkerInserted_ = false;
updateBodyHeight();
contentElement.innerHTML = html;
@@ -191,8 +198,34 @@
setPercentScroll(percent);
}
// HACK for Mark.js bug - https://github.com/julmot/mark.js/issues/127
let markJsHackMarkerInserted_ = false;
function addMarkJsSpaceHack(document) {
if (markJsHackMarkerInserted_) return;
const prepareElementsForMarkJs = (elements, type) => {
// const markJsHackMarker_ = '&#8203; &#8203;'
const markJsHackMarker_ = ' ';
for (let i = 0; i < elements.length; i++) {
if (!type) {
elements[i].innerHTML = elements[i].innerHTML + markJsHackMarker_;
} else if (type === 'insertBefore') {
elements[i].insertAdjacentHTML('beforeBegin', markJsHackMarker_);
}
}
}
prepareElementsForMarkJs(document.getElementsByTagName('p'));
prepareElementsForMarkJs(document.getElementsByTagName('div'));
prepareElementsForMarkJs(document.getElementsByTagName('br'), 'insertBefore');
markJsHackMarkerInserted_ = true;
}
let mark_ = null;
function setMarkers(keywords) {
let markSelectedElement_ = null;
function setMarkers(keywords, options = null) {
if (!options) options = {};
if (!mark_) {
mark_ = new Mark(document.getElementById('content'), {
exclude: ['img'],
@@ -200,26 +233,66 @@
});
}
mark_.mark(keywords);
addMarkJsSpaceHack(document);
mark_.unmark()
if (markSelectedElement_) markSelectedElement_.classList.remove('mark-selected');
let selectedElement = null;
let elementIndex = 0;
const onEachElement = (element) => {
if (!('selectedIndex' in options)) return;
if (('selectedIndex' in options) && elementIndex === options.selectedIndex) {
markSelectedElement_ = element;
element.classList.add('mark-selected');
selectedElement = element;
}
elementIndex++;
}
for (let i = 0; i < keywords.length; i++) {
const keyword = keywords[i];
if (keyword.type === 'regex') {
mark_.markRegExp(new RegExp(keyword.value, 'gmi'), {
each: onEachElement,
acrossElements: true,
});
} else {
mark_.mark([keyword], {
each: onEachElement,
accuracy: 'exactly',
});
}
}
ipcProxySendToHost('setMarkerCount', elementIndex);
if (selectedElement) selectedElement.scrollIntoView();
}
let markLoaded_ = false;
ipc.setMarkers = (event) => {
const keywords = event.keywords;
const options = event.options;
if (!keywords.length && !markLoaded_) return;
if (!markLoaded_) {
const script = document.createElement('script');
script.onload = function() {
setMarkers(keywords);
setMarkers(keywords, options);
};
script.src = '../../node_modules/mark.js/dist/mark.min.js';
document.getElementById('markScriptContainer').appendChild(script);
markLoaded_ = true;
} else {
setMarkers(keywords);
setMarkers(keywords, options);
}
}
@@ -276,24 +349,32 @@
}
});
function handleInternalLink(event, anchorNode) {
const href = anchorNode.getAttribute('href');
if (href.indexOf('#') === 0) {
event.preventDefault();
location.hash = href;
return true;
}
return false;
}
document.addEventListener('click', function(event) {
const t = event.target;
// Prevent URLs added via <a> tags from being opened within the application itself
// otherwise it would open the whole website within the WebView.
if (t && t.nodeName === 'A' && !t.hasAttribute('data-from-md')) {
if (handleInternalLink(event, t)) return;
event.preventDefault();
ipcProxySendToHost(t.getAttribute('href'));
return;
}
// IF this is an internal link, jump to the anchor directly
// If this is an internal link, jump to the anchor directly
if (t && t.nodeName === 'A' && t.hasAttribute('data-from-md')) {
const href = t.getAttribute('href');
if (href.indexOf('#') === 0) {
event.preventDefault();
location.hash = href;
return;
}
if (handleInternalLink(event, t)) return;
}
});

View File

@@ -13,8 +13,8 @@ ipcRenderer.on('setPercentScroll', (event, percent) => {
window.postMessage({ target: 'webview', name: 'setPercentScroll', data: { percent: percent } }, '*');
});
ipcRenderer.on('setMarkers', (event, keywords) => {
window.postMessage({ target: 'webview', name: 'setMarkers', data: { keywords: keywords } }, '*');
ipcRenderer.on('setMarkers', (event, keywords, options) => {
window.postMessage({ target: 'webview', name: 'setMarkers', data: { keywords: keywords, options: options } }, '*');
});
window.addEventListener('message', (event) => {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -12,9 +12,9 @@ locales['hr_HR'] = require('./hr_HR.json');
locales['it_IT'] = require('./it_IT.json');
locales['ja_JP'] = require('./ja_JP.json');
locales['ko'] = require('./ko.json');
locales['nb_NO'] = require('./nb_NO.json');
locales['nl_BE'] = require('./nl_BE.json');
locales['nl_NL'] = require('./nl_NL.json');
locales['no'] = require('./no.json');
locales['pt_BR'] = require('./pt_BR.json');
locales['ro'] = require('./ro.json');
locales['ru_RU'] = require('./ru_RU.json');

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
{
"name": "Joplin",
"version": "1.0.115",
"version": "1.0.119",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -6981,11 +6981,6 @@
"random-bytes": "~1.0.0"
}
},
"unidecode": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/unidecode/-/unidecode-0.1.8.tgz",
"integrity": "sha1-77swFTi8RSRqmsjFWdcvAVMFBT4="
},
"union-value": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "Joplin",
"version": "1.0.115",
"version": "1.0.119",
"description": "Joplin for Desktop",
"main": "main.js",
"scripts": {
@@ -8,7 +8,7 @@
"pack": "node_modules/.bin/electron-builder --dir",
"dist": "node_modules/.bin/electron-builder",
"publish": "build -p always",
"postinstall": "node compile-jsx.js && node compile-package-info.js && node ../../Tools/copycss.js --copy-fonts",
"postinstall": "node compile-jsx.js && node compile-package-info.js && node ../../Tools/copycss.js --copy-fonts && install-app-deps",
"compile": "node compile-jsx.js && node compile-package-info.js && node ../../Tools/copycss.js --copy-fonts"
},
"repository": {
@@ -71,8 +71,8 @@
"electron-builder": "20.14.7"
},
"optionalDependencies": {
"7zip-bin-mac": "^1.0.1",
"7zip-bin-linux": "^1.0.1",
"7zip-bin-mac": "^1.0.1",
"7zip-bin-win": "^2.1.1"
},
"dependencies": {
@@ -121,13 +121,12 @@
"server-destroy": "^1.0.1",
"smalltalk": "^2.5.1",
"sprintf-js": "^1.1.1",
"sqlite3": "^3.1.13",
"sqlite3": "^4.0.4",
"string-padding": "^1.0.2",
"string-to-stream": "^1.1.1",
"syswide-cas": "^5.1.0",
"tar": "^4.4.4",
"tcp-port-used": "^0.1.2",
"unidecode": "^0.1.8",
"url-parse": "^1.4.1",
"uuid": "^3.2.1",
"valid-url": "^1.0.9",

View File

@@ -1,6 +1,7 @@
const Setting = require('lib/models/Setting.js');
const zoomRatio = Setting.value('style.zoom') / 100;
const editorFontSize = Setting.value('style.editor.fontSize');
// globalStyle should be used for properties that do not change across themes
// i.e. should not be used for colors
@@ -14,7 +15,8 @@ const globalStyle = {
disabledOpacity: 0.3,
buttonMinWidth: 50,
buttonMinHeight: 30,
textAreaLineHeight: 17,
editorFontSize: editorFontSize,
textAreaLineHeight: Math.round(17 * editorFontSize / 12),
headerHeight: 35,
headerButtonHPadding: 6,

View File

@@ -11,6 +11,14 @@ echo " | | "
echo " |_| "
echo ""
# Check and warn if running as root.
if [[ $EUID = 0 ]] ; then
if [[ $* != *--allow-root* ]] ; then
echo "It is not recommended (nor necessary) to run this script as root. To do so anyway, please use '--allow-root'"
exit 1
fi
fi
#-----------------------------------------------------
# Download Joplin
#-----------------------------------------------------
@@ -19,7 +27,6 @@ echo ""
version=$(curl --silent "https://api.github.com/repos/laurent22/joplin/releases/latest" | grep -Po '"tag_name": "v\K.*?(?=")')
# Check if it's in the latest version
touch VERSION
if [[ $(< ~/.joplin/VERSION) != "$version" ]]; then
# Delete previous version
@@ -51,9 +58,9 @@ if [[ $(< ~/.joplin/VERSION) != "$version" ]]; then
desktop=${desktop,,} # convert to lower case
# Create icon for Gnome
if [[ $desktop =~ .*gnome.* ]]
if [[ $desktop =~ .*gnome.* ]] || [[ $desktop =~ .*kde.* ]]
then
echo -e "[Desktop Entry]\nEncoding=UTF-8\nName=Joplin\nExec=/home/$USER/.joplin/Joplin-$version-x86_64.AppImage\nIcon=/home/$USER/.joplin/Icon512.png\nType=Application\nCategories=Application;" >> ~/.local/share/applications/joplin.desktop
echo -e "[Desktop Entry]\nEncoding=UTF-8\nName=Joplin\nExec=/home/$USER/.joplin/Joplin.AppImage\nIcon=/home/$USER/.joplin/Icon512.png\nType=Application\nCategories=Application;" >> ~/.local/share/applications/joplin.desktop
fi
#-----------------------------------------------------

View File

@@ -20,9 +20,9 @@ Three types of applications are available: for the **desktop** (Windows, macOS a
Operating System | Download | Alternative
-----------------|--------|-------------------
Windows (32 and 64-bit) | <a href='https://github.com/laurent22/joplin/releases/download/v1.0.114/Joplin-Setup-1.0.114.exe'><img alt='Get it on Windows' height="40px" src='https://joplin.cozic.net/images/BadgeWindows.png'/></a> | or Get the <a href='https://github.com/laurent22/joplin/releases/download/v1.0.114/JoplinPortable.exe'>Portable version</a><br>(to run from a USB key, etc.)
macOS | <a href='https://github.com/laurent22/joplin/releases/download/v1.0.114/Joplin-1.0.114.dmg'><img alt='Get it on macOS' height="40px" src='https://joplin.cozic.net/images/BadgeMacOS.png'/></a> |
Linux | <a href='https://github.com/laurent22/joplin/releases/download/v1.0.114/Joplin-1.0.114-x86_64.AppImage'><img alt='Get it on Linux' height="40px" src='https://joplin.cozic.net/images/BadgeLinux.png'/></a> | An Arch Linux package<br>[is also available](#terminal-application).
Windows (32 and 64-bit) | <a href='https://github.com/laurent22/joplin/releases/download/v1.0.117/Joplin-Setup-1.0.117.exe'><img alt='Get it on Windows' height="40px" src='https://joplin.cozic.net/images/BadgeWindows.png'/></a> | or Get the <a href='https://github.com/laurent22/joplin/releases/download/v1.0.117/JoplinPortable.exe'>Portable version</a><br>(to run from a USB key, etc.)
macOS | <a href='https://github.com/laurent22/joplin/releases/download/v1.0.117/Joplin-1.0.117.dmg'><img alt='Get it on macOS' height="40px" src='https://joplin.cozic.net/images/BadgeMacOS.png'/></a> |
Linux | <a href='https://github.com/laurent22/joplin/releases/download/v1.0.117/Joplin-1.0.117-x86_64.AppImage'><img alt='Get it on Linux' height="40px" src='https://joplin.cozic.net/images/BadgeLinux.png'/></a> | An Arch Linux package<br>[is also available](#terminal-application).
The [portable application](https://en.wikipedia.org/wiki/Portable_application) allows installing the software on a portable device such as a USB key. Simply copy the file JoplinPortable.exe in any directory on that USB key ; the application will then create a directory called "JoplinProfile" next to the executable file.
@@ -36,7 +36,7 @@ wget -O - https://raw.githubusercontent.com/laurent22/joplin/master/Joplin_insta
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://joplin.cozic.net/images/BadgeAndroid.png'/></a> | or [Download APK File](https://github.com/laurent22/joplin-android/releases/download/android-v1.0.175/joplin-v1.0.175.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://joplin.cozic.net/images/BadgeAndroid.png'/></a> | or [Download APK File](https://github.com/laurent22/joplin-android/releases/download/android-v1.0.178/joplin-v1.0.178.apk)
iOS | <a href='https://itunes.apple.com/us/app/joplin/id1315599797'><img alt='Get it on the App Store' height="40px" src='https://joplin.cozic.net/images/BadgeIOS.png'/></a> | -
## Terminal application
@@ -81,7 +81,7 @@ The Web Clipper is a browser extension that allows you to save web pages and scr
- [Donate](https://github.com/laurent22/joplin/blob/master/readme/donate.md)
<!-- TOC -->
# Features
# Features
- Desktop, mobile and terminal applications.
- [Web Clipper](https://github.com/laurent22/joplin/blob/master/readme/clipper.md) for Firefox and Chrome.
@@ -98,10 +98,11 @@ The Web Clipper is a browser extension that allows you to save web pages and scr
- Search functionality.
- Geo-location support.
- Supports multiple languages
- External editor support - open notes in your favorite external editor with one click in Joplin.
# Importing
## Importing from Evernote
## Importing from Evernote
Joplin was designed as a replacement for Evernote and so can import complete Evernote notebooks, as well as notes, tags, resources (attached files) and note metadata (such as author, geo-location, etc.) via ENEX files. In terms of data, the only two things that might slightly differ are:
@@ -173,6 +174,7 @@ Select the "WebDAV" synchronisation target and follow the same instructions as f
WebDAV-compatible services that are known to work with Joplin:
- [Apache WebDAV Module](https://httpd.apache.org/docs/current/mod/mod_dav.html)
- [Box.com](https://www.box.com/)
- [DriveHQ](https://www.drivehq.com)
- [Fastmail](https://www.fastmail.com/)
@@ -284,6 +286,20 @@ It is generally recommended to enter the notes as Markdown as it makes the notes
Rendered markdown can be customized by placing a userstyle file in the profile directory `~/.config/joplin-desktop/userstyle.css` (This path might be different on your device - check at the top of the Config screen for the exact path). This file supports standard CSS syntax.
# Searching
Joplin implements the SQLite Full Text Search (FTS4) extension. It means the content of all the notes is indexed in real time and search queries return results very fast. Both [Simple FTS Queries](https://www.sqlite.org/fts3.html#simple_fts_queries) and [Full-Text Index Queries](https://www.sqlite.org/fts3.html#full_text_index_queries) are supported. See below for the list of supported queries:
Search type | Description | Example
------------|-------------|---------
Single word | Returns all the notes that contain this term. | `dog`, `cat`
Multiples words | Returns all the notes that contain **all** these words, but not necessarily next to each other. | `dog cat` - will return any notes that contain the words "dog" and "cat" anywhere in the note, no necessarily in that order nor next to each others. It will **not** return results that contain "dog" or "cat" only.
Phrase query | Add double quotes to return the notes that contain exactly this phrase. | `"shopping list"` - will return the notes that contain these **exact terms** next to each others and in this order. It will **not** return for example a note that contain "going shopping with my list".
Prefix | Add a wildmark to return all the notes that contain a term with a specified prefix. | `swim*` - will return all the notes that contain eg. "swim", but also "swimming", "swimsuit", etc. IMPORTANT: The wildcard **can only be at the end** - it will be ignored at the beginning of a word (eg. `*swim`) and will be treated as a literal asterisk in the middle of a word (eg. `ast*rix`)
Field restricted | Add either `title:` or `body:` before a note to restrict your search to just the title, or just the body. | `title:shopping`, `body:egg`
Notes are sorted by "relevance". Currently it means the notes that contain the requested terms the most times are on top. For queries with multiple terms, it also matter how close to each others are the terms. This is a bit experimental so if you notice a search query that returns unexpected results, please report it in the forum, providing as much details as possible to replicate the issue.
# Donations
Donations to Joplin support the development of the project. Developing quality applications mostly takes time, but there are also some expenses, such as digital certificates to sign the applications, app store fees, hosting, etc. Most of all, your donation will make it possible to keep up the current development standard.
@@ -320,28 +336,28 @@ Current translations:
&nbsp; | Language | Po File | Last translator | Percent done
---|---|---|---|---
![](https://joplin.cozic.net/images/flags/es/basque_country.png) | Basque | [eu](https://github.com/laurent22/joplin/blob/master/CliClient/locales/eu.po) | juan.abasolo@ehu.eus | 61%
![](https://joplin.cozic.net/images/flags/es/catalonia.png) | Catalan | [ca](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ca.po) | jmontane, 2018 | 87%
![](https://joplin.cozic.net/images/flags/es/catalonia.png) | Catalan | [ca](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ca.po) | jmontane, 2018 | 86%
![](https://joplin.cozic.net/images/flags/country-4x3/hr.png) | Croatian | [hr_HR](https://github.com/laurent22/joplin/blob/master/CliClient/locales/hr_HR.po) | Hrvoje Mandić (trbuhom@net.hr) | 49%
![](https://joplin.cozic.net/images/flags/country-4x3/cz.png) | Czech | [cs_CZ](https://github.com/laurent22/joplin/blob/master/CliClient/locales/cs_CZ.po) | Lukas Helebrandt (lukas@aiya.cz) | 77%
![](https://joplin.cozic.net/images/flags/country-4x3/cz.png) | Czech | [cs_CZ](https://github.com/laurent22/joplin/blob/master/CliClient/locales/cs_CZ.po) | Lukas Helebrandt (lukas@aiya.cz) | 76%
![](https://joplin.cozic.net/images/flags/country-4x3/dk.png) | Dansk | [da_DK](https://github.com/laurent22/joplin/blob/master/CliClient/locales/da_DK.po) | Morten Juhl-Johansen Zölde-Fejér (mjjzf@syntaktisk. | 78%
![](https://joplin.cozic.net/images/flags/country-4x3/de.png) | Deutsch | [de_DE](https://github.com/laurent22/joplin/blob/master/CliClient/locales/de_DE.po) | Michael Sonntag (ms@editorei.de) | 98%
![](https://joplin.cozic.net/images/flags/country-4x3/de.png) | Deutsch | [de_DE](https://github.com/laurent22/joplin/blob/master/CliClient/locales/de_DE.po) | Michael Sonntag (ms@editorei.de) | 97%
![](https://joplin.cozic.net/images/flags/country-4x3/gb.png) | English | [en_GB](https://github.com/laurent22/joplin/blob/master/CliClient/locales/en_GB.po) | | 100%
![](https://joplin.cozic.net/images/flags/country-4x3/es.png) | Español | [es_ES](https://github.com/laurent22/joplin/blob/master/CliClient/locales/es_ES.po) | Fernando Martín (f@mrtn.es) | 93%
![](https://joplin.cozic.net/images/flags/country-4x3/es.png) | Español | [es_ES](https://github.com/laurent22/joplin/blob/master/CliClient/locales/es_ES.po) | Fernando Martín (f@mrtn.es) | 92%
![](https://joplin.cozic.net/images/flags/country-4x3/fr.png) | Français | [fr_FR](https://github.com/laurent22/joplin/blob/master/CliClient/locales/fr_FR.po) | Laurent Cozic | 100%
![](https://joplin.cozic.net/images/flags/es/galicia.png) | Galician | [gl_ES](https://github.com/laurent22/joplin/blob/master/CliClient/locales/gl_ES.po) | Marcos Lans (marcoslansgarza@gmail.com) | 77%
![](https://joplin.cozic.net/images/flags/country-4x3/it.png) | Italiano | [it_IT](https://github.com/laurent22/joplin/blob/master/CliClient/locales/it_IT.po) | | 92%
![](https://joplin.cozic.net/images/flags/country-4x3/nl.png) | Nederlands | [nl_NL](https://github.com/laurent22/joplin/blob/master/CliClient/locales/nl_NL.po) | Heimen Stoffels (vistausss@outlook.com) | 93%
![](https://joplin.cozic.net/images/flags/country-4x3/it.png) | Italiano | [it_IT](https://github.com/laurent22/joplin/blob/master/CliClient/locales/it_IT.po) | | 91%
![](https://joplin.cozic.net/images/flags/country-4x3/be.png) | Nederlands | [nl_BE](https://github.com/laurent22/joplin/blob/master/CliClient/locales/nl_BE.po) | | 61%
![](https://joplin.cozic.net/images/flags/country-4x3/no.png) | Norwegian | [no](https://github.com/laurent22/joplin/blob/master/CliClient/locales/no.po) | | 83%
![](https://joplin.cozic.net/images/flags/country-4x3/br.png) | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/master/CliClient/locales/pt_BR.po) | Renato Nunes Bastos (rnbastos@gmail.com) | 93%
![](https://joplin.cozic.net/images/flags/country-4x3/ro.png) | Română | [ro](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ro.po) | | 61%
![](https://joplin.cozic.net/images/flags/country-4x3/nl.png) | Nederlands | [nl_NL](https://github.com/laurent22/joplin/blob/master/CliClient/locales/nl_NL.po) | Heimen Stoffels (vistausss@outlook.com) | 92%
![](https://joplin.cozic.net/images/flags/country-4x3/no.png) | Norwegian | [nb_NO](https://github.com/laurent22/joplin/blob/master/CliClient/locales/nb_NO.po) | Mats Estensen (matsest@mxe.no) | 99%
![](https://joplin.cozic.net/images/flags/country-4x3/br.png) | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/master/CliClient/locales/pt_BR.po) | Renato Nunes Bastos (rnbastos@gmail.com) | 99%
![](https://joplin.cozic.net/images/flags/country-4x3/ro.png) | Română | [ro](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ro.po) | | 60%
![](https://joplin.cozic.net/images/flags/country-4x3/si.png) | Slovenian | [sl_SI](https://github.com/laurent22/joplin/blob/master/CliClient/locales/sl_SI.po) | | 76%
![](https://joplin.cozic.net/images/flags/country-4x3/se.png) | Svenska | [sv](https://github.com/laurent22/joplin/blob/master/CliClient/locales/sv.po) | Jonatan Nyberg (jonatan@autistici.org) | 92%
![](https://joplin.cozic.net/images/flags/country-4x3/se.png) | Svenska | [sv](https://github.com/laurent22/joplin/blob/master/CliClient/locales/sv.po) | Jonatan Nyberg (jonatan@autistici.org) | 91%
![](https://joplin.cozic.net/images/flags/country-4x3/ru.png) | Русский | [ru_RU](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ru_RU.po) | Artyom Karlov (artyom.karlov@gmail.com) | 76%
![](https://joplin.cozic.net/images/flags/country-4x3/cn.png) | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/master/CliClient/locales/zh_CN.po) | | 93%
![](https://joplin.cozic.net/images/flags/country-4x3/tw.png) | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/master/CliClient/locales/zh_TW.po) | penguinsam (samliu@gmail.com) | 93%
![](https://joplin.cozic.net/images/flags/country-4x3/jp.png) | 日本語 | [ja_JP](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ja_JP.po) | AWASHIRO Ikuya (ikunya@gmail.com) | 94%
![](https://joplin.cozic.net/images/flags/country-4x3/kr.png) | 한국말 | [ko](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ko.po) | | 93%
![](https://joplin.cozic.net/images/flags/country-4x3/cn.png) | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/master/CliClient/locales/zh_CN.po) | | 95%
![](https://joplin.cozic.net/images/flags/country-4x3/tw.png) | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/master/CliClient/locales/zh_TW.po) | penguinsam (samliu@gmail.com) | 92%
![](https://joplin.cozic.net/images/flags/country-4x3/jp.png) | 日本語 | [ja_JP](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ja_JP.po) | AWASHIRO Ikuya (ikunya@gmail.com) | 93%
![](https://joplin.cozic.net/images/flags/country-4x3/kr.png) | 한국말 | [ko](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ko.po) | | 92%
<!-- LOCALE-TABLE-AUTO-GENERATED -->
# Known bugs

View File

@@ -90,8 +90,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2097411
versionName "1.0.175"
versionCode 2097414
versionName "1.0.178"
ndk {
abiFilters "armeabi-v7a", "x86"
}
@@ -137,24 +137,39 @@ android {
}
dependencies {
compile project(':react-native-camera')
compile project(':react-native-file-viewer')
compile project(':react-native-securerandom')
compile project(':react-native-push-notification')
compile project(':react-native-fs')
compile project(':react-native-image-picker')
compile project(':react-native-vector-icons')
compile project(':react-native-fs')
implementation project(':react-native-firebase')
implementation (project(':react-native-camera')) {
// This is required because com.google.firebase requires v16.0.x of com.google.android.gms
// while react-native-camera requires v15.x, which results in broken dependencies with
// this error message:
//
// The library com.google.android.gms:play-services-base is being requested by various other libraries at [[15.0.1,15.0.1]], but resolves to 16.0.1
//
// For the record: found solution by removing all Firebase stuff here and running "gradlew.bat :app:dependencies"
// That shows that react-native-camera was the one requiring v15.0.1.
exclude group: "com.google.android.gms"
}
implementation project(':react-native-file-viewer')
implementation project(':react-native-securerandom')
implementation project(':react-native-fs')
implementation project(':react-native-image-picker')
implementation project(':react-native-vector-icons')
implementation project(':react-native-fs')
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
implementation "com.facebook.react:react-native:+" // From node_modules
compile project(':react-native-sqlite-storage')
compile project(':rn-fetch-blob')
compile project(':react-native-document-picker')
compile project(':react-native-image-resizer')
compile project(':react-native-share-extension')
compile project(':react-native-version-info')
compile "com.facebook.react:react-native:+"
implementation project(':react-native-sqlite-storage')
implementation project(':rn-fetch-blob')
implementation project(':react-native-document-picker')
implementation project(':react-native-image-resizer')
implementation project(':react-native-share-extension')
implementation project(':react-native-version-info')
implementation "com.facebook.react:react-native:+"
implementation "com.google.android.gms:play-services-base:16.0.1" // For Firebase
implementation "com.google.firebase:firebase-core:16.0.4" // For Firebase
implementation "com.google.firebase:firebase-messaging:17.3.4" // For Firebase
implementation 'me.leolin:ShortcutBadger:1.1.21@aar' // For Firebase - this line if you wish to use badge on Android
// To fix the error below, which happened after adding react-native-camera.
// Doesn't make any sense since rn-camera neither defines v26 nor 27 but
@@ -183,3 +198,4 @@ task copyDownloadableDepsToLibs(type: Copy) {
}
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
apply plugin: 'com.google.gms.google-services' // For Firebase

View File

@@ -0,0 +1,42 @@
{
"project_info": {
"project_number": "790045682275",
"firebase_url": "https://joplin-b5b20.firebaseio.com",
"project_id": "joplin-b5b20",
"storage_bucket": "joplin-b5b20.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:790045682275:android:8b68903cf881e9f7",
"android_client_info": {
"package_name": "net.cozic.joplin"
}
},
"oauth_client": [
{
"client_id": "790045682275-fkusmvsm7gv3nve7h0sg0uuor9njf4sm.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyCbHjUWAKcbldLTuoN7JybJ8dfznwBG_gM"
}
],
"services": {
"analytics_service": {
"status": 1
},
"appinvite_service": {
"status": 1,
"other_platform_oauth_client": []
},
"ads_service": {
"status": 2
}
}
}
],
"configuration_version": "1"
}

View File

@@ -11,25 +11,23 @@
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" tools:node="remove"/>
<!-- ============================= -->
<!-- START RNFirebaseNotifications -->
<!-- ============================= -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE" />
<!-- ============================= -->
<!-- END RNFirebaseNotifications -->
<!-- ============================= -->
<!-- Make these features optional to enable Chromebooks -->
<!-- https://github.com/laurent22/joplin/issues/37 -->
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
<!-- ==================================== -->
<!-- START react-native-push-notification -->
<!-- ==================================== -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<permission
android:name="${applicationId}.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<!-- ================================== -->
<!-- END react-native-push-notification -->
<!-- ================================== -->
<uses-sdk
android:minSdkVersion="16"
android:targetSdkVersion="26" />
@@ -41,27 +39,62 @@
android:icon="@mipmap/ic_launcher"
android:theme="@style/AppTheme">
<!-- ==================================== -->
<!-- START react-native-push-notification -->
<!-- ==================================== -->
<receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationPublisher" />
<receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationBootEventReceiver">
<!-- ============================= -->
<!-- START RNFirebaseNotifications -->
<!-- ============================= -->
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_stat_access_alarm" />
<receiver android:name="io.invertase.firebase.notifications.RNFirebaseNotificationReceiver"/>
<receiver android:enabled="true" android:exported="true" android:name="io.invertase.firebase.notifications.RNFirebaseNotificationsRebootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.QUICKBOOT_POWERON"/>
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<service android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationRegistrationService"/>
<!-- ================================== -->
<!-- END react-native-push-notification -->
<!-- ================================== -->
<!-- ============================= -->
<!-- END RNFirebaseNotifications -->
<!-- ============================= -->
<!-- ============================= -->
<!-- START RNFirebaseNotifications -->
<!-- ============================= -->
<service android:name="io.invertase.firebase.messaging.RNFirebaseMessagingService">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<service android:name="io.invertase.firebase.messaging.RNFirebaseInstanceIdService">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
</intent-filter>
</service>
<!-- "If you want to be able to react to data-only messages when your app is in the background, e.g. to display a heads up notification" -->
<service android:name="io.invertase.firebase.messaging.RNFirebaseBackgroundMessagingService" />
<!-- ============================= -->
<!-- END RNFirebaseNotifications -->
<!-- ============================= -->
<!-- 2018-12-16: Changed android:launchMode from "singleInstance" to "singleTop" for Firebase notification -->
<!-- Previously singleInstance was necessary to prevent multiple instance of the RN app from running at the same time, but maybe no longer needed. -->
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:windowSoftInputMode="adjustResize"
android:launchMode="singleInstance">
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />

View File

@@ -3,10 +3,12 @@ package net.cozic.joplin;
import android.app.Application;
import com.facebook.react.ReactApplication;
import io.invertase.firebase.RNFirebasePackage;
import io.invertase.firebase.notifications.RNFirebaseNotificationsPackage;
import io.invertase.firebase.messaging.RNFirebaseMessagingPackage;
import org.reactnative.camera.RNCameraPackage;
import com.vinzscam.reactnativefileviewer.RNFileViewerPackage;
import net.rhogan.rnsecurerandom.RNSecureRandomPackage;
import com.dieam.reactnativepushnotification.ReactNativePushNotificationPackage;
import com.imagepicker.ImagePickerPackage;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
@@ -38,12 +40,14 @@ public class MainApplication extends Application implements ReactApplication {
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new ImageResizerPackage(),
new MainReactPackage(),
new RNCameraPackage(),
new RNFileViewerPackage(),
new RNSecureRandomPackage(),
new ReactNativePushNotificationPackage(),
new ImageResizerPackage(),
new RNFirebasePackage(),
new RNFirebaseMessagingPackage(),
new RNFirebaseNotificationsPackage(),
new RNCameraPackage(),
new RNFileViewerPackage(),
new RNSecureRandomPackage(),
new ImagePickerPackage(),
new ReactNativeDocumentPicker(),
new RNFetchBlobPackage(),

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -13,7 +13,8 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.4'
classpath 'com.android.tools.build:gradle:3.2.0' // Upgraded from 3.1.4 to 3.2.0 for Firebase
classpath 'com.google.gms:google-services:4.0.1' // For Firebase
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -23,6 +24,8 @@ buildscript {
allprojects {
repositories {
mavenLocal()
google()
jcenter() // Was added by me - still needed?
maven {
url "https://maven.google.com"
}
@@ -30,8 +33,6 @@ allprojects {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url "$rootDir/../node_modules/react-native/android"
}
jcenter() // Was added by me - still needed?
google()
}
}

View File

@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip

View File

@@ -1,12 +1,12 @@
rootProject.name = 'Joplin'
include ':react-native-firebase'
project(':react-native-firebase').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-firebase/android')
include ':react-native-camera'
project(':react-native-camera').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-camera/android')
include ':react-native-file-viewer'
project(':react-native-file-viewer').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-file-viewer/android')
include ':react-native-securerandom'
project(':react-native-securerandom').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-securerandom/android')
include ':react-native-push-notification'
project(':react-native-push-notification').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-push-notification/android')
include ':react-native-fs'
project(':react-native-fs').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fs/android')
include ':react-native-image-picker'

View File

@@ -34,6 +34,7 @@ const SyncTargetWebDAV = require('lib/SyncTargetWebDAV.js');
const SyncTargetDropbox = require('lib/SyncTargetDropbox.js');
const EncryptionService = require('lib/services/EncryptionService');
const ResourceFetcher = require('lib/services/ResourceFetcher');
const SearchEngineUtils = require('lib/services/SearchEngineUtils');
const DecryptionWorker = require('lib/services/DecryptionWorker');
const BaseService = require('lib/services/BaseService');
@@ -218,12 +219,8 @@ class BaseApplication {
} else if (parentType === Tag.modelType()) {
notes = await Tag.notes(parentId, options);
} else if (parentType === BaseModel.TYPE_SEARCH) {
let fields = Note.previewFields();
let search = BaseModel.byId(state.searches, parentId);
notes = await Note.previews(null, {
fields: fields,
anywherePattern: '*' + search.query_pattern + '*',
});
const search = BaseModel.byId(state.searches, parentId);
notes = await SearchEngineUtils.notesForQuery(search.query_pattern);
}
}

View File

@@ -7,6 +7,7 @@ const { shim } = require('lib/shim.js');
const { _ } = require('lib/locale');
const md5 = require('md5');
const MdToHtml_Katex = require('lib/MdToHtml_Katex');
const { pregQuote } = require('lib/string-utils.js');
class MdToHtml {
@@ -311,6 +312,8 @@ class MdToHtml {
output.push(t.content);
} else if (t.type === 'softbreak') {
output.push('<br/>');
} else if (t.type === 'hardbreak') {
output.push('<br/>');
} else if (t.type === 'hr') {
output.push('<hr/>');
} else {
@@ -395,8 +398,6 @@ class MdToHtml {
previousToken = t;
}
output.unshift('<!-- START_OF_DOCUMENT -->');
// Insert the extra CSS at the top of the HTML
if (!ObjectUtils.isEmpty(extraCssBlocks)) {
@@ -413,10 +414,30 @@ class MdToHtml {
return output.join('');
}
applyHighlightedKeywords_(body, keywords) {
for (let i = 0; i < keywords.length; i++) {
const k = keywords[i];
let regexString = '';
if (k.type === 'regex') {
regexString = k.value;
} else {
regexString = pregQuote(k);
}
const re = new RegExp('(^|\n|\b)(' + regexString + ')(\n|\b|$)', 'gi');
body = body.replace(re, '$1<span class="highlighted-keyword">$2</span>$3');
}
return body;
}
render(body, style, options = null) {
if (!options) options = {};
if (!options.postMessageSyntax) options.postMessageSyntax = 'postMessage';
if (!options.paddingBottom) options.paddingBottom = '0';
if (!options.highlightedKeywords) options.highlightedKeywords = [];
const cacheKey = this.makeContentKey(this.loadedResources_, body, style, options);
if (this.cachedContentKey_ === cacheKey) return this.cachedContent_;
@@ -427,38 +448,34 @@ class MdToHtml {
html: true,
});
body = this.applyHighlightedKeywords_(body, options.highlightedKeywords);
// Add `file:` protocol in linkify to allow text in the format of "file://..." to translate into
// file-URL links in html view
md.linkify.add('file:', {
validate: function (text, pos, self) {
var tail = text.slice(pos);
if (!self.re.file) {
self.re.file = new RegExp(
'^[\\/]{2,3}[\\S]+' // matches all local file URI on Win/Unix/MacOS systems including reserved characters in some OS (i.e. no OS specific sanity check)
);
}
if (self.re.file.test(tail)) {
return tail.match(self.re.file)[0].length;
}
return 0;
}
validate: function (text, pos, self) {
var tail = text.slice(pos);
if (!self.re.file) {
// matches all local file URI on Win/Unix/MacOS systems including reserved characters in some OS (i.e. no OS specific sanity check)
self.re.file = new RegExp('^[\\/]{2,3}[\\S]+');
}
if (self.re.file.test(tail)) {
return tail.match(self.re.file)[0].length;
}
return 0;
}
});
// enable file link URLs in MarkdownIt. Keeps other URL restrictions of MarkdownIt untouched.
// Format [link name](file://...)
md.validateLink = function (url) {
var BAD_PROTO_RE = /^(vbscript|javascript|data):/;
var GOOD_DATA_RE = /^data:image\/(gif|png|jpeg|webp);/;
// url should be normalized at this point, and existing entities are decoded
// url should be normalized at this point, and existing entities are decoded
var str = url.trim().toLowerCase();
return BAD_PROTO_RE.test(str) ? (GOOD_DATA_RE.test(str) ? true : false) : true;
}
// This is currently used only so that the $expression$ and $$\nexpression\n$$ blocks are translated
@@ -609,6 +626,11 @@ class MdToHtml {
padding-left: .2em;
}
.highlighted-keyword {
background-color: #F3B717;
color: black;
}
/*
This is to fix https://github.com/laurent22/joplin/issues/764
Without this, the tag attached to an equation float at an absoluate position of the page,

View File

@@ -322,6 +322,17 @@ class WebDavApi {
const output = await loadResponseJson();
// Trying to fix 404 error issue with Nginx WebDAV server.
// https://github.com/laurent22/joplin/issues/624
// https://github.com/laurent22/joplin/issues/808
// Not tested but someone confirmed it worked - https://github.com/laurent22/joplin/issues/808#issuecomment-443552858
// and fix is narrowly scoped so shouldn't affect anything outside this particular edge case.
const responseArray = this.arrayFromJson(output, ['d:multistatus', 'd:response']);
if (responseArray && responseArray.length === 1) {
const status = this.stringFromJson(output, ['d:multistatus', 'd:response', 0, 'd:propstat', 0, 'd:status', 0]);
if (status && status.indexOf('404') >= 0) throw newError('Not found', 404);
}
// Check that we didn't get for example an HTML page (as an error) instead of the JSON response
// null responses are possible, for example for DELETE calls
if (output !== null && typeof output === 'object' && !('d:multistatus' in output)) throw newError('Not a valid WebDAV response');

View File

@@ -34,7 +34,7 @@ class Dropdown extends React.Component {
// Dimensions doesn't return quite the right dimensions so leave an extra gap to make
// sure nothing is off screen.
const listMaxHeight = windowHeight;
const listHeight = Math.min(items.length * itemHeight, listMaxHeight); //Dimensions.get('window').height - this.state.headerSize.y - this.state.headerSize.height - 50;
const listHeight = Math.min(items.length * itemHeight, listMaxHeight);
const maxListTop = windowHeight - listHeight;
const listTop = Math.min(maxListTop, this.state.headerSize.y + this.state.headerSize.height);
@@ -60,10 +60,6 @@ class Dropdown extends React.Component {
const headerWrapperStyle = Object.assign({}, this.props.headerWrapperStyle ? this.props.headerWrapperStyle : {}, {
height: 35,
// borderWidth: 1,
// borderColor: '#ccc',
//paddingLeft: 20,
//paddingRight: 20,
flex: 1,
flexDirection: 'row',
alignItems: 'center',
@@ -91,6 +87,8 @@ class Dropdown extends React.Component {
}
}
if (this.props.labelTransform && this.props.labelTransform === 'trim') headerLabel = headerLabel.trim();
const closeList = () => {
this.setState({ listVisible: false });
}

View File

@@ -13,9 +13,10 @@ const globalStyle = {
colorFaded: "#777777", // For less important text
fontSizeSmaller: 14,
dividerColor: "#dddddd",
strongDividerColor: "#aaaaaa",
selectedColor: '#e5e5e5',
disabledOpacity: 0.2,
colorUrl: '#000CFF',
colorUrl: '#7B81FF',
textSelectionColor: "#0096FF",
raisedBackgroundColor: "#0080EF",
@@ -72,6 +73,9 @@ function addExtraStyles(style) {
style.lineInput = {
color: style.color,
backgroundColor: style.backgroundColor,
borderBottomWidth: 1,
borderColor: style.strongDividerColor,
paddingBottom: 0,
};
if (Platform.OS === 'ios') {
@@ -114,6 +118,7 @@ function themeStyle(theme) {
output.color = '#dddddd';
output.colorFaded = '#777777';
output.dividerColor = '#555555';
output.strongDividerColor = '#888888';
output.selectedColor = '#333333';
output.textSelectionColor = '#00AEFF';
@@ -125,6 +130,8 @@ function themeStyle(theme) {
output.htmlBackgroundColor = 'rgb(29,32,36)';
output.htmlLinkColor = 'rgb(166,166,255)';
output.colorUrl = '#7B81FF';
themeCache_[theme] = output;
return addExtraStyles(themeCache_[theme]);
}

View File

@@ -76,9 +76,18 @@ class NoteBodyViewer extends Component {
const mdOptions = {
onResourceLoaded: () => {
this.forceUpdate();
if (this.resourceLoadedTimeoutId_) {
clearTimeout(this.resourceLoadedTimeoutId_);
this.resourceLoadedTimeoutId_ = null;
}
this.resourceLoadedTimeoutId_ = setTimeout(() => {
this.resourceLoadedTimeoutId_ = null;
this.forceUpdate();
}, 100);
},
paddingBottom: '3.8em', // Extra bottom padding to make it possible to scroll past the action button (so that it doesn't overlap the text)
highlightedKeywords: this.props.highlightedKeywords,
};
let html = this.mdToHtml_.render(note ? note.body : '', this.props.webViewStyle, mdOptions);

View File

@@ -362,18 +362,25 @@ class ScreenHeaderComponent extends Component {
if (folderPickerOptions && folderPickerOptions.enabled) {
const addFolderChildren = (folders, pickerItems, indent) => {
folders.sort((a, b) => {
return a.title.toLowerCase() < b.title.toLowerCase() ? -1 : +1;
});
for (let i = 0; i < folders.length; i++) {
const f = folders[i];
pickerItems.push({ label: ' '.repeat(indent) + ' ' + Folder.displayTitle(f), value: f.id });
pickerItems = addFolderChildren(f.children, pickerItems, indent + 1);
}
return pickerItems;
}
const titlePickerItems = (mustSelect) => {
let output = [];
if (mustSelect) output.push({ label: _('Move to notebook...'), value: null });
for (let i = 0; i < this.props.folders.length; i++) {
let f = this.props.folders[i];
output.push({ label: Folder.displayTitle(f), value: f.id });
}
output.sort((a, b) => {
if (a.value === null) return -1;
if (b.value === null) return +1;
return a.label.toLowerCase() < b.label.toLowerCase() ? -1 : +1;
});
const folderTree = Folder.buildTree(this.props.folders);
output = addFolderChildren(folderTree, output, 0);
return output;
}
@@ -381,6 +388,7 @@ class ScreenHeaderComponent extends Component {
<Dropdown
items={titlePickerItems(!!folderPickerOptions.mustSelect)}
itemHeight={35}
labelTransform="trim"
selectedValue={('selectedFolderId' in folderPickerOptions) ? folderPickerOptions.selectedFolderId : null}
itemListStyle={{
backgroundColor: theme.backgroundColor,

View File

@@ -83,10 +83,10 @@ class ConfigScreenComponent extends BaseScreenComponent {
},
}
if (Platform.OS === 'ios') {
// if (Platform.OS === 'ios') {
styles.settingControl.borderBottomWidth = 1;
styles.settingControl.borderBottomColor = theme.dividerColor;
}
styles.settingControl.borderBottomColor = theme.strongDividerColor;
// }
styles.switchSettingText = Object.assign({}, styles.settingText);
styles.switchSettingText.width = '80%';

View File

@@ -62,8 +62,8 @@ class DropboxLoginScreenComponent extends BaseScreenComponent {
</TouchableOpacity>
</View>
<Text style={this.styles().stepText}>{_('Step 2: Enter the code provided by Dropbox:')}</Text>
<TextInput selectionColor={theme.textSelectionColor} value={this.state.authCode} onChangeText={this.shared_.authCodeInput_change} style={theme.lineInput}/>
<TextInput placeholder={_('Enter code here')} placeholderTextColor={theme.colorFaded} selectionColor={theme.textSelectionColor} value={this.state.authCode} onChangeText={this.shared_.authCodeInput_change} style={theme.lineInput}/>
<View style={{height:10}}></View>
<Button disabled={this.state.checkingAuthToken} title={_("Submit")} onPress={this.shared_.submit_click}></Button>
{/* Add this extra padding to make sure the view is scrollable when the keyboard is visible on small screens (iPhone SE) */}

View File

@@ -110,12 +110,9 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent {
const active = this.props.activeMasterKeyId === mk.id ? '✔' : '';
const inputStyle = {flex:1, marginRight: 10, color: theme.color};
if (Platform.OS === 'ios') {
inputStyle.borderBottomWidth = 1;
inputStyle.borderBottomColor = theme.dividerColor;
}
inputStyle.borderBottomWidth = 1;
inputStyle.borderBottomColor = theme.strongDividerColor;
return (
<View key={mk.id}>
<Text style={this.styles().titleText}>{_('Master Key %s', mk.id.substr(0,6))}</Text>

View File

@@ -37,6 +37,7 @@ const AlarmService = require('lib/services/AlarmService.js');
const { SelectDateTimeDialog } = require('lib/components/select-date-time-dialog.js');
const ShareExtension = require('react-native-share-extension').default;
const CameraView = require('lib/components/CameraView');
const SearchEngine = require('lib/services/SearchEngine');
import FileViewer from 'react-native-file-viewer';
@@ -156,12 +157,12 @@ class NoteScreenComponent extends BaseScreenComponent {
this.resourceFetcher_downloadComplete = async (resource) => {
if (!this.state.note || !this.state.note.body) return;
const resourceIds = await Note.linkedResourceIds(this.state.note.body);
if (resourceIds.indexOf(resource.id) >= 0) {
if (resourceIds.indexOf(resource.id) >= 0 && this.refs.noteBodyViewer) {
this.refs.noteBodyViewer.rebuildMd();
}
}
this.attachPhoto_onPress = this.attachPhoto_onPress.bind(this);
this.takePhoto_onPress = this.takePhoto_onPress.bind(this);
this.cameraView_onPhoto = this.cameraView_onPhoto.bind(this);
this.cameraView_onCancel = this.cameraView_onCancel.bind(this);
}
@@ -306,7 +307,7 @@ class NoteScreenComponent extends BaseScreenComponent {
showImagePicker(options) {
return new Promise((resolve, reject) => {
ImagePicker.showImagePicker(options, (response) => {
ImagePicker.launchImageLibrary(options, (response) => {
resolve(response);
});
});
@@ -380,7 +381,7 @@ class NoteScreenComponent extends BaseScreenComponent {
let resource = Resource.new();
resource.id = uuid.create();
resource.mime = mimeType;
resource.title = pickerResponse.fileName ? pickerResponse.fileName : _('Untitled');
resource.title = pickerResponse.fileName ? pickerResponse.fileName : '';
resource.file_extension = safeFileExtension(fileExtension(pickerResponse.fileName ? pickerResponse.fileName : localFilePath));
if (!resource.mime) resource.mime = 'application/octet-stream';
@@ -419,15 +420,12 @@ class NoteScreenComponent extends BaseScreenComponent {
this.setState({ note: newNote });
}
async attachImage_onPress() {
const options = {
mediaType: 'photo',
};
const response = await this.showImagePicker(options);
async attachPhoto_onPress() {
const response = await this.showImagePicker({ mediaType: 'photo' });
await this.attachFile(response, 'image');
}
attachPhoto_onPress() {
takePhoto_onPress() {
this.setState({ showCamera: true });
}
@@ -530,6 +528,7 @@ class NoteScreenComponent extends BaseScreenComponent {
let canAttachPicture = true;
if (Platform.OS === 'android' && Platform.Version < 21) canAttachPicture = false;
if (canAttachPicture) {
output.push({ title: _('Take photo'), onPress: () => { this.takePhoto_onPress(); } });
output.push({ title: _('Attach photo'), onPress: () => { this.attachPhoto_onPress(); } });
output.push({ title: _('Attach any file'), onPress: () => { this.attachFile_onPress(); } });
output.push({ isDivider: true });
@@ -583,21 +582,25 @@ class NoteScreenComponent extends BaseScreenComponent {
return <CameraView theme={this.props.theme} style={{flex:1}} onPhoto={this.cameraView_onPhoto} onCancel={this.cameraView_onCancel}/>
}
let bodyComponent = null;
if (this.state.mode == 'view') {
const onCheckboxChange = (newBody) => {
this.saveOneProperty('body', newBody);
};
let keywords = [];
if (this.props.searchQuery) {
const parsedQuery = SearchEngine.instance().parseQuery(this.props.searchQuery);
keywords = SearchEngine.instance().allParsedQueryTerms(parsedQuery);
}
bodyComponent = <NoteBodyViewer
onJoplinLinkClick={this.onJoplinLinkClick_}
ref="noteBodyViewer"
style={this.styles().noteBodyViewer}
webViewStyle={theme}
note={note}
highlightedKeywords={keywords}
onCheckboxChange={(newBody) => { onCheckboxChange(newBody) }}
/>
} else {
@@ -742,6 +745,7 @@ const NoteScreen = connect(
folderId: state.selectedFolderId,
itemType: state.selectedItemType,
folders: state.folders,
searchQuery: state.searchQuery,
theme: state.settings.theme,
sharedData: state.sharedData,
showAdvancedOptions: state.settings.showAdvancedOptions,

View File

@@ -203,7 +203,7 @@ class NotesScreenComponent extends BaseScreenComponent {
let title = parent ? parent.title : null;
const addFolderNoteButtons = this.props.selectedFolderId && this.props.selectedFolderId != Folder.conflictFolderId();
const thisComp = this;
const actionButtonComp = this.props.noteSelectionEnabled ? null : <ActionButton addFolderNoteButtons={addFolderNoteButtons} parentFolderId={this.props.selectedFolderId}></ActionButton>
const actionButtonComp = this.props.noteSelectionEnabled || !this.props.visible ? null : <ActionButton addFolderNoteButtons={addFolderNoteButtons} parentFolderId={this.props.selectedFolderId}></ActionButton>
return (
<View style={rootStyle}>

View File

@@ -9,6 +9,7 @@ const { NoteItem } = require('lib/components/note-item.js');
const { BaseScreenComponent } = require('lib/components/base-screen.js');
const { themeStyle } = require('lib/components/global-style.js');
const { dialogs } = require('lib/dialogs.js');
const SearchEngineUtils = require('lib/services/SearchEngineUtils');
const DialogBox = require('react-native-dialogbox').default;
class SearchScreenComponent extends BaseScreenComponent {
@@ -105,17 +106,22 @@ class SearchScreenComponent extends BaseScreenComponent {
let notes = []
if (query) {
let p = query.split(' ');
let temp = [];
for (let i = 0; i < p.length; i++) {
let t = p[i].trim();
if (!t) continue;
temp.push(t);
}
notes = await SearchEngineUtils.notesForQuery(query);
notes = await Note.previews(null, {
anywherePattern: '*' + temp.join('*') + '*',
});
// Keeping the code below in case of compatibility issue with old versions
// of Android and SQLite FTS.
// let p = query.split(' ');
// let temp = [];
// for (let i = 0; i < p.length; i++) {
// let t = p[i].trim();
// if (!t) continue;
// temp.push(t);
// }
// notes = await Note.previews(null, {
// anywherePattern: '*' + temp.join('*') + '*',
// });
}
if (!this.isMounted_) return;

View File

@@ -49,8 +49,6 @@ class TagScreenComponent extends BaseScreenComponent {
render() {
let title = tag ? tag.title : '';
// <ActionButton addFolderNoteButtons={true} parentFolderId={this.props.selectedFolderId}></ActionButton>
const { navigate } = this.props.navigation;
return (
<View style={this.styles().screen}>

View File

@@ -44,7 +44,7 @@ class WelcomeScreenComponent extends BaseScreenComponent {
<View style={this.rootStyle(this.props.theme).root} >
<ScreenHeader title={_('Welcome')}/>
<Text style={this.styles().message}>{message}</Text>
<ActionButton addFolderNoteButtons={true}/>
<ActionButton addFolderNoteButtons={true} parentFolderId={this.props.selectedFolderId}/>
</View>
);
}
@@ -56,6 +56,7 @@ const WelcomeScreen = connect(
return {
folders: state.folders,
theme: state.settings.theme,
selectedFolderId: state.selectedFolderId,
};
}
)(WelcomeScreenComponent)

View File

@@ -1,3 +1,5 @@
const { filename, fileExtension } = require('lib/path-utils');
class FsDriverBase {
async isDirectory(path) {
@@ -19,6 +21,23 @@ class FsDriverBase {
return output;
}
async findUniqueFilename(name) {
let counter = 1;
let nameNoExt = filename(name, true);
let extension = fileExtension(name);
if (extension) extension = '.' + extension;
let nameToTry = nameNoExt + extension;
while (true) {
const exists = await this.exists(nameToTry);
if (!exists) return nameToTry;
nameToTry = nameNoExt + ' (' + counter + ')' + extension;
counter++;
if (counter >= 1000) nameToTry = nameNoExt + ' (' + ((new Date()).getTime()) + ')' + extension;
if (counter >= 10000) throw new Error('Cannot find unique title');
}
}
}
module.exports = FsDriverBase;

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