You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-08-27 20:29:45 +02:00
Compare commits
26 Commits
android-v1
...
v1.0.80
Author | SHA1 | Date | |
---|---|---|---|
|
3a9643c1ea | ||
|
aee7f5a8ac | ||
|
d3cd378922 | ||
|
4f5e7367d0 | ||
|
2280fb5c43 | ||
|
96fb7c2087 | ||
|
6e994fd8b9 | ||
|
3c2281dbf9 | ||
|
a7cde1e269 | ||
|
f8310ba0d5 | ||
|
ac07bf784d | ||
|
067455542f | ||
|
5bfeaa357b | ||
|
fe27a64331 | ||
|
ed638612aa | ||
|
1d7ec83510 | ||
|
75c710232d | ||
|
5af52afadb | ||
|
0f4324c2f8 | ||
|
b48e1dac94 | ||
|
f0ca8e1e31 | ||
|
74b83eb71e | ||
|
28dce0fbb5 | ||
|
c12d402c7e | ||
|
014f5b123c | ||
|
58601dfc04 |
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
|
@@ -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 {
|
||||
|
1382
CliClient/locales/cs_CZ.po
Normal file
1382
CliClient/locales/cs_CZ.po
Normal file
File diff suppressed because it is too large
Load Diff
@@ -784,12 +784,13 @@ msgstr "Omdøb notesbog:"
|
||||
msgid "Set alarm:"
|
||||
msgstr "Indstil alarm:"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Søg"
|
||||
|
||||
msgid "Layout"
|
||||
msgstr "Layout"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Search..."
|
||||
msgstr "Søg"
|
||||
|
||||
msgid "Some items cannot be synchronised."
|
||||
msgstr "Nogle emner kan ikke synkroniseres."
|
||||
|
||||
@@ -889,9 +890,6 @@ msgstr "Synkroniser"
|
||||
msgid "Notebooks"
|
||||
msgstr "Notesbøger"
|
||||
|
||||
msgid "Searches"
|
||||
msgstr "Søgninger"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr "Vælg hvor sync status skal eksporteres til"
|
||||
|
||||
@@ -1369,6 +1367,9 @@ msgstr "Slet notesbog"
|
||||
msgid "Login with OneDrive"
|
||||
msgstr "Log på med OneDrive"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Søg"
|
||||
|
||||
msgid ""
|
||||
"Click on the (+) button to create a new note or notebook. Click on the side "
|
||||
"menu to access your existing notebooks."
|
||||
@@ -1381,3 +1382,6 @@ msgstr "Du har ingen notesbøger. Opret en ved at klikke på (+) knappen."
|
||||
|
||||
msgid "Welcome"
|
||||
msgstr "Velkommen"
|
||||
|
||||
#~ msgid "Searches"
|
||||
#~ msgstr "Søgninger"
|
||||
|
@@ -811,12 +811,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."
|
||||
|
||||
@@ -922,9 +923,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"
|
||||
@@ -1409,6 +1407,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."
|
||||
@@ -1425,6 +1426,9 @@ msgstr ""
|
||||
msgid "Welcome"
|
||||
msgstr "Willkommen"
|
||||
|
||||
#~ msgid "Searches"
|
||||
#~ msgstr "Suchen"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "Release notes:\n"
|
||||
#~ "\n"
|
||||
|
@@ -708,10 +708,10 @@ msgstr ""
|
||||
msgid "Set alarm:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Search"
|
||||
msgid "Layout"
|
||||
msgstr ""
|
||||
|
||||
msgid "Layout"
|
||||
msgid "Search..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Some items cannot be synchronised."
|
||||
@@ -813,9 +813,6 @@ msgstr ""
|
||||
msgid "Notebooks"
|
||||
msgstr ""
|
||||
|
||||
msgid "Searches"
|
||||
msgstr ""
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr ""
|
||||
|
||||
@@ -1272,6 +1269,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."
|
||||
|
@@ -795,12 +795,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."
|
||||
|
||||
@@ -902,9 +903,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"
|
||||
|
||||
@@ -1384,6 +1382,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."
|
||||
@@ -1398,6 +1399,9 @@ msgstr ""
|
||||
msgid "Welcome"
|
||||
msgstr "Bienvenido"
|
||||
|
||||
#~ msgid "Searches"
|
||||
#~ msgstr "Búsquedas"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "Release notes:\n"
|
||||
#~ "\n"
|
||||
|
@@ -798,12 +798,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."
|
||||
|
||||
@@ -907,9 +908,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"
|
||||
@@ -1396,6 +1394,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."
|
||||
@@ -1409,6 +1410,9 @@ msgstr "Oraindik ez duzu koadernorik. Sortu bat (+) botoian sakatuta."
|
||||
msgid "Welcome"
|
||||
msgstr "Ongi etorri!"
|
||||
|
||||
#~ msgid "Searches"
|
||||
#~ msgstr "Bilaketak"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid ""
|
||||
#~ "Release notes:\n"
|
||||
|
@@ -797,12 +797,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."
|
||||
|
||||
@@ -907,9 +907,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"
|
||||
@@ -1391,6 +1388,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."
|
||||
@@ -1406,6 +1406,9 @@ msgstr ""
|
||||
msgid "Welcome"
|
||||
msgstr "Bienvenue"
|
||||
|
||||
#~ msgid "Searches"
|
||||
#~ msgstr "Recherches"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "Release notes:\n"
|
||||
#~ "\n"
|
||||
|
@@ -789,12 +789,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."
|
||||
|
||||
@@ -898,9 +899,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"
|
||||
|
||||
@@ -1377,6 +1375,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."
|
||||
@@ -1390,6 +1391,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"
|
||||
|
@@ -771,12 +771,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."
|
||||
|
||||
@@ -881,9 +882,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."
|
||||
@@ -1363,6 +1361,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."
|
||||
@@ -1378,6 +1379,9 @@ msgstr ""
|
||||
msgid "Welcome"
|
||||
msgstr "Benvenuto"
|
||||
|
||||
#~ msgid "Searches"
|
||||
#~ msgstr "Ricerche"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid ""
|
||||
#~ "Release notes:\n"
|
||||
|
@@ -775,12 +775,13 @@ msgstr "ノートブックの名前を変更:"
|
||||
msgid "Set alarm:"
|
||||
msgstr "アラームをセット:"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "検索"
|
||||
|
||||
msgid "Layout"
|
||||
msgstr "レイアウト"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Search..."
|
||||
msgstr "検索"
|
||||
|
||||
msgid "Some items cannot be synchronised."
|
||||
msgstr "いくつかの項目は同期されませんでした。"
|
||||
|
||||
@@ -884,9 +885,6 @@ msgstr "同期"
|
||||
msgid "Notebooks"
|
||||
msgstr "ノートブック"
|
||||
|
||||
msgid "Searches"
|
||||
msgstr "検索"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr "同期状況の出力先を選択してください"
|
||||
|
||||
@@ -1367,6 +1365,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."
|
||||
@@ -1382,6 +1383,9 @@ msgstr ""
|
||||
msgid "Welcome"
|
||||
msgstr "ようこそ"
|
||||
|
||||
#~ msgid "Searches"
|
||||
#~ msgstr "検索"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid ""
|
||||
#~ "Release notes:\n"
|
||||
|
@@ -708,10 +708,10 @@ msgstr ""
|
||||
msgid "Set alarm:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Search"
|
||||
msgid "Layout"
|
||||
msgstr ""
|
||||
|
||||
msgid "Layout"
|
||||
msgid "Search..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Some items cannot be synchronised."
|
||||
@@ -813,9 +813,6 @@ msgstr ""
|
||||
msgid "Notebooks"
|
||||
msgstr ""
|
||||
|
||||
msgid "Searches"
|
||||
msgstr ""
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr ""
|
||||
|
||||
@@ -1272,6 +1269,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."
|
||||
|
@@ -800,12 +800,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."
|
||||
|
||||
@@ -910,9 +911,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"
|
||||
|
||||
@@ -1398,6 +1396,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."
|
||||
@@ -1413,6 +1414,9 @@ msgstr ""
|
||||
msgid "Welcome"
|
||||
msgstr "Welkom"
|
||||
|
||||
#~ msgid "Searches"
|
||||
#~ msgstr "Zoekopdrachten"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid ""
|
||||
#~ "Release notes:\n"
|
||||
|
@@ -793,12 +793,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."
|
||||
|
||||
@@ -900,9 +901,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 "
|
||||
@@ -1384,6 +1382,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."
|
||||
@@ -1397,6 +1398,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"
|
||||
|
@@ -795,12 +795,13 @@ msgstr "Переименовать блокнот:"
|
||||
msgid "Set alarm:"
|
||||
msgstr "Установить напоминание:"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Поиск"
|
||||
|
||||
msgid "Layout"
|
||||
msgstr "Вид"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Search..."
|
||||
msgstr "Поиск"
|
||||
|
||||
msgid "Some items cannot be synchronised."
|
||||
msgstr "Некоторые элементы не могут быть синхронизированы."
|
||||
|
||||
@@ -902,9 +903,6 @@ msgstr "Синхронизировать"
|
||||
msgid "Notebooks"
|
||||
msgstr "Блокноты"
|
||||
|
||||
msgid "Searches"
|
||||
msgstr "Запросы"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr "Выберите, куда должен быть экспортирован статус синхронизации"
|
||||
|
||||
@@ -1383,6 +1381,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."
|
||||
@@ -1396,6 +1397,9 @@ msgstr "У вас сейчас нет блокнота. Создайте его
|
||||
msgid "Welcome"
|
||||
msgstr "Добро пожаловать"
|
||||
|
||||
#~ msgid "Searches"
|
||||
#~ msgstr "Запросы"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid ""
|
||||
#~ "Release notes:\n"
|
||||
|
@@ -742,12 +742,13 @@ msgstr "重命名笔记本:"
|
||||
msgid "Set alarm:"
|
||||
msgstr "设置提醒:"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "搜索"
|
||||
|
||||
msgid "Layout"
|
||||
msgstr "布局"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Search..."
|
||||
msgstr "搜索"
|
||||
|
||||
msgid "Some items cannot be synchronised."
|
||||
msgstr "一些项目无法被同步。"
|
||||
|
||||
@@ -852,9 +853,6 @@ msgstr "同步"
|
||||
msgid "Notebooks"
|
||||
msgstr "笔记本"
|
||||
|
||||
msgid "Searches"
|
||||
msgstr "搜索历史"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr "请选择最先删除的笔记或笔记本。"
|
||||
@@ -1327,6 +1325,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."
|
||||
@@ -1338,6 +1339,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.103",
|
||||
"version": "1.0.104",
|
||||
"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.103",
|
||||
"version": "1.0.104",
|
||||
"bin": {
|
||||
"joplin": "./main.js"
|
||||
},
|
||||
|
@@ -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]);
|
||||
|
@@ -16,6 +16,7 @@ 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');
|
||||
@@ -25,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_ = [];
|
||||
@@ -51,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;
|
||||
@@ -247,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;
|
||||
@@ -306,8 +301,9 @@ function asyncTest(callback) {
|
||||
await callback();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
done();
|
||||
}
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -448,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: () => {
|
||||
|
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/help/e2ee')}} href="#">https://joplin.cozic.net/help/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}
|
||||
|
@@ -370,7 +370,7 @@ class NoteTextComponent extends React.Component {
|
||||
webviewReady: true,
|
||||
});
|
||||
|
||||
if (Setting.value('env') === 'dev') this.webview_.openDevTools();
|
||||
// if (Setting.value('env') === 'dev') this.webview_.openDevTools();
|
||||
}
|
||||
|
||||
webview_ref(element) {
|
||||
|
@@ -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') },
|
||||
|
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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,5 +1,6 @@
|
||||
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');
|
||||
|
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
2
ElectronClient/app/package-lock.json
generated
2
ElectronClient/app/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Joplin",
|
||||
"version": "1.0.79",
|
||||
"version": "1.0.80",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Joplin",
|
||||
"version": "1.0.79",
|
||||
"version": "1.0.80",
|
||||
"description": "Joplin for Desktop",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
@@ -62,6 +62,7 @@ globalStyle.icon = {
|
||||
globalStyle.lineInput = {
|
||||
color: globalStyle.color,
|
||||
backgroundColor: globalStyle.backgroundColor,
|
||||
fontFamily: globalStyle.fontFamily,
|
||||
};
|
||||
|
||||
globalStyle.textStyle = {
|
||||
|
25
README.md
25
README.md
@@ -20,15 +20,15 @@ Three types of applications are available: for the **desktop** (Windows, macOS a
|
||||
|
||||
Operating System | Download
|
||||
-----------------|--------
|
||||
Windows | <a href='https://github.com/laurent22/joplin/releases/download/v1.0.78/Joplin-Setup-1.0.78.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.78/Joplin-1.0.78.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.78/Joplin-1.0.78-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.79/Joplin-Setup-1.0.79.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.79/Joplin-1.0.79.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.79/Joplin-1.0.79-x86_64.AppImage'><img alt='Get it on Linux' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeLinux.png'/></a>
|
||||
|
||||
## Mobile applications
|
||||
|
||||
Operating System | Download | Alt. Download
|
||||
-----------------|----------|----------------
|
||||
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeAndroid.png'/></a> | or [Download APK File](https://github.com/laurent22/joplin-android/releases/download/android-v1.0.112/joplin-v1.0.112.apk)
|
||||
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeAndroid.png'/></a> | or [Download APK File](https://github.com/laurent22/joplin-android/releases/download/android-v1.0.114/joplin-v1.0.114.apk)
|
||||
iOS | <a href='https://itunes.apple.com/us/app/joplin/id1315599797'><img alt='Get it on the App Store' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeIOS.png'/></a> | -
|
||||
|
||||
## Terminal application
|
||||
@@ -48,7 +48,7 @@ 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).
|
||||
|
||||
# Features
|
||||
|
||||
@@ -81,7 +81,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
|
||||
|
||||
@@ -89,7 +89,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
|
||||
|
||||
@@ -149,9 +149,9 @@ On the **terminal application**, to initiate the synchronisation process, type `
|
||||
|
||||
# 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/help/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/help/spec).
|
||||
|
||||
# Attachments / Resources
|
||||
|
||||
@@ -209,7 +209,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
|
||||
|
||||
@@ -235,15 +235,16 @@ Current translations:
|
||||
---|---|---|---|---
|
||||
 | Basque | [eu](https://github.com/laurent22/joplin/blob/master/CliClient/locales/eu.po) | juan.abasolo@ehu.eus | 79%
|
||||
 | Croatian | [hr_HR](https://github.com/laurent22/joplin/blob/master/CliClient/locales/hr_HR.po) | Hrvoje Mandić <trbuhom@net.hr> | 64%
|
||||
 | Czech | [cs_CZ](https://github.com/laurent22/joplin/blob/master/CliClient/locales/cs_CZ.po) | Lukas Helebrandt <lukas@aiya.cz> | 99%
|
||||
 | Dansk | [da_DK](https://github.com/laurent22/joplin/blob/master/CliClient/locales/da_DK.po) | | 99%
|
||||
 | Deutsch | [de_DE](https://github.com/laurent22/joplin/blob/master/CliClient/locales/de_DE.po) | Tobias Grasse <mail@tobias-grasse.net> | 98%
|
||||
 | 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> | 99%
|
||||
 | Español | [es_ES](https://github.com/laurent22/joplin/blob/master/CliClient/locales/es_ES.po) | Fernando Martín <f@mrtn.es> | 98%
|
||||
 | 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) | | 79%
|
||||
 | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/master/CliClient/locales/pt_BR.po) | Renato Nunes Bastos <rnbastos@gmail.com> | 97%
|
||||
 | Русский | [ru_RU](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ru_RU.po) | Artyom Karlov <artyom.karlov@gmail.com> | 99%
|
||||
 | Русский | [ru_RU](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ru_RU.po) | Artyom Karlov <artyom.karlov@gmail.com> | 98%
|
||||
 | 中文 (简体) | [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) | | 64%
|
||||
<!-- LOCALE-TABLE-AUTO-GENERATED -->
|
||||
|
@@ -90,8 +90,8 @@ android {
|
||||
applicationId "net.cozic.joplin"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 22
|
||||
versionCode 2097290
|
||||
versionName "1.0.112"
|
||||
versionCode 2097292
|
||||
versionName "1.0.114"
|
||||
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.19</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>13</string>
|
||||
<string>19</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,6 +29,7 @@ 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');
|
||||
@@ -38,6 +39,7 @@ SyncTargetRegistry.addClass(SyncTargetOneDrive);
|
||||
SyncTargetRegistry.addClass(SyncTargetOneDriveDev);
|
||||
SyncTargetRegistry.addClass(SyncTargetNextcloud);
|
||||
SyncTargetRegistry.addClass(SyncTargetWebDAV);
|
||||
SyncTargetRegistry.addClass(SyncTargetDropbox);
|
||||
|
||||
class BaseApplication {
|
||||
|
||||
@@ -421,8 +423,14 @@ 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'));
|
||||
}
|
||||
|
@@ -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;
|
@@ -334,8 +334,12 @@ class MdToHtml {
|
||||
if (closeTag) {
|
||||
if (closeTag === 'a') {
|
||||
const currentAnchorAttrs = anchorAttrs.pop();
|
||||
const previousContent = output.length ? output[output.length - 1].trim() : '';
|
||||
const anchorHref = this.getAttr_(currentAnchorAttrs, 'href', '').trim();
|
||||
|
||||
// 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
|
||||
@@ -349,10 +353,11 @@ class MdToHtml {
|
||||
// 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)'));
|
||||
}
|
||||
|
||||
// if (this.urldecode_(previousContent) === htmlentities(this.urldecode_(anchorHref))) {
|
||||
// output.pop();
|
||||
// output.push(_('(Link)'));
|
||||
// }
|
||||
|
||||
output.push(this.renderCloseLink_(currentAnchorAttrs, options));
|
||||
} else {
|
||||
|
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;
|
||||
}
|
||||
|
||||
|
@@ -35,16 +35,17 @@ class ModalDialog extends React.Component {
|
||||
modalContentWrapper2: {
|
||||
flex:1,
|
||||
},
|
||||
title: {
|
||||
title: Object.assign({}, theme.normalText, {
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: theme.dividerColor,
|
||||
paddingBottom: 10,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
}),
|
||||
buttonRow: {
|
||||
flexDirection: 'row',
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: theme.dividerColor,
|
||||
paddingTop: 10,
|
||||
},
|
||||
};
|
||||
|
||||
|
@@ -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,30 +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.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';
|
||||
@@ -76,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 };
|
@@ -65,7 +65,7 @@ class NoteTagsDialogComponent extends React.Component {
|
||||
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>{tag.title}</Text>
|
||||
<Icon name={iconName} style={this.styles().tagCheckbox}/><Text style={this.styles().tagText}>{tag.title}</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
@@ -114,7 +114,6 @@ class NoteTagsDialogComponent extends React.Component {
|
||||
|
||||
tagListData.sort((a, b) => {
|
||||
return naturalCompare.caseInsensitive(a.title, b.title);
|
||||
//return a.title.toLowerCase() < b.title.toLowerCase() ? -1 : +1;
|
||||
});
|
||||
|
||||
this.setState({ tagListData: tagListData });
|
||||
@@ -137,9 +136,11 @@ class NoteTagsDialogComponent extends React.Component {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
tagText: Object.assign({}, theme.normalText),
|
||||
tagCheckbox: {
|
||||
marginRight: 5,
|
||||
marginRight: 8,
|
||||
fontSize: 20,
|
||||
color: theme.color,
|
||||
},
|
||||
newTagBox: {
|
||||
flexDirection:'row',
|
||||
@@ -149,6 +150,8 @@ class NoteTagsDialogComponent extends React.Component {
|
||||
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);
|
||||
@@ -161,7 +164,7 @@ class NoteTagsDialogComponent extends React.Component {
|
||||
const dialogContent = (
|
||||
<View style={{flex:1}}>
|
||||
<View style={this.styles().newTagBox}>
|
||||
<Text>{_('New tags:')}</Text><TextInput value={this.state.newTags} onChangeText={value => { this.setState({ newTags: value }) }} style={{flex:1}}/>
|
||||
<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}
|
||||
|
@@ -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/help/e2ee') }}><Text>https://joplin.cozic.net/help/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>
|
||||
|
@@ -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() }}>
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -206,7 +206,7 @@ class JoplinDatabase extends Database {
|
||||
|
||||
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.
|
||||
|
@@ -99,7 +99,7 @@ class Setting extends BaseModel {
|
||||
}},
|
||||
'noteVisiblePanes': { value: ['editor', 'viewer'], type: Setting.TYPE_ARRAY, public: false, appTypes: ['desktop'] },
|
||||
'showAdvancedOptions': { value: false, type: Setting.TYPE_BOOL, public: true, appTypes: ['mobile' ], label: () => _('Show advanced options') },
|
||||
'sync.target': { value: SyncTargetRegistry.nameToId('onedrive'), type: Setting.TYPE_INT, isEnum: true, public: true, label: () => _('Synchronisation target'), description: (appType) => { return appType !== 'cli' ? null : _('The target to synchonise to. Each sync target may have additional parameters which are named as `sync.NUM.NAME` (all documented below).') }, options: () => {
|
||||
'sync.target': { value: SyncTargetRegistry.nameToId('dropbox'), type: Setting.TYPE_INT, isEnum: true, public: true, label: () => _('Synchronisation target'), description: (appType) => { return appType !== 'cli' ? null : _('The target to synchonise to. Each sync target may have additional parameters which are named as `sync.NUM.NAME` (all documented below).') }, options: () => {
|
||||
return SyncTargetRegistry.idAndLabelPlainObject();
|
||||
}},
|
||||
|
||||
@@ -121,12 +121,14 @@ class Setting extends BaseModel {
|
||||
|
||||
'sync.3.auth': { value: '', type: Setting.TYPE_STRING, public: false },
|
||||
'sync.4.auth': { value: '', type: Setting.TYPE_STRING, public: false },
|
||||
'sync.7.auth': { value: '', type: Setting.TYPE_STRING, public: false },
|
||||
'sync.1.context': { value: '', type: Setting.TYPE_STRING, public: false },
|
||||
'sync.2.context': { value: '', type: Setting.TYPE_STRING, public: false },
|
||||
'sync.3.context': { value: '', type: Setting.TYPE_STRING, public: false },
|
||||
'sync.4.context': { value: '', type: Setting.TYPE_STRING, public: false },
|
||||
'sync.5.context': { value: '', type: Setting.TYPE_STRING, public: false },
|
||||
'sync.6.context': { value: '', type: Setting.TYPE_STRING, public: false },
|
||||
'sync.7.context': { value: '', type: Setting.TYPE_STRING, public: false },
|
||||
};
|
||||
|
||||
return this.metadata_;
|
||||
|
@@ -11,6 +11,10 @@ parameters_.dev = {
|
||||
id: '606fd4d7-4dfb-4310-b8b7-a47d96aa22b6',
|
||||
secret: 'qabchuPYL7931$ePDEQ3~_$',
|
||||
},
|
||||
dropbox: {
|
||||
id: 'cx9li9ur8taq1z7',
|
||||
secret: 'i8f9a1mvx3bijrt',
|
||||
},
|
||||
};
|
||||
|
||||
parameters_.prod = {
|
||||
@@ -22,6 +26,10 @@ parameters_.prod = {
|
||||
id: '606fd4d7-4dfb-4310-b8b7-a47d96aa22b6',
|
||||
secret: 'qabchuPYL7931$ePDEQ3~_$',
|
||||
},
|
||||
dropbox: {
|
||||
id: 'm044w3cvmxhzvop',
|
||||
secret: 'r298deqisz0od56',
|
||||
},
|
||||
};
|
||||
|
||||
function parameters(env = null) {
|
||||
|
@@ -70,7 +70,7 @@ reg.scheduleSync = async (delay = null, syncOptions = null) => {
|
||||
|
||||
const syncTargetId = Setting.value('sync.target');
|
||||
|
||||
if (!reg.syncTarget(syncTargetId).isAuthenticated()) {
|
||||
if (!await reg.syncTarget(syncTargetId).isAuthenticated()) {
|
||||
reg.logger().info('Synchroniser is missing credentials - manual sync required to authenticate.');
|
||||
promiseResolve();
|
||||
return;
|
||||
|
@@ -111,10 +111,9 @@ function shimInit() {
|
||||
const urlParse = require('url').parse;
|
||||
|
||||
url = urlParse(url.trim());
|
||||
const method = options.method ? options.method : 'GET';
|
||||
const http = url.protocol.toLowerCase() == 'http:' ? require('follow-redirects').http : require('follow-redirects').https;
|
||||
const headers = options.headers ? options.headers : {};
|
||||
const method = options.method ? options.method : 'GET';
|
||||
if (method != 'GET') throw new Error('Only GET is supported');
|
||||
const filePath = options.path;
|
||||
|
||||
function makeResponse(response) {
|
||||
@@ -143,7 +142,7 @@ function shimInit() {
|
||||
// Note: relative paths aren't supported
|
||||
const file = fs.createWriteStream(filePath);
|
||||
|
||||
const request = http.get(requestOptions, function(response) {
|
||||
const request = http.request(requestOptions, function(response) {
|
||||
response.pipe(file);
|
||||
|
||||
file.on('finish', function() {
|
||||
@@ -157,6 +156,8 @@ function shimInit() {
|
||||
fs.unlink(filePath);
|
||||
reject(error);
|
||||
});
|
||||
|
||||
request.end();
|
||||
} catch(error) {
|
||||
fs.unlink(filePath);
|
||||
reject(error);
|
||||
@@ -180,6 +181,13 @@ function shimInit() {
|
||||
return Buffer.byteLength(string, 'utf-8');
|
||||
}
|
||||
|
||||
shim.Buffer = Buffer;
|
||||
|
||||
shim.openUrl = (url) => {
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
bridge().openExternal(url)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = { shimInit };
|
@@ -6,6 +6,7 @@ const { generateSecureRandom } = require('react-native-securerandom');
|
||||
const FsDriverRN = require('lib/fs-driver-rn.js').FsDriverRN;
|
||||
const urlValidator = require('valid-url');
|
||||
const { Buffer } = require('buffer');
|
||||
const { Linking } = require('react-native');
|
||||
|
||||
function shimInit() {
|
||||
shim.Geolocation = GeolocationReact;
|
||||
@@ -116,6 +117,12 @@ function shimInit() {
|
||||
shim.stringByteLength = function(string) {
|
||||
return Buffer.byteLength(string, 'utf-8');
|
||||
}
|
||||
|
||||
shim.Buffer = Buffer;
|
||||
|
||||
shim.openUrl = (url) => {
|
||||
Linking.openURL(url);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { shimInit };
|
@@ -85,6 +85,9 @@ shim.fetchRequestCanBeRetried = function(error) {
|
||||
// Code: ETIMEDOUT
|
||||
if (error.code === 'ETIMEDOUT') return true;
|
||||
|
||||
// ECONNREFUSED is generally temporary
|
||||
if (error.code === 'ECONNREFUSED') return true;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -129,5 +132,7 @@ shim.clearInterval = function(id) {
|
||||
shim.stringByteLength = function(string) { throw new Error('Not implemented'); }
|
||||
shim.detectAndSetLocale = null;
|
||||
shim.attachFileToNote = async (note, filePath) => {}
|
||||
shim.Buffer = null;
|
||||
shim.openUrl = () => { throw new Error('Not implemented'); }
|
||||
|
||||
module.exports = { shim };
|
1
ReactNativeClient/locales/cs_CZ.json
Normal file
1
ReactNativeClient/locales/cs_CZ.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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,5 +1,6 @@
|
||||
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');
|
||||
|
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
@@ -36,6 +36,7 @@ const { WelcomeScreen } = require('lib/components/screens/welcome.js');
|
||||
const { SearchScreen } = require('lib/components/screens/search.js');
|
||||
const { OneDriveLoginScreen } = require('lib/components/screens/onedrive-login.js');
|
||||
const { EncryptionConfigScreen } = require('lib/components/screens/encryption-config.js');
|
||||
const { DropboxLoginScreen } = require('lib/components/screens/dropbox-login.js');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const { MenuContext } = require('react-native-popup-menu');
|
||||
const { SideMenu } = require('lib/components/side-menu.js');
|
||||
@@ -55,10 +56,12 @@ const SyncTargetFilesystem = require('lib/SyncTargetFilesystem.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');
|
||||
SyncTargetRegistry.addClass(SyncTargetOneDrive);
|
||||
SyncTargetRegistry.addClass(SyncTargetOneDriveDev);
|
||||
SyncTargetRegistry.addClass(SyncTargetNextcloud);
|
||||
SyncTargetRegistry.addClass(SyncTargetWebDAV);
|
||||
SyncTargetRegistry.addClass(SyncTargetDropbox);
|
||||
|
||||
// Disabled because not fully working
|
||||
//SyncTargetRegistry.addClass(SyncTargetFilesystem);
|
||||
@@ -365,16 +368,6 @@ async function initialize(dispatch) {
|
||||
await db.open({ name: 'joplin.sqlite' })
|
||||
} else {
|
||||
await db.open({ name: 'joplin-68.sqlite' })
|
||||
//await db.open({ name: 'joplin-67.sqlite' })
|
||||
|
||||
// await db.exec('DELETE FROM notes');
|
||||
// await db.exec('DELETE FROM folders');
|
||||
// await db.exec('DELETE FROM tags');
|
||||
// await db.exec('DELETE FROM note_tags');
|
||||
// await db.exec('DELETE FROM resources');
|
||||
// await db.exec('DELETE FROM deleted_items');
|
||||
|
||||
// await db.exec('UPDATE notes SET is_conflict = 1 where id like "546f%"');
|
||||
}
|
||||
|
||||
reg.logger().info('Database is ready.');
|
||||
@@ -559,6 +552,7 @@ class AppComponent extends React.Component {
|
||||
Note: { screen: NoteScreen },
|
||||
Folder: { screen: FolderScreen },
|
||||
OneDriveLogin: { screen: OneDriveLoginScreen },
|
||||
DropboxLogin: { screen: DropboxLoginScreen },
|
||||
EncryptionConfig: { screen: EncryptionConfigScreen },
|
||||
Log: { screen: LogScreen },
|
||||
Status: { screen: StatusScreen },
|
||||
|
@@ -298,7 +298,7 @@ function markdownToHtml(md) {
|
||||
|
||||
function renderFileToHtml(sourcePath, targetPath, params) {
|
||||
const md = fs.readFileSync(sourcePath, 'utf8');
|
||||
params.baseUrl = 'http://joplin.cozic.net';
|
||||
params.baseUrl = 'https://joplin.cozic.net';
|
||||
params.imageBaseUrl = params.baseUrl + '/images';
|
||||
const html = Mustache.render(markdownToHtml(md), params);
|
||||
fs.writeFileSync(targetPath, html);
|
||||
|
@@ -186,17 +186,17 @@
|
||||
<div class="container">
|
||||
|
||||
<div class="header">
|
||||
<a class="forkme" href="https://github.com/laurent22/joplin"><img src="http://joplin.cozic.net/images/ForkMe.png"/></a>
|
||||
<h1 id="joplin"><img class="title-icon" src="http://joplin.cozic.net/images/Icon512.png">oplin</h1>
|
||||
<a class="forkme" href="https://github.com/laurent22/joplin"><img src="https://joplin.cozic.net/images/ForkMe.png"/></a>
|
||||
<h1 id="joplin"><img class="title-icon" src="https://joplin.cozic.net/images/Icon512.png">oplin</h1>
|
||||
<p class="sub-title">An open source note taking and to-do application with synchronisation capabilities.</p>
|
||||
</div>
|
||||
|
||||
<div class="nav-wrapper">
|
||||
<div class="nav">
|
||||
<ul>
|
||||
<li class=""><a href="http://joplin.cozic.net/" title="Home"><i class="fa fa-home"></i></a></li>
|
||||
<li class=""><a href="http://joplin.cozic.net/terminal" title="Terminal"><i class="fa fa-terminal"></i></a></li>
|
||||
<li class="selected"><a href="http://joplin.cozic.net/desktop" title="Desktop"><i class="fa fa-desktop"></i></a></li>
|
||||
<li class=""><a href="https://joplin.cozic.net/" title="Home"><i class="fa fa-home"></i></a></li>
|
||||
<li class=""><a href="https://joplin.cozic.net/terminal" title="Terminal"><i class="fa fa-terminal"></i></a></li>
|
||||
<li class="selected"><a href="https://joplin.cozic.net/desktop" title="Desktop"><i class="fa fa-desktop"></i></a></li>
|
||||
</ul>
|
||||
<div class="nav-right">
|
||||
<iframe class="share-btn" src="https://www.facebook.com/plugins/share_button.php?href=http%3A%2F%2Fjoplin.cozic.net&layout=button&size=small&mobile_iframe=true&width=60&height=20&appId" width="60" height="20" style="border:none;overflow:hidden" scrolling="no" frameborder="0" allowTransparency="true"></iframe>
|
||||
|
@@ -186,17 +186,17 @@
|
||||
<div class="container">
|
||||
|
||||
<div class="header">
|
||||
<a class="forkme" href="https://github.com/laurent22/joplin"><img src="http://joplin.cozic.net/images/ForkMe.png"/></a>
|
||||
<h1 id="joplin"><img class="title-icon" src="http://joplin.cozic.net/images/Icon512.png">oplin</h1>
|
||||
<a class="forkme" href="https://github.com/laurent22/joplin"><img src="https://joplin.cozic.net/images/ForkMe.png"/></a>
|
||||
<h1 id="joplin"><img class="title-icon" src="https://joplin.cozic.net/images/Icon512.png">oplin</h1>
|
||||
<p class="sub-title">An open source note taking and to-do application with synchronisation capabilities.</p>
|
||||
</div>
|
||||
|
||||
<div class="nav-wrapper">
|
||||
<div class="nav">
|
||||
<ul>
|
||||
<li class=""><a href="http://joplin.cozic.net/" title="Home"><i class="fa fa-home"></i></a></li>
|
||||
<li class=""><a href="http://joplin.cozic.net/terminal" title="Terminal"><i class="fa fa-terminal"></i></a></li>
|
||||
<li class=""><a href="http://joplin.cozic.net/desktop" title="Desktop"><i class="fa fa-desktop"></i></a></li>
|
||||
<li class=""><a href="https://joplin.cozic.net/" title="Home"><i class="fa fa-home"></i></a></li>
|
||||
<li class=""><a href="https://joplin.cozic.net/terminal" title="Terminal"><i class="fa fa-terminal"></i></a></li>
|
||||
<li class=""><a href="https://joplin.cozic.net/desktop" title="Desktop"><i class="fa fa-desktop"></i></a></li>
|
||||
</ul>
|
||||
<div class="nav-right">
|
||||
<iframe class="share-btn" src="https://www.facebook.com/plugins/share_button.php?href=http%3A%2F%2Fjoplin.cozic.net&layout=button&size=small&mobile_iframe=true&width=60&height=20&appId" width="60" height="20" style="border:none;overflow:hidden" scrolling="no" frameborder="0" allowTransparency="true"></iframe>
|
||||
@@ -219,7 +219,7 @@
|
||||
<p>There are other ways to support the development of Joplin:</p>
|
||||
<ul>
|
||||
<li>Consider rating the app on <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">Google Play</a> or <a href="https://itunes.apple.com/us/app/joplin/id1315599797">App Store</a>.</li>
|
||||
<li><a href="http://joplin.cozic.net/#localisation">Create of update a translation</a>.</li>
|
||||
<li><a href="https://joplin.cozic.net/#localisation">Create of update a translation</a>.</li>
|
||||
<li>Help with the <a href="https://github.com/laurent22/joplin">documentation</a>.</li>
|
||||
</ul>
|
||||
|
||||
|
@@ -186,17 +186,17 @@
|
||||
<div class="container">
|
||||
|
||||
<div class="header">
|
||||
<a class="forkme" href="https://github.com/laurent22/joplin"><img src="http://joplin.cozic.net/images/ForkMe.png"/></a>
|
||||
<h1 id="joplin"><img class="title-icon" src="http://joplin.cozic.net/images/Icon512.png">oplin</h1>
|
||||
<a class="forkme" href="https://github.com/laurent22/joplin"><img src="https://joplin.cozic.net/images/ForkMe.png"/></a>
|
||||
<h1 id="joplin"><img class="title-icon" src="https://joplin.cozic.net/images/Icon512.png">oplin</h1>
|
||||
<p class="sub-title">An open source note taking and to-do application with synchronisation capabilities.</p>
|
||||
</div>
|
||||
|
||||
<div class="nav-wrapper">
|
||||
<div class="nav">
|
||||
<ul>
|
||||
<li class=""><a href="http://joplin.cozic.net/" title="Home"><i class="fa fa-home"></i></a></li>
|
||||
<li class=""><a href="http://joplin.cozic.net/terminal" title="Terminal"><i class="fa fa-terminal"></i></a></li>
|
||||
<li class=""><a href="http://joplin.cozic.net/desktop" title="Desktop"><i class="fa fa-desktop"></i></a></li>
|
||||
<li class=""><a href="https://joplin.cozic.net/" title="Home"><i class="fa fa-home"></i></a></li>
|
||||
<li class=""><a href="https://joplin.cozic.net/terminal" title="Terminal"><i class="fa fa-terminal"></i></a></li>
|
||||
<li class=""><a href="https://joplin.cozic.net/desktop" title="Desktop"><i class="fa fa-desktop"></i></a></li>
|
||||
</ul>
|
||||
<div class="nav-right">
|
||||
<iframe class="share-btn" src="https://www.facebook.com/plugins/share_button.php?href=http%3A%2F%2Fjoplin.cozic.net&layout=button&size=small&mobile_iframe=true&width=60&height=20&appId" width="60" height="20" style="border:none;overflow:hidden" scrolling="no" frameborder="0" allowTransparency="true"></iframe>
|
||||
@@ -226,7 +226,7 @@
|
||||
<h1 id="disabling-e2ee">Disabling E2EE</h1>
|
||||
<p>Follow the same procedure as above but instead disable E2EE on each device one by one. Again it might be simpler to do it one device at a time and to wait every time for the synchronisation to complete.</p>
|
||||
<h1 id="technical-specification">Technical specification</h1>
|
||||
<p>For a more technical description, mostly relevant for development or to review the method being used, please see the <a href="http://joplin.cozic.net/help/spec">Encryption specification</a>.</p>
|
||||
<p>For a more technical description, mostly relevant for development or to review the method being used, please see the <a href="https://joplin.cozic.net/help/spec">Encryption specification</a>.</p>
|
||||
|
||||
<script>
|
||||
function stickyHeader() {
|
||||
|
@@ -186,17 +186,17 @@
|
||||
<div class="container">
|
||||
|
||||
<div class="header">
|
||||
<a class="forkme" href="https://github.com/laurent22/joplin"><img src="http://joplin.cozic.net/images/ForkMe.png"/></a>
|
||||
<h1 id="joplin"><img class="title-icon" src="http://joplin.cozic.net/images/Icon512.png">oplin</h1>
|
||||
<a class="forkme" href="https://github.com/laurent22/joplin"><img src="https://joplin.cozic.net/images/ForkMe.png"/></a>
|
||||
<h1 id="joplin"><img class="title-icon" src="https://joplin.cozic.net/images/Icon512.png">oplin</h1>
|
||||
<p class="sub-title">An open source note taking and to-do application with synchronisation capabilities.</p>
|
||||
</div>
|
||||
|
||||
<div class="nav-wrapper">
|
||||
<div class="nav">
|
||||
<ul>
|
||||
<li class=""><a href="http://joplin.cozic.net/" title="Home"><i class="fa fa-home"></i></a></li>
|
||||
<li class=""><a href="http://joplin.cozic.net/terminal" title="Terminal"><i class="fa fa-terminal"></i></a></li>
|
||||
<li class=""><a href="http://joplin.cozic.net/desktop" title="Desktop"><i class="fa fa-desktop"></i></a></li>
|
||||
<li class=""><a href="https://joplin.cozic.net/" title="Home"><i class="fa fa-home"></i></a></li>
|
||||
<li class=""><a href="https://joplin.cozic.net/terminal" title="Terminal"><i class="fa fa-terminal"></i></a></li>
|
||||
<li class=""><a href="https://joplin.cozic.net/desktop" title="Desktop"><i class="fa fa-desktop"></i></a></li>
|
||||
</ul>
|
||||
<div class="nav-right">
|
||||
<iframe class="share-btn" src="https://www.facebook.com/plugins/share_button.php?href=http%3A%2F%2Fjoplin.cozic.net&layout=button&size=small&mobile_iframe=true&width=60&height=20&appId" width="60" height="20" style="border:none;overflow:hidden" scrolling="no" frameborder="0" allowTransparency="true"></iframe>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user