1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-08-24 20:19:10 +02:00

Compare commits

...

75 Commits

Author SHA1 Message Date
Laurent Cozic
7b85c33213 CLI v1.0.117 2018-10-29 23:24:19 +00:00
Laurent Cozic
4b4d0e8b25 Update website 2018-10-24 21:14:38 +01:00
Laurent Cozic
4fb6af3c62 Android release v1.0.175 2018-10-24 19:59:37 +01:00
Laurent Cozic
d7ffe7e294 Electron release v1.0.114 2018-10-24 19:52:07 +01:00
Laurent Cozic
3ff139d445 Merge branch 'master' of github.com:laurent22/joplin 2018-10-24 19:51:16 +01:00
Laurent Cozic
40443e0134 Android v153 2018-10-24 19:50:26 +01:00
Laurent Cozic
1f927c1285 iOS v26 2018-10-24 19:48:09 +01:00
Andros Fenollosa
5e82e62335 Linux script install: implement previous updates (#905)
Avoid an erroneous update and prevent installation icon only on gnome.
2018-10-24 19:17:18 +01:00
Yannis Mitsos
de954827df Support FreeBSD in terminal (#896) 2018-10-24 19:16:15 +01:00
Laurent Cozic
2cb24bf198 Mobile: Fixes #902: Don't change existing note when sharing with mobile app 2018-10-24 19:10:05 +01:00
Laurent Cozic
739a6a4a9c Documentation 2018-10-24 18:47:04 +01:00
Laurent Cozic
dfcf1193dc Electron: Handle internal anchors 2018-10-17 08:01:18 +01:00
Laurent Cozic
c72f92e22f Additional info 2018-10-15 18:40:11 +01:00
Laurent Cozic
f6d01ce7e1 Android release v1.0.174 2018-10-15 00:01:36 +01:00
Laurent Cozic
fed9700587 Merge branch 'master' of github.com:laurent22/joplin 2018-10-15 00:00:13 +01:00
Laurent Cozic
12a3a9a89e android release 2018-10-14 23:59:42 +01:00
Laurent Cozic
590c62c371 Merge branch 'fixing_android_build' 2018-10-14 23:56:42 +01:00
Laurent Cozic
df41f64b3c Revert "trying to fix android build"
This reverts commit 621d0260f4.
2018-10-14 21:47:12 +01:00
Laurent Cozic
1849355245 Android: Tryinc to fix release builkd 2018-10-14 21:42:34 +01:00
Laurent Cozic
fa1b471ea4 Android: Tryinc to fix release builkd 2018-10-14 21:41:29 +01:00
Laurent Cozic
0a67f8c947 Revert "Android: Updated project to build on macOS"
This reverts commit b547f9aa13.
2018-10-14 20:44:05 +01:00
Laurent Cozic
621d0260f4 trying to fix android build 2018-10-14 19:57:30 +01:00
Laurent Cozic
f93fca7c5b Revert "Android: Updated project to build on macOS"
This reverts commit b547f9aa13.
2018-10-13 11:35:21 +01:00
Laurent Cozic
f4d830c2ef Android release v1.0.151 2018-10-13 11:09:03 +01:00
Laurent Cozic
1aa2844efa Android release v1.0.148 2018-10-13 10:39:55 +01:00
Laurent Cozic
f22b2adaad Mobile: Improved camera attachment 2018-10-13 10:32:44 +01:00
Laurent Cozic
b547f9aa13 Android: Updated project to build on macOS 2018-10-13 00:30:41 +01:00
Laurent Cozic
e4166e9da7 Electron: Fixes #312 (maybe): Removed power saving feature, which wasn\'t doing anything and added a possible fix to the UI freezing issue on Linux 2018-10-12 23:44:00 +01:00
Laurent Cozic
1634fdb421 Merge branch 'master' of github.com:laurent22/joplin 2018-10-12 23:26:11 +01:00
Laurent Cozic
7f51035f91 Mobile: Reload note when resource got downloaded. Also fixed Android build script to make it work in macOS. 2018-10-12 23:25:11 +01:00
ebayer
70e71cbc2a Mobile: Fixes #856: Add option to open source url (#872)
* Mobile: Fixes #856: Add option to open source url

* Mobile: Fixes #856: Change menu wording for opening source url
2018-10-12 19:30:00 +01:00
Laurent Cozic
ffd03bf34c Merge branch 'master' of github.com:laurent22/joplin 2018-10-11 17:53:40 +01:00
Christian Baer
f59a3dee78 Fixed some more inconsistencies in german language file. (#855)
* Fixed some typos and inconsistencies in the german language file and added missing translations.

* Fixed some more inconsistencies in german language file.
2018-10-11 17:21:12 +01:00
Timothy Cyrus
3ba3037242 Update README.md (#874) 2018-10-11 17:20:42 +01:00
Helmut K. C. Tessarek
dbb269fef6 add support for webp images (#858)
fixes #848
2018-10-11 17:18:33 +01:00
Laurent Cozic
e209189faa All: Added resource test units 2018-10-11 17:18:24 +01:00
Laurent Cozic
2d7065cde2 Android release v1.0.143 2018-10-10 20:36:42 +01:00
Laurent Cozic
59f5972c93 Electron release v1.0.113 2018-10-10 20:32:45 +01:00
Laurent Cozic
8bac5275c3 All: Fixed fetch logic - mark resource as fetched by default, unless it comes from sync 2018-10-10 18:53:09 +01:00
Helmut K. C. Tessarek
58d748e235 fix permissions for shell scripts (add executable flag) (#866) 2018-10-09 22:58:18 +01:00
Laurent Cozic
e69ac3e62a Android release v1.0.142 2018-10-09 22:05:29 +01:00
Laurent Cozic
7fc8ac4c0f Electron release v1.0.112 2018-10-09 22:02:47 +01:00
Laurent Cozic
069dce69cd Mobile: Added support for ResourceFetcher service 2018-10-09 22:01:50 +01:00
Laurent Cozic
3bdf621026 Api: Document tags parameter 2018-10-08 19:38:27 +01:00
Laurent Cozic
2f62897fb6 All: Improved resource side loading 2018-10-08 19:11:53 +01:00
Laurent Cozic
dbdd602f50 All: Created ResourceFetcher class to handle resource downloads 2018-10-08 07:36:45 +01:00
Laurent Cozic
d66fa87b2b All: Allow excluding certain keys during sync 2018-10-07 20:18:43 +01:00
Laurent Cozic
124a959c8d All: Simplifying serialisation of base items 2018-10-07 20:11:33 +01:00
Laurent Cozic
127dce1cd6 Fixed typo 2018-10-07 19:28:19 +01:00
Laurent Cozic
44986a35a4 Android: Fix crash when attaching certain files 2018-10-07 18:55:49 +01:00
Laurent Cozic
ea516301fd Udpated German translation 2018-10-05 19:53:54 +01:00
Christian Baer
90b684457a Fixed some typos and inconsistencies in the german language file and added missing translations. (#854) 2018-10-05 19:53:13 +01:00
Laurent Cozic
8517e2aa42 Mobile: Fixes #840: Images were not being displayed right after being attached in view mode 2018-10-05 19:49:36 +01:00
Laurent Cozic
b880be8b7c All: Fixes #853: Replace characters to equivalent US-ASCII ones when exporting files 2018-10-05 17:53:55 +01:00
Laurent Cozic
57fd1a7588 Electron: Prevent URLs added via A tag from being opened inside app 2018-10-05 18:21:23 +00:00
Laurent Cozic
5ed458f634 Electron: Fixed potential crash that can happen if editor is not ready 2018-10-05 18:19:47 +00:00
Laurent Cozic
ac12143d00 All: Fixes #671: Make string translatable 2018-10-05 18:17:49 +00:00
Helmut K. C. Tessarek
b6c36d1961 fix file permissions (#851) 2018-10-04 22:32:19 +01:00
FoxMaSk
3c2de70baa add shared folder (#850)
shared 📁 is needed, so the encryption menu will be translated
2018-10-04 22:30:48 +01:00
Laurent Cozic
f6c5620682 Electron: Resolves #751: Allow switching between todo and note when multiple notes are selected 2018-10-04 18:34:30 +01:00
Laurent Cozic
79b6f64bd0 Merge branch 'master' of github.com:laurent22/joplin 2018-10-04 18:01:23 +01:00
Laurent Cozic
ed89f55bff Electron: Fixes #847: Prevent view from scrolling to top when clicking checkbox and editor not visible 2018-10-04 17:56:39 +01:00
Laurent Cozic
8841a92142 Update README.md 2018-10-04 09:10:46 +01:00
Laurent Cozic
0bd19c97eb Merge branch 'master' of github.com:laurent22/joplin 2018-10-04 08:53:11 +01:00
Laurent Cozic
2fd026d107 Mentioned Hacktoberfest 🎃 in Readme 2018-10-04 08:51:48 +01:00
Laurent Cozic
5e7eb37ca7 Merge branch 'master' of github.com:laurent22/joplin 2018-10-04 08:18:19 +01:00
Laurent Cozic
6b10d5d821 Api: Fixes #843: Fixed regression that was preventing resource metadata from being downloaded 2018-10-04 08:17:53 +01:00
Laurent Cozic
0f4dbfbcbf Merge pull request #841 from shorty2380/patch-1
Joplin_install_and_update.sh
2018-10-04 08:10:51 +01:00
Laurent Cozic
99493174ec API: Fixed handling of PUT method and log errors to file 2018-10-04 08:05:22 +01:00
Patrick Petermann
333253fd4f Joplin_install_and_update.sh
The script was original written for “Ubuntu – Gnome” only. I change it a little bit to support more distributions and desktop enviroments.

This script could be used to install and update Joplin at several Linux distributions. I could test this script with “Fedora 28 – Cinnamon” and “Mint LMDE 3”.   There are a lot of requests how to install / start Joplin at Linux in the FAQ’s. Hopefully this could help the people
2018-10-03 20:48:14 +02:00
Laurent Cozic
01470e8d3b Android release v1.0.141 2018-10-03 08:30:19 +01:00
Laurent Cozic
bda2fe6717 Merge branch 'master' of github.com:laurent22/joplin 2018-10-03 08:19:15 +01:00
Laurent Cozic
d1f4c5be18 Disable non-working ShareExtension on iOS 2018-10-03 08:17:37 +01:00
Laurent Cozic
377adea51d Android release v1.0.140 2018-10-02 18:19:27 +01:00
Laurent Cozic
30165e8d6a Electron: Fixes #798: Enable Select All shortcut in macOS 2018-10-02 17:45:39 +01:00
140 changed files with 2506 additions and 550 deletions

0
.gitignore vendored Executable file → Normal file
View File

View File

@@ -21,7 +21,6 @@ const { _, setLocale, defaultLocale, closestSupportedLocale } = require('lib/loc
const os = require('os');
const fs = require('fs-extra');
const { cliUtils } = require('./cli-utils.js');
const EventEmitter = require('events');
const Cache = require('lib/Cache');
class Application extends BaseApplication {

View File

@@ -152,6 +152,11 @@ class Command extends BaseCommand {
type: Database.enumId('fieldType', 'text'),
description: 'If an image is provided, you can also specify an optional rectangle that will be used to crop the image. In format `{ x: x, y: y, width: width, height: height }`',
});
tableFields.push({
name: 'tags',
type: Database.enumId('fieldType', 'text'),
description: 'Comma-separated list of tags. eg. `tag1,tag2`.',
});
}
lines.push('# ' + toTitleCase(tableName));

View File

@@ -4,6 +4,7 @@ const { _ } = require('lib/locale.js');
const { OneDriveApiNodeUtils } = require('./onedrive-api-node-utils.js');
const Setting = require('lib/models/Setting.js');
const BaseItem = require('lib/models/BaseItem.js');
const ResourceFetcher = require('lib/services/ResourceFetcher');
const { Synchronizer } = require('lib/synchronizer.js');
const { reg } = require('lib/registry.js');
const { cliUtils } = require('./cli-utils.js');
@@ -191,6 +192,14 @@ class Command extends BaseCommand {
}
}
// When using the tool in command line mode, the ResourceFetcher service is
// not going to be running in the background, so the resources need to be
// explicitely downloaded below.
if (!app().hasGui()) {
await ResourceFetcher.instance().fetchAll();
await ResourceFetcher.instance().waitForAllFinished();
}
await app().refreshCurrentFolder();
} catch (error) {
cleanUp();

View File

@@ -633,6 +633,10 @@ msgstr "Retalla"
msgid "Paste"
msgstr "Enganxa"
#, fuzzy
msgid "Select all"
msgstr "Seleccioneu una data"
msgid "Bold"
msgstr ""
@@ -948,6 +952,14 @@ msgstr "Copia"
msgid "Switch between note and to-do type"
msgstr "Alterna entre el tipus nota i tasques pendents"
#, fuzzy
msgid "Switch to note type"
msgstr "Alterna entre el tipus nota i tasques pendents"
#, fuzzy
msgid "Switch to to-do type"
msgstr "Alterna entre el tipus nota i tasques pendents"
msgid "Copy Markdown link"
msgstr "Copia l'enllaç Markdown"
@@ -1541,6 +1553,10 @@ msgstr "Voleu moure %d notes al bloc de notes «%s»?"
msgid "Press to set the decryption password."
msgstr "Premeu per a establir la contrasenya de desxifratge."
#, fuzzy
msgid "Clear alarm"
msgstr "Estableix una alarma"
msgid "Save alarm"
msgstr "Desa l'alarma"
@@ -1557,6 +1573,36 @@ msgstr "Cancel·la la sincronització"
msgid "Decrypting items: %d/%d"
msgstr "Elements obtinguts: %d/%d."
#, fuzzy
msgid "Checking... Please wait."
msgstr "S'està cancel·lant... Espereu."
#, fuzzy
msgid "Success! Synchronisation configuration appears to be correct."
msgstr "Comprova la configuració de la sincronització"
msgid ""
"Error. Please check that URL, username, password, etc. are correct and that "
"the sync target is accessible. The reported error was:"
msgstr ""
#, fuzzy
msgid "The application has been authorised!"
msgstr "L'aplicació s'ha autoritzat correctament."
#, javascript-format
msgid ""
"Could not authorise application:\n"
"\n"
"%s\n"
"\n"
"Please try again."
msgstr ""
#, fuzzy, javascript-format
msgid "Decrypted items: %s / %s"
msgstr "Elements obtinguts: %d/%d."
msgid "New tags:"
msgstr "Etiquetes noves:"

View File

@@ -615,6 +615,10 @@ msgstr "Vyjmout"
msgid "Paste"
msgstr "Vložit"
#, fuzzy
msgid "Select all"
msgstr "Vybrat datum"
msgid "Bold"
msgstr ""
@@ -920,6 +924,14 @@ msgstr "Kopírovat"
msgid "Switch between note and to-do type"
msgstr "Přepnout mezi poznámkou a to-do"
#, fuzzy
msgid "Switch to note type"
msgstr "Přepnout mezi poznámkou a to-do"
#, fuzzy
msgid "Switch to to-do type"
msgstr "Přepnout mezi poznámkou a to-do"
#, fuzzy
msgid "Copy Markdown link"
msgstr "Markdown"
@@ -1516,6 +1528,10 @@ msgstr "Přesunout poznámky %d do zápisníku \"%s\"?"
msgid "Press to set the decryption password."
msgstr "Stiskněte pro zadání hesla k dešifrování."
#, fuzzy
msgid "Clear alarm"
msgstr "Nastavit alarm"
#, fuzzy
msgid "Save alarm"
msgstr "Nastavit alarm"
@@ -1533,6 +1549,36 @@ msgstr "Zrušit synchronizaci"
msgid "Decrypting items: %d/%d"
msgstr "Získané položky: %d/%d."
#, fuzzy
msgid "Checking... Please wait."
msgstr "Zastavuji, chvíli strpení."
#, fuzzy
msgid "Success! Synchronisation configuration appears to be correct."
msgstr "Zkontrolujte nastavení synchronizace"
msgid ""
"Error. Please check that URL, username, password, etc. are correct and that "
"the sync target is accessible. The reported error was:"
msgstr ""
#, fuzzy
msgid "The application has been authorised!"
msgstr "Aplikace byla úspěšně autorizována."
#, javascript-format
msgid ""
"Could not authorise application:\n"
"\n"
"%s\n"
"\n"
"Please try again."
msgstr ""
#, fuzzy, javascript-format
msgid "Decrypted items: %s / %s"
msgstr "Získané položky: %d/%d."
msgid "New tags:"
msgstr "Nové tagy:"

View File

@@ -620,6 +620,10 @@ msgstr "Klip"
msgid "Paste"
msgstr "Indsæt"
#, fuzzy
msgid "Select all"
msgstr "Vælg dato"
msgid "Bold"
msgstr ""
@@ -929,6 +933,14 @@ msgstr "Kopier"
msgid "Switch between note and to-do type"
msgstr "Skift mellem note- og opgave type"
#, fuzzy
msgid "Switch to note type"
msgstr "Skift mellem note- og opgave type"
#, fuzzy
msgid "Switch to to-do type"
msgstr "Skift mellem note- og opgave type"
#, fuzzy
msgid "Copy Markdown link"
msgstr "Markdown"
@@ -1525,6 +1537,10 @@ msgstr "Flyt %d noter til notesbogen \"%s\"?"
msgid "Press to set the decryption password."
msgstr "Klik for at gemme dekrypterings kodeord."
#, fuzzy
msgid "Clear alarm"
msgstr "Indstil alarm"
#, fuzzy
msgid "Save alarm"
msgstr "Indstil alarm"
@@ -1542,6 +1558,36 @@ msgstr "Afbryd synkronisering"
msgid "Decrypting items: %d/%d"
msgstr "Hentede emner: %d/%d."
#, fuzzy
msgid "Checking... Please wait."
msgstr "Annullerer... Vent venligst."
#, fuzzy
msgid "Success! Synchronisation configuration appears to be correct."
msgstr "Check synkroniserings Indstillinger"
msgid ""
"Error. Please check that URL, username, password, etc. are correct and that "
"the sync target is accessible. The reported error was:"
msgstr ""
#, fuzzy
msgid "The application has been authorised!"
msgstr "Denne app er succesfuldt godkendt."
#, javascript-format
msgid ""
"Could not authorise application:\n"
"\n"
"%s\n"
"\n"
"Please try again."
msgstr ""
#, fuzzy, javascript-format
msgid "Decrypted items: %s / %s"
msgstr "Hentede emner: %d/%d."
msgid "New tags:"
msgstr "Nye tags:"

View File

@@ -15,11 +15,12 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.1.1\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
msgid "To delete a tag, untag the associated notes."
msgstr ""
"Um ein Schlagwort zu löschen, entferne es bei allen damit verbundenen "
"Notizen."
"Um ein Tag zu löschen, entferne es bei allen damit verbundenen Notizen."
msgid "Please select the note or notebook to be deleted first."
msgstr ""
@@ -197,8 +198,8 @@ msgid ""
"Exports Joplin data to the given path. By default, it will export the "
"complete database including notebooks, notes, tags and resources."
msgstr ""
"Exportiert Joplin Dateien in den angegebenen Pfad. Standardmäßig wird die "
"komplette Datenbank inklusive Notizbüchern, Notizen, Markierungen und "
"Exportiert Joplin-Dateien in den angegebenen Pfad. Standardmäßig wird die "
"komplette Datenbank inklusive Notizbüchern, Notizen, Schlagwörtern und "
"Anhängen exportiert."
#, javascript-format
@@ -306,7 +307,7 @@ msgstr "Anhänge: %d."
#, javascript-format
msgid "Tagged: %d."
msgstr "Markiert: %d."
msgstr "Verschlagwortet: %d."
msgid "Importing notes..."
msgstr "Importiere Notizen..."
@@ -475,16 +476,15 @@ msgstr "Starte Synchronisation..."
msgid "Cancelling... Please wait."
msgstr "Abbrechen… Bitte warten."
#, 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 "
"`tag list` can be used to list all the tags (use -l for long option)."
msgstr ""
"<tag-command> kann \"add\", \"remove\" or \"list\" sein, um ein [Schlagwort] "
"zu [Notiz] zuzuweisen oder zu entfernen, oder um mit [Schlagwort] markierte "
"Notizen anzuzeigen. Mit dem Befehl `tag list` können alle Schlagwörter "
"angezeigt werden."
"<tag-command> kann add“, „remove“ oder „list sein um [tag] zu [note] "
"hinzuzufügen oder zu entfernen, oder um die Notizen aufzulisten die [tag] "
"zugeordnet sind. Der Befehl `tag list` kann benutzt werden, um alle "
"Schlagwörter anzuzeigen (nutze -l für die lange Option)."
#, javascript-format
msgid "Invalid command: \"%s\""
@@ -644,6 +644,9 @@ msgstr "Ausschneiden"
msgid "Paste"
msgstr "Einfügen"
msgid "Select all"
msgstr "Alle auswählen"
msgid "Bold"
msgstr "Fett"
@@ -666,7 +669,7 @@ msgid "Toggle sidebar"
msgstr "Seitenleiste ein/aus"
msgid "Toggle editor layout"
msgstr "Editor Layout umschalten"
msgstr "Editor-Layout umschalten"
msgid "Tools"
msgstr "Werkzeuge"
@@ -675,7 +678,7 @@ msgid "Synchronisation status"
msgstr "Status der Synchronisation"
msgid "Web clipper options"
msgstr "Web Clipper Optionen"
msgstr "Web-Clipper Optionen"
msgid "Encryption options"
msgstr "Verschlüsselungsoptionen"
@@ -719,7 +722,7 @@ msgid "Current version is up-to-date."
msgstr "Die aktuelle Version ist up-to-date."
msgid "An update is available, do you want to download it now?"
msgstr "Es ist ein Update verfügbar! Soll es jetzt heruntergeladen werden?"
msgstr "Es ist ein Update verfügbar. Soll es jetzt heruntergeladen werden?"
msgid "Yes"
msgstr "Ja"
@@ -728,11 +731,11 @@ msgid "No"
msgstr "Nein"
msgid "Token has been copied to the clipboard!"
msgstr ""
msgstr "Token wurde in die Zwischenablage kopiert."
msgid "The web clipper service is enabled and set to auto-start."
msgstr ""
"Der Webclipperservice ist bereits aktiviert und auf Autostart eingestellt."
"Der Web-Clipper-Service ist bereits aktiviert und auf Autostart eingestellt."
#, javascript-format
msgid "Status: Started on port %d"
@@ -743,26 +746,26 @@ msgid "Status: %s"
msgstr "Status: %s"
msgid "Disable Web Clipper Service"
msgstr "Web Clipper Service deaktivieren"
msgstr "Web-Clipper-Service deaktivieren"
msgid "The web clipper service is not enabled."
msgstr "Der Web Clipper Service ist nicht aktiviert."
msgstr "Der Web-Clipper-Service ist nicht aktiviert."
msgid "Enable Web Clipper Service"
msgstr "Web Clipper Service aktivieren"
msgstr "Web-Clipper-Service aktivieren"
msgid ""
"Joplin Web Clipper allows saving web pages and screenshots from your browser "
"to Joplin."
msgstr ""
"Joplin Web Clipper erlaubt im Browser das Speichern von Webseiten und "
"Joplin Web-Clipper erlaubt im Browser das Speichern von Webseiten und "
"Screenshots nach Joplin."
msgid "In order to use the web clipper, you need to do the following:"
msgstr "Um den Web Clipper zu benutzen, musst du folgendes machen:"
msgstr "Um den Web-Clipper zu benutzen, musst du folgendes machen:"
msgid "Step 1: Enable the clipper service"
msgstr "Schritt 1: Clipper Service aktivieren"
msgstr "Schritt 1: Clipper-Service aktivieren"
msgid ""
"This service allows the browser extension to communicate with Joplin. When "
@@ -771,7 +774,7 @@ msgid ""
msgstr ""
"Dieser Service erlaubt es der Browser-Erweiterung mit Joplin zu "
"kommunizieren. Beim Aktivieren kann deine Firewall dich nach der Erlaubnis "
"bitten, dass Joplin auf einen bestimmten Port mithört."
"bitten, dass Joplin auf einen bestimmten Port mithören darf."
msgid "Step 2: Install the extension"
msgstr "Schritt 2: Erweiterung installieren"
@@ -781,20 +784,21 @@ msgstr ""
"Lade die entsprechende Erweiterung für deinen Browser herunter und "
"installiere sie:"
#, fuzzy
msgid "Advanced options"
msgstr "Erweiterte Optionen anzeigen"
msgstr "Erweiterte Optionen"
msgid "Authorisation token:"
msgstr ""
msgstr "Autorisierung-Token:"
msgid "Copy token"
msgstr ""
msgstr "Token kopieren"
msgid ""
"This authorisation token is only needed to allow third-party applications to "
"access Joplin."
msgstr ""
"Dieses Autorisierung-Token wird nur benötigt, um Drittanbieter-Anwendungen "
"Zugriff auf Joplin zu gewähren."
msgid "Check synchronisation configuration"
msgstr "Überprüfen der Synchronisationseinstellungen"
@@ -874,13 +878,14 @@ msgstr ""
"Notizbücher ursprünglich verschlüsselt wurden."
msgid "Missing Master Keys"
msgstr "Fehlender Master-Key"
msgstr "Fehlender Hauptschlüssel"
msgid ""
"The master keys with these IDs are used to encrypt some of your items, "
"however the application does not currently have access to them. It is likely "
"they will eventually be downloaded via synchronisation."
msgstr "Die Master-Keas dieser IDs werden für die Verschlüsselung einiger ..."
msgstr ""
"Die Hauptschlüssel dieser IDs werden für die Verschlüsselung einiger ..."
msgid ""
"For more information about End-To-End Encryption (E2EE) and advices on how "
@@ -912,10 +917,10 @@ msgid "Please create a notebook first"
msgstr "Bitte erstelle zuerst ein Notizbuch"
msgid "Notebook title:"
msgstr "Notizbuch Titel:"
msgstr "Notizbuch-Titel:"
msgid "Add or remove tags:"
msgstr "Füge hinzu oder entferne Schlagwörter:"
msgstr "Schlagwörter hinzufügen oder entfernen:"
msgid "Separate each tag by a comma."
msgstr "Trenne jedes Schlagwort mit einem Komma."
@@ -960,6 +965,12 @@ msgstr "%s kopieren"
msgid "Switch between note and to-do type"
msgstr "Zwischen Notiz und To-Do Typ wechseln"
msgid "Switch to note type"
msgstr "Zu Notiz-Typ wechseln"
msgid "Switch to to-do type"
msgstr "Zu To-Do-Typ wechseln"
msgid "Copy Markdown link"
msgstr "Markdown-Link kopieren"
@@ -971,8 +982,7 @@ msgstr "Notizen löschen?"
msgid "No notes in here. Create one by clicking on \"New note\"."
msgstr ""
"Hier sind noch keine Notizen. Erstelle eine, indem du auf \"Neue Notiz\" "
"drückst."
"Keine Notizen vorhanden. Erstelle eine, indem du auf \"Neue Notiz\" drückst."
msgid ""
"There is currently no notebook. Create one by clicking on \"New notebook\"."
@@ -984,7 +994,7 @@ msgid "Location"
msgstr "Ablageort"
msgid "URL"
msgstr ""
msgstr "URL"
msgid "Open..."
msgstr "Öffne..."
@@ -994,7 +1004,7 @@ msgid "This file could not be opened: %s"
msgstr "Dieses Notizbuch konnte nicht geöffnet werden: %s"
msgid "Save as..."
msgstr "Sichern unter..."
msgstr "Speichern unter..."
msgid "Copy path to clipboard"
msgstr "Pfad in Zwischenablage kopieren"
@@ -1030,7 +1040,7 @@ msgid "Attach file"
msgstr "Datei anhängen"
msgid "Tags"
msgstr "Markierungen"
msgstr "Schlagwörter"
msgid "Set alarm"
msgstr "Alarm erstellen"
@@ -1066,9 +1076,8 @@ msgstr "Horizontale Linie"
msgid "Click to stop external editing"
msgstr "Klicken Sie hier, um die externe Bearbeitung anzuhalten"
#, fuzzy
msgid "Watching..."
msgstr "Ansehen…"
msgstr "Zuschauend…"
msgid "to-do"
msgstr "To-Do"
@@ -1087,22 +1096,22 @@ msgid "Clear"
msgstr "Leeren"
msgid "OneDrive Login"
msgstr "OneDrive Anmeldung"
msgstr "OneDrive-Anmeldung"
msgid "Dropbox Login"
msgstr "Dropbox Anmeldung"
msgstr "Dropbox-Anmeldung"
msgid "Options"
msgstr "Optionen"
msgid "Synchronisation Status"
msgstr "Synchronisations Status"
msgstr "Synchronisations-Status"
msgid "Encryption Options"
msgstr "Verschlüsselungsoptionen"
msgid "Clipper Options"
msgstr "Clipper Einstellungen"
msgstr "Clipper-Einstellungen"
msgid "Remove this tag from all the notes?"
msgstr "Dieses Schlagwort von allen Notizen entfernen?"
@@ -1121,7 +1130,7 @@ msgstr "Notizbücher"
msgid "Please select where the sync status should be exported to"
msgstr ""
"Bitte wähle aus, wohin der Synchronisations Status exportiert werden soll"
"Bitte wähle aus, wohin der Synchronisations-Status exportiert werden soll"
#, javascript-format
msgid "Usage: %s"
@@ -1151,11 +1160,11 @@ msgstr "WebDAV"
#, javascript-format
msgid "Unknown log level: %s"
msgstr "Unbekanntes Log Level: %s"
msgstr "Unbekanntes Log-Level: %s"
#, javascript-format
msgid "Unknown level ID: %s"
msgstr "Unbekannte Level ID: %s"
msgstr "Unbekannte Level-ID: %s"
msgid ""
"Cannot refresh token: authentication data is missing. Starting the "
@@ -1177,7 +1186,7 @@ msgstr ""
"Dieser Fehler kommt oft vor, wenn OneDrive Business benutzt wird, das leider "
"nicht unterstützt wird.\n"
"\n"
"Bitte benutze stattdessen einen normalen OneDrive Account."
"Bitte benutze stattdessen einen normalen OneDrive-Account."
#, javascript-format
msgid "Cannot access %s"
@@ -1193,11 +1202,11 @@ msgstr "Lokale Objekte aktualisiert: %d."
#, javascript-format
msgid "Created remote items: %d."
msgstr "Remote Objekte erstellt: %d."
msgstr "Remote-Objekte erstellt: %d."
#, javascript-format
msgid "Updated remote items: %d."
msgstr "Remote Objekte aktualisiert: %d."
msgstr "Remote-Objekte aktualisiert: %d."
#, javascript-format
msgid "Deleted local items: %d."
@@ -1205,7 +1214,7 @@ msgstr "Lokale Objekte gelöscht: %d."
#, javascript-format
msgid "Deleted remote items: %d."
msgstr "Remote Objekte gelöscht: %d."
msgstr "Remote-Objekte gelöscht: %d."
#, javascript-format
msgid "Fetched items: %d/%d."
@@ -1216,7 +1225,7 @@ msgid "State: %s."
msgstr "Status: %s."
msgid "Cancelling..."
msgstr "Abbrechen…"
msgstr "Breche ab…"
#, javascript-format
msgid "Completed: %s"
@@ -1251,7 +1260,7 @@ msgstr "Kann Notizbuch nicht an diesen Ort verschieben"
#, javascript-format
msgid "Notebooks cannot be named \"%s\", which is a reserved title."
msgstr ""
"Notizbuch kann nicht \"%s\" genannt werden. Dies ist ein reservierter Titel."
"Notizbuch kann nicht \"%s\" genannt werden. Dieser Name ist reserviert.."
msgid "title"
msgstr "Titel"
@@ -1298,7 +1307,7 @@ msgid "Uncompleted to-dos on top"
msgstr "Zeige unvollständige To-Dos an oberster Stelle"
msgid "Show completed to-dos"
msgstr "Abgeschlossene ToDos anzeigen"
msgstr "Abgeschlossene To-Dos anzeigen"
msgid "Sort notes by"
msgstr "Sortiere Notizen nach"
@@ -1322,7 +1331,7 @@ msgid "When creating a new note:"
msgstr "Wenn eine neue Notiz erstellt wird:"
msgid "Show tray icon"
msgstr "Zeige Tray Icon"
msgstr "Zeige Tray-Icon"
msgid "Note: Does not work in all desktop environments."
msgstr "Hinweis: Funktioniert nicht in allen Desktopumgebungen."
@@ -1387,7 +1396,7 @@ msgstr ""
"dokumentiert) bezeichnet werden."
msgid "Directory to synchronise with (absolute path)"
msgstr "Verzeichnis zum synchronisieren (absoluter Pfad)"
msgstr "Verzeichnis mit dem synchronisiert werden soll (absoluter Pfad)"
msgid ""
"The path to synchronise with when file system synchronisation is enabled. "
@@ -1397,7 +1406,7 @@ msgstr ""
"Synchronisation aktiviert ist. Siehe `sync.target`."
msgid "Nextcloud WebDAV URL"
msgstr "Nextcloud WebDAV URL"
msgstr "Nextcloud WebDAV-URL"
#, javascript-format
msgid ""
@@ -1410,19 +1419,19 @@ msgstr ""
"die FAQs hierzu: %s"
msgid "Nextcloud username"
msgstr "Nextcloud Benutzername"
msgstr "Nextcloud-Benutzername"
msgid "Nextcloud password"
msgstr "Nextcloud Passwort"
msgstr "Nextcloud-Passwort"
msgid "WebDAV URL"
msgstr "WebDAV URL"
msgstr "WebDAV-URL"
msgid "WebDAV username"
msgstr "WebDAV Benutzername"
msgstr "WebDAV-Benutzername"
msgid "WebDAV password"
msgstr "WebDAV Passwort"
msgstr "WebDAV-Passwort"
msgid "Custom TLS certificates"
msgstr "Benutzerdefinierte TLS-Zertifikate"
@@ -1434,9 +1443,9 @@ msgid ""
"changes before clicking on \"Check synchronisation configuration\"."
msgstr ""
"Kommagetrennte Liste von Pfaden zu Verzeichnissen, aus denen die Zertifikate "
"geladen werden, oder Pfad zu einzelnen Zertifikatsdateien. Zum Beispiel: / "
"my / cert_dir, /other/custom.pem. Wenn Sie Änderungen an den TLS-"
"Einstellungen vornehmen, müssen Sie Ihre Änderungen speichern, bevor Sie auf "
"geladen werden, oder Pfad zu einzelnen Zertifikatsdateien. Zum Beispiel: /my/"
"cert_dir, /other/custom.pem. Wenn Sie Änderungen an den TLS-Einstellungen "
"vornehmen, müssen Sie Ihre Änderungen speichern, bevor Sie auf "
"\"Synchronisierungskonfiguration prüfen\" klicken."
msgid "Ignore TLS certificate errors"
@@ -1568,6 +1577,9 @@ msgstr "%d Notizen in das Notizbuch \"%s\" verschieben?"
msgid "Press to set the decryption password."
msgstr "Tippe hier, um das Entschlüsselungspasswort festzulegen."
msgid "Clear alarm"
msgstr "Alarm löschen"
msgid "Save alarm"
msgstr "Alarm speichern"
@@ -1584,6 +1596,41 @@ msgstr "Synchronisation abbrechen"
msgid "Decrypting items: %d/%d"
msgstr "Entschlüsselte Objekte: %d/%d"
msgid "Checking... Please wait."
msgstr "Überprüfe… Bitte warten."
msgid "Success! Synchronisation configuration appears to be correct."
msgstr ""
"Erfolgreich. Die Synchronisation-Konfiguration scheint korrekt zu sein."
msgid ""
"Error. Please check that URL, username, password, etc. are correct and that "
"the sync target is accessible. The reported error was:"
msgstr ""
"Fehler. Bitte überprüfe, ob die URL, der Benutzername, das Passwort. usw. "
"korrekt sind und das das Synchronisierungsziel erreichbar ist. Fehlermeldung:"
msgid "The application has been authorised!"
msgstr "Das Programm wurde erfolgreich autorisiert."
#, javascript-format
msgid ""
"Could not authorise application:\n"
"\n"
"%s\n"
"\n"
"Please try again."
msgstr ""
"Konnte Applikation nicht autorisieren:\n"
"\n"
"%s\n"
"\n"
"Bitte versuche es erneut."
#, javascript-format
msgid "Decrypted items: %s / %s"
msgstr "Entschlüsselte Objekte: %s / %s"
msgid "New tags:"
msgstr "Neue Schlagwörter:"
@@ -1713,8 +1760,8 @@ msgstr ""
msgid "You currently have no notebook. Create one by clicking on (+) button."
msgstr ""
"Du hast noch kein Notizbuch. Erstelle eines, indem du auf den (+) Knopf "
"drückst."
"Du hast noch kein Notizbuch angelegt. Erstelle eines, indem du auf den (+) "
"Knopf drückst."
msgid "Welcome"
msgstr "Willkommen"

View File

@@ -552,6 +552,9 @@ msgstr ""
msgid "Paste"
msgstr ""
msgid "Select all"
msgstr ""
msgid "Bold"
msgstr ""
@@ -843,6 +846,12 @@ msgstr ""
msgid "Switch between note and to-do type"
msgstr ""
msgid "Switch to note type"
msgstr ""
msgid "Switch to to-do type"
msgstr ""
msgid "Copy Markdown link"
msgstr ""
@@ -1407,6 +1416,9 @@ msgstr ""
msgid "Press to set the decryption password."
msgstr ""
msgid "Clear alarm"
msgstr ""
msgid "Save alarm"
msgstr ""
@@ -1423,6 +1435,33 @@ msgstr ""
msgid "Decrypting items: %d/%d"
msgstr ""
msgid "Checking... Please wait."
msgstr ""
msgid "Success! Synchronisation configuration appears to be correct."
msgstr ""
msgid ""
"Error. Please check that URL, username, password, etc. are correct and that "
"the sync target is accessible. The reported error was:"
msgstr ""
msgid "The application has been authorised!"
msgstr ""
#, javascript-format
msgid ""
"Could not authorise application:\n"
"\n"
"%s\n"
"\n"
"Please try again."
msgstr ""
#, javascript-format
msgid "Decrypted items: %s / %s"
msgstr ""
msgid "New tags:"
msgstr ""

View File

@@ -629,6 +629,10 @@ msgstr "Cortar"
msgid "Paste"
msgstr "Pegar"
#, fuzzy
msgid "Select all"
msgstr "Seleccione fecha"
msgid "Bold"
msgstr "Negrita"
@@ -943,6 +947,14 @@ msgstr "%s - Copiar"
msgid "Switch between note and to-do type"
msgstr "Cambiar entre nota y lista de tareas"
#, fuzzy
msgid "Switch to note type"
msgstr "Cambiar entre nota y lista de tareas"
#, fuzzy
msgid "Switch to to-do type"
msgstr "Cambiar entre nota y lista de tareas"
msgid "Copy Markdown link"
msgstr "Copiar el enlace de Markdown"
@@ -1539,6 +1551,10 @@ msgstr "¿Desea mover %d notas a libreta «%s»?"
msgid "Press to set the decryption password."
msgstr "Presione para establecer la contraseña de descifrado."
#, fuzzy
msgid "Clear alarm"
msgstr "Establecer alarma"
msgid "Save alarm"
msgstr "Establecer alarma"
@@ -1555,6 +1571,36 @@ msgstr "Cancelar sincronización"
msgid "Decrypting items: %d/%d"
msgstr "Descifrando elementos: %d/%d."
#, fuzzy
msgid "Checking... Please wait."
msgstr "Cancelando... Por favor espere."
#, fuzzy
msgid "Success! Synchronisation configuration appears to be correct."
msgstr "Comprobar sincronización"
msgid ""
"Error. Please check that URL, username, password, etc. are correct and that "
"the sync target is accessible. The reported error was:"
msgstr ""
#, fuzzy
msgid "The application has been authorised!"
msgstr "La aplicacion ha sido autorizada éxitosamente."
#, javascript-format
msgid ""
"Could not authorise application:\n"
"\n"
"%s\n"
"\n"
"Please try again."
msgstr ""
#, fuzzy, javascript-format
msgid "Decrypted items: %s / %s"
msgstr "Descifrando elementos: %d/%d."
msgid "New tags:"
msgstr "Nuevas etiquetas:"

View File

@@ -628,6 +628,10 @@ msgstr "Moztu"
msgid "Paste"
msgstr "Itsatsi"
#, fuzzy
msgid "Select all"
msgstr "Data aukeratu"
msgid "Bold"
msgstr ""
@@ -939,6 +943,14 @@ msgstr "Kopiatu"
msgid "Switch between note and to-do type"
msgstr "Aldatu oharra eta zeregin eren artean."
#, fuzzy
msgid "Switch to note type"
msgstr "Aldatu oharra eta zeregin eren artean."
#, fuzzy
msgid "Switch to to-do type"
msgstr "Aldatu oharra eta zeregin eren artean."
msgid "Copy Markdown link"
msgstr ""
@@ -1546,6 +1558,10 @@ msgstr "Mugitu %d oharrak \"%s\" koadernora?"
msgid "Press to set the decryption password."
msgstr "Sakatu deszifratze pasahitza ezartzeko."
#, fuzzy
msgid "Clear alarm"
msgstr "Ezarri alarma"
#, fuzzy
msgid "Save alarm"
msgstr "Ezarri alarma"
@@ -1563,6 +1579,36 @@ msgstr "Sinkronizazioa utzi"
msgid "Decrypting items: %d/%d"
msgstr "Itemak eskuratuta: %d%d."
#, fuzzy
msgid "Checking... Please wait."
msgstr "Bertan behera uzten... itxaron, mesedez."
#, fuzzy
msgid "Success! Synchronisation configuration appears to be correct."
msgstr "Sinkronizazioa utzi"
msgid ""
"Error. Please check that URL, username, password, etc. are correct and that "
"the sync target is accessible. The reported error was:"
msgstr ""
#, fuzzy
msgid "The application has been authorised!"
msgstr "Aplikazioak baimena hartu du."
#, javascript-format
msgid ""
"Could not authorise application:\n"
"\n"
"%s\n"
"\n"
"Please try again."
msgstr ""
#, fuzzy, javascript-format
msgid "Decrypted items: %s / %s"
msgstr "Itemak eskuratuta: %d%d."
msgid "New tags:"
msgstr ""

View File

@@ -627,6 +627,9 @@ msgstr "Couper"
msgid "Paste"
msgstr "Coller"
msgid "Select all"
msgstr "Sélectionner tout"
msgid "Bold"
msgstr "Gras"
@@ -947,6 +950,12 @@ msgstr "%s - Copie"
msgid "Switch between note and to-do type"
msgstr "Alterner entre note et tâche"
msgid "Switch to note type"
msgstr "Convertir en note"
msgid "Switch to to-do type"
msgstr "Convertir en tâche"
msgid "Copy Markdown link"
msgstr "Copier lien Markdown"
@@ -1550,6 +1559,9 @@ msgstr "Déplacer %d notes vers carnet \"%s\" ?"
msgid "Press to set the decryption password."
msgstr "Définir mot de passe de synchronisation."
msgid "Clear alarm"
msgstr "Enlever l'alarme"
msgid "Save alarm"
msgstr "Enregistrer alarme"
@@ -1566,6 +1578,40 @@ msgstr "Annuler synchronisation"
msgid "Decrypting items: %d/%d"
msgstr "Déchiffrement des objets : %d/%d"
msgid "Checking... Please wait."
msgstr "Vérification... Veuillez attendre."
msgid "Success! Synchronisation configuration appears to be correct."
msgstr "La configuration de la synchronisation semble correcte."
msgid ""
"Error. Please check that URL, username, password, etc. are correct and that "
"the sync target is accessible. The reported error was:"
msgstr ""
"Erreur. Veuillez vérifier que l'URL, le nom, le mot de passe, etc. sont "
"corrects et que la destination est accessible. L'erreur reportée est :"
msgid "The application has been authorised!"
msgstr "Le logiciel a été autorisé !"
#, javascript-format
msgid ""
"Could not authorise application:\n"
"\n"
"%s\n"
"\n"
"Please try again."
msgstr ""
"Impossible d'autoriser le logiciel :\n"
"\n"
"%s\n"
"\n"
"Veuillez réessayer."
#, javascript-format
msgid "Decrypted items: %s / %s"
msgstr "Déchiffrement : %s / %s"
msgid "New tags:"
msgstr "Nouvelles étiquettes :"

View File

@@ -620,6 +620,10 @@ msgstr "Cortar"
msgid "Paste"
msgstr "Pegar"
#, fuzzy
msgid "Select all"
msgstr "Seleccionar data"
msgid "Bold"
msgstr ""
@@ -926,6 +930,14 @@ msgstr "Copiar"
msgid "Switch between note and to-do type"
msgstr "Cambiar entre notas e tarefas"
#, fuzzy
msgid "Switch to note type"
msgstr "Cambiar entre notas e tarefas"
#, fuzzy
msgid "Switch to to-do type"
msgstr "Cambiar entre notas e tarefas"
#, fuzzy
msgid "Copy Markdown link"
msgstr "Markdown"
@@ -1524,6 +1536,10 @@ msgstr "Mover %d notas para o caderno \"%s\"?"
msgid "Press to set the decryption password."
msgstr "Prema para estabelecer o contrasinal de descifrado."
#, fuzzy
msgid "Clear alarm"
msgstr "Estabelecer alarma"
#, fuzzy
msgid "Save alarm"
msgstr "Estabelecer alarma"
@@ -1541,6 +1557,36 @@ msgstr "Cancelar sincronización"
msgid "Decrypting items: %d/%d"
msgstr "Elementos obtidos: %d/%d."
#, fuzzy
msgid "Checking... Please wait."
msgstr "Cancelando... Agarde."
#, fuzzy
msgid "Success! Synchronisation configuration appears to be correct."
msgstr "Comprobar a configuración da sincronización"
msgid ""
"Error. Please check that URL, username, password, etc. are correct and that "
"the sync target is accessible. The reported error was:"
msgstr ""
#, fuzzy
msgid "The application has been authorised!"
msgstr "O aplicativo foi autorizado correctamente."
#, javascript-format
msgid ""
"Could not authorise application:\n"
"\n"
"%s\n"
"\n"
"Please try again."
msgstr ""
#, fuzzy, javascript-format
msgid "Decrypted items: %s / %s"
msgstr "Elementos obtidos: %d/%d."
msgid "New tags:"
msgstr "Etiquetas novas:"

View File

@@ -625,6 +625,10 @@ msgstr "Izreži"
msgid "Paste"
msgstr "Zalijepi"
#, fuzzy
msgid "Select all"
msgstr "Odaberi datum"
msgid "Bold"
msgstr ""
@@ -926,6 +930,14 @@ msgstr "Kopiraj"
msgid "Switch between note and to-do type"
msgstr "Zamijeni bilješku i zadatak"
#, fuzzy
msgid "Switch to note type"
msgstr "Zamijeni bilješku i zadatak"
#, fuzzy
msgid "Switch to to-do type"
msgstr "Zamijeni bilješku i zadatak"
msgid "Copy Markdown link"
msgstr ""
@@ -1521,6 +1533,10 @@ msgstr "Premjesti %d bilješke u bilježnicu \"%s\"?"
msgid "Press to set the decryption password."
msgstr ""
#, fuzzy
msgid "Clear alarm"
msgstr "Postavi upozorenje"
#, fuzzy
msgid "Save alarm"
msgstr "Postavi upozorenje"
@@ -1538,6 +1554,36 @@ msgstr "Prekini sinkronizaciju"
msgid "Decrypting items: %d/%d"
msgstr "Stvorene lokalne stavke: %d."
#, fuzzy
msgid "Checking... Please wait."
msgstr "Prekidam... Pričekaj."
#, fuzzy
msgid "Success! Synchronisation configuration appears to be correct."
msgstr "Prekini sinkronizaciju"
msgid ""
"Error. Please check that URL, username, password, etc. are correct and that "
"the sync target is accessible. The reported error was:"
msgstr ""
#, fuzzy
msgid "The application has been authorised!"
msgstr "Aplikacija je uspješno autorizirana."
#, javascript-format
msgid ""
"Could not authorise application:\n"
"\n"
"%s\n"
"\n"
"Please try again."
msgstr ""
#, fuzzy, javascript-format
msgid "Decrypted items: %s / %s"
msgstr "Stvorene lokalne stavke: %d."
msgid "New tags:"
msgstr ""

View File

@@ -626,6 +626,10 @@ msgstr "Taglia"
msgid "Paste"
msgstr "Incolla"
#, fuzzy
msgid "Select all"
msgstr "Seleziona la data"
msgid "Bold"
msgstr "Grasseto"
@@ -941,6 +945,14 @@ msgstr "%s - Copia"
msgid "Switch between note and to-do type"
msgstr "Converti nota in \"Cose-da-fare\" e viceversa"
#, fuzzy
msgid "Switch to note type"
msgstr "Converti nota in \"Cose-da-fare\" e viceversa"
#, fuzzy
msgid "Switch to to-do type"
msgstr "Converti nota in \"Cose-da-fare\" e viceversa"
msgid "Copy Markdown link"
msgstr "Copia il link Markdown"
@@ -1538,6 +1550,10 @@ msgstr "Spostare le note %d sul Taccuino \"%s\"?"
msgid "Press to set the decryption password."
msgstr "Premere per impostare la password di decrittografia."
#, fuzzy
msgid "Clear alarm"
msgstr "Imposta Allarme"
msgid "Save alarm"
msgstr "Salva Allarme"
@@ -1554,6 +1570,36 @@ msgstr "Cancella la sincronizzazione"
msgid "Decrypting items: %d/%d"
msgstr "Decrittografia Elementi: %d/%d"
#, fuzzy
msgid "Checking... Please wait."
msgstr "Cancellazione... Attendere per favore."
#, fuzzy
msgid "Success! Synchronisation configuration appears to be correct."
msgstr "Controlla la configurazione della sincronizzazione"
msgid ""
"Error. Please check that URL, username, password, etc. are correct and that "
"the sync target is accessible. The reported error was:"
msgstr ""
#, fuzzy
msgid "The application has been authorised!"
msgstr "L'applicazione è stata autorizzata con successo."
#, javascript-format
msgid ""
"Could not authorise application:\n"
"\n"
"%s\n"
"\n"
"Please try again."
msgstr ""
#, fuzzy, javascript-format
msgid "Decrypted items: %s / %s"
msgstr "Decrittografia Elementi: %d/%d"
msgid "New tags:"
msgstr "Nuovi tag:"

View File

@@ -615,6 +615,10 @@ msgstr "切り取り"
msgid "Paste"
msgstr "貼り付け"
#, fuzzy
msgid "Select all"
msgstr "日付の選択"
msgid "Bold"
msgstr "太字"
@@ -927,6 +931,14 @@ msgstr "%s - コピー"
msgid "Switch between note and to-do type"
msgstr "ノートとToDoを切り替え"
#, fuzzy
msgid "Switch to note type"
msgstr "ノートとToDoを切り替え"
#, fuzzy
msgid "Switch to to-do type"
msgstr "ノートとToDoを切り替え"
msgid "Copy Markdown link"
msgstr "Markdownのリンクをコピー"
@@ -1523,6 +1535,10 @@ msgstr "%d個のノートを\"%s\"に移動しますか?"
msgid "Press to set the decryption password."
msgstr "復号するパスワードを入力してください。"
#, fuzzy
msgid "Clear alarm"
msgstr "アラームをセット"
msgid "Save alarm"
msgstr "アラームの保存"
@@ -1539,6 +1555,36 @@ msgstr "同期の中止"
msgid "Decrypting items: %d/%d"
msgstr "復号中のアイテム: %d/%d"
#, fuzzy
msgid "Checking... Please wait."
msgstr "中止中...お待ちください。"
#, fuzzy
msgid "Success! Synchronisation configuration appears to be correct."
msgstr "同期の設定を確認する"
msgid ""
"Error. Please check that URL, username, password, etc. are correct and that "
"the sync target is accessible. The reported error was:"
msgstr ""
#, fuzzy
msgid "The application has been authorised!"
msgstr "アプリケーションは問題なく認証されました。"
#, javascript-format
msgid ""
"Could not authorise application:\n"
"\n"
"%s\n"
"\n"
"Please try again."
msgstr ""
#, fuzzy, javascript-format
msgid "Decrypted items: %s / %s"
msgstr "復号中のアイテム: %d/%d"
msgid "New tags:"
msgstr "新しいタグ:"

View File

@@ -552,6 +552,9 @@ msgstr ""
msgid "Paste"
msgstr ""
msgid "Select all"
msgstr ""
msgid "Bold"
msgstr ""
@@ -843,6 +846,12 @@ msgstr ""
msgid "Switch between note and to-do type"
msgstr ""
msgid "Switch to note type"
msgstr ""
msgid "Switch to to-do type"
msgstr ""
msgid "Copy Markdown link"
msgstr ""
@@ -1407,6 +1416,9 @@ msgstr ""
msgid "Press to set the decryption password."
msgstr ""
msgid "Clear alarm"
msgstr ""
msgid "Save alarm"
msgstr ""
@@ -1423,6 +1435,33 @@ msgstr ""
msgid "Decrypting items: %d/%d"
msgstr ""
msgid "Checking... Please wait."
msgstr ""
msgid "Success! Synchronisation configuration appears to be correct."
msgstr ""
msgid ""
"Error. Please check that URL, username, password, etc. are correct and that "
"the sync target is accessible. The reported error was:"
msgstr ""
msgid "The application has been authorised!"
msgstr ""
#, javascript-format
msgid ""
"Could not authorise application:\n"
"\n"
"%s\n"
"\n"
"Please try again."
msgstr ""
#, javascript-format
msgid "Decrypted items: %s / %s"
msgstr ""
msgid "New tags:"
msgstr ""

View File

@@ -612,6 +612,10 @@ msgstr "잘라내기"
msgid "Paste"
msgstr "붙여넣기"
#, fuzzy
msgid "Select all"
msgstr "날짜 선택"
msgid "Bold"
msgstr "굵게"
@@ -921,6 +925,14 @@ msgstr "%s - 복사"
msgid "Switch between note and to-do type"
msgstr "'노트' 또는'할 일' 형식으로 전환합니다."
#, fuzzy
msgid "Switch to note type"
msgstr "'노트' 또는'할 일' 형식으로 전환합니다."
#, fuzzy
msgid "Switch to to-do type"
msgstr "'노트' 또는'할 일' 형식으로 전환합니다."
msgid "Copy Markdown link"
msgstr "마크다운 링크 복사"
@@ -1513,6 +1525,10 @@ msgstr "%d 노트를 \"%s\" 노트북으로 옮길까요?"
msgid "Press to set the decryption password."
msgstr "복호화 암호를 설정하려면 누르세요."
#, fuzzy
msgid "Clear alarm"
msgstr "알람 설정"
msgid "Save alarm"
msgstr "알람 저장"
@@ -1529,6 +1545,36 @@ msgstr "동기화 취소"
msgid "Decrypting items: %d/%d"
msgstr "복호화 항목: %d/%d"
#, fuzzy
msgid "Checking... Please wait."
msgstr "취소하는 중입니다... 잠시만 기다리세요."
#, fuzzy
msgid "Success! Synchronisation configuration appears to be correct."
msgstr "동기화 설정 확인"
msgid ""
"Error. Please check that URL, username, password, etc. are correct and that "
"the sync target is accessible. The reported error was:"
msgstr ""
#, fuzzy
msgid "The application has been authorised!"
msgstr "애플리케이션이 성공적으로 허가되었습니다."
#, javascript-format
msgid ""
"Could not authorise application:\n"
"\n"
"%s\n"
"\n"
"Please try again."
msgstr ""
#, fuzzy, javascript-format
msgid "Decrypted items: %s / %s"
msgstr "복호화 항목: %d/%d"
msgid "New tags:"
msgstr "새 태그:"

View File

@@ -630,6 +630,10 @@ msgstr "Knip"
msgid "Paste"
msgstr "Plak"
#, fuzzy
msgid "Select all"
msgstr "Selecteer datum"
msgid "Bold"
msgstr ""
@@ -941,6 +945,14 @@ msgstr "Kopieer"
msgid "Switch between note and to-do type"
msgstr "Wissel tussen notitie en to-do type"
#, fuzzy
msgid "Switch to note type"
msgstr "Wissel tussen notitie en to-do type"
#, fuzzy
msgid "Switch to to-do type"
msgstr "Wissel tussen notitie en to-do type"
msgid "Copy Markdown link"
msgstr ""
@@ -1548,6 +1560,10 @@ msgstr "Verplaats %d notities naar notitieboek \"%s\"?"
msgid "Press to set the decryption password."
msgstr "Klik om het decryptie wachtwoord in te stellen"
#, fuzzy
msgid "Clear alarm"
msgstr "Zet melding"
#, fuzzy
msgid "Save alarm"
msgstr "Zet melding"
@@ -1565,6 +1581,36 @@ msgstr "Annuleer synchronisatie"
msgid "Decrypting items: %d/%d"
msgstr "Opgehaalde items: %d/%d."
#, fuzzy
msgid "Checking... Please wait."
msgstr "Annuleren.. Even geduld."
#, fuzzy
msgid "Success! Synchronisation configuration appears to be correct."
msgstr "Annuleer synchronisatie"
msgid ""
"Error. Please check that URL, username, password, etc. are correct and that "
"the sync target is accessible. The reported error was:"
msgstr ""
#, fuzzy
msgid "The application has been authorised!"
msgstr "De applicatie is succesvol geauthenticeerd."
#, javascript-format
msgid ""
"Could not authorise application:\n"
"\n"
"%s\n"
"\n"
"Please try again."
msgstr ""
#, fuzzy, javascript-format
msgid "Decrypted items: %s / %s"
msgstr "Opgehaalde items: %d/%d."
msgid "New tags:"
msgstr ""

View File

@@ -629,6 +629,10 @@ msgstr "Knippen"
msgid "Paste"
msgstr "Plakken"
#, fuzzy
msgid "Select all"
msgstr "Datum kiezen"
msgid "Bold"
msgstr "Vetgedrukt"
@@ -945,6 +949,14 @@ msgstr "%s - kopiëren"
msgid "Switch between note and to-do type"
msgstr "Schakelen tussen notitie en taak"
#, fuzzy
msgid "Switch to note type"
msgstr "Schakelen tussen notitie en taak"
#, fuzzy
msgid "Switch to to-do type"
msgstr "Schakelen tussen notitie en taak"
msgid "Copy Markdown link"
msgstr "Markdownlink kopiëren"
@@ -1548,6 +1560,10 @@ msgstr "%d notities verplaatsen naar notitieboek \"%s\"?"
msgid "Press to set the decryption password."
msgstr "Druk om het ontsleutelwachtwoord in te stellen."
#, fuzzy
msgid "Clear alarm"
msgstr "Alarm instellen"
msgid "Save alarm"
msgstr "Alarm opslaam"
@@ -1564,6 +1580,36 @@ msgstr "Synchronisatie annuleren"
msgid "Decrypting items: %d/%d"
msgstr "Bezig met ontsleutelen van items: %d/%d"
#, fuzzy
msgid "Checking... Please wait."
msgstr "Bezig met annuleren... Even geduld."
#, fuzzy
msgid "Success! Synchronisation configuration appears to be correct."
msgstr "Synchronisatieconfiguratie controleren"
msgid ""
"Error. Please check that URL, username, password, etc. are correct and that "
"the sync target is accessible. The reported error was:"
msgstr ""
#, fuzzy
msgid "The application has been authorised!"
msgstr "De applicatie is geautoriseerd."
#, javascript-format
msgid ""
"Could not authorise application:\n"
"\n"
"%s\n"
"\n"
"Please try again."
msgstr ""
#, fuzzy, javascript-format
msgid "Decrypted items: %s / %s"
msgstr "Bezig met ontsleutelen van items: %d/%d"
msgid "New tags:"
msgstr "Nieuwe labels:"

View File

@@ -623,6 +623,10 @@ msgstr "Klipp ut"
msgid "Paste"
msgstr "Lim inn"
#, fuzzy
msgid "Select all"
msgstr "Velg dato"
msgid "Bold"
msgstr ""
@@ -933,6 +937,14 @@ msgstr "Kopier"
msgid "Switch between note and to-do type"
msgstr "Bytt mellom notat og gjøremål"
#, fuzzy
msgid "Switch to note type"
msgstr "Bytt mellom notat og gjøremål"
#, fuzzy
msgid "Switch to to-do type"
msgstr "Bytt mellom notat og gjøremål"
msgid "Copy Markdown link"
msgstr "Kopier Link"
@@ -1530,6 +1542,10 @@ msgstr "Flytt %d notater til notisblokk \"%s\"?"
msgid "Press to set the decryption password."
msgstr "Trykk for å sette krypteringspassordet"
#, fuzzy
msgid "Clear alarm"
msgstr "Angi Alarm"
#, fuzzy
msgid "Save alarm"
msgstr "Angi Alarm"
@@ -1547,6 +1563,36 @@ msgstr "Stopp Synkronisering"
msgid "Decrypting items: %d/%d"
msgstr "Hentede elementer: %d/%d."
#, fuzzy
msgid "Checking... Please wait."
msgstr "Stopper… Vennligst vent."
#, fuzzy
msgid "Success! Synchronisation configuration appears to be correct."
msgstr "Sjekk synkroniseringskonfigurasjonen"
msgid ""
"Error. Please check that URL, username, password, etc. are correct and that "
"the sync target is accessible. The reported error was:"
msgstr ""
#, fuzzy
msgid "The application has been authorised!"
msgstr "Applikasjonen har blitt godkjent."
#, javascript-format
msgid ""
"Could not authorise application:\n"
"\n"
"%s\n"
"\n"
"Please try again."
msgstr ""
#, fuzzy, javascript-format
msgid "Decrypted items: %s / %s"
msgstr "Hentede elementer: %d/%d."
msgid "New tags:"
msgstr "Nye merkelapper:"

View File

@@ -625,6 +625,10 @@ msgstr "Cortar"
msgid "Paste"
msgstr "Colar"
#, fuzzy
msgid "Select all"
msgstr "Selecionar data"
msgid "Bold"
msgstr "Negrito"
@@ -939,6 +943,14 @@ 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"
#, fuzzy
msgid "Switch to to-do type"
msgstr "Alternar entre os tipos Nota e Tarefa"
msgid "Copy Markdown link"
msgstr "Copiar link de Markdown"
@@ -1536,6 +1548,10 @@ 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"
msgid "Save alarm"
msgstr "Salvar alarme"
@@ -1552,6 +1568,36 @@ msgstr "Cancelar sincronização"
msgid "Decrypting items: %d/%d"
msgstr "Decriptando itens: %d/%d."
#, fuzzy
msgid "Checking... Please wait."
msgstr "Cancelando... Aguarde."
#, fuzzy
msgid "Success! Synchronisation configuration appears to be correct."
msgstr "Verificar a configuração da sincronização"
msgid ""
"Error. Please check that URL, username, password, etc. are correct and that "
"the sync target is accessible. The reported error was:"
msgstr ""
#, fuzzy
msgid "The application has been authorised!"
msgstr "O aplicativo foi autorizado com sucesso."
#, javascript-format
msgid ""
"Could not authorise application:\n"
"\n"
"%s\n"
"\n"
"Please try again."
msgstr ""
#, fuzzy, javascript-format
msgid "Decrypted items: %s / %s"
msgstr "Decriptando itens: %d/%d."
msgid "New tags:"
msgstr "Novas tags:"

View File

@@ -565,6 +565,10 @@ msgstr "Tăiați"
msgid "Paste"
msgstr "Lipește"
#, fuzzy
msgid "Select all"
msgstr "Selectați data"
msgid "Bold"
msgstr ""
@@ -857,6 +861,14 @@ msgstr "%s - Copiați"
msgid "Switch between note and to-do type"
msgstr "Schimbați între notiță și sarcină"
#, fuzzy
msgid "Switch to note type"
msgstr "Schimbați între notiță și sarcină"
#, fuzzy
msgid "Switch to to-do type"
msgstr "Schimbați între notiță și sarcină"
msgid "Copy Markdown link"
msgstr "Copiați link-ul Markdown"
@@ -1421,6 +1433,10 @@ msgstr "Mutați %d notițe în caietul de notițe \"%s\"?"
msgid "Press to set the decryption password."
msgstr ""
#, fuzzy
msgid "Clear alarm"
msgstr "Setați alarma"
msgid "Save alarm"
msgstr "Salvați alarma"
@@ -1437,6 +1453,34 @@ msgstr "Amânați sincronizarea"
msgid "Decrypting items: %d/%d"
msgstr "Se decriptează itemi: %d/%d"
#, fuzzy
msgid "Checking... Please wait."
msgstr "Se anulează... Vă rugăm să așteptați."
msgid "Success! Synchronisation configuration appears to be correct."
msgstr ""
msgid ""
"Error. Please check that URL, username, password, etc. are correct and that "
"the sync target is accessible. The reported error was:"
msgstr ""
msgid "The application has been authorised!"
msgstr ""
#, javascript-format
msgid ""
"Could not authorise application:\n"
"\n"
"%s\n"
"\n"
"Please try again."
msgstr ""
#, fuzzy, javascript-format
msgid "Decrypted items: %s / %s"
msgstr "Se decriptează itemi: %d/%d"
msgid "New tags:"
msgstr "Etichete noi:"

View File

@@ -627,6 +627,10 @@ msgstr "Вырезать"
msgid "Paste"
msgstr "Вставить"
#, fuzzy
msgid "Select all"
msgstr "Выбрать дату"
msgid "Bold"
msgstr ""
@@ -936,6 +940,14 @@ msgstr "Копировать"
msgid "Switch between note and to-do type"
msgstr "Переключить тип между заметкой и задачей"
#, fuzzy
msgid "Switch to note type"
msgstr "Переключить тип между заметкой и задачей"
#, fuzzy
msgid "Switch to to-do type"
msgstr "Переключить тип между заметкой и задачей"
#, fuzzy
msgid "Copy Markdown link"
msgstr "Markdown"
@@ -1535,6 +1547,10 @@ msgstr "Переместить %d заметок в блокнот «%s»?"
msgid "Press to set the decryption password."
msgstr "Нажмите, чтобы установить пароль для расшифровки."
#, fuzzy
msgid "Clear alarm"
msgstr "Установить напоминание"
#, fuzzy
msgid "Save alarm"
msgstr "Установить напоминание"
@@ -1552,6 +1568,36 @@ msgstr "Отменить синхронизацию"
msgid "Decrypting items: %d/%d"
msgstr "Получено элементов: %d/%d."
#, fuzzy
msgid "Checking... Please wait."
msgstr "Отмена... Пожалуйста, ожидайте."
#, fuzzy
msgid "Success! Synchronisation configuration appears to be correct."
msgstr "Проверить настройки синхронизации"
msgid ""
"Error. Please check that URL, username, password, etc. are correct and that "
"the sync target is accessible. The reported error was:"
msgstr ""
#, fuzzy
msgid "The application has been authorised!"
msgstr "Приложение успешно авторизовано."
#, javascript-format
msgid ""
"Could not authorise application:\n"
"\n"
"%s\n"
"\n"
"Please try again."
msgstr ""
#, fuzzy, javascript-format
msgid "Decrypted items: %s / %s"
msgstr "Получено элементов: %d/%d."
msgid "New tags:"
msgstr ""

View File

@@ -626,6 +626,10 @@ msgstr "Izreži"
msgid "Paste"
msgstr "Prilepi"
#, fuzzy
msgid "Select all"
msgstr "Izberi datum"
msgid "Bold"
msgstr ""
@@ -936,6 +940,14 @@ msgstr "Kopiraj"
msgid "Switch between note and to-do type"
msgstr "Menjaj med zabeležko in seznamom opravil"
#, fuzzy
msgid "Switch to note type"
msgstr "Menjaj med zabeležko in seznamom opravil"
#, fuzzy
msgid "Switch to to-do type"
msgstr "Menjaj med zabeležko in seznamom opravil"
#, fuzzy
msgid "Copy Markdown link"
msgstr "Sistem označevanja"
@@ -1538,6 +1550,10 @@ msgstr "Premakni %d zabeležk v beležnico \"%s\"?"
msgid "Press to set the decryption password."
msgstr "Klikni za nastavitev dekripcijskega gesla."
#, fuzzy
msgid "Clear alarm"
msgstr "Nastavi alarm"
#, fuzzy
msgid "Save alarm"
msgstr "Nastavi alarm"
@@ -1555,6 +1571,36 @@ msgstr "Prekliči sinhronizacijo"
msgid "Decrypting items: %d/%d"
msgstr "Preneseni predmeti: %d/%d."
#, fuzzy
msgid "Checking... Please wait."
msgstr "V preklicu...Prosim počakajte."
#, fuzzy
msgid "Success! Synchronisation configuration appears to be correct."
msgstr "Preveri nastavitve sinhronizacije"
msgid ""
"Error. Please check that URL, username, password, etc. are correct and that "
"the sync target is accessible. The reported error was:"
msgstr ""
#, fuzzy
msgid "The application has been authorised!"
msgstr "Aplikacija je bila uspešno avtorizirana."
#, javascript-format
msgid ""
"Could not authorise application:\n"
"\n"
"%s\n"
"\n"
"Please try again."
msgstr ""
#, fuzzy, javascript-format
msgid "Decrypted items: %s / %s"
msgstr "Preneseni predmeti: %d/%d."
msgid "New tags:"
msgstr ""

View File

@@ -633,6 +633,10 @@ msgstr "Klipp ut"
msgid "Paste"
msgstr "Klistra in"
#, fuzzy
msgid "Select all"
msgstr "Välj datum"
msgid "Bold"
msgstr "Fet"
@@ -948,6 +952,14 @@ msgstr "%s - Kopiera"
msgid "Switch between note and to-do type"
msgstr "Växla mellan antecknings- och att-göra-typ"
#, fuzzy
msgid "Switch to note type"
msgstr "Växla mellan antecknings- och att-göra-typ"
#, fuzzy
msgid "Switch to to-do type"
msgstr "Växla mellan antecknings- och att-göra-typ"
msgid "Copy Markdown link"
msgstr "Kopiera Markdown-länk"
@@ -1547,6 +1559,10 @@ msgstr "Flytta %d anteckningar till anteckningsboken \"%s\"?"
msgid "Press to set the decryption password."
msgstr "Tryck för att ställa in dekrypteringslösenordet."
#, fuzzy
msgid "Clear alarm"
msgstr "Sätt alarm"
msgid "Save alarm"
msgstr "Spara alarm"
@@ -1563,6 +1579,36 @@ msgstr "Avbryt synkronisering"
msgid "Decrypting items: %d/%d"
msgstr "Dekrypterar objekt: %d/%d"
#, fuzzy
msgid "Checking... Please wait."
msgstr "Avbryter... vänta."
#, fuzzy
msgid "Success! Synchronisation configuration appears to be correct."
msgstr "Kontrollera synkroniseringskonfigurationen"
msgid ""
"Error. Please check that URL, username, password, etc. are correct and that "
"the sync target is accessible. The reported error was:"
msgstr ""
#, fuzzy
msgid "The application has been authorised!"
msgstr "Programmet har godkänts."
#, javascript-format
msgid ""
"Could not authorise application:\n"
"\n"
"%s\n"
"\n"
"Please try again."
msgstr ""
#, fuzzy, javascript-format
msgid "Decrypted items: %s / %s"
msgstr "Dekrypterar objekt: %d/%d"
msgid "New tags:"
msgstr "Nya taggar:"

View File

@@ -591,6 +591,10 @@ msgstr "剪切"
msgid "Paste"
msgstr "粘贴"
#, fuzzy
msgid "Select all"
msgstr "选择日期"
msgid "Bold"
msgstr "粗体"
@@ -895,6 +899,14 @@ msgstr "%s - 副本"
msgid "Switch between note and to-do type"
msgstr "在笔记和待办事项类型之间切换"
#, fuzzy
msgid "Switch to note type"
msgstr "在笔记和待办事项类型之间切换"
#, fuzzy
msgid "Switch to to-do type"
msgstr "在笔记和待办事项类型之间切换"
msgid "Copy Markdown link"
msgstr "复制 Markdown 链接"
@@ -1478,6 +1490,10 @@ msgstr "移动%d条笔记至笔记本\"%s\"?"
msgid "Press to set the decryption password."
msgstr "按键将设置解密密码。"
#, fuzzy
msgid "Clear alarm"
msgstr "设置提醒"
msgid "Save alarm"
msgstr "保存提醒"
@@ -1494,6 +1510,36 @@ msgstr "取消同步"
msgid "Decrypting items: %d/%d"
msgstr "解密项目:%d/%d"
#, fuzzy
msgid "Checking... Please wait."
msgstr "正在取消... 请稍后。"
#, fuzzy
msgid "Success! Synchronisation configuration appears to be correct."
msgstr "检查同步配置"
msgid ""
"Error. Please check that URL, username, password, etc. are correct and that "
"the sync target is accessible. The reported error was:"
msgstr ""
#, fuzzy
msgid "The application has been authorised!"
msgstr "此程序已被成功授权。"
#, javascript-format
msgid ""
"Could not authorise application:\n"
"\n"
"%s\n"
"\n"
"Please try again."
msgstr ""
#, fuzzy, javascript-format
msgid "Decrypted items: %s / %s"
msgstr "解密项目:%d/%d"
msgid "New tags:"
msgstr "新标签:"

View File

@@ -592,6 +592,10 @@ msgstr "剪下"
msgid "Paste"
msgstr "貼上"
#, fuzzy
msgid "Select all"
msgstr "選擇日期"
msgid "Bold"
msgstr "粗體"
@@ -895,6 +899,14 @@ msgstr "%s - 複本"
msgid "Switch between note and to-do type"
msgstr "切換到記事 / 待辦事項"
#, fuzzy
msgid "Switch to note type"
msgstr "切換到記事 / 待辦事項"
#, fuzzy
msgid "Switch to to-do type"
msgstr "切換到記事 / 待辦事項"
msgid "Copy Markdown link"
msgstr "複製 Markdown 連結"
@@ -1475,6 +1487,10 @@ msgstr "移動 %d 記事到記事本 \"%s\"?"
msgid "Press to set the decryption password."
msgstr "按下以設置解密密碼。"
#, fuzzy
msgid "Clear alarm"
msgstr "設置提醒"
msgid "Save alarm"
msgstr "儲存提醒事項"
@@ -1491,6 +1507,36 @@ msgstr "取消同步"
msgid "Decrypting items: %d/%d"
msgstr "正在解密項目: %d/%d 項"
#, fuzzy
msgid "Checking... Please wait."
msgstr "正在取消中...請稍候。"
#, fuzzy
msgid "Success! Synchronisation configuration appears to be correct."
msgstr "檢測同步設置"
msgid ""
"Error. Please check that URL, username, password, etc. are correct and that "
"the sync target is accessible. The reported error was:"
msgstr ""
#, fuzzy
msgid "The application has been authorised!"
msgstr "應用程式已成功取得權限。"
#, javascript-format
msgid ""
"Could not authorise application:\n"
"\n"
"%s\n"
"\n"
"Please try again."
msgstr ""
#, fuzzy, javascript-format
msgid "Decrypted items: %s / %s"
msgstr "正在解密項目: %d/%d 項"
msgid "New tags:"
msgstr "新增標籤:"

View File

@@ -1,6 +1,6 @@
{
"name": "joplin",
"version": "1.0.116",
"version": "1.0.117",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -2890,6 +2890,11 @@
"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.116",
"version": "1.0.117",
"bin": {
"joplin": "./main.js"
},
@@ -68,6 +68,7 @@
"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

@@ -42,4 +42,25 @@ describe('models_Note', function() {
expect(items.length).toBe(4);
}));
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 });
note1 = await Note.load(note1.id);
let changedNote = Note.changeNoteType(note1, 'todo');
expect(changedNote === note1).toBe(false);
expect(!!changedNote.is_todo).toBe(true);
await Note.save(changedNote);
note1 = await Note.load(note1.id);
changedNote = Note.changeNoteType(note1, 'todo');
expect(changedNote === note1).toBe(true);
expect(!!changedNote.is_todo).toBe(true);
note1 = await Note.load(note1.id);
changedNote = Note.changeNoteType(note1, 'note');
expect(changedNote === note1).toBe(false);
expect(!!changedNote.is_todo).toBe(false);
}));
});

View File

@@ -0,0 +1,31 @@
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Resource = require('lib/models/Resource.js');
const BaseModel = require('lib/BaseModel.js');
const { shim } = require('lib/shim');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
describe('models_Resource', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
done();
});
it('should have a "done" fetch_status when created locally', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
let resource1 = (await Resource.all())[0];
console.info(resource1);
}));
});

View File

@@ -7,6 +7,7 @@ const fs = require('fs-extra');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Resource = require('lib/models/Resource.js');
const ResourceFetcher = require('lib/services/ResourceFetcher');
const Tag = require('lib/models/Tag.js');
const { Database } = require('lib/database.js');
const Setting = require('lib/models/Setting.js');
@@ -872,12 +873,46 @@ describe('Synchronizer', function() {
let allResources = await Resource.all();
expect(allResources.length).toBe(1);
let resource1_2 = allResources[0];
let resourcePath1_2 = Resource.fullPath(resource1_2);
expect(resource1_2.id).toBe(resource1.id);
expect(resource1_2.fetch_status).toBe(Resource.FETCH_STATUS_IDLE);
const fetcher = new ResourceFetcher(() => { return synchronizer().api() });
fetcher.queueDownload(resource1_2.id);
await fetcher.waitForAllFinished();
resource1_2 = await Resource.load(resource1.id);
expect(resource1_2.fetch_status).toBe(Resource.FETCH_STATUS_DONE);
let resourcePath1_2 = Resource.fullPath(resource1_2);
expect(fileContentEqual(resourcePath1, resourcePath1_2)).toBe(true);
}));
it('should handle resource download errors', asyncTest(async () => {
while (insideBeforeEach) await time.msleep(500);
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
let resource1 = (await Resource.all())[0];
let resourcePath1 = Resource.fullPath(resource1);
await synchronizer().start();
await switchClient(2);
await synchronizer().start();
const fetcher = new ResourceFetcher(() => { return {
// Simulate a failed download
get: () => { return new Promise((resolve, reject) => { reject(new Error('did not work')) }); }
} });
fetcher.queueDownload(resource1.id);
await fetcher.waitForAllFinished();
resource1 = await Resource.load(resource1.id);
expect(resource1.fetch_status).toBe(Resource.FETCH_STATUS_ERROR);
expect(resource1.fetch_error).toBe('did not work');
}));
it('should delete resources', asyncTest(async () => {
while (insideBeforeEach) await time.msleep(500);
@@ -926,6 +961,10 @@ describe('Synchronizer', function() {
Setting.setObjectKey('encryption.passwordCache', masterKey.id, '123456');
await encryptionService().loadMasterKeysFromSettings();
const fetcher = new ResourceFetcher(() => { return synchronizer().api() });
fetcher.queueDownload(resource1.id);
await fetcher.waitForAllFinished();
let resource1_2 = (await Resource.all())[0];
resource1_2 = await Resource.decrypt(resource1_2);
let resourcePath1_2 = Resource.fullPath(resource1_2);

0
CliClientDemo/publish.sh Normal file → Executable file
View File

View File

@@ -1,20 +1,20 @@
function randomClipperPort(state, env) {
const startPorts = {
prod: 41184,
dev: 27583,
};
const startPorts = {
prod: 41184,
dev: 27583,
};
const startPort = env === 'prod' ? startPorts.prod : startPorts.dev;
const startPort = env === 'prod' ? startPorts.prod : startPorts.dev;
if (!state) {
state = { offset: 0 };
} else {
state.offset++;
}
if (!state) {
state = { offset: 0 };
} else {
state.offset++;
}
state.port = startPort + state.offset;
state.port = startPort + state.offset;
return state;
return state;
}
module.exports = randomClipperPort;

View File

@@ -49,7 +49,6 @@ class Application extends BaseApplication {
constructor() {
super();
this.lastMenuScreen_ = null;
this.powerSaveBlockerId_ = null;
}
hasGui() {
@@ -221,17 +220,6 @@ class Application extends BaseApplication {
Setting.setValue('sidebarVisibility', newState.sidebarVisibility);
}
if (action.type === 'SYNC_STARTED') {
if (!this.powerSaveBlockerId_) this.powerSaveBlockerId_ = bridge().powerSaveBlockerStart('prevent-app-suspension');
}
if (action.type === 'SYNC_COMPLETED') {
if (this.powerSaveBlockerId_) {
bridge().powerSaveBlockerStop(this.powerSaveBlockerId_);
this.powerSaveBlockerId_ = null;
}
}
return result;
}
@@ -420,6 +408,10 @@ class Application extends BaseApplication {
label: _('Paste'),
role: 'paste',
accelerator: 'CommandOrControl+V',
}, {
label: _('Select all'),
role: 'selectall',
accelerator: 'CommandOrControl+A',
}, {
type: 'separator',
screens: ['Main'],
@@ -737,8 +729,6 @@ class Application extends BaseApplication {
ids: Setting.value('collapsedFolderIds'),
});
if (shim.isLinux()) bridge().setAllowPowerSaveBlockerToggle(true);
// Note: Auto-update currently doesn't work in Linux: it downloads the update
// but then doesn't install it on exit.
if (shim.isWindows() || shim.isMac()) {

View File

@@ -1,7 +1,6 @@
const { _, setLocale } = require('lib/locale.js');
const { dirname } = require('lib/path-utils.js');
const { Logger } = require('lib/logger.js');
const { powerSaveBlocker } = require('electron');
class Bridge {
@@ -9,7 +8,6 @@ class Bridge {
this.electronWrapper_ = electronWrapper;
this.autoUpdateLogger_ = null;
this.lastSelectedPath_ = null;
this.allowPowerSaveBlockerToggle_ = false;
}
electronApp() {
@@ -24,10 +22,6 @@ class Bridge {
return this.electronWrapper_.window();
}
setAllowPowerSaveBlockerToggle(v) {
this.allowPowerSaveBlockerToggle_ = v;
}
windowContentSize() {
if (!this.window()) return { width: 0, height: 0 };
const s = this.window().getContentSize();
@@ -126,19 +120,7 @@ class Bridge {
const { checkForUpdates } = require('./checkForUpdates.js');
checkForUpdates(inBackground, window, logFilePath);
}
powerSaveBlockerStart(type) {
if (!this.allowPowerSaveBlockerToggle_) return null;
console.info('Enable powerSaveBlockerStart: ' + type);
return powerSaveBlocker.start(type);
}
powerSaveBlockerStop(id) {
if (!this.allowPowerSaveBlockerToggle_) return null;
console.info('Disable powerSaveBlocker: ' + id);
return powerSaveBlocker.stop(id);
}
}
let bridge_ = null;

View File

@@ -97,13 +97,33 @@ class NoteListComponent extends React.Component {
}
}}));
menu.append(new MenuItem({label: _('Switch between note and to-do type'), click: async () => {
for (let i = 0; i < noteIds.length; i++) {
const note = await Note.load(noteIds[i]);
await Note.save(Note.toggleIsTodo(note), { userSideValidation: true });
eventManager.emit('noteTypeToggle', { noteId: note.id });
if (noteIds.length <= 1) {
menu.append(new MenuItem({label: _('Switch between note and to-do type'), click: async () => {
for (let i = 0; i < noteIds.length; i++) {
const note = await Note.load(noteIds[i]);
await Note.save(Note.toggleIsTodo(note), { userSideValidation: true });
eventManager.emit('noteTypeToggle', { noteId: note.id });
}
}}));
} else {
const switchNoteType = async (noteIds, type) => {
for (let i = 0; i < noteIds.length; i++) {
const note = await Note.load(noteIds[i]);
const newNote = Note.changeNoteType(note, type);
if (newNote === note) continue;
await Note.save(newNote, { userSideValidation: true });
eventManager.emit('noteTypeToggle', { noteId: note.id });
}
}
}}));
menu.append(new MenuItem({label: _('Switch to note type'), click: async () => {
await switchNoteType(noteIds, 'note');
}}));
menu.append(new MenuItem({label: _('Switch to to-do type'), click: async () => {
await switchNoteType(noteIds, 'todo');
}}));
}
menu.append(new MenuItem({label: _('Copy Markdown link'), click: async () => {
const { clipboard } = require('electron');

View File

@@ -6,6 +6,7 @@ const Search = require('lib/models/Search.js');
const { time } = require('lib/time-utils.js');
const Setting = require('lib/models/Setting.js');
const { IconButton } = require('./IconButton.min.js');
const { urlDecode } = require('lib/string-utils');
const Toolbar = require('./Toolbar.min.js');
const { connect } = require('react-redux');
const { _ } = require('lib/locale.js');
@@ -27,6 +28,7 @@ const urlUtils = require('lib/urlUtils');
const dialogs = require('./dialogs');
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');
@@ -170,6 +172,10 @@ class NoteTextComponent extends React.Component {
}
const updateSelectionRange = () => {
if (!this.rawEditor()) {
this.selectionRange_ = null;
return;
}
const ranges = this.rawEditor().getSelection().getAllRanges();
if (!ranges || !ranges.length || !this.state.note) {
@@ -193,6 +199,16 @@ class NoteTextComponent extends React.Component {
this.reloadNote(this.props);
}
}
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) {
this.mdToHtml().clearCache();
this.lastSetHtml_ = '';
this.updateHtml(this.state.note.body);
}
}
}
// Note:
@@ -282,6 +298,8 @@ class NoteTextComponent extends React.Component {
eventManager.on('alarmChange', this.onAlarmChange_);
eventManager.on('noteTypeToggle', this.onNoteTypeToggle_);
eventManager.on('todoToggle', this.onTodoToggle_);
ResourceFetcher.instance().on('downloadComplete', this.resourceFetcher_downloadComplete);
}
componentWillUnmount() {
@@ -294,6 +312,8 @@ class NoteTextComponent extends React.Component {
eventManager.removeListener('noteTypeToggle', this.onNoteTypeToggle_);
eventManager.removeListener('todoToggle', this.onTodoToggle_);
ResourceFetcher.instance().off('downloadComplete', this.resourceFetcher_downloadComplete);
this.destroyExternalEditWatcher();
}
@@ -547,6 +567,10 @@ class NoteTextComponent extends React.Component {
if (!item) throw new Error('No item with ID ' + itemId);
if (item.type_ === BaseModel.TYPE_RESOURCE) {
if (item.fetch_status !== Resource.FETCH_STATUS_DONE || !!item.encryption_blob_encrypted) {
bridge().showErrorMessageBox(_('This attachment is not downloaded or not decrypted yet.'));
return;
}
const filePath = Resource.fullPath(item);
bridge().openItem(filePath);
} else if (item.type_ === BaseModel.TYPE_NOTE) {
@@ -565,7 +589,14 @@ class NoteTextComponent extends React.Component {
throw new Error('Unsupported item type: ' + item.type_);
}
} else if (urlUtils.urlProtocol(msg)) {
require('electron').shell.openExternal(msg);
if (msg.indexOf('file://') === 0) {
// When using the file:// protocol, openExternal doesn't work (does nothing) with URL-encoded paths
require('electron').shell.openExternal(urlDecode(msg));
} else {
require('electron').shell.openExternal(msg);
}
} else if (msg.indexOf('#') === 0) {
// This is an internal anchor, which is handled by the WebView so skip this case
} else {
bridge().showErrorMessageBox(_('Unsupported link or message: %s', msg));
}
@@ -1366,7 +1397,11 @@ class NoteTextComponent extends React.Component {
}
if (visiblePanes.indexOf('editor') < 0) {
editorStyle.display = 'none';
// Note: Ideally we'd set the display to "none" to take the editor out
// of the DOM but if we do that, certain things won't work, in particular
// things related to scroll, which are based on the editor. See
// editorScrollTop_, restoreScrollTop_, etc.
editorStyle.width = 0;
viewerStyle.width = innerWidth;
}

View File

@@ -12,6 +12,7 @@ const { bridge } = require("electron").remote.require("./bridge");
const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem;
const InteropServiceHelper = require("../InteropServiceHelper.js");
const { shim } = require('lib/shim');
class SideBarComponent extends React.Component {
@@ -178,6 +179,35 @@ class SideBarComponent extends React.Component {
return style;
}
clearForceUpdateDuringSync() {
if (this.forceUpdateDuringSyncIID_) {
clearInterval(this.forceUpdateDuringSyncIID_);
this.forceUpdateDuringSyncIID_ = null;
}
}
componentDidUpdate(prevProps) {
if (shim.isLinux()) {
// For some reason, the UI seems to sleep in some Linux distro during
// sync. Cannot find the reason for it and cannot replicate, so here
// as a test force the update at regular intervals.
// https://github.com/laurent22/joplin/issues/312#issuecomment-429472193
if (!prevProps.syncStarted && this.props.syncStarted) {
this.clearForceUpdateDuringSync();
this.forceUpdateDuringSyncIID_ = setInterval(() => {
this.forceUpdate();
}, 2000);
}
if (prevProps.syncStarted && !this.props.syncStarted) this.clearForceUpdateDuringSync();
}
}
componentWillUnmount() {
this.clearForceUpdateDuringSync();
}
itemContextMenu(event) {
const itemId = event.target.getAttribute("data-id");
if (itemId === Folder.conflictFolderId()) return;
@@ -470,7 +500,13 @@ class SideBarComponent extends React.Component {
);
}
let decryptionReportText = '';
if (this.props.decryptionWorker && this.props.decryptionWorker.state !== 'idle' && this.props.decryptionWorker.itemCount) {
decryptionReportText = _('Decrypting items: %d/%d', this.props.decryptionWorker.itemIndex + 1, this.props.decryptionWorker.itemCount);
}
let lines = Synchronizer.reportToLines(this.props.syncReport);
if (decryptionReportText) lines.push(decryptionReportText);
const syncReportText = [];
for (let i = 0; i < lines.length; i++) {
syncReportText.push(
@@ -510,6 +546,7 @@ const mapStateToProps = state => {
locale: state.settings.locale,
theme: state.settings.theme,
collapsedFolderIds: state.collapsedFolderIds,
decryptionWorker: state.decryptionWorker,
};
};

View File

@@ -276,6 +276,27 @@
}
});
document.addEventListener('click', function(event) {
const t = event.target;
// Prevent URLs added via <a> tags from being opened within the application itself
if (t && t.nodeName === 'A' && !t.hasAttribute('data-from-md')) {
event.preventDefault();
ipcProxySendToHost(t.getAttribute('href'));
return;
}
// 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;
}
}
});
// Disable drag and drop otherwise it's possible to drop a URL
// on it and it will open in the view as a website.
document.addEventListener('drop', function(e) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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

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.111",
"version": "1.0.114",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -6981,6 +6981,11 @@
"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.111",
"version": "1.0.114",
"description": "Joplin for Desktop",
"main": "main.js",
"scripts": {
@@ -127,6 +127,7 @@
"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",

0
ElectronClient/run-prod.sh Normal file → Executable file
View File

70
Joplin_install_and_update.sh Executable file
View File

@@ -0,0 +1,70 @@
#!/bin/bash
set -e
# Title
echo " _ _ _ _ _ _ "
echo " | | (_) (_) | | | | | "
echo " | | ___ _ __ _ _ __ _ _ __ ___| |_ __ _| | | ___ _ __ "
echo " _ | |/ _ \\\| '_ \| | '_ \\ | | '_ \\\/ __| __/ _\` | | |/ _ \ '__|"
echo " | |__| | (_) | |_) | | | | | | | | | \__ \ || (_| | | | __/ | "
echo " \____/ \___/| .__/|_|_| |_| |_|_| |_|___/\__\__,_|_|_|\___|_| "
echo " | | "
echo " |_| "
echo ""
#-----------------------------------------------------
# Download Joplin
#-----------------------------------------------------
# Get the latest version to download
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
rm -f ~/.joplin/*.AppImage ~/.local/share/applications/joplin.desktop ~/.joplin/VERSION
# Creates the folder where the binary will be stored
mkdir -p ~/.joplin/
# Download the latest version
wget -O ~/.joplin/Joplin.AppImage https://github.com/laurent22/joplin/releases/download/v$version/Joplin-$version-x86_64.AppImage
# Gives execution privileges
chmod +x ~/.joplin/Joplin.AppImage
#-----------------------------------------------------
# Icon
#-----------------------------------------------------
# Download icon
wget -O ~/.joplin/Icon512.png https://joplin.cozic.net/images/Icon512.png
# Detect desktop environment
if [ "$XDG_CURRENT_DESKTOP" = "" ]
then
desktop=$(echo "$XDG_DATA_DIRS" | sed 's/.*\(xfce\|kde\|gnome\).*/\1/')
else
desktop=$XDG_CURRENT_DESKTOP
fi
desktop=${desktop,,} # convert to lower case
# Create icon for Gnome
if [[ $desktop =~ .*gnome.* ]]
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
fi
#-----------------------------------------------------
# Finish
#-----------------------------------------------------
# Informs the user that it has been installed and cleans variables
echo 'Joplin installed in the version' $version
# Add version
echo $version > ~/.joplin/VERSION
else
echo 'You are now in the latest version.'
fi
unset version

View File

@@ -2,6 +2,20 @@
[![Donate](https://joplin.cozic.net/images/badges/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=E8JMYD2LQ8MMA&lc=GB&item_name=Joplin+Development&currency_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted) [![Become a patron](https://joplin.cozic.net/images/badges/Patreon-Badge.svg)](https://www.patreon.com/joplin) [![Travis Build Status](https://travis-ci.org/laurent22/joplin.svg?branch=master)](https://travis-ci.org/laurent22/joplin) [![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/github/laurent22/joplin?branch=master&passingText=master%20-%20OK&svg=true)](https://ci.appveyor.com/project/laurent22/joplin)
* * *
**Joplin and Hacktobertfest 2018 :jack_o_lantern:**
The [Hacktobertfest event](https://hacktoberfest.digitalocean.com/) has started - it allows you to contribute to Joplin and, at the end of the month, after having done 5 PR, you'll earn a limited edition T-shirt.
To participate, go on [https://hacktoberfest.digitalocean.com/ ](https://hacktoberfest.digitalocean.com/) log in (with you github account) and you are ready to get in.
Next, go dive into the Joplin issues list labelled ["Hacktoberfest"](https://github.com/laurent22/joplin/labels/Hacktoberfest%20%3Ajack_o_lantern%3A)
We hope you will enjoy that event by contributing to the project which is a nice moment of sharing good vibe :jack_o_lantern: :tada:
_PS: the 5 Pull Request don't have to be done __only__ on Joplin project, those can be done on any FOSS projects._
* * *
Joplin is a free, open source note taking and to-do application, which can handle a large number of notes organised into notebooks. The notes are searchable, can be copied, tagged and modified either from the applications directly or from your own text editor. The notes are in [Markdown format](#markdown).
Notes exported from Evernote via .enex files [can be imported](#importing) into Joplin, including the formatted content (which is converted to Markdown), resources (images, attachments, etc.) and complete metadata (geolocation, updated time, created time, etc.). Plain Markdown files can also be imported.
@@ -20,23 +34,23 @@ 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.111/Joplin-Setup-1.0.111.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.111/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.111/Joplin-1.0.111.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.111/Joplin-1.0.111-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.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).
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.
### Install and Update Ubuntu/Debian (Gnome Shell)
On Linux, if it works with your distribution (it has been tested on Ubuntu, Fedora, Gnome and Mint), the recommended way is to use this script as it will handle the desktop icon too:
``` sh
wget -O - https://raw.githubusercontent.com/laurent22/joplin/master/install_ubuntu.sh | bash
wget -O - https://raw.githubusercontent.com/laurent22/joplin/master/Joplin_install_and_update.sh | bash
```
## Mobile applications
Operating System | Download | Alt. Download
-----------------|----------|----------------
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://joplin.cozic.net/images/BadgeAndroid.png'/></a> | or [Download APK File](https://github.com/laurent22/joplin-android/releases/download/android-v1.0.138/joplin-v1.0.138.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.175/joplin-v1.0.175.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
@@ -159,7 +173,7 @@ If synchronisation does not work, please consult the logs in the app profile dir
## Dropbox synchronisation
When syncing with Dropbox, Joplin creates a sub-directory in Dropbox, in /Apps/Joplin and read/write the notes and notebooks from it. The application does not have access to anything outside this directory.
When syncing with Dropbox, Joplin creates a sub-directory in Dropbox, in `/Apps/Joplin` and read/write the notes and notebooks from it. The application does not have access to anything outside this directory.
On the **desktop application** or **mobile application**, select "Dropbox" as the synchronisation target in the config screen (it is selected by default). Then, to initiate the synchronisation process, click on the "Synchronise" button in the sidebar and follow the instructions.
@@ -178,6 +192,7 @@ WebDAV-compatible services that are known to work with Joplin:
- [Fastmail](https://www.fastmail.com/)
- [HiDrive](https://www.strato.fr/stockage-en-ligne/) from Strato. [Setup help](https://github.com/laurent22/joplin/issues/309)
- [Nginx WebDAV Module](https://nginx.org/en/docs/http/ngx_http_dav_module.html)
- [NextCloud](https://nextcloud.com/)
- [OwnCloud](https://owncloud.org/)
- [Seafile](https://www.seafile.com/)
- [Stack](https://www.transip.nl/stack/)
@@ -245,7 +260,7 @@ Since getting the ID of a note is not straightforward, each app provides a way t
## Math notation
Math expressions can be added using the [Katex notation](https://khan.github.io/KaTeX/). To add an inline equation, wrap the expression in `$EXPRESSION$`, eg. `$\sqrt{3x-1}+(1+x)^2$`. To create an expression block, wrap it as follow:
Math expressions can be added using the [KaTeX notation](https://khan.github.io/KaTeX/). To add an inline equation, wrap the expression in `$EXPRESSION$`, eg. `$\sqrt{3x-1}+(1+x)^2$`. To create an expression block, wrap it as follow:
$$
EXPRESSION
@@ -314,29 +329,29 @@ Current translations:
<!-- LOCALE-TABLE-AUTO-GENERATED -->
&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 | 63%
![](https://joplin.cozic.net/images/flags/es/catalonia.png) | Catalan | [ca](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ca.po) | jmontane, 2018 | 90%
![](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) | 51%
![](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) | 80%
![](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. | 82%
![](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/es/basque_country.png) | Basque | [eu](https://github.com/laurent22/joplin/blob/master/CliClient/locales/eu.po) | juan.abasolo@ehu.eus | 62%
![](https://joplin.cozic.net/images/flags/es/catalonia.png) | Catalan | [ca](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ca.po) | jmontane, 2018 | 88%
![](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) | 50%
![](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) | 78%
![](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. | 80%
![](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) | 100%
![](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) | 97%
![](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) | 95%
![](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) | 80%
![](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) | | 95%
![](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) | 97%
![](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) | | 64%
![](https://joplin.cozic.net/images/flags/country-4x3/no.png) | Norwegian | [no](https://github.com/laurent22/joplin/blob/master/CliClient/locales/no.po) | | 86%
![](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) | 97%
![](https://joplin.cozic.net/images/flags/country-4x3/ro.png) | Română | [ro](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ro.po) | | 63%
![](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) | | 79%
![](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) | 96%
![](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) | 79%
![](https://joplin.cozic.net/images/flags/country-4x3/cn.png) | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/master/CliClient/locales/zh_CN.po) | | 97%
![](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) | 97%
![](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) | 98%
![](https://joplin.cozic.net/images/flags/country-4x3/kr.png) | 한국말 | [ko](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ko.po) | | 97%
![](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) | 79%
![](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) | | 93%
![](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) | 95%
![](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) | | 62%
![](https://joplin.cozic.net/images/flags/country-4x3/no.png) | Norwegian | [no](https://github.com/laurent22/joplin/blob/master/CliClient/locales/no.po) | | 84%
![](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) | 95%
![](https://joplin.cozic.net/images/flags/country-4x3/ro.png) | Română | [ro](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ro.po) | | 62%
![](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) | | 78%
![](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) | 94%
![](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) | 78%
![](https://joplin.cozic.net/images/flags/country-4x3/cn.png) | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/master/CliClient/locales/zh_CN.po) | | 94%
![](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) | 95%
![](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) | 95%
![](https://joplin.cozic.net/images/flags/country-4x3/kr.png) | 한국말 | [ko](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ko.po) | | 95%
<!-- 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 2097316
versionName "1.0.138"
versionCode 2097411
versionName "1.0.175"
ndk {
abiFilters "armeabi-v7a", "x86"
}
@@ -137,6 +137,7 @@ android {
}
dependencies {
compile project(':react-native-camera')
compile project(':react-native-file-viewer')
compile project(':react-native-securerandom')
compile project(':react-native-push-notification')
@@ -148,11 +149,29 @@ dependencies {
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(':react-native-fetch-blob')
compile project(':rn-fetch-blob')
compile project(':react-native-document-picker')
compile project(':react-native-image-resizer')
compile project(':react-native-share-extension')
compile "com.facebook.react:react-native:+"
// 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
// v25.0.2 in build.gradle, but anyway now it works ¯\_(ツ)_/¯
// --------------------------------------------------------------------------------------
// Fatal error
// { Error: Command failed: ./gradlew assembleRelease -PbuildDir=build --console plain
//
// FAILURE: Build failed with an exception.
//
// * What went wrong:
// Execution failed for task ':app:preReleaseBuild'.
// > Android dependency 'com.android.support:support-v4' has different version for the compile (26.1.0) and runtime (27.1.1) classpath. You should manually set the same version via DependencyResolution
// --------------------------------------------------------------------------------------
// https://github.com/react-native-community/react-native-camera/issues/1532#issuecomment-386434771
compile ("com.android.support:support-v4:26.0.1") {
force = true //<-- force dependency resolution to 26.0.1 in my case
}
}
// Run this once to be able to run the application with BUCK

View File

@@ -3,6 +3,7 @@ package net.cozic.joplin;
import android.app.Application;
import com.facebook.react.ReactApplication;
import org.reactnative.camera.RNCameraPackage;
import com.vinzscam.reactnativefileviewer.RNFileViewerPackage;
import net.rhogan.rnsecurerandom.RNSecureRandomPackage;
import com.dieam.reactnativepushnotification.ReactNativePushNotificationPackage;
@@ -37,6 +38,7 @@ public class MainApplication extends Application implements ReactApplication {
return Arrays.<ReactPackage>asList(
new ImageResizerPackage(),
new MainReactPackage(),
new RNCameraPackage(),
new RNFileViewerPackage(),
new RNSecureRandomPackage(),
new ReactNativePushNotificationPackage(),

View File

@@ -1,4 +1,6 @@
rootProject.name = 'Joplin'
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'
@@ -21,8 +23,8 @@ include ':app'
include ':react-native-sqlite-storage'
project(':react-native-sqlite-storage').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sqlite-storage/src/android')
include ':react-native-fetch-blob'
project(':react-native-fetch-blob').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fetch-blob/android')
include ':rn-fetch-blob'
project(':rn-fetch-blob').projectDir = new File(rootProject.projectDir, '../node_modules/rn-fetch-blob/android')
include ':react-native-document-picker'
project(':react-native-document-picker').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-document-picker/android')

0
ReactNativeClient/debug_log.sh Normal file → Executable file
View File

View File

@@ -5,6 +5,7 @@
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; };
00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; };
@@ -12,6 +13,7 @@
00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */; };
00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */; };
0DAD2E67F6A14BDC8250B927 /* libRNDocumentPicker.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 82214D3345D846709A314868 /* libRNDocumentPicker.a */; };
12AE298E1C0E445682922DAB /* libRNCamera.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E132B594F4FB4C96A2E2B0FF /* libRNCamera.a */; };
133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 78C398B91ACF4ADC00677621 /* libRCTLinking.a */; };
139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */; };
139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */; };
@@ -27,6 +29,7 @@
5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E9157331DD0AC6500FF2AA8 /* libRCTAnimation.a */; };
725A77EC604947A0AFF12C2B /* libRNFetchBlob.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F5E37D05726A4A08B2EE323A /* libRNFetchBlob.a */; };
73F8D08845494D1396B6CD0B /* Entypo.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 1F79F2CD7CED446B986A6252 /* Entypo.ttf */; };
82C61D3DAE0A4666883001E9 /* libRNFileViewer.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CCDC2774CD86466F897D88E2 /* libRNFileViewer.a */; };
832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; };
929CA5ABC80E4D398AFC4E44 /* libSQLite.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 87BABCF4ED0A406B9546CCE9 /* libSQLite.a */; };
A47605BC216B4DB3B3C2ED42 /* FontAwesome.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E2638D52624B477FABB52B8F /* FontAwesome.ttf */; };
@@ -43,7 +46,6 @@
EC11356C90E9419799A2626F /* EvilIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 51BCEC3BC28046C8BB19531F /* EvilIcons.ttf */; };
F3D0BB525E6C490294D73075 /* libRNSecureRandom.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 22647ACF9A4C45918C44C599 /* libRNSecureRandom.a */; };
FBF57CE2F0F448FA9A8985E2 /* libsqlite3.0.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 0EB8BCAEA9AA41CAAE460443 /* libsqlite3.0.tbd */; };
82C61D3DAE0A4666883001E9 /* libRNFileViewer.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CCDC2774CD86466F897D88E2 /* libRNFileViewer.a */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -334,6 +336,20 @@
remoteGlobalIDString = 9936F32F1F5F2E5B0010BF04;
remoteInfo = "privatedata-tvOS";
};
4D8B719B2163E8C500136BBC /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 59F5448FAF7345F8B568BD00 /* RNFileViewer.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 134814201AA4EA6300B7C361;
remoteInfo = RNFileViewer;
};
4D8C5643217161BF00E93280 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = FC908F114F494130A324B402 /* RNCamera.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 4107012F1ACB723B00C6AA39;
remoteInfo = RNCamera;
};
4DA7F80C1FC1DA9C00353191 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A4716DB8654B431D894F89E1 /* RNImagePicker.xcodeproj */;
@@ -415,6 +431,7 @@
4DDA31011FC88EEA00B5A80D /* RCTPushNotification.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTPushNotification.xcodeproj; path = "../node_modules/react-native/Libraries/PushNotificationIOS/RCTPushNotification.xcodeproj"; sourceTree = "<group>"; };
508DD20D1EA341CA8F730F22 /* libRCTImageResizer.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRCTImageResizer.a; sourceTree = "<group>"; };
51BCEC3BC28046C8BB19531F /* EvilIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = EvilIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf"; sourceTree = "<group>"; };
59F5448FAF7345F8B568BD00 /* RNFileViewer.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNFileViewer.xcodeproj; path = "../node_modules/react-native-file-viewer/ios/RNFileViewer.xcodeproj"; sourceTree = "<group>"; };
5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAnimation.xcodeproj; path = "../node_modules/react-native/Libraries/NativeAnimation/RCTAnimation.xcodeproj"; sourceTree = "<group>"; };
69B8EE98BFBC4AABA4885BB0 /* SimpleLineIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = SimpleLineIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf"; sourceTree = "<group>"; };
711CBD21F0894B83A2D8E234 /* RNVectorIcons.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNVectorIcons.xcodeproj; path = "../node_modules/react-native-vector-icons/RNVectorIcons.xcodeproj"; sourceTree = "<group>"; };
@@ -426,18 +443,19 @@
9D8705D0D07C4A098FD912EB /* RNFS.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNFS.xcodeproj; path = "../node_modules/react-native-fs/RNFS.xcodeproj"; sourceTree = "<group>"; };
A4716DB8654B431D894F89E1 /* RNImagePicker.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNImagePicker.xcodeproj; path = "../node_modules/react-native-image-picker/ios/RNImagePicker.xcodeproj"; sourceTree = "<group>"; };
ADBDB91F1DFEBF0600ED6528 /* RCTBlob.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTBlob.xcodeproj; path = "../node_modules/react-native/Libraries/Blob/RCTBlob.xcodeproj"; sourceTree = "<group>"; };
B8C44254005A4B80AD5CA558 /* RNFetchBlob.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNFetchBlob.xcodeproj; path = "../node_modules/react-native-fetch-blob/ios/RNFetchBlob.xcodeproj"; sourceTree = "<group>"; };
B8C44254005A4B80AD5CA558 /* RNFetchBlob.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNFetchBlob.xcodeproj; path = "../node_modules/rn-fetch-blob/ios/RNFetchBlob.xcodeproj"; sourceTree = "<group>"; };
C77F905EF22646F39B673572 /* MaterialCommunityIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = MaterialCommunityIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf"; sourceTree = "<group>"; };
CC3CF4044A914711B8D30D1A /* Zocial.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Zocial.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Zocial.ttf"; sourceTree = "<group>"; };
CCDC2774CD86466F897D88E2 /* libRNFileViewer.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFileViewer.a; sourceTree = "<group>"; };
CCDE9E9AF09B45F391B1C2AF /* SQLite.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = SQLite.xcodeproj; path = "../node_modules/react-native-sqlite-storage/src/ios/SQLite.xcodeproj"; sourceTree = "<group>"; };
DF1C50EBC11E46A3AF87F80A /* RCTImageResizer.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RCTImageResizer.xcodeproj; path = "../node_modules/react-native-image-resizer/ios/RCTImageResizer.xcodeproj"; sourceTree = "<group>"; };
E132B594F4FB4C96A2E2B0FF /* libRNCamera.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNCamera.a; sourceTree = "<group>"; };
E2638D52624B477FABB52B8F /* FontAwesome.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf"; sourceTree = "<group>"; };
F098E1ACCB594C828C851A57 /* libRNFS.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFS.a; sourceTree = "<group>"; };
F5E37D05726A4A08B2EE323A /* libRNFetchBlob.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFetchBlob.a; sourceTree = "<group>"; };
FC908F114F494130A324B402 /* RNCamera.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNCamera.xcodeproj; path = "../node_modules/react-native-camera/ios/RNCamera.xcodeproj"; sourceTree = "<group>"; };
FD370E24D76E461D960DD85D /* Feather.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Feather.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Feather.ttf"; sourceTree = "<group>"; };
FF411B45E68B4A8CBCC35777 /* Ionicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Ionicons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf"; sourceTree = "<group>"; };
59F5448FAF7345F8B568BD00 /* RNFileViewer.xcodeproj */ = {isa = PBXFileReference; name = "RNFileViewer.xcodeproj"; path = "../node_modules/react-native-file-viewer/ios/RNFileViewer.xcodeproj"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; };
CCDC2774CD86466F897D88E2 /* libRNFileViewer.a */ = {isa = PBXFileReference; name = "libRNFileViewer.a"; path = "libRNFileViewer.a"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -469,6 +487,7 @@
AF99EEC6C55042F7BFC87583 /* libRNImagePicker.a in Frameworks */,
F3D0BB525E6C490294D73075 /* libRNSecureRandom.a in Frameworks */,
82C61D3DAE0A4666883001E9 /* libRNFileViewer.a in Frameworks */,
12AE298E1C0E445682922DAB /* libRNCamera.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -602,6 +621,8 @@
381C047F2739439CB3E6452A /* libRNVectorIcons.a */,
44A39642217548C8ADA91CBA /* libRNImagePicker.a */,
22647ACF9A4C45918C44C599 /* libRNSecureRandom.a */,
CCDC2774CD86466F897D88E2 /* libRNFileViewer.a */,
E132B594F4FB4C96A2E2B0FF /* libRNCamera.a */,
);
name = "Recovered References";
sourceTree = "<group>";
@@ -655,6 +676,22 @@
name = Products;
sourceTree = "<group>";
};
4D8B71982163E8C500136BBC /* Products */ = {
isa = PBXGroup;
children = (
4D8B719C2163E8C500136BBC /* libRNFileViewer.a */,
);
name = Products;
sourceTree = "<group>";
};
4D8C5640217161BF00E93280 /* Products */ = {
isa = PBXGroup;
children = (
4D8C5644217161BF00E93280 /* libRNCamera.a */,
);
name = Products;
sourceTree = "<group>";
};
4DA7F8091FC1DA9C00353191 /* Products */ = {
isa = PBXGroup;
children = (
@@ -733,6 +770,7 @@
A4716DB8654B431D894F89E1 /* RNImagePicker.xcodeproj */,
252BD7B86BF7435B960DA901 /* RNSecureRandom.xcodeproj */,
59F5448FAF7345F8B568BD00 /* RNFileViewer.xcodeproj */,
FC908F114F494130A324B402 /* RNCamera.xcodeproj */,
);
name = Libraries;
sourceTree = "<group>";
@@ -890,6 +928,10 @@
ProductGroup = 146834001AC3E56700842450 /* Products */;
ProjectRef = 146833FF1AC3E56700842450 /* React.xcodeproj */;
},
{
ProductGroup = 4D8C5640217161BF00E93280 /* Products */;
ProjectRef = FC908F114F494130A324B402 /* RNCamera.xcodeproj */;
},
{
ProductGroup = 4D2A85AD1FBCE3AC0028537D /* Products */;
ProjectRef = 02C42EA98156482DB00BF86D /* RNDocumentPicker.xcodeproj */;
@@ -898,6 +940,10 @@
ProductGroup = 4D2A85AF1FBCE3AC0028537D /* Products */;
ProjectRef = B8C44254005A4B80AD5CA558 /* RNFetchBlob.xcodeproj */;
},
{
ProductGroup = 4D8B71982163E8C500136BBC /* Products */;
ProjectRef = 59F5448FAF7345F8B568BD00 /* RNFileViewer.xcodeproj */;
},
{
ProductGroup = 4D2A85B11FBCE3AC0028537D /* Products */;
ProjectRef = 9D8705D0D07C4A098FD912EB /* RNFS.xcodeproj */;
@@ -1214,6 +1260,20 @@
remoteRef = 4D7F8DA620A32BA0008B757D /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
4D8B719C2163E8C500136BBC /* libRNFileViewer.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRNFileViewer.a;
remoteRef = 4D8B719B2163E8C500136BBC /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
4D8C5644217161BF00E93280 /* libRNCamera.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRNCamera.a;
remoteRef = 4D8C5643217161BF00E93280 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
4DA7F80D1FC1DA9C00353191 /* libRNImagePicker.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
@@ -1343,14 +1403,15 @@
HEADER_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
"$(SRCROOT)/../node_modules/react-native-fetch-blob/ios/**",
"$(SRCROOT)/../node_modules/rn-fetch-blob/ios/**",
"$(SRCROOT)/../node_modules/react-native-fs/**",
"$(SRCROOT)/../node_modules/react-native-image-resizer/ios/RCTImageResizer",
"$(SRCROOT)/../node_modules/react-native-sqlite-storage/src/ios",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
"$(SRCROOT)..\node_modules\neact-native-image-pickerios",
"$(SRCROOT)..\node_modules\neact-native-securerandomios",
"$(SRCROOT)\..\node_modules\react-native-file-viewer\ios",
"$(SRCROOT)..\node_modules\neact-native-file-viewerios",
"$(SRCROOT)/../node_modules/react-native-camera/ios/**",
);
INFOPLIST_FILE = Joplin/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@@ -1358,6 +1419,7 @@
"$(inherited)",
"\"$(SRCROOT)/Joplin\"",
"\"$(SRCROOT)/Joplin\"",
"\"$(SRCROOT)/Joplin\"",
);
OTHER_LDFLAGS = (
"$(inherited)",
@@ -1385,14 +1447,15 @@
HEADER_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
"$(SRCROOT)/../node_modules/react-native-fetch-blob/ios/**",
"$(SRCROOT)/../node_modules/rn-fetch-blob/ios/**",
"$(SRCROOT)/../node_modules/react-native-fs/**",
"$(SRCROOT)/../node_modules/react-native-image-resizer/ios/RCTImageResizer",
"$(SRCROOT)/../node_modules/react-native-sqlite-storage/src/ios",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
"$(SRCROOT)..\node_modules\neact-native-image-pickerios",
"$(SRCROOT)..\node_modules\neact-native-securerandomios",
"$(SRCROOT)\..\node_modules\react-native-file-viewer\ios",
"$(SRCROOT)..\node_modules\neact-native-file-viewerios",
"$(SRCROOT)/../node_modules/react-native-camera/ios/**",
);
INFOPLIST_FILE = Joplin/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@@ -1400,6 +1463,7 @@
"$(inherited)",
"\"$(SRCROOT)/Joplin\"",
"\"$(SRCROOT)/Joplin\"",
"\"$(SRCROOT)/Joplin\"",
);
OTHER_LDFLAGS = (
"$(inherited)",

View File

@@ -17,11 +17,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>10.0.24</string>
<string>10.0.26</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>24</string>
<string>26</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>

View File

@@ -33,6 +33,7 @@ const SyncTargetNextcloud = require('lib/SyncTargetNextcloud.js');
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 DecryptionWorker = require('lib/services/DecryptionWorker');
const BaseService = require('lib/services/BaseService');
@@ -356,6 +357,10 @@ class BaseApplication {
DecryptionWorker.instance().scheduleStart();
}
if (this.hasGui() && action.type === 'SYNC_CREATED_RESOURCE') {
ResourceFetcher.instance().queueDownload(action.id);
}
return result;
}
@@ -454,7 +459,7 @@ class BaseApplication {
initArgs = Object.assign(initArgs, extraFlags);
this.logger_.addTarget('file', { path: profileDir + '/log.txt' });
//this.logger_.addTarget('console');
// if (Setting.value('env') === 'dev') this.logger_.addTarget('console');
this.logger_.setLevel(initArgs.logLevel);
reg.setLogger(this.logger_);
@@ -510,6 +515,10 @@ class BaseApplication {
DecryptionWorker.instance().setEncryptionService(EncryptionService.instance());
await EncryptionService.instance().loadMasterKeysFromSettings();
ResourceFetcher.instance().setFileApi(() => { return reg.syncTarget().fileApi() });
ResourceFetcher.instance().setLogger(this.logger_);
ResourceFetcher.instance().start();
let currentFolderId = Setting.value('activeFolderId');
let currentFolder = null;
if (currentFolderId) currentFolder = await Folder.load(currentFolderId);

View File

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

View File

@@ -147,6 +147,7 @@ class ClipperServer {
const response = await this.api_.route(request.method, url.pathname, url.query, body, files);
writeResponse(200, response);
} catch (error) {
this.logger().error(error);
writeResponse(error.httpCode ? error.httpCode : 500, error.message);
}
}
@@ -173,7 +174,7 @@ class ClipperServer {
}
});
} else {
if (request.method === 'POST') {
if (request.method === 'POST' || request.method === 'PUT') {
let body = '';
request.on('data', (data) => {

View File

@@ -2,7 +2,6 @@ const MarkdownIt = require('markdown-it');
const Entities = require('html-entities').AllHtmlEntities;
const htmlentities = (new Entities()).encode;
const Resource = require('lib/models/Resource.js');
const ModelCache = require('lib/ModelCache');
const ObjectUtils = require('lib/ObjectUtils');
const { shim } = require('lib/shim.js');
const { _ } = require('lib/locale');
@@ -17,7 +16,6 @@ class MdToHtml {
this.loadedResources_ = {};
this.cachedContent_ = null;
this.cachedContentKey_ = null;
this.modelCache_ = new ModelCache();
// Must include last "/"
this.resourceBaseUrl_ = ('resourceBaseUrl' in options) ? options.resourceBaseUrl : null;
@@ -30,12 +28,18 @@ class MdToHtml {
const r = resources[n];
k.push(r.id);
}
k.push(md5(escape(body))); // https://github.com/pvorb/node-md5/issues/41
k.push(md5(JSON.stringify(style)));
k.push(md5(JSON.stringify(options)));
return k.join('_');
}
clearCache() {
this.cachedContent_ = null;
this.cachedContentKey_ = null;
}
renderAttrs_(attrs) {
if (!attrs) return '';
@@ -74,8 +78,6 @@ class MdToHtml {
}
async loadResource(id, options) {
// console.info('Loading resource: ' + id);
// Initially set to to an empty object to make
// it clear that it is being loaded. Otherwise
// it sometimes results in multiple calls to
@@ -83,12 +85,18 @@ class MdToHtml {
this.loadedResources_[id] = {};
const resource = await Resource.load(id);
//const resource = await this.modelCache_.load(Resource, id);
if (!resource) {
// Can happen for example if an image is attached to a note, but the resource hasn't
// been downloaded from the sync target yet.
console.warn('Cannot load resource: ' + id);
console.info('Cannot load resource: ' + id);
delete this.loadedResources_[id];
return;
}
if (resource.fetch_status !== Resource.FETCH_STATUS_DONE) {
delete this.loadedResources_[id];
console.info('Resource not yet fetched: ' + id);
return;
}
@@ -102,7 +110,7 @@ class MdToHtml {
const href = this.getAttr_(attrs, 'src');
if (!Resource.isResourceUrl(href)) {
return '<img title="' + htmlentities(title) + '" src="' + href + '"/>';
return '<img data-from-md title="' + htmlentities(title) + '" src="' + href + '"/>';
}
const resourceId = Resource.urlToId(href);
@@ -118,7 +126,7 @@ class MdToHtml {
if (Resource.isSupportedImageMimeType(mime)) {
let src = './' + Resource.filename(resource);
if (this.resourceBaseUrl_ !== null) src = this.resourceBaseUrl_ + src;
let output = '<img data-resource-id="' + resource.id + '" title="' + htmlentities(title) + '" src="' + src + '"/>';
let output = '<img data-from-md data-resource-id="' + resource.id + '" title="' + htmlentities(title) + '" src="' + src + '"/>';
return output;
}
@@ -167,7 +175,7 @@ class MdToHtml {
}
const js = options.postMessageSyntax + "(" + JSON.stringify(href) + "); return false;";
let output = "<a " + resourceIdAttr + " title='" + htmlentities(title) + "' href='" + hrefAttr + "' onclick='" + js + "'>" + icon;
let output = "<a data-from-md " + resourceIdAttr + " title='" + htmlentities(title) + "' href='" + hrefAttr + "' onclick='" + js + "'>" + icon;
return output;
}
@@ -209,7 +217,6 @@ class MdToHtml {
}
}
renderTokens_(markdownIt, tokens, options) {
let output = [];
let previousToken = null;
@@ -487,7 +494,7 @@ class MdToHtml {
while (renderedBody.indexOf('mJOPm') >= 0) {
renderedBody = renderedBody.replace(/mJOPmCHECKBOXm([A-Z]+)m(\d+)m/, function(v, type, index) {
const js = options.postMessageSyntax + "('checkboxclick:" + type + ':' + index + "'); this.classList.contains('tick') ? this.classList.remove('tick') : this.classList.add('tick'); return false;";
return '<a href="#" onclick="' + js + '" class="checkbox ' + (type == 'NOTICK' ? '' : 'tick') + '"><span>' + '' + '</span></a>';
return '<a data-from-md href="#" onclick="' + js + '" class="checkbox ' + (type == 'NOTICK' ? '' : 'tick') + '"><span>' + '' + '</span></a>';
});
if (loopCount++ >= 9999) break;
}

View File

@@ -0,0 +1,83 @@
const React = require('react'); const Component = React.Component;
const { View, Button, StyleSheet, TouchableOpacity } = require('react-native');
const { globalStyle, themeStyle } = require('lib/components/global-style.js');
import { RNCamera } from 'react-native-camera';
const Icon = require('react-native-vector-icons/Ionicons').default;
const { _ } = require('lib/locale.js');
class CameraView extends Component {
constructor() {
super();
this.state = {
snapping: false,
};
this.back_onPress = this.back_onPress.bind(this);
this.photo_onPress = this.photo_onPress.bind(this);
}
back_onPress() {
if (this.props.onCancel) this.props.onCancel();
}
async photo_onPress() {
if (!this.camera || !this.props.onPhoto) return;
this.setState({ snapping: true });
const result = await this.camera.takePictureAsync({
quality: 0.8,
exif: true,
fixOrientation: true
});
if (this.props.onPhoto) this.props.onPhoto(result);
this.setState({ snapping: false });
}
render() {
const theme = themeStyle(this.props.theme);
const photoIcon = this.state.snapping ? 'md-checkmark' : 'md-camera';
return (
<View style={this.props.style}>
<RNCamera
style={{flex:1}}
ref={ref => { this.camera = ref; }}
type={RNCamera.Constants.Type.back}
permissionDialogTitle={_('Permission to use camera')}
permissionDialogMessage={_('Your permission to use your camera is required.')}
>
<View style={{flex:1, justifyContent:'space-between', flexDirection:'column'}}>
<View style={{flex:1, justifyContent:'flex-start'}}>
<TouchableOpacity onPress={this.back_onPress}>
<View style={{ marginLeft:5, marginTop:5, borderRadius:90, width:50,height:50, display:'flex', backgroundColor:'#ffffff55', justifyContent:'center', alignItems:'center'}}>
<Icon name={'md-arrow-back'} style={{
fontSize: 40,
color: 'black',
}} />
</View>
</TouchableOpacity>
</View>
<View style={{flex:1, justifyContent:'center', alignItems:'flex-end', flexDirection:'row'}}>
<TouchableOpacity onPress={this.photo_onPress}>
<View style={{marginBottom:20, borderRadius:90, width:90,height:90,backgroundColor:'#ffffffaa', display:'flex', justifyContent:'center', alignItems:'center'}}>
<Icon name={photoIcon} style={{
fontSize: 60,
color: 'black',
}} />
</View>
</TouchableOpacity>
</View>
</View>
</RNCamera>
</View>
);
}
}
module.exports = CameraView;

View File

@@ -40,6 +40,13 @@ class NoteBodyViewer extends Component {
}
shouldComponentUpdate(nextProps, nextState) {
const safeGetNoteProp = (props, propName) => {
if (!props) return null;
if (!props.note) return null;
return props.note[propName];
}
// To address https://github.com/laurent22/joplin/issues/433
// If a checkbox in a note is ticked, the body changes, which normally would trigger a re-render
// of this component, which has the unfortunate side effect of making the view scroll back to the top.
@@ -47,9 +54,19 @@ class NoteBodyViewer extends Component {
// So here, if the note has not changed, we prevent the component from updating.
// This fixes the above issue. A drawback of this is if the note is updated via sync, this change
// will not be displayed immediately.
const currentNoteId = this.props && this.props.note ? this.props.note.id : null;
const nextNoteId = nextProps && nextProps.note ? nextProps.note.id : null;
return currentNoteId !== nextNoteId || nextState.webViewLoaded !== this.state.webViewLoaded;
const currentNoteId = safeGetNoteProp(this.props, 'id');
const nextNoteId = safeGetNoteProp(nextProps, 'id');
if (currentNoteId !== nextNoteId || nextState.webViewLoaded !== this.state.webViewLoaded) return true;
// If the length of the body has changed, then it's something other than a checkbox that has changed,
// for example a resource that has been attached to the note while in View mode. In that case, update.
return (safeGetNoteProp(this.props, 'body') + '').length !== (safeGetNoteProp(nextProps, 'body') + '').length;
}
rebuildMd() {
this.mdToHtml_.clearCache();
this.forceUpdate();
}
render() {

View File

@@ -22,12 +22,13 @@ const { Checkbox } = require('lib/components/checkbox.js');
const { _ } = require('lib/locale.js');
const { reg } = require('lib/registry.js');
const { shim } = require('lib/shim.js');
const ResourceFetcher = require('lib/services/ResourceFetcher');
const { BaseScreenComponent } = require('lib/components/base-screen.js');
const { globalStyle, themeStyle } = require('lib/components/global-style.js');
const { dialogs } = require('lib/dialogs.js');
const DialogBox = require('react-native-dialogbox').default;
const { NoteBodyViewer } = require('lib/components/note-body-viewer.js');
const RNFetchBlob = require('react-native-fetch-blob').default;
const RNFetchBlob = require('rn-fetch-blob').default;
const { DocumentPicker, DocumentPickerUtil } = require('react-native-document-picker');
const ImageResizer = require('react-native-image-resizer').default;
const shared = require('lib/components/shared/note-screen-shared.js');
@@ -35,6 +36,7 @@ const ImagePicker = require('react-native-image-picker');
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');
import FileViewer from 'react-native-file-viewer';
@@ -59,6 +61,7 @@ class NoteScreenComponent extends BaseScreenComponent {
heightBumpView:0,
noteTagDialogShown: false,
fromShare: false,
showCamera: false,
};
// iOS doesn't support multiline text fields properly so disable it
@@ -136,6 +139,7 @@ class NoteScreenComponent extends BaseScreenComponent {
});
}, 5);
} else if (item.type_ === BaseModel.TYPE_RESOURCE) {
if (!Resource.isReady(item)) throw new Error(_('This attachment is not downloaded or not decrypted yet.'));
const resourcePath = Resource.fullPath(item);
await FileViewer.open(resourcePath);
} else {
@@ -148,6 +152,18 @@ class NoteScreenComponent extends BaseScreenComponent {
dialogs.error(this, error.message);
}
}
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) {
this.refs.noteBodyViewer.rebuildMd();
}
}
this.attachPhoto_onPress = this.attachPhoto_onPress.bind(this);
this.cameraView_onPhoto = this.cameraView_onPhoto.bind(this);
this.cameraView_onCancel = this.cameraView_onCancel.bind(this);
}
styles() {
@@ -205,6 +221,8 @@ class NoteScreenComponent extends BaseScreenComponent {
BackButtonService.addHandler(this.backHandler);
NavService.addHandler(this.navHandler);
ResourceFetcher.instance().on('downloadComplete', this.resourceFetcher_downloadComplete);
await shared.initState(this);
this.refreshNoteMetadata();
@@ -218,7 +236,9 @@ class NoteScreenComponent extends BaseScreenComponent {
BackButtonService.removeHandler(this.backHandler);
NavService.removeHandler(this.navHandler);
if (this.state.fromShare) {
ResourceFetcher.instance().off('downloadComplete', this.resourceFetcher_downloadComplete);
if (Platform.OS !== 'ios' && this.state.fromShare) {
ShareExtension.close();
}
}
@@ -321,7 +341,7 @@ class NoteScreenComponent extends BaseScreenComponent {
}
}
async attachFile(pickerResponse, fileType) {
async attachFile(pickerResponse, fileType) {
if (!pickerResponse) {
reg.logger().warn('Got no response from picker');
return;
@@ -361,7 +381,7 @@ class NoteScreenComponent extends BaseScreenComponent {
resource.id = uuid.create();
resource.mime = mimeType;
resource.title = pickerResponse.fileName ? pickerResponse.fileName : _('Untitled');
resource.file_extension = safeFileExtension(fileExtension(pickerResponse.fileName));
resource.file_extension = safeFileExtension(fileExtension(pickerResponse.fileName ? pickerResponse.fileName : localFilePath));
if (!resource.mime) resource.mime = 'application/octet-stream';
@@ -375,7 +395,8 @@ class NoteScreenComponent extends BaseScreenComponent {
dialogs.error(this, _('Unsupported image type: %s', mimeType));
return;
} else {
await RNFetchBlob.fs.cp(localFilePath, targetPath);
await shim.fsDriver().copy(localFilePath, targetPath);
const stat = await shim.fsDriver().stat(targetPath);
if (stat.size >= 10000000) {
await shim.fsDriver().remove(targetPath);
@@ -406,6 +427,25 @@ class NoteScreenComponent extends BaseScreenComponent {
await this.attachFile(response, 'image');
}
attachPhoto_onPress() {
this.setState({ showCamera: true });
}
cameraView_onPhoto(data) {
this.attachFile({
uri: data.uri,
didCancel: false,
error: null,
type: 'image/jpg',
}, 'image');
this.setState({ showCamera: false });
}
cameraView_onCancel() {
this.setState({ showCamera: false });
}
async attachFile_onPress() {
const response = await this.pickDocument();
await this.attachFile(response, 'all');
@@ -461,6 +501,17 @@ class NoteScreenComponent extends BaseScreenComponent {
}
}
async showSource_onPress() {
if (!this.state.note.id) return;
let note = await Note.load(this.state.note.id);
try {
Linking.openURL(note.source_url);
} catch (error) {
await dialogs.error(this, error.message);
}
}
copyMarkdownLink_onPress() {
const note = this.state.note;
Clipboard.setString(Note.markdownTag(note));
@@ -470,6 +521,7 @@ class NoteScreenComponent extends BaseScreenComponent {
const note = this.state.note;
const isTodo = note && !!note.is_todo;
const isSaved = note && note.id;
const hasSource = note && note.source_url;
let output = [];
@@ -478,7 +530,7 @@ class NoteScreenComponent extends BaseScreenComponent {
let canAttachPicture = true;
if (Platform.OS === 'android' && Platform.Version < 21) canAttachPicture = false;
if (canAttachPicture) {
output.push({ title: _('Attach photo'), onPress: () => { this.attachImage_onPress(); } });
output.push({ title: _('Attach photo'), onPress: () => { this.attachPhoto_onPress(); } });
output.push({ title: _('Attach any file'), onPress: () => { this.attachFile_onPress(); } });
output.push({ isDivider: true });
}
@@ -494,6 +546,7 @@ class NoteScreenComponent extends BaseScreenComponent {
output.push({ isDivider: true });
if (this.props.showAdvancedOptions) output.push({ title: this.state.showNoteMetadata ? _('Hide metadata') : _('Show metadata'), onPress: () => { this.showMetadata_onPress(); } });
output.push({ title: _('View on map'), onPress: () => { this.showOnMap_onPress(); } });
if (hasSource) output.push({ title: _('Go to source URL'), onPress: () => { this.showSource_onPress(); } });
output.push({ isDivider: true });
output.push({ title: _('Delete'), onPress: () => { this.deleteNote_onPress(); } });
@@ -526,6 +579,13 @@ class NoteScreenComponent extends BaseScreenComponent {
const folder = this.state.folder;
const isNew = !note.id;
if (this.state.showCamera) {
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) => {
@@ -534,6 +594,7 @@ class NoteScreenComponent extends BaseScreenComponent {
bodyComponent = <NoteBodyViewer
onJoplinLinkClick={this.onJoplinLinkClick_}
ref="noteBodyViewer"
style={this.styles().noteBodyViewer}
webViewStyle={theme}
note={note}

View File

@@ -59,9 +59,11 @@ class SelectDateTimeDialog extends Component {
}
render() {
const clearAlarmText = _("Clear alarm"); // For unknown reasons, this particular string doesn't get translated if it's directly in the text property below
const popupActions = [
<DialogButton text={_("Save alarm")} align="center" onPress={() => this.onAccept()} key="saveButton" />,
<DialogButton text={_("Clear alarm")} align="center" onPress={() => this.onClear()} key="clearButton" />,
<DialogButton text={clearAlarmText} align="center" onPress={() => this.onClear()} key="clearButton" />,
<DialogButton text={_("Cancel")} align="center" onPress={() => this.onReject()} key="cancelButton" />,
];

View File

@@ -13,7 +13,7 @@ class GeolocationNode {
response = await response.json();
if (!('lat' in response) || !('lon' in response)) throw new Error('Invalid geolocation response: ' . JSON.stringify(response));
if (!('lat' in response) || !('lon' in response)) throw new Error('Invalid geolocation response: ' + (response ? JSON.stringify(response) : '<null>'));
return {
timestamp: (new Date()).getTime(),

View File

@@ -3,6 +3,7 @@ const { promiseChain } = require('lib/promise-utils.js');
const { time } = require('lib/time-utils.js');
const { Database } = require('lib/database.js');
const { sprintf } = require('sprintf-js');
const Resource = require('lib/models/Resource');
const structureSql = `
CREATE TABLE folders (
@@ -249,7 +250,7 @@ class JoplinDatabase extends Database {
// default value and thus might cause problems. In that case, the default value
// must be set in the synchronizer too.
const existingDatabaseVersions = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
const existingDatabaseVersions = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
let currentVersionIndex = existingDatabaseVersions.indexOf(fromVersion);
@@ -395,6 +396,12 @@ class JoplinDatabase extends Database {
queries.push('ALTER TABLE folders ADD COLUMN parent_id TEXT NOT NULL DEFAULT ""');
}
if (targetVersion == 13) {
queries.push('ALTER TABLE resources ADD COLUMN fetch_status INT NOT NULL DEFAULT "2"');
queries.push('ALTER TABLE resources ADD COLUMN fetch_error TEXT NOT NULL DEFAULT ""');
queries.push({ sql: 'UPDATE resources SET fetch_status = ?', params: [Resource.FETCH_STATUS_DONE] });
}
queries.push({ sql: 'UPDATE version SET version = ?', params: [targetVersion] });
await this.transactionExecBatch(queries);

View File

@@ -236,7 +236,12 @@ class BaseItem extends BaseModel {
return propValue;
}
static async serialize(item, type = null, shownKeys = null) {
static async serialize(item, shownKeys = null) {
if (shownKeys === null) {
shownKeys = this.itemClass(item).fieldNames();
shownKeys.push('type_');
}
item = this.filter(item);
let output = {};
@@ -283,7 +288,19 @@ class BaseItem extends BaseModel {
static async serializeForSync(item) {
const ItemClass = this.itemClass(item);
let serialized = await ItemClass.serialize(item);
let shownKeys = ItemClass.fieldNames();
shownKeys.push('type_');
if (ItemClass.syncExcludedKeys) {
const keys = ItemClass.syncExcludedKeys();
for (let i = 0; i < keys.length; i++) {
const idx = shownKeys.indexOf(keys[i]);
shownKeys.splice(idx, 1);
}
}
const serialized = await ItemClass.serialize(item, shownKeys);
if (!Setting.value('encryption.enabled') || !ItemClass.encryptionSupported()) {
// Normally not possible since itemsThatNeedSync should only return decrypted items
if (!!item.encryption_applied) throw new JoplinError('Item is encrypted but encryption is currently disabled', 'cannotSyncEncrypted');
@@ -307,7 +324,6 @@ class BaseItem extends BaseModel {
reducedItem.encryption_applied = 1;
reducedItem.encryption_cipher_text = cipherText;
return ItemClass.serialize(reducedItem)
}

View File

@@ -15,13 +15,6 @@ class Folder extends BaseItem {
return 'folders';
}
static async serialize(folder) {
let fieldNames = this.fieldNames();
fieldNames.push('type_');
//lodash.pull(fieldNames, 'parent_id');
return super.serialize(folder, 'folder', fieldNames);
}
static modelType() {
return BaseModel.TYPE_FOLDER;
}

View File

@@ -19,12 +19,6 @@ class MasterKey extends BaseItem {
return this.modelSelectOne('SELECT * FROM master_keys WHERE created_time >= (SELECT max(created_time) FROM master_keys)');
}
static async serialize(item, type = null, shownKeys = null) {
let fieldNames = this.fieldNames();
fieldNames.push('type_');
return super.serialize(item, 'master_key', fieldNames);
}
static async save(o, options = null) {
return super.save(o, options).then((item) => {
this.dispatch({

View File

@@ -25,14 +25,8 @@ class Note extends BaseItem {
return field in fieldsToLabels ? fieldsToLabels[field] : field;
}
static async serialize(note, type = null, shownKeys = null) {
let fieldNames = this.fieldNames();
fieldNames.push('type_');
return super.serialize(note, 'note', fieldNames);
}
static async serializeForEdit(note) {
return super.serialize(note, 'note', ['title', 'body']);
return super.serialize(note, ['title', 'body']);
}
static async unserializeForEdit(content) {
@@ -47,7 +41,7 @@ class Note extends BaseItem {
let fieldNames = this.fieldNames();
fieldNames.push('type_');
lodash.pull(fieldNames, 'title', 'body');
return super.serialize(note, 'note', fieldNames);
return super.serialize(note, fieldNames);
}
static minimalSerializeForDisplay(note) {
@@ -75,12 +69,16 @@ class Note extends BaseItem {
lodash.pull(fieldNames, 'updated_time');
lodash.pull(fieldNames, 'order');
return super.serialize(n, 'note', fieldNames);
return super.serialize(n, fieldNames);
}
static defaultTitle(note) {
if (note.body && note.body.length) {
const lines = note.body.trim().split("\n");
return this.defaultTitleFromBody(note.body);
}
static defaultTitleFromBody(body) {
if (body && body.length) {
const lines = body.trim().split("\n");
let output = lines[0].trim();
// Remove the first #, *, etc.
while (output.length) {
@@ -424,17 +422,25 @@ class Note extends BaseItem {
return Note.save(modifiedNote, { autoTimestamp: false });
}
static toggleIsTodo(note) {
static changeNoteType(note, type) {
if (!('is_todo' in note)) throw new Error('Missing "is_todo" property');
let output = Object.assign({}, note);
output.is_todo = output.is_todo ? 0 : 1;
const newIsTodo = type === 'todo' ? 1 : 0;
if (Number(note.is_todo) === newIsTodo) return note;
const output = Object.assign({}, note);
output.is_todo = newIsTodo;
output.todo_due = 0;
output.todo_completed = 0;
return output;
}
static toggleIsTodo(note) {
return this.changeNoteType(note, !!note.is_todo ? 'note' : 'todo');
}
static async duplicate(noteId, options = null) {
const changes = options && options.changes;
const uniqueTitle = options && options.uniqueTitle;

View File

@@ -11,12 +11,6 @@ class NoteTag extends BaseItem {
return BaseModel.TYPE_NOTE_TAG;
}
static async serialize(item, type = null, shownKeys = null) {
let fieldNames = this.fieldNames();
fieldNames.push('type_');
return super.serialize(item, 'note_tag', fieldNames);
}
static async byNoteIds(noteIds) {
if (!noteIds.length) return [];
return this.modelSelectAll('SELECT * FROM note_tags WHERE note_id IN ("' + noteIds.join('","') + '")');

View File

@@ -26,22 +26,36 @@ class Resource extends BaseItem {
}
static isSupportedImageMimeType(type) {
const imageMimeTypes = ["image/jpg", "image/jpeg", "image/png", "image/gif", "image/svg+xml"];
const imageMimeTypes = ["image/jpg", "image/jpeg", "image/png", "image/gif", "image/svg+xml", "image/webp"];
return imageMimeTypes.indexOf(type.toLowerCase()) >= 0;
}
static resetStartedFetchStatus() {
return this.db().exec('UPDATE resources SET fetch_status = ? WHERE fetch_status = ?', [Resource.FETCH_STATUS_IDLE, Resource.FETCH_STATUS_STARTED]);
}
static needToBeFetched(limit = null) {
let sql = 'SELECT * FROM resources WHERE fetch_status = ? ORDER BY updated_time DESC';
if (limit !== null) sql += ' LIMIT ' + limit;
return this.modelSelectAll(sql, [Resource.FETCH_STATUS_IDLE]);
}
static async saveFetchStatus(resourceId, status, error = null) {
const o = {
id: resourceId,
fetch_status: status,
}
if (error !== null) o.fetch_error = error;
return Resource.save(o, { autoTimestamp: false });
}
static fsDriver() {
if (!Resource.fsDriver_) Resource.fsDriver_ = new FsDriverDummy();
return Resource.fsDriver_;
}
static async serialize(item, type = null, shownKeys = null) {
let fieldNames = this.fieldNames();
fieldNames.push('type_');
//fieldNames = ArrayUtils.removeElement(fieldNames, 'encryption_blob_encrypted');
return super.serialize(item, 'resource', fieldNames);
}
static filename(resource, encryptedBlob = false) {
let extension = encryptedBlob ? 'crypted' : resource.file_extension;
if (!extension) extension = resource.mime ? mime.toFileExtension(resource.mime) : '';
@@ -49,6 +63,10 @@ class Resource extends BaseItem {
return resource.id + extension;
}
static syncExcludedKeys() {
return ['fetch_status', 'fetch_error'];
}
static friendlyFilename(resource) {
let output = safeFilename(resource.title); // Make sure not to allow spaces or any special characters as it's not supported in HTTP headers
if (!output) output = resource.id;
@@ -62,6 +80,10 @@ class Resource extends BaseItem {
return Setting.value('resourceDir') + '/' + this.filename(resource, encryptedBlob);
}
static isReady(resource) {
return resource && resource.fetch_status === Resource.FETCH_STATUS_DONE && !resource.encryption_blob_encrypted;
}
// For resources, we need to decrypt the item (metadata) and the resource binary blob.
static async decrypt(item) {
// The item might already be decrypted but not the blob (for instance if it crashes while
@@ -72,7 +94,7 @@ class Resource extends BaseItem {
const plainTextPath = this.fullPath(decryptedItem);
const encryptedPath = this.fullPath(decryptedItem, true);
const noExtPath = pathUtils.dirname(encryptedPath) + '/' + pathUtils.filename(encryptedPath);
// When the resource blob is downloaded by the synchroniser, it's initially a file with no
// extension (since it's encrypted, so we don't know its extension). So here rename it
// to a file with a ".crypted" extension so that it's better identified, and then decrypt it.
@@ -199,4 +221,9 @@ class Resource extends BaseItem {
Resource.IMAGE_MAX_DIMENSION = 1920;
Resource.FETCH_STATUS_IDLE = 0;
Resource.FETCH_STATUS_STARTED = 1;
Resource.FETCH_STATUS_DONE = 2;
Resource.FETCH_STATUS_ERROR = 3;
module.exports = Resource;

View File

@@ -90,7 +90,7 @@ class Setting extends BaseModel {
// Might be fixed in Electron 18.x but no non-beta release yet. So for now
// by default we disable it on Linux.
'showTrayIcon': { value: platform !== 'linux', type: Setting.TYPE_BOOL, public: true, appTypes: ['desktop'], label: () => _('Show tray icon'), description: () => {
return platform === 'linux' ? _('Note: Does not work in all desktop environments.') : null;
return platform === 'linux' ? _('Note: Does not work in all desktop environments.') : _('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.');
}},
'startMinimized': { value: false, type: Setting.TYPE_BOOL, public: true, appTypes: ['desktop'], label: () => _('Start application minimised in the tray icon') },

View File

@@ -15,12 +15,6 @@ class Tag extends BaseItem {
return BaseModel.TYPE_TAG;
}
static async serialize(item, type = null, shownKeys = null) {
let fieldNames = this.fieldNames();
fieldNames.push('type_');
return super.serialize(item, 'tag', fieldNames);
}
static async noteIds(tagId) {
let rows = await this.db().selectAll('SELECT note_id FROM note_tags WHERE tag_id = ?', [tagId]);
let output = [];

View File

@@ -1,4 +1,5 @@
const BaseItem = require('lib/models/BaseItem');
const Resource = require('lib/models/Resource');
const { Logger } = require('lib/logger.js');
class DecryptionWorker {
@@ -43,7 +44,7 @@ class DecryptionWorker {
this.scheduleId_ = setTimeout(() => {
this.scheduleId_ = null;
this.start({
materKeyNotLoadedHandler: 'dispatch',
masterKeyNotLoadedHandler: 'dispatch',
});
}, 1000);
}
@@ -56,7 +57,7 @@ class DecryptionWorker {
async start(options = null) {
if (options === null) options = {};
if (!('materKeyNotLoadedHandler' in options)) options.materKeyNotLoadedHandler = 'throw';
if (!('masterKeyNotLoadedHandler' in options)) options.masterKeyNotLoadedHandler = 'throw';
if (this.state_ !== 'idle') {
this.logger().info('DecryptionWorker: cannot start because state is "' + this.state_ + '"');
@@ -83,6 +84,8 @@ class DecryptionWorker {
const ItemClass = BaseItem.itemClass(item);
if (('fetch_status' in item) && item.fetch_status !== Resource.FETCH_STATUS_DONE) continue;
this.dispatchReport({
itemIndex: i,
itemCount: items.length,
@@ -95,7 +98,7 @@ class DecryptionWorker {
} catch (error) {
excludedIds.push(item.id);
if (error.code === 'masterKeyNotLoaded' && options.materKeyNotLoadedHandler === 'dispatch') {
if (error.code === 'masterKeyNotLoaded' && options.masterKeyNotLoadedHandler === 'dispatch') {
if (notLoadedMasterKeyDisptaches.indexOf(error.masterKeyId) < 0) {
this.dispatch({
type: 'MASTERKEY_ADD_NOT_LOADED',
@@ -106,11 +109,11 @@ class DecryptionWorker {
continue;
}
if (error.code === 'masterKeyNotLoaded' && options.materKeyNotLoadedHandler === 'throw') {
if (error.code === 'masterKeyNotLoaded' && options.masterKeyNotLoadedHandler === 'throw') {
throw error;
}
this.logger().warn('DecryptionWorker: error for: ' + item.id + ' (' + ItemClass.tableName() + ')', error);
this.logger().warn('DecryptionWorker: error for: ' + item.id + ' (' + ItemClass.tableName() + ')', error, item);
}
}

View File

@@ -4,6 +4,7 @@ const BaseModel = require('lib/BaseModel');
const Folder = require('lib/models/Folder');
const Note = require('lib/models/Note');
const { shim } = require('lib/shim');
const unidecode = require('unidecode');
class InteropService_Exporter_Md extends InteropService_Exporter_Base {
@@ -40,7 +41,7 @@ class InteropService_Exporter_Md extends InteropService_Exporter_Base {
}
if (item.type_ === BaseModel.TYPE_NOTE) {
const noteFilePath = dirPath + '/' + safeFilename(item.title, null, true) + '.md';
const noteFilePath = dirPath + '/' + safeFilename(unidecode(item.title), null, true) + '.md';
const noteContent = await Note.serializeForEdit(item);
await shim.fsDriver().writeFile(noteFilePath, noteContent, 'utf-8');
}

View File

@@ -0,0 +1,182 @@
const Resource = require('lib/models/Resource');
const BaseService = require('lib/services/BaseService');
const BaseSyncTarget = require('lib/BaseSyncTarget');
const { Logger } = require('lib/logger.js');
const EventEmitter = require('events');
class ResourceFetcher extends BaseService {
constructor(fileApi = null) {
super();
this.setFileApi(fileApi);
this.logger_ = new Logger();
this.queue_ = [];
this.fetchingItems_ = {};
this.resourceDirName_ = BaseSyncTarget.resourceDirName();
this.maxDownloads_ = 3;
this.addingResources_ = false;
this.eventEmitter_ = new EventEmitter();
}
static instance() {
if (this.instance_) return this.instance_;
this.instance_ = new ResourceFetcher();
return this.instance_;
}
on(eventName, callback) {
return this.eventEmitter_.on(eventName, callback);
}
off(eventName, callback) {
return this.eventEmitter_.removeListener(eventName, callback);
}
setLogger(logger) {
this.logger_ = logger;
}
logger() {
return this.logger_;
}
setFileApi(v) {
if (v !== null && typeof v !== 'function') throw new Error('fileApi must be a function that returns the API. Type is ' + (typeof v));
this.fileApi_ = v;
}
async fileApi() {
return this.fileApi_();
}
queuedItemIndex_(resourceId) {
for (let i = 0; i < this.fetchingItems_.length; i++) {
const item = this.fetchingItems_[i];
if (item.id === resourceId) return i;
}
return -1;
}
queueDownload(resourceId, priority = null) {
if (priority === null) priority = 'normal';
const index = this.queuedItemIndex_(resourceId);
if (index >= 0) return false;
const item = { id: resourceId };
if (priority === 'high') {
this.queue_.splice(0, 0, item);
} else {
this.queue_.push(item);
}
this.scheduleQueueProcess();
return true;
}
async startDownload_(resourceId) {
if (this.fetchingItems_[resourceId]) return;
this.fetchingItems_[resourceId] = true;
const completeDownload = (emitDownloadComplete = true) => {
delete this.fetchingItems_[resource.id];
this.scheduleQueueProcess();
if (emitDownloadComplete) this.eventEmitter_.emit('downloadComplete', { id: resource.id });
}
const resource = await Resource.load(resourceId);
// Shouldn't happen, but just to be safe don't re-download the
// resource if it's already been downloaded.
if (resource.fetch_status === Resource.FETCH_STATUS_DONE) {
completeDownload(false);
return;
}
this.fetchingItems_[resourceId] = resource;
const localResourceContentPath = Resource.fullPath(resource);
const remoteResourceContentPath = this.resourceDirName_ + "/" + resource.id;
await Resource.saveFetchStatus(resource.id, Resource.FETCH_STATUS_STARTED);
const fileApi = await this.fileApi();
this.logger().debug('ResourceFetcher: Downloading resource: ' + resource.id);
fileApi.get(remoteResourceContentPath, { path: localResourceContentPath, target: "file" }).then(async () => {
await Resource.saveFetchStatus(resource.id, Resource.FETCH_STATUS_DONE);
this.logger().debug('ResourceFetcher: Resource downloaded: ' + resource.id);
completeDownload();
}).catch(async (error) => {
this.logger().error('ResourceFetcher: Could not download resource: ' + resource.id, error);
await Resource.saveFetchStatus(resource.id, Resource.FETCH_STATUS_ERROR, error.message);
completeDownload();
});
}
processQueue_() {
while (Object.getOwnPropertyNames(this.fetchingItems_).length < this.maxDownloads_) {
if (!this.queue_.length) break;
const item = this.queue_.splice(0, 1)[0];
this.startDownload_(item.id);
}
if (!this.queue_.length) {
this.autoAddResources(10);
}
}
async waitForAllFinished() {
return new Promise((resolve, reject) => {
const iid = setInterval(() => {
if (!this.queue_.length && !Object.getOwnPropertyNames(this.fetchingItems_).length) {
clearInterval(iid);
resolve();
}
}, 100);
});
}
async autoAddResources(limit) {
if (this.addingResources_) return;
this.addingResources_ = true;
let count = 0;
const resources = await Resource.needToBeFetched(limit);
for (let i = 0; i < resources.length; i++) {
const added = this.queueDownload(resources[i].id);
if (added) count++;
}
this.logger().info('ResourceFetcher: Auto-added resources: ' + count);
this.addingResources_ = false;
}
async start() {
await Resource.resetStartedFetchStatus();
this.autoAddResources(10);
}
scheduleQueueProcess() {
if (this.scheduleQueueProcessIID_) {
clearTimeout(this.scheduleQueueProcessIID_);
this.scheduleQueueProcessIID_ = null;
}
this.scheduleQueueProcessIID_ = setTimeout(() => {
this.processQueue_();
this.scheduleQueueProcessIID_ = null;
}, 100);
}
async fetchAll() {
await Resource.resetStartedFetchStatus();
this.autoAddResources(null);
}
}
module.exports = ResourceFetcher;

View File

@@ -265,20 +265,22 @@ class Api {
// size: 164394
if (request.method === 'GET') {
if (link !== 'file') throw new ErrorNotFound();
if (link === 'file') {
const resource = await Resource.load(id);
if (!resource) throw new ErrorNotFound();
const resource = await Resource.load(id);
if (!resource) throw new ErrorNotFound();
const filePath = Resource.fullPath(resource);
const buffer = await shim.fsDriver().readFile(filePath, 'Buffer');
const response = new ApiResponse();
response.type = 'attachment';
response.body = buffer;
response.contentType = resource.mime;
response.attachmentFilename = Resource.friendlyFilename(resource);
return response;
}
const filePath = Resource.fullPath(resource);
const buffer = await shim.fsDriver().readFile(filePath, 'Buffer');
const response = new ApiResponse();
response.type = 'attachment';
response.body = buffer;
response.contentType = resource.mime;
response.attachmentFilename = Resource.friendlyFilename(resource);
return response;
if (link) throw new ErrorNotFound();
}
if (request.method === 'POST') {

View File

@@ -1,7 +1,7 @@
const { shim } = require('lib/shim.js');
const { GeolocationReact } = require('lib/geolocation-react.js');
const { PoorManIntervals } = require('lib/poor-man-intervals.js');
const RNFetchBlob = require('react-native-fetch-blob').default;
const RNFetchBlob = require('rn-fetch-blob').default;
const { generateSecureRandom } = require('react-native-securerandom');
const FsDriverRN = require('lib/fs-driver-rn.js').FsDriverRN;
const urlValidator = require('valid-url');

View File

@@ -7,6 +7,10 @@ shim.isNode = () => {
};
shim.isReactNative = () => {
if (typeof navigator === 'object' && typeof navigator.userAgent === 'string' && navigator.userAgent.indexOf('ReactNativeDebugger') >= 0) {
return true;
}
return !shim.isNode();
};
@@ -14,6 +18,10 @@ shim.isLinux = () => {
return process && process.platform === 'linux';
}
shim.isFreeBSD = () => {
return process && process.platform === 'freebsd';
}
shim.isWindows = () => {
return process && process.platform === 'win32';
}
@@ -27,6 +35,7 @@ shim.platformName = function() {
if (shim.isMac()) return 'darwin';
if (shim.isWindows()) return 'win32';
if (shim.isLinux()) return 'linux';
if (shim.isFreeBSD()) return 'freebsd';
throw new Error('Cannot determine platform');
}
@@ -141,4 +150,4 @@ shim.Buffer = null;
shim.openUrl = () => { throw new Error('Not implemented'); }
shim.waitForFrame = () => { throw new Error('Not implemented'); }
module.exports = { shim };
module.exports = { shim };

View File

@@ -206,4 +206,8 @@ function toTitleCase(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
function urlDecode(string) {
return decodeURIComponent((string+'').replace(/\+/g, '%20'));
}
module.exports = { removeDiacritics, escapeFilename, wrap, splitCommandString, padLeft, toTitleCase };

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