You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-08-30 20:39:46 +02:00
Compare commits
99 Commits
android-v1
...
v1.0.83
Author | SHA1 | Date | |
---|---|---|---|
|
6e9d70c5cb | ||
|
4821b4cdf2 | ||
|
9778098d6c | ||
|
5b1755f988 | ||
|
2a772895dd | ||
|
5fbb01cf2f | ||
|
f9e0870b4e | ||
|
a58f1e9b4b | ||
|
6fc0d89b30 | ||
|
2dcadab7d2 | ||
|
bb3307e156 | ||
|
ecd07f1209 | ||
|
266cb1174f | ||
|
bfb9b77b6e | ||
|
01b1361dcb | ||
|
3a921720d6 | ||
|
cdfd3d9c31 | ||
|
9961fb64bb | ||
|
3137c355cf | ||
|
16abaf60d2 | ||
|
9004b710ea | ||
|
6ebac21c2b | ||
|
99f79faf83 | ||
|
613fa20806 | ||
|
1b5f812278 | ||
|
3a9643c1ea | ||
|
aee7f5a8ac | ||
|
d3cd378922 | ||
|
4f5e7367d0 | ||
|
2280fb5c43 | ||
|
96fb7c2087 | ||
|
6e994fd8b9 | ||
|
a7cde1e269 | ||
|
f8310ba0d5 | ||
|
b239c3faba | ||
|
3c2281dbf9 | ||
|
ac07bf784d | ||
|
067455542f | ||
|
5bfeaa357b | ||
|
fe27a64331 | ||
|
ed638612aa | ||
|
1d7ec83510 | ||
|
75c710232d | ||
|
5af52afadb | ||
|
0f4324c2f8 | ||
|
b48e1dac94 | ||
|
f0ca8e1e31 | ||
|
74b83eb71e | ||
|
28dce0fbb5 | ||
|
c12d402c7e | ||
|
014f5b123c | ||
|
58601dfc04 | ||
|
9fe7f0adae | ||
|
ea1374371f | ||
|
bce4294529 | ||
|
de409b632a | ||
|
a677b2e844 | ||
|
c63bb19cb6 | ||
|
72fd77812e | ||
|
40f3e72bd1 | ||
|
d6d86f2aff | ||
|
c71809438b | ||
|
3e6e1a0a36 | ||
|
f590ce4a34 | ||
|
67608e29c8 | ||
|
d5c2982093 | ||
|
90fad2a3ab | ||
|
bc7c82e3da | ||
|
cb824f7dd7 | ||
|
32c47a96f1 | ||
|
4e3f8893f7 | ||
|
ca3946689a | ||
|
e2ad2dfcaa | ||
|
d6f7893c56 | ||
|
8c65a7cc31 | ||
|
aabb9be7de | ||
|
544f93bf22 | ||
|
f81dbf4a4c | ||
|
fbec8263a3 | ||
|
68d77a69e6 | ||
|
f2ef2446c6 | ||
|
875cb5387a | ||
|
ae9ecdad40 | ||
|
86a0e34975 | ||
|
1141074745 | ||
|
efc46d9989 | ||
|
2b45f745b6 | ||
|
37fb81e9b2 | ||
|
255a4fac93 | ||
|
3e3fb88de8 | ||
|
e4cf03ae46 | ||
|
554a3eb10d | ||
|
61881b528a | ||
|
c2507cbc4e | ||
|
c1bb51c12b | ||
|
945018b698 | ||
|
df7b981e5e | ||
|
c9e130a771 | ||
|
f595be07d4 |
@@ -3,6 +3,14 @@ if: tag IS present
|
||||
|
||||
rvm: 2.3.3
|
||||
|
||||
# It's important to only build production branches otherwise Electron Builder
|
||||
# might take assets from dev branches and overwrite those of production.
|
||||
# https://docs.travis-ci.com/user/customizing-the-build/#Building-Specific-Branches
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^v\d+\.\d+(\.\d+)?(-\S*)?$/
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: osx
|
||||
|
6
BUILD.md
6
BUILD.md
@@ -37,8 +37,6 @@ yarn dist
|
||||
|
||||
If there's an error `while loading shared libraries: libgconf-2.so.4: cannot open shared object file: No such file or directory`, run `sudo apt-get install libgconf-2-4`
|
||||
|
||||
For node-gyp to work, you might need to install the `windows-build-tools` using `npm install --global windows-build-tools`.
|
||||
|
||||
That will create the executable file in the `dist` directory.
|
||||
|
||||
From `/ElectronClient` you can also run `run.sh` to run the app for testing.
|
||||
@@ -54,6 +52,10 @@ npm install
|
||||
yarn dist
|
||||
```
|
||||
|
||||
If node-gyp does not works (MSBUILD: error MSB3428: Could not load the Visual C++ component "VCBuild.exe"), you might need to install the `windows-build-tools` using `npm install --global windows-build-tools`.
|
||||
|
||||
If `yarn dist` fails, it may need administrative rights.
|
||||
|
||||
# Building the Mobile application
|
||||
|
||||
First you need to setup React Native to build projects with native code. For this, follow the instructions on the [Get Started](https://facebook.github.io/react-native/docs/getting-started.html) tutorial, in the "Building Projects with Native Code" tab.
|
||||
|
@@ -10,10 +10,6 @@ Again, please check that it has not already been requested. If it has, simply **
|
||||
|
||||
# Adding new features
|
||||
|
||||
If you want to add a new feature, consider asking about it before implementing it to make sure it is within the scope of the project. Of course you are free to create the pull request directly but it is not guaranteed it is going to be accepted.
|
||||
If you want to add a new feature, consider asking about it before implementing it or checking existing discussions to make sure it is within the scope of the project. Of course you are free to create the pull request directly but it is not guaranteed it is going to be accepted.
|
||||
|
||||
Building the apps is relatively easy - please [see the build instructions](https://github.com/laurent22/joplin/blob/master/BUILD.md) for more details.
|
||||
|
||||
# Coding style
|
||||
|
||||
See the [prettier config](https://github.com/laurent22/joplin/blob/master/prettier.config.js).
|
||||
|
3
CliClient/.gitignore
vendored
3
CliClient/.gitignore
vendored
@@ -18,4 +18,5 @@ tests/cli-integration/
|
||||
tests/sync
|
||||
out.txt
|
||||
linkToLocal.sh
|
||||
yarn-error.log
|
||||
yarn-error.log
|
||||
tests/support/dropbox-auth.txt
|
@@ -5,6 +5,7 @@ const { JoplinDatabase } = require('lib/joplin-database.js');
|
||||
const { Database } = require('lib/database.js');
|
||||
const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
|
||||
const { DatabaseDriverNode } = require('lib/database-driver-node.js');
|
||||
const ResourceService = require('lib/services/ResourceService');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const BaseItem = require('lib/models/BaseItem.js');
|
||||
@@ -412,6 +413,8 @@ class Application extends BaseApplication {
|
||||
|
||||
const tags = await Tag.allWithNotes();
|
||||
|
||||
ResourceService.runInBackground();
|
||||
|
||||
this.dispatch({
|
||||
type: 'TAG_UPDATE_ALL',
|
||||
items: tags,
|
||||
|
@@ -102,7 +102,7 @@ function getFooter() {
|
||||
|
||||
output.push('WEBSITE');
|
||||
output.push('');
|
||||
output.push(INDENT + 'http://joplin.cozic.net');
|
||||
output.push(INDENT + 'https://joplin.cozic.net');
|
||||
|
||||
output.push('');
|
||||
|
||||
|
@@ -37,7 +37,7 @@ class Command extends BaseCommand {
|
||||
const stdoutWidth = app().commandStdoutMaxWidth();
|
||||
|
||||
if (args.command === 'shortcuts' || args.command === 'keymap') {
|
||||
this.stdout(_('For information on how to customise the shortcuts please visit %s', 'http://joplin.cozic.net/terminal/#shortcuts'));
|
||||
this.stdout(_('For information on how to customise the shortcuts please visit %s', 'https://joplin.cozic.net/terminal/#shortcuts'));
|
||||
this.stdout('');
|
||||
|
||||
if (app().gui().isDummy()) {
|
||||
|
@@ -78,10 +78,26 @@ class Command extends BaseCommand {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if (syncTargetMd.name === 'dropbox') { // Dropbox
|
||||
const api = await syncTarget.api();
|
||||
const loginUrl = api.loginUrl();
|
||||
this.stdout(_('To allow Joplin to synchronise with Dropbox, please follow the steps below:'));
|
||||
this.stdout(_('Step 1: Open this URL in your browser to authorise the application:'));
|
||||
this.stdout(loginUrl);
|
||||
const authCode = await this.prompt(_('Step 2: Enter the code provided by Dropbox:'), { type: 'string' });
|
||||
if (!authCode) {
|
||||
this.stdout(_('Authentication was not completed (did not receive an authentication token).'));
|
||||
return false;
|
||||
}
|
||||
|
||||
const response = await api.execAuthToken(authCode);
|
||||
Setting.setValue('sync.' + this.syncTargetId_ + '.auth', response.access_token);
|
||||
api.setAuthToken(response.access_token);
|
||||
return true;
|
||||
}
|
||||
|
||||
this.stdout(_('Not authentified with %s. Please provide any missing credentials.', syncTarget.label()));
|
||||
this.stdout(_('Not authentified with %s. Please provide any missing credentials.', syncTargetMd.label));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -100,6 +116,7 @@ class Command extends BaseCommand {
|
||||
this.releaseLockFn_ = null;
|
||||
|
||||
// Lock is unique per profile/database
|
||||
// TODO: use SQLite database to do lock?
|
||||
const lockFilePath = require('os').tmpdir() + '/synclock_' + md5(escape(Setting.value('profileDir'))); // https://github.com/pvorb/node-md5/issues/41
|
||||
if (!await fs.pathExists(lockFilePath)) await fs.writeFile(lockFilePath, 'synclock');
|
||||
|
||||
@@ -130,7 +147,7 @@ class Command extends BaseCommand {
|
||||
|
||||
const syncTarget = reg.syncTarget(this.syncTargetId_);
|
||||
|
||||
if (!syncTarget.isAuthenticated()) {
|
||||
if (!await syncTarget.isAuthenticated()) {
|
||||
app().gui().showConsole();
|
||||
app().gui().maximizeConsole();
|
||||
|
||||
@@ -197,7 +214,7 @@ class Command extends BaseCommand {
|
||||
|
||||
const syncTarget = reg.syncTarget(syncTargetId);
|
||||
|
||||
if (syncTarget.isAuthenticated()) {
|
||||
if (await syncTarget.isAuthenticated()) {
|
||||
const sync = await syncTarget.synchronizer();
|
||||
if (sync) await sync.cancel();
|
||||
} else {
|
||||
|
1415
CliClient/locales/cs_CZ.po
Normal file
1415
CliClient/locales/cs_CZ.po
Normal file
File diff suppressed because it is too large
Load Diff
1420
CliClient/locales/da_DK.po
Normal file
1420
CliClient/locales/da_DK.po
Normal file
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Joplin-CLI 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"Last-Translator: Tobias Strobel <git@strobeltobias.de>\n"
|
||||
"Last-Translator: Tobias Grasse <mail@tobias-grasse.net>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: de_DE\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -38,7 +38,7 @@ msgid "No notebook selected."
|
||||
msgstr "Kein Notizbuch ausgewählt."
|
||||
|
||||
msgid "No notebook has been specified."
|
||||
msgstr "Kein Notizbuch wurde ausgewählt."
|
||||
msgstr "Es wurde kein Notizbuch festgelegt."
|
||||
|
||||
msgid "Y"
|
||||
msgstr "J"
|
||||
@@ -429,6 +429,16 @@ msgstr ""
|
||||
"Authentifizierung wurde nicht abgeschlossen (keinen Authentifizierung-Token "
|
||||
"erhalten)."
|
||||
|
||||
msgid ""
|
||||
"To allow Joplin to synchronise with Dropbox, please follow the steps below:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Step 1: Open this URL in your browser to authorise the application:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Step 2: Enter the code provided by Dropbox:"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Not authentified with %s. Please provide any missing credentials."
|
||||
msgstr ""
|
||||
@@ -586,9 +596,8 @@ msgstr "Exportiere „%s“ ins „%s“ Format. Bitte warten..."
|
||||
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
|
||||
msgstr "Importiere „%s“ ins „%s“ Format. Bitte warten…"
|
||||
|
||||
#, fuzzy
|
||||
msgid "PDF File"
|
||||
msgstr "Datei"
|
||||
msgstr "PDF-Datei"
|
||||
|
||||
msgid "File"
|
||||
msgstr "Datei"
|
||||
@@ -609,7 +618,7 @@ msgid "Export"
|
||||
msgstr "Exportieren"
|
||||
|
||||
msgid "Print"
|
||||
msgstr ""
|
||||
msgstr "Drucken"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Hide %s"
|
||||
@@ -657,9 +666,8 @@ msgstr "Hilfe"
|
||||
msgid "Website and documentation"
|
||||
msgstr "Webseite und Dokumentation"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Make a donation"
|
||||
msgstr "Webseite und Dokumentation"
|
||||
msgstr "Spenden"
|
||||
|
||||
msgid "Check for updates..."
|
||||
msgstr "Überprüfe auf Updates..."
|
||||
@@ -706,6 +714,9 @@ msgstr "Notizen und Einstellungen werden gespeichert in: %s"
|
||||
msgid "Save"
|
||||
msgstr "Speichern"
|
||||
|
||||
msgid "Submit"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Disabling encryption means *all* your notes and attachments are going to be "
|
||||
"re-synchronised and sent unencrypted to the sync target. Do you wish to "
|
||||
@@ -776,6 +787,11 @@ msgid ""
|
||||
"they will eventually be downloaded via synchronisation."
|
||||
msgstr "Die Master-Keas dieser IDs werden für die Verschlüsselung einiger ..."
|
||||
|
||||
msgid ""
|
||||
"For more information about End-To-End Encryption (E2EE) and advices on how "
|
||||
"to enable it please check the documentation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Status"
|
||||
msgstr "Status"
|
||||
|
||||
@@ -813,12 +829,13 @@ msgstr "Notizbuch umbenennen:"
|
||||
msgid "Set alarm:"
|
||||
msgstr "Alarm erstellen:"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Suchen"
|
||||
|
||||
msgid "Layout"
|
||||
msgstr "Layout"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Search..."
|
||||
msgstr "Suchen"
|
||||
|
||||
msgid "Some items cannot be synchronised."
|
||||
msgstr "Manche Objekte können nicht synchronisiert werden."
|
||||
|
||||
@@ -900,6 +917,9 @@ msgstr "Leeren"
|
||||
msgid "OneDrive Login"
|
||||
msgstr "OneDrive Login"
|
||||
|
||||
msgid "Dropbox Login"
|
||||
msgstr ""
|
||||
|
||||
msgid "Options"
|
||||
msgstr "Optionen"
|
||||
|
||||
@@ -924,9 +944,6 @@ msgstr "Synchronisieren"
|
||||
msgid "Notebooks"
|
||||
msgstr "Notizbücher"
|
||||
|
||||
msgid "Searches"
|
||||
msgstr "Suchen"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr ""
|
||||
"Bitte wähle aus, wohin der Synchronisations Status exportiert werden soll"
|
||||
@@ -939,6 +956,9 @@ msgstr "Nutzung: %s"
|
||||
msgid "Unknown flag: %s"
|
||||
msgstr "Unbekanntes Argument: %s"
|
||||
|
||||
msgid "Dropbox"
|
||||
msgstr ""
|
||||
|
||||
msgid "File system"
|
||||
msgstr "Dateisystem"
|
||||
|
||||
@@ -1016,8 +1036,8 @@ msgstr "Remote Objekte gelöscht: %d."
|
||||
msgid "Fetched items: %d/%d."
|
||||
msgstr "Geladene Objekte: %d/%d."
|
||||
|
||||
#, javascript-format
|
||||
msgid "State: \"%s\"."
|
||||
#, fuzzy, javascript-format
|
||||
msgid "State: %s."
|
||||
msgstr "Status: \"%s\"."
|
||||
|
||||
msgid "Cancelling..."
|
||||
@@ -1027,15 +1047,15 @@ msgstr "Abbrechen..."
|
||||
msgid "Completed: %s"
|
||||
msgstr "Abgeschlossen: %s"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
#, javascript-format
|
||||
msgid "Last error: %s"
|
||||
msgstr "Schwerwiegender Fehler:"
|
||||
msgstr "Letzte Fehlermeldung: %s"
|
||||
|
||||
msgid "Idle"
|
||||
msgstr ""
|
||||
msgstr "wartend"
|
||||
|
||||
msgid "In progress"
|
||||
msgstr ""
|
||||
msgstr "In Bearbeitung"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
@@ -1332,9 +1352,19 @@ msgstr "Bestätigen"
|
||||
msgid "Cancel synchronisation"
|
||||
msgstr "Synchronisation abbrechen"
|
||||
|
||||
msgid "Joplin website"
|
||||
msgid "New tags:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Type new tags or select from list"
|
||||
msgstr ""
|
||||
|
||||
msgid "Joplin website"
|
||||
msgstr "Website von Joplin"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Login with Dropbox"
|
||||
msgstr "Mit OneDrive anmelden"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Master Key %s"
|
||||
msgstr "Hauptschlüssel %s"
|
||||
@@ -1352,6 +1382,11 @@ msgstr "Passwort darf nicht leer sein"
|
||||
msgid "Enable"
|
||||
msgstr "Aktivieren"
|
||||
|
||||
msgid ""
|
||||
"For more information about End-To-End Encryption (E2EE) and advices on how "
|
||||
"to enable it please check the documentation:"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The notebook could not be saved: %s"
|
||||
msgstr "Dieses Notizbuch konnte nicht gespeichert werden: %s"
|
||||
@@ -1405,6 +1440,9 @@ msgstr "Notizbuch löschen"
|
||||
msgid "Login with OneDrive"
|
||||
msgstr "Mit OneDrive anmelden"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Suchen"
|
||||
|
||||
msgid ""
|
||||
"Click on the (+) button to create a new note or notebook. Click on the side "
|
||||
"menu to access your existing notebooks."
|
||||
@@ -1421,6 +1459,9 @@ msgstr ""
|
||||
msgid "Welcome"
|
||||
msgstr "Willkommen"
|
||||
|
||||
#~ msgid "Searches"
|
||||
#~ msgstr "Suchen"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "Release notes:\n"
|
||||
#~ "\n"
|
||||
|
@@ -372,6 +372,16 @@ msgid ""
|
||||
"Authentication was not completed (did not receive an authentication token)."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"To allow Joplin to synchronise with Dropbox, please follow the steps below:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Step 1: Open this URL in your browser to authorise the application:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Step 2: Enter the code provided by Dropbox:"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Not authentified with %s. Please provide any missing credentials."
|
||||
msgstr ""
|
||||
@@ -615,6 +625,9 @@ msgstr ""
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
|
||||
msgid "Submit"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Disabling encryption means *all* your notes and attachments are going to be "
|
||||
"re-synchronised and sent unencrypted to the sync target. Do you wish to "
|
||||
@@ -673,6 +686,11 @@ msgid ""
|
||||
"they will eventually be downloaded via synchronisation."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"For more information about End-To-End Encryption (E2EE) and advices on how "
|
||||
"to enable it please check the documentation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Status"
|
||||
msgstr ""
|
||||
|
||||
@@ -708,10 +726,10 @@ msgstr ""
|
||||
msgid "Set alarm:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Search"
|
||||
msgid "Layout"
|
||||
msgstr ""
|
||||
|
||||
msgid "Layout"
|
||||
msgid "Search..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Some items cannot be synchronised."
|
||||
@@ -789,6 +807,9 @@ msgstr ""
|
||||
msgid "OneDrive Login"
|
||||
msgstr ""
|
||||
|
||||
msgid "Dropbox Login"
|
||||
msgstr ""
|
||||
|
||||
msgid "Options"
|
||||
msgstr ""
|
||||
|
||||
@@ -813,9 +834,6 @@ msgstr ""
|
||||
msgid "Notebooks"
|
||||
msgstr ""
|
||||
|
||||
msgid "Searches"
|
||||
msgstr ""
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr ""
|
||||
|
||||
@@ -827,6 +845,9 @@ msgstr ""
|
||||
msgid "Unknown flag: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Dropbox"
|
||||
msgstr ""
|
||||
|
||||
msgid "File system"
|
||||
msgstr ""
|
||||
|
||||
@@ -897,7 +918,7 @@ msgid "Fetched items: %d/%d."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "State: \"%s\"."
|
||||
msgid "State: %s."
|
||||
msgstr ""
|
||||
|
||||
msgid "Cancelling..."
|
||||
@@ -1193,9 +1214,18 @@ msgstr ""
|
||||
msgid "Cancel synchronisation"
|
||||
msgstr ""
|
||||
|
||||
msgid "New tags:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Type new tags or select from list"
|
||||
msgstr ""
|
||||
|
||||
msgid "Joplin website"
|
||||
msgstr ""
|
||||
|
||||
msgid "Login with Dropbox"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Master Key %s"
|
||||
msgstr ""
|
||||
@@ -1213,6 +1243,11 @@ msgstr ""
|
||||
msgid "Enable"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"For more information about End-To-End Encryption (E2EE) and advices on how "
|
||||
"to enable it please check the documentation:"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The notebook could not be saved: %s"
|
||||
msgstr ""
|
||||
@@ -1266,6 +1301,9 @@ msgstr ""
|
||||
msgid "Login with OneDrive"
|
||||
msgstr ""
|
||||
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Click on the (+) button to create a new note or notebook. Click on the side "
|
||||
"menu to access your existing notebooks."
|
||||
|
@@ -416,6 +416,16 @@ msgid ""
|
||||
"Authentication was not completed (did not receive an authentication token)."
|
||||
msgstr "Autenticación no completada (no se recibió token de autenticación)."
|
||||
|
||||
msgid ""
|
||||
"To allow Joplin to synchronise with Dropbox, please follow the steps below:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Step 1: Open this URL in your browser to authorise the application:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Step 2: Enter the code provided by Dropbox:"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Not authentified with %s. Please provide any missing credentials."
|
||||
msgstr "No autenticado con %s. Por favor provea las credenciales."
|
||||
@@ -689,6 +699,9 @@ msgstr "Las notas y los ajustes se guardan en: %s"
|
||||
msgid "Save"
|
||||
msgstr "Guardar"
|
||||
|
||||
msgid "Submit"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Disabling encryption means *all* your notes and attachments are going to be "
|
||||
"re-synchronised and sent unencrypted to the sync target. Do you wish to "
|
||||
@@ -760,6 +773,11 @@ msgstr ""
|
||||
"elementos, pero la apliación no tiene acceso a ellas. Serán descargadas a "
|
||||
"través de la sincronización."
|
||||
|
||||
msgid ""
|
||||
"For more information about End-To-End Encryption (E2EE) and advices on how "
|
||||
"to enable it please check the documentation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Status"
|
||||
msgstr "Estado"
|
||||
|
||||
@@ -795,12 +813,13 @@ msgstr "Renombrar libreta:"
|
||||
msgid "Set alarm:"
|
||||
msgstr "Ajustar alarma:"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Buscar"
|
||||
|
||||
msgid "Layout"
|
||||
msgstr "Diseño"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Search..."
|
||||
msgstr "Buscar"
|
||||
|
||||
msgid "Some items cannot be synchronised."
|
||||
msgstr "No se han podido sincronizar algunos de los elementos."
|
||||
|
||||
@@ -878,6 +897,9 @@ msgstr "Limpiar"
|
||||
msgid "OneDrive Login"
|
||||
msgstr "Inicio de sesión de OneDrive"
|
||||
|
||||
msgid "Dropbox Login"
|
||||
msgstr ""
|
||||
|
||||
msgid "Options"
|
||||
msgstr "Opciones"
|
||||
|
||||
@@ -902,9 +924,6 @@ msgstr "Sincronizar"
|
||||
msgid "Notebooks"
|
||||
msgstr "Libretas"
|
||||
|
||||
msgid "Searches"
|
||||
msgstr "Búsquedas"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr "Seleccione a dónde se debería exportar el estado de sincronización"
|
||||
|
||||
@@ -916,6 +935,9 @@ msgstr "Uso: %s"
|
||||
msgid "Unknown flag: %s"
|
||||
msgstr "Etiqueta desconocida: %s"
|
||||
|
||||
msgid "Dropbox"
|
||||
msgstr ""
|
||||
|
||||
msgid "File system"
|
||||
msgstr "Sistema de archivos"
|
||||
|
||||
@@ -993,8 +1015,8 @@ msgstr "Elementos remotos borrados: %d."
|
||||
msgid "Fetched items: %d/%d."
|
||||
msgstr "Elementos obtenidos: %d/%d."
|
||||
|
||||
#, javascript-format
|
||||
msgid "State: \"%s\"."
|
||||
#, fuzzy, javascript-format
|
||||
msgid "State: %s."
|
||||
msgstr "Estado: «%s»."
|
||||
|
||||
msgid "Cancelling..."
|
||||
@@ -1305,9 +1327,19 @@ msgstr "Confirmar"
|
||||
msgid "Cancel synchronisation"
|
||||
msgstr "Cancelar sincronización"
|
||||
|
||||
msgid "New tags:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Type new tags or select from list"
|
||||
msgstr ""
|
||||
|
||||
msgid "Joplin website"
|
||||
msgstr "Sitio web de Joplin"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Login with Dropbox"
|
||||
msgstr "Acceder con OneDrive"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Master Key %s"
|
||||
msgstr "Clave maestra %s"
|
||||
@@ -1325,6 +1357,11 @@ msgstr "La contraseña no puede estar vacía"
|
||||
msgid "Enable"
|
||||
msgstr "Habilitado"
|
||||
|
||||
msgid ""
|
||||
"For more information about End-To-End Encryption (E2EE) and advices on how "
|
||||
"to enable it please check the documentation:"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The notebook could not be saved: %s"
|
||||
msgstr "No se ha podido guardar esta libreta: %s"
|
||||
@@ -1378,6 +1415,9 @@ msgstr "Borrar libreta"
|
||||
msgid "Login with OneDrive"
|
||||
msgstr "Acceder con OneDrive"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Buscar"
|
||||
|
||||
msgid ""
|
||||
"Click on the (+) button to create a new note or notebook. Click on the side "
|
||||
"menu to access your existing notebooks."
|
||||
@@ -1392,6 +1432,9 @@ msgstr ""
|
||||
msgid "Welcome"
|
||||
msgstr "Bienvenido"
|
||||
|
||||
#~ msgid "Searches"
|
||||
#~ msgstr "Búsquedas"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "Release notes:\n"
|
||||
#~ "\n"
|
||||
|
@@ -414,6 +414,16 @@ msgid ""
|
||||
"Authentication was not completed (did not receive an authentication token)."
|
||||
msgstr "Autentifikazioa ez da egin osorik (ez du token-ik hartu)."
|
||||
|
||||
msgid ""
|
||||
"To allow Joplin to synchronise with Dropbox, please follow the steps below:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Step 1: Open this URL in your browser to authorise the application:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Step 2: Enter the code provided by Dropbox:"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Not authentified with %s. Please provide any missing credentials."
|
||||
msgstr "Ez da autentifikatu %s -rekin. Eman galdutako kredentzialak."
|
||||
@@ -691,6 +701,9 @@ msgstr "Oharrak eta ezarpenak hemen daude gordeta: %s"
|
||||
msgid "Save"
|
||||
msgstr "Gorde"
|
||||
|
||||
msgid "Submit"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Disabling encryption means *all* your notes and attachments are going to be "
|
||||
"re-synchronised and sent unencrypted to the sync target. Do you wish to "
|
||||
@@ -761,6 +774,11 @@ msgid ""
|
||||
"they will eventually be downloaded via synchronisation."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"For more information about End-To-End Encryption (E2EE) and advices on how "
|
||||
"to enable it please check the documentation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Status"
|
||||
msgstr "Egoera"
|
||||
|
||||
@@ -798,12 +816,13 @@ msgstr "Berrizendatu koadernoa:"
|
||||
msgid "Set alarm:"
|
||||
msgstr "Ezarri alarma:"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Bilatu"
|
||||
|
||||
msgid "Layout"
|
||||
msgstr "Diseinua"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Search..."
|
||||
msgstr "Bilatu"
|
||||
|
||||
msgid "Some items cannot be synchronised."
|
||||
msgstr "Zenbait item ezin dira sinkronizatu."
|
||||
|
||||
@@ -883,6 +902,9 @@ msgstr "Garbitu"
|
||||
msgid "OneDrive Login"
|
||||
msgstr "Logeatu OneDriven"
|
||||
|
||||
msgid "Dropbox Login"
|
||||
msgstr ""
|
||||
|
||||
msgid "Options"
|
||||
msgstr "Aukerak"
|
||||
|
||||
@@ -907,9 +929,6 @@ msgstr "Sinkronizatu"
|
||||
msgid "Notebooks"
|
||||
msgstr "Koadernoak"
|
||||
|
||||
msgid "Searches"
|
||||
msgstr "Bilaketak"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr "Aukeratu nora esportatu sinkronizazioaren egoera, mesedez"
|
||||
@@ -922,6 +941,9 @@ msgstr "Erabili: %s"
|
||||
msgid "Unknown flag: %s"
|
||||
msgstr "Marka ezezaguna: %s"
|
||||
|
||||
msgid "Dropbox"
|
||||
msgstr ""
|
||||
|
||||
msgid "File system"
|
||||
msgstr "Fitxategi sistema"
|
||||
|
||||
@@ -1000,8 +1022,8 @@ msgstr "Urruneko itemak ezabatuta: %d."
|
||||
msgid "Fetched items: %d/%d."
|
||||
msgstr "Itemak eskuratuta: %d%d."
|
||||
|
||||
#, javascript-format
|
||||
msgid "State: \"%s\"."
|
||||
#, fuzzy, javascript-format
|
||||
msgid "State: %s."
|
||||
msgstr "Egoera: \"%s\"."
|
||||
|
||||
msgid "Cancelling..."
|
||||
@@ -1317,9 +1339,19 @@ msgstr "Baieztatu"
|
||||
msgid "Cancel synchronisation"
|
||||
msgstr "Sinkronizazioa utzi"
|
||||
|
||||
msgid "New tags:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Type new tags or select from list"
|
||||
msgstr ""
|
||||
|
||||
msgid "Joplin website"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Login with Dropbox"
|
||||
msgstr "Login with OneDrive"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Master Key %s"
|
||||
msgstr "Pasahitz Nagusia %s"
|
||||
@@ -1337,6 +1369,11 @@ msgstr "Pasahitza ezin utz daiteke hutsik"
|
||||
msgid "Enable"
|
||||
msgstr "Gaituta"
|
||||
|
||||
msgid ""
|
||||
"For more information about End-To-End Encryption (E2EE) and advices on how "
|
||||
"to enable it please check the documentation:"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The notebook could not be saved: %s"
|
||||
msgstr "Koadernoa ezin gorde daiteke: %s"
|
||||
@@ -1390,6 +1427,9 @@ msgstr "Ezabatu koadernoa"
|
||||
msgid "Login with OneDrive"
|
||||
msgstr "Login with OneDrive"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Bilatu"
|
||||
|
||||
msgid ""
|
||||
"Click on the (+) button to create a new note or notebook. Click on the side "
|
||||
"menu to access your existing notebooks."
|
||||
@@ -1403,6 +1443,9 @@ msgstr "Oraindik ez duzu koadernorik. Sortu bat (+) botoian sakatuta."
|
||||
msgid "Welcome"
|
||||
msgstr "Ongi etorri!"
|
||||
|
||||
#~ msgid "Searches"
|
||||
#~ msgstr "Bilaketak"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid ""
|
||||
#~ "Release notes:\n"
|
||||
|
@@ -413,6 +413,20 @@ msgid ""
|
||||
"Authentication was not completed (did not receive an authentication token)."
|
||||
msgstr "Impossible d'autoriser le logiciel (jeton d'identification non-reçu)."
|
||||
|
||||
msgid ""
|
||||
"To allow Joplin to synchronise with Dropbox, please follow the steps below:"
|
||||
msgstr ""
|
||||
"Pour permettre à Joplin de synchroniser avec Dropbox, veuillez suivre les "
|
||||
"étapes ci-dessous :"
|
||||
|
||||
msgid "Step 1: Open this URL in your browser to authorise the application:"
|
||||
msgstr ""
|
||||
"Étape 1: Veuillez ouvrir cette URL dans votre navigateur internet pour "
|
||||
"autoriser le logiciel :"
|
||||
|
||||
msgid "Step 2: Enter the code provided by Dropbox:"
|
||||
msgstr "Étape 2 : Entrez le code fourni par Dropbox :"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Not authentified with %s. Please provide any missing credentials."
|
||||
msgstr ""
|
||||
@@ -687,6 +701,9 @@ msgstr "Les notes et paramètres se trouve dans : %s"
|
||||
msgid "Save"
|
||||
msgstr "Enregistrer"
|
||||
|
||||
msgid "Submit"
|
||||
msgstr "Envoyer"
|
||||
|
||||
msgid ""
|
||||
"Disabling encryption means *all* your notes and attachments are going to be "
|
||||
"re-synchronised and sent unencrypted to the sync target. Do you wish to "
|
||||
@@ -760,6 +777,13 @@ msgstr ""
|
||||
"de vos objets, cependant le logiciel n'y a pour l'instant pas accès. Il est "
|
||||
"probable qu'elle vont être prochainement disponible via la synchronisation."
|
||||
|
||||
msgid ""
|
||||
"For more information about End-To-End Encryption (E2EE) and advices on how "
|
||||
"to enable it please check the documentation"
|
||||
msgstr ""
|
||||
"Pour plus d'informations sur l'encryption de bout en bout, ainsi que des "
|
||||
"conseils pour l'activer, veuillez consulter la documentation"
|
||||
|
||||
msgid "Status"
|
||||
msgstr "État"
|
||||
|
||||
@@ -797,12 +821,12 @@ msgstr "Renommer le carnet :"
|
||||
msgid "Set alarm:"
|
||||
msgstr "Régler alarme :"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Chercher"
|
||||
|
||||
msgid "Layout"
|
||||
msgstr "Disposition"
|
||||
|
||||
msgid "Search..."
|
||||
msgstr "Chercher..."
|
||||
|
||||
msgid "Some items cannot be synchronised."
|
||||
msgstr "Certains objets ne peuvent être synchronisés."
|
||||
|
||||
@@ -883,6 +907,9 @@ msgstr "Supprimer"
|
||||
msgid "OneDrive Login"
|
||||
msgstr "Connexion OneDrive"
|
||||
|
||||
msgid "Dropbox Login"
|
||||
msgstr "Connection à Dropbox"
|
||||
|
||||
msgid "Options"
|
||||
msgstr "Options"
|
||||
|
||||
@@ -907,9 +934,6 @@ msgstr "Synchroniser"
|
||||
msgid "Notebooks"
|
||||
msgstr "Carnets"
|
||||
|
||||
msgid "Searches"
|
||||
msgstr "Recherches"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr ""
|
||||
"Veuillez sélectionner un répertoire ou exporter l'état de la synchronisation"
|
||||
@@ -922,6 +946,9 @@ msgstr "Utilisation : %s"
|
||||
msgid "Unknown flag: %s"
|
||||
msgstr "Paramètre inconnu : %s"
|
||||
|
||||
msgid "Dropbox"
|
||||
msgstr "Dropbox"
|
||||
|
||||
msgid "File system"
|
||||
msgstr "Système de fichier"
|
||||
|
||||
@@ -1000,8 +1027,8 @@ msgid "Fetched items: %d/%d."
|
||||
msgstr "Téléchargés : %d/%d."
|
||||
|
||||
#, javascript-format
|
||||
msgid "State: \"%s\"."
|
||||
msgstr "État : \"%s\"."
|
||||
msgid "State: %s."
|
||||
msgstr "État : %s."
|
||||
|
||||
msgid "Cancelling..."
|
||||
msgstr "Annulation..."
|
||||
@@ -1312,9 +1339,18 @@ msgstr "Confirmer"
|
||||
msgid "Cancel synchronisation"
|
||||
msgstr "Annuler synchronisation"
|
||||
|
||||
msgid "New tags:"
|
||||
msgstr "Nouvelles étiquettes :"
|
||||
|
||||
msgid "Type new tags or select from list"
|
||||
msgstr "Entrez de nouvelles étiquettes ou sélectionnez de la liste"
|
||||
|
||||
msgid "Joplin website"
|
||||
msgstr "Site web de Joplin"
|
||||
|
||||
msgid "Login with Dropbox"
|
||||
msgstr "Se connecter à Dropbox"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Master Key %s"
|
||||
msgstr "Clef maître %s"
|
||||
@@ -1332,6 +1368,13 @@ msgstr "Mot de passe ne peut être vide"
|
||||
msgid "Enable"
|
||||
msgstr "Activer"
|
||||
|
||||
msgid ""
|
||||
"For more information about End-To-End Encryption (E2EE) and advices on how "
|
||||
"to enable it please check the documentation:"
|
||||
msgstr ""
|
||||
"Pour plus d'informations sur l'encryption de bout en bout, ainsi que des "
|
||||
"conseils pour l'activer, veuillez consulter la documentation :"
|
||||
|
||||
#, javascript-format
|
||||
msgid "The notebook could not be saved: %s"
|
||||
msgstr "Ce carnet n'a pas pu être sauvegardé : %s"
|
||||
@@ -1385,6 +1428,9 @@ msgstr "Supprimer le carnet"
|
||||
msgid "Login with OneDrive"
|
||||
msgstr "Se connecter à OneDrive"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Chercher"
|
||||
|
||||
msgid ""
|
||||
"Click on the (+) button to create a new note or notebook. Click on the side "
|
||||
"menu to access your existing notebooks."
|
||||
@@ -1400,6 +1446,9 @@ msgstr ""
|
||||
msgid "Welcome"
|
||||
msgstr "Bienvenue"
|
||||
|
||||
#~ msgid "Searches"
|
||||
#~ msgstr "Recherches"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "Release notes:\n"
|
||||
#~ "\n"
|
||||
@@ -1587,12 +1636,6 @@ msgstr "Bienvenue"
|
||||
#~ msgid "Done."
|
||||
#~ msgstr "Terminé."
|
||||
|
||||
#~ msgid ""
|
||||
#~ "Please open this URL in your browser to authenticate the application:"
|
||||
#~ msgstr ""
|
||||
#~ "Veuillez ouvrir cette URL dans votre navigateur internet pour autoriser "
|
||||
#~ "le logiciel :"
|
||||
|
||||
#~ msgid "Note does not exist."
|
||||
#~ msgstr "Cette note n'existe pas."
|
||||
|
||||
|
1437
CliClient/locales/gl_ES.po
Normal file
1437
CliClient/locales/gl_ES.po
Normal file
File diff suppressed because it is too large
Load Diff
@@ -420,6 +420,16 @@ msgid ""
|
||||
msgstr ""
|
||||
"Ovjera nije dovršena (nije dobivena potvrda ovjere - authentication token)."
|
||||
|
||||
msgid ""
|
||||
"To allow Joplin to synchronise with Dropbox, please follow the steps below:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Step 1: Open this URL in your browser to authorise the application:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Step 2: Enter the code provided by Dropbox:"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Not authentified with %s. Please provide any missing credentials."
|
||||
msgstr ""
|
||||
@@ -694,6 +704,9 @@ msgstr "Bilješke i postavke su pohranjene u: %s"
|
||||
msgid "Save"
|
||||
msgstr "Spremi"
|
||||
|
||||
msgid "Submit"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Disabling encryption means *all* your notes and attachments are going to be "
|
||||
"re-synchronised and sent unencrypted to the sync target. Do you wish to "
|
||||
@@ -752,6 +765,11 @@ msgid ""
|
||||
"they will eventually be downloaded via synchronisation."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"For more information about End-To-End Encryption (E2EE) and advices on how "
|
||||
"to enable it please check the documentation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Status"
|
||||
msgstr "Status"
|
||||
|
||||
@@ -789,12 +807,13 @@ msgstr "Preimenuj bilježnicu:"
|
||||
msgid "Set alarm:"
|
||||
msgstr "Postavi upozorenje:"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Traži"
|
||||
|
||||
msgid "Layout"
|
||||
msgstr "Izgled"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Search..."
|
||||
msgstr "Traži"
|
||||
|
||||
msgid "Some items cannot be synchronised."
|
||||
msgstr "Neke stavke se ne mogu sinkronizirati."
|
||||
|
||||
@@ -874,6 +893,9 @@ msgstr "Očisti"
|
||||
msgid "OneDrive Login"
|
||||
msgstr "OneDrive Login"
|
||||
|
||||
msgid "Dropbox Login"
|
||||
msgstr ""
|
||||
|
||||
msgid "Options"
|
||||
msgstr "Opcije"
|
||||
|
||||
@@ -898,9 +920,6 @@ msgstr "Sinkroniziraj"
|
||||
msgid "Notebooks"
|
||||
msgstr "Bilježnice"
|
||||
|
||||
msgid "Searches"
|
||||
msgstr "Pretraživanja"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr "Odaberi lokaciju za izvoz statusa sinkronizacije"
|
||||
|
||||
@@ -912,6 +931,9 @@ msgstr "Korištenje: %s"
|
||||
msgid "Unknown flag: %s"
|
||||
msgstr "Nepoznata zastavica: %s"
|
||||
|
||||
msgid "Dropbox"
|
||||
msgstr ""
|
||||
|
||||
msgid "File system"
|
||||
msgstr "Datotečni sustav"
|
||||
|
||||
@@ -987,8 +1009,8 @@ msgstr "Obrisane udaljene stavke: %d."
|
||||
msgid "Fetched items: %d/%d."
|
||||
msgstr "Stvorene lokalne stavke: %d."
|
||||
|
||||
#, javascript-format
|
||||
msgid "State: \"%s\"."
|
||||
#, fuzzy, javascript-format
|
||||
msgid "State: %s."
|
||||
msgstr "Stanje: \"%s\"."
|
||||
|
||||
msgid "Cancelling..."
|
||||
@@ -1297,9 +1319,19 @@ msgstr "Potvrdi"
|
||||
msgid "Cancel synchronisation"
|
||||
msgstr "Prekini sinkronizaciju"
|
||||
|
||||
msgid "New tags:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Type new tags or select from list"
|
||||
msgstr ""
|
||||
|
||||
msgid "Joplin website"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Login with Dropbox"
|
||||
msgstr "Prijavi se u OneDrive"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Master Key %s"
|
||||
msgstr ""
|
||||
@@ -1318,6 +1350,11 @@ msgstr ""
|
||||
msgid "Enable"
|
||||
msgstr "Onemogućeno"
|
||||
|
||||
msgid ""
|
||||
"For more information about End-To-End Encryption (E2EE) and advices on how "
|
||||
"to enable it please check the documentation:"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The notebook could not be saved: %s"
|
||||
msgstr "Bilježnicu nije moguće snimiti: %s"
|
||||
@@ -1371,6 +1408,9 @@ msgstr "Obriši bilježnicu"
|
||||
msgid "Login with OneDrive"
|
||||
msgstr "Prijavi se u OneDrive"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Traži"
|
||||
|
||||
msgid ""
|
||||
"Click on the (+) button to create a new note or notebook. Click on the side "
|
||||
"menu to access your existing notebooks."
|
||||
@@ -1384,6 +1424,9 @@ msgstr "Trenutno nemaš nijednu bilježnicu. Stvori novu klikom na (+) gumb."
|
||||
msgid "Welcome"
|
||||
msgstr "Dobro došli"
|
||||
|
||||
#~ msgid "Searches"
|
||||
#~ msgstr "Pretraživanja"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid ""
|
||||
#~ "Release notes:\n"
|
||||
|
@@ -407,6 +407,16 @@ msgstr ""
|
||||
"Autenticazione non completata (non è stato ricevuto alcun token di "
|
||||
"autenticazione)."
|
||||
|
||||
msgid ""
|
||||
"To allow Joplin to synchronise with Dropbox, please follow the steps below:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Step 1: Open this URL in your browser to authorise the application:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Step 2: Enter the code provided by Dropbox:"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Not authentified with %s. Please provide any missing credentials."
|
||||
msgstr ""
|
||||
@@ -676,6 +686,9 @@ msgstr ""
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
|
||||
msgid "Submit"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Disabling encryption means *all* your notes and attachments are going to be "
|
||||
"re-synchronised and sent unencrypted to the sync target. Do you wish to "
|
||||
@@ -736,6 +749,11 @@ msgid ""
|
||||
"they will eventually be downloaded via synchronisation."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"For more information about End-To-End Encryption (E2EE) and advices on how "
|
||||
"to enable it please check the documentation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Status"
|
||||
msgstr "Stato"
|
||||
|
||||
@@ -771,12 +789,13 @@ msgstr "Rinomina il blocco note:"
|
||||
msgid "Set alarm:"
|
||||
msgstr "Imposta allarme:"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Cerca"
|
||||
|
||||
msgid "Layout"
|
||||
msgstr "Disposizione"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Search..."
|
||||
msgstr "Cerca"
|
||||
|
||||
msgid "Some items cannot be synchronised."
|
||||
msgstr "Alcuni elementi non possono essere sincronizzati."
|
||||
|
||||
@@ -857,6 +876,9 @@ msgstr "Pulisci"
|
||||
msgid "OneDrive Login"
|
||||
msgstr "Login OneDrive"
|
||||
|
||||
msgid "Dropbox Login"
|
||||
msgstr ""
|
||||
|
||||
msgid "Options"
|
||||
msgstr "Opzioni"
|
||||
|
||||
@@ -881,9 +903,6 @@ msgstr "Sincronizza"
|
||||
msgid "Notebooks"
|
||||
msgstr "Blocchi note"
|
||||
|
||||
msgid "Searches"
|
||||
msgstr "Ricerche"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr "Per favore seleziona la nota o il blocco note da eliminare."
|
||||
@@ -896,6 +915,9 @@ msgstr "Uso: %s"
|
||||
msgid "Unknown flag: %s"
|
||||
msgstr "Etichetta sconosciuta: %s"
|
||||
|
||||
msgid "Dropbox"
|
||||
msgstr ""
|
||||
|
||||
msgid "File system"
|
||||
msgstr "File system"
|
||||
|
||||
@@ -973,8 +995,8 @@ msgstr "Elementi remoti eliminati: %d."
|
||||
msgid "Fetched items: %d/%d."
|
||||
msgstr "Elementi locali creati: %d."
|
||||
|
||||
#, javascript-format
|
||||
msgid "State: \"%s\"."
|
||||
#, fuzzy, javascript-format
|
||||
msgid "State: %s."
|
||||
msgstr "Stato: \"%s\"."
|
||||
|
||||
msgid "Cancelling..."
|
||||
@@ -1283,9 +1305,19 @@ msgstr "Conferma"
|
||||
msgid "Cancel synchronisation"
|
||||
msgstr "Cancella la sincronizzazione"
|
||||
|
||||
msgid "New tags:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Type new tags or select from list"
|
||||
msgstr ""
|
||||
|
||||
msgid "Joplin website"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Login with Dropbox"
|
||||
msgstr "Accedi a OneDrive"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Master Key %s"
|
||||
msgstr ""
|
||||
@@ -1304,6 +1336,11 @@ msgstr ""
|
||||
msgid "Enable"
|
||||
msgstr "Disabilitato"
|
||||
|
||||
msgid ""
|
||||
"For more information about End-To-End Encryption (E2EE) and advices on how "
|
||||
"to enable it please check the documentation:"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The notebook could not be saved: %s"
|
||||
msgstr "Il blocco note non può essere salvato: %s"
|
||||
@@ -1357,6 +1394,9 @@ msgstr "Cancella blocco note"
|
||||
msgid "Login with OneDrive"
|
||||
msgstr "Accedi a OneDrive"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Cerca"
|
||||
|
||||
msgid ""
|
||||
"Click on the (+) button to create a new note or notebook. Click on the side "
|
||||
"menu to access your existing notebooks."
|
||||
@@ -1372,6 +1412,9 @@ msgstr ""
|
||||
msgid "Welcome"
|
||||
msgstr "Benvenuto"
|
||||
|
||||
#~ msgid "Searches"
|
||||
#~ msgstr "Ricerche"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid ""
|
||||
#~ "Release notes:\n"
|
||||
|
@@ -404,6 +404,16 @@ msgid ""
|
||||
"Authentication was not completed (did not receive an authentication token)."
|
||||
msgstr "認証は完了していません(認証トークンが得られませんでした)"
|
||||
|
||||
msgid ""
|
||||
"To allow Joplin to synchronise with Dropbox, please follow the steps below:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Step 1: Open this URL in your browser to authorise the application:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Step 2: Enter the code provided by Dropbox:"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Not authentified with %s. Please provide any missing credentials."
|
||||
msgstr ""
|
||||
@@ -676,6 +686,9 @@ msgstr "ノートと設定は、%sに保存されます。"
|
||||
msgid "Save"
|
||||
msgstr "保存"
|
||||
|
||||
msgid "Submit"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Disabling encryption means *all* your notes and attachments are going to be "
|
||||
"re-synchronised and sent unencrypted to the sync target. Do you wish to "
|
||||
@@ -738,6 +751,11 @@ msgid ""
|
||||
"they will eventually be downloaded via synchronisation."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"For more information about End-To-End Encryption (E2EE) and advices on how "
|
||||
"to enable it please check the documentation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Status"
|
||||
msgstr "状態"
|
||||
|
||||
@@ -775,12 +793,13 @@ msgstr "ノートブックの名前を変更:"
|
||||
msgid "Set alarm:"
|
||||
msgstr "アラームをセット:"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "検索"
|
||||
|
||||
msgid "Layout"
|
||||
msgstr "レイアウト"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Search..."
|
||||
msgstr "検索"
|
||||
|
||||
msgid "Some items cannot be synchronised."
|
||||
msgstr "いくつかの項目は同期されませんでした。"
|
||||
|
||||
@@ -860,6 +879,9 @@ msgstr "クリア"
|
||||
msgid "OneDrive Login"
|
||||
msgstr "OneDriveログイン"
|
||||
|
||||
msgid "Dropbox Login"
|
||||
msgstr ""
|
||||
|
||||
msgid "Options"
|
||||
msgstr "オプション"
|
||||
|
||||
@@ -884,9 +906,6 @@ msgstr "同期"
|
||||
msgid "Notebooks"
|
||||
msgstr "ノートブック"
|
||||
|
||||
msgid "Searches"
|
||||
msgstr "検索"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr "同期状況の出力先を選択してください"
|
||||
|
||||
@@ -898,6 +917,9 @@ msgstr "使用方法: %s"
|
||||
msgid "Unknown flag: %s"
|
||||
msgstr "不明なフラグ: %s"
|
||||
|
||||
msgid "Dropbox"
|
||||
msgstr ""
|
||||
|
||||
msgid "File system"
|
||||
msgstr "ファイルシステム"
|
||||
|
||||
@@ -975,8 +997,8 @@ msgstr "リモートアイテムの削除: %d."
|
||||
msgid "Fetched items: %d/%d."
|
||||
msgstr "ローカルアイテムの作成: %d."
|
||||
|
||||
#, javascript-format
|
||||
msgid "State: \"%s\"."
|
||||
#, fuzzy, javascript-format
|
||||
msgid "State: %s."
|
||||
msgstr "状態: \"%s\"。"
|
||||
|
||||
msgid "Cancelling..."
|
||||
@@ -1287,9 +1309,19 @@ msgstr "確認"
|
||||
msgid "Cancel synchronisation"
|
||||
msgstr "同期の中止"
|
||||
|
||||
msgid "New tags:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Type new tags or select from list"
|
||||
msgstr ""
|
||||
|
||||
msgid "Joplin website"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Login with Dropbox"
|
||||
msgstr "OneDriveログイン"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Master Key %s"
|
||||
msgstr ""
|
||||
@@ -1308,6 +1340,11 @@ msgstr ""
|
||||
msgid "Enable"
|
||||
msgstr "無効"
|
||||
|
||||
msgid ""
|
||||
"For more information about End-To-End Encryption (E2EE) and advices on how "
|
||||
"to enable it please check the documentation:"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The notebook could not be saved: %s"
|
||||
msgstr "ノートブックは保存できませんでした:%s"
|
||||
@@ -1361,6 +1398,9 @@ msgstr "ノートブックを削除"
|
||||
msgid "Login with OneDrive"
|
||||
msgstr "OneDriveログイン"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "検索"
|
||||
|
||||
msgid ""
|
||||
"Click on the (+) button to create a new note or notebook. Click on the side "
|
||||
"menu to access your existing notebooks."
|
||||
@@ -1376,6 +1416,9 @@ msgstr ""
|
||||
msgid "Welcome"
|
||||
msgstr "ようこそ"
|
||||
|
||||
#~ msgid "Searches"
|
||||
#~ msgstr "検索"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid ""
|
||||
#~ "Release notes:\n"
|
||||
|
@@ -372,6 +372,16 @@ msgid ""
|
||||
"Authentication was not completed (did not receive an authentication token)."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"To allow Joplin to synchronise with Dropbox, please follow the steps below:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Step 1: Open this URL in your browser to authorise the application:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Step 2: Enter the code provided by Dropbox:"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Not authentified with %s. Please provide any missing credentials."
|
||||
msgstr ""
|
||||
@@ -615,6 +625,9 @@ msgstr ""
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
|
||||
msgid "Submit"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Disabling encryption means *all* your notes and attachments are going to be "
|
||||
"re-synchronised and sent unencrypted to the sync target. Do you wish to "
|
||||
@@ -673,6 +686,11 @@ msgid ""
|
||||
"they will eventually be downloaded via synchronisation."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"For more information about End-To-End Encryption (E2EE) and advices on how "
|
||||
"to enable it please check the documentation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Status"
|
||||
msgstr ""
|
||||
|
||||
@@ -708,10 +726,10 @@ msgstr ""
|
||||
msgid "Set alarm:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Search"
|
||||
msgid "Layout"
|
||||
msgstr ""
|
||||
|
||||
msgid "Layout"
|
||||
msgid "Search..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Some items cannot be synchronised."
|
||||
@@ -789,6 +807,9 @@ msgstr ""
|
||||
msgid "OneDrive Login"
|
||||
msgstr ""
|
||||
|
||||
msgid "Dropbox Login"
|
||||
msgstr ""
|
||||
|
||||
msgid "Options"
|
||||
msgstr ""
|
||||
|
||||
@@ -813,9 +834,6 @@ msgstr ""
|
||||
msgid "Notebooks"
|
||||
msgstr ""
|
||||
|
||||
msgid "Searches"
|
||||
msgstr ""
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr ""
|
||||
|
||||
@@ -827,6 +845,9 @@ msgstr ""
|
||||
msgid "Unknown flag: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Dropbox"
|
||||
msgstr ""
|
||||
|
||||
msgid "File system"
|
||||
msgstr ""
|
||||
|
||||
@@ -897,7 +918,7 @@ msgid "Fetched items: %d/%d."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "State: \"%s\"."
|
||||
msgid "State: %s."
|
||||
msgstr ""
|
||||
|
||||
msgid "Cancelling..."
|
||||
@@ -1193,9 +1214,18 @@ msgstr ""
|
||||
msgid "Cancel synchronisation"
|
||||
msgstr ""
|
||||
|
||||
msgid "New tags:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Type new tags or select from list"
|
||||
msgstr ""
|
||||
|
||||
msgid "Joplin website"
|
||||
msgstr ""
|
||||
|
||||
msgid "Login with Dropbox"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Master Key %s"
|
||||
msgstr ""
|
||||
@@ -1213,6 +1243,11 @@ msgstr ""
|
||||
msgid "Enable"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"For more information about End-To-End Encryption (E2EE) and advices on how "
|
||||
"to enable it please check the documentation:"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The notebook could not be saved: %s"
|
||||
msgstr ""
|
||||
@@ -1266,6 +1301,9 @@ msgstr ""
|
||||
msgid "Login with OneDrive"
|
||||
msgstr ""
|
||||
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Click on the (+) button to create a new note or notebook. Click on the side "
|
||||
"menu to access your existing notebooks."
|
||||
|
@@ -415,6 +415,16 @@ msgid ""
|
||||
"Authentication was not completed (did not receive an authentication token)."
|
||||
msgstr "Authenticatie was niet voltooid (geen authenticatietoken ontvangen)."
|
||||
|
||||
msgid ""
|
||||
"To allow Joplin to synchronise with Dropbox, please follow the steps below:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Step 1: Open this URL in your browser to authorise the application:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Step 2: Enter the code provided by Dropbox:"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Not authentified with %s. Please provide any missing credentials."
|
||||
msgstr ""
|
||||
@@ -693,6 +703,9 @@ msgstr "Notities en instellingen zijn opgeslaan in %s"
|
||||
msgid "Save"
|
||||
msgstr "Sla op"
|
||||
|
||||
msgid "Submit"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Disabling encryption means *all* your notes and attachments are going to be "
|
||||
"re-synchronised and sent unencrypted to the sync target. Do you wish to "
|
||||
@@ -763,6 +776,11 @@ msgid ""
|
||||
"they will eventually be downloaded via synchronisation."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"For more information about End-To-End Encryption (E2EE) and advices on how "
|
||||
"to enable it please check the documentation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Status"
|
||||
msgstr "Status"
|
||||
|
||||
@@ -800,12 +818,13 @@ msgstr "Hernoem notitieboek:"
|
||||
msgid "Set alarm:"
|
||||
msgstr "Stel melding in:"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Zoeken"
|
||||
|
||||
msgid "Layout"
|
||||
msgstr "Layout"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Search..."
|
||||
msgstr "Zoeken"
|
||||
|
||||
msgid "Some items cannot be synchronised."
|
||||
msgstr "Sommige items kunnen niet gesynchroniseerd worden."
|
||||
|
||||
@@ -886,6 +905,9 @@ msgstr "Vrijmaken"
|
||||
msgid "OneDrive Login"
|
||||
msgstr "OneDrive Login"
|
||||
|
||||
msgid "Dropbox Login"
|
||||
msgstr ""
|
||||
|
||||
msgid "Options"
|
||||
msgstr "Opties"
|
||||
|
||||
@@ -910,9 +932,6 @@ msgstr "Synchroniseer"
|
||||
msgid "Notebooks"
|
||||
msgstr "Notitieboeken"
|
||||
|
||||
msgid "Searches"
|
||||
msgstr "Zoekopdrachten"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr "Selecteer waar de synchronisatie status naar geëxporteerd moet worden"
|
||||
|
||||
@@ -924,6 +943,9 @@ msgstr "Gebruik: %s"
|
||||
msgid "Unknown flag: %s"
|
||||
msgstr "Onbekende optie: %s"
|
||||
|
||||
msgid "Dropbox"
|
||||
msgstr ""
|
||||
|
||||
msgid "File system"
|
||||
msgstr "Bestandssysteem"
|
||||
|
||||
@@ -1002,8 +1024,8 @@ msgstr "Verwijderde remote items: %d."
|
||||
msgid "Fetched items: %d/%d."
|
||||
msgstr "Opgehaalde items: %d/%d."
|
||||
|
||||
#, javascript-format
|
||||
msgid "State: \"%s\"."
|
||||
#, fuzzy, javascript-format
|
||||
msgid "State: %s."
|
||||
msgstr "Status: \"%s\""
|
||||
|
||||
msgid "Cancelling..."
|
||||
@@ -1319,9 +1341,19 @@ msgstr "Bevestig"
|
||||
msgid "Cancel synchronisation"
|
||||
msgstr "Annuleer synchronisatie"
|
||||
|
||||
msgid "New tags:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Type new tags or select from list"
|
||||
msgstr ""
|
||||
|
||||
msgid "Joplin website"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Login with Dropbox"
|
||||
msgstr "Log in met OneDrive"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Master Key %s"
|
||||
msgstr "Hoofdsleutel: %s"
|
||||
@@ -1339,6 +1371,11 @@ msgstr "Wachtwoord kan niet leeg zijn"
|
||||
msgid "Enable"
|
||||
msgstr "Activeer"
|
||||
|
||||
msgid ""
|
||||
"For more information about End-To-End Encryption (E2EE) and advices on how "
|
||||
"to enable it please check the documentation:"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The notebook could not be saved: %s"
|
||||
msgstr "Het notitieboek kon niet opgeslaan worden: %s"
|
||||
@@ -1392,6 +1429,9 @@ msgstr "Verwijder notitieboek"
|
||||
msgid "Login with OneDrive"
|
||||
msgstr "Log in met OneDrive"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Zoeken"
|
||||
|
||||
msgid ""
|
||||
"Click on the (+) button to create a new note or notebook. Click on the side "
|
||||
"menu to access your existing notebooks."
|
||||
@@ -1407,6 +1447,9 @@ msgstr ""
|
||||
msgid "Welcome"
|
||||
msgstr "Welkom"
|
||||
|
||||
#~ msgid "Searches"
|
||||
#~ msgstr "Zoekopdrachten"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid ""
|
||||
#~ "Release notes:\n"
|
||||
|
@@ -411,6 +411,16 @@ msgid ""
|
||||
msgstr ""
|
||||
"A autenticação não foi concluída (não recebeu um token de autenticação)."
|
||||
|
||||
msgid ""
|
||||
"To allow Joplin to synchronise with Dropbox, please follow the steps below:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Step 1: Open this URL in your browser to authorise the application:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Step 2: Enter the code provided by Dropbox:"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Not authentified with %s. Please provide any missing credentials."
|
||||
msgstr ""
|
||||
@@ -686,6 +696,9 @@ msgstr "Notas e configurações estão armazenadas em: %s"
|
||||
msgid "Save"
|
||||
msgstr "Salvar"
|
||||
|
||||
msgid "Submit"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Disabling encryption means *all* your notes and attachments are going to be "
|
||||
"re-synchronised and sent unencrypted to the sync target. Do you wish to "
|
||||
@@ -757,6 +770,11 @@ msgstr ""
|
||||
"itens, contudo a aplicação atualmente não tem acesso a elas. Provavelmente, "
|
||||
"elas serão baixadas via sincronização."
|
||||
|
||||
msgid ""
|
||||
"For more information about End-To-End Encryption (E2EE) and advices on how "
|
||||
"to enable it please check the documentation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Status"
|
||||
msgstr "Status"
|
||||
|
||||
@@ -793,12 +811,13 @@ msgstr "Renomear caderno:"
|
||||
msgid "Set alarm:"
|
||||
msgstr "Definir alarme:"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Procurar"
|
||||
|
||||
msgid "Layout"
|
||||
msgstr "Layout"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Search..."
|
||||
msgstr "Procurar"
|
||||
|
||||
msgid "Some items cannot be synchronised."
|
||||
msgstr "Alguns itens não podem ser sincronizados."
|
||||
|
||||
@@ -876,6 +895,9 @@ msgstr "Limpar (clear)"
|
||||
msgid "OneDrive Login"
|
||||
msgstr "Login no OneDrive"
|
||||
|
||||
msgid "Dropbox Login"
|
||||
msgstr ""
|
||||
|
||||
msgid "Options"
|
||||
msgstr "Opções"
|
||||
|
||||
@@ -900,9 +922,6 @@ msgstr "Sincronizar"
|
||||
msgid "Notebooks"
|
||||
msgstr "Cadernos"
|
||||
|
||||
msgid "Searches"
|
||||
msgstr "Pesquisas"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr ""
|
||||
"Favor selecionar o local para onde o status de sincronia deveria ser "
|
||||
@@ -916,6 +935,9 @@ msgstr "Uso: %s"
|
||||
msgid "Unknown flag: %s"
|
||||
msgstr "Flag desconhecido: %s"
|
||||
|
||||
msgid "Dropbox"
|
||||
msgstr ""
|
||||
|
||||
msgid "File system"
|
||||
msgstr "Sistema de arquivos"
|
||||
|
||||
@@ -993,8 +1015,8 @@ msgstr "Itens remotos excluídos: %d."
|
||||
msgid "Fetched items: %d/%d."
|
||||
msgstr "Itens pesquisados: %d/%d."
|
||||
|
||||
#, javascript-format
|
||||
msgid "State: \"%s\"."
|
||||
#, fuzzy, javascript-format
|
||||
msgid "State: %s."
|
||||
msgstr "Estado: \"%s\"."
|
||||
|
||||
msgid "Cancelling..."
|
||||
@@ -1305,9 +1327,19 @@ msgstr "Confirmar"
|
||||
msgid "Cancel synchronisation"
|
||||
msgstr "Cancelar sincronização"
|
||||
|
||||
msgid "New tags:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Type new tags or select from list"
|
||||
msgstr ""
|
||||
|
||||
msgid "Joplin website"
|
||||
msgstr "Site do Joplin"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Login with Dropbox"
|
||||
msgstr "Login com OneDrive"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Master Key %s"
|
||||
msgstr "Chave Master %s"
|
||||
@@ -1325,6 +1357,11 @@ msgstr "Senha não pode ser vazia"
|
||||
msgid "Enable"
|
||||
msgstr "Habilitar"
|
||||
|
||||
msgid ""
|
||||
"For more information about End-To-End Encryption (E2EE) and advices on how "
|
||||
"to enable it please check the documentation:"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The notebook could not be saved: %s"
|
||||
msgstr "O caderno não pôde ser salvo: %s"
|
||||
@@ -1378,6 +1415,9 @@ msgstr "Excluir caderno"
|
||||
msgid "Login with OneDrive"
|
||||
msgstr "Login com OneDrive"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Procurar"
|
||||
|
||||
msgid ""
|
||||
"Click on the (+) button to create a new note or notebook. Click on the side "
|
||||
"menu to access your existing notebooks."
|
||||
@@ -1391,6 +1431,9 @@ msgstr "Você não possui cadernos. Crie um clicando no botão (+)."
|
||||
msgid "Welcome"
|
||||
msgstr "Bem-vindo"
|
||||
|
||||
#~ msgid "Searches"
|
||||
#~ msgstr "Pesquisas"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "Release notes:\n"
|
||||
#~ "\n"
|
||||
|
@@ -414,6 +414,16 @@ msgid ""
|
||||
"Authentication was not completed (did not receive an authentication token)."
|
||||
msgstr "Аутентификация не была завершена (не получен токен аутентификации)."
|
||||
|
||||
msgid ""
|
||||
"To allow Joplin to synchronise with Dropbox, please follow the steps below:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Step 1: Open this URL in your browser to authorise the application:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Step 2: Enter the code provided by Dropbox:"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Not authentified with %s. Please provide any missing credentials."
|
||||
msgstr ""
|
||||
@@ -687,6 +697,9 @@ msgstr "Заметки и настройки сохранены в: %s"
|
||||
msgid "Save"
|
||||
msgstr "Сохранить"
|
||||
|
||||
msgid "Submit"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Disabling encryption means *all* your notes and attachments are going to be "
|
||||
"re-synchronised and sent unencrypted to the sync target. Do you wish to "
|
||||
@@ -760,6 +773,11 @@ msgstr ""
|
||||
"элементов, однако у приложения сейчас нет к ним доступа. Скорее всего, они "
|
||||
"загрузятся при синхронизации."
|
||||
|
||||
msgid ""
|
||||
"For more information about End-To-End Encryption (E2EE) and advices on how "
|
||||
"to enable it please check the documentation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Status"
|
||||
msgstr "Статус"
|
||||
|
||||
@@ -795,12 +813,13 @@ msgstr "Переименовать блокнот:"
|
||||
msgid "Set alarm:"
|
||||
msgstr "Установить напоминание:"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Поиск"
|
||||
|
||||
msgid "Layout"
|
||||
msgstr "Вид"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Search..."
|
||||
msgstr "Поиск"
|
||||
|
||||
msgid "Some items cannot be synchronised."
|
||||
msgstr "Некоторые элементы не могут быть синхронизированы."
|
||||
|
||||
@@ -878,6 +897,9 @@ msgstr "Очистить"
|
||||
msgid "OneDrive Login"
|
||||
msgstr "Вход в OneDrive"
|
||||
|
||||
msgid "Dropbox Login"
|
||||
msgstr ""
|
||||
|
||||
msgid "Options"
|
||||
msgstr "Настройки"
|
||||
|
||||
@@ -902,9 +924,6 @@ msgstr "Синхронизировать"
|
||||
msgid "Notebooks"
|
||||
msgstr "Блокноты"
|
||||
|
||||
msgid "Searches"
|
||||
msgstr "Запросы"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr "Выберите, куда должен быть экспортирован статус синхронизации"
|
||||
|
||||
@@ -916,6 +935,9 @@ msgstr "Использование: %s"
|
||||
msgid "Unknown flag: %s"
|
||||
msgstr "Неизвестный флаг: %s"
|
||||
|
||||
msgid "Dropbox"
|
||||
msgstr ""
|
||||
|
||||
msgid "File system"
|
||||
msgstr "Файловая система"
|
||||
|
||||
@@ -993,8 +1015,8 @@ msgstr "Удалено удалённых элементов: %d."
|
||||
msgid "Fetched items: %d/%d."
|
||||
msgstr "Получено элементов: %d/%d."
|
||||
|
||||
#, javascript-format
|
||||
msgid "State: \"%s\"."
|
||||
#, fuzzy, javascript-format
|
||||
msgid "State: %s."
|
||||
msgstr "Статус: «%s»."
|
||||
|
||||
msgid "Cancelling..."
|
||||
@@ -1304,9 +1326,19 @@ msgstr "Подтвердить"
|
||||
msgid "Cancel synchronisation"
|
||||
msgstr "Отменить синхронизацию"
|
||||
|
||||
msgid "New tags:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Type new tags or select from list"
|
||||
msgstr ""
|
||||
|
||||
msgid "Joplin website"
|
||||
msgstr "Сайт Joplin"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Login with Dropbox"
|
||||
msgstr "Войти в OneDrive"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Master Key %s"
|
||||
msgstr "Мастер-ключ %s"
|
||||
@@ -1324,6 +1356,11 @@ msgstr "Пароль не может быть пустым"
|
||||
msgid "Enable"
|
||||
msgstr "Включено"
|
||||
|
||||
msgid ""
|
||||
"For more information about End-To-End Encryption (E2EE) and advices on how "
|
||||
"to enable it please check the documentation:"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The notebook could not be saved: %s"
|
||||
msgstr "Не удалось сохранить блокнот: %s"
|
||||
@@ -1377,6 +1414,9 @@ msgstr "Удалить блокнот"
|
||||
msgid "Login with OneDrive"
|
||||
msgstr "Войти в OneDrive"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Поиск"
|
||||
|
||||
msgid ""
|
||||
"Click on the (+) button to create a new note or notebook. Click on the side "
|
||||
"menu to access your existing notebooks."
|
||||
@@ -1390,6 +1430,9 @@ msgstr "У вас сейчас нет блокнота. Создайте его
|
||||
msgid "Welcome"
|
||||
msgstr "Добро пожаловать"
|
||||
|
||||
#~ msgid "Searches"
|
||||
#~ msgstr "Запросы"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid ""
|
||||
#~ "Release notes:\n"
|
||||
|
@@ -387,6 +387,16 @@ msgid ""
|
||||
"Authentication was not completed (did not receive an authentication token)."
|
||||
msgstr "认证未完成(未收到认证令牌)。"
|
||||
|
||||
msgid ""
|
||||
"To allow Joplin to synchronise with Dropbox, please follow the steps below:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Step 1: Open this URL in your browser to authorise the application:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Step 2: Enter the code provided by Dropbox:"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Not authentified with %s. Please provide any missing credentials."
|
||||
msgstr ""
|
||||
@@ -647,6 +657,9 @@ msgstr ""
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
|
||||
msgid "Submit"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Disabling encryption means *all* your notes and attachments are going to be "
|
||||
"re-synchronised and sent unencrypted to the sync target. Do you wish to "
|
||||
@@ -707,6 +720,11 @@ msgid ""
|
||||
"they will eventually be downloaded via synchronisation."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"For more information about End-To-End Encryption (E2EE) and advices on how "
|
||||
"to enable it please check the documentation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Status"
|
||||
msgstr "状态"
|
||||
|
||||
@@ -742,12 +760,13 @@ msgstr "重命名笔记本:"
|
||||
msgid "Set alarm:"
|
||||
msgstr "设置提醒:"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "搜索"
|
||||
|
||||
msgid "Layout"
|
||||
msgstr "布局"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Search..."
|
||||
msgstr "搜索"
|
||||
|
||||
msgid "Some items cannot be synchronised."
|
||||
msgstr "一些项目无法被同步。"
|
||||
|
||||
@@ -828,6 +847,9 @@ msgstr "清除"
|
||||
msgid "OneDrive Login"
|
||||
msgstr "登陆OneDrive"
|
||||
|
||||
msgid "Dropbox Login"
|
||||
msgstr ""
|
||||
|
||||
msgid "Options"
|
||||
msgstr "选项"
|
||||
|
||||
@@ -852,9 +874,6 @@ msgstr "同步"
|
||||
msgid "Notebooks"
|
||||
msgstr "笔记本"
|
||||
|
||||
msgid "Searches"
|
||||
msgstr "搜索历史"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr "请选择最先删除的笔记或笔记本。"
|
||||
@@ -867,6 +886,9 @@ msgstr "使用:%s"
|
||||
msgid "Unknown flag: %s"
|
||||
msgstr "未知标记:%s"
|
||||
|
||||
msgid "Dropbox"
|
||||
msgstr ""
|
||||
|
||||
msgid "File system"
|
||||
msgstr "文件系统"
|
||||
|
||||
@@ -941,8 +963,8 @@ msgstr "已删除远程项目: %d。"
|
||||
msgid "Fetched items: %d/%d."
|
||||
msgstr "已新建本地项目: %d。"
|
||||
|
||||
#, javascript-format
|
||||
msgid "State: \"%s\"."
|
||||
#, fuzzy, javascript-format
|
||||
msgid "State: %s."
|
||||
msgstr "状态:\"%s\"。"
|
||||
|
||||
msgid "Cancelling..."
|
||||
@@ -1247,9 +1269,19 @@ msgstr "确认"
|
||||
msgid "Cancel synchronisation"
|
||||
msgstr "取消同步"
|
||||
|
||||
msgid "New tags:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Type new tags or select from list"
|
||||
msgstr ""
|
||||
|
||||
msgid "Joplin website"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Login with Dropbox"
|
||||
msgstr "用OneDrive登陆"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Master Key %s"
|
||||
msgstr ""
|
||||
@@ -1268,6 +1300,11 @@ msgstr ""
|
||||
msgid "Enable"
|
||||
msgstr "已禁止"
|
||||
|
||||
msgid ""
|
||||
"For more information about End-To-End Encryption (E2EE) and advices on how "
|
||||
"to enable it please check the documentation:"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The notebook could not be saved: %s"
|
||||
msgstr "此笔记本无法保存:%s"
|
||||
@@ -1321,6 +1358,9 @@ msgstr "删除笔记本"
|
||||
msgid "Login with OneDrive"
|
||||
msgstr "用OneDrive登陆"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "搜索"
|
||||
|
||||
msgid ""
|
||||
"Click on the (+) button to create a new note or notebook. Click on the side "
|
||||
"menu to access your existing notebooks."
|
||||
@@ -1332,6 +1372,9 @@ msgstr "您当前没有任何笔记本。点击(+)按钮创建新笔记本。"
|
||||
msgid "Welcome"
|
||||
msgstr "欢迎"
|
||||
|
||||
#~ msgid "Searches"
|
||||
#~ msgstr "搜索历史"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid ""
|
||||
#~ "Release notes:\n"
|
||||
|
7
CliClient/package-lock.json
generated
7
CliClient/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "joplin",
|
||||
"version": "1.0.100",
|
||||
"version": "1.0.106",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -983,11 +983,6 @@
|
||||
"wrappy": "1.0.2"
|
||||
}
|
||||
},
|
||||
"os-tmpdir": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
||||
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
|
||||
},
|
||||
"parse-data-uri": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-data-uri/-/parse-data-uri-0.2.0.tgz",
|
||||
|
@@ -19,7 +19,7 @@
|
||||
],
|
||||
"owner": "Laurent Cozic"
|
||||
},
|
||||
"version": "1.0.100",
|
||||
"version": "1.0.106",
|
||||
"bin": {
|
||||
"joplin": "./main.js"
|
||||
},
|
||||
|
99
CliClient/tests/services_ResourceService.js
Normal file
99
CliClient/tests/services_ResourceService.js
Normal file
@@ -0,0 +1,99 @@
|
||||
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 InteropService = require('lib/services/InteropService.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const Tag = require('lib/models/Tag.js');
|
||||
const NoteTag = require('lib/models/NoteTag.js');
|
||||
const Resource = require('lib/models/Resource.js');
|
||||
const NoteResource = require('lib/models/NoteResource.js');
|
||||
const ResourceService = require('lib/services/ResourceService.js');
|
||||
const fs = require('fs-extra');
|
||||
const ArrayUtils = require('lib/ArrayUtils');
|
||||
const ObjectUtils = require('lib/ObjectUtils');
|
||||
const { shim } = require('lib/shim.js');
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
});
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
|
||||
|
||||
function exportDir() {
|
||||
return __dirname + '/export';
|
||||
}
|
||||
|
||||
function fieldsEqual(model1, model2, fieldNames) {
|
||||
for (let i = 0; i < fieldNames.length; i++) {
|
||||
const f = fieldNames[i];
|
||||
expect(model1[f]).toBe(model2[f], 'For key ' + f);
|
||||
}
|
||||
}
|
||||
|
||||
describe('services_ResourceService', function() {
|
||||
|
||||
beforeEach(async (done) => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
await switchClient(1);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should delete orphaned resources', asyncTest(async () => {
|
||||
const service = new ResourceService();
|
||||
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
|
||||
note1 = await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
|
||||
let resource1 = (await Resource.all())[0];
|
||||
const resourcePath = Resource.fullPath(resource1);
|
||||
|
||||
await service.indexNoteResources();
|
||||
await service.deleteOrphanResources(0);
|
||||
|
||||
expect(!!(await Resource.load(resource1.id))).toBe(true);
|
||||
|
||||
await Note.delete(note1.id);
|
||||
await service.deleteOrphanResources(0);
|
||||
|
||||
expect(!!(await Resource.load(resource1.id))).toBe(true);
|
||||
|
||||
await service.indexNoteResources();
|
||||
await service.deleteOrphanResources(1000 * 60);
|
||||
|
||||
expect(!!(await Resource.load(resource1.id))).toBe(true);
|
||||
|
||||
await service.deleteOrphanResources(0);
|
||||
|
||||
expect(!!(await Resource.load(resource1.id))).toBe(false);
|
||||
expect(await shim.fsDriver().exists(resourcePath)).toBe(false);
|
||||
expect(!(await NoteResource.all()).length).toBe(true);
|
||||
}));
|
||||
|
||||
it('should not delete resource if still associated with at least one note', asyncTest(async () => {
|
||||
const service = new ResourceService();
|
||||
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
|
||||
let note2 = await Note.save({ title: 'ma deuxième note', parent_id: folder1.id });
|
||||
note1 = await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
|
||||
let resource1 = (await Resource.all())[0];
|
||||
const resourcePath = Resource.fullPath(resource1);
|
||||
|
||||
await service.indexNoteResources();
|
||||
|
||||
await Note.delete(note1.id);
|
||||
|
||||
await service.indexNoteResources();
|
||||
|
||||
await Note.save({ id: note2.id, body: Resource.markdownTag(resource1) });
|
||||
|
||||
await service.indexNoteResources();
|
||||
|
||||
await service.deleteOrphanResources(0);
|
||||
|
||||
expect(!!(await Resource.load(resource1.id))).toBe(true);
|
||||
}));
|
||||
|
||||
});
|
@@ -339,22 +339,17 @@ describe('Synchronizer', function() {
|
||||
it('should delete local folder', asyncTest(async () => {
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
let folder2 = await Folder.save({ title: "folder2" });
|
||||
await synchronizer().start();
|
||||
let context1 = await synchronizer().start();
|
||||
|
||||
await switchClient(2);
|
||||
|
||||
await synchronizer().start();
|
||||
|
||||
await sleep(0.1);
|
||||
|
||||
let context2 = await synchronizer().start();
|
||||
await Folder.delete(folder2.id);
|
||||
|
||||
await synchronizer().start();
|
||||
await synchronizer().start({ context: context2 });
|
||||
|
||||
await switchClient(1);
|
||||
|
||||
await synchronizer().start();
|
||||
|
||||
await synchronizer().start({ context: context1 });
|
||||
let items = await allItems();
|
||||
await localItemsSameAsRemote(items, expect);
|
||||
}));
|
||||
@@ -377,7 +372,7 @@ describe('Synchronizer', function() {
|
||||
expect(items.length).toBe(1);
|
||||
expect(items[0].title).toBe('note1');
|
||||
expect(items[0].is_conflict).toBe(1);
|
||||
}));
|
||||
}));
|
||||
|
||||
it('should resolve conflict if note has been deleted remotely and locally', asyncTest(async () => {
|
||||
let folder = await Folder.save({ title: "folder" });
|
||||
@@ -438,7 +433,7 @@ describe('Synchronizer', function() {
|
||||
|
||||
expect(items1.length).toBe(0);
|
||||
expect(items1.length).toBe(items2.length);
|
||||
}));
|
||||
}));
|
||||
|
||||
it('should handle conflict when remote note is deleted then local note is modified', asyncTest(async () => {
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
@@ -547,11 +542,11 @@ describe('Synchronizer', function() {
|
||||
let n1 = await Note.save({ title: "mynote" });
|
||||
let n2 = await Note.save({ title: "mynote2" });
|
||||
let tag = await Tag.save({ title: 'mytag' });
|
||||
await synchronizer().start();
|
||||
let context1 = await synchronizer().start();
|
||||
|
||||
await switchClient(2);
|
||||
|
||||
await synchronizer().start();
|
||||
let context2 = await synchronizer().start();
|
||||
if (withEncryption) {
|
||||
const masterKey_2 = await MasterKey.load(masterKey.id);
|
||||
await encryptionService().loadMasterKey(masterKey_2, '123456', true);
|
||||
@@ -565,21 +560,21 @@ describe('Synchronizer', function() {
|
||||
await Tag.addNote(remoteTag.id, n2.id);
|
||||
let noteIds = await Tag.noteIds(tag.id);
|
||||
expect(noteIds.length).toBe(2);
|
||||
await synchronizer().start();
|
||||
context2 = await synchronizer().start({ context: context2 });
|
||||
|
||||
await switchClient(1);
|
||||
|
||||
await synchronizer().start();
|
||||
context1 = await synchronizer().start({ context: context1 });
|
||||
let remoteNoteIds = await Tag.noteIds(tag.id);
|
||||
expect(remoteNoteIds.length).toBe(2);
|
||||
await Tag.removeNote(tag.id, n1.id);
|
||||
remoteNoteIds = await Tag.noteIds(tag.id);
|
||||
expect(remoteNoteIds.length).toBe(1);
|
||||
await synchronizer().start();
|
||||
context1 = await synchronizer().start({ context: context1 });
|
||||
|
||||
await switchClient(2);
|
||||
|
||||
await synchronizer().start();
|
||||
context2 = await synchronizer().start({ context: context2 });
|
||||
noteIds = await Tag.noteIds(tag.id);
|
||||
expect(noteIds.length).toBe(1);
|
||||
expect(remoteNoteIds[0]).toBe(noteIds[0]);
|
||||
@@ -883,6 +878,37 @@ describe('Synchronizer', function() {
|
||||
expect(fileContentEqual(resourcePath1, resourcePath1_2)).toBe(true);
|
||||
}));
|
||||
|
||||
it('should delete resources', 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();
|
||||
let allResources = await Resource.all();
|
||||
expect(allResources.length).toBe(1);
|
||||
let all = await fileApi().list();
|
||||
expect(all.items.length).toBe(3);
|
||||
await Resource.delete(resource1.id);
|
||||
await synchronizer().start();
|
||||
all = await fileApi().list();
|
||||
expect(all.items.length).toBe(2);
|
||||
|
||||
await switchClient(1);
|
||||
|
||||
expect(await shim.fsDriver().exists(resourcePath1)).toBe(true);
|
||||
await synchronizer().start();
|
||||
allResources = await Resource.all();
|
||||
expect(allResources.length).toBe(0);
|
||||
expect(await shim.fsDriver().exists(resourcePath1)).toBe(false);
|
||||
}));
|
||||
|
||||
it('should encryt resources', asyncTest(async () => {
|
||||
Setting.setValue('encryption.enabled', true);
|
||||
const masterKey = await loadEncryptionMasterKey();
|
||||
|
@@ -16,6 +16,8 @@ const { FileApi } = require('lib/file-api.js');
|
||||
const { FileApiDriverMemory } = require('lib/file-api-driver-memory.js');
|
||||
const { FileApiDriverLocal } = require('lib/file-api-driver-local.js');
|
||||
const { FileApiDriverWebDav } = require('lib/file-api-driver-webdav.js');
|
||||
const { FileApiDriverDropbox } = require('lib/file-api-driver-dropbox.js');
|
||||
const BaseService = require('lib/services/BaseService.js');
|
||||
const { FsDriverNode } = require('lib/fs-driver-node.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const { shimInit } = require('lib/shim-init-node.js');
|
||||
@@ -24,9 +26,11 @@ const SyncTargetMemory = require('lib/SyncTargetMemory.js');
|
||||
const SyncTargetFilesystem = require('lib/SyncTargetFilesystem.js');
|
||||
const SyncTargetOneDrive = require('lib/SyncTargetOneDrive.js');
|
||||
const SyncTargetNextcloud = require('lib/SyncTargetNextcloud.js');
|
||||
const SyncTargetDropbox = require('lib/SyncTargetDropbox.js');
|
||||
const EncryptionService = require('lib/services/EncryptionService.js');
|
||||
const DecryptionWorker = require('lib/services/DecryptionWorker.js');
|
||||
const WebDavApi = require('lib/WebDavApi');
|
||||
const DropboxApi = require('lib/DropboxApi');
|
||||
|
||||
let databases_ = [];
|
||||
let synchronizers_ = [];
|
||||
@@ -50,10 +54,12 @@ SyncTargetRegistry.addClass(SyncTargetMemory);
|
||||
SyncTargetRegistry.addClass(SyncTargetFilesystem);
|
||||
SyncTargetRegistry.addClass(SyncTargetOneDrive);
|
||||
SyncTargetRegistry.addClass(SyncTargetNextcloud);
|
||||
SyncTargetRegistry.addClass(SyncTargetDropbox);
|
||||
|
||||
// const syncTargetId_ = SyncTargetRegistry.nameToId("nextcloud");
|
||||
const syncTargetId_ = SyncTargetRegistry.nameToId("memory");
|
||||
//const syncTargetId_ = SyncTargetRegistry.nameToId('filesystem');
|
||||
// const syncTargetId_ = SyncTargetRegistry.nameToId('dropbox');
|
||||
const syncDir = __dirname + '/../tests/sync';
|
||||
|
||||
const sleepTime = syncTargetId_ == SyncTargetRegistry.nameToId('filesystem') ? 1001 : 100;//400;
|
||||
@@ -63,7 +69,7 @@ console.info('Testing with sync target: ' + SyncTargetRegistry.idToName(syncTarg
|
||||
const logger = new Logger();
|
||||
logger.addTarget('console');
|
||||
logger.addTarget('file', { path: logDir + '/log.txt' });
|
||||
logger.setLevel(Logger.LEVEL_WARN); // Set to INFO to display sync process in console
|
||||
logger.setLevel(Logger.LEVEL_WARN); // Set to DEBUG to display sync process in console
|
||||
|
||||
BaseItem.loadClass('Note', Note);
|
||||
BaseItem.loadClass('Folder', Folder);
|
||||
@@ -75,6 +81,8 @@ BaseItem.loadClass('MasterKey', MasterKey);
|
||||
Setting.setConstant('appId', 'net.cozic.joplin-cli');
|
||||
Setting.setConstant('appType', 'cli');
|
||||
|
||||
BaseService.logger_ = logger;
|
||||
|
||||
Setting.autoSaveEnabled = false;
|
||||
|
||||
function syncTargetId() {
|
||||
@@ -118,8 +126,9 @@ async function clearDatabase(id = null) {
|
||||
'DELETE FROM tags',
|
||||
'DELETE FROM note_tags',
|
||||
'DELETE FROM master_keys',
|
||||
'DELETE FROM settings',
|
||||
|
||||
'DELETE FROM item_changes',
|
||||
'DELETE FROM note_resources',
|
||||
'DELETE FROM settings',
|
||||
'DELETE FROM deleted_items',
|
||||
'DELETE FROM sync_items',
|
||||
];
|
||||
@@ -243,25 +252,15 @@ function fileApi() {
|
||||
|
||||
const api = new WebDavApi(options);
|
||||
fileApi_ = new FileApi('', new FileApiDriverWebDav(api));
|
||||
} else if (syncTargetId_ == SyncTargetRegistry.nameToId('dropbox')) {
|
||||
const api = new DropboxApi();
|
||||
const authTokenPath = __dirname + '/support/dropbox-auth.txt';
|
||||
const authToken = fs.readFileSync(authTokenPath, 'utf8');
|
||||
if (!authToken) throw new Error('Dropbox auth token missing in ' + authTokenPath);
|
||||
api.setAuthToken(authToken);
|
||||
fileApi_ = new FileApi('', new FileApiDriverDropbox(api));
|
||||
}
|
||||
|
||||
// } else if (syncTargetId == Setting.SYNC_TARGET_ONEDRIVE) {
|
||||
// let auth = require('./onedrive-auth.json');
|
||||
// if (!auth) {
|
||||
// const oneDriveApiUtils = new OneDriveApiNodeUtils(oneDriveApi);
|
||||
// auth = await oneDriveApiUtils.oauthDance();
|
||||
// fs.writeFileSync('./onedrive-auth.json', JSON.stringify(auth));
|
||||
// process.exit(1);
|
||||
// } else {
|
||||
// auth = JSON.parse(auth);
|
||||
// }
|
||||
|
||||
// // const oneDriveApiUtils = new OneDriveApiNodeUtils(reg.oneDriveApi());
|
||||
// // const auth = await oneDriveApiUtils.oauthDance(this);
|
||||
// // Setting.setValue('sync.3.auth', auth ? JSON.stringify(auth) : null);
|
||||
// // if (!auth) return;
|
||||
// }
|
||||
|
||||
fileApi_.setLogger(logger);
|
||||
fileApi_.setSyncTargetId(syncTargetId_);
|
||||
fileApi_.requestRepeatCount_ = 0;
|
||||
@@ -302,8 +301,9 @@ function asyncTest(callback) {
|
||||
await callback();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
done();
|
||||
}
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -22,6 +22,7 @@ const AlarmServiceDriverNode = require('lib/services/AlarmServiceDriverNode');
|
||||
const DecryptionWorker = require('lib/services/DecryptionWorker');
|
||||
const InteropService = require('lib/services/InteropService');
|
||||
const InteropServiceHelper = require('./InteropServiceHelper.js');
|
||||
const ResourceService = require('lib/services/ResourceService');
|
||||
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
const Menu = bridge().Menu;
|
||||
@@ -348,17 +349,17 @@ class Application extends BaseApplication {
|
||||
label: _('Edit'),
|
||||
submenu: [{
|
||||
label: _('Copy'),
|
||||
screens: ['Main', 'OneDriveLogin', 'Config', 'EncryptionConfig'],
|
||||
//screens: ['Main', 'OneDriveLogin', 'Config', 'EncryptionConfig'],
|
||||
role: 'copy',
|
||||
accelerator: 'CommandOrControl+C',
|
||||
}, {
|
||||
label: _('Cut'),
|
||||
screens: ['Main', 'OneDriveLogin', 'Config', 'EncryptionConfig'],
|
||||
//screens: ['Main', 'OneDriveLogin', 'Config', 'EncryptionConfig'],
|
||||
role: 'cut',
|
||||
accelerator: 'CommandOrControl+X',
|
||||
}, {
|
||||
label: _('Paste'),
|
||||
screens: ['Main', 'OneDriveLogin', 'Config', 'EncryptionConfig'],
|
||||
//screens: ['Main', 'OneDriveLogin', 'Config', 'EncryptionConfig'],
|
||||
role: 'paste',
|
||||
accelerator: 'CommandOrControl+V',
|
||||
}, {
|
||||
@@ -367,11 +368,11 @@ class Application extends BaseApplication {
|
||||
}, {
|
||||
label: _('Search in all the notes'),
|
||||
screens: ['Main'],
|
||||
accelerator: 'F6',
|
||||
accelerator: 'CommandOrControl+F',
|
||||
click: () => {
|
||||
this.dispatch({
|
||||
type: 'WINDOW_COMMAND',
|
||||
name: 'search',
|
||||
name: 'focus_search',
|
||||
});
|
||||
},
|
||||
}],
|
||||
@@ -447,10 +448,10 @@ class Application extends BaseApplication {
|
||||
submenu: [{
|
||||
label: _('Website and documentation'),
|
||||
accelerator: 'F1',
|
||||
click () { bridge().openExternal('http://joplin.cozic.net') }
|
||||
click () { bridge().openExternal('https://joplin.cozic.net') }
|
||||
}, {
|
||||
label: _('Make a donation'),
|
||||
click () { bridge().openExternal('http://joplin.cozic.net/donate') }
|
||||
click () { bridge().openExternal('https://joplin.cozic.net/donate') }
|
||||
}, {
|
||||
label: _('Check for updates...'),
|
||||
click: () => {
|
||||
@@ -608,6 +609,8 @@ class Application extends BaseApplication {
|
||||
AlarmService.garbageCollect();
|
||||
}, 1000 * 60 * 60);
|
||||
|
||||
ResourceService.runInBackground();
|
||||
|
||||
if (Setting.value('env') === 'dev') {
|
||||
AlarmService.updateAllNotifications();
|
||||
} else {
|
||||
|
62
ElectronClient/app/gui/DropboxLoginScreen.jsx
Normal file
62
ElectronClient/app/gui/DropboxLoginScreen.jsx
Normal file
@@ -0,0 +1,62 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
const { Header } = require('./Header.min.js');
|
||||
const { themeStyle } = require('../theme.js');
|
||||
const SyncTargetRegistry = require('lib/SyncTargetRegistry');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const Shared = require('lib/components/shared/dropbox-login-shared');
|
||||
|
||||
class DropboxLoginScreenComponent extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.shared_ = new Shared(
|
||||
this,
|
||||
(msg) => bridge().showInfoMessageBox(msg),
|
||||
(msg) => bridge().showErrorMessageBox(msg)
|
||||
);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.shared_.refreshUrl();
|
||||
}
|
||||
|
||||
render() {
|
||||
const style = this.props.style;
|
||||
const theme = themeStyle(this.props.theme);
|
||||
|
||||
const headerStyle = {
|
||||
width: style.width,
|
||||
};
|
||||
|
||||
const inputStyle = Object.assign({}, theme.inputStyle, { width: 500 });
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Header style={headerStyle} />
|
||||
<div style={{padding: theme.margin}}>
|
||||
<p style={theme.textStyle}>{_('To allow Joplin to synchronise with Dropbox, please follow the steps below:')}</p>
|
||||
<p style={theme.textStyle}>{_('Step 1: Open this URL in your browser to authorise the application:')}</p>
|
||||
<a style={theme.textStyle} href="#" onClick={this.shared_.loginUrl_click}>{this.state.loginUrl}</a>
|
||||
<p style={theme.textStyle}>{_('Step 2: Enter the code provided by Dropbox:')}</p>
|
||||
<p><input type="text" value={this.state.authCode} onChange={this.shared_.authCodeInput_change} style={inputStyle}/></p>
|
||||
<button disabled={this.state.checkingAuthToken} onClick={this.shared_.submit_click}>{_('Submit')}</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
};
|
||||
};
|
||||
|
||||
const DropboxLoginScreen = connect(mapStateToProps)(DropboxLoginScreenComponent);
|
||||
|
||||
module.exports = { DropboxLoginScreen };
|
@@ -182,17 +182,11 @@ class EncryptionConfigScreenComponent extends React.Component {
|
||||
<div>
|
||||
<Header style={headerStyle} />
|
||||
<div style={containerStyle}>
|
||||
{/*<div style={{backgroundColor: theme.warningBackgroundColor, paddingLeft: 10, paddingRight: 10, paddingTop: 2, paddingBottom: 2 }}>
|
||||
{<div style={{backgroundColor: theme.warningBackgroundColor, paddingLeft: 10, paddingRight: 10, paddingTop: 2, paddingBottom: 2 }}>
|
||||
<p style={theme.textStyle}>
|
||||
Important: This is a <b>beta</b> feature. It has been extensively tested and is already in use by some users, but it is possible that some bugs remain.
|
||||
<span>{_('For more information about End-To-End Encryption (E2EE) and advices on how to enable it please check the documentation:')}</span> <a onClick={() => {bridge().openExternal('https://joplin.cozic.net/e2ee')}} href="#">https://joplin.cozic.net/e2ee</a>
|
||||
</p>
|
||||
<p style={theme.textStyle}>
|
||||
If you wish to you use it, it is recommended that you keep a backup of your data. The simplest way is to regularly backup <b>{pathUtils.toSystemSlashes(Setting.value('profileDir'), process.platform)}</b>
|
||||
</p>
|
||||
<p style={theme.textStyle}>
|
||||
For more information about End-To-End Encryption (E2EE) and how it is going to work, please check the documentation: <a onClick={() => {bridge().openExternal('http://joplin.cozic.net/help/e2ee.html')}} href="#">http://joplin.cozic.net/help/e2ee.html</a>
|
||||
</p>
|
||||
</div>*/}
|
||||
</div>}
|
||||
<h1 style={theme.h1Style}>{_('Status')}</h1>
|
||||
<p style={theme.textStyle}>{_('Encryption is:')} <strong>{this.props.encryptionEnabled ? _('Enabled') : _('Disabled')}</strong></p>
|
||||
{decryptedItemsInfo}
|
||||
|
@@ -6,6 +6,63 @@ const { _ } = require('lib/locale.js');
|
||||
|
||||
class HeaderComponent extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
searchQuery: '',
|
||||
};
|
||||
|
||||
this.scheduleSearchChangeEventIid_ = null;
|
||||
this.searchOnQuery_ = null;
|
||||
this.searchElement_ = null;
|
||||
|
||||
const triggerOnQuery = (query) => {
|
||||
clearTimeout(this.scheduleSearchChangeEventIid_);
|
||||
if (this.searchOnQuery_) this.searchOnQuery_(query);
|
||||
this.scheduleSearchChangeEventIid_ = null;
|
||||
}
|
||||
|
||||
this.search_onChange = (event) => {
|
||||
this.setState({ searchQuery: event.target.value });
|
||||
|
||||
if (this.scheduleSearchChangeEventIid_) clearTimeout(this.scheduleSearchChangeEventIid_);
|
||||
|
||||
this.scheduleSearchChangeEventIid_ = setTimeout(() => {
|
||||
triggerOnQuery(this.state.searchQuery);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
this.search_onClear = (event) => {
|
||||
this.setState({ searchQuery: '' });
|
||||
triggerOnQuery('');
|
||||
}
|
||||
}
|
||||
|
||||
async componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.windowCommand) {
|
||||
this.doCommand(nextProps.windowCommand);
|
||||
}
|
||||
}
|
||||
|
||||
async doCommand(command) {
|
||||
if (!command) return;
|
||||
|
||||
let commandProcessed = true;
|
||||
|
||||
if (command.name === 'focus_search' && this.searchElement_) {
|
||||
this.searchElement_.focus();
|
||||
} else {
|
||||
commandProcessed = false;
|
||||
}
|
||||
|
||||
if (commandProcessed) {
|
||||
this.props.dispatch({
|
||||
type: 'WINDOW_COMMAND',
|
||||
name: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
back_click() {
|
||||
this.props.dispatch({ type: 'NAV_BACK' });
|
||||
}
|
||||
@@ -40,6 +97,59 @@ class HeaderComponent extends React.Component {
|
||||
</a>
|
||||
}
|
||||
|
||||
makeSearch(key, style, options, state) {
|
||||
const inputStyle = {
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
paddingLeft: 4,
|
||||
paddingRight: 4,
|
||||
color: style.color,
|
||||
fontSize: style.fontSize,
|
||||
fontFamily: style.fontFamily,
|
||||
};
|
||||
|
||||
const searchButton = {
|
||||
paddingLeft: 4,
|
||||
paddingRight: 4,
|
||||
paddingTop: 2,
|
||||
paddingBottom: 2,
|
||||
textDecoration: 'none',
|
||||
};
|
||||
|
||||
const iconStyle = {
|
||||
display: 'flex',
|
||||
fontSize: Math.round(style.fontSize) * 1.2,
|
||||
color: style.color,
|
||||
};
|
||||
|
||||
const containerStyle = {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
};
|
||||
|
||||
const iconName = state.searchQuery ? 'fa-times' : 'fa-search';
|
||||
const icon = <i style={iconStyle} className={"fa " + iconName}></i>
|
||||
if (options.onQuery) this.searchOnQuery_ = options.onQuery;
|
||||
|
||||
return (
|
||||
<div key={key} style={containerStyle}>
|
||||
<input
|
||||
type="text"
|
||||
style={inputStyle}
|
||||
placeholder={options.title}
|
||||
value={state.searchQuery}
|
||||
onChange={this.search_onChange}
|
||||
ref={elem => this.searchElement_ = elem}
|
||||
/>
|
||||
<a
|
||||
href="#"
|
||||
style={searchButton}
|
||||
onClick={this.search_onClear}
|
||||
>{icon}</a>
|
||||
</div>);
|
||||
}
|
||||
|
||||
render() {
|
||||
const style = Object.assign({}, this.props.style);
|
||||
const theme = themeStyle(this.props.theme);
|
||||
@@ -50,9 +160,9 @@ class HeaderComponent extends React.Component {
|
||||
style.borderBottom = '1px solid ' + theme.dividerColor;
|
||||
style.boxSizing = 'border-box';
|
||||
|
||||
const buttons = [];
|
||||
const items = [];
|
||||
|
||||
const buttonStyle = {
|
||||
const itemStyle = {
|
||||
height: theme.headerHeight,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
@@ -67,19 +177,24 @@ class HeaderComponent extends React.Component {
|
||||
};
|
||||
|
||||
if (showBackButton) {
|
||||
buttons.push(this.makeButton('back', buttonStyle, { title: _('Back'), onClick: () => this.back_click(), iconName: 'fa-chevron-left ' }));
|
||||
items.push(this.makeButton('back', itemStyle, { title: _('Back'), onClick: () => this.back_click(), iconName: 'fa-chevron-left ' }));
|
||||
}
|
||||
|
||||
if (this.props.buttons) {
|
||||
for (let i = 0; i < this.props.buttons.length; i++) {
|
||||
const o = this.props.buttons[i];
|
||||
buttons.push(this.makeButton('btn_' + i + '_' + o.title, buttonStyle, o));
|
||||
if (this.props.items) {
|
||||
for (let i = 0; i < this.props.items.length; i++) {
|
||||
const item = this.props.items[i];
|
||||
|
||||
if (item.type === 'search') {
|
||||
items.push(this.makeSearch('item_' + i + '_search', itemStyle, item, this.state));
|
||||
} else {
|
||||
items.push(this.makeButton('item_' + i + '_' + item.title, itemStyle, item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="header" style={style}>
|
||||
{ buttons }
|
||||
{ items }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -87,7 +202,10 @@ class HeaderComponent extends React.Component {
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return { theme: state.settings.theme };
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
windowCommand: state.windowCommand,
|
||||
};
|
||||
};
|
||||
|
||||
const Header = connect(mapStateToProps)(HeaderComponent);
|
||||
|
@@ -140,33 +140,27 @@ class MainScreenComponent extends React.Component {
|
||||
},
|
||||
});
|
||||
} else if (command.name === 'search') {
|
||||
this.setState({
|
||||
promptOptions: {
|
||||
label: _('Search:'),
|
||||
onClose: async (answer) => {
|
||||
if (answer !== null) {
|
||||
const searchId = uuid.create();
|
||||
|
||||
this.props.dispatch({
|
||||
type: 'SEARCH_ADD',
|
||||
search: {
|
||||
id: searchId,
|
||||
title: answer,
|
||||
query_pattern: answer,
|
||||
query_folder_id: null,
|
||||
type_: BaseModel.TYPE_SEARCH,
|
||||
},
|
||||
});
|
||||
if (!this.searchId_) this.searchId_ = uuid.create();
|
||||
|
||||
this.props.dispatch({
|
||||
type: 'SEARCH_SELECT',
|
||||
id: searchId,
|
||||
});
|
||||
}
|
||||
this.setState({ promptOptions: null });
|
||||
}
|
||||
this.props.dispatch({
|
||||
type: 'SEARCH_UPDATE',
|
||||
search: {
|
||||
id: this.searchId_,
|
||||
title: command.query,
|
||||
query_pattern: command.query,
|
||||
query_folder_id: null,
|
||||
type_: BaseModel.TYPE_SEARCH,
|
||||
},
|
||||
});
|
||||
|
||||
if (command.query) {
|
||||
this.props.dispatch({
|
||||
type: 'SEARCH_SELECT',
|
||||
id: this.searchId_,
|
||||
});
|
||||
}
|
||||
|
||||
} else if (command.name === 'toggleVisiblePanes') {
|
||||
this.toggleVisiblePanes();
|
||||
} else if (command.name === 'showModalMessage') {
|
||||
@@ -278,7 +272,7 @@ class MainScreenComponent extends React.Component {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
backgroundColor: theme.backgroundColorTransparent,
|
||||
backgroundColor: theme.backgroundColor,
|
||||
width: width - 20,
|
||||
height: height - 20,
|
||||
padding: 10,
|
||||
@@ -298,41 +292,42 @@ class MainScreenComponent extends React.Component {
|
||||
const selectedFolderId = this.props.selectedFolderId;
|
||||
const onConflictFolder = this.props.selectedFolderId === Folder.conflictFolderId();
|
||||
|
||||
const headerButtons = [];
|
||||
const headerItems = [];
|
||||
|
||||
headerButtons.push({
|
||||
headerItems.push({
|
||||
title: _('New note'),
|
||||
iconName: 'fa-file-o',
|
||||
enabled: !!folders.length && !onConflictFolder,
|
||||
onClick: () => { this.doCommand({ name: 'newNote' }) },
|
||||
});
|
||||
|
||||
headerButtons.push({
|
||||
headerItems.push({
|
||||
title: _('New to-do'),
|
||||
iconName: 'fa-check-square-o',
|
||||
enabled: !!folders.length && !onConflictFolder,
|
||||
onClick: () => { this.doCommand({ name: 'newTodo' }) },
|
||||
});
|
||||
|
||||
headerButtons.push({
|
||||
headerItems.push({
|
||||
title: _('New notebook'),
|
||||
iconName: 'fa-folder-o',
|
||||
onClick: () => { this.doCommand({ name: 'newNotebook' }) },
|
||||
});
|
||||
|
||||
headerButtons.push({
|
||||
title: _('Search'),
|
||||
iconName: 'fa-search',
|
||||
onClick: () => { this.doCommand({ name: 'search' }) },
|
||||
});
|
||||
|
||||
headerButtons.push({
|
||||
headerItems.push({
|
||||
title: _('Layout'),
|
||||
iconName: 'fa-columns',
|
||||
enabled: !!notes.length,
|
||||
onClick: () => { this.doCommand({ name: 'toggleVisiblePanes' }) },
|
||||
});
|
||||
|
||||
headerItems.push({
|
||||
title: _('Search...'),
|
||||
iconName: 'fa-search',
|
||||
onQuery: (query) => { this.doCommand({ name: 'search', query: query }) },
|
||||
type: 'search',
|
||||
});
|
||||
|
||||
if (!this.promptOnClose_) {
|
||||
this.promptOnClose_ = (answer, buttonType) => {
|
||||
return this.state.promptOptions.onClose(answer, buttonType);
|
||||
@@ -389,7 +384,7 @@ class MainScreenComponent extends React.Component {
|
||||
visible={!!this.state.promptOptions}
|
||||
buttons={promptOptions && ('buttons' in promptOptions) ? promptOptions.buttons : null}
|
||||
inputType={promptOptions && ('inputType' in promptOptions) ? promptOptions.inputType : null} />
|
||||
<Header style={styles.header} showBackButton={false} buttons={headerButtons} />
|
||||
<Header style={styles.header} showBackButton={false} items={headerItems} />
|
||||
{messageComp}
|
||||
<SideBar style={styles.sideBar} />
|
||||
<NoteList style={styles.noteList} />
|
||||
|
@@ -11,6 +11,8 @@ const MenuItem = bridge().MenuItem;
|
||||
const eventManager = require('../eventManager');
|
||||
const InteropService = require('lib/services/InteropService');
|
||||
const InteropServiceHelper = require('../InteropServiceHelper.js');
|
||||
const Search = require('lib/models/Search');
|
||||
const Mark = require('mark.js/dist/mark.min.js');
|
||||
|
||||
class NoteListComponent extends React.Component {
|
||||
|
||||
@@ -164,6 +166,12 @@ class NoteListComponent extends React.Component {
|
||||
|
||||
const hPadding = 10;
|
||||
|
||||
let highlightedWords = [];
|
||||
if (this.props.notesParentType === 'Search') {
|
||||
const search = BaseModel.byId(this.props.searches, this.props.selectedSearchId);
|
||||
highlightedWords = search ? Search.keywords(search.query_pattern) : [];
|
||||
}
|
||||
|
||||
let style = Object.assign({ width: width }, this.style().listItem);
|
||||
|
||||
if (this.props.selectedNoteIds.indexOf(item.id) >= 0) {
|
||||
@@ -182,8 +190,30 @@ class NoteListComponent extends React.Component {
|
||||
listItemTitleStyle.paddingLeft = !checkbox ? hPadding : 4;
|
||||
if (item.is_todo && !!item.todo_completed) listItemTitleStyle = Object.assign(listItemTitleStyle, this.style().listItemTitleCompleted);
|
||||
|
||||
let displayTitle = Note.displayTitle(item);
|
||||
let titleComp = null;
|
||||
|
||||
if (highlightedWords.length) {
|
||||
const titleElement = document.createElement('span');
|
||||
titleElement.textContent = displayTitle;
|
||||
const mark = new Mark(titleElement, {
|
||||
exclude: ['img'],
|
||||
acrossElements: true,
|
||||
});
|
||||
mark.mark(highlightedWords);
|
||||
|
||||
// Note: in this case it is safe to use dangerouslySetInnerHTML because titleElement
|
||||
// is a span tag that we created and that contains data that's been inserted as plain text
|
||||
// with `textContent` so it cannot contain any XSS attacks. We use this feature because
|
||||
// mark.js can only deal with DOM elements.
|
||||
// https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
|
||||
titleComp = <span dangerouslySetInnerHTML={{ __html: titleElement.outerHTML }}></span>
|
||||
} else {
|
||||
titleComp = <span>{displayTitle}</span>
|
||||
}
|
||||
|
||||
// Need to include "todo_completed" in key so that checkbox is updated when
|
||||
// item is changed via sync.
|
||||
// item is changed via sync.
|
||||
return <div key={item.id + '_' + item.todo_completed} style={style}>
|
||||
{checkbox}
|
||||
<a
|
||||
@@ -196,7 +226,7 @@ class NoteListComponent extends React.Component {
|
||||
onDragStart={(event) => onDragStart(event) }
|
||||
data-id={item.id}
|
||||
>
|
||||
{Note.displayTitle(item)}
|
||||
{titleComp}
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
@@ -239,7 +269,9 @@ const mapStateToProps = (state) => {
|
||||
folders: state.folders,
|
||||
selectedNoteIds: state.selectedNoteIds,
|
||||
theme: state.settings.theme,
|
||||
// uncompletedTodosOnTop: state.settings.uncompletedTodosOnTop,
|
||||
notesParentType: state.notesParentType,
|
||||
searches: state.searches,
|
||||
selectedSearchId: state.selectedSearchId,
|
||||
};
|
||||
};
|
||||
|
||||
|
47
ElectronClient/app/gui/NoteStatusBar.jsx
Normal file
47
ElectronClient/app/gui/NoteStatusBar.jsx
Normal file
@@ -0,0 +1,47 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const { themeStyle } = require('../theme.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
|
||||
class NoteStatusBarComponent extends React.Component {
|
||||
|
||||
style() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
|
||||
const itemHeight = 34;
|
||||
|
||||
let style = {
|
||||
root: Object.assign({}, theme.textStyle, {
|
||||
backgroundColor: theme.backgroundColor,
|
||||
color: theme.colorFaded,
|
||||
}),
|
||||
};
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const style = this.props.style;
|
||||
const note = this.props.note;
|
||||
|
||||
return (
|
||||
<div style={this.style().root}>{time.formatMsToLocal(note.user_updated_time)}</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
// notes: state.notes,
|
||||
// folders: state.folders,
|
||||
// selectedNoteIds: state.selectedNoteIds,
|
||||
theme: state.settings.theme,
|
||||
};
|
||||
};
|
||||
|
||||
const NoteStatusBar = connect(mapStateToProps)(NoteStatusBarComponent);
|
||||
|
||||
module.exports = { NoteStatusBar };
|
@@ -1,5 +1,7 @@
|
||||
const React = require('react');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
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');
|
||||
@@ -195,7 +197,9 @@ class NoteTextComponent extends React.Component {
|
||||
this.editorSetScrollTop(1);
|
||||
this.restoreScrollTop_ = 0;
|
||||
|
||||
if (note) {
|
||||
// If a search is in progress we don't focus any field otherwise it will
|
||||
// take the focus out of the search box.
|
||||
if (note && this.props.notesParentType !== 'Search') {
|
||||
const focusSettingName = !!note.is_todo ? 'newTodoFocus' : 'newNoteFocus';
|
||||
|
||||
if (Setting.value(focusSettingName) === 'title') {
|
||||
@@ -393,6 +397,18 @@ class NoteTextComponent extends React.Component {
|
||||
|
||||
if (this.editor_) {
|
||||
this.editor_.editor.renderer.on('afterRender', this.onAfterEditorRender_);
|
||||
|
||||
const cancelledKeys = ['Ctrl+F', 'Ctrl+T', 'Ctrl+P', 'Ctrl+Q', 'Ctrl+L', 'Ctrl+,'];
|
||||
for (let i = 0; i < cancelledKeys.length; i++) {
|
||||
const k = cancelledKeys[i];
|
||||
this.editor_.editor.commands.bindKey(k, () => {
|
||||
// HACK: Ace doesn't seem to provide a way to override its shortcuts, but throwing
|
||||
// an exception from this undocumented function seems to cancel it without any
|
||||
// side effect.
|
||||
// https://stackoverflow.com/questions/36075846
|
||||
throw new Error('HACK: Overriding Ace Editor shortcut: ' + k);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,6 +582,7 @@ class NoteTextComponent extends React.Component {
|
||||
marginBottom: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
};
|
||||
|
||||
const titleEditorStyle = {
|
||||
@@ -642,6 +659,10 @@ class NoteTextComponent extends React.Component {
|
||||
|
||||
const html = this.mdToHtml().render(bodyToRender, theme, mdOptions);
|
||||
this.webview_.send('setHtml', html);
|
||||
|
||||
const search = BaseModel.byId(this.props.searches, this.props.selectedSearchId);
|
||||
const keywords = search ? Search.keywords(search.query_pattern) : [];
|
||||
this.webview_.send('setMarkers', keywords);
|
||||
}
|
||||
|
||||
const toolbarItems = [];
|
||||
@@ -685,6 +706,8 @@ class NoteTextComponent extends React.Component {
|
||||
display: 'flex',
|
||||
}} iconName="fa-caret-down" theme={this.props.theme} onClick={() => { this.itemContextMenu() }} />
|
||||
|
||||
const titleBarDate = <span style={Object.assign({}, theme.textStyle, {color: theme.colorFaded})}>{time.formatMsToLocal(note.user_updated_time)}</span>
|
||||
|
||||
const viewer = <webview
|
||||
style={viewerStyle}
|
||||
nodeintegration="1"
|
||||
@@ -692,6 +715,24 @@ class NoteTextComponent extends React.Component {
|
||||
ref={(elem) => { this.webview_ref(elem); } }
|
||||
/>
|
||||
|
||||
// const markers = [{
|
||||
// startRow: 2,
|
||||
// startCol: 3,
|
||||
// endRow: 2,
|
||||
// endCol: 6,
|
||||
// type: 'text',
|
||||
// className: 'test-marker'
|
||||
// }];
|
||||
|
||||
// markers={markers}
|
||||
// editorProps={{$useWorker: false}}
|
||||
|
||||
// #note-editor .test-marker {
|
||||
// background-color: red;
|
||||
// color: yellow;
|
||||
// position: absolute;
|
||||
// }
|
||||
|
||||
const editorRootStyle = Object.assign({}, editorStyle);
|
||||
delete editorRootStyle.width;
|
||||
delete editorRootStyle.height;
|
||||
@@ -718,14 +759,15 @@ class NoteTextComponent extends React.Component {
|
||||
editorProps={{$blockScrolling: true}}
|
||||
|
||||
// This is buggy (gets outside the container)
|
||||
highlightActiveLine={false}
|
||||
highlightActiveLine={false}
|
||||
/>
|
||||
|
||||
return (
|
||||
<div style={rootStyle}>
|
||||
<div style={titleBarStyle}>
|
||||
{ titleEditor }
|
||||
{ titleBarMenuButton }
|
||||
{ titleBarDate }
|
||||
{ false ? titleBarMenuButton : null }
|
||||
</div>
|
||||
{ toolbar }
|
||||
{ editor }
|
||||
@@ -747,6 +789,9 @@ const mapStateToProps = (state) => {
|
||||
syncStarted: state.syncStarted,
|
||||
newNote: state.newNote,
|
||||
windowCommand: state.windowCommand,
|
||||
notesParentType: state.notesParentType,
|
||||
searches: state.searches,
|
||||
selectedSearchId: state.selectedSearchId,
|
||||
};
|
||||
};
|
||||
|
||||
|
@@ -8,6 +8,7 @@ const Setting = require('lib/models/Setting.js');
|
||||
|
||||
const { MainScreen } = require('./MainScreen.min.js');
|
||||
const { OneDriveLoginScreen } = require('./OneDriveLoginScreen.min.js');
|
||||
const { DropboxLoginScreen } = require('./DropboxLoginScreen.min.js');
|
||||
const { StatusScreen } = require('./StatusScreen.min.js');
|
||||
const { ImportScreen } = require('./ImportScreen.min.js');
|
||||
const { ConfigScreen } = require('./ConfigScreen.min.js');
|
||||
@@ -75,6 +76,7 @@ class RootComponent extends React.Component {
|
||||
const screens = {
|
||||
Main: { screen: MainScreen },
|
||||
OneDriveLogin: { screen: OneDriveLoginScreen, title: () => _('OneDrive Login') },
|
||||
DropboxLogin: { screen: DropboxLoginScreen, title: () => _('Dropbox Login') },
|
||||
Import: { screen: ImportScreen, title: () => _('Import') },
|
||||
Config: { screen: ConfigScreen, title: () => _('Options') },
|
||||
Status: { screen: StatusScreen, title: () => _('Synchronisation Status') },
|
||||
|
@@ -345,17 +345,17 @@ class SideBarComponent extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.searches.length) {
|
||||
items.push(this.makeHeader("searchHeader", _("Searches"), "fa-search"));
|
||||
// if (this.props.searches.length) {
|
||||
// items.push(this.makeHeader("searchHeader", _("Searches"), "fa-search"));
|
||||
|
||||
const searchItems = shared.renderSearches(this.props, this.searchItem.bind(this));
|
||||
// const searchItems = shared.renderSearches(this.props, this.searchItem.bind(this));
|
||||
|
||||
items.push(
|
||||
<div className="searches" key="search_items">
|
||||
{searchItems}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// items.push(
|
||||
// <div className="searches" key="search_items">
|
||||
// {searchItems}
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
let lines = Synchronizer.reportToLines(this.props.syncReport);
|
||||
const syncReportText = [];
|
||||
|
@@ -1,6 +1,8 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<style>
|
||||
body {
|
||||
overflow: hidden;
|
||||
@@ -13,12 +15,18 @@
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
mark {
|
||||
background: #CF3F00;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.katex { font-size: 1.3em; } /* This controls the global Katex font size*/
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body id="body">
|
||||
<div id="hlScriptContainer"></div>
|
||||
<div id="markScriptContainer"></div>
|
||||
<div id="content" ondragstart="return false;" ondrop="return false;"></div>
|
||||
|
||||
<script>
|
||||
@@ -38,7 +46,7 @@
|
||||
const script = document.createElement('script');
|
||||
script.onload = function () {
|
||||
hljsLoaded = true;
|
||||
applyHljs();
|
||||
applyHljs();
|
||||
};
|
||||
script.src = 'highlight/highlight.pack.js';
|
||||
document.getElementById('hlScriptContainer').appendChild(script);
|
||||
@@ -160,6 +168,36 @@
|
||||
setPercentScroll(percent);
|
||||
});
|
||||
|
||||
let mark_ = null;
|
||||
function setMarkers(keywords) {
|
||||
if (!mark_) {
|
||||
mark_ = new Mark(document.getElementById('content'), {
|
||||
exclude: ['img'],
|
||||
acrossElements: true,
|
||||
});
|
||||
}
|
||||
|
||||
mark_.mark(keywords);
|
||||
}
|
||||
|
||||
let markLoaded_ = false;
|
||||
ipcRenderer.on('setMarkers', (event, keywords) => {
|
||||
if (!keywords.length && !markLoaded_) return;
|
||||
|
||||
if (!markLoaded_) {
|
||||
const script = document.createElement('script');
|
||||
script.onload = function() {
|
||||
setMarkers(keywords);
|
||||
};
|
||||
|
||||
script.src = '../../node_modules/mark.js/dist/mark.min.js';
|
||||
document.getElementById('markScriptContainer').appendChild(script);
|
||||
markLoaded_ = true;
|
||||
} else {
|
||||
setMarkers(keywords);
|
||||
}
|
||||
});
|
||||
|
||||
function maxScrollTop() {
|
||||
return Math.max(0, contentElement.scrollHeight - contentElement.clientHeight);
|
||||
}
|
||||
|
@@ -17,6 +17,10 @@
|
||||
.smalltalk .page {
|
||||
max-width: 30em;
|
||||
}
|
||||
mark {
|
||||
background: #CF3F00;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
1
ElectronClient/app/locales/cs_CZ.json
Normal file
1
ElectronClient/app/locales/cs_CZ.json
Normal file
File diff suppressed because one or more lines are too long
1
ElectronClient/app/locales/da_DK.json
Normal file
1
ElectronClient/app/locales/da_DK.json
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
ElectronClient/app/locales/gl_ES.json
Normal file
1
ElectronClient/app/locales/gl_ES.json
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,9 +1,12 @@
|
||||
var locales = {};
|
||||
locales['en_GB'] = require('./en_GB.json');
|
||||
locales['cs_CZ'] = require('./cs_CZ.json');
|
||||
locales['da_DK'] = require('./da_DK.json');
|
||||
locales['de_DE'] = require('./de_DE.json');
|
||||
locales['es_ES'] = require('./es_ES.json');
|
||||
locales['eu'] = require('./eu.json');
|
||||
locales['fr_FR'] = require('./fr_FR.json');
|
||||
locales['gl_ES'] = require('./gl_ES.json');
|
||||
locales['hr_HR'] = require('./hr_HR.json');
|
||||
locales['it_IT'] = require('./it_IT.json');
|
||||
locales['ja_JP'] = require('./ja_JP.json');
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
455
ElectronClient/app/package-lock.json
generated
455
ElectronClient/app/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Joplin",
|
||||
"version": "1.0.73",
|
||||
"version": "1.0.83",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -643,7 +643,7 @@
|
||||
"requires": {
|
||||
"ansi-align": "2.0.0",
|
||||
"camelcase": "4.1.0",
|
||||
"chalk": "2.3.1",
|
||||
"chalk": "2.3.2",
|
||||
"cli-boxes": "1.0.0",
|
||||
"string-width": "2.1.1",
|
||||
"term-size": "1.2.0",
|
||||
@@ -657,9 +657,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
|
||||
"integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==",
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "1.9.1"
|
||||
@@ -672,14 +672,14 @@
|
||||
"dev": true
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz",
|
||||
"integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==",
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz",
|
||||
"integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "3.2.0",
|
||||
"ansi-styles": "3.2.1",
|
||||
"escape-string-regexp": "1.0.5",
|
||||
"supports-color": "5.2.0"
|
||||
"supports-color": "5.3.0"
|
||||
}
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
@@ -708,9 +708,9 @@
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz",
|
||||
"integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==",
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz",
|
||||
"integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "3.0.0"
|
||||
@@ -748,45 +748,77 @@
|
||||
}
|
||||
},
|
||||
"builder-util": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/builder-util/-/builder-util-5.6.0.tgz",
|
||||
"integrity": "sha512-5Enhnnm9gCHjzOUnVqqGjuMlx6pPA36VImQ9wgpRIIyfqLPXLVyWHOYbd0CThm/+GFMWx9xwAUyU6uL93+vwMg==",
|
||||
"version": "5.6.5",
|
||||
"resolved": "https://registry.npmjs.org/builder-util/-/builder-util-5.6.5.tgz",
|
||||
"integrity": "sha512-J03MEuyUf8li8KjTpml3r0K2Q9jZl21bHTEuGiZZX9vSMY81mXZe5AmEutsquyDrcsQFjCrDkbEDaqSc7vj5sw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"7zip-bin": "3.1.0",
|
||||
"app-builder-bin": "1.5.0",
|
||||
"app-builder-bin": "1.7.2",
|
||||
"bluebird-lst": "1.0.5",
|
||||
"builder-util-runtime": "4.0.5",
|
||||
"chalk": "2.3.1",
|
||||
"chalk": "2.3.2",
|
||||
"debug": "3.1.0",
|
||||
"fs-extra-p": "4.5.2",
|
||||
"is-ci": "1.1.0",
|
||||
"js-yaml": "3.10.0",
|
||||
"js-yaml": "3.11.0",
|
||||
"lazy-val": "1.0.3",
|
||||
"semver": "5.5.0",
|
||||
"source-map-support": "0.5.3",
|
||||
"source-map-support": "0.5.4",
|
||||
"stat-mode": "0.2.2",
|
||||
"temp-file": "3.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
|
||||
"integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==",
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "1.9.1"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz",
|
||||
"integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==",
|
||||
"app-builder-bin": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-1.7.2.tgz",
|
||||
"integrity": "sha512-2uJICLdVnkDqizLZa4HclhBsAWiSf1sEPeKS5+GhuxGaDdWnabXZ4ed9hYQ5u81P3hW3lB+xvxDw2TTinDB9Tw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "3.2.0",
|
||||
"app-builder-bin-linux": "1.7.2",
|
||||
"app-builder-bin-mac": "1.7.2",
|
||||
"app-builder-bin-win": "1.7.2"
|
||||
}
|
||||
},
|
||||
"app-builder-bin-linux": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/app-builder-bin-linux/-/app-builder-bin-linux-1.7.2.tgz",
|
||||
"integrity": "sha512-spoW8f6sqo5aKpoZx+scIPMonSTrh8JtKWM3MuDqBJiXiUCtpVIPez5c4AycGwQnmh167KFjK4pn129o3k+aHQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"app-builder-bin-mac": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/app-builder-bin-mac/-/app-builder-bin-mac-1.7.2.tgz",
|
||||
"integrity": "sha512-GLrQ9r17Hnc8dap2rKJ1N7ZukLBbTN88BSG4EC3xmNeafoWbekuxq3IdJYkZAT/eS1Ig4Q6nRcLI9TfnafwZEQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"app-builder-bin-win": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/app-builder-bin-win/-/app-builder-bin-win-1.7.2.tgz",
|
||||
"integrity": "sha512-/7tvJZas9T5TBM3QUV0xQkRQAyUlsXdtUsqtOg48mgp1ogPqDjs4W2Jr31YhhiUHDdNgamZc655PzWqAEnbZfQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz",
|
||||
"integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "3.2.1",
|
||||
"escape-string-regexp": "1.0.5",
|
||||
"supports-color": "5.2.0"
|
||||
"supports-color": "5.3.0"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
@@ -798,22 +830,6 @@
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"fs-extra-p": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra-p/-/fs-extra-p-4.5.2.tgz",
|
||||
"integrity": "sha512-ZYqFpBdy9w7PsK+vB30j+TnHOyWHm/CJbUq1qqoE8tb71m6qgk5Wa7gp3MYQdlGFxb9vfznF+yD4jcl8l+y91A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bluebird-lst": "1.0.5",
|
||||
"fs-extra": "5.0.0"
|
||||
}
|
||||
},
|
||||
"lazy-val": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.3.tgz",
|
||||
"integrity": "sha512-pjCf3BYk+uv3ZcPzEVM0BFvO9Uw58TmlrU0oG5tTrr9Kcid3+kdKxapH8CjdYmVa2nO5wOoZn2rdvZx2PKj/xg==",
|
||||
"dev": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
|
||||
@@ -827,18 +843,18 @@
|
||||
"dev": true
|
||||
},
|
||||
"source-map-support": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.3.tgz",
|
||||
"integrity": "sha512-eKkTgWYeBOQqFGXRfKabMFdnWepo51vWqEdoeikaEPFiJC7MCU5j2h4+6Q8npkZTeLGbSyecZvRxiSoWl3rh+w==",
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.4.tgz",
|
||||
"integrity": "sha512-PETSPG6BjY1AHs2t64vS2aqAgu6dMIMXJULWFBGbh2Gr8nVLbCFDo6i/RMMvviIQ2h1Z8+5gQhVKSn2je9nmdg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"source-map": "0.6.1"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz",
|
||||
"integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==",
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz",
|
||||
"integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "3.0.0"
|
||||
@@ -866,16 +882,6 @@
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"fs-extra-p": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra-p/-/fs-extra-p-4.5.2.tgz",
|
||||
"integrity": "sha512-ZYqFpBdy9w7PsK+vB30j+TnHOyWHm/CJbUq1qqoE8tb71m6qgk5Wa7gp3MYQdlGFxb9vfznF+yD4jcl8l+y91A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bluebird-lst": "1.0.5",
|
||||
"fs-extra": "5.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -971,9 +977,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"ci-info": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.1.2.tgz",
|
||||
"integrity": "sha512-uTGIPNx/nSpBdsF6xnseRXLLtfr9VLqkz8ZqHXr3Y7b6SftyRxBGjwMtJj1OhNbmlc1wZzLNAlAcvyIiE8a6ZA==",
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.1.3.tgz",
|
||||
"integrity": "sha512-SK/846h/Rcy8q9Z9CAwGBLfCJ6EkjJWdpelWDufQpqVDYq2Wnnv8zlSO6AMQap02jvhVruKKpEtQOufo3pFhLg==",
|
||||
"dev": true
|
||||
},
|
||||
"cli-boxes": {
|
||||
@@ -1173,7 +1179,7 @@
|
||||
"integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lru-cache": "4.1.1",
|
||||
"lru-cache": "4.1.2",
|
||||
"shebang-command": "1.2.0",
|
||||
"which": "1.3.0"
|
||||
}
|
||||
@@ -1282,31 +1288,19 @@
|
||||
}
|
||||
},
|
||||
"dmg-builder": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-4.1.1.tgz",
|
||||
"integrity": "sha512-AhRa1J1coSVIUE2KpmievfIA8WI3G1Rxhf7qJYkiR5XWkJYp+6W1Z7vaCabRwEtEYyeJ0M8EdZnFT2BCYxEYVA==",
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-4.1.2.tgz",
|
||||
"integrity": "sha512-nH7PYrRG9er8sVEOEV9lhngRyZDznViBFVTV/E5p4ZDFy5YZLDHNFWI3m7RKiWnRli5UpOwqLVn0Nxn1vJqsGg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bluebird-lst": "1.0.5",
|
||||
"builder-util": "5.6.0",
|
||||
"electron-builder-lib": "20.2.0",
|
||||
"builder-util": "5.6.5",
|
||||
"electron-builder-lib": "20.5.1",
|
||||
"fs-extra-p": "4.5.2",
|
||||
"iconv-lite": "0.4.19",
|
||||
"js-yaml": "3.10.0",
|
||||
"js-yaml": "3.11.0",
|
||||
"parse-color": "1.0.0",
|
||||
"sanitize-filename": "1.6.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"fs-extra-p": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra-p/-/fs-extra-p-4.5.2.tgz",
|
||||
"integrity": "sha512-ZYqFpBdy9w7PsK+vB30j+TnHOyWHm/CJbUq1qqoE8tb71m6qgk5Wa7gp3MYQdlGFxb9vfznF+yD4jcl8l+y91A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bluebird-lst": "1.0.5",
|
||||
"fs-extra": "5.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dot-prop": {
|
||||
@@ -1363,17 +1357,17 @@
|
||||
}
|
||||
},
|
||||
"electron-builder": {
|
||||
"version": "20.2.0",
|
||||
"resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-20.2.0.tgz",
|
||||
"integrity": "sha512-gl+veD9FqunV5oGwBVhoHlHeHbVeXYiLoMw8/Cv3b91gC7XuXHoZ3oGbgaVGgycjm7suW8O6QXYaFujE8osnfw==",
|
||||
"version": "20.5.1",
|
||||
"resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-20.5.1.tgz",
|
||||
"integrity": "sha512-TBBWDXnqrRjTKAqnBJU/fxSxcwHjnXepTBVUEG02TZPUp7jQEdZjdvZDRvK0jM2xsGrL7q/UUS4jFqe4tRIF6g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bluebird-lst": "1.0.5",
|
||||
"builder-util": "5.6.0",
|
||||
"builder-util": "5.6.5",
|
||||
"builder-util-runtime": "4.0.5",
|
||||
"chalk": "2.3.1",
|
||||
"dmg-builder": "4.1.1",
|
||||
"electron-builder-lib": "20.2.0",
|
||||
"chalk": "2.3.2",
|
||||
"dmg-builder": "4.1.2",
|
||||
"electron-builder-lib": "20.5.1",
|
||||
"electron-download-tf": "4.3.4",
|
||||
"fs-extra-p": "4.5.2",
|
||||
"is-ci": "1.1.0",
|
||||
@@ -1385,23 +1379,23 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
|
||||
"integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==",
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "1.9.1"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz",
|
||||
"integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==",
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz",
|
||||
"integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "3.2.0",
|
||||
"ansi-styles": "3.2.1",
|
||||
"escape-string-regexp": "1.0.5",
|
||||
"supports-color": "5.2.0"
|
||||
"supports-color": "5.3.0"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
@@ -1441,35 +1435,6 @@
|
||||
"universalify": "0.1.1"
|
||||
}
|
||||
},
|
||||
"fs-extra-p": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra-p/-/fs-extra-p-4.5.2.tgz",
|
||||
"integrity": "sha512-ZYqFpBdy9w7PsK+vB30j+TnHOyWHm/CJbUq1qqoE8tb71m6qgk5Wa7gp3MYQdlGFxb9vfznF+yD4jcl8l+y91A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bluebird-lst": "1.0.5",
|
||||
"fs-extra": "5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"fs-extra": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz",
|
||||
"integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "4.1.11",
|
||||
"jsonfile": "4.0.0",
|
||||
"universalify": "0.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"lazy-val": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.3.tgz",
|
||||
"integrity": "sha512-pjCf3BYk+uv3ZcPzEVM0BFvO9Uw58TmlrU0oG5tTrr9Kcid3+kdKxapH8CjdYmVa2nO5wOoZn2rdvZx2PKj/xg==",
|
||||
"dev": true
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||
@@ -1497,9 +1462,9 @@
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz",
|
||||
"integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==",
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz",
|
||||
"integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "3.0.0"
|
||||
@@ -1508,27 +1473,27 @@
|
||||
}
|
||||
},
|
||||
"electron-builder-lib": {
|
||||
"version": "20.2.0",
|
||||
"resolved": "https://registry.npmjs.org/electron-builder-lib/-/electron-builder-lib-20.2.0.tgz",
|
||||
"integrity": "sha512-bHESbb/OjO0F+tyUAj2wFXVDpuXweB5YR94/f7CKqdpd7k2LeYJvy+cYtgtVXt4CJyg5Vs4Kmak2VvDfWxbO/A==",
|
||||
"version": "20.5.1",
|
||||
"resolved": "https://registry.npmjs.org/electron-builder-lib/-/electron-builder-lib-20.5.1.tgz",
|
||||
"integrity": "sha512-YAUu4KHZQNFPdHqnvwOHmYWmqnwiExKuB4fnETZl5jmN3ZUgxCQFqWdwIGQWoAIdxtkQxTervJMt+uJ/wJWbZA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"7zip-bin": "3.1.0",
|
||||
"app-builder-bin": "1.5.0",
|
||||
"app-builder-bin": "1.7.2",
|
||||
"async-exit-hook": "2.0.1",
|
||||
"bluebird-lst": "1.0.5",
|
||||
"builder-util": "5.6.0",
|
||||
"builder-util": "5.6.5",
|
||||
"builder-util-runtime": "4.0.5",
|
||||
"chromium-pickle-js": "0.2.0",
|
||||
"debug": "3.1.0",
|
||||
"ejs": "2.5.7",
|
||||
"electron-osx-sign": "0.4.8",
|
||||
"electron-publish": "20.2.0",
|
||||
"electron-osx-sign": "0.4.10",
|
||||
"electron-publish": "20.5.0",
|
||||
"fs-extra-p": "4.5.2",
|
||||
"hosted-git-info": "2.5.0",
|
||||
"hosted-git-info": "2.6.0",
|
||||
"is-ci": "1.1.0",
|
||||
"isbinaryfile": "3.0.2",
|
||||
"js-yaml": "3.10.0",
|
||||
"js-yaml": "3.11.0",
|
||||
"lazy-val": "1.0.3",
|
||||
"minimatch": "3.0.4",
|
||||
"normalize-package-data": "2.4.0",
|
||||
@@ -1539,6 +1504,38 @@
|
||||
"temp-file": "3.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"app-builder-bin": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-1.7.2.tgz",
|
||||
"integrity": "sha512-2uJICLdVnkDqizLZa4HclhBsAWiSf1sEPeKS5+GhuxGaDdWnabXZ4ed9hYQ5u81P3hW3lB+xvxDw2TTinDB9Tw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"app-builder-bin-linux": "1.7.2",
|
||||
"app-builder-bin-mac": "1.7.2",
|
||||
"app-builder-bin-win": "1.7.2"
|
||||
}
|
||||
},
|
||||
"app-builder-bin-linux": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/app-builder-bin-linux/-/app-builder-bin-linux-1.7.2.tgz",
|
||||
"integrity": "sha512-spoW8f6sqo5aKpoZx+scIPMonSTrh8JtKWM3MuDqBJiXiUCtpVIPez5c4AycGwQnmh167KFjK4pn129o3k+aHQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"app-builder-bin-mac": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/app-builder-bin-mac/-/app-builder-bin-mac-1.7.2.tgz",
|
||||
"integrity": "sha512-GLrQ9r17Hnc8dap2rKJ1N7ZukLBbTN88BSG4EC3xmNeafoWbekuxq3IdJYkZAT/eS1Ig4Q6nRcLI9TfnafwZEQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"app-builder-bin-win": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/app-builder-bin-win/-/app-builder-bin-win-1.7.2.tgz",
|
||||
"integrity": "sha512-/7tvJZas9T5TBM3QUV0xQkRQAyUlsXdtUsqtOg48mgp1ogPqDjs4W2Jr31YhhiUHDdNgamZc655PzWqAEnbZfQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
@@ -1548,20 +1545,10 @@
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"fs-extra-p": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra-p/-/fs-extra-p-4.5.2.tgz",
|
||||
"integrity": "sha512-ZYqFpBdy9w7PsK+vB30j+TnHOyWHm/CJbUq1qqoE8tb71m6qgk5Wa7gp3MYQdlGFxb9vfznF+yD4jcl8l+y91A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bluebird-lst": "1.0.5",
|
||||
"fs-extra": "5.0.0"
|
||||
}
|
||||
},
|
||||
"lazy-val": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.3.tgz",
|
||||
"integrity": "sha512-pjCf3BYk+uv3ZcPzEVM0BFvO9Uw58TmlrU0oG5tTrr9Kcid3+kdKxapH8CjdYmVa2nO5wOoZn2rdvZx2PKj/xg==",
|
||||
"hosted-git-info": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz",
|
||||
"integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==",
|
||||
"dev": true
|
||||
},
|
||||
"semver": {
|
||||
@@ -1653,9 +1640,9 @@
|
||||
"integrity": "sha1-ihBD4ys6HaHD9VPc4oznZCRhZ+M="
|
||||
},
|
||||
"electron-osx-sign": {
|
||||
"version": "0.4.8",
|
||||
"resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.4.8.tgz",
|
||||
"integrity": "sha1-8Ln63e2eHlTsNfqJh3tcbDTHvEA=",
|
||||
"version": "0.4.10",
|
||||
"resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.4.10.tgz",
|
||||
"integrity": "sha1-vk87ibKnWh3F8eckkIGrKSnKOiY=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bluebird": "3.5.1",
|
||||
@@ -1675,56 +1662,40 @@
|
||||
}
|
||||
},
|
||||
"electron-publish": {
|
||||
"version": "20.2.0",
|
||||
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-20.2.0.tgz",
|
||||
"integrity": "sha512-n8MEDVSYXi8ZC8sHJMoSzTrOrV6X+6cWmyQP4M6nh0RZaLcyPa/txWuHDeRNysvhOKJvgQJrf09Fuc+CMSY6zg==",
|
||||
"version": "20.5.0",
|
||||
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-20.5.0.tgz",
|
||||
"integrity": "sha512-BejALjAMG0QbjJNjN66pruwhWt07Iy86VBDxHWXO9IoupYykCEyFdy20jjl5rNTpfnojvynqZyj8QpRkNjJBZg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bluebird-lst": "1.0.5",
|
||||
"builder-util": "5.6.0",
|
||||
"builder-util": "5.6.5",
|
||||
"builder-util-runtime": "4.0.5",
|
||||
"chalk": "2.3.1",
|
||||
"chalk": "2.3.2",
|
||||
"fs-extra-p": "4.5.2",
|
||||
"lazy-val": "1.0.3",
|
||||
"mime": "2.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
|
||||
"integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==",
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "1.9.1"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz",
|
||||
"integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==",
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz",
|
||||
"integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "3.2.0",
|
||||
"ansi-styles": "3.2.1",
|
||||
"escape-string-regexp": "1.0.5",
|
||||
"supports-color": "5.2.0"
|
||||
"supports-color": "5.3.0"
|
||||
}
|
||||
},
|
||||
"fs-extra-p": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra-p/-/fs-extra-p-4.5.2.tgz",
|
||||
"integrity": "sha512-ZYqFpBdy9w7PsK+vB30j+TnHOyWHm/CJbUq1qqoE8tb71m6qgk5Wa7gp3MYQdlGFxb9vfznF+yD4jcl8l+y91A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bluebird-lst": "1.0.5",
|
||||
"fs-extra": "5.0.0"
|
||||
}
|
||||
},
|
||||
"lazy-val": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.3.tgz",
|
||||
"integrity": "sha512-pjCf3BYk+uv3ZcPzEVM0BFvO9Uw58TmlrU0oG5tTrr9Kcid3+kdKxapH8CjdYmVa2nO5wOoZn2rdvZx2PKj/xg==",
|
||||
"dev": true
|
||||
},
|
||||
"mime": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.2.0.tgz",
|
||||
@@ -1732,9 +1703,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz",
|
||||
"integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==",
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz",
|
||||
"integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "3.0.0"
|
||||
@@ -2056,6 +2027,16 @@
|
||||
"universalify": "0.1.1"
|
||||
}
|
||||
},
|
||||
"fs-extra-p": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra-p/-/fs-extra-p-4.5.2.tgz",
|
||||
"integrity": "sha512-ZYqFpBdy9w7PsK+vB30j+TnHOyWHm/CJbUq1qqoE8tb71m6qgk5Wa7gp3MYQdlGFxb9vfznF+yD4jcl8l+y91A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bluebird-lst": "1.0.5",
|
||||
"fs-extra": "5.0.0"
|
||||
}
|
||||
},
|
||||
"fs-minipass": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz",
|
||||
@@ -3329,7 +3310,7 @@
|
||||
"integrity": "sha512-c7TnwxLePuqIlxHgr7xtxzycJPegNHFuIrBkwbf8hc58//+Op1CqFkyS+xnIMkwn9UsJIwc174BIjkyBmSpjKg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ci-info": "1.1.2"
|
||||
"ci-info": "1.1.3"
|
||||
}
|
||||
},
|
||||
"is-dotfile": {
|
||||
@@ -3545,9 +3526,9 @@
|
||||
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "3.10.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz",
|
||||
"integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==",
|
||||
"version": "3.11.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz",
|
||||
"integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"argparse": "1.0.9",
|
||||
@@ -3651,6 +3632,12 @@
|
||||
"package-json": "4.0.1"
|
||||
}
|
||||
},
|
||||
"lazy-val": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.3.tgz",
|
||||
"integrity": "sha512-pjCf3BYk+uv3ZcPzEVM0BFvO9Uw58TmlrU0oG5tTrr9Kcid3+kdKxapH8CjdYmVa2nO5wOoZn2rdvZx2PKj/xg==",
|
||||
"dev": true
|
||||
},
|
||||
"lcid": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
|
||||
@@ -3748,9 +3735,9 @@
|
||||
"integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY="
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
|
||||
"integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==",
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.2.tgz",
|
||||
"integrity": "sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"pseudomap": "1.0.2",
|
||||
@@ -3780,6 +3767,11 @@
|
||||
"integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
|
||||
"dev": true
|
||||
},
|
||||
"mark.js": {
|
||||
"version": "8.11.1",
|
||||
"resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz",
|
||||
"integrity": "sha1-GA8fnr74sOY45BZq1S24eb6y/8U="
|
||||
},
|
||||
"markdown-it": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-8.4.0.tgz",
|
||||
@@ -4553,38 +4545,9 @@
|
||||
"dotenv": "5.0.1",
|
||||
"dotenv-expand": "4.2.0",
|
||||
"fs-extra-p": "4.5.2",
|
||||
"js-yaml": "3.10.0",
|
||||
"js-yaml": "3.11.0",
|
||||
"json5": "0.5.1",
|
||||
"lazy-val": "1.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"ajv": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.2.0.tgz",
|
||||
"integrity": "sha1-r6wpW7qgFSRJ5SJ0LkVHwa6TKNI=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fast-deep-equal": "1.0.0",
|
||||
"fast-json-stable-stringify": "2.0.0",
|
||||
"json-schema-traverse": "0.3.1"
|
||||
}
|
||||
},
|
||||
"fs-extra-p": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra-p/-/fs-extra-p-4.5.2.tgz",
|
||||
"integrity": "sha512-ZYqFpBdy9w7PsK+vB30j+TnHOyWHm/CJbUq1qqoE8tb71m6qgk5Wa7gp3MYQdlGFxb9vfznF+yD4jcl8l+y91A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bluebird-lst": "1.0.5",
|
||||
"fs-extra": "5.0.0"
|
||||
}
|
||||
},
|
||||
"lazy-val": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.3.tgz",
|
||||
"integrity": "sha512-pjCf3BYk+uv3ZcPzEVM0BFvO9Uw58TmlrU0oG5tTrr9Kcid3+kdKxapH8CjdYmVa2nO5wOoZn2rdvZx2PKj/xg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"read-pkg": {
|
||||
@@ -5859,24 +5822,6 @@
|
||||
"bluebird-lst": "1.0.5",
|
||||
"fs-extra-p": "4.5.2",
|
||||
"lazy-val": "1.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"fs-extra-p": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra-p/-/fs-extra-p-4.5.2.tgz",
|
||||
"integrity": "sha512-ZYqFpBdy9w7PsK+vB30j+TnHOyWHm/CJbUq1qqoE8tb71m6qgk5Wa7gp3MYQdlGFxb9vfznF+yD4jcl8l+y91A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bluebird-lst": "1.0.5",
|
||||
"fs-extra": "5.0.0"
|
||||
}
|
||||
},
|
||||
"lazy-val": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.3.tgz",
|
||||
"integrity": "sha512-pjCf3BYk+uv3ZcPzEVM0BFvO9Uw58TmlrU0oG5tTrr9Kcid3+kdKxapH8CjdYmVa2nO5wOoZn2rdvZx2PKj/xg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"term-size": {
|
||||
@@ -6032,7 +5977,7 @@
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"boxen": "1.3.0",
|
||||
"chalk": "2.3.1",
|
||||
"chalk": "2.3.2",
|
||||
"configstore": "3.1.1",
|
||||
"import-lazy": "2.1.0",
|
||||
"is-installed-globally": "0.1.0",
|
||||
@@ -6043,29 +5988,29 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
|
||||
"integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==",
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "1.9.1"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz",
|
||||
"integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==",
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz",
|
||||
"integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "3.2.0",
|
||||
"ansi-styles": "3.2.1",
|
||||
"escape-string-regexp": "1.0.5",
|
||||
"supports-color": "5.2.0"
|
||||
"supports-color": "5.3.0"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz",
|
||||
"integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==",
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz",
|
||||
"integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "3.0.0"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Joplin",
|
||||
"version": "1.0.73",
|
||||
"version": "1.0.83",
|
||||
"description": "Joplin for Desktop",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
@@ -48,7 +48,7 @@
|
||||
"babel-cli": "^6.26.0",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"electron": "^1.7.11",
|
||||
"electron-builder": "^20.2.0"
|
||||
"electron-builder": "^20.5.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"7zip-bin-mac": "^1.0.1",
|
||||
@@ -71,6 +71,7 @@
|
||||
"katex": "^0.9.0-beta1",
|
||||
"levenshtein": "^1.0.5",
|
||||
"lodash": "^4.17.4",
|
||||
"mark.js": "^8.11.1",
|
||||
"markdown-it": "^8.4.0",
|
||||
"markdown-it-katex": "^2.0.3",
|
||||
"md5": "^2.2.1",
|
||||
|
@@ -36,12 +36,14 @@ table td, table th {
|
||||
|
||||
.side-bar .list-item:hover,
|
||||
.side-bar .synchronize-button:hover {
|
||||
background-color: #453E53;
|
||||
/*background-color: #453E53;*/
|
||||
background-color: #01427B;
|
||||
}
|
||||
|
||||
.side-bar .list-item:active,
|
||||
.side-bar .synchronize-button:active {
|
||||
background-color: #564B6C;
|
||||
/*background-color: #564B6C;*/
|
||||
background-color: #0465BB;
|
||||
}
|
||||
|
||||
.editor-toolbar .button:not(.disabled):hover,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
|
||||
const globalStyle = {
|
||||
fontSize: 12 * Setting.value('style.zoom')/100,
|
||||
fontSize: 12 * Setting.value('style.zoom') / 100,
|
||||
fontFamily: 'sans-serif',
|
||||
margin: 15, // No text and no interactive component should be within this margin
|
||||
itemMarginTop: 10,
|
||||
@@ -21,9 +21,11 @@ const globalStyle = {
|
||||
buttonMinHeight: 30,
|
||||
textAreaLineHeight: 17,
|
||||
|
||||
backgroundColor2: "#2B2634",
|
||||
//backgroundColor2: "#2B2634",
|
||||
backgroundColor2: "#162B3D",
|
||||
color2: "#ffffff",
|
||||
selectedColor2: "#5A4D70",
|
||||
//selectedColor2: "#5A4D70",
|
||||
selectedColor2: "#0269C2",
|
||||
colorError2: "#ff6c6c",
|
||||
|
||||
warningBackgroundColor: "#FFD08D",
|
||||
@@ -60,6 +62,7 @@ globalStyle.icon = {
|
||||
globalStyle.lineInput = {
|
||||
color: globalStyle.color,
|
||||
backgroundColor: globalStyle.backgroundColor,
|
||||
fontFamily: globalStyle.fontFamily,
|
||||
};
|
||||
|
||||
globalStyle.textStyle = {
|
||||
|
139
README.md
139
README.md
@@ -1,14 +1,16 @@
|
||||
# Joplin
|
||||
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=E8JMYD2LQ8MMA&lc=GB&item_name=Joplin+Development¤cy_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted) [](https://joplin.cozic.net/donate/#bitcoin) [](https://travis-ci.org/laurent22/joplin) [](https://ci.appveyor.com/project/laurent22/joplin)
|
||||
|
||||
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.
|
||||
|
||||
The notes can be [synchronised](#synchronisation) with various targets including [Nextcloud](https://nextcloud.com/), the file system (for example with a network directory) or with Microsoft OneDrive. When synchronising the notes, notebooks, tags and other metadata are saved to plain text files which can be easily inspected, backed up and moved around.
|
||||
The notes can be [synchronised](#synchronisation) with various cloud services including [Nextcloud](https://nextcloud.com/), Dropbox, OneDrive or the file system (for example with a network directory). When synchronising the notes, notebooks, tags and other metadata are saved to plain text files which can be easily inspected, backed up and moved around.
|
||||
|
||||
The UI of the terminal client is built on top of the great [terminal-kit](https://github.com/cronvel/terminal-kit) library, the desktop client using [Electron](https://electronjs.org/), and the Android client front end is done using [React Native](https://facebook.github.io/react-native/).
|
||||
|
||||
<div class="top-screenshot"><img src="https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/AllClients.jpg" style="max-width: 100%; max-height: 35em;"></div>
|
||||
<div class="top-screenshot"><img src="https://joplin.cozic.net/images/AllClients.jpg" style="max-width: 100%; max-height: 35em;"></div>
|
||||
|
||||
# Installation
|
||||
|
||||
@@ -18,16 +20,16 @@ Three types of applications are available: for the **desktop** (Windows, macOS a
|
||||
|
||||
Operating System | Download
|
||||
-----------------|--------
|
||||
Windows | <a href='https://github.com/laurent22/joplin/releases/download/v1.0.70/Joplin-Setup-1.0.70.exe'><img alt='Get it on Windows' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeWindows.png'/></a>
|
||||
macOS | <a href='https://github.com/laurent22/joplin/releases/download/v1.0.70/Joplin-1.0.70.dmg'><img alt='Get it on macOS' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeMacOS.png'/></a>
|
||||
Linux | <a href='https://github.com/laurent22/joplin/releases/download/v1.0.70/Joplin-1.0.70-x86_64.AppImage'><img alt='Get it on Linux' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeLinux.png'/></a>
|
||||
Windows (64-bit only) | <a href='https://github.com/laurent22/joplin/releases/download/v1.0.81/Joplin-Setup-1.0.81.exe'><img alt='Get it on Windows' height="40px" src='https://joplin.cozic.net/images/BadgeWindows.png'/></a>
|
||||
macOS | <a href='https://github.com/laurent22/joplin/releases/download/v1.0.81/Joplin-1.0.81.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.81/Joplin-1.0.81-x86_64.AppImage'><img alt='Get it on Linux' height="40px" src='https://joplin.cozic.net/images/BadgeLinux.png'/></a>
|
||||
|
||||
## Mobile applications
|
||||
|
||||
Operating System | Download | Alt. Download
|
||||
-----------------|----------|----------------
|
||||
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeAndroid.png'/></a> | or [Download APK File](https://github.com/laurent22/joplin-android/releases/download/android-v1.0.107/joplin-v1.0.107.apk)
|
||||
iOS | <a href='https://itunes.apple.com/us/app/joplin/id1315599797'><img alt='Get it on the App Store' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeIOS.png'/></a> | -
|
||||
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.116/joplin-v1.0.116.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
|
||||
|
||||
@@ -46,13 +48,35 @@ By default, the application binary will be installed under `~/.joplin-bin`. You
|
||||
|
||||
To start it, type `joplin`.
|
||||
|
||||
For usage information, please refer to the full [Joplin Terminal Application Documentation](http://joplin.cozic.net/terminal).
|
||||
For usage information, please refer to the full [Joplin Terminal Application Documentation](https://joplin.cozic.net/terminal).
|
||||
|
||||
<!-- TOC -->
|
||||
# Table of contents
|
||||
|
||||
- Applications
|
||||
|
||||
- [Desktop application](https://github.com/laurent22/joplin/blob/master/readme/desktop.md)
|
||||
- [Mobile applications](https://github.com/laurent22/joplin/blob/master/readme/mobile.md)
|
||||
- [Terminal application](https://github.com/laurent22/joplin/blob/master/readme/terminal.md)
|
||||
|
||||
- Support
|
||||
|
||||
- [How to enable end-to-end encryption](https://github.com/laurent22/joplin/blob/master/readme/e2ee.md)
|
||||
- [End-to-end encryption spec](https://github.com/laurent22/joplin/blob/master/readme/spec.md)
|
||||
- [How to enable debug mode](https://github.com/laurent22/joplin/blob/master/readme/debugging.md)
|
||||
|
||||
- About
|
||||
|
||||
- [Changelog](https://github.com/laurent22/joplin/blob/master/readme/changelog.md)
|
||||
- [Stats](https://github.com/laurent22/joplin/blob/master/readme/stats.md)
|
||||
- [Donate](https://github.com/laurent22/joplin/blob/master/readme/donate.md)
|
||||
<!-- TOC -->
|
||||
|
||||
# Features
|
||||
|
||||
- Desktop, mobile and terminal applications.
|
||||
- End To End Encryption (E2EE)
|
||||
- Synchronisation with various services, including NextCloud, WebDAV and OneDrive. Dropbox is planned.
|
||||
- Synchronisation with various services, including NextCloud, Dropbox, WebDAV and OneDrive.
|
||||
- Import Enex files (Evernote export format) and Markdown files.
|
||||
- Export JEX files (Joplin Export format) and raw files.
|
||||
- Support notes, to-dos, tags and notebooks.
|
||||
@@ -79,7 +103,7 @@ To import Evernote data, first export your Evernote notebooks to ENEX files as d
|
||||
|
||||
On the **desktop application**, open File > Import > ENEX and select your file. The notes will be imported into a new separate notebook. If needed they can then be moved to a different notebook, or the notebook can be renamed, etc.
|
||||
|
||||
On the **terminal application**, in [command-line mode](/terminal#command-line-mode), type `import /path/to/file.enex`. This will import the notes into a new notebook named after the filename.
|
||||
On the **terminal application**, in [command-line mode](https://joplin.cozic.net/terminal#command-line-mode), type `import /path/to/file.enex`. This will import the notes into a new notebook named after the filename.
|
||||
|
||||
## Importing from Markdown files
|
||||
|
||||
@@ -87,7 +111,7 @@ Joplin can import notes from plain Markdown file. You can either import a comple
|
||||
|
||||
On the **desktop application**, open File > Import > MD and select your Markdown file or directory.
|
||||
|
||||
On the **terminal application**, in [command-line mode](/terminal#command-line-mode), type `import --format md /path/to/file.md` or `import --format md /path/to/directory/`.
|
||||
On the **terminal application**, in [command-line mode](https://joplin.cozic.net/terminal#command-line-mode), type `import --format md /path/to/file.md` or `import --format md /path/to/directory/`.
|
||||
|
||||
## Importing from other applications
|
||||
|
||||
@@ -95,6 +119,7 @@ In general the way to import notes from any application into Joplin is to conver
|
||||
|
||||
* Standard Notes: Please see [this tutorial](https://programadorwebvalencia.com/migrate-notes-from-standard-notes-to-joplin/)
|
||||
* Tomboy Notes: Export the notes to ENEX files [as described here](https://askubuntu.com/questions/243691/how-can-i-export-my-tomboy-notes-into-evernote/608551) for example, and import these ENEX files into Joplin.
|
||||
* OneNote: First [import the notes from OneNote into Evernote](https://discussion.evernote.com/topic/107736-is-there-a-way-to-import-from-onenote-into-evernote-on-the-mac/). Then export the ENEX file from Evernote and import it into Joplin.
|
||||
|
||||
# Exporting
|
||||
|
||||
@@ -102,31 +127,44 @@ Joplin can export to the JEX format (Joplin Export file), which is a tar file th
|
||||
|
||||
# Synchronisation
|
||||
|
||||
One of the goals of Joplin was to avoid being tied to any particular company or service, whether it is Evernote, Google or Microsoft. As such the synchronisation is designed without any hard dependency to any particular service. Most of the synchronisation process is done at an abstract level and access to external services, such as Nextcloud or OneDrive, is done via lightweight drivers. It is easy to support new services by creating simple drivers that provide a filesystem-like interface, i.e. the ability to read, write, delete and list items. It is also simple to switch from one service to another or to even sync to multiple services at once. Each note, notebook, tags, as well as the relation between items is transmitted as plain text files during synchronisation, which means the data can also be moved to a different application, can be easily backed up, inspected, etc.
|
||||
One of the goals of Joplin was to avoid being tied to any particular company or service, whether it is Evernote, Google or Microsoft. As such the synchronisation is designed without any hard dependency to any particular service. Most of the synchronisation process is done at an abstract level and access to external services, such as Nextcloud or Dropbox, is done via lightweight drivers. It is easy to support new services by creating simple drivers that provide a filesystem-like interface, i.e. the ability to read, write, delete and list items. It is also simple to switch from one service to another or to even sync to multiple services at once. Each note, notebook, tags, as well as the relation between items is transmitted as plain text files during synchronisation, which means the data can also be moved to a different application, can be easily backed up, inspected, etc.
|
||||
|
||||
Currently, synchronisation is possible with Nextcloud and OneDrive (by default) or the local filesystem. A Dropbox one will also be available once [this React Native bug](https://github.com/facebook/react-native/issues/14445) is fixed. To setup synchronisation please follow the instructions below. After that, the application will synchronise in the background whenever it is running, or you can click on "Synchronise" to start a synchronisation manually.
|
||||
Currently, synchronisation is possible with Nextcloud, Dropbox (by default) or the local filesystem. To setup synchronisation please follow the instructions below. After that, the application will synchronise in the background whenever it is running, or you can click on "Synchronise" to start a synchronisation manually.
|
||||
|
||||
## Nextcloud synchronisation
|
||||
|
||||
On the **desktop application** or **mobile application**, go to the config screen and select Nextcloud as the synchronisation target. Then input [the WebDAV URL](https://docs.nextcloud.com/server/9/user_manual/files/access_webdav.html), this is normally `https://example.com/nextcloud/remote.php/dav/files/USERNAME/Joplin` (make sure to create the "Joplin" directory in Nextcloud and to replace USERNAME by your Nextcloud username), and set the username and password.
|
||||
<img src="https://joplin.cozic.net/images/nextcloud-logo-background.png" width="100" align="left"> <a href="https://nextcloud.com/">Nextcloud</a> is a self-hosted, private cloud solution. It can store documents, images and videos but also calendars, passwords and countless other things and can sync them to your laptop or phone. As you can host your own Nextcloud server, you own both the data on your device and infrastructure used for synchronisation. As such it is a good fit for Joplin. The platform is also well supported and with a strong community, so it is likely to be around for a while - since it's open source anyway, it is not a service that can be closed, it can exist on a server for as long as one chooses.
|
||||
|
||||
On the **desktop application** or **mobile application**, go to the config screen and select Nextcloud as the synchronisation target. Then input the WebDAV URL (to get it, click on Settings in the bottom left corner of the page, in Nextcloud), this is normally `https://example.com/nextcloud/remote.php/webdav/Joplin` (**make sure to create the "Joplin" directory in Nextcloud**), and set the username and password. If it does not work, please [see this explanation](https://github.com/laurent22/joplin/issues/61#issuecomment-373282608) for more details.
|
||||
|
||||
On the **terminal application**, you will need to set the `sync.target` config variable and all the `sync.5.path`, `sync.5.username` and `sync.5.password` config variables to, respectively the Nextcloud WebDAV URL, your username and your password. This can be done from the command line mode using:
|
||||
|
||||
:config sync.5.path https://example.com/nextcloud/remote.php/dav/files/USERNAME/Joplin
|
||||
:config sync.5.username YOUR_USERNAME
|
||||
:config sync.5.password YOUR_PASSWORD
|
||||
:config sync.5.path https://example.com/nextcloud/remote.php/dav/files/USERNAME/Joplin
|
||||
:config sync.5.username YOUR_USERNAME
|
||||
:config sync.5.password YOUR_PASSWORD
|
||||
:config sync.target 5
|
||||
|
||||
If synchronisation does not work, please consult the logs in the app profile directory - it is often due to a misconfigured URL or password. The log should indicate what the exact issue is.
|
||||
|
||||
## 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.
|
||||
|
||||
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.
|
||||
|
||||
On the **terminal application**, to initiate the synchronisation process, type `:sync`. You will be asked to follow a link to authorise the application. It is possible to also synchronise outside of the user interface by typing `joplin sync` from the terminal. This can be used to setup a cron script to synchronise at regular interval. For example, this would do it every 30 minutes:
|
||||
|
||||
*/30 * * * * /path/to/joplin sync
|
||||
|
||||
## WebDAV synchronisation
|
||||
|
||||
Select the "WebDAV" synchronisation target and follow the same instructions as for Nextcloud above.
|
||||
|
||||
WebDAV-compatible services that are known to work with Joplin:
|
||||
WebDAV-compatible services that are known to work with Joplin.
|
||||
|
||||
- [Box.com](https://www.box.com/)
|
||||
- [DriveHQ](https://www.drivehq.com)
|
||||
- [HiDrive](https://www.strato.fr/stockage-en-ligne/) from Strato. [Setup help](https://github.com/laurent22/joplin/issues/309)
|
||||
- [OwnCloud](https://owncloud.org/)
|
||||
- [Seafile](https://www.seafile.com/)
|
||||
- [Stack](https://www.transip.nl/stack/)
|
||||
@@ -136,17 +174,15 @@ WebDAV-compatible services that are known to work with Joplin:
|
||||
|
||||
When syncing with OneDrive, Joplin creates a sub-directory in OneDrive, in /Apps/Joplin and read/write the notes and notebooks from it. The application does not have access to anything outside this directory.
|
||||
|
||||
On the **desktop application** or **mobile application**, select "OneDrive" 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. You will be asked to login to OneDrive to authorise the application (simply input your Microsoft credentials - you do not need to register with OneDrive).
|
||||
On the **desktop application** or **mobile application**, select "OneDrive" as the synchronisation target in the config screen. Then, to initiate the synchronisation process, click on the "Synchronise" button in the sidebar and follow the instructions.
|
||||
|
||||
On the **terminal application**, to initiate the synchronisation process, type `:sync`. You will be asked to follow a link to authorise the application (simply input your Microsoft credentials - you do not need to register with OneDrive). It is possible to also synchronise outside of the user interface by typing `joplin sync` from the terminal. This can be used to setup a cron script to synchronise at regular interval. For example, this would do it every 30 minutes:
|
||||
|
||||
*/30 * * * * /path/to/joplin sync
|
||||
On the **terminal application**, to initiate the synchronisation process, type `:sync`. You will be asked to follow a link to authorise the application (simply input your Microsoft credentials - you do not need to register with OneDrive).
|
||||
|
||||
# Encryption
|
||||
|
||||
Joplin supports end-to-end encryption (E2EE) on all the applications. E2EE is a system where only the owner of the notes, notebooks, tags or resources can read them. It prevents potential eavesdroppers - including telecom providers, internet providers, and even the developers of Joplin from being able to access the data. Please see the [End-To-End Encryption Tutorial](http://joplin.cozic.net/help/e2ee) for more information about this feature and how to enable it.
|
||||
Joplin supports end-to-end encryption (E2EE) on all the applications. E2EE is a system where only the owner of the notes, notebooks, tags or resources can read them. It prevents potential eavesdroppers - including telecom providers, internet providers, and even the developers of Joplin from being able to access the data. Please see the [End-To-End Encryption Tutorial](https://joplin.cozic.net/e2ee) for more information about this feature and how to enable it.
|
||||
|
||||
For a more technical description, mostly relevant for development or to review the method being used, please see the [Encryption specification](http://joplin.cozic.net/help/spec).
|
||||
For a more technical description, mostly relevant for development or to review the method being used, please see the [Encryption specification](https://joplin.cozic.net/spec).
|
||||
|
||||
# Attachments / Resources
|
||||
|
||||
@@ -174,29 +210,29 @@ Joplin uses and renders [Github-flavoured Markdown](https://github.com/adam-p/ma
|
||||
|
||||
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
|
||||
$$
|
||||
$$
|
||||
EXPRESSION
|
||||
$$
|
||||
|
||||
For example:
|
||||
|
||||
$$
|
||||
f(x) = \int_{-\infty}^\infty
|
||||
\hat f(\xi)\,e^{2 \pi i \xi x}
|
||||
\,d\xi
|
||||
$$
|
||||
$$
|
||||
f(x) = \int_{-\infty}^\infty
|
||||
\hat f(\xi)\,e^{2 \pi i \xi x}
|
||||
\,d\xi
|
||||
$$
|
||||
|
||||
Here is an example with the Markdown and rendered result side by side:
|
||||
|
||||
<img src="https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/Katex.png" style="max-width: 100%; max-height: 35em;">
|
||||
<img src="https://joplin.cozic.net/images/Katex.png" style="max-width: 100%; max-height: 35em;">
|
||||
|
||||
## Checkboxes
|
||||
|
||||
Checkboxes can be added like so:
|
||||
|
||||
- [ ] Milk
|
||||
- [ ] Rice
|
||||
- [ ] Eggs
|
||||
- [ ] Milk
|
||||
- [ ] Rice
|
||||
- [ ] Eggs
|
||||
|
||||
The checkboxes can then be ticked in the mobile and desktop applications.
|
||||
|
||||
@@ -204,7 +240,7 @@ The checkboxes can then be ticked in the mobile and desktop applications.
|
||||
|
||||
Donations to Joplin support the development of the project. Developing quality applications mostly takes time, but there are also some expenses, such as digital certificates to sign the applications, app store fees, hosting, etc. Most of all, your donation will make it possible to keep up the current development standard.
|
||||
|
||||
Please see the [donation page](http://joplin.cozic.net/donate/) for information on how to support the development of Joplin.
|
||||
Please see the [donation page](https://joplin.cozic.net/donate/) for information on how to support the development of Joplin.
|
||||
|
||||
# Contributing
|
||||
|
||||
@@ -228,24 +264,27 @@ Current translations:
|
||||
<!-- LOCALE-TABLE-AUTO-GENERATED -->
|
||||
| Language | Po File | Last translator | Percent done
|
||||
---|---|---|---|---
|
||||
 | Basque | [eu](https://github.com/laurent22/joplin/blob/master/CliClient/locales/eu.po) | juan.abasolo@ehu.eus | 80%
|
||||
 | Croatian | [hr_HR](https://github.com/laurent22/joplin/blob/master/CliClient/locales/hr_HR.po) | Hrvoje Mandić <trbuhom@net.hr> | 65%
|
||||
 | Deutsch | [de_DE](https://github.com/laurent22/joplin/blob/master/CliClient/locales/de_DE.po) | Tobias Strobel <git@strobeltobias.de> | 97%
|
||||
 | English | [en_GB](https://github.com/laurent22/joplin/blob/master/CliClient/locales/en_GB.po) | | 100%
|
||||
 | Español | [es_ES](https://github.com/laurent22/joplin/blob/master/CliClient/locales/es_ES.po) | Fernando Martín <f@mrtn.es> | 100%
|
||||
 | Français | [fr_FR](https://github.com/laurent22/joplin/blob/master/CliClient/locales/fr_FR.po) | Laurent Cozic | 100%
|
||||
 | Italiano | [it_IT](https://github.com/laurent22/joplin/blob/master/CliClient/locales/it_IT.po) | | 66%
|
||||
 | Nederlands | [nl_BE](https://github.com/laurent22/joplin/blob/master/CliClient/locales/nl_BE.po) | | 80%
|
||||
 | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/master/CliClient/locales/pt_BR.po) | Renato Nunes Bastos <rnbastos@gmail.com> | 98%
|
||||
 | Русский | [ru_RU](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ru_RU.po) | Artyom Karlov <artyom.karlov@gmail.com> | 100%
|
||||
 | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/master/CliClient/locales/zh_CN.po) | RCJacH <RCJacH@outlook.com> | 66%
|
||||
 | 日本語 | [ja_JP](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ja_JP.po) | | 65%
|
||||
 | Basque | [eu](https://github.com/laurent22/joplin/blob/master/CliClient/locales/eu.po) | juan.abasolo@ehu.eus | 77%
|
||||
 | Croatian | [hr_HR](https://github.com/laurent22/joplin/blob/master/CliClient/locales/hr_HR.po) | Hrvoje Mandić <trbuhom@net.hr> | 62%
|
||||
 | Czech | [cs_CZ](https://github.com/laurent22/joplin/blob/master/CliClient/locales/cs_CZ.po) | Lukas Helebrandt <lukas@aiya.cz> | 97%
|
||||
 | Dansk | [da_DK](https://github.com/laurent22/joplin/blob/master/CliClient/locales/da_DK.po) | | 96%
|
||||
 | Deutsch | [de_DE](https://github.com/laurent22/joplin/blob/master/CliClient/locales/de_DE.po) | Tobias Grasse <mail@tobias-grasse.net> | 96%
|
||||
 | English | [en_GB](https://github.com/laurent22/joplin/blob/master/CliClient/locales/en_GB.po) | | 100%
|
||||
 | Español | [es_ES](https://github.com/laurent22/joplin/blob/master/CliClient/locales/es_ES.po) | Fernando Martín <f@mrtn.es> | 96%
|
||||
 | Français | [fr_FR](https://github.com/laurent22/joplin/blob/master/CliClient/locales/fr_FR.po) | Laurent Cozic | 100%
|
||||
 | Galician | [gl_ES](https://github.com/laurent22/joplin/blob/master/CliClient/locales/gl_ES.po) | José Antonio Martínez <facemoshistoria@gmail.com> | 100%
|
||||
 | Italiano | [it_IT](https://github.com/laurent22/joplin/blob/master/CliClient/locales/it_IT.po) | | 64%
|
||||
 | Nederlands | [nl_BE](https://github.com/laurent22/joplin/blob/master/CliClient/locales/nl_BE.po) | | 77%
|
||||
 | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/master/CliClient/locales/pt_BR.po) | Renato Nunes Bastos <rnbastos@gmail.com> | 95%
|
||||
 | Русский | [ru_RU](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ru_RU.po) | Artyom Karlov <artyom.karlov@gmail.com> | 96%
|
||||
 | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/master/CliClient/locales/zh_CN.po) | RCJacH <RCJacH@outlook.com> | 64%
|
||||
 | 日本語 | [ja_JP](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ja_JP.po) | | 62%
|
||||
<!-- LOCALE-TABLE-AUTO-GENERATED -->
|
||||
|
||||
# Known bugs
|
||||
|
||||
- Non-alphabetical characters such as Chinese or Arabic might create glitches in the terminal on Windows. This is a limitation of the current Windows console.
|
||||
- While the mobile can sync and load tags, it is not currently possible to create new ones. The desktop and terminal apps can create, delete and edit tags.
|
||||
- It is only possible to upload files of up to 4MB to OneDrive due to a limitation of [the API](https://docs.microsoft.com/en-gb/onedrive/developer/rest-api/api/driveitem_put_content) being currently used. There is currently no plan to support OneDrive "large file" API.
|
||||
|
||||
# License
|
||||
|
||||
|
@@ -90,8 +90,8 @@ android {
|
||||
applicationId "net.cozic.joplin"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 22
|
||||
versionCode 2097285
|
||||
versionName "1.0.107"
|
||||
versionCode 2097294
|
||||
versionName "1.0.116"
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", "x86"
|
||||
}
|
||||
|
@@ -17,11 +17,11 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.13</string>
|
||||
<string>10.0.20</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>13</string>
|
||||
<string>20</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
@@ -67,6 +67,13 @@
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
|
@@ -29,14 +29,17 @@ const SyncTargetOneDrive = require('lib/SyncTargetOneDrive.js');
|
||||
const SyncTargetOneDriveDev = require('lib/SyncTargetOneDriveDev.js');
|
||||
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 DecryptionWorker = require('lib/services/DecryptionWorker');
|
||||
const BaseService = require('lib/services/BaseService');
|
||||
|
||||
SyncTargetRegistry.addClass(SyncTargetFilesystem);
|
||||
SyncTargetRegistry.addClass(SyncTargetOneDrive);
|
||||
SyncTargetRegistry.addClass(SyncTargetOneDriveDev);
|
||||
SyncTargetRegistry.addClass(SyncTargetNextcloud);
|
||||
SyncTargetRegistry.addClass(SyncTargetWebDAV);
|
||||
SyncTargetRegistry.addClass(SyncTargetDropbox);
|
||||
|
||||
class BaseApplication {
|
||||
|
||||
@@ -259,7 +262,7 @@ class BaseApplication {
|
||||
const newState = store.getState();
|
||||
let refreshNotes = false;
|
||||
|
||||
if (action.type == 'FOLDER_SELECT' || action.type === 'FOLDER_DELETE') {
|
||||
if (action.type == 'FOLDER_SELECT' || action.type === 'FOLDER_DELETE' || (action.type === 'SEARCH_UPDATE' && newState.notesParentType === 'Folder')) {
|
||||
Setting.setValue('activeFolderId', newState.selectedFolderId);
|
||||
this.currentFolder_ = newState.selectedFolderId ? await Folder.load(newState.selectedFolderId) : null;
|
||||
refreshNotes = true;
|
||||
@@ -420,12 +423,19 @@ class BaseApplication {
|
||||
if (Setting.value('firstStart')) {
|
||||
const locale = shim.detectAndSetLocale(Setting);
|
||||
reg.logger().info('First start: detected locale as ' + locale);
|
||||
if (Setting.value('env') === 'dev') Setting.setValue('sync.target', SyncTargetRegistry.nameToId('onedrive_dev'));
|
||||
Setting.setValue('firstStart', 0)
|
||||
|
||||
if (Setting.value('env') === 'dev') {
|
||||
Setting.setValue('showTrayIcon', 0);
|
||||
Setting.setValue('autoUpdateEnabled', 0);
|
||||
Setting.setValue('sync.interval', 3600);
|
||||
}
|
||||
|
||||
Setting.setValue('firstStart', 0);
|
||||
} else {
|
||||
setLocale(Setting.value('locale'));
|
||||
}
|
||||
|
||||
BaseService.logger_ = this.logger_;
|
||||
EncryptionService.instance().setLogger(this.logger_);
|
||||
BaseItem.encryptionService_ = EncryptionService.instance();
|
||||
DecryptionWorker.instance().setLogger(this.logger_);
|
||||
|
@@ -121,15 +121,6 @@ class BaseModel {
|
||||
return id.substr(0, 5);
|
||||
}
|
||||
|
||||
// static minimalPartialId(id) {
|
||||
// let length = 2;
|
||||
// while (true) {
|
||||
// const partialId = id.substr(0, length);
|
||||
// const r = await this.db().selectOne('SELECT count(*) as total FROM `' + this.tableName() + '` WHERE `id` LIKE ?', [partialId + '%']);
|
||||
// if (r['total'] <= 1) return partialId;
|
||||
// }
|
||||
// }
|
||||
|
||||
static loadByPartialId(partialId) {
|
||||
return this.modelSelectAll('SELECT * FROM `' + this.tableName() + '` WHERE `id` LIKE ?', [partialId + '%']);
|
||||
}
|
||||
@@ -221,20 +212,6 @@ class BaseModel {
|
||||
}
|
||||
if ('type_' in newModel) output.type_ = newModel.type_;
|
||||
return output;
|
||||
// let output = {};
|
||||
// let type = null;
|
||||
// for (let n in newModel) {
|
||||
// if (!newModel.hasOwnProperty(n)) continue;
|
||||
// if (n == 'type_') {
|
||||
// type = newModel[n];
|
||||
// continue;
|
||||
// }
|
||||
// if (!(n in oldModel) || newModel[n] !== oldModel[n]) {
|
||||
// output[n] = newModel[n];
|
||||
// }
|
||||
// }
|
||||
// if (type !== null) output.type_ = type;
|
||||
// return output;
|
||||
}
|
||||
|
||||
static diffObjectsFields(oldModel, newModel) {
|
||||
@@ -421,11 +398,10 @@ class BaseModel {
|
||||
}
|
||||
|
||||
output = this.filter(o);
|
||||
} catch (error) {
|
||||
this.logger().error('Cannot save model', error);
|
||||
} finally {
|
||||
this.releaseSaveMutex(o, mutexRelease);
|
||||
}
|
||||
|
||||
this.releaseSaveMutex(o, mutexRelease);
|
||||
|
||||
|
||||
return output;
|
||||
}
|
||||
@@ -504,6 +480,8 @@ BaseModel.typeEnum_ = [
|
||||
['TYPE_SEARCH', 7],
|
||||
['TYPE_ALARM', 8],
|
||||
['TYPE_MASTER_KEY', 9],
|
||||
['TYPE_ITEM_CHANGE', 10],
|
||||
['TYPE_NOTE_RESOURCE', 11],
|
||||
];
|
||||
|
||||
for (let i = 0; i < BaseModel.typeEnum_.length; i++) {
|
||||
@@ -511,16 +489,6 @@ for (let i = 0; i < BaseModel.typeEnum_.length; i++) {
|
||||
BaseModel[e[0]] = e[1];
|
||||
}
|
||||
|
||||
// BaseModel.TYPE_NOTE = 1;
|
||||
// BaseModel.TYPE_FOLDER = 2;
|
||||
// BaseModel.TYPE_SETTING = 3;
|
||||
// BaseModel.TYPE_RESOURCE = 4;
|
||||
// BaseModel.TYPE_TAG = 5;
|
||||
// BaseModel.TYPE_NOTE_TAG = 6;
|
||||
// BaseModel.TYPE_SEARCH = 7;
|
||||
// BaseModel.TYPE_ALARM = 8;
|
||||
// BaseModel.TYPE_MASTER_KEY = 9;
|
||||
|
||||
BaseModel.db_ = null;
|
||||
BaseModel.dispatch = function(o) {};
|
||||
BaseModel.saveMutexes_ = {};
|
||||
|
@@ -30,7 +30,7 @@ class BaseSyncTarget {
|
||||
return this.db_;
|
||||
}
|
||||
|
||||
isAuthenticated() {
|
||||
async isAuthenticated() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -66,6 +66,10 @@ class BaseSyncTarget {
|
||||
return this.fileApi_;
|
||||
}
|
||||
|
||||
fileApiSync() {
|
||||
return this.fileApi_;
|
||||
}
|
||||
|
||||
// Usually each sync target should create and setup its own file API via initFileApi()
|
||||
// but for testing purposes it might be convenient to provide it here so that multiple
|
||||
// clients can share and sync to the same file api (see test-utils.js)
|
||||
@@ -109,7 +113,7 @@ class BaseSyncTarget {
|
||||
|
||||
async syncStarted() {
|
||||
if (!this.synchronizer_) return false;
|
||||
if (!this.isAuthenticated()) return false;
|
||||
if (!await this.isAuthenticated()) return false;
|
||||
const sync = await this.synchronizer();
|
||||
return sync.state() != 'idle';
|
||||
}
|
||||
|
214
ReactNativeClient/lib/DropboxApi.js
Normal file
214
ReactNativeClient/lib/DropboxApi.js
Normal file
@@ -0,0 +1,214 @@
|
||||
const { Logger } = require('lib/logger.js');
|
||||
const { shim } = require('lib/shim.js');
|
||||
const JoplinError = require('lib/JoplinError');
|
||||
const URL = require('url-parse');
|
||||
const { time } = require('lib/time-utils');
|
||||
const EventDispatcher = require('lib/EventDispatcher');
|
||||
|
||||
class DropboxApi {
|
||||
|
||||
constructor(options) {
|
||||
this.logger_ = new Logger();
|
||||
this.options_ = options;
|
||||
this.authToken_ = null;
|
||||
this.dispatcher_ = new EventDispatcher();
|
||||
}
|
||||
|
||||
clientId() {
|
||||
return this.options_.id;
|
||||
}
|
||||
|
||||
clientSecret() {
|
||||
return this.options_.secret;
|
||||
}
|
||||
|
||||
setLogger(l) {
|
||||
this.logger_ = l;
|
||||
}
|
||||
|
||||
logger() {
|
||||
return this.logger_;
|
||||
}
|
||||
|
||||
authToken() {
|
||||
return this.authToken_; // Without the "Bearer " prefix
|
||||
}
|
||||
|
||||
on(eventName, callback) {
|
||||
return this.dispatcher_.on(eventName, callback);
|
||||
}
|
||||
|
||||
setAuthToken(v) {
|
||||
this.authToken_ = v;
|
||||
this.dispatcher_.dispatch('authRefreshed', this.authToken());
|
||||
}
|
||||
|
||||
loginUrl() {
|
||||
return 'https://www.dropbox.com/oauth2/authorize?response_type=code&client_id=' + this.clientId();
|
||||
}
|
||||
|
||||
baseUrl(endPointFormat) {
|
||||
if (['content', 'api'].indexOf(endPointFormat) < 0) throw new Error('Invalid end point format: ' + endPointFormat);
|
||||
return 'https://' + endPointFormat + '.dropboxapi.com/2';
|
||||
}
|
||||
|
||||
requestToCurl_(url, options) {
|
||||
let output = [];
|
||||
output.push('curl');
|
||||
if (options.method) output.push('-X ' + options.method);
|
||||
if (options.headers) {
|
||||
for (let n in options.headers) {
|
||||
if (!options.headers.hasOwnProperty(n)) continue;
|
||||
output.push('-H ' + "'" + n + ': ' + options.headers[n] + "'");
|
||||
}
|
||||
}
|
||||
if (options.body) output.push('--data ' + '"' + options.body + '"');
|
||||
output.push(url);
|
||||
|
||||
return output.join(' ');
|
||||
}
|
||||
|
||||
async execAuthToken(authCode) {
|
||||
const postData = {
|
||||
code: authCode,
|
||||
grant_type: 'authorization_code',
|
||||
client_id: this.clientId(),
|
||||
client_secret: this.clientSecret(),
|
||||
};
|
||||
|
||||
var formBody = [];
|
||||
for (var property in postData) {
|
||||
var encodedKey = encodeURIComponent(property);
|
||||
var encodedValue = encodeURIComponent(postData[property]);
|
||||
formBody.push(encodedKey + "=" + encodedValue);
|
||||
}
|
||||
formBody = formBody.join("&");
|
||||
|
||||
const response = await shim.fetch('https://api.dropboxapi.com/oauth2/token', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
|
||||
},
|
||||
body: formBody
|
||||
});
|
||||
|
||||
const responseText = await response.text();
|
||||
if (!response.ok) throw new Error(responseText);
|
||||
return JSON.parse(responseText);
|
||||
}
|
||||
|
||||
isTokenError(status, responseText) {
|
||||
if (status === 401) return true;
|
||||
if (responseText.indexOf('OAuth 2 access token is malformed') >= 0) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
async exec(method, path = '', body = null, headers = null, options = null) {
|
||||
if (headers === null) headers = {};
|
||||
if (options === null) options = {};
|
||||
if (!options.target) options.target = 'string';
|
||||
|
||||
const authToken = this.authToken();
|
||||
|
||||
if (authToken) headers['Authorization'] = 'Bearer ' + authToken;
|
||||
|
||||
const endPointFormat = ['files/upload', 'files/download'].indexOf(path) >= 0 ? 'content' : 'api';
|
||||
|
||||
if (endPointFormat === 'api') {
|
||||
headers['Content-Type'] = 'application/json';
|
||||
if (body && typeof body === 'object') body = JSON.stringify(body);
|
||||
} else {
|
||||
headers['Content-Type'] = 'application/octet-stream';
|
||||
}
|
||||
|
||||
const fetchOptions = {};
|
||||
fetchOptions.headers = headers;
|
||||
fetchOptions.method = method;
|
||||
if (options.path) fetchOptions.path = options.path;
|
||||
if (body) fetchOptions.body = body;
|
||||
|
||||
const url = path.indexOf('https://') === 0 ? path : this.baseUrl(endPointFormat) + '/' + path;
|
||||
|
||||
let tryCount = 0;
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
let response = null;
|
||||
|
||||
// console.info(this.requestToCurl_(url, fetchOptions));
|
||||
|
||||
// console.info(method + ' ' + url);
|
||||
|
||||
if (options.source == 'file' && (method == 'POST' || method == 'PUT')) {
|
||||
response = await shim.uploadBlob(url, fetchOptions);
|
||||
} else if (options.target == 'string') {
|
||||
response = await shim.fetch(url, fetchOptions);
|
||||
} else { // file
|
||||
response = await shim.fetchBlob(url, fetchOptions);
|
||||
}
|
||||
|
||||
const responseText = await response.text();
|
||||
|
||||
// console.info('Response: ' + responseText);
|
||||
|
||||
let responseJson_ = null;
|
||||
const loadResponseJson = () => {
|
||||
if (!responseText) return null;
|
||||
if (responseJson_) return responseJson_;
|
||||
try {
|
||||
responseJson_ = JSON.parse(responseText);
|
||||
} catch (error) {
|
||||
return { error: responseText };
|
||||
}
|
||||
return responseJson_;
|
||||
}
|
||||
|
||||
// Creates an error object with as much data as possible as it will appear in the log, which will make debugging easier
|
||||
const newError = (message) => {
|
||||
const json = loadResponseJson();
|
||||
let code = '';
|
||||
if (json && json.error_summary) {
|
||||
code = json.error_summary;
|
||||
}
|
||||
|
||||
// Gives a shorter response for error messages. Useful for cases where a full HTML page is accidentally loaded instead of
|
||||
// JSON. That way the error message will still show there's a problem but without filling up the log or screen.
|
||||
const shortResponseText = (responseText + '').substr(0, 1024);
|
||||
const error = new JoplinError(method + ' ' + path + ': ' + message + ' (' + response.status + '): ' + shortResponseText, code);
|
||||
error.httpStatus = response.status;
|
||||
return error;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const json = loadResponseJson();
|
||||
if (this.isTokenError(response.status, responseText)) {
|
||||
this.setAuthToken(null);
|
||||
}
|
||||
|
||||
// When using fetchBlob we only get a string (not xml or json) back
|
||||
if (options.target === 'file') throw newError('fetchBlob error');
|
||||
|
||||
throw newError('Error');
|
||||
}
|
||||
|
||||
if (options.responseFormat === 'text') return responseText;
|
||||
|
||||
return loadResponseJson();
|
||||
} catch (error) {
|
||||
tryCount++;
|
||||
if (error.code.indexOf('too_many_write_operations') >= 0) {
|
||||
this.logger().warn('too_many_write_operations ' + tryCount);
|
||||
if (tryCount >= 3) {
|
||||
throw error;
|
||||
}
|
||||
await time.sleep(tryCount * 2);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = DropboxApi;
|
@@ -32,4 +32,4 @@ class EventDispatcher {
|
||||
|
||||
}
|
||||
|
||||
module.exports = { EventDispatcher };
|
||||
module.exports = EventDispatcher;
|
@@ -3,7 +3,9 @@ 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');
|
||||
const md5 = require('md5');
|
||||
const MdToHtml_Katex = require('lib/MdToHtml_Katex');
|
||||
|
||||
@@ -54,11 +56,11 @@ class MdToHtml {
|
||||
return output.join(' ');
|
||||
}
|
||||
|
||||
getAttr_(attrs, name) {
|
||||
getAttr_(attrs, name, defaultValue = null) {
|
||||
for (let i = 0; i < attrs.length; i++) {
|
||||
if (attrs[i][0] === name) return attrs[i].length > 1 ? attrs[i][1] : null;
|
||||
}
|
||||
return null;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
setAttr_(attrs, name, value) {
|
||||
@@ -182,11 +184,23 @@ class MdToHtml {
|
||||
return null;
|
||||
}
|
||||
|
||||
urldecode_(str) {
|
||||
try {
|
||||
return decodeURIComponent((str+'').replace(/\+/g, '%20'));
|
||||
} catch (error) {
|
||||
// decodeURIComponent can throw if the string contains non-encoded data (for example "100%")
|
||||
// so in this case just return the non encoded string.
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
renderTokens_(markdownIt, tokens, options) {
|
||||
let output = [];
|
||||
let previousToken = null;
|
||||
let anchorAttrs = [];
|
||||
let extraCssBlocks = {};
|
||||
let anchorHrefs = [];
|
||||
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
let t = tokens[i];
|
||||
@@ -202,6 +216,7 @@ class MdToHtml {
|
||||
const codeBlockLanguage = t && t.info ? t.info : null;
|
||||
let rendererPlugin = null;
|
||||
let rendererPluginOptions = { tagType: 'inline' };
|
||||
let linkHref = null;
|
||||
|
||||
if (isCodeBlock) rendererPlugin = this.rendererPlugin_(codeBlockLanguage);
|
||||
|
||||
@@ -233,6 +248,7 @@ class MdToHtml {
|
||||
if (openTag) {
|
||||
if (openTag === 'a') {
|
||||
anchorAttrs.push(attrs);
|
||||
anchorHrefs.push(this.getAttr_(attrs, 'href'));
|
||||
output.push(this.renderOpenLink_(attrs, options));
|
||||
} else {
|
||||
const attrsHtml = this.renderAttrs_(attrs);
|
||||
@@ -317,7 +333,33 @@ class MdToHtml {
|
||||
|
||||
if (closeTag) {
|
||||
if (closeTag === 'a') {
|
||||
output.push(this.renderCloseLink_(anchorAttrs.pop(), options));
|
||||
const currentAnchorAttrs = anchorAttrs.pop();
|
||||
|
||||
// NOTE: Disabled for now due to this:
|
||||
// https://github.com/laurent22/joplin/issues/318#issuecomment-375854848
|
||||
|
||||
// const previousContent = output.length ? output[output.length - 1].trim() : '';
|
||||
// const anchorHref = this.getAttr_(currentAnchorAttrs, 'href', '').trim();
|
||||
|
||||
// Optimisation: If the content of the anchor is the same as the URL, we replace the content
|
||||
// by (Link). This is to shorten the text, which is important especially when the note comes
|
||||
// from imported HTML, which can contain many such links and make the text unreadble. An example
|
||||
// would be a movie review that has multiple links to allow a user to rate the film from 1 to 5 stars.
|
||||
// In the original page, it might be rendered as stars, via CSS, but in the imported note it would look like this:
|
||||
// http://example.com/rate/1 http://example.com/rate/2 http://example.com/rate/3
|
||||
// http://example.com/rate/4 http://example.com/rate/5
|
||||
// which would take a lot of screen space even though it doesn't matter since the user is unlikely
|
||||
// to rate the film from the note. This is actually a nice example, still readable, but there is way
|
||||
// worse that this in notes that come from web-clipped content.
|
||||
// With this change, the links will still be preserved but displayed like
|
||||
// (link) (link) (link) (link) (link)
|
||||
|
||||
// if (this.urldecode_(previousContent) === htmlentities(this.urldecode_(anchorHref))) {
|
||||
// output.pop();
|
||||
// output.push(_('(Link)'));
|
||||
// }
|
||||
|
||||
output.push(this.renderCloseLink_(currentAnchorAttrs, options));
|
||||
} else {
|
||||
output.push('</' + closeTag + '>');
|
||||
}
|
||||
@@ -336,14 +378,16 @@ class MdToHtml {
|
||||
|
||||
// Insert the extra CSS at the top of the HTML
|
||||
|
||||
const temp = ['<style>'];
|
||||
for (let n in extraCssBlocks) {
|
||||
if (!extraCssBlocks.hasOwnProperty(n)) continue;
|
||||
temp.push(extraCssBlocks[n]);
|
||||
}
|
||||
temp.push('</style>');
|
||||
if (!ObjectUtils.isEmpty(extraCssBlocks)) {
|
||||
const temp = ['<style>'];
|
||||
for (let n in extraCssBlocks) {
|
||||
if (!extraCssBlocks.hasOwnProperty(n)) continue;
|
||||
temp.push(extraCssBlocks[n]);
|
||||
}
|
||||
temp.push('</style>');
|
||||
|
||||
output = temp.concat(output);
|
||||
output = temp.concat(output);
|
||||
}
|
||||
|
||||
return output.join('');
|
||||
}
|
||||
|
@@ -53,4 +53,9 @@ ObjectUtils.convertValuesToFunctions = function(o) {
|
||||
return output;
|
||||
}
|
||||
|
||||
ObjectUtils.isEmpty = function(o) {
|
||||
if (!o) return true;
|
||||
return Object.keys(o).length === 0 && o.constructor === Object;
|
||||
}
|
||||
|
||||
module.exports = ObjectUtils;
|
73
ReactNativeClient/lib/SyncTargetDropbox.js
Normal file
73
ReactNativeClient/lib/SyncTargetDropbox.js
Normal file
@@ -0,0 +1,73 @@
|
||||
const BaseSyncTarget = require('lib/BaseSyncTarget.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const DropboxApi = require('lib/DropboxApi');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const { parameters } = require('lib/parameters.js');
|
||||
const { FileApi } = require('lib/file-api.js');
|
||||
const { Synchronizer } = require('lib/synchronizer.js');
|
||||
const { FileApiDriverDropbox } = require('lib/file-api-driver-dropbox.js');
|
||||
|
||||
class SyncTargetDropbox extends BaseSyncTarget {
|
||||
|
||||
static id() {
|
||||
return 7;
|
||||
}
|
||||
|
||||
constructor(db, options = null) {
|
||||
super(db, options);
|
||||
this.api_ = null;
|
||||
}
|
||||
|
||||
static targetName() {
|
||||
return 'dropbox';
|
||||
}
|
||||
|
||||
static label() {
|
||||
return _('Dropbox');
|
||||
}
|
||||
|
||||
authRouteName() {
|
||||
return 'DropboxLogin';
|
||||
}
|
||||
|
||||
async isAuthenticated() {
|
||||
const f = await this.fileApi();
|
||||
return !!f.driver().api().authToken();
|
||||
}
|
||||
|
||||
async api() {
|
||||
const fileApi = await this.fileApi();
|
||||
return fileApi.driver().api();
|
||||
}
|
||||
|
||||
async initFileApi() {
|
||||
const params = parameters().dropbox;
|
||||
|
||||
const api = new DropboxApi({
|
||||
id: params.id,
|
||||
secret: params.secret,
|
||||
});
|
||||
|
||||
api.on('authRefreshed', (auth) => {
|
||||
this.logger().info('Saving updated OneDrive auth.');
|
||||
Setting.setValue('sync.' + SyncTargetDropbox.id() + '.auth', auth ? auth : null);
|
||||
});
|
||||
|
||||
const authToken = Setting.value('sync.' + SyncTargetDropbox.id() + '.auth');
|
||||
api.setAuthToken(authToken);
|
||||
|
||||
const appDir = '';
|
||||
const fileApi = new FileApi(appDir, new FileApiDriverDropbox(api));
|
||||
fileApi.setSyncTargetId(SyncTargetDropbox.id());
|
||||
fileApi.setLogger(this.logger());
|
||||
return fileApi;
|
||||
}
|
||||
|
||||
async initSynchronizer() {
|
||||
if (!(await this.isAuthenticated())) throw new Error('User is not authentified');
|
||||
return new Synchronizer(this.db(), await this.fileApi(), Setting.value('appType'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = SyncTargetDropbox;
|
@@ -19,7 +19,7 @@ class SyncTargetFilesystem extends BaseSyncTarget {
|
||||
return _('File system');
|
||||
}
|
||||
|
||||
isAuthenticated() {
|
||||
async isAuthenticated() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -19,7 +19,7 @@ class SyncTargetMemory extends BaseSyncTarget {
|
||||
return 'Memory';
|
||||
}
|
||||
|
||||
isAuthenticated() {
|
||||
async isAuthenticated() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -28,7 +28,7 @@ class SyncTargetNextcloud extends BaseSyncTarget {
|
||||
return _('Nextcloud');
|
||||
}
|
||||
|
||||
isAuthenticated() {
|
||||
async isAuthenticated() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -26,7 +26,7 @@ class SyncTargetOneDrive extends BaseSyncTarget {
|
||||
return _('OneDrive');
|
||||
}
|
||||
|
||||
isAuthenticated() {
|
||||
async isAuthenticated() {
|
||||
return this.api().auth();
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ class SyncTargetOneDrive extends BaseSyncTarget {
|
||||
}
|
||||
|
||||
async initSynchronizer() {
|
||||
if (!this.isAuthenticated()) throw new Error('User is not authentified');
|
||||
if (!await this.isAuthenticated()) throw new Error('User is not authentified');
|
||||
return new Synchronizer(this.db(), await this.fileApi(), Setting.value('appType'));
|
||||
}
|
||||
|
||||
|
@@ -24,7 +24,7 @@ class SyncTargetWebDAV extends BaseSyncTarget {
|
||||
return _('WebDAV');
|
||||
}
|
||||
|
||||
isAuthenticated() {
|
||||
async isAuthenticated() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -30,16 +30,17 @@ class ModalDialog extends React.Component {
|
||||
borderColor:theme.dividerColor,
|
||||
margin: 20,
|
||||
padding: 10,
|
||||
borderRadius: 5,
|
||||
},
|
||||
modalContentWrapper2: {
|
||||
paddingTop: 10,
|
||||
flex:1,
|
||||
},
|
||||
title: {
|
||||
title: Object.assign({}, theme.normalText, {
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: theme.dividerColor,
|
||||
paddingBottom: 10,
|
||||
},
|
||||
fontWeight: 'bold',
|
||||
}),
|
||||
buttonRow: {
|
||||
flexDirection: 'row',
|
||||
borderTopWidth: 1,
|
||||
@@ -54,21 +55,22 @@ class ModalDialog extends React.Component {
|
||||
|
||||
render() {
|
||||
const ContentComponent = this.props.ContentComponent;
|
||||
const buttonBarEnabled = this.props.buttonBarEnabled !== false;
|
||||
|
||||
return (
|
||||
<View style={this.styles().modalWrapper}>
|
||||
<Modal transparent={true} visible={true} onRequestClose={() => { }} >
|
||||
<View style={this.styles().modalContentWrapper}>
|
||||
<Text style={this.styles().title}>Title</Text>
|
||||
<View elevation={10} style={this.styles().modalContentWrapper}>
|
||||
<Text style={this.styles().title}>{this.props.title}</Text>
|
||||
<View style={this.styles().modalContentWrapper2}>
|
||||
{ContentComponent}
|
||||
</View>
|
||||
<View style={this.styles().buttonRow}>
|
||||
<View style={{flex:1}}>
|
||||
<Button title={_('OK')} onPress={() => {}}></Button>
|
||||
<Button disabled={!buttonBarEnabled} title={_('OK')} onPress={this.props.onOkPress}></Button>
|
||||
</View>
|
||||
<View style={{flex:1, marginLeft: 5}}>
|
||||
<Button title={_('Cancel')} onPress={() => {}}></Button>
|
||||
<Button disabled={!buttonBarEnabled} title={_('Cancel')} onPress={this.props.onCancelPress}></Button>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
@@ -1,4 +1,5 @@
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const { Platform } = require('react-native');
|
||||
|
||||
const globalStyle = {
|
||||
fontSize: 16,
|
||||
@@ -14,6 +15,7 @@ const globalStyle = {
|
||||
dividerColor: "#dddddd",
|
||||
selectedColor: '#e5e5e5',
|
||||
disabledOpacity: 0.2,
|
||||
colorUrl: '#000CFF',
|
||||
|
||||
raisedBackgroundColor: "#0080EF",
|
||||
raisedColor: "#003363",
|
||||
@@ -36,23 +38,73 @@ globalStyle.marginTop = globalStyle.margin;
|
||||
globalStyle.marginBottom = globalStyle.margin;
|
||||
globalStyle.htmlMarginLeft = ((globalStyle.marginLeft / 10) * 0.6).toFixed(2) + 'em';
|
||||
|
||||
globalStyle.icon = {
|
||||
color: globalStyle.color,
|
||||
fontSize: 30,
|
||||
};
|
||||
// globalStyle.icon = {
|
||||
// color: globalStyle.color,
|
||||
// fontSize: 30,
|
||||
// };
|
||||
|
||||
globalStyle.lineInput = {
|
||||
color: globalStyle.color,
|
||||
backgroundColor: globalStyle.backgroundColor,
|
||||
};
|
||||
// globalStyle.lineInput = {
|
||||
// color: globalStyle.color,
|
||||
// backgroundColor: globalStyle.backgroundColor,
|
||||
// };
|
||||
|
||||
// globalStyle.buttonRow = {
|
||||
// flexDirection: 'row',
|
||||
// borderTopWidth: 1,
|
||||
// borderTopColor: globalStyle.dividerColor,
|
||||
// paddingTop: 10,
|
||||
// };
|
||||
|
||||
// globalStyle.normalText = {
|
||||
// color: globalStyle.color,
|
||||
// fontSize: globalStyle.fontSize,
|
||||
// };
|
||||
|
||||
let themeCache_ = {};
|
||||
|
||||
function addExtraStyles(style) {
|
||||
style.icon = {
|
||||
color: style.color,
|
||||
fontSize: 30,
|
||||
};
|
||||
|
||||
style.lineInput = {
|
||||
color: style.color,
|
||||
backgroundColor: style.backgroundColor,
|
||||
};
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
style.lineInput.borderBottomWidth = 1;
|
||||
style.lineInput.borderBottomColor = style.dividerColor;
|
||||
}
|
||||
|
||||
style.buttonRow = {
|
||||
flexDirection: 'row',
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: style.dividerColor,
|
||||
paddingTop: 10,
|
||||
};
|
||||
|
||||
style.normalText = {
|
||||
color: style.color,
|
||||
fontSize: style.fontSize,
|
||||
};
|
||||
|
||||
style.urlText = {
|
||||
color: style.colorUrl,
|
||||
fontSize: style.fontSize,
|
||||
};
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
function themeStyle(theme) {
|
||||
if (!theme) throw new Error('Theme not set');
|
||||
|
||||
if (themeCache_[theme]) return themeCache_[theme];
|
||||
|
||||
let output = Object.assign({}, globalStyle);
|
||||
if (theme == Setting.THEME_LIGHT) return output;
|
||||
if (theme == Setting.THEME_LIGHT) return addExtraStyles(output);
|
||||
|
||||
output.backgroundColor = '#1D2024';
|
||||
output.color = '#dddddd';
|
||||
@@ -69,7 +121,7 @@ function themeStyle(theme) {
|
||||
output.htmlLinkColor = 'rgb(166,166,255)';
|
||||
|
||||
themeCache_[theme] = output;
|
||||
return themeCache_[theme];
|
||||
return addExtraStyles(themeCache_[theme]);
|
||||
}
|
||||
|
||||
module.exports = { globalStyle, themeStyle };
|
@@ -128,6 +128,8 @@ class ScreenHeaderComponent extends Component {
|
||||
color: theme.raisedHighlightedColor,
|
||||
fontWeight: 'bold',
|
||||
fontSize: theme.fontSize,
|
||||
paddingTop: 15,
|
||||
paddingBottom: 15,
|
||||
},
|
||||
warningBox: {
|
||||
backgroundColor: "#ff9900",
|
||||
@@ -428,15 +430,19 @@ class ScreenHeaderComponent extends Component {
|
||||
</TouchableOpacity>
|
||||
) : null;
|
||||
|
||||
const showSideMenuButton = this.props.showSideMenuButton !== false && !this.props.noteSelectionEnabled;
|
||||
const showSearchButton = this.props.showSearchButton !== false && !this.props.noteSelectionEnabled;
|
||||
const showContextMenuButton = this.props.showContextMenuButton !== false;
|
||||
|
||||
const titleComp = createTitleComponent();
|
||||
const sideMenuComp = this.props.noteSelectionEnabled ? null : sideMenuButton(this.styles(), () => this.sideMenuButton_press());
|
||||
const sideMenuComp = !showSideMenuButton ? null : sideMenuButton(this.styles(), () => this.sideMenuButton_press());
|
||||
const backButtonComp = backButton(this.styles(), () => this.backButton_press(), !this.props.historyCanGoBack);
|
||||
const searchButtonComp = this.props.noteSelectionEnabled ? null : searchButton(this.styles(), () => this.searchButton_press());
|
||||
const searchButtonComp = !showSearchButton ? null : searchButton(this.styles(), () => this.searchButton_press());
|
||||
const deleteButtonComp = this.props.noteSelectionEnabled ? deleteButton(this.styles(), () => this.deleteButton_press()) : null;
|
||||
const sortButtonComp = this.props.sortButton_press ? sortButton(this.styles(), () => this.props.sortButton_press()) : null;
|
||||
const windowHeight = Dimensions.get('window').height - 50;
|
||||
|
||||
const menuComp = (
|
||||
const menuComp = !showContextMenuButton ? null : (
|
||||
<Menu onSelect={(value) => this.menu_select(value)} style={this.styles().contextMenu}>
|
||||
<MenuTrigger style={{ paddingTop: PADDING_V, paddingBottom: PADDING_V }}>
|
||||
<Text style={this.styles().contextMenuTrigger}> ⋮</Text>
|
||||
|
199
ReactNativeClient/lib/components/screens/NoteTagsDialog.js
Normal file
199
ReactNativeClient/lib/components/screens/NoteTagsDialog.js
Normal file
@@ -0,0 +1,199 @@
|
||||
const React = require('react'); const Component = React.Component;
|
||||
const { ListView, StyleSheet, View, Text, Button, FlatList, TouchableOpacity, TextInput } = require('react-native');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const { connect } = require('react-redux');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const { ScreenHeader } = require('lib/components/screen-header.js');
|
||||
const { time } = require('lib/time-utils');
|
||||
const { Logger } = require('lib/logger.js');
|
||||
const BaseItem = require('lib/models/BaseItem.js');
|
||||
const Tag = require('lib/models/Tag.js');
|
||||
const { Database } = require('lib/database.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const { ReportService } = require('lib/services/report.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { globalStyle, themeStyle } = require('lib/components/global-style.js');
|
||||
const Icon = require('react-native-vector-icons/Ionicons').default;
|
||||
const ModalDialog = require('lib/components/ModalDialog');
|
||||
const naturalCompare = require('string-natural-compare');
|
||||
|
||||
class NoteTagsDialogComponent extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.styles_ = {};
|
||||
this.state = {
|
||||
noteTagIds: [],
|
||||
noteId: null,
|
||||
tagListData: [],
|
||||
newTags: '',
|
||||
savingTags: false,
|
||||
};
|
||||
|
||||
const noteHasTag = (tagId) => {
|
||||
for (let i = 0; i < this.state.tagListData.length; i++) {
|
||||
if (this.state.tagListData[i].id === tagId) return this.state.tagListData[i].selected;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const newTagTitles = () => {
|
||||
return this.state.newTags
|
||||
.split(',')
|
||||
.map(t => t.trim().toLowerCase())
|
||||
.filter(t => !!t);
|
||||
}
|
||||
|
||||
this.tag_press = (tagId) => {
|
||||
const newData = this.state.tagListData.slice();
|
||||
for (let i = 0; i < newData.length; i++) {
|
||||
const t = newData[i];
|
||||
if (t.id === tagId) {
|
||||
const newTag = Object.assign({}, t);
|
||||
newTag.selected = !newTag.selected;
|
||||
newData[i] = newTag;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({ tagListData: newData });
|
||||
}
|
||||
|
||||
this.renderTag = (data) => {
|
||||
const tag = data.item;
|
||||
const iconName = noteHasTag(tag.id) ? 'md-checkbox-outline' : 'md-square-outline';
|
||||
return (
|
||||
<TouchableOpacity key={tag.id} onPress={() => this.tag_press(tag.id)} style={this.styles().tag}>
|
||||
<View style={this.styles().tagIconText}>
|
||||
<Icon name={iconName} style={this.styles().tagCheckbox}/><Text style={this.styles().tagText}>{tag.title}</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
this.tagKeyExtractor = (tag, index) => tag.id;
|
||||
|
||||
this.okButton_press = async () => {
|
||||
this.setState({ savingTags: true });
|
||||
|
||||
try {
|
||||
const tagIds = this.state.tagListData.filter(t => t.selected).map(t => t.id);
|
||||
await Tag.setNoteTagsByIds(this.state.noteId, tagIds);
|
||||
|
||||
const extraTitles = newTagTitles();
|
||||
for (let i = 0; i < extraTitles.length; i++) {
|
||||
await Tag.addNoteTagByTitle(this.state.noteId, extraTitles[i]);
|
||||
}
|
||||
} finally {
|
||||
this.setState({ savingTags: false });
|
||||
}
|
||||
|
||||
if (this.props.onCloseRequested) this.props.onCloseRequested();
|
||||
}
|
||||
|
||||
this.cancelButton_press = () => {
|
||||
if (this.props.onCloseRequested) this.props.onCloseRequested();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
const noteId = this.props.noteId;
|
||||
this.setState({ noteId: noteId });
|
||||
this.loadNoteTags(noteId);
|
||||
}
|
||||
|
||||
async loadNoteTags(noteId) {
|
||||
const tags = await Tag.tagsByNoteId(noteId);
|
||||
const tagIds = tags.map(t => t.id);
|
||||
|
||||
const tagListData = this.props.tags.map(tag => { return {
|
||||
id: tag.id,
|
||||
title: tag.title,
|
||||
selected: tagIds.indexOf(tag.id) >= 0,
|
||||
}});
|
||||
|
||||
tagListData.sort((a, b) => {
|
||||
return naturalCompare.caseInsensitive(a.title, b.title);
|
||||
});
|
||||
|
||||
this.setState({ tagListData: tagListData });
|
||||
}
|
||||
|
||||
styles() {
|
||||
const themeId = this.props.theme;
|
||||
const theme = themeStyle(themeId);
|
||||
|
||||
if (this.styles_[themeId]) return this.styles_[themeId];
|
||||
this.styles_ = {};
|
||||
|
||||
let styles = {
|
||||
tag: {
|
||||
padding: 10,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: theme.dividerColor,
|
||||
},
|
||||
tagIconText: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
tagText: Object.assign({}, theme.normalText),
|
||||
tagCheckbox: {
|
||||
marginRight: 8,
|
||||
fontSize: 20,
|
||||
color: theme.color,
|
||||
},
|
||||
newTagBox: {
|
||||
flexDirection:'row',
|
||||
alignItems: 'center',
|
||||
paddingLeft: theme.marginLeft,
|
||||
paddingRight: theme.marginRight,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: theme.dividerColor
|
||||
},
|
||||
newTagBoxLabel: Object.assign({}, theme.normalText, { marginRight: 8 }),
|
||||
newTagBoxInput: Object.assign({}, theme.lineInput, { flex: 1 }),
|
||||
};
|
||||
|
||||
this.styles_[themeId] = StyleSheet.create(styles);
|
||||
return this.styles_[themeId];
|
||||
}
|
||||
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
|
||||
const dialogContent = (
|
||||
<View style={{flex:1}}>
|
||||
<View style={this.styles().newTagBox}>
|
||||
<Text style={this.styles().newTagBoxLabel}>{_('New tags:')}</Text><TextInput value={this.state.newTags} onChangeText={value => { this.setState({ newTags: value }) }} style={this.styles().newTagBoxInput}/>
|
||||
</View>
|
||||
<FlatList
|
||||
data={this.state.tagListData}
|
||||
renderItem={this.renderTag}
|
||||
keyExtractor={this.tagKeyExtractor}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
return <ModalDialog
|
||||
theme={this.props.theme}
|
||||
ContentComponent={dialogContent}
|
||||
title={_('Type new tags or select from list')}
|
||||
onOkPress={this.okButton_press}
|
||||
onCancelPress={this.cancelButton_press}
|
||||
buttonBarEnabled={!this.state.savingTags}
|
||||
/>
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const NoteTagsDialog = connect(
|
||||
(state) => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
tags: state.tags,
|
||||
noteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null,
|
||||
};
|
||||
}
|
||||
)(NoteTagsDialogComponent)
|
||||
|
||||
module.exports = NoteTagsDialog;
|
@@ -203,7 +203,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
|
||||
|
||||
settingComps.push(
|
||||
<View key="donate_link" style={this.styles().settingContainer}>
|
||||
<TouchableOpacity onPress={() => { Linking.openURL('http://joplin.cozic.net/donate/') }}>
|
||||
<TouchableOpacity onPress={() => { Linking.openURL('https://joplin.cozic.net/donate/') }}>
|
||||
<Text key="label" style={this.styles().linkText}>{_('Make a donation')}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
@@ -211,7 +211,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
|
||||
|
||||
settingComps.push(
|
||||
<View key="website_link" style={this.styles().settingContainer}>
|
||||
<TouchableOpacity onPress={() => { Linking.openURL('http://joplin.cozic.net/') }}>
|
||||
<TouchableOpacity onPress={() => { Linking.openURL('https://joplin.cozic.net/') }}>
|
||||
<Text key="label" style={this.styles().linkText}>{_('Joplin website')}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
@@ -219,7 +219,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
|
||||
|
||||
settingComps.push(
|
||||
<View key="privacy_link" style={this.styles().settingContainer}>
|
||||
<TouchableOpacity onPress={() => { Linking.openURL('http://joplin.cozic.net/privacy/') }}>
|
||||
<TouchableOpacity onPress={() => { Linking.openURL('https://joplin.cozic.net/privacy/') }}>
|
||||
<Text key="label" style={this.styles().linkText}>Privacy Policy</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
83
ReactNativeClient/lib/components/screens/dropbox-login.js
Normal file
83
ReactNativeClient/lib/components/screens/dropbox-login.js
Normal file
@@ -0,0 +1,83 @@
|
||||
const React = require('react'); const Component = React.Component;
|
||||
const { View, Button, Text, TextInput, TouchableOpacity, StyleSheet } = require('react-native');
|
||||
const { connect } = require('react-redux');
|
||||
const { ScreenHeader } = require('lib/components/screen-header.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { BaseScreenComponent } = require('lib/components/base-screen.js');
|
||||
const DialogBox = require('react-native-dialogbox').default;
|
||||
const { dialogs } = require('lib/dialogs.js');
|
||||
const Shared = require('lib/components/shared/dropbox-login-shared');
|
||||
const { themeStyle } = require('lib/components/global-style.js');
|
||||
|
||||
class DropboxLoginScreenComponent extends BaseScreenComponent {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.styles_ = {};
|
||||
|
||||
this.shared_ = new Shared(
|
||||
this,
|
||||
(msg) => dialogs.info(this, msg),
|
||||
(msg) => dialogs.error(this, msg)
|
||||
);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.shared_.refreshUrl();
|
||||
}
|
||||
|
||||
styles() {
|
||||
const themeId = this.props.theme;
|
||||
const theme = themeStyle(themeId);
|
||||
|
||||
if (this.styles_[themeId]) return this.styles_[themeId];
|
||||
this.styles_ = {};
|
||||
|
||||
let styles = {
|
||||
container: {
|
||||
padding: theme.margin,
|
||||
},
|
||||
stepText: Object.assign({}, theme.normalText, { marginBottom: theme.margin }),
|
||||
urlText: Object.assign({}, theme.urlText, { marginBottom: theme.margin }),
|
||||
}
|
||||
|
||||
this.styles_[themeId] = StyleSheet.create(styles);
|
||||
return this.styles_[themeId];
|
||||
}
|
||||
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
|
||||
return (
|
||||
<View style={this.styles().screen}>
|
||||
<ScreenHeader title={_('Login with Dropbox')}/>
|
||||
|
||||
<View style={this.styles().container}>
|
||||
<Text style={this.styles().stepText}>{_('To allow Joplin to synchronise with Dropbox, please follow the steps below:')}</Text>
|
||||
<Text style={this.styles().stepText}>{_('Step 1: Open this URL in your browser to authorise the application:')}</Text>
|
||||
<View>
|
||||
<TouchableOpacity onPress={this.shared_.loginUrl_click}>
|
||||
<Text style={this.styles().urlText}>{this.state.loginUrl}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<Text style={this.styles().stepText}>{_('Step 2: Enter the code provided by Dropbox:')}</Text>
|
||||
<TextInput value={this.state.authCode} onChangeText={this.shared_.authCodeInput_change} style={theme.lineInput}/>
|
||||
|
||||
<Button disabled={this.state.checkingAuthToken} title={_("Submit")} onPress={this.shared_.submit_click}></Button>
|
||||
</View>
|
||||
|
||||
<DialogBox ref={dialogbox => { this.dialogbox = dialogbox }}/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const DropboxLoginScreen = connect((state) => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
};
|
||||
})(DropboxLoginScreenComponent)
|
||||
|
||||
module.exports = { DropboxLoginScreen };
|
@@ -222,12 +222,10 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent {
|
||||
<ScreenHeader title={_('Encryption Config')}/>
|
||||
<ScrollView style={this.styles().container}>
|
||||
|
||||
{/*<View style={{backgroundColor: theme.warningBackgroundColor, padding: 5}}>
|
||||
<Text>Important: This is a *beta* feature. It has been extensively tested and is already in use by some users, but it is possible that some bugs remain.</Text>
|
||||
<Text>If you wish to you use it, it is recommended that you keep a backup of your data. The simplest way is to regularly backup your notes from the desktop or terminal application.</Text>
|
||||
<Text>For more information about End-To-End Encryption (E2EE) and how it is going to work, please check the documentation:</Text>
|
||||
<TouchableOpacity onPress={() => { Linking.openURL('http://joplin.cozic.net/help/e2ee.html') }}><Text>http://joplin.cozic.net/help/e2ee.html</Text></TouchableOpacity>
|
||||
</View>*/}
|
||||
{<View style={{backgroundColor: theme.warningBackgroundColor, paddingTop: 5, paddingBottom: 5, paddingLeft: 10, paddingRight: 10 }}>
|
||||
<Text>{_('For more information about End-To-End Encryption (E2EE) and advices on how to enable it please check the documentation:')}</Text>
|
||||
<TouchableOpacity onPress={() => { Linking.openURL('https://joplin.cozic.net/e2ee') }}><Text>https://joplin.cozic.net/e2ee</Text></TouchableOpacity>
|
||||
</View>}
|
||||
|
||||
<Text style={this.styles().titleText}>{_('Status')}</Text>
|
||||
<Text style={this.styles().normalText}>{_('Encryption is: %s', this.props.encryptionEnabled ? _('Enabled') : _('Disabled'))}</Text>
|
||||
|
@@ -15,6 +15,7 @@ const Icon = require('react-native-vector-icons/Ionicons').default;
|
||||
const { fileExtension, basename, safeFileExtension } = require('lib/path-utils.js');
|
||||
const mimeUtils = require('lib/mime-utils.js').mime;
|
||||
const { ScreenHeader } = require('lib/components/screen-header.js');
|
||||
const NoteTagsDialog = require('lib/components/screens/NoteTagsDialog');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const { Checkbox } = require('lib/components/checkbox.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
@@ -51,7 +52,8 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
isLoading: true,
|
||||
titleTextInputHeight: 20,
|
||||
alarmDialogShown: false,
|
||||
heightBumpView:0
|
||||
heightBumpView:0,
|
||||
noteTagDialogShown: false,
|
||||
};
|
||||
|
||||
// iOS doesn't support multiline text fields properly so disable it
|
||||
@@ -101,6 +103,10 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
this.noteTagDialog_closeRequested = () => {
|
||||
this.setState({ noteTagDialogShown: false });
|
||||
}
|
||||
}
|
||||
|
||||
styles() {
|
||||
@@ -357,6 +363,12 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
shared.toggleIsTodo_onPress(this);
|
||||
}
|
||||
|
||||
tags_onPress() {
|
||||
if (!this.state.note || !this.state.note.id) return;
|
||||
|
||||
this.setState({ noteTagDialogShown: true });
|
||||
}
|
||||
|
||||
setAlarm_onPress() {
|
||||
this.setState({ alarmDialogShown: true });
|
||||
}
|
||||
@@ -393,6 +405,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
menuOptions() {
|
||||
const note = this.state.note;
|
||||
const isTodo = note && !!note.is_todo;
|
||||
const isSaved = note && note.id;
|
||||
|
||||
let output = [];
|
||||
|
||||
@@ -410,6 +423,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
output.push({ title: _('Set alarm'), onPress: () => { this.setState({ alarmDialogShown: true }) }});;
|
||||
}
|
||||
|
||||
if (isSaved) output.push({ title: _('Tags'), onPress: () => { this.tags_onPress(); } });
|
||||
output.push({ title: isTodo ? _('Convert to note') : _('Convert to todo'), onPress: () => { this.toggleIsTodo_onPress(); } });
|
||||
output.push({ isDivider: true });
|
||||
if (this.props.showAdvancedOptions) output.push({ title: this.state.showNoteMetadata ? _('Hide metadata') : _('Show metadata'), onPress: () => { this.showMetadata_onPress(); } });
|
||||
@@ -535,6 +549,8 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
</View>
|
||||
);
|
||||
|
||||
const noteTagDialog = !this.state.noteTagDialogShown ? null : <NoteTagsDialog onCloseRequested={this.noteTagDialog_closeRequested}/>;
|
||||
|
||||
return (
|
||||
<View style={this.rootStyle(this.props.theme).root}>
|
||||
<ScreenHeader
|
||||
@@ -572,6 +588,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
/>
|
||||
|
||||
<DialogBox ref={dialogbox => { this.dialogbox = dialogbox }}/>
|
||||
{ noteTagDialog }
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
@@ -0,0 +1,73 @@
|
||||
const { shim } = require('lib/shim');
|
||||
const SyncTargetRegistry = require('lib/SyncTargetRegistry');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const Setting = require('lib/models/Setting');
|
||||
|
||||
class Shared {
|
||||
|
||||
constructor(comp, showInfoMessageBox, showErrorMessageBox) {
|
||||
this.comp_ = comp;
|
||||
|
||||
this.dropboxApi_ = null;
|
||||
|
||||
this.comp_.state = {
|
||||
loginUrl: '',
|
||||
authCode: '',
|
||||
checkingAuthToken: false,
|
||||
};
|
||||
|
||||
this.loginUrl_click = () => {
|
||||
if (!this.comp_.state.loginUrl) return;
|
||||
shim.openUrl(this.comp_.state.loginUrl);
|
||||
}
|
||||
|
||||
this.authCodeInput_change = (event) => {
|
||||
this.comp_.setState({
|
||||
authCode: typeof event === 'object' ? event.target.value : event
|
||||
});
|
||||
}
|
||||
|
||||
this.submit_click = async () => {
|
||||
this.comp_.setState({ checkingAuthToken: true });
|
||||
|
||||
const api = await this.dropboxApi();
|
||||
try {
|
||||
const response = await api.execAuthToken(this.comp_.state.authCode);
|
||||
|
||||
Setting.setValue('sync.' + this.syncTargetId() + '.auth', response.access_token);
|
||||
api.setAuthToken(response.access_token);
|
||||
await showInfoMessageBox(_('The application has been authorised!'));
|
||||
this.comp_.props.dispatch({ type: 'NAV_BACK' });
|
||||
reg.scheduleSync();
|
||||
} catch (error) {
|
||||
await showErrorMessageBox(_('Could not authorise application:\n\n%s\n\nPlease try again.', error.message));
|
||||
} finally {
|
||||
this.comp_.setState({ checkingAuthToken: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
syncTargetId() {
|
||||
return SyncTargetRegistry.nameToId('dropbox');
|
||||
}
|
||||
|
||||
async dropboxApi() {
|
||||
if (this.dropboxApi_) return this.dropboxApi_;
|
||||
|
||||
const syncTarget = reg.syncTarget(this.syncTargetId());
|
||||
this.dropboxApi_ = await syncTarget.api();
|
||||
return this.dropboxApi_;
|
||||
}
|
||||
|
||||
async refreshUrl() {
|
||||
const api = await this.dropboxApi();
|
||||
|
||||
this.comp_.setState({
|
||||
loginUrl: api.loginUrl(),
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Shared;
|
@@ -36,7 +36,7 @@ shared.synchronize_press = async function(comp) {
|
||||
|
||||
const action = comp.props.syncStarted ? 'cancel' : 'start';
|
||||
|
||||
if (!reg.syncTarget().isAuthenticated()) {
|
||||
if (!await reg.syncTarget().isAuthenticated()) {
|
||||
if (reg.syncTarget().authRouteName()) {
|
||||
comp.props.dispatch({
|
||||
type: 'NAV_GO',
|
||||
|
@@ -138,8 +138,10 @@ class SideMenuContentComponent extends Component {
|
||||
}
|
||||
|
||||
synchronizeButton(state) {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
|
||||
const title = state == 'sync' ? _('Synchronise') : _('Cancel synchronisation');
|
||||
const iconComp = state == 'sync' ? <Icon name='md-sync' style={globalStyle.icon} /> : <Icon name='md-close' style={globalStyle.icon} />;
|
||||
const iconComp = state == 'sync' ? <Icon name='md-sync' style={theme.icon} /> : <Icon name='md-close' style={theme.icon} />;
|
||||
|
||||
return (
|
||||
<TouchableOpacity key={'synchronize_button'} onPress={() => { this.synchronize_press() }}>
|
||||
|
@@ -3,16 +3,16 @@ const { promiseChain } = require('lib/promise-utils.js');
|
||||
const { Logger } = require('lib/logger.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const { sprintf } = require('sprintf-js');
|
||||
const Mutex = require('async-mutex').Mutex;
|
||||
|
||||
class Database {
|
||||
|
||||
constructor(driver) {
|
||||
this.debugMode_ = false;
|
||||
this.driver_ = driver;
|
||||
this.inTransaction_ = false;
|
||||
|
||||
this.logger_ = new Logger();
|
||||
this.logExcludedQueryTypes_ = [];
|
||||
this.batchTransactionMutex_ = new Mutex();
|
||||
}
|
||||
|
||||
setLogExcludedQueryTypes(v) {
|
||||
@@ -113,92 +113,24 @@ class Database {
|
||||
return;
|
||||
}
|
||||
|
||||
// There can be only one transaction running at a time so queue
|
||||
// any new transaction here.
|
||||
if (this.inTransaction_) {
|
||||
while (true) {
|
||||
await time.msleep(100);
|
||||
if (!this.inTransaction_) {
|
||||
this.inTransaction_ = true;
|
||||
break;
|
||||
}
|
||||
// There can be only one transaction running at a time so use a mutex
|
||||
const release = await this.batchTransactionMutex_.acquire();
|
||||
|
||||
try {
|
||||
await this.exec('BEGIN TRANSACTION');
|
||||
|
||||
for (let i = 0; i < queries.length; i++) {
|
||||
let query = this.wrapQuery(queries[i]);
|
||||
await this.exec(query.sql, query.params);
|
||||
}
|
||||
|
||||
// return new Promise((resolve, reject) => {
|
||||
// let iid = setInterval(() => {
|
||||
// if (!this.inTransaction_) {
|
||||
// clearInterval(iid);
|
||||
// this.transactionExecBatch(queries).then(() => {
|
||||
// resolve();
|
||||
// }).catch((error) => {
|
||||
// reject(error);
|
||||
// });
|
||||
// }
|
||||
// }, 100);
|
||||
// });
|
||||
await this.exec('COMMIT');
|
||||
} catch (error) {
|
||||
await this.exec('ROLLBACK');
|
||||
throw error;
|
||||
} finally {
|
||||
release();
|
||||
}
|
||||
|
||||
this.inTransaction_ = true;
|
||||
|
||||
queries.splice(0, 0, 'BEGIN TRANSACTION');
|
||||
queries.push('COMMIT'); // Note: ROLLBACK is currently not supported
|
||||
|
||||
for (let i = 0; i < queries.length; i++) {
|
||||
let query = this.wrapQuery(queries[i]);
|
||||
await this.exec(query.sql, query.params);
|
||||
}
|
||||
|
||||
this.inTransaction_ = false;
|
||||
|
||||
// return promiseChain(chain).then(() => {
|
||||
// this.inTransaction_ = false;
|
||||
// });
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// if (queries.length <= 0) return Promise.resolve();
|
||||
|
||||
// if (queries.length == 1) {
|
||||
// let q = this.wrapQuery(queries[0]);
|
||||
// return this.exec(q.sql, q.params);
|
||||
// }
|
||||
|
||||
// // There can be only one transaction running at a time so queue
|
||||
// // any new transaction here.
|
||||
// if (this.inTransaction_) {
|
||||
// return new Promise((resolve, reject) => {
|
||||
// let iid = setInterval(() => {
|
||||
// if (!this.inTransaction_) {
|
||||
// clearInterval(iid);
|
||||
// this.transactionExecBatch(queries).then(() => {
|
||||
// resolve();
|
||||
// }).catch((error) => {
|
||||
// reject(error);
|
||||
// });
|
||||
// }
|
||||
// }, 100);
|
||||
// });
|
||||
// }
|
||||
|
||||
// this.inTransaction_ = true;
|
||||
|
||||
// queries.splice(0, 0, 'BEGIN TRANSACTION');
|
||||
// queries.push('COMMIT'); // Note: ROLLBACK is currently not supported
|
||||
|
||||
// let chain = [];
|
||||
// for (let i = 0; i < queries.length; i++) {
|
||||
// let query = this.wrapQuery(queries[i]);
|
||||
// chain.push(() => {
|
||||
// return this.exec(query.sql, query.params);
|
||||
// });
|
||||
// }
|
||||
|
||||
// return promiseChain(chain).then(() => {
|
||||
// this.inTransaction_ = false;
|
||||
// });
|
||||
}
|
||||
|
||||
static enumId(type, s) {
|
||||
|
@@ -67,6 +67,11 @@ dialogs.error = (parentComponent, message) => {
|
||||
return parentComponent.dialogbox.alert(message);
|
||||
}
|
||||
|
||||
dialogs.info = (parentComponent, message) => {
|
||||
Keyboard.dismiss();
|
||||
return parentComponent.dialogbox.alert(message);
|
||||
}
|
||||
|
||||
dialogs.DialogBox = DialogBox
|
||||
|
||||
module.exports = { dialogs };
|
209
ReactNativeClient/lib/file-api-driver-dropbox.js
Normal file
209
ReactNativeClient/lib/file-api-driver-dropbox.js
Normal file
@@ -0,0 +1,209 @@
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const { shim } = require('lib/shim');
|
||||
const JoplinError = require('lib/JoplinError');
|
||||
const { basicDelta } = require('lib/file-api');
|
||||
|
||||
class FileApiDriverDropbox {
|
||||
|
||||
constructor(api) {
|
||||
this.api_ = api;
|
||||
}
|
||||
|
||||
api() {
|
||||
return this.api_;
|
||||
}
|
||||
|
||||
requestRepeatCount() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
makePath_(path) {
|
||||
if (!path) return '';
|
||||
return '/' + path;
|
||||
}
|
||||
|
||||
async stat(path) {
|
||||
try {
|
||||
const metadata = await this.api().exec('POST', 'files/get_metadata', {
|
||||
path: this.makePath_(path),
|
||||
});
|
||||
|
||||
return this.metadataToStat_(metadata, path);
|
||||
} catch (error) {
|
||||
if (error.code.indexOf('not_found') >= 0) {
|
||||
// ignore
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
metadataToStat_(md, path) {
|
||||
const output = {
|
||||
path: path,
|
||||
updated_time: md.server_modified ? new Date(md.server_modified) : new Date(),
|
||||
isDir: md['.tag'] === 'folder',
|
||||
};
|
||||
|
||||
if (md['.tag'] === 'deleted') output.isDeleted = true;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
metadataToStats_(mds) {
|
||||
const output = [];
|
||||
for (let i = 0; i < mds.length; i++) {
|
||||
output.push(this.metadataToStat_(mds[i], mds[i].name));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
async setTimestamp(path, timestampMs) {
|
||||
throw new Error('Not implemented'); // Not needed anymore
|
||||
}
|
||||
|
||||
async delta(path, options) {
|
||||
const context = options ? options.context : null;
|
||||
let cursor = context ? context.cursor : null;
|
||||
|
||||
while (true) {
|
||||
const urlPath = cursor ? 'files/list_folder/continue' : 'files/list_folder';
|
||||
const body = cursor ? { cursor: cursor } : { path: this.makePath_(path), include_deleted: true };
|
||||
|
||||
try {
|
||||
const response = await this.api().exec('POST', urlPath, body);
|
||||
|
||||
const output = {
|
||||
items: this.metadataToStats_(response.entries),
|
||||
hasMore: response.has_more,
|
||||
context: { cursor: response.cursor },
|
||||
}
|
||||
|
||||
return output;
|
||||
} catch (error) {
|
||||
// If there's an error related to an invalid cursor, clear the cursor and retry.
|
||||
if (cursor) {
|
||||
if (error.httpStatus === 400 || error.code.indexOf('reset') >= 0) {
|
||||
// console.info('Clearing cursor and retrying', error);
|
||||
cursor = null;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async list(path, options) {
|
||||
let response = await this.api().exec('POST', 'files/list_folder', {
|
||||
path: this.makePath_(path),
|
||||
});
|
||||
|
||||
let output = this.metadataToStats_(response.entries);
|
||||
|
||||
while (response.has_more) {
|
||||
response = await this.api().exec('POST', 'files/list_folder/continue', {
|
||||
cursor: response.cursor,
|
||||
});
|
||||
|
||||
output = output.concat(this.metadataToStats_(response.entries));
|
||||
}
|
||||
|
||||
return {
|
||||
items: output,
|
||||
hasMore: false,
|
||||
context: { cursor: response.cursor },
|
||||
};
|
||||
}
|
||||
|
||||
async get(path, options) {
|
||||
if (!options) options = {};
|
||||
if (!options.responseFormat) options.responseFormat = 'text';
|
||||
|
||||
try {
|
||||
const response = await this.api().exec('POST', 'files/download', null, {
|
||||
'Dropbox-API-Arg': JSON.stringify({ "path": this.makePath_(path) }),
|
||||
}, options);
|
||||
return response;
|
||||
} catch (error) {
|
||||
if (error.code.indexOf('not_found') >= 0) {
|
||||
return null;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async mkdir(path) {
|
||||
try {
|
||||
await this.api().exec('POST', 'files/create_folder_v2', {
|
||||
path: this.makePath_(path),
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.code.indexOf('path/conflict') >= 0) {
|
||||
// Ignore
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async put(path, content, options = null) {
|
||||
// See https://github.com/facebook/react-native/issues/14445#issuecomment-352965210
|
||||
if (typeof content === 'string') content = shim.Buffer.from(content, 'utf8')
|
||||
|
||||
await this.api().exec('POST', 'files/upload', content, {
|
||||
'Dropbox-API-Arg': JSON.stringify({
|
||||
path: this.makePath_(path),
|
||||
mode: 'overwrite',
|
||||
mute: true, // Don't send a notification to user since there can be many of these updates
|
||||
})}, options);
|
||||
}
|
||||
|
||||
async delete(path) {
|
||||
try {
|
||||
await this.api().exec('POST', 'files/delete_v2', {
|
||||
path: this.makePath_(path),
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.code.indexOf('not_found') >= 0) {
|
||||
// ignore
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async move(oldPath, newPath) {
|
||||
throw new Error('Not supported');
|
||||
}
|
||||
|
||||
format() {
|
||||
throw new Error('Not supported');
|
||||
}
|
||||
|
||||
async clearRoot() {
|
||||
const entries = await this.list('');
|
||||
const batchDelete = [];
|
||||
for (let i = 0; i < entries.items.length; i++) {
|
||||
batchDelete.push({ path: this.makePath_(entries.items[i].path) });
|
||||
}
|
||||
|
||||
const response = await this.api().exec('POST', 'files/delete_batch', { entries: batchDelete });
|
||||
const jobId = response.async_job_id;
|
||||
|
||||
while (true) {
|
||||
const check = await this.api().exec('POST', 'files/delete_batch/check', { async_job_id: jobId });
|
||||
if (check['.tag'] === 'complete') break;
|
||||
|
||||
// It returns "failed" if it didn't work but anyway throw an error if it's anything other than complete or in_progress
|
||||
if (check['.tag'] !== 'in_progress') {
|
||||
throw new Error('Batch delete failed? ' + JSON.stringify(check));
|
||||
}
|
||||
await time.sleep(2);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = { FileApiDriverDropbox };
|
@@ -293,6 +293,7 @@ class FileApiDriverWebDav {
|
||||
return response;
|
||||
} catch (error) {
|
||||
if (error.code !== 404) throw error;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -243,7 +243,9 @@ async function basicDelta(path, getDirStatFn, options) {
|
||||
newContext.statsCache.sort(function(a, b) {
|
||||
return a.updated_time - b.updated_time;
|
||||
});
|
||||
newContext.statIdsCache = newContext.statsCache.map((item) => BaseItem.pathToId(item.path));
|
||||
newContext.statIdsCache = newContext.statsCache
|
||||
.filter(item => BaseItem.isSystemPath(item.path))
|
||||
.map(item => BaseItem.pathToId(item.path));
|
||||
newContext.statIdsCache.sort(); // Items must be sorted to use binary search below
|
||||
}
|
||||
|
||||
|
@@ -17,7 +17,7 @@ class FsDriverRN extends FsDriverBase {
|
||||
|
||||
// same as rm -rf
|
||||
async remove(path) {
|
||||
throw new Error('Not implemented');
|
||||
return await this.unlink(path);
|
||||
}
|
||||
|
||||
writeBinaryFile(path, content) {
|
||||
|
@@ -476,12 +476,16 @@ function enexXmlToMdArray(stream, resources) {
|
||||
// </note>
|
||||
// </en-export>
|
||||
|
||||
// Note that there's also the case of resources with no ID where the ID is actually the MD5 of the content.
|
||||
// This is handled in import-enex.js
|
||||
|
||||
let found = false;
|
||||
for (let i = 0; i < remainingResources.length; i++) {
|
||||
let r = remainingResources[i];
|
||||
if (!r.id) {
|
||||
r.id = hash;
|
||||
remainingResources[i] = r;
|
||||
resource = Object.assign({}, r);
|
||||
resource.id = hash;
|
||||
remainingResources.splice(i, 1);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
@@ -490,13 +494,13 @@ function enexXmlToMdArray(stream, resources) {
|
||||
if (!found) {
|
||||
console.warn('Hash with no associated resource: ' + hash);
|
||||
}
|
||||
} else {
|
||||
// If the resource does not appear among the note's resources, it
|
||||
// means it's an attachement. It will be appended along with the
|
||||
// other remaining resources at the bottom of the markdown text.
|
||||
if (!!resource.id) {
|
||||
section.lines = addResourceTag(section.lines, resource, nodeAttributes.alt);
|
||||
}
|
||||
}
|
||||
|
||||
// If the resource does not appear among the note's resources, it
|
||||
// means it's an attachement. It will be appended along with the
|
||||
// other remaining resources at the bottom of the markdown text.
|
||||
if (resource && !!resource.id) {
|
||||
section.lines = addResourceTag(section.lines, resource, nodeAttributes.alt);
|
||||
}
|
||||
} else if (["span", "font", 'sup', 'cite', 'abbr', 'small', 'tt', 'sub', 'colgroup', 'col', 'ins', 'caption', 'var', 'map', 'area'].indexOf(n) >= 0) {
|
||||
// Inline tags that can be ignored in Markdown
|
||||
@@ -545,10 +549,6 @@ function enexXmlToMdArray(stream, resources) {
|
||||
|
||||
if (section.lines.length < 1) throw new Error('Invalid anchor tag closing'); // Sanity check, but normally not possible
|
||||
|
||||
const pushEmptyAnchor = (url) => {
|
||||
section.lines.push('[link](' + url + ')');
|
||||
}
|
||||
|
||||
// When closing the anchor tag, check if there's is any text content. If not
|
||||
// put the URL as is (don't wrap it in [](url)). The markdown parser, using
|
||||
// GitHub flavour, will turn this URL into a link. This is to generate slightly
|
||||
@@ -556,11 +556,11 @@ function enexXmlToMdArray(stream, resources) {
|
||||
let previous = section.lines[section.lines.length - 1];
|
||||
if (previous == '[') {
|
||||
section.lines.pop();
|
||||
pushEmptyAnchor(url);
|
||||
section.lines.push(url);
|
||||
} else if (!previous || previous == url) {
|
||||
section.lines.pop();
|
||||
section.lines.pop();
|
||||
pushEmptyAnchor(url);
|
||||
section.lines.push(url);
|
||||
} else {
|
||||
// Need to remove any new line character between the current ']' and the previous '['
|
||||
// otherwise it won't render properly.
|
||||
@@ -583,8 +583,7 @@ function enexXmlToMdArray(stream, resources) {
|
||||
const c = section.lines.pop();
|
||||
if (c === '[') break;
|
||||
}
|
||||
//section.lines.push(url);
|
||||
pushEmptyAnchor(url);
|
||||
section.lines.push(url);
|
||||
} else {
|
||||
section.lines.push('](' + url + ')');
|
||||
}
|
||||
@@ -644,7 +643,6 @@ function drawTable(table) {
|
||||
// https://gist.github.com/IanWang/28965e13cdafdef4e11dc91f578d160d#tables
|
||||
|
||||
const flatRender = tableHasSubTables(table); // Render the table has regular text
|
||||
const minColWidth = 3;
|
||||
let lines = [];
|
||||
lines.push(BLOCK_OPEN);
|
||||
let headerDone = false;
|
||||
@@ -687,9 +685,16 @@ function drawTable(table) {
|
||||
|
||||
// A cell in a Markdown table cannot have actual new lines so replace
|
||||
// them with <br>, which are supported by the markdown renderers.
|
||||
const cellText = processMdArrayNewLines(td.lines).replace(/\n+/g, "<br>");
|
||||
let cellText = processMdArrayNewLines(td.lines).replace(/\n+/g, "<br>");
|
||||
|
||||
const width = Math.max(cellText.length, 3);
|
||||
// Inside tables cells, "|" needs to be escaped
|
||||
cellText = cellText.replace(/\|/g, "\\|");
|
||||
|
||||
// Previously the width of the cell was as big as the content since it looks nicer, however that often doesn't work
|
||||
// since the content can be very long, resulting in unreadable markdown. So no solution is perfect but making it a
|
||||
// width of 3 is a bit better. Note that 3 is the minimum width of a cell - below this, it won't be rendered by
|
||||
// markdown parsers.
|
||||
const width = 3;
|
||||
line.push(stringPadding(cellText, width, ' ', stringPadding.RIGHT));
|
||||
|
||||
if (!headerDone) {
|
||||
|
@@ -11,6 +11,7 @@ const { enexXmlToMd } = require('./import-enex-md-gen.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const Levenshtein = require('levenshtein');
|
||||
const jsSHA = require("jssha");
|
||||
const md5 = require('md5');
|
||||
|
||||
//const Promise = require('promise');
|
||||
const fs = require('fs-extra');
|
||||
@@ -30,8 +31,8 @@ function extractRecognitionObjId(recognitionXml) {
|
||||
return r && r.length >= 2 ? r[1] : null;
|
||||
}
|
||||
|
||||
function filePutContents(filePath, content) {
|
||||
return fs.writeFile(filePath, content);
|
||||
async function filePutContents(filePath, content) {
|
||||
await fs.writeFile(filePath, content);
|
||||
}
|
||||
|
||||
function removeUndefinedProperties(note) {
|
||||
@@ -255,49 +256,6 @@ function importEnex(parentFolderId, filePath, importOptions = null) {
|
||||
stream.resume();
|
||||
processingNotes = false;
|
||||
return true;
|
||||
|
||||
// let chain = [];
|
||||
// while (notes.length) {
|
||||
// let note = notes.shift();
|
||||
// const contentStream = stringToStream(note.bodyXml);
|
||||
// chain.push(() => {
|
||||
// return enexXmlToMd(contentStream, note.resources).then((body) => {
|
||||
// delete note.bodyXml;
|
||||
|
||||
// // console.info('-----------------------------------------------------------');
|
||||
// // console.info(body);
|
||||
// // console.info('-----------------------------------------------------------');
|
||||
|
||||
// note.id = uuid.create();
|
||||
// note.parent_id = parentFolderId;
|
||||
// note.body = body;
|
||||
|
||||
// // Notes in enex files always have a created timestamp but not always an
|
||||
// // updated timestamp (it the note has never been modified). For sync
|
||||
// // we require an updated_time property, so set it to create_time in that case
|
||||
// if (!note.updated_time) note.updated_time = note.created_time;
|
||||
|
||||
// return saveNoteToStorage(note, importOptions.fuzzyMatching);
|
||||
// }).then((result) => {
|
||||
// if (result.noteUpdated) {
|
||||
// progressState.updated++;
|
||||
// } else if (result.noteCreated) {
|
||||
// progressState.created++;
|
||||
// } else if (result.noteSkipped) {
|
||||
// progressState.skipped++;
|
||||
// }
|
||||
// progressState.resourcesCreated += result.resourcesCreated;
|
||||
// progressState.notesTagged += result.notesTagged;
|
||||
// importOptions.onProgress(progressState);
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
// return promiseChain(chain).then(() => {
|
||||
// stream.resume();
|
||||
// processingNotes = false;
|
||||
// return true;
|
||||
// });
|
||||
}
|
||||
|
||||
saxStream.on('error', (error) => {
|
||||
@@ -418,6 +376,7 @@ function importEnex(parentFolderId, filePath, importOptions = null) {
|
||||
noteAttributes = null;
|
||||
} else if (n == 'resource') {
|
||||
let decodedData = null;
|
||||
let resourceId = noteResource.id;
|
||||
if (noteResource.dataEncoding == 'base64') {
|
||||
try {
|
||||
decodedData = Buffer.from(noteResource.data, 'base64');
|
||||
@@ -429,8 +388,14 @@ function importEnex(parentFolderId, filePath, importOptions = null) {
|
||||
decodedData = noteResource.data; // Just put the encoded data directly in the file so it can, potentially, be manually decoded later
|
||||
}
|
||||
|
||||
if (!resourceId && decodedData) {
|
||||
// If no resource ID is present, the resource ID is actually the MD5 of the data.
|
||||
// This ID will match the "hash" attribute of the corresponding <en-media> tag.
|
||||
resourceId = md5(decodedData);
|
||||
}
|
||||
|
||||
let r = {
|
||||
id: noteResource.id,
|
||||
id: resourceId,
|
||||
data: decodedData,
|
||||
mime: noteResource.mime,
|
||||
title: noteResource.filename ? noteResource.filename : '',
|
||||
|
@@ -202,11 +202,11 @@ 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];
|
||||
const existingDatabaseVersions = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
|
||||
|
||||
let currentVersionIndex = existingDatabaseVersions.indexOf(fromVersion);
|
||||
|
||||
if (currentVersionIndex < 0) throw new Error('Unknown profile version. Most likely this is an old version of Joplin, while the profile was created by a newer version. Please upgrade Joplin at http://joplin.cozic.net and try again.');
|
||||
if (currentVersionIndex < 0) throw new Error('Unknown profile version. Most likely this is an old version of Joplin, while the profile was created by a newer version. Please upgrade Joplin at https://joplin.cozic.net and try again.');
|
||||
|
||||
// currentVersionIndex < 0 if for the case where an old version of Joplin used with a newer
|
||||
// version of the database, so that migration is not run in this case.
|
||||
@@ -298,6 +298,52 @@ class JoplinDatabase extends Database {
|
||||
queries.push('ALTER TABLE resources ADD COLUMN encryption_blob_encrypted INT NOT NULL DEFAULT 0');
|
||||
}
|
||||
|
||||
const upgradeVersion10 = () => {
|
||||
const itemChangesTable = `
|
||||
CREATE TABLE item_changes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
item_type INT NOT NULL,
|
||||
item_id TEXT NOT NULL,
|
||||
type INT NOT NULL,
|
||||
created_time INT NOT NULL
|
||||
);
|
||||
`;
|
||||
|
||||
const noteResourcesTable = `
|
||||
CREATE TABLE note_resources (
|
||||
id INTEGER PRIMARY KEY,
|
||||
note_id TEXT NOT NULL,
|
||||
resource_id TEXT NOT NULL,
|
||||
is_associated INT NOT NULL,
|
||||
last_seen_time INT NOT NULL
|
||||
);
|
||||
`;
|
||||
|
||||
queries.push(this.sqlStringToLines(itemChangesTable)[0]);
|
||||
queries.push('CREATE INDEX item_changes_item_id ON item_changes (item_id)');
|
||||
queries.push('CREATE INDEX item_changes_created_time ON item_changes (created_time)');
|
||||
queries.push('CREATE INDEX item_changes_item_type ON item_changes (item_type)');
|
||||
|
||||
queries.push(this.sqlStringToLines(noteResourcesTable)[0]);
|
||||
queries.push('CREATE INDEX note_resources_note_id ON note_resources (note_id)');
|
||||
queries.push('CREATE INDEX note_resources_resource_id ON note_resources (resource_id)');
|
||||
|
||||
queries.push({ sql: 'INSERT INTO item_changes (item_type, item_id, type, created_time) SELECT 1, id, 1, ? FROM notes', params: [Date.now()] });
|
||||
}
|
||||
|
||||
if (targetVersion == 10) {
|
||||
upgradeVersion10();
|
||||
}
|
||||
|
||||
if (targetVersion == 11) {
|
||||
// This trick was needed because Electron Builder incorrectly released a dev branch containing v10 as it was
|
||||
// still being developed, and the db schema was not final at that time. So this v11 was created to
|
||||
// make sure any invalid db schema that was accidentally created was deleted and recreated.
|
||||
queries.push('DROP TABLE item_changes');
|
||||
queries.push('DROP TABLE note_resources');
|
||||
upgradeVersion10();
|
||||
}
|
||||
|
||||
queries.push({ sql: 'UPDATE version SET version = ?', params: [targetVersion] });
|
||||
await this.transactionExecBatch(queries);
|
||||
|
||||
|
54
ReactNativeClient/lib/models/ItemChange.js
Normal file
54
ReactNativeClient/lib/models/ItemChange.js
Normal file
@@ -0,0 +1,54 @@
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const Mutex = require('async-mutex').Mutex;
|
||||
|
||||
class ItemChange extends BaseModel {
|
||||
|
||||
static tableName() {
|
||||
return 'item_changes';
|
||||
}
|
||||
|
||||
static modelType() {
|
||||
return BaseModel.TYPE_ITEM_CHANGE;
|
||||
}
|
||||
|
||||
static async add(itemType, itemId, type) {
|
||||
ItemChange.saveCalls_.push(true);
|
||||
|
||||
// Using a mutex so that records can be added to the database in the
|
||||
// background, without making the UI wait.
|
||||
const release = await ItemChange.addChangeMutex_.acquire();
|
||||
|
||||
try {
|
||||
await this.db().transactionExecBatch([
|
||||
{ sql: 'DELETE FROM item_changes WHERE item_id = ?', params: [itemId] },
|
||||
{ sql: 'INSERT INTO item_changes (item_type, item_id, type, created_time) VALUES (?, ?, ?, ?)', params: [itemType, itemId, type, Date.now()] },
|
||||
]);
|
||||
} finally {
|
||||
release();
|
||||
ItemChange.saveCalls_.pop();
|
||||
}
|
||||
}
|
||||
|
||||
// Because item changes are recorded in the background, this function
|
||||
// can be used for synchronous code, in particular when unit testing.
|
||||
static async waitForAllSaved() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const iid = setInterval(() => {
|
||||
if (!ItemChange.saveCalls_.length) {
|
||||
clearInterval(iid);
|
||||
resolve();
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ItemChange.addChangeMutex_ = new Mutex();
|
||||
ItemChange.saveCalls_ = [];
|
||||
|
||||
ItemChange.TYPE_CREATE = 1;
|
||||
ItemChange.TYPE_UPDATE = 2;
|
||||
ItemChange.TYPE_DELETE = 3;
|
||||
|
||||
module.exports = ItemChange;
|
@@ -1,6 +1,7 @@
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const { sprintf } = require('sprintf-js');
|
||||
const BaseItem = require('lib/models/BaseItem.js');
|
||||
const ItemChange = require('lib/models/ItemChange.js');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const { shim } = require('lib/shim.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
@@ -407,6 +408,8 @@ class Note extends BaseItem {
|
||||
|
||||
const note = await super.save(o, options);
|
||||
|
||||
ItemChange.add(BaseModel.TYPE_NOTE, note.id, isNew ? ItemChange.TYPE_CREATE : ItemChange.TYPE_UPDATE);
|
||||
|
||||
this.dispatch({
|
||||
type: 'NOTE_UPDATE_ONE',
|
||||
note: note,
|
||||
@@ -422,18 +425,22 @@ class Note extends BaseItem {
|
||||
return note;
|
||||
}
|
||||
|
||||
static async delete(id, options = null) {
|
||||
let r = await super.delete(id, options);
|
||||
// Not used?
|
||||
|
||||
this.dispatch({
|
||||
type: 'NOTE_DELETE',
|
||||
id: id,
|
||||
});
|
||||
}
|
||||
// static async delete(id, options = null) {
|
||||
// let r = await super.delete(id, options);
|
||||
|
||||
static batchDelete(ids, options = null) {
|
||||
const result = super.batchDelete(ids, options);
|
||||
// this.dispatch({
|
||||
// type: 'NOTE_DELETE',
|
||||
// id: id,
|
||||
// });
|
||||
// }
|
||||
|
||||
static async batchDelete(ids, options = null) {
|
||||
const result = await super.batchDelete(ids, options);
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
ItemChange.add(BaseModel.TYPE_NOTE, ids[i], ItemChange.TYPE_DELETE);
|
||||
|
||||
this.dispatch({
|
||||
type: 'NOTE_DELETE',
|
||||
id: ids[i],
|
||||
|
70
ReactNativeClient/lib/models/NoteResource.js
Normal file
70
ReactNativeClient/lib/models/NoteResource.js
Normal file
@@ -0,0 +1,70 @@
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
|
||||
class NoteResource extends BaseModel {
|
||||
|
||||
static tableName() {
|
||||
return 'note_resources';
|
||||
}
|
||||
|
||||
static modelType() {
|
||||
return BaseModel.TYPE_NOTE_RESOURCE;
|
||||
}
|
||||
|
||||
static async setAssociatedResources(noteId, resourceIds) {
|
||||
const existingRows = await this.modelSelectAll('SELECT * FROM note_resources WHERE note_id = ?', [noteId]);
|
||||
|
||||
const notProcessedResourceIds = resourceIds.slice();
|
||||
const queries = [];
|
||||
for (let i = 0; i < existingRows.length; i++) {
|
||||
const row = existingRows[i];
|
||||
const resourceIndex = resourceIds.indexOf(row.resource_id);
|
||||
|
||||
if (resourceIndex >= 0) {
|
||||
queries.push({ sql: 'UPDATE note_resources SET last_seen_time = ?, is_associated = 1 WHERE id = ?', params: [Date.now(), row.id] });
|
||||
notProcessedResourceIds.splice(notProcessedResourceIds.indexOf(row.resource_id), 1);
|
||||
} else {
|
||||
queries.push({ sql: 'UPDATE note_resources SET is_associated = 0 WHERE id = ?', params: [row.id] });
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < notProcessedResourceIds.length; i++) {
|
||||
queries.push({ sql: 'INSERT INTO note_resources (note_id, resource_id, is_associated, last_seen_time) VALUES (?, ?, ?, ?)', params: [noteId, notProcessedResourceIds[i], 1, Date.now()] });
|
||||
}
|
||||
|
||||
await this.db().transactionExecBatch(queries);
|
||||
}
|
||||
|
||||
static async addOrphanedResources() {
|
||||
const missingResources = await this.db().selectAll('SELECT id FROM resources WHERE id NOT IN (SELECT DISTINCT resource_id FROM note_resources)');
|
||||
const queries = [];
|
||||
for (let i = 0; i < missingResources.length; i++) {
|
||||
const id = missingResources[i].id;
|
||||
queries.push({ sql: 'INSERT INTO note_resources (note_id, resource_id, is_associated, last_seen_time) VALUES (?, ?, ?, ?)', params: ["", id, 0, Date.now()] });
|
||||
}
|
||||
await this.db().transactionExecBatch(queries);
|
||||
}
|
||||
|
||||
static async remove(noteId) {
|
||||
await this.db().exec({ sql: 'UPDATE note_resources SET is_associated = 0 WHERE note_id = ?', params: [noteId] });
|
||||
}
|
||||
|
||||
static async orphanResources(expiryDelay = null) {
|
||||
if (expiryDelay === null) expiryDelay = 1000 * 60 * 60 * 24;
|
||||
const cutOffTime = Date.now() - expiryDelay;
|
||||
const output = await this.modelSelectAll(`
|
||||
SELECT resource_id, sum(is_associated)
|
||||
FROM note_resources
|
||||
GROUP BY resource_id
|
||||
HAVING sum(is_associated) <= 0
|
||||
AND last_seen_time < ?
|
||||
`, [cutOffTime]);
|
||||
return output.map(r => r.resource_id);
|
||||
}
|
||||
|
||||
static async deleteByResource(resourceId) {
|
||||
await this.db().exec('DELETE FROM note_resources WHERE resource_id = ?', [resourceId]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = NoteResource;
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user