You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2026-01-02 00:08:04 +02:00
Compare commits
28 Commits
mobile_plu
...
spellcheck
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2bfbeb002d | ||
|
|
6d2bad01e6 | ||
|
|
3847831d80 | ||
|
|
44462f4d71 | ||
|
|
4d20589773 | ||
|
|
52140ec1a9 | ||
|
|
08c2a7ad64 | ||
|
|
d19796f14c | ||
|
|
e4f53a48d2 | ||
|
|
154163bd6c | ||
|
|
4502414934 | ||
|
|
86c471afcd | ||
|
|
d0c8693c26 | ||
|
|
8a461a66e6 | ||
|
|
8cfe4b0f82 | ||
|
|
8e783b78fe | ||
|
|
9077bb13d5 | ||
|
|
d45f7ae523 | ||
|
|
5dd619a766 | ||
|
|
0c8708a363 | ||
|
|
28c1676ed9 | ||
|
|
421ef885cd | ||
|
|
3582aeaec8 | ||
|
|
d07b2d4469 | ||
|
|
e50e548e25 | ||
|
|
84a87c10f7 | ||
|
|
559e88e35d | ||
|
|
da8469a81b |
@@ -126,6 +126,7 @@ ElectronClient/gui/MainScreen/commands/toggleSideBar.js
|
||||
ElectronClient/gui/MainScreen/commands/toggleVisiblePanes.js
|
||||
ElectronClient/gui/MainScreen/MainScreen.js
|
||||
ElectronClient/gui/MenuBar.js
|
||||
ElectronClient/gui/menuCommandNames.js
|
||||
ElectronClient/gui/MultiNoteActions.js
|
||||
ElectronClient/gui/NoteContentPropertiesDialog.js
|
||||
ElectronClient/gui/NoteEditor/commands/editorCommandDeclarations.js
|
||||
@@ -148,8 +149,10 @@ ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useListIdent.js
|
||||
ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useScrollUtils.js
|
||||
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/styles/index.js
|
||||
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js
|
||||
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/utils/setupContextMenu.js
|
||||
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/utils/useScroll.js
|
||||
ElectronClient/gui/NoteEditor/NoteEditor.js
|
||||
ElectronClient/gui/NoteEditor/NoteTitle/NoteTitleBar.js
|
||||
ElectronClient/gui/NoteEditor/styles/index.js
|
||||
ElectronClient/gui/NoteEditor/utils/contextMenu.js
|
||||
ElectronClient/gui/NoteEditor/utils/index.js
|
||||
@@ -206,6 +209,7 @@ ElectronClient/services/plugins/PluginRunner.js
|
||||
ElectronClient/services/plugins/UserWebview.js
|
||||
ElectronClient/services/plugins/UserWebviewDialog.js
|
||||
ElectronClient/services/plugins/UserWebviewDialogButtonBar.js
|
||||
ElectronClient/services/spellChecker/SpellCheckerServiceDriverNative.js
|
||||
ReactNativeClient/lib/AsyncActionQueue.js
|
||||
ReactNativeClient/lib/BaseApplication.js
|
||||
ReactNativeClient/lib/checkPermissions.js
|
||||
@@ -329,6 +333,8 @@ ReactNativeClient/lib/services/rest/errors.js
|
||||
ReactNativeClient/lib/services/searchengine/filterParser.js
|
||||
ReactNativeClient/lib/services/searchengine/queryBuilder.js
|
||||
ReactNativeClient/lib/services/SettingUtils.js
|
||||
ReactNativeClient/lib/services/spellChecker/SpellCheckerService.js
|
||||
ReactNativeClient/lib/services/spellChecker/SpellCheckerServiceDriverBase.js
|
||||
ReactNativeClient/lib/services/synchronizer/gui/useSyncTargetUpgrade.js
|
||||
ReactNativeClient/lib/services/synchronizer/LockHandler.js
|
||||
ReactNativeClient/lib/services/synchronizer/MigrationHandler.js
|
||||
@@ -351,6 +357,7 @@ ReactNativeClient/lib/themes/oledDark.js
|
||||
ReactNativeClient/lib/themes/solarizedDark.js
|
||||
ReactNativeClient/lib/themes/solarizedLight.js
|
||||
ReactNativeClient/lib/themes/type.js
|
||||
ReactNativeClient/lib/time.js
|
||||
ReactNativeClient/lib/uuid.js
|
||||
ReactNativeClient/lib/versionInfo.js
|
||||
ReactNativeClient/PluginAssetsLoader.js
|
||||
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -120,6 +120,7 @@ ElectronClient/gui/MainScreen/commands/toggleSideBar.js
|
||||
ElectronClient/gui/MainScreen/commands/toggleVisiblePanes.js
|
||||
ElectronClient/gui/MainScreen/MainScreen.js
|
||||
ElectronClient/gui/MenuBar.js
|
||||
ElectronClient/gui/menuCommandNames.js
|
||||
ElectronClient/gui/MultiNoteActions.js
|
||||
ElectronClient/gui/NoteContentPropertiesDialog.js
|
||||
ElectronClient/gui/NoteEditor/commands/editorCommandDeclarations.js
|
||||
@@ -142,8 +143,10 @@ ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useListIdent.js
|
||||
ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useScrollUtils.js
|
||||
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/styles/index.js
|
||||
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js
|
||||
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/utils/setupContextMenu.js
|
||||
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/utils/useScroll.js
|
||||
ElectronClient/gui/NoteEditor/NoteEditor.js
|
||||
ElectronClient/gui/NoteEditor/NoteTitle/NoteTitleBar.js
|
||||
ElectronClient/gui/NoteEditor/styles/index.js
|
||||
ElectronClient/gui/NoteEditor/utils/contextMenu.js
|
||||
ElectronClient/gui/NoteEditor/utils/index.js
|
||||
@@ -200,6 +203,7 @@ ElectronClient/services/plugins/PluginRunner.js
|
||||
ElectronClient/services/plugins/UserWebview.js
|
||||
ElectronClient/services/plugins/UserWebviewDialog.js
|
||||
ElectronClient/services/plugins/UserWebviewDialogButtonBar.js
|
||||
ElectronClient/services/spellChecker/SpellCheckerServiceDriverNative.js
|
||||
ReactNativeClient/lib/AsyncActionQueue.js
|
||||
ReactNativeClient/lib/BaseApplication.js
|
||||
ReactNativeClient/lib/checkPermissions.js
|
||||
@@ -323,6 +327,8 @@ ReactNativeClient/lib/services/rest/errors.js
|
||||
ReactNativeClient/lib/services/searchengine/filterParser.js
|
||||
ReactNativeClient/lib/services/searchengine/queryBuilder.js
|
||||
ReactNativeClient/lib/services/SettingUtils.js
|
||||
ReactNativeClient/lib/services/spellChecker/SpellCheckerService.js
|
||||
ReactNativeClient/lib/services/spellChecker/SpellCheckerServiceDriverBase.js
|
||||
ReactNativeClient/lib/services/synchronizer/gui/useSyncTargetUpgrade.js
|
||||
ReactNativeClient/lib/services/synchronizer/LockHandler.js
|
||||
ReactNativeClient/lib/services/synchronizer/MigrationHandler.js
|
||||
@@ -345,6 +351,7 @@ ReactNativeClient/lib/themes/oledDark.js
|
||||
ReactNativeClient/lib/themes/solarizedDark.js
|
||||
ReactNativeClient/lib/themes/solarizedLight.js
|
||||
ReactNativeClient/lib/themes/type.js
|
||||
ReactNativeClient/lib/time.js
|
||||
ReactNativeClient/lib/uuid.js
|
||||
ReactNativeClient/lib/versionInfo.js
|
||||
ReactNativeClient/PluginAssetsLoader.js
|
||||
|
||||
7
.ignore
7
.ignore
@@ -69,6 +69,7 @@ ElectronClient/gui/MainScreen/commands/toggleSideBar.js
|
||||
ElectronClient/gui/MainScreen/commands/toggleVisiblePanes.js
|
||||
ElectronClient/gui/MainScreen/MainScreen.js
|
||||
ElectronClient/gui/MenuBar.js
|
||||
ElectronClient/gui/menuCommandNames.js
|
||||
ElectronClient/gui/MultiNoteActions.js
|
||||
ElectronClient/gui/NoteContentPropertiesDialog.js
|
||||
ElectronClient/gui/NoteEditor/commands/editorCommandDeclarations.js
|
||||
@@ -91,8 +92,10 @@ ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useListIdent.js
|
||||
ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useScrollUtils.js
|
||||
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/styles/index.js
|
||||
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js
|
||||
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/utils/setupContextMenu.js
|
||||
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/utils/useScroll.js
|
||||
ElectronClient/gui/NoteEditor/NoteEditor.js
|
||||
ElectronClient/gui/NoteEditor/NoteTitle/NoteTitleBar.js
|
||||
ElectronClient/gui/NoteEditor/styles/index.js
|
||||
ElectronClient/gui/NoteEditor/utils/contextMenu.js
|
||||
ElectronClient/gui/NoteEditor/utils/index.js
|
||||
@@ -149,6 +152,7 @@ ElectronClient/services/plugins/PluginRunner.js
|
||||
ElectronClient/services/plugins/UserWebview.js
|
||||
ElectronClient/services/plugins/UserWebviewDialog.js
|
||||
ElectronClient/services/plugins/UserWebviewDialogButtonBar.js
|
||||
ElectronClient/services/spellChecker/SpellCheckerServiceDriverNative.js
|
||||
ReactNativeClient/lib/AsyncActionQueue.js
|
||||
ReactNativeClient/lib/BaseApplication.js
|
||||
ReactNativeClient/lib/checkPermissions.js
|
||||
@@ -272,6 +276,8 @@ ReactNativeClient/lib/services/rest/errors.js
|
||||
ReactNativeClient/lib/services/searchengine/filterParser.js
|
||||
ReactNativeClient/lib/services/searchengine/queryBuilder.js
|
||||
ReactNativeClient/lib/services/SettingUtils.js
|
||||
ReactNativeClient/lib/services/spellChecker/SpellCheckerService.js
|
||||
ReactNativeClient/lib/services/spellChecker/SpellCheckerServiceDriverBase.js
|
||||
ReactNativeClient/lib/services/synchronizer/gui/useSyncTargetUpgrade.js
|
||||
ReactNativeClient/lib/services/synchronizer/LockHandler.js
|
||||
ReactNativeClient/lib/services/synchronizer/MigrationHandler.js
|
||||
@@ -294,6 +300,7 @@ ReactNativeClient/lib/themes/oledDark.js
|
||||
ReactNativeClient/lib/themes/solarizedDark.js
|
||||
ReactNativeClient/lib/themes/solarizedLight.js
|
||||
ReactNativeClient/lib/themes/type.js
|
||||
ReactNativeClient/lib/time.js
|
||||
ReactNativeClient/lib/uuid.js
|
||||
ReactNativeClient/lib/versionInfo.js
|
||||
ReactNativeClient/PluginAssetsLoader.js
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const yargParser = require('yargs-parser');
|
||||
const { _ } = require('lib/locale');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const stringPadding = require('string-padding');
|
||||
const Logger = require('lib/Logger').default;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ const { app } = require('./app.js');
|
||||
const { _ } = require('lib/locale');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
|
||||
class Command extends BaseCommand {
|
||||
usage() {
|
||||
|
||||
@@ -45,10 +45,12 @@ class Command extends BaseCommand {
|
||||
|
||||
const startDecryption = async () => {
|
||||
this.stdout(_('Starting decryption... Please wait as it may take several minutes depending on how much there is to decrypt.'));
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
const result = await DecryptionWorker.instance().start();
|
||||
|
||||
if (result.error) throw result.error;
|
||||
|
||||
const line = [];
|
||||
line.push(_('Decrypted items: %d', result.decryptedItemCount));
|
||||
if (result.skippedItemCount) line.push(_('Skipped items: %d (use --retry-failed-items to retry decrypting them)', result.skippedItemCount));
|
||||
|
||||
@@ -6,7 +6,7 @@ const Folder = require('lib/models/Folder.js');
|
||||
const Setting = require('lib/models/Setting').default;
|
||||
const Note = require('lib/models/Note.js');
|
||||
const { sprintf } = require('sprintf-js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { cliUtils } = require('./cli-utils.js');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
|
||||
@@ -3,7 +3,7 @@ const { app } = require('./app.js');
|
||||
const { _ } = require('lib/locale');
|
||||
const Tag = require('lib/models/Tag.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
|
||||
class Command extends BaseCommand {
|
||||
usage() {
|
||||
|
||||
@@ -3,7 +3,7 @@ const { app } = require('./app.js');
|
||||
const { _ } = require('lib/locale');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
|
||||
class Command extends BaseCommand {
|
||||
usage() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const Logger = require('lib/Logger').default;
|
||||
const Resource = require('lib/models/Resource.js');
|
||||
const { dirname } = require('lib/path-utils');
|
||||
|
||||
@@ -13,8 +13,10 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 2.3.1\n"
|
||||
"X-Generator: Poedit 2.4.1\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
|
||||
#: CliClient/app/command-cp.js:13
|
||||
msgid ""
|
||||
@@ -315,8 +317,9 @@ msgid "Sync to provided target (defaults to sync.target config value)"
|
||||
msgstr "Synchroniseren naar opgegeven doel (standaard is dit sync.target)"
|
||||
|
||||
#: CliClient/app/command-sync.js:35
|
||||
#, fuzzy
|
||||
msgid "Upgrade the sync target to the latest version."
|
||||
msgstr ""
|
||||
msgstr "Sync target updaten naar de nieuwste versie."
|
||||
|
||||
#: CliClient/app/command-sync.js:81 CliClient/app/command-sync.js:95
|
||||
#: ElectronClient/gui/OneDriveLoginScreen.min.js:40
|
||||
@@ -818,6 +821,8 @@ msgstr "Annuleren"
|
||||
msgid ""
|
||||
"The app is now going to close. Please relaunch it to complete the process."
|
||||
msgstr ""
|
||||
"De applicatie zal nu afsluiten. Gelieve het weer op te starten om het proces "
|
||||
"te voltooien."
|
||||
|
||||
#: ElectronClient/plugins/GotoAnything.min.js:459
|
||||
msgid ""
|
||||
@@ -835,15 +840,14 @@ msgid "Goto Anything..."
|
||||
msgstr "Ga naar Alles..."
|
||||
|
||||
#: ElectronClient/plugins/GotoAnything.js:431
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"Type a note title or part of its content to jump to it. Or type # followed "
|
||||
"by a tag name, or @ followed by a notebook name. Or type : to search for "
|
||||
"commands."
|
||||
msgstr ""
|
||||
"Typ de titel van een notitie of een deel van de inhoud om er naartoe te "
|
||||
"springen. Of typ # gevolgd door de naam van een label, of @ gevolgd door de "
|
||||
"naam van een notitieboek."
|
||||
"springen. Typ # gevolgd door de naam van een label, @ gevolgd door de naam "
|
||||
"van een notitieboek, of : om te zoeken op commando's."
|
||||
|
||||
#: ElectronClient/plugins/GotoAnything.js:463
|
||||
#: ElectronClient/gui/KeymapConfig/utils/getLabel.js:28
|
||||
@@ -884,9 +888,8 @@ msgid "New version: %s"
|
||||
msgstr "Nieuwe versie: %s"
|
||||
|
||||
#: ElectronClient/checkForUpdates.js:154
|
||||
#, fuzzy
|
||||
msgid "Download"
|
||||
msgstr "Gedownload"
|
||||
msgstr "Downloaden"
|
||||
|
||||
#: ElectronClient/checkForUpdates.js:154
|
||||
msgid "Full Release Notes"
|
||||
@@ -1235,13 +1238,15 @@ msgstr "Bezig met creëren van nieuw(e) %s..."
|
||||
|
||||
#: ElectronClient/gui/NoteEditor/NoteEditor.js:379
|
||||
msgid "The following attachments are being watched for changes:"
|
||||
msgstr ""
|
||||
msgstr "De volgende bijlagen worden gecontroleerd op wijzigingen:"
|
||||
|
||||
#: ElectronClient/gui/NoteEditor/NoteEditor.js:382
|
||||
msgid ""
|
||||
"The attachments will no longer be watched when you switch to a different "
|
||||
"note."
|
||||
msgstr ""
|
||||
"De bijlagen worden niet meer gecontroleerd wanneer je naar een andere "
|
||||
"notitie schakelt."
|
||||
|
||||
#: ElectronClient/gui/NoteEditor/NoteEditor.js:387
|
||||
#: ElectronClient/gui/NoteText.min.js:1656
|
||||
@@ -1358,9 +1363,9 @@ msgstr "Eigenschappen van notitie"
|
||||
|
||||
#: ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js:63
|
||||
#: ReactNativeClient/lib/services/KeymapService.js:144
|
||||
#, fuzzy, javascript-format
|
||||
#, javascript-format
|
||||
msgid "Error: %s"
|
||||
msgstr "Fout"
|
||||
msgstr "Fout: %s"
|
||||
|
||||
#: ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js:121
|
||||
#: ElectronClient/gui/Root.min.js:91 ElectronClient/gui/MenuBar.js:391
|
||||
@@ -1370,12 +1375,11 @@ msgstr "Importeren"
|
||||
|
||||
#: ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js:126
|
||||
msgid "Command"
|
||||
msgstr ""
|
||||
msgstr "Commando"
|
||||
|
||||
#: ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js:127
|
||||
#, fuzzy
|
||||
msgid "Keyboard Shortcut"
|
||||
msgstr "Toetsenbordmodus"
|
||||
msgstr "Sneltoets"
|
||||
|
||||
#: ElectronClient/gui/KeymapConfig/utils/getLabel.js:14
|
||||
#: ElectronClient/gui/MenuBar.js:178 ElectronClient/app.js:347
|
||||
@@ -1398,9 +1402,8 @@ msgid "Website and documentation"
|
||||
msgstr "Website en documentatie"
|
||||
|
||||
#: ElectronClient/gui/KeymapConfig/utils/getLabel.js:24
|
||||
#, fuzzy
|
||||
msgid "Hide Joplin"
|
||||
msgstr "Over Joplin"
|
||||
msgstr "Joplin verbergen"
|
||||
|
||||
#: ElectronClient/gui/KeymapConfig/utils/getLabel.js:26
|
||||
#: ElectronClient/gui/MenuBar.js:428
|
||||
@@ -1408,9 +1411,8 @@ msgid "Close Window"
|
||||
msgstr "Venster afsluiten"
|
||||
|
||||
#: ElectronClient/gui/KeymapConfig/utils/getLabel.js:30
|
||||
#, fuzzy
|
||||
msgid "Preferences"
|
||||
msgstr "Voorkeuren..."
|
||||
msgstr "Voorkeuren"
|
||||
|
||||
#: ElectronClient/gui/KeymapConfig/utils/getLabel.js:30
|
||||
#: ElectronClient/gui/Root.min.js:92 ElectronClient/gui/MenuBar.js:304
|
||||
@@ -1420,13 +1422,15 @@ msgstr "Opties"
|
||||
|
||||
#: ElectronClient/gui/KeymapConfig/ShortcutRecorder.js:49
|
||||
msgid "Press the shortcut"
|
||||
msgstr ""
|
||||
msgstr "Druk op de sneltoetsen"
|
||||
|
||||
#: ElectronClient/gui/KeymapConfig/ShortcutRecorder.js:49
|
||||
msgid ""
|
||||
"Press the shortcut and then press ENTER. Or, press BACKSPACE to clear the "
|
||||
"shortcut."
|
||||
msgstr ""
|
||||
"Druk op de sneltoetsen en dan op ENTER. Of, druk op backspace om de "
|
||||
"sneltoets te wissen."
|
||||
|
||||
#: ElectronClient/gui/KeymapConfig/ShortcutRecorder.js:50
|
||||
#: ElectronClient/gui/EncryptionConfigScreen.min.js:96
|
||||
@@ -1436,17 +1440,20 @@ msgstr "Opslaan"
|
||||
|
||||
#: ElectronClient/gui/MainScreen/MainScreen.js:418
|
||||
#: ElectronClient/gui/MainScreen/MainScreen.min.js:301
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"The sync target needs to be upgraded before Joplin can sync. The operation "
|
||||
"may take a few minutes to complete and the app needs to be restarted. To "
|
||||
"proceed please click on the link."
|
||||
msgstr ""
|
||||
"De sync target moet worden geüpdatet voordat Joplin kan synchroniseren. Het "
|
||||
"kan een paar minuten duren en de app moet opnieuw opgestart worden. Om door "
|
||||
"te gaan, klik op de link."
|
||||
|
||||
#: ElectronClient/gui/MainScreen/MainScreen.js:420
|
||||
#: ElectronClient/gui/MainScreen/MainScreen.min.js:306
|
||||
#, fuzzy
|
||||
msgid "Restart and upgrade"
|
||||
msgstr "Hoofdsleutels die moeten worden bijgewerkt"
|
||||
msgstr "Opnieuw opstarten en bijwerken"
|
||||
|
||||
#: ElectronClient/gui/MainScreen/MainScreen.js:424
|
||||
#: ElectronClient/gui/MainScreen/MainScreen.min.js:313
|
||||
@@ -2268,18 +2275,16 @@ msgstr[0] "Kopieer Deelbare Link"
|
||||
msgstr[1] "Kopieer Deelbare Links"
|
||||
|
||||
#: ElectronClient/commands/toggleExternalEditing.js:18
|
||||
#, fuzzy
|
||||
msgid "Toggle external editing"
|
||||
msgstr "Klik om extern bewerken te stoppen"
|
||||
msgstr "Extern bewerken in- of uitschakelen"
|
||||
|
||||
#: ElectronClient/commands/toggleExternalEditing.js:37
|
||||
msgid "Stop"
|
||||
msgstr ""
|
||||
msgstr "Stop"
|
||||
|
||||
#: ElectronClient/commands/copyDevCommand.js:18
|
||||
#, fuzzy
|
||||
msgid "Copy dev mode command to clipboard"
|
||||
msgstr "Pad kopiëren naar klembord"
|
||||
msgstr "Ontwikkelaarsmodus commando kopiëren naar klembord"
|
||||
|
||||
#: ElectronClient/commands/stopExternalEditing.js:18
|
||||
msgid "Stop external editing"
|
||||
@@ -2291,9 +2296,8 @@ msgid "Error opening note in editor: %s"
|
||||
msgstr "Fout bij openen van notitie in editor: %s"
|
||||
|
||||
#: ElectronClient/commands/openProfileDirectory.js:18
|
||||
#, fuzzy
|
||||
msgid "Open profile directory"
|
||||
msgstr "Open sjabloon map"
|
||||
msgstr "Open profiel map"
|
||||
|
||||
#: ElectronClient/app.js:345
|
||||
#, javascript-format
|
||||
@@ -2339,7 +2343,7 @@ msgstr ""
|
||||
|
||||
#: ReactNativeClient/lib/SyncTargetAmazonS3.js:28
|
||||
msgid "AWS S3"
|
||||
msgstr ""
|
||||
msgstr "AWS S3"
|
||||
|
||||
#: ReactNativeClient/lib/SyncTargetDropbox.js:25
|
||||
msgid "Dropbox"
|
||||
@@ -2483,19 +2487,19 @@ msgstr "WebDAV-wachtwoord"
|
||||
|
||||
#: ReactNativeClient/lib/models/Setting.js:215
|
||||
msgid "AWS S3 bucket"
|
||||
msgstr ""
|
||||
msgstr "AWS S3 bucket"
|
||||
|
||||
#: ReactNativeClient/lib/models/Setting.js:226
|
||||
msgid "AWS S3 URL"
|
||||
msgstr ""
|
||||
msgstr "AWS S3 URL"
|
||||
|
||||
#: ReactNativeClient/lib/models/Setting.js:237
|
||||
msgid "AWS key"
|
||||
msgstr ""
|
||||
msgstr "AWS key"
|
||||
|
||||
#: ReactNativeClient/lib/models/Setting.js:247
|
||||
msgid "AWS secret"
|
||||
msgstr ""
|
||||
msgstr "AWS secret"
|
||||
|
||||
#: ReactNativeClient/lib/models/Setting.js:259
|
||||
msgid "Attachment download behaviour"
|
||||
@@ -2955,9 +2959,8 @@ msgid "Web Clipper"
|
||||
msgstr "Webclipper"
|
||||
|
||||
#: ReactNativeClient/lib/models/Setting.js:1310
|
||||
#, fuzzy
|
||||
msgid "Keyboard Shortcuts"
|
||||
msgstr "Toetsenbordmodus"
|
||||
msgstr "Sneltoetsen"
|
||||
|
||||
#: ReactNativeClient/lib/models/Setting.js:1317
|
||||
msgid ""
|
||||
@@ -3074,9 +3077,8 @@ msgid "Save alarm"
|
||||
msgstr "Alarm opslaan"
|
||||
|
||||
#: ReactNativeClient/lib/components/NoteBodyViewer/hooks/useOnResourceLongPress.js:28
|
||||
#, fuzzy
|
||||
msgid "Open"
|
||||
msgstr "Openen..."
|
||||
msgstr "Openen"
|
||||
|
||||
#: ReactNativeClient/lib/components/NoteBodyViewer/hooks/useOnResourceLongPress.js:29
|
||||
#: ReactNativeClient/lib/components/screens/Note.js:794
|
||||
@@ -3114,8 +3116,9 @@ msgstr ""
|
||||
"Sommige items kunnen niet worden gesynchroniseerd. Klik voor meer informatie."
|
||||
|
||||
#: ReactNativeClient/lib/components/screen-header.js:453
|
||||
#, fuzzy
|
||||
msgid "The sync target needs to be upgraded. Press this banner to proceed."
|
||||
msgstr ""
|
||||
msgstr "De sync target moet worden geüpdatet. Druk hier om door te gaan."
|
||||
|
||||
#: ReactNativeClient/lib/components/side-menu-content.js:126
|
||||
#, javascript-format
|
||||
@@ -3239,8 +3242,9 @@ msgid "Refresh"
|
||||
msgstr "Verversen"
|
||||
|
||||
#: ReactNativeClient/lib/components/screens/UpgradeSyncTargetScreen.js:42
|
||||
#, fuzzy
|
||||
msgid "Sync Target Upgrade"
|
||||
msgstr ""
|
||||
msgstr "Sync target update"
|
||||
|
||||
#: ReactNativeClient/lib/components/screens/NoteTagsDialog.js:163
|
||||
msgid "New tags:"
|
||||
@@ -3484,11 +3488,14 @@ msgid ""
|
||||
"\n"
|
||||
"You may turn off this option at any time in the Configuration screen."
|
||||
msgstr ""
|
||||
"Om een geolocatie aan de notitie te koppelen, heeft de app uw toestemming "
|
||||
"nodig om toegang te krijgen tot uw locatie.\n"
|
||||
"\n"
|
||||
"U kunt deze optie op elk moment uitschakelen in het 'Configuratie'-scherm."
|
||||
|
||||
#: ReactNativeClient/lib/components/screens/Note.js:341
|
||||
#, fuzzy
|
||||
msgid "Permission needed"
|
||||
msgstr "Toestemming om de camera te gebruiken"
|
||||
msgstr "Toestemming vereist"
|
||||
|
||||
#: ReactNativeClient/lib/components/screens/Note.js:583
|
||||
#, javascript-format
|
||||
@@ -3737,23 +3744,23 @@ msgstr "Geef een importformaat op voor %s"
|
||||
|
||||
#: ReactNativeClient/lib/services/KeymapService.js:240
|
||||
msgid "command"
|
||||
msgstr ""
|
||||
msgstr "commando"
|
||||
|
||||
#: ReactNativeClient/lib/services/KeymapService.js:240
|
||||
#: ReactNativeClient/lib/services/KeymapService.js:245
|
||||
#, javascript-format
|
||||
msgid "\"%s\" is missing the required \"%s\" property."
|
||||
msgstr ""
|
||||
msgstr "\"%s\" mist de vereiste \"%s\" eigenschap."
|
||||
|
||||
#: ReactNativeClient/lib/services/KeymapService.js:245
|
||||
#: ReactNativeClient/lib/services/KeymapService.js:252
|
||||
msgid "accelerator"
|
||||
msgstr ""
|
||||
msgstr "versneller"
|
||||
|
||||
#: ReactNativeClient/lib/services/KeymapService.js:252
|
||||
#, fuzzy, javascript-format
|
||||
#, javascript-format
|
||||
msgid "Invalid %s: %s."
|
||||
msgstr "Ongeldig antwoord: %s"
|
||||
msgstr "Ongeldig %s: %s."
|
||||
|
||||
#: ReactNativeClient/lib/services/KeymapService.js:270
|
||||
#, javascript-format
|
||||
@@ -3761,11 +3768,13 @@ msgid ""
|
||||
"Accelerator \"%s\" is used for \"%s\" and \"%s\" commands. This may lead to "
|
||||
"unexpected behaviour."
|
||||
msgstr ""
|
||||
"Versneller \"%s\" wordt gebruikt voor \"%s\" en \"%s\" commando's. Dit kan "
|
||||
"tot onverwacht gedrag leiden."
|
||||
|
||||
#: ReactNativeClient/lib/services/KeymapService.js:295
|
||||
#, javascript-format
|
||||
msgid "Accelerator \"%s\" is not valid."
|
||||
msgstr ""
|
||||
msgstr "Versneller \"%s\" is niet geldig."
|
||||
|
||||
#: ReactNativeClient/lib/services/report.js:121
|
||||
msgid "Items that cannot be synchronised"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const ArrayUtils = require('lib/ArrayUtils.js');
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const os = require('os');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { filename } = require('lib/path-utils');
|
||||
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const os = require('os');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { filename } = require('lib/path-utils');
|
||||
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const os = require('os');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { filename } = require('lib/path-utils');
|
||||
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import InMemoryCache from 'lib/InMemoryCache';
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
|
||||
describe('InMemoryCache', function() {
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { sortedIds, createNTestNotes, asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
|
||||
@@ -5,7 +5,7 @@ const Setting = require('lib/models/Setting').default;
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const Tag = require('lib/models/Tag.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
|
||||
let testApp = null;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ const Setting = require('lib/models/Setting').default;
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const Tag = require('lib/models/Tag.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { ALL_NOTES_FILTER_ID } = require('lib/reserved-ids.js');
|
||||
|
||||
//
|
||||
|
||||
@@ -5,7 +5,7 @@ const Setting = require('lib/models/Setting').default;
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const Tag = require('lib/models/Tag.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
|
||||
let testApp = null;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const uuid = require('lib/uuid').default;
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { asyncTest, sleep, fileApi, fileContentEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const shim = require('lib/shim').default;
|
||||
const fs = require('fs-extra');
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const mimeUtils = require('lib/mime-utils.js').mime;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { createNTestNotes, asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { asyncTest, fileContentEqual, revisionService, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const SearchEngine = require('lib/services/searchengine/SearchEngine');
|
||||
const ResourceService = require('lib/services/ResourceService');
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { sortedIds, createNTestNotes, asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { sortedIds, createNTestNotes, asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
|
||||
@@ -3,6 +3,7 @@ require('app-module-path').addPath(__dirname);
|
||||
const { tempFilePath } = require('test-utils.js');
|
||||
const KeymapService = require('lib/services/KeymapService').default;
|
||||
const keymapService = KeymapService.instance();
|
||||
keymapService.initialize([]);
|
||||
|
||||
describe('services_KeymapService', () => {
|
||||
describe('validateAccelerator', () => {
|
||||
@@ -31,7 +32,7 @@ describe('services_KeymapService', () => {
|
||||
};
|
||||
|
||||
Object.entries(testCases).forEach(([platform, accelerators]) => {
|
||||
keymapService.initialize(platform);
|
||||
keymapService.initialize([], platform);
|
||||
accelerators.forEach(accelerator => {
|
||||
expect(() => keymapService.validateAccelerator(accelerator)).not.toThrow();
|
||||
});
|
||||
@@ -69,7 +70,7 @@ describe('services_KeymapService', () => {
|
||||
};
|
||||
|
||||
Object.entries(testCases).forEach(([platform, accelerators]) => {
|
||||
keymapService.initialize(platform);
|
||||
keymapService.initialize([], platform);
|
||||
accelerators.forEach(accelerator => {
|
||||
expect(() => keymapService.validateAccelerator(accelerator)).toThrow();
|
||||
});
|
||||
@@ -81,12 +82,12 @@ describe('services_KeymapService', () => {
|
||||
beforeEach(() => keymapService.initialize());
|
||||
|
||||
it('should allow registering new commands', async () => {
|
||||
keymapService.initialize('linux');
|
||||
keymapService.initialize([], 'linux');
|
||||
keymapService.registerCommandAccelerator('myCustomCommand', 'Ctrl+Shift+Alt+B');
|
||||
expect(keymapService.getAccelerator('myCustomCommand')).toEqual('Ctrl+Shift+Alt+B');
|
||||
|
||||
// Check that macOS key conversion is working
|
||||
keymapService.initialize('darwin');
|
||||
keymapService.initialize([], 'darwin');
|
||||
keymapService.registerCommandAccelerator('myCustomCommand', 'CmdOrCtrl+Shift+Alt+B');
|
||||
expect(keymapService.getAccelerator('myCustomCommand')).toEqual('Cmd+Shift+Option+B');
|
||||
keymapService.setAccelerator('myCustomCommand', 'Cmd+Shift+Option+X');
|
||||
@@ -95,7 +96,7 @@ describe('services_KeymapService', () => {
|
||||
const keymapFilePath = tempFilePath('json');
|
||||
await keymapService.saveCustomKeymap(keymapFilePath);
|
||||
|
||||
keymapService.initialize('darwin');
|
||||
keymapService.initialize([], 'darwin');
|
||||
await keymapService.loadCustomKeymap(keymapFilePath);
|
||||
|
||||
expect(keymapService.getAccelerator('myCustomCommand')).toEqual('Cmd+Shift+Option+X');
|
||||
@@ -106,17 +107,17 @@ describe('services_KeymapService', () => {
|
||||
beforeEach(() => keymapService.initialize());
|
||||
|
||||
it('should return the platform-specific default Accelerator', () => {
|
||||
keymapService.initialize('darwin');
|
||||
keymapService.initialize([], 'darwin');
|
||||
expect(keymapService.getAccelerator('newNote')).toEqual('Cmd+N');
|
||||
expect(keymapService.getAccelerator('synchronize')).toEqual('Cmd+S');
|
||||
expect(keymapService.getAccelerator('textSelectAll')).toEqual('Cmd+A');
|
||||
expect(keymapService.getAccelerator('textBold')).toEqual('Cmd+B');
|
||||
|
||||
keymapService.initialize('linux');
|
||||
keymapService.initialize([], 'linux');
|
||||
expect(keymapService.getAccelerator('newNote')).toEqual('Ctrl+N');
|
||||
expect(keymapService.getAccelerator('synchronize')).toEqual('Ctrl+S');
|
||||
|
||||
keymapService.initialize('win32');
|
||||
keymapService.initialize([], 'win32');
|
||||
expect(keymapService.getAccelerator('textSelectAll')).toEqual('Ctrl+A');
|
||||
expect(keymapService.getAccelerator('textBold')).toEqual('Ctrl+B');
|
||||
});
|
||||
@@ -130,7 +131,7 @@ describe('services_KeymapService', () => {
|
||||
beforeEach(() => keymapService.initialize());
|
||||
|
||||
it('should update the Accelerator', () => {
|
||||
keymapService.initialize('darwin');
|
||||
keymapService.initialize(['print'], 'darwin');
|
||||
const testCases_Darwin = [
|
||||
{ command: 'newNote', accelerator: 'Ctrl+Option+Shift+N' },
|
||||
{ command: 'synchronize', accelerator: 'F11' },
|
||||
@@ -147,7 +148,7 @@ describe('services_KeymapService', () => {
|
||||
expect(keymapService.getAccelerator(command)).toEqual(accelerator);
|
||||
});
|
||||
|
||||
keymapService.initialize('linux');
|
||||
keymapService.initialize(['print'], 'linux');
|
||||
const testCases_Linux = [
|
||||
{ command: 'newNote', accelerator: 'Ctrl+Alt+Shift+N' },
|
||||
{ command: 'synchronize', accelerator: 'F15' },
|
||||
@@ -167,7 +168,7 @@ describe('services_KeymapService', () => {
|
||||
});
|
||||
|
||||
describe('getDefaultAccelerator', () => {
|
||||
beforeEach(() => keymapService.initialize());
|
||||
beforeEach(() => keymapService.initialize(['print', 'linux']));
|
||||
|
||||
it('should return the default accelerator', () => {
|
||||
const testCases = [
|
||||
@@ -196,7 +197,7 @@ describe('services_KeymapService', () => {
|
||||
beforeEach(() => keymapService.initialize());
|
||||
|
||||
it('should update the keymap', () => {
|
||||
keymapService.initialize('darwin');
|
||||
keymapService.initialize([], 'darwin');
|
||||
const customKeymapItems_Darwin = [
|
||||
{ command: 'newNote', accelerator: 'Option+Shift+Cmd+N' },
|
||||
{ command: 'synchronize', accelerator: 'Ctrl+F11' },
|
||||
@@ -217,7 +218,7 @@ describe('services_KeymapService', () => {
|
||||
expect(keymapService.getAccelerator(command)).toEqual(accelerator);
|
||||
});
|
||||
|
||||
keymapService.initialize('win32');
|
||||
keymapService.initialize([], 'win32');
|
||||
const customKeymapItems_Win32 = [
|
||||
{ command: 'newNote', accelerator: 'Ctrl+Alt+Shift+N' },
|
||||
{ command: 'synchronize', accelerator: 'Ctrl+F11' },
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { asyncTest, resourceService, decryptionWorker, encryptionService, loadEncryptionMasterKey, allSyncTargetItemsEncrypted, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const InteropService = require('lib/services/interop/InteropService').default;
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { asyncTest, fileContentEqual, setupDatabase, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Setting = require('lib/models/Setting').default;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, asyncTest, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync, mockDate, restoreDate } = require('test-utils.js');
|
||||
const SearchEngine = require('lib/services/searchengine/SearchEngine');
|
||||
const Note = require('lib/models/Note');
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, asyncTest, db, synchronizer, fileApi, sleep, createNTestNotes, switchClient, createNTestFolders } = require('test-utils.js');
|
||||
const SearchEngine = require('lib/services/searchengine/SearchEngine');
|
||||
const Note = require('lib/models/Note');
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
// require('app-module-path').addPath(__dirname);
|
||||
|
||||
// const { time } = require('lib/time-utils.js');
|
||||
// const time = require('lib/time').default;
|
||||
// const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, asyncTest, db, synchronizer, fileApi, sleep, createNTestNotes, switchClient, createNTestFolders } = require('test-utils.js');
|
||||
// const SearchEngine = require('lib/services/searchengine/SearchEngine');
|
||||
// const Note = require('lib/models/Note');
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { setupDatabase, synchronizerStart, syncTargetName, allSyncTargetItemsEncrypted, tempFilePath, resourceFetcher, kvStore, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, encryptionService, loadEncryptionMasterKey, fileContentEqual, decryptionWorker, checkThrowAsync, asyncTest } = require('test-utils.js');
|
||||
const shim = require('lib/shim').default;
|
||||
const fs = require('fs-extra');
|
||||
|
||||
@@ -25,7 +25,7 @@ const { FileApiDriverOneDrive } = require('lib/file-api-driver-onedrive.js');
|
||||
const { FileApiDriverAmazonS3 } = require('lib/file-api-driver-amazon-s3.js');
|
||||
const BaseService = require('lib/services/BaseService').default;
|
||||
const FsDriverNode = require('lib/fs-driver-node').default;
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { shimInit } = require('lib/shim-init-node.js');
|
||||
const shim = require('lib/shim').default;
|
||||
const uuid = require('lib/uuid').default;
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const timeUtils = require('../../ReactNativeClient/lib/time-utils');
|
||||
const timeUtils = require('../../ReactNativeClient/lib/time');
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
|
||||
@@ -86,6 +86,7 @@ export default class ElectronAppWrapper {
|
||||
backgroundColor: '#fff', // required to enable sub pixel rendering, can't be in css
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
spellcheck: true,
|
||||
},
|
||||
webviewTag: true,
|
||||
// We start with a hidden window, which is then made visible depending on the showTrayIcon setting
|
||||
@@ -97,14 +98,6 @@ export default class ElectronAppWrapper {
|
||||
// Fix: https://github.com/electron-userland/electron-builder/issues/2269
|
||||
if (shim.isLinux()) windowOptions.icon = path.join(__dirname, '..', 'build/icons/128x128.png');
|
||||
|
||||
require('electron-context-menu')({
|
||||
shouldShowMenu: (_event:any, params:any) => {
|
||||
// params.inputFieldType === 'none' when right-clicking the text editor. This is a bit of a hack to detect it because in this
|
||||
// case we don't want to use the built-in context menu but a custom one.
|
||||
return params.isEditable && params.inputFieldType !== 'none';
|
||||
},
|
||||
});
|
||||
|
||||
this.win_ = new BrowserWindow(windowOptions);
|
||||
|
||||
if (!screen.getDisplayMatching(this.win_.getBounds())) {
|
||||
|
||||
@@ -8,7 +8,7 @@ const bridge = require('electron').remote.require('./bridge').default;
|
||||
const Setting = require('lib/models/Setting').default;
|
||||
const Note = require('lib/models/Note.js');
|
||||
const { friendlySafeFilename } = require('lib/path-utils');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const md5 = require('md5');
|
||||
const url = require('url');
|
||||
|
||||
|
||||
@@ -14,6 +14,10 @@ import Setting from 'lib/models/Setting';
|
||||
import actionApi from 'lib/services/rest/actionApi.desktop';
|
||||
import BaseApplication from 'lib/BaseApplication';
|
||||
import { _, setLocale } from 'lib/locale';
|
||||
import SpellCheckerService from 'lib/services/spellChecker/SpellCheckerService';
|
||||
import SpellCheckerServiceDriverNative from './services/spellChecker/SpellCheckerServiceDriverNative';
|
||||
import bridge from './services/bridge';
|
||||
import menuCommandNames from './gui/menuCommandNames';
|
||||
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
@@ -28,7 +32,6 @@ const DecryptionWorker = require('lib/services/DecryptionWorker');
|
||||
const ResourceService = require('lib/services/ResourceService');
|
||||
const ClipperServer = require('lib/ClipperServer');
|
||||
const ExternalEditWatcher = require('lib/services/ExternalEditWatcher');
|
||||
const bridge = require('electron').remote.require('./bridge').default;
|
||||
const { webFrame } = require('electron');
|
||||
const Menu = bridge().Menu;
|
||||
const PluginManager = require('lib/services/PluginManager');
|
||||
@@ -451,6 +454,31 @@ class Application extends BaseApplication {
|
||||
document.head.appendChild(styleTag);
|
||||
}
|
||||
|
||||
setupContextMenu() {
|
||||
// The context menu must be setup in renderer process because that's where
|
||||
// the spell checker service lives.
|
||||
require('electron-context-menu')({
|
||||
shouldShowMenu: (_event:any, params:any) => {
|
||||
// params.inputFieldType === 'none' when right-clicking the text editor. This is a bit of a hack to detect it because in this
|
||||
// case we don't want to use the built-in context menu but a custom one.
|
||||
return params.isEditable && params.inputFieldType !== 'none';
|
||||
},
|
||||
|
||||
menu: (actions:any, props:any) => {
|
||||
const spellCheckerMenuItems = SpellCheckerService.instance().contextMenuItems(props.misspelledWord, props.dictionarySuggestions);
|
||||
|
||||
const output = [
|
||||
actions.cut(),
|
||||
actions.copy(),
|
||||
actions.paste(),
|
||||
...spellCheckerMenuItems,
|
||||
];
|
||||
|
||||
return output;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async loadCustomCss(filePath:string) {
|
||||
let cssString = '';
|
||||
if (await fs.pathExists(filePath)) {
|
||||
@@ -495,14 +523,6 @@ class Application extends BaseApplication {
|
||||
const filename = Setting.custom_css_files.JOPLIN_APP;
|
||||
await CssUtils.injectCustomStyles(`${dir}/${filename}`);
|
||||
|
||||
const keymapService = KeymapService.instance();
|
||||
|
||||
try {
|
||||
await keymapService.loadCustomKeymap(`${dir}/keymap-desktop.json`);
|
||||
} catch (err) {
|
||||
reg.logger().error(err.message);
|
||||
}
|
||||
|
||||
AlarmService.setDriver(new AlarmServiceDriverNode({ appName: packageInfo.build.appId }));
|
||||
AlarmService.setLogger(reg.logger());
|
||||
|
||||
@@ -533,9 +553,21 @@ class Application extends BaseApplication {
|
||||
CommandService.instance().registerDeclaration(declaration);
|
||||
}
|
||||
|
||||
// Since the settings need to be loaded before the store is created, it will never
|
||||
// receive the SETTING_UPDATE_ALL even, which mean state.settings will not be
|
||||
// initialised. So we manually call dispatchUpdateAll() to force an update.
|
||||
const keymapService = KeymapService.instance();
|
||||
// We only add the commands that appear in the menu because only
|
||||
// those can have a shortcut associated with them.
|
||||
keymapService.initialize(menuCommandNames());
|
||||
|
||||
try {
|
||||
await keymapService.loadCustomKeymap(`${dir}/keymap-desktop.json`);
|
||||
} catch (error) {
|
||||
reg.logger().error(error);
|
||||
}
|
||||
|
||||
// Since the settings need to be loaded before the store is
|
||||
// created, it will never receive the SETTING_UPDATE_ALL even,
|
||||
// which mean state.settings will not be initialised. So we
|
||||
// manually call dispatchUpdateAll() to force an update.
|
||||
Setting.dispatchUpdateAll();
|
||||
|
||||
await FoldersScreenUtils.refreshFolders();
|
||||
@@ -684,6 +716,10 @@ class Application extends BaseApplication {
|
||||
this.logger().error(`There was an error loading plugins from ${Setting.value('plugins.devPluginPaths')}:`, error);
|
||||
}
|
||||
|
||||
this.setupContextMenu();
|
||||
|
||||
await SpellCheckerService.instance().initialize(new SpellCheckerServiceDriverNative());
|
||||
|
||||
// await populateDatabase(reg.db());
|
||||
|
||||
// setTimeout(() => {
|
||||
|
||||
@@ -4,7 +4,7 @@ const Setting = require('lib/models/Setting').default;
|
||||
const EncryptionService = require('lib/services/EncryptionService');
|
||||
const { themeStyle } = require('lib/theme');
|
||||
const { _ } = require('lib/locale');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const shim = require('lib/shim').default;
|
||||
const dialogs = require('./dialogs');
|
||||
const shared = require('lib/components/shared/encryption-config-shared.js');
|
||||
|
||||
@@ -5,7 +5,7 @@ import { _ } from 'lib/locale';
|
||||
|
||||
const commandService = CommandService.instance();
|
||||
|
||||
const getLabel = (commandName: string) => {
|
||||
const getLabel = (commandName: string):string => {
|
||||
if (commandService.exists(commandName)) return commandService.label(commandName, true);
|
||||
|
||||
// Some commands are not registered in CommandService at the moment
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import KeymapService, { KeymapItem } from 'lib/services/KeymapService';
|
||||
import getLabel from './getLabel';
|
||||
|
||||
const keymapService = KeymapService.instance();
|
||||
|
||||
// This custom hook provides a synchronized snapshot of the keymap residing at KeymapService
|
||||
// All the logic regarding altering and interacting with the keymap is isolated from the components
|
||||
|
||||
function allKeymapItems() {
|
||||
const output = keymapService.getKeymapItems().slice();
|
||||
|
||||
output.sort((a:KeymapItem, b:KeymapItem) => {
|
||||
return getLabel(a.command).toLocaleLowerCase() < getLabel(b.command).toLocaleLowerCase() ? -1 : +1;
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
const useKeymap = (): [
|
||||
KeymapItem[],
|
||||
Error,
|
||||
@@ -13,7 +24,7 @@ const useKeymap = (): [
|
||||
(commandName: string, accelerator: string) => void,
|
||||
(commandName: string) => void
|
||||
] => {
|
||||
const [keymapItems, setKeymapItems] = useState<KeymapItem[]>(() => keymapService.getKeymapItems());
|
||||
const [keymapItems, setKeymapItems] = useState<KeymapItem[]>(() => allKeymapItems());
|
||||
const [keymapError, setKeymapError] = useState<Error>(null);
|
||||
const [mustSave, setMustSave] = useState(false);
|
||||
|
||||
@@ -42,7 +53,7 @@ const useKeymap = (): [
|
||||
|
||||
const overrideKeymapItems = (customKeymapItems: KeymapItem[]) => {
|
||||
const oldKeymapItems = [...customKeymapItems];
|
||||
keymapService.initialize(); // Start with a fresh keymap
|
||||
keymapService.resetKeymap(); // Start with a fresh keymap
|
||||
|
||||
try {
|
||||
// First, try to update the in-memory keymap of KeymapService
|
||||
|
||||
@@ -27,7 +27,7 @@ const bridge = require('electron').remote.require('./bridge').default;
|
||||
const PluginManager = require('lib/services/PluginManager');
|
||||
const EncryptionService = require('lib/services/EncryptionService');
|
||||
const ipcRenderer = require('electron').ipcRenderer;
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const styled = require('styled-components').default;
|
||||
|
||||
const StyledUserWebviewDialogContainer = styled.div`
|
||||
|
||||
@@ -3,7 +3,7 @@ import eventManager from 'lib/eventManager';
|
||||
import { _ } from 'lib/locale';
|
||||
import { stateUtils } from 'lib/reducer';
|
||||
const Note = require('lib/models/Note');
|
||||
const { time } = require('lib/time-utils');
|
||||
const time = require('lib/time').default;
|
||||
|
||||
export const declaration:CommandDeclaration = {
|
||||
name: 'editAlarm',
|
||||
|
||||
@@ -14,6 +14,8 @@ import InteropServiceHelper from '../InteropServiceHelper';
|
||||
import { _ } from 'lib/locale';
|
||||
import { MenuItem, MenuItemLocation } from 'lib/services/plugins/api/types';
|
||||
import stateToWhenClauseContext from 'lib/services/commands/stateToWhenClauseContext';
|
||||
import SpellCheckerService from 'lib/services/spellChecker/SpellCheckerService';
|
||||
import menuCommandNames from './menuCommandNames';
|
||||
|
||||
const { connect } = require('react-redux');
|
||||
const { reg } = require('lib/registry.js');
|
||||
@@ -84,41 +86,11 @@ interface Props {
|
||||
showCompletedTodos: boolean,
|
||||
pluginMenuItems: any[],
|
||||
pluginMenus: any[],
|
||||
['spellChecker.enabled']: boolean,
|
||||
['spellChecker.language']: string,
|
||||
}
|
||||
|
||||
const commandNames:string[] = [
|
||||
'focusElementSideBar',
|
||||
'focusElementNoteList',
|
||||
'focusElementNoteTitle',
|
||||
'focusElementNoteBody',
|
||||
'exportPdf',
|
||||
'newNote',
|
||||
'newTodo',
|
||||
'newFolder',
|
||||
'newSubFolder',
|
||||
'print',
|
||||
'synchronize',
|
||||
'textCopy',
|
||||
'textCut',
|
||||
'textPaste',
|
||||
'textSelectAll',
|
||||
'textBold',
|
||||
'textItalic',
|
||||
'textLink',
|
||||
'textCode',
|
||||
'insertDateTime',
|
||||
'attachFile',
|
||||
'focusSearch',
|
||||
'showLocalSearch',
|
||||
'toggleSideBar',
|
||||
'toggleNoteList',
|
||||
'toggleVisiblePanes',
|
||||
'toggleExternalEditing',
|
||||
'setTags',
|
||||
'showNoteContentProperties',
|
||||
'copyDevCommand',
|
||||
'openProfileDirectory',
|
||||
];
|
||||
const commandNames:string[] = menuCommandNames();
|
||||
|
||||
function menuItemSetChecked(id:string, checked:boolean) {
|
||||
const menu = Menu.getApplicationMenu();
|
||||
@@ -249,10 +221,8 @@ function useMenu(props:Props) {
|
||||
menuItemDic.focusElementNoteBody,
|
||||
];
|
||||
|
||||
let toolsItems:any[] = [];
|
||||
const importItems = [];
|
||||
const exportItems = [];
|
||||
const toolsItemsFirst = [];
|
||||
const templateItems:any[] = [];
|
||||
const ioService = InteropService.instance();
|
||||
const ioModules = ioService.modules();
|
||||
@@ -299,16 +269,18 @@ function useMenu(props:Props) {
|
||||
},
|
||||
};
|
||||
|
||||
const separator = () => {
|
||||
return {
|
||||
type: 'separator',
|
||||
};
|
||||
};
|
||||
|
||||
const newNoteItem = menuItemDic.newNote;
|
||||
const newTodoItem = menuItemDic.newTodo;
|
||||
const newFolderItem = menuItemDic.newFolder;
|
||||
const newSubFolderItem = menuItemDic.newSubFolder;
|
||||
const printItem = menuItemDic.print;
|
||||
|
||||
toolsItemsFirst.push(syncStatusItem, {
|
||||
type: 'separator',
|
||||
});
|
||||
|
||||
templateItems.push({
|
||||
label: _('Create note from template'),
|
||||
click: () => {
|
||||
@@ -342,18 +314,22 @@ function useMenu(props:Props) {
|
||||
},
|
||||
});
|
||||
|
||||
let toolsItems:any[] = [];
|
||||
|
||||
// we need this workaround, because on macOS the menu is different
|
||||
const toolsItemsWindowsLinux:any[] = toolsItemsFirst.concat([{
|
||||
label: _('Options'),
|
||||
visible: !shim.isMac(),
|
||||
accelerator: !shim.isMac() && keymapService.getAccelerator('config'),
|
||||
click: () => {
|
||||
props.dispatch({
|
||||
type: 'NAV_GO',
|
||||
routeName: 'Config',
|
||||
});
|
||||
const toolsItemsWindowsLinux:any[] = [
|
||||
{
|
||||
label: _('Options'),
|
||||
accelerator: keymapService.getAccelerator('config'),
|
||||
click: () => {
|
||||
props.dispatch({
|
||||
type: 'NAV_GO',
|
||||
routeName: 'Config',
|
||||
});
|
||||
},
|
||||
},
|
||||
} as any]);
|
||||
separator(),
|
||||
];
|
||||
|
||||
// the following menu items will be available for all OS under Tools
|
||||
const toolsItemsAll = [{
|
||||
@@ -371,6 +347,8 @@ function useMenu(props:Props) {
|
||||
}
|
||||
toolsItems = toolsItems.concat(toolsItemsAll);
|
||||
|
||||
toolsItems.push(SpellCheckerService.instance().spellCheckerConfigMenuItem(props['spellChecker.language'], props['spellChecker.enabled']));
|
||||
|
||||
function _checkForUpdates() {
|
||||
bridge().checkForUpdates(false, bridge().window(), `${Setting.value('profileDir')}/log-autoupdater.txt`, { includePreReleases: Setting.value('autoUpdate.includePreReleases') });
|
||||
}
|
||||
@@ -451,9 +429,7 @@ function useMenu(props:Props) {
|
||||
|
||||
menuItemDic.synchronize,
|
||||
|
||||
shim.isMac() ? syncStatusItem : noItem, {
|
||||
type: 'separator',
|
||||
}, shim.isMac() ? noItem : printItem, {
|
||||
shim.isMac() ? noItem : printItem, {
|
||||
type: 'separator',
|
||||
platforms: ['darwin'],
|
||||
},
|
||||
@@ -518,12 +494,6 @@ function useMenu(props:Props) {
|
||||
});
|
||||
}
|
||||
|
||||
const separator = () => {
|
||||
return {
|
||||
type: 'separator',
|
||||
};
|
||||
};
|
||||
|
||||
const rootMenus:any = {
|
||||
edit: {
|
||||
id: 'edit',
|
||||
@@ -586,11 +556,6 @@ function useMenu(props:Props) {
|
||||
},
|
||||
},
|
||||
separator(),
|
||||
{
|
||||
label: _('Focus'),
|
||||
submenu: focusItems,
|
||||
},
|
||||
separator(),
|
||||
{
|
||||
label: _('Actual Size'),
|
||||
click: () => {
|
||||
@@ -623,6 +588,18 @@ function useMenu(props:Props) {
|
||||
accelerator: 'CommandOrControl+-',
|
||||
}],
|
||||
},
|
||||
go: {
|
||||
label: _('&Go'),
|
||||
submenu: [
|
||||
menuItemDic.historyBackward,
|
||||
menuItemDic.historyForward,
|
||||
separator(),
|
||||
{
|
||||
label: _('Focus'),
|
||||
submenu: focusItems,
|
||||
},
|
||||
],
|
||||
},
|
||||
note: {
|
||||
label: _('&Note'),
|
||||
submenu: [
|
||||
@@ -655,6 +632,8 @@ function useMenu(props:Props) {
|
||||
click: () => _checkForUpdates(),
|
||||
},
|
||||
separator(),
|
||||
syncStatusItem,
|
||||
separator(),
|
||||
{
|
||||
id: 'help:toggleDevTools',
|
||||
label: _('Toggle development tools'),
|
||||
@@ -709,6 +688,7 @@ function useMenu(props:Props) {
|
||||
const pluginMenuItems = PluginManager.instance().menuItems();
|
||||
for (const item of pluginMenuItems) {
|
||||
const itemParent = rootMenus[item.parent] ? rootMenus[item.parent] : 'tools';
|
||||
itemParent.submenu.push(separator());
|
||||
itemParent.submenu.push(item);
|
||||
}
|
||||
}
|
||||
@@ -741,6 +721,7 @@ function useMenu(props:Props) {
|
||||
rootMenus.file,
|
||||
rootMenus.edit,
|
||||
rootMenus.view,
|
||||
rootMenus.go,
|
||||
rootMenus.note,
|
||||
rootMenus.tools,
|
||||
rootMenus.help,
|
||||
@@ -748,46 +729,6 @@ function useMenu(props:Props) {
|
||||
|
||||
if (shim.isMac()) template.splice(0, 0, rootMenus.macOsApp);
|
||||
|
||||
// TODO
|
||||
|
||||
// function isEmptyMenu(template:any[]) {
|
||||
// for (let i = 0; i < template.length; i++) {
|
||||
// const t = template[i];
|
||||
// if (t.type !== 'separator') return false;
|
||||
// }
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// function removeUnwantedItems(template:any[], screen:string) {
|
||||
// const platform = shim.platformName();
|
||||
|
||||
// let output = [];
|
||||
// for (let i = 0; i < template.length; i++) {
|
||||
// const t = Object.assign({}, template[i]);
|
||||
// if (t.screens && t.screens.indexOf(screen) < 0) continue;
|
||||
// if (t.platforms && t.platforms.indexOf(platform) < 0) continue;
|
||||
// if (t.submenu) t.submenu = removeUnwantedItems(t.submenu, screen);
|
||||
// if (('submenu' in t) && isEmptyMenu(t.submenu)) continue;
|
||||
// output.push(t);
|
||||
// }
|
||||
|
||||
// // Remove empty separator for now empty sections
|
||||
// const temp = [];
|
||||
// let previous = null;
|
||||
// for (let i = 0; i < output.length; i++) {
|
||||
// const t = Object.assign({}, output[i]);
|
||||
// if (t.type === 'separator') {
|
||||
// if (!previous) continue;
|
||||
// if (previous.type === 'separator') continue;
|
||||
// }
|
||||
// temp.push(t);
|
||||
// previous = t;
|
||||
// }
|
||||
// output = temp;
|
||||
|
||||
// return output;
|
||||
// }
|
||||
|
||||
if (props.routeName !== 'Main') {
|
||||
setMenu(Menu.buildFromTemplate([
|
||||
{
|
||||
@@ -807,7 +748,7 @@ function useMenu(props:Props) {
|
||||
} else {
|
||||
setMenu(Menu.buildFromTemplate(template));
|
||||
}
|
||||
}, [props.routeName, props.pluginMenuItems, props.pluginMenus, keymapLastChangeTime, modulesLastChangeTime]);
|
||||
}, [props.routeName, props.pluginMenuItems, props.pluginMenus, keymapLastChangeTime, modulesLastChangeTime, props['spellChecker.language'], props['spellChecker.enabled']]);
|
||||
|
||||
useEffect(() => {
|
||||
const whenClauseContext = CommandService.instance().currentWhenClauseContext();
|
||||
@@ -902,6 +843,8 @@ const mapStateToProps = (state:AppState) => {
|
||||
showCompletedTodos: state.settings.showCompletedTodos,
|
||||
pluginMenuItems: stateUtils.selectArrayShallow({ array: pluginUtils.viewsByType(state.pluginService.plugins, 'menuItem') }, 'menuBar.pluginMenuItems'),
|
||||
pluginMenus: stateUtils.selectArrayShallow({ array: pluginUtils.viewsByType(state.pluginService.plugins, 'menu') }, 'menuBar.pluginMenus'),
|
||||
['spellChecker.language']: state.settings['spellChecker.language'],
|
||||
['spellChecker.enabled']: state.settings['spellChecker.enabled'],
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import { ScrollOptions, ScrollOptionTypes, EditorCommand, NoteBodyEditorProps }
|
||||
import { resourcesStatus, commandAttachFileToBody, handlePasteEvent } from '../../utils/resourceHandling';
|
||||
import useScroll from './utils/useScroll';
|
||||
import styles_ from './styles';
|
||||
import { menuItems, ContextMenuOptions, ContextMenuItemType } from '../../utils/contextMenu';
|
||||
import CommandService from 'lib/services/CommandService';
|
||||
import { ToolbarButtonInfo } from 'lib/services/commands/ToolbarButtonUtils';
|
||||
import ToggleEditorsButton, { Value as ToggleEditorsButtonValue } from '../../../ToggleEditorsButton/ToggleEditorsButton';
|
||||
@@ -12,13 +11,13 @@ import ToolbarButton from '../../../../gui/ToolbarButton/ToolbarButton';
|
||||
import usePluginServiceRegistration from '../../utils/usePluginServiceRegistration';
|
||||
import { utils as pluginUtils } from 'lib/services/plugins/reducer';
|
||||
import { _, closestSupportedLocale } from 'lib/locale';
|
||||
import setupContextMenu from './utils/setupContextMenu';
|
||||
|
||||
const { MarkupToHtml } = require('lib/joplin-renderer');
|
||||
const taboverride = require('taboverride');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const BaseItem = require('lib/models/BaseItem');
|
||||
const shim = require('lib/shim').default;
|
||||
const Resource = require('lib/models/Resource');
|
||||
const { themeStyle } = require('lib/theme');
|
||||
const { clipboard } = require('electron');
|
||||
const supportedLocales = require('./supportedLocales');
|
||||
@@ -143,8 +142,6 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
|
||||
const props_onDrop = useRef(null);
|
||||
props_onDrop.current = props.onDrop;
|
||||
|
||||
const contextMenuActionOptions = useRef<ContextMenuOptions>(null);
|
||||
|
||||
const markupToHtml = useRef(null);
|
||||
markupToHtml.current = props.markupToHtml;
|
||||
|
||||
@@ -510,19 +507,7 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
|
||||
loadedCssFiles_ = [];
|
||||
loadedJsFiles_ = [];
|
||||
|
||||
function contextMenuItemNameWithNamespace(name:string) {
|
||||
// For unknown reasons, TinyMCE converts all context menu names to
|
||||
// lowercase when setting them in the init method, so we need to
|
||||
// make them lowercase too, to make sure that the update() method
|
||||
// addContextMenu is triggered.
|
||||
return (`joplin${name}`).toLowerCase();
|
||||
}
|
||||
|
||||
const loadEditor = async () => {
|
||||
const contextMenuItems = menuItems();
|
||||
const contextMenuItemNames = [];
|
||||
for (const name in contextMenuItems) contextMenuItemNames.push(contextMenuItemNameWithNamespace(name));
|
||||
|
||||
const language = closestSupportedLocale(props.locale, true, supportedLocales);
|
||||
|
||||
const pluginCommandNames:string[] = [];
|
||||
@@ -564,7 +549,8 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
|
||||
language: ['en_US', 'en_GB'].includes(language) ? undefined : language,
|
||||
toolbar: toolbar.join(' '),
|
||||
localization_function: _,
|
||||
contextmenu: contextMenuItemNames.join(' '),
|
||||
contextmenu: false,
|
||||
browser_spellcheck: true,
|
||||
setup: (editor:any) => {
|
||||
|
||||
function openEditDialog(editable:any) {
|
||||
@@ -680,51 +666,7 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
|
||||
});
|
||||
}
|
||||
|
||||
for (const itemName in contextMenuItems) {
|
||||
const item = contextMenuItems[itemName];
|
||||
|
||||
const itemNameNS = contextMenuItemNameWithNamespace(itemName);
|
||||
|
||||
editor.ui.registry.addMenuItem(itemNameNS, {
|
||||
text: item.label,
|
||||
onAction: () => {
|
||||
item.onAction(contextMenuActionOptions.current);
|
||||
},
|
||||
});
|
||||
|
||||
editor.ui.registry.addContextMenu(itemNameNS, {
|
||||
update: function(element:any) {
|
||||
let itemType:ContextMenuItemType = ContextMenuItemType.None;
|
||||
let resourceId = '';
|
||||
let linkToCopy = null;
|
||||
|
||||
if (element.nodeName === 'IMG') {
|
||||
itemType = ContextMenuItemType.Image;
|
||||
resourceId = Resource.pathToId(element.src);
|
||||
} else if (element.nodeName === 'A') {
|
||||
resourceId = Resource.pathToId(element.href);
|
||||
itemType = resourceId ? ContextMenuItemType.Resource : ContextMenuItemType.Link;
|
||||
linkToCopy = element.getAttribute('href') || '';
|
||||
} else {
|
||||
itemType = ContextMenuItemType.Text;
|
||||
}
|
||||
|
||||
contextMenuActionOptions.current = {
|
||||
itemType,
|
||||
resourceId,
|
||||
linkToCopy,
|
||||
textToCopy: null,
|
||||
htmlToCopy: editor.selection ? editor.selection.getContent() : '',
|
||||
insertContent: (content:string) => {
|
||||
editor.insertContent(content);
|
||||
},
|
||||
isReadOnly: false,
|
||||
};
|
||||
|
||||
return item.isActive(itemType, contextMenuActionOptions.current) ? itemNameNS : '';
|
||||
},
|
||||
});
|
||||
}
|
||||
setupContextMenu(editor);
|
||||
|
||||
// TODO: remove event on unmount?
|
||||
editor.on('DblClick', (event:any) => {
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import SpellCheckerService from 'lib/services/spellChecker/SpellCheckerService';
|
||||
import bridge from '../../../../../services/bridge';
|
||||
import { menuItems, ContextMenuOptions, ContextMenuItemType } from '../../../utils/contextMenu';
|
||||
const Resource = require('lib/models/Resource');
|
||||
|
||||
// x and y are the absolute coordinates, as returned by the context-menu event
|
||||
// handler on the webContent. This function will return null if the point is
|
||||
// not within the TinyMCE editor.
|
||||
function contextMenuElement(editor:any, x:number, y:number) {
|
||||
const iframes = document.getElementsByClassName('tox-edit-area__iframe');
|
||||
if (!iframes.length) return null;
|
||||
|
||||
const iframeRect = iframes[0].getBoundingClientRect();
|
||||
|
||||
if (iframeRect.x < x && iframeRect.y < y && iframeRect.right > x && iframeRect.bottom > y) {
|
||||
const relativeX = x - iframeRect.x;
|
||||
const relativeY = y - iframeRect.y;
|
||||
|
||||
return editor.getDoc().elementFromPoint(relativeX, relativeY);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
interface ContextMenuActionOptions {
|
||||
current: ContextMenuOptions,
|
||||
}
|
||||
|
||||
const contextMenuActionOptions:ContextMenuActionOptions = { current: null };
|
||||
|
||||
export default function(editor:any) {
|
||||
const contextMenuItems = menuItems();
|
||||
|
||||
bridge().window().webContents.on('context-menu', (_event:any, params:any) => {
|
||||
const element = contextMenuElement(editor, params.x, params.y);
|
||||
if (!element) return;
|
||||
|
||||
const Menu = bridge().Menu;
|
||||
const MenuItem = bridge().MenuItem;
|
||||
|
||||
let itemType:ContextMenuItemType = ContextMenuItemType.None;
|
||||
let resourceId = '';
|
||||
let linkToCopy = null;
|
||||
|
||||
if (element.nodeName === 'IMG') {
|
||||
itemType = ContextMenuItemType.Image;
|
||||
resourceId = Resource.pathToId(element.src);
|
||||
} else if (element.nodeName === 'A') {
|
||||
resourceId = Resource.pathToId(element.href);
|
||||
itemType = resourceId ? ContextMenuItemType.Resource : ContextMenuItemType.Link;
|
||||
linkToCopy = element.getAttribute('href') || '';
|
||||
} else {
|
||||
itemType = ContextMenuItemType.Text;
|
||||
}
|
||||
|
||||
contextMenuActionOptions.current = {
|
||||
itemType,
|
||||
resourceId,
|
||||
linkToCopy,
|
||||
textToCopy: null,
|
||||
htmlToCopy: editor.selection ? editor.selection.getContent() : '',
|
||||
insertContent: (content:string) => {
|
||||
editor.insertContent(content);
|
||||
},
|
||||
isReadOnly: false,
|
||||
};
|
||||
|
||||
const menu = new Menu();
|
||||
|
||||
for (const itemName in contextMenuItems) {
|
||||
const item = contextMenuItems[itemName];
|
||||
|
||||
if (!item.isActive(itemType, contextMenuActionOptions.current)) continue;
|
||||
|
||||
menu.append(new MenuItem({
|
||||
label: item.label,
|
||||
click: () => {
|
||||
item.onAction(contextMenuActionOptions.current);
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
const spellCheckerMenuItems = SpellCheckerService.instance().contextMenuItems(params.misspelledWord, params.dictionarySuggestions);
|
||||
|
||||
for (const item of spellCheckerMenuItems) {
|
||||
menu.append(item);
|
||||
}
|
||||
|
||||
menu.popup();
|
||||
});
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
import * as React from 'react';
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import TinyMCE from './NoteBody/TinyMCE/TinyMCE';
|
||||
import CodeMirror from './NoteBody/CodeMirror/CodeMirror';
|
||||
import { connect } from 'react-redux';
|
||||
import MultiNoteActions from '../MultiNoteActions';
|
||||
import NoteToolbar from '../NoteToolbar/NoteToolbar';
|
||||
import { htmlToMarkdown, formNoteToNote } from './utils';
|
||||
import useSearchMarkers from './utils/useSearchMarkers';
|
||||
import useNoteSearchBar from './utils/useNoteSearchBar';
|
||||
@@ -27,15 +25,15 @@ import ToolbarButtonUtils from 'lib/services/commands/ToolbarButtonUtils';
|
||||
import { _ } from 'lib/locale';
|
||||
import stateToWhenClauseContext from 'lib/services/commands/stateToWhenClauseContext';
|
||||
import TagList from '../TagList';
|
||||
import NoteTitleBar from './NoteTitle/NoteTitleBar';
|
||||
import markupLanguageUtils from 'lib/markupLanguageUtils';
|
||||
import usePrevious from 'lib/hooks/usePrevious';
|
||||
import Setting from 'lib/models/Setting';
|
||||
|
||||
const { themeStyle } = require('lib/theme');
|
||||
const { substrWithEllipsis } = require('lib/string-utils');
|
||||
const NoteSearchBar = require('../NoteSearchBar.min.js');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const markupLanguageUtils = require('lib/markupLanguageUtils').default;
|
||||
const usePrevious = require('lib/hooks/usePrevious').default;
|
||||
const Setting = require('lib/models/Setting').default;
|
||||
const Note = require('lib/models/Note.js');
|
||||
const bridge = require('electron').remote.require('./bridge').default;
|
||||
const ExternalEditWatcher = require('lib/services/ExternalEditWatcher');
|
||||
@@ -45,10 +43,6 @@ const commands = [
|
||||
require('./commands/showRevisions'),
|
||||
];
|
||||
|
||||
const toolbarStyle = {
|
||||
marginBottom: 0,
|
||||
};
|
||||
|
||||
const toolbarButtonUtils = new ToolbarButtonUtils(CommandService.instance());
|
||||
|
||||
function NoteEditor(props: NoteEditorProps) {
|
||||
@@ -244,20 +238,20 @@ function NoteEditor(props: NoteEditorProps) {
|
||||
|
||||
const onTitleChange = useCallback((event: any) => onFieldChange('title', event.target.value), [onFieldChange]);
|
||||
|
||||
const onTitleKeydown = useCallback((event:any) => {
|
||||
const keyCode = event.keyCode;
|
||||
// const onTitleKeydown = useCallback((event:any) => {
|
||||
// const keyCode = event.keyCode;
|
||||
|
||||
if (keyCode === 9) {
|
||||
// TAB
|
||||
event.preventDefault();
|
||||
// if (keyCode === 9) {
|
||||
// // TAB
|
||||
// event.preventDefault();
|
||||
|
||||
if (event.shiftKey) {
|
||||
CommandService.instance().execute('focusElement', 'noteList');
|
||||
} else {
|
||||
CommandService.instance().execute('focusElement', 'noteBody');
|
||||
}
|
||||
}
|
||||
}, [props.dispatch]);
|
||||
// if (event.shiftKey) {
|
||||
// CommandService.instance().execute('focusElement', 'noteList');
|
||||
// } else {
|
||||
// CommandService.instance().execute('focusElement', 'noteBody');
|
||||
// }
|
||||
// }
|
||||
// }, [props.dispatch]);
|
||||
|
||||
const onBodyWillChange = useCallback((event: any) => {
|
||||
handleProvisionalFlag();
|
||||
@@ -351,14 +345,6 @@ function NoteEditor(props: NoteEditorProps) {
|
||||
return <div style={emptyDivStyle}></div>;
|
||||
}
|
||||
|
||||
function renderNoteToolbar() {
|
||||
return <NoteToolbar
|
||||
themeId={props.themeId}
|
||||
// note={formNote}
|
||||
style={toolbarStyle}
|
||||
/>;
|
||||
}
|
||||
|
||||
function renderTagButton() {
|
||||
return <ToolbarButton
|
||||
themeId={props.themeId}
|
||||
@@ -377,26 +363,6 @@ function NoteEditor(props: NoteEditorProps) {
|
||||
);
|
||||
}
|
||||
|
||||
function renderTitleBar() {
|
||||
const theme = themeStyle(props.themeId);
|
||||
const titleBarDate = <span style={styles.titleDate}>{time.formatMsToLocal(formNote.user_updated_time)}</span>;
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', height: theme.topRowHeight }}>
|
||||
<input
|
||||
type="text"
|
||||
ref={titleInputRef}
|
||||
placeholder={props.isProvisional ? _('Creating new %s...', formNote.is_todo ? _('to-do') : _('note')) : ''}
|
||||
style={styles.titleInput}
|
||||
onChange={onTitleChange}
|
||||
onKeyDown={onTitleKeydown}
|
||||
value={formNote.title}
|
||||
/>
|
||||
{titleBarDate}
|
||||
{renderNoteToolbar()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const searchMarkers = useSearchMarkers(showLocalSearch, localSearchMarkerOptions, props.searches, props.selectedSearchId, props.highlightedWords);
|
||||
|
||||
const editorProps:NoteBodyEditorProps = {
|
||||
@@ -546,7 +512,15 @@ function NoteEditor(props: NoteEditorProps) {
|
||||
<div style={styles.root} onDrop={onDrop}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
{renderResourceWatchingNotification()}
|
||||
{renderTitleBar()}
|
||||
<NoteTitleBar
|
||||
titleInputRef={titleInputRef}
|
||||
themeId={props.themeId}
|
||||
isProvisional={props.isProvisional}
|
||||
noteIsTodo={formNote.is_todo}
|
||||
noteTitle={formNote.title}
|
||||
noteUserUpdatedTime={formNote.user_updated_time}
|
||||
onTitleChange={onTitleChange}
|
||||
/>
|
||||
{renderSearchInfo()}
|
||||
<div style={{ display: 'flex', flex: 1 }}>
|
||||
{editor}
|
||||
|
||||
98
ElectronClient/gui/NoteEditor/NoteTitle/NoteTitleBar.tsx
Normal file
98
ElectronClient/gui/NoteEditor/NoteTitle/NoteTitleBar.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import * as React from 'react';
|
||||
import { _ } from 'lib/locale';
|
||||
import CommandService from 'lib/services/CommandService';
|
||||
import { ChangeEvent, useCallback } from 'react';
|
||||
import NoteToolbar from '../../NoteToolbar/NoteToolbar';
|
||||
import { buildStyle } from 'lib/theme';
|
||||
import time from 'lib/time';
|
||||
|
||||
interface Props {
|
||||
themeId: number,
|
||||
noteUserUpdatedTime: number,
|
||||
noteTitle: string,
|
||||
noteIsTodo: number,
|
||||
isProvisional: boolean,
|
||||
titleInputRef: any,
|
||||
onTitleChange(event: ChangeEvent<HTMLInputElement>):void,
|
||||
}
|
||||
|
||||
function styles_(props: Props) {
|
||||
return buildStyle(['NoteEditorTitleBar'], props.themeId, (theme: any) => {
|
||||
return {
|
||||
root: {
|
||||
display: 'flex', flexDirection: 'row', alignItems: 'center', height: theme.topRowHeight,
|
||||
},
|
||||
titleInput: {
|
||||
flex: 1,
|
||||
display: 'inline-block',
|
||||
paddingTop: 5,
|
||||
minHeight: 35,
|
||||
boxSizing: 'border-box',
|
||||
fontWeight: 'bold',
|
||||
paddingBottom: 5,
|
||||
paddingLeft: 0,
|
||||
paddingRight: 8,
|
||||
marginLeft: 5,
|
||||
color: theme.textStyle.color,
|
||||
fontSize: Math.round(theme.textStyle.fontSize * 1.5),
|
||||
backgroundColor: theme.backgroundColor,
|
||||
border: 'none',
|
||||
},
|
||||
|
||||
titleDate: {
|
||||
...theme.textStyle,
|
||||
color: theme.colorFaded,
|
||||
paddingLeft: 10,
|
||||
paddingRight: 10,
|
||||
},
|
||||
toolbarStyle: {
|
||||
marginBottom: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export default function NoteTitleBar(props:Props) {
|
||||
const styles = styles_(props);
|
||||
|
||||
const onTitleKeydown = useCallback((event:any) => {
|
||||
const keyCode = event.keyCode;
|
||||
|
||||
if (keyCode === 9) { // TAB
|
||||
event.preventDefault();
|
||||
|
||||
if (event.shiftKey) {
|
||||
CommandService.instance().execute('focusElement', 'noteList');
|
||||
} else {
|
||||
CommandService.instance().execute('focusElement', 'noteBody');
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
function renderTitleBarDate() {
|
||||
return <span style={styles.titleDate}>{time.formatMsToLocal(props.noteUserUpdatedTime)}</span>;
|
||||
}
|
||||
|
||||
function renderNoteToolbar() {
|
||||
return <NoteToolbar
|
||||
themeId={props.themeId}
|
||||
style={styles.toolbarStyle}
|
||||
/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={styles.root}>
|
||||
<input
|
||||
type="text"
|
||||
ref={props.titleInputRef}
|
||||
placeholder={props.isProvisional ? _('Creating new %s...', props.noteIsTodo ? _('to-do') : _('note')) : ''}
|
||||
style={styles.titleInput}
|
||||
onChange={props.onTitleChange}
|
||||
onKeyDown={onTitleKeydown}
|
||||
value={props.noteTitle}
|
||||
/>
|
||||
{renderTitleBarDate()}
|
||||
{renderNoteToolbar()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { useEffect } from 'react';
|
||||
import { FormNote, ScrollOptionTypes } from './types';
|
||||
import editorCommandDeclarations from '../commands/editorCommandDeclarations';
|
||||
import CommandService, { CommandDeclaration, CommandRuntime, CommandContext } from 'lib/services/CommandService';
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { reg } = require('lib/registry.js');
|
||||
|
||||
const commandsWithDependencies = [
|
||||
|
||||
@@ -5,7 +5,7 @@ import { _ } from 'lib/locale';
|
||||
const { ItemList } = require('../ItemList.min.js');
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { themeStyle } = require('lib/theme');
|
||||
const BaseModel = require('lib/BaseModel');
|
||||
const bridge = require('electron').remote.require('./bridge').default;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const React = require('react');
|
||||
const { _ } = require('lib/locale');
|
||||
const { themeStyle } = require('lib/theme');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const DialogButtonRow = require('./DialogButtonRow.min');
|
||||
const Datetime = require('react-datetime');
|
||||
const Note = require('lib/models/Note');
|
||||
|
||||
@@ -11,7 +11,7 @@ const Setting = require('lib/models/Setting').default;
|
||||
const RevisionService = require('lib/services/RevisionService');
|
||||
const shared = require('lib/components/shared/note-screen-shared.js');
|
||||
const { MarkupToHtml } = require('lib/joplin-renderer');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const ReactTooltip = require('react-tooltip');
|
||||
const { urlDecode, substrWithEllipsis } = require('lib/string-utils');
|
||||
const bridge = require('electron').remote.require('./bridge').default;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { themeStyle } = require('lib/theme');
|
||||
|
||||
class NoteStatusBarComponent extends React.Component {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const React = require('react');
|
||||
const { _ } = require('lib/locale');
|
||||
const { themeStyle } = require('lib/theme');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const Datetime = require('react-datetime');
|
||||
const CreatableSelect = require('react-select/lib/Creatable').default;
|
||||
const Select = require('react-select').default;
|
||||
|
||||
37
ElectronClient/gui/menuCommandNames.ts
Normal file
37
ElectronClient/gui/menuCommandNames.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
export default function() {
|
||||
return [
|
||||
'attachFile',
|
||||
'copyDevCommand',
|
||||
'exportPdf',
|
||||
'focusElementNoteBody',
|
||||
'focusElementNoteList',
|
||||
'focusElementNoteTitle',
|
||||
'focusElementSideBar',
|
||||
'focusSearch',
|
||||
'historyBackward',
|
||||
'historyForward',
|
||||
'insertDateTime',
|
||||
'newFolder',
|
||||
'newNote',
|
||||
'newSubFolder',
|
||||
'newTodo',
|
||||
'openProfileDirectory',
|
||||
'print',
|
||||
'setTags',
|
||||
'showLocalSearch',
|
||||
'showNoteContentProperties',
|
||||
'synchronize',
|
||||
'textBold',
|
||||
'textCode',
|
||||
'textCopy',
|
||||
'textCut',
|
||||
'textItalic',
|
||||
'textLink',
|
||||
'textPaste',
|
||||
'textSelectAll',
|
||||
'toggleExternalEditing',
|
||||
'toggleNoteList',
|
||||
'toggleSideBar',
|
||||
'toggleVisiblePanes',
|
||||
];
|
||||
}
|
||||
2
ElectronClient/package-lock.json
generated
2
ElectronClient/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Joplin",
|
||||
"version": "1.3.10",
|
||||
"version": "1.3.11",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Joplin",
|
||||
"version": "1.3.10",
|
||||
"version": "1.3.11",
|
||||
"description": "Joplin for Desktop",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -557,7 +557,7 @@ GotoAnything.manifest = {
|
||||
menuItems: [
|
||||
{
|
||||
name: 'main',
|
||||
parent: 'tools',
|
||||
parent: 'go',
|
||||
label: _('Goto Anything...'),
|
||||
accelerator: () => KeymapService.instance().getAccelerator('gotoAnything'),
|
||||
screens: ['Main'],
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
// Provides spell checking feature via the native Electron built-in spell checker
|
||||
|
||||
import SpellCheckerServiceDriverBase from 'lib/services/spellChecker/SpellCheckerServiceDriverBase';
|
||||
import bridge from '../bridge';
|
||||
|
||||
export default class SpellCheckerServiceDriverNative extends SpellCheckerServiceDriverBase {
|
||||
|
||||
private session():any {
|
||||
return bridge().window().webContents.session;
|
||||
}
|
||||
|
||||
public get availableLanguages():string[] {
|
||||
return this.session().availableSpellCheckerLanguages;
|
||||
}
|
||||
|
||||
// Language can be set to '' to disable spell-checking
|
||||
public setLanguage(v:string) {
|
||||
// If we pass an empty array, it disables spell checking
|
||||
// https://github.com/electron/electron/issues/25228
|
||||
this.session().setSpellCheckerLanguages(v ? [v] : []);
|
||||
}
|
||||
|
||||
public get language():string {
|
||||
const languages = this.session().getSpellCheckerLanguages();
|
||||
return languages.length ? languages[0] : '';
|
||||
}
|
||||
|
||||
public makeMenuItem(item:any):any {
|
||||
const MenuItem = bridge().MenuItem;
|
||||
return new MenuItem(item);
|
||||
}
|
||||
|
||||
public addWordToSpellCheckerDictionary(_language:string, word:string) {
|
||||
// Actually on Electron all languages share the same dictionary, or
|
||||
// perhaps it's added to the currently active language.
|
||||
this.session().addWordToSpellCheckerDictionary(word);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,876 +0,0 @@
|
||||
import setUpQuickActions from './setUpQuickActions';
|
||||
import PluginAssetsLoader from './PluginAssetsLoader';
|
||||
import reducer, { defaultState, State } from 'lib/reducer';
|
||||
import AlarmService from 'lib/services/AlarmService';
|
||||
import AlarmServiceDriver from 'lib/services/AlarmServiceDriver.ios';
|
||||
import Alarm from 'lib/models/Alarm';
|
||||
import Logger, { TargetType } from 'lib/Logger';
|
||||
import BaseService from 'lib/services/BaseService';
|
||||
import NoteScreen from 'lib/components/screens/Note';
|
||||
import UpgradeSyncTargetScreen from 'lib/components/screens/UpgradeSyncTargetScreen';
|
||||
import Setting from 'lib/models/Setting';
|
||||
import PoorManIntervals from 'lib/PoorManIntervals';
|
||||
import ShareExtension from 'lib/ShareExtension';
|
||||
import handleShared from 'lib/shareHandler';
|
||||
import uuid from 'lib/uuid';
|
||||
import KeychainServiceDriverMobile from 'lib/services/keychain/KeychainServiceDriver.mobile';
|
||||
import shim from 'lib/shim';
|
||||
import PluginService from 'lib/services/plugins/PluginService';
|
||||
import PluginRunner from './services/plugins/PluginRunner';
|
||||
|
||||
const React = require('react');
|
||||
const { AppState, Keyboard, NativeModules, BackHandler, Animated, View, StatusBar } = require('react-native');
|
||||
const SafeAreaView = require('lib/components/SafeAreaView');
|
||||
const { connect, Provider } = require('react-redux');
|
||||
const { BackButtonService } = require('lib/services/back-button.js');
|
||||
const NavService = require('lib/services/NavService.js');
|
||||
const { createStore, applyMiddleware } = require('redux');
|
||||
const reduxSharedMiddleware = require('lib/components/shared/reduxSharedMiddleware');
|
||||
const { shimInit } = require('lib/shim-init-react.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const { AppNav } = require('lib/components/app-nav.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const BaseSyncTarget = require('lib/BaseSyncTarget.js');
|
||||
const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
|
||||
const Resource = require('lib/models/Resource.js');
|
||||
const Tag = require('lib/models/Tag.js');
|
||||
const NoteTag = require('lib/models/NoteTag.js');
|
||||
const BaseItem = require('lib/models/BaseItem.js');
|
||||
const MasterKey = require('lib/models/MasterKey.js');
|
||||
const Revision = require('lib/models/Revision.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const ResourceService = require('lib/services/ResourceService');
|
||||
const RevisionService = require('lib/services/RevisionService');
|
||||
const KvStore = require('lib/services/KvStore');
|
||||
const { JoplinDatabase } = require('lib/joplin-database.js');
|
||||
const { Database } = require('lib/database.js');
|
||||
const { NotesScreen } = require('lib/components/screens/notes.js');
|
||||
const { TagsScreen } = require('lib/components/screens/tags.js');
|
||||
const { ConfigScreen } = require('lib/components/screens/config.js');
|
||||
const { FolderScreen } = require('lib/components/screens/folder.js');
|
||||
const { LogScreen } = require('lib/components/screens/log.js');
|
||||
const { StatusScreen } = require('lib/components/screens/status.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 { MenuContext } = require('react-native-popup-menu');
|
||||
const { SideMenu } = require('lib/components/side-menu.js');
|
||||
const { SideMenuContent } = require('lib/components/side-menu-content.js');
|
||||
const { SideMenuContentNote } = require('lib/components/side-menu-content-note.js');
|
||||
const { DatabaseDriverReactNative } = require('lib/database-driver-react-native');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const { setLocale, closestSupportedLocale, defaultLocale } = require('lib/locale');
|
||||
const { FileApiDriverLocal } = require('lib/file-api-driver-local.js');
|
||||
const RNFetchBlob = require('rn-fetch-blob').default;
|
||||
const DropdownAlert = require('react-native-dropdownalert').default;
|
||||
const ResourceFetcher = require('lib/services/ResourceFetcher');
|
||||
const SearchEngine = require('lib/services/searchengine/SearchEngine');
|
||||
const WelcomeUtils = require('lib/WelcomeUtils');
|
||||
const { themeStyle } = require('lib/components/global-style.js');
|
||||
|
||||
const { loadKeychainServiceAndSettings } = require('lib/services/SettingUtils');
|
||||
|
||||
const SyncTargetRegistry = require('lib/SyncTargetRegistry.js');
|
||||
const SyncTargetOneDrive = require('lib/SyncTargetOneDrive.js');
|
||||
const SyncTargetFilesystem = require('lib/SyncTargetFilesystem.js');
|
||||
const SyncTargetNextcloud = require('lib/SyncTargetNextcloud.js');
|
||||
const SyncTargetWebDAV = require('lib/SyncTargetWebDAV.js');
|
||||
const SyncTargetDropbox = require('lib/SyncTargetDropbox.js');
|
||||
const SyncTargetAmazonS3 = require('lib/SyncTargetAmazonS3.js');
|
||||
|
||||
SyncTargetRegistry.addClass(SyncTargetOneDrive);
|
||||
SyncTargetRegistry.addClass(SyncTargetNextcloud);
|
||||
SyncTargetRegistry.addClass(SyncTargetWebDAV);
|
||||
SyncTargetRegistry.addClass(SyncTargetDropbox);
|
||||
SyncTargetRegistry.addClass(SyncTargetFilesystem);
|
||||
SyncTargetRegistry.addClass(SyncTargetAmazonS3);
|
||||
|
||||
const FsDriverRN = require('lib/fs-driver-rn.js').FsDriverRN;
|
||||
const DecryptionWorker = require('lib/services/DecryptionWorker');
|
||||
const EncryptionService = require('lib/services/EncryptionService');
|
||||
const MigrationService = require('lib/services/MigrationService');
|
||||
|
||||
const DEFAULT_ROUTE = {
|
||||
type: 'NAV_GO',
|
||||
routeName: 'Notes',
|
||||
smartFilterId: 'c3176726992c11e9ac940492261af972',
|
||||
};
|
||||
|
||||
export interface AppState extends State {
|
||||
sideMenuOpenPercent: number,
|
||||
route: any,
|
||||
noteSelectionEnabled: boolean,
|
||||
noteSideMenuOptions: any,
|
||||
}
|
||||
|
||||
const appDefaultState:AppState = {
|
||||
...defaultState,
|
||||
sideMenuOpenPercent: 0,
|
||||
route: DEFAULT_ROUTE,
|
||||
noteSelectionEnabled: false,
|
||||
noteSideMenuOptions: null,
|
||||
}
|
||||
|
||||
let storeDispatch = function(_action:any) {};
|
||||
|
||||
const logReducerAction = function(action:any) {
|
||||
if (['SIDE_MENU_OPEN_PERCENT', 'SYNC_REPORT_UPDATE'].indexOf(action.type) >= 0) return;
|
||||
|
||||
const msg = [action.type];
|
||||
if (action.routeName) msg.push(action.routeName);
|
||||
|
||||
// reg.logger().debug('Reducer action', msg.join(', '));
|
||||
};
|
||||
|
||||
const generalMiddleware = (store:any) => (next:any) => async (action:any) => {
|
||||
logReducerAction(action);
|
||||
PoorManIntervals.update(); // This function needs to be called regularly so put it here
|
||||
|
||||
const result = next(action);
|
||||
const newState:AppState = store.getState();
|
||||
|
||||
await reduxSharedMiddleware(store, next, action);
|
||||
|
||||
if (action.type == 'NAV_GO') Keyboard.dismiss();
|
||||
|
||||
if (['NOTE_UPDATE_ONE', 'NOTE_DELETE', 'FOLDER_UPDATE_ONE', 'FOLDER_DELETE'].indexOf(action.type) >= 0) {
|
||||
if (!await reg.syncTarget().syncStarted()) reg.scheduleSync(5 * 1000, { syncSteps: ['update_remote', 'delete_remote'] });
|
||||
SearchEngine.instance().scheduleSyncTables();
|
||||
}
|
||||
|
||||
if (['EVENT_NOTE_ALARM_FIELD_CHANGE', 'NOTE_DELETE'].indexOf(action.type) >= 0) {
|
||||
await AlarmService.updateNoteNotification(action.id, action.type === 'NOTE_DELETE');
|
||||
}
|
||||
|
||||
if (action.type == 'SETTING_UPDATE_ONE' && action.key == 'sync.interval' || action.type == 'SETTING_UPDATE_ALL') {
|
||||
reg.setupRecurrentSync();
|
||||
}
|
||||
|
||||
if ((action.type == 'SETTING_UPDATE_ONE' && (action.key == 'dateFormat' || action.key == 'timeFormat')) || (action.type == 'SETTING_UPDATE_ALL')) {
|
||||
time.setDateFormat(Setting.value('dateFormat'));
|
||||
time.setTimeFormat(Setting.value('timeFormat'));
|
||||
}
|
||||
|
||||
if (action.type == 'SETTING_UPDATE_ONE' && action.key == 'locale' || action.type == 'SETTING_UPDATE_ALL') {
|
||||
setLocale(Setting.value('locale'));
|
||||
}
|
||||
|
||||
if ((action.type == 'SETTING_UPDATE_ONE' && (action.key.indexOf('encryption.') === 0)) || (action.type == 'SETTING_UPDATE_ALL')) {
|
||||
await EncryptionService.instance().loadMasterKeysFromSettings();
|
||||
DecryptionWorker.instance().scheduleStart();
|
||||
const loadedMasterKeyIds = EncryptionService.instance().loadedMasterKeyIds();
|
||||
|
||||
storeDispatch({
|
||||
type: 'MASTERKEY_REMOVE_NOT_LOADED',
|
||||
ids: loadedMasterKeyIds,
|
||||
});
|
||||
|
||||
// Schedule a sync operation so that items that need to be encrypted
|
||||
// are sent to sync target.
|
||||
reg.scheduleSync();
|
||||
}
|
||||
|
||||
if (action.type == 'NAV_GO' && action.routeName == 'Notes') {
|
||||
Setting.setValue('activeFolderId', newState.selectedFolderId);
|
||||
}
|
||||
|
||||
if (action.type === 'SYNC_GOT_ENCRYPTED_ITEM') {
|
||||
DecryptionWorker.instance().scheduleStart();
|
||||
}
|
||||
|
||||
if (action.type === 'SYNC_CREATED_OR_UPDATED_RESOURCE') {
|
||||
ResourceFetcher.instance().autoAddResources();
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const navHistory:any[] = [];
|
||||
|
||||
function historyCanGoBackTo(route:any) {
|
||||
if (route.routeName === 'Note') return false;
|
||||
if (route.routeName === 'Folder') return false;
|
||||
|
||||
// There's no point going back to these screens in general and, at least in OneDrive case,
|
||||
// it can be buggy to do so, due to incorrectly relying on global state (reg.syncTarget...)
|
||||
if (route.routeName === 'OneDriveLogin') return false;
|
||||
if (route.routeName === 'DropboxLogin') return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const appReducer = (state:AppState = appDefaultState, action:any) => {
|
||||
let newState = state;
|
||||
let historyGoingBack = false;
|
||||
|
||||
try {
|
||||
switch (action.type) {
|
||||
|
||||
// @ts-ignore
|
||||
case 'NAV_BACK':
|
||||
|
||||
{
|
||||
if (!navHistory.length) break;
|
||||
|
||||
let newAction = null;
|
||||
while (navHistory.length) {
|
||||
newAction = navHistory.pop();
|
||||
if (newAction.routeName != state.route.routeName) break;
|
||||
}
|
||||
|
||||
action = newAction ? newAction : navHistory.pop();
|
||||
|
||||
historyGoingBack = true;
|
||||
}
|
||||
|
||||
// Fall throught
|
||||
|
||||
case 'NAV_GO':
|
||||
|
||||
{
|
||||
const currentRoute = state.route;
|
||||
|
||||
if (!historyGoingBack && historyCanGoBackTo(currentRoute)) {
|
||||
// If the route *name* is the same (even if the other parameters are different), we
|
||||
// overwrite the last route in the history with the current one. If the route name
|
||||
// is different, we push a new history entry.
|
||||
if (currentRoute.routeName == action.routeName) {
|
||||
// nothing
|
||||
} else {
|
||||
navHistory.push(currentRoute);
|
||||
}
|
||||
}
|
||||
|
||||
// HACK: whenever a new screen is loaded, all the previous screens of that type
|
||||
// are overwritten with the new screen parameters. This is because the way notes
|
||||
// are currently loaded is not optimal (doesn't retain history properly) so
|
||||
// this is a simple fix without doing a big refactoring to change the way notes
|
||||
// are loaded. Might be good enough since going back to different folders
|
||||
// is probably not a common workflow.
|
||||
for (let i = 0; i < navHistory.length; i++) {
|
||||
const n = navHistory[i];
|
||||
if (n.routeName == action.routeName) {
|
||||
navHistory[i] = Object.assign({}, action);
|
||||
}
|
||||
}
|
||||
|
||||
newState = Object.assign({}, state);
|
||||
|
||||
newState.selectedNoteHash = '';
|
||||
|
||||
if ('noteId' in action) {
|
||||
newState.selectedNoteIds = action.noteId ? [action.noteId] : [];
|
||||
}
|
||||
|
||||
if ('folderId' in action) {
|
||||
newState.selectedFolderId = action.folderId;
|
||||
newState.notesParentType = 'Folder';
|
||||
}
|
||||
|
||||
if ('tagId' in action) {
|
||||
newState.selectedTagId = action.tagId;
|
||||
newState.notesParentType = 'Tag';
|
||||
}
|
||||
|
||||
if ('smartFilterId' in action) {
|
||||
newState.selectedSmartFilterId = action.smartFilterId;
|
||||
newState.notesParentType = 'SmartFilter';
|
||||
}
|
||||
|
||||
if ('itemType' in action) {
|
||||
newState.selectedItemType = action.itemType;
|
||||
}
|
||||
|
||||
if ('noteHash' in action) {
|
||||
newState.selectedNoteHash = action.noteHash;
|
||||
}
|
||||
|
||||
if ('sharedData' in action) {
|
||||
newState.sharedData = action.sharedData;
|
||||
} else {
|
||||
newState.sharedData = null;
|
||||
}
|
||||
|
||||
newState.route = action;
|
||||
newState.historyCanGoBack = !!navHistory.length;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'SIDE_MENU_TOGGLE':
|
||||
|
||||
newState = Object.assign({}, state);
|
||||
newState.showSideMenu = !newState.showSideMenu;
|
||||
break;
|
||||
|
||||
case 'SIDE_MENU_OPEN':
|
||||
|
||||
newState = Object.assign({}, state);
|
||||
newState.showSideMenu = true;
|
||||
break;
|
||||
|
||||
case 'SIDE_MENU_CLOSE':
|
||||
|
||||
newState = Object.assign({}, state);
|
||||
newState.showSideMenu = false;
|
||||
break;
|
||||
|
||||
case 'SIDE_MENU_OPEN_PERCENT':
|
||||
|
||||
newState = Object.assign({}, state);
|
||||
newState.sideMenuOpenPercent = action.value;
|
||||
break;
|
||||
|
||||
case 'NOTE_SELECTION_TOGGLE':
|
||||
|
||||
{
|
||||
newState = Object.assign({}, state);
|
||||
|
||||
const noteId = action.id;
|
||||
const newSelectedNoteIds = state.selectedNoteIds.slice();
|
||||
const existingIndex = state.selectedNoteIds.indexOf(noteId);
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
newSelectedNoteIds.splice(existingIndex, 1);
|
||||
} else {
|
||||
newSelectedNoteIds.push(noteId);
|
||||
}
|
||||
|
||||
newState.selectedNoteIds = newSelectedNoteIds;
|
||||
newState.noteSelectionEnabled = !!newSelectedNoteIds.length;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'NOTE_SELECTION_START':
|
||||
|
||||
if (!state.noteSelectionEnabled) {
|
||||
newState = Object.assign({}, state);
|
||||
newState.noteSelectionEnabled = true;
|
||||
newState.selectedNoteIds = [action.id];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'NOTE_SELECTION_END':
|
||||
|
||||
newState = Object.assign({}, state);
|
||||
newState.noteSelectionEnabled = false;
|
||||
newState.selectedNoteIds = [];
|
||||
break;
|
||||
|
||||
case 'NOTE_SIDE_MENU_OPTIONS_SET':
|
||||
|
||||
newState = Object.assign({}, state);
|
||||
newState.noteSideMenuOptions = action.options;
|
||||
break;
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
error.message = `In reducer: ${error.message} Action: ${JSON.stringify(action)}`;
|
||||
throw error;
|
||||
}
|
||||
|
||||
return reducer(newState, action);
|
||||
};
|
||||
|
||||
const store = createStore(appReducer, applyMiddleware(generalMiddleware));
|
||||
storeDispatch = store.dispatch;
|
||||
|
||||
function resourceFetcher_downloadComplete(event:any) {
|
||||
if (event.encrypted) {
|
||||
DecryptionWorker.instance().scheduleStart();
|
||||
}
|
||||
}
|
||||
|
||||
function decryptionWorker_resourceMetadataButNotBlobDecrypted() {
|
||||
ResourceFetcher.instance().scheduleAutoAddResources();
|
||||
}
|
||||
|
||||
async function initialize(dispatch:Function) {
|
||||
shimInit();
|
||||
|
||||
Setting.setConstant('env', __DEV__ ? 'dev' : 'prod');
|
||||
Setting.setConstant('appId', 'net.cozic.joplin-mobile');
|
||||
Setting.setConstant('appType', 'mobile');
|
||||
Setting.setConstant('resourceDir', RNFetchBlob.fs.dirs.DocumentDir);
|
||||
Setting.setConstant('pluginDir', Setting.value('resourceDir') + '/plugins');
|
||||
|
||||
const logDatabase = new Database(new DatabaseDriverReactNative());
|
||||
await logDatabase.open({ name: 'log.sqlite' });
|
||||
await logDatabase.exec(Logger.databaseCreateTableSql());
|
||||
|
||||
const mainLogger = new Logger();
|
||||
mainLogger.addTarget(TargetType.Database, { database: logDatabase, source: 'm' });
|
||||
mainLogger.setLevel(Logger.LEVEL_INFO);
|
||||
|
||||
if (Setting.value('env') == 'dev') {
|
||||
mainLogger.addTarget(TargetType.Console);
|
||||
mainLogger.setLevel(Logger.LEVEL_DEBUG);
|
||||
}
|
||||
|
||||
reg.setLogger(mainLogger);
|
||||
reg.setShowErrorMessageBoxHandler((message:string) => { alert(message); });
|
||||
|
||||
BaseService.logger_ = mainLogger;
|
||||
// require('lib/ntpDate').setLogger(reg.logger());
|
||||
|
||||
reg.logger().info('====================================');
|
||||
reg.logger().info(`Starting application ${Setting.value('appId')} (${Setting.value('env')})`);
|
||||
|
||||
const dbLogger = new Logger();
|
||||
dbLogger.addTarget(TargetType.Database, { database: logDatabase, source: 'm' });
|
||||
if (Setting.value('env') == 'dev') {
|
||||
dbLogger.addTarget(TargetType.Console);
|
||||
dbLogger.setLevel(Logger.LEVEL_INFO); // Set to LEVEL_DEBUG for full SQL queries
|
||||
} else {
|
||||
dbLogger.setLevel(Logger.LEVEL_INFO);
|
||||
}
|
||||
|
||||
const db = new JoplinDatabase(new DatabaseDriverReactNative());
|
||||
db.setLogger(dbLogger);
|
||||
reg.setDb(db);
|
||||
|
||||
reg.dispatch = dispatch;
|
||||
BaseModel.dispatch = dispatch;
|
||||
FoldersScreenUtils.dispatch = dispatch;
|
||||
BaseSyncTarget.dispatch = dispatch;
|
||||
NavService.dispatch = dispatch;
|
||||
BaseModel.setDb(db);
|
||||
|
||||
KvStore.instance().setDb(reg.db());
|
||||
|
||||
BaseItem.loadClass('Note', Note);
|
||||
BaseItem.loadClass('Folder', Folder);
|
||||
BaseItem.loadClass('Resource', Resource);
|
||||
BaseItem.loadClass('Tag', Tag);
|
||||
BaseItem.loadClass('NoteTag', NoteTag);
|
||||
BaseItem.loadClass('MasterKey', MasterKey);
|
||||
BaseItem.loadClass('Revision', Revision);
|
||||
|
||||
const fsDriver = new FsDriverRN();
|
||||
|
||||
Resource.fsDriver_ = fsDriver;
|
||||
FileApiDriverLocal.fsDriver_ = fsDriver;
|
||||
|
||||
AlarmService.setDriver(new AlarmServiceDriver(mainLogger));
|
||||
AlarmService.setLogger(mainLogger);
|
||||
|
||||
try {
|
||||
if (Setting.value('env') == 'prod') {
|
||||
await db.open({ name: 'joplin.sqlite' });
|
||||
} else {
|
||||
await db.open({ name: 'joplin-76.sqlite' });
|
||||
|
||||
// await db.clearForTesting();
|
||||
}
|
||||
|
||||
reg.logger().info('Database is ready.');
|
||||
reg.logger().info('Loading settings...');
|
||||
|
||||
await loadKeychainServiceAndSettings(KeychainServiceDriverMobile);
|
||||
|
||||
if (!Setting.value('clientId')) Setting.setValue('clientId', uuid.create());
|
||||
|
||||
if (Setting.value('firstStart')) {
|
||||
let locale = NativeModules.I18nManager.localeIdentifier;
|
||||
if (!locale) locale = defaultLocale();
|
||||
Setting.setValue('locale', closestSupportedLocale(locale));
|
||||
Setting.setValue('firstStart', 0);
|
||||
}
|
||||
|
||||
if (Setting.value('db.ftsEnabled') === -1) {
|
||||
const ftsEnabled = await db.ftsEnabled();
|
||||
Setting.setValue('db.ftsEnabled', ftsEnabled ? 1 : 0);
|
||||
reg.logger().info('db.ftsEnabled = ', Setting.value('db.ftsEnabled'));
|
||||
}
|
||||
|
||||
if (Setting.value('env') === 'dev') {
|
||||
Setting.setValue('welcome.enabled', false);
|
||||
}
|
||||
|
||||
PluginAssetsLoader.instance().setLogger(mainLogger);
|
||||
await PluginAssetsLoader.instance().importAssets();
|
||||
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
BaseItem.revisionService_ = RevisionService.instance();
|
||||
|
||||
// Note: for now we hard-code the folder sort order as we need to
|
||||
// create a UI to allow customisation (started in branch mobile_add_sidebar_buttons)
|
||||
Setting.setValue('folders.sortOrder.field', 'title');
|
||||
Setting.setValue('folders.sortOrder.reverse', false);
|
||||
|
||||
reg.logger().info(`Sync target: ${Setting.value('sync.target')}`);
|
||||
|
||||
setLocale(Setting.value('locale'));
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// E2EE SETUP
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
EncryptionService.fsDriver_ = fsDriver;
|
||||
EncryptionService.instance().setLogger(mainLogger);
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
BaseItem.encryptionService_ = EncryptionService.instance();
|
||||
DecryptionWorker.instance().dispatch = dispatch;
|
||||
DecryptionWorker.instance().setLogger(mainLogger);
|
||||
DecryptionWorker.instance().setKvStore(KvStore.instance());
|
||||
DecryptionWorker.instance().setEncryptionService(EncryptionService.instance());
|
||||
await EncryptionService.instance().loadMasterKeysFromSettings();
|
||||
DecryptionWorker.instance().on('resourceMetadataButNotBlobDecrypted', decryptionWorker_resourceMetadataButNotBlobDecrypted);
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// / E2EE SETUP
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
reg.logger().info('Loading folders...');
|
||||
|
||||
await FoldersScreenUtils.refreshFolders();
|
||||
|
||||
const tags = await Tag.allWithNotes();
|
||||
|
||||
dispatch({
|
||||
type: 'TAG_UPDATE_ALL',
|
||||
items: tags,
|
||||
});
|
||||
|
||||
const masterKeys = await MasterKey.all();
|
||||
|
||||
dispatch({
|
||||
type: 'MASTERKEY_UPDATE_ALL',
|
||||
items: masterKeys,
|
||||
});
|
||||
|
||||
const folderId = Setting.value('activeFolderId');
|
||||
let folder = await Folder.load(folderId);
|
||||
|
||||
if (!folder) folder = await Folder.defaultFolder();
|
||||
|
||||
dispatch({
|
||||
type: 'FOLDER_SET_COLLAPSED_ALL',
|
||||
ids: Setting.value('collapsedFolderIds'),
|
||||
});
|
||||
|
||||
if (!folder) {
|
||||
dispatch(DEFAULT_ROUTE);
|
||||
} else {
|
||||
dispatch({
|
||||
type: 'NAV_GO',
|
||||
routeName: 'Notes',
|
||||
folderId: folder.id,
|
||||
});
|
||||
}
|
||||
|
||||
setUpQuickActions(dispatch, folderId);
|
||||
} catch (error) {
|
||||
alert(`Initialization error: ${error.message}`);
|
||||
reg.logger().error('Initialization error:', error);
|
||||
}
|
||||
|
||||
reg.setupRecurrentSync();
|
||||
|
||||
PoorManIntervals.setTimeout(() => {
|
||||
AlarmService.garbageCollect();
|
||||
}, 1000 * 60 * 60);
|
||||
|
||||
ResourceService.runInBackground();
|
||||
|
||||
ResourceFetcher.instance().setFileApi(() => { return reg.syncTarget().fileApi(); });
|
||||
ResourceFetcher.instance().setLogger(reg.logger());
|
||||
ResourceFetcher.instance().dispatch = dispatch;
|
||||
ResourceFetcher.instance().on('downloadComplete', resourceFetcher_downloadComplete);
|
||||
ResourceFetcher.instance().start();
|
||||
|
||||
SearchEngine.instance().setDb(reg.db());
|
||||
SearchEngine.instance().setLogger(reg.logger());
|
||||
SearchEngine.instance().scheduleSyncTables();
|
||||
|
||||
await MigrationService.instance().run();
|
||||
|
||||
// When the app starts we want the full sync to
|
||||
// start almost immediately to get the latest data.
|
||||
reg.scheduleSync(1000).then(() => {
|
||||
// Wait for the first sync before updating the notifications, since synchronisation
|
||||
// might change the notifications.
|
||||
AlarmService.updateAllNotifications();
|
||||
|
||||
DecryptionWorker.instance().scheduleStart();
|
||||
});
|
||||
|
||||
await WelcomeUtils.install(dispatch);
|
||||
|
||||
// Collect revisions more frequently on mobile because it doesn't auto-save
|
||||
// and it cannot collect anything when the app is not active.
|
||||
RevisionService.instance().runInBackground(1000 * 30);
|
||||
|
||||
|
||||
|
||||
|
||||
const pluginRunner = new PluginRunner();
|
||||
PluginService.instance().setLogger(reg.logger());
|
||||
PluginService.instance().initialize({
|
||||
joplin: {
|
||||
workspace: {},
|
||||
},
|
||||
}, pluginRunner, store);
|
||||
|
||||
try {
|
||||
if (await shim.fsDriver().exists(Setting.value('pluginDir'))) await PluginService.instance().loadAndRunPlugins(Setting.value('pluginDir'));
|
||||
} catch (error) {
|
||||
this.logger().error(`There was an error loading plugins from ${Setting.value('pluginDir')}:`, error);
|
||||
}
|
||||
|
||||
try {
|
||||
if (Setting.value('plugins.devPluginPaths')) {
|
||||
const paths = Setting.value('plugins.devPluginPaths').split(',').map((p:string) => p.trim());
|
||||
await PluginService.instance().loadAndRunPlugins(paths);
|
||||
}
|
||||
|
||||
// Also load dev plugins that have passed via command line arguments
|
||||
if (Setting.value('startupDevPlugins')) {
|
||||
await PluginService.instance().loadAndRunPlugins(Setting.value('startupDevPlugins'));
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger().error(`There was an error loading plugins from ${Setting.value('plugins.devPluginPaths')}:`, error);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// const pluginString = `
|
||||
// /* joplin-manifest:
|
||||
// {
|
||||
// "manifest_version": 1,
|
||||
// "name": "JS Bundle test",
|
||||
// "description": "JS Bundle Test plugin",
|
||||
// "version": "1.0.0",
|
||||
// "author": "Laurent Cozic",
|
||||
// "homepage_url": "https://joplinapp.org"
|
||||
// }
|
||||
// */
|
||||
|
||||
// joplin.plugins.register({
|
||||
// onStart: async function() {
|
||||
// const folder = await joplin.data.post(['folders'], null, { title: "my plugin folder" });
|
||||
// await joplin.data.post(['notes'], null, { parent_id: folder.id, title: "testing plugin!" });
|
||||
// },
|
||||
// });
|
||||
// `;
|
||||
|
||||
// await shim.fsDriver().writeFile(Setting.value('pluginDir') + '/simple.js', pluginString, 'utf8');
|
||||
|
||||
// console.info(await shim.fsDriver().readFile(Setting.value('pluginDir') + '/simple.js', 'utf8'));
|
||||
|
||||
reg.logger().info('Application initialized');
|
||||
}
|
||||
|
||||
class AppComponent extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
sideMenuContentOpacity: new Animated.Value(0),
|
||||
};
|
||||
|
||||
this.lastSyncStarted_ = defaultState.syncStarted;
|
||||
|
||||
this.backButtonHandler_ = () => {
|
||||
return this.backButtonHandler();
|
||||
};
|
||||
|
||||
this.onAppStateChange_ = () => {
|
||||
PoorManIntervals.update();
|
||||
};
|
||||
}
|
||||
|
||||
// 2020-10-08: It seems the initialisation code is quite fragile in general and should be kept simple.
|
||||
// For example, adding a loading screen as was done in this commit: https://github.com/laurent22/joplin/commit/569355a3182bc12e50a54249882e3d68a72c2b28.
|
||||
// had for effect that sharing with the app would create multiple instances of the app, thus breaking
|
||||
// database access and so on. It's unclear why it happens and how to fix it but reverting that commit
|
||||
// fixed the issue for now.
|
||||
//
|
||||
// Changing app launch mode doesn't help.
|
||||
//
|
||||
// It's possible that it's a bug in React Native, or perhaps the framework expects that the whole app can be
|
||||
// mounted/unmounted or multiple ones can be running at the same time, but the app was not designed in this
|
||||
// way.
|
||||
//
|
||||
// More reports and info about the multiple instance bug:
|
||||
//
|
||||
// https://github.com/laurent22/joplin/issues/3800
|
||||
// https://github.com/laurent22/joplin/issues/3804
|
||||
// https://github.com/laurent22/joplin/issues/3807
|
||||
// https://discourse.joplinapp.org/t/webdav-config-encryption-config-randomly-lost-on-android/11364
|
||||
// https://discourse.joplinapp.org/t/android-keeps-on-resetting-my-sync-and-theme/11443
|
||||
async componentDidMount() {
|
||||
if (this.props.appState == 'starting') {
|
||||
this.props.dispatch({
|
||||
type: 'APP_STATE_SET',
|
||||
state: 'initializing',
|
||||
});
|
||||
|
||||
await initialize(this.props.dispatch);
|
||||
|
||||
this.props.dispatch({
|
||||
type: 'APP_STATE_SET',
|
||||
state: 'ready',
|
||||
});
|
||||
}
|
||||
|
||||
BackButtonService.initialize(this.backButtonHandler_);
|
||||
|
||||
AlarmService.setInAppNotificationHandler(async (alarmId:string) => {
|
||||
const alarm = await Alarm.load(alarmId);
|
||||
const notification = await Alarm.makeNotification(alarm);
|
||||
this.dropdownAlert_.alertWithType('info', notification.title, notification.body ? notification.body : '');
|
||||
});
|
||||
|
||||
AppState.addEventListener('change', this.onAppStateChange_);
|
||||
|
||||
const sharedData = await ShareExtension.data();
|
||||
if (sharedData) {
|
||||
reg.logger().info('Received shared data');
|
||||
if (this.props.selectedFolderId) {
|
||||
handleShared(sharedData, this.props.selectedFolderId, this.props.dispatch);
|
||||
} else {
|
||||
reg.logger.info('Cannot handle share - default folder id is not set');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
AppState.removeEventListener('change', this.onAppStateChange_);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps:any) {
|
||||
if (this.props.showSideMenu !== prevProps.showSideMenu) {
|
||||
Animated.timing(this.state.sideMenuContentOpacity, {
|
||||
toValue: this.props.showSideMenu ? 0.5 : 0,
|
||||
duration: 600,
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
async backButtonHandler() {
|
||||
if (this.props.noteSelectionEnabled) {
|
||||
this.props.dispatch({ type: 'NOTE_SELECTION_END' });
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.props.showSideMenu) {
|
||||
this.props.dispatch({ type: 'SIDE_MENU_CLOSE' });
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.props.historyCanGoBack) {
|
||||
this.props.dispatch({ type: 'NAV_BACK' });
|
||||
return true;
|
||||
}
|
||||
|
||||
BackHandler.exitApp();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(newProps:any) {
|
||||
if (newProps.syncStarted != this.lastSyncStarted_) {
|
||||
if (!newProps.syncStarted) FoldersScreenUtils.refreshFolders();
|
||||
this.lastSyncStarted_ = newProps.syncStarted;
|
||||
}
|
||||
}
|
||||
|
||||
sideMenu_change(isOpen:boolean) {
|
||||
// Make sure showSideMenu property of state is updated
|
||||
// when the menu is open/closed.
|
||||
this.props.dispatch({
|
||||
type: isOpen ? 'SIDE_MENU_OPEN' : 'SIDE_MENU_CLOSE',
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.appState != 'ready') return null;
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
let sideMenuContent = null;
|
||||
let menuPosition = 'left';
|
||||
|
||||
if (this.props.routeName === 'Note') {
|
||||
sideMenuContent = <SafeAreaView style={{ flex: 1, backgroundColor: theme.backgroundColor }}><SideMenuContentNote options={this.props.noteSideMenuOptions}/></SafeAreaView>;
|
||||
menuPosition = 'right';
|
||||
} else {
|
||||
sideMenuContent = <SafeAreaView style={{ flex: 1, backgroundColor: theme.backgroundColor }}><SideMenuContent/></SafeAreaView>;
|
||||
}
|
||||
|
||||
const appNavInit = {
|
||||
Notes: { screen: NotesScreen },
|
||||
Note: { screen: NoteScreen },
|
||||
Tags: { screen: TagsScreen },
|
||||
Folder: { screen: FolderScreen },
|
||||
OneDriveLogin: { screen: OneDriveLoginScreen },
|
||||
DropboxLogin: { screen: DropboxLoginScreen },
|
||||
EncryptionConfig: { screen: EncryptionConfigScreen },
|
||||
UpgradeSyncTarget: { screen: UpgradeSyncTargetScreen },
|
||||
Log: { screen: LogScreen },
|
||||
Status: { screen: StatusScreen },
|
||||
Search: { screen: SearchScreen },
|
||||
Config: { screen: ConfigScreen },
|
||||
};
|
||||
|
||||
const statusBarStyle = theme.appearance === 'light' ? 'dark-content' : 'light-content';
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, backgroundColor: theme.backgroundColor }}>
|
||||
<SideMenu
|
||||
menu={sideMenuContent}
|
||||
edgeHitWidth={5}
|
||||
menuPosition={menuPosition}
|
||||
onChange={(isOpen:boolean) => this.sideMenu_change(isOpen)}
|
||||
onSliding={(percent:number) => {
|
||||
this.props.dispatch({
|
||||
type: 'SIDE_MENU_OPEN_PERCENT',
|
||||
value: percent,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<StatusBar barStyle={statusBarStyle} />
|
||||
<MenuContext style={{ flex: 1, backgroundColor: theme.backgroundColor }}>
|
||||
<SafeAreaView style={{ flex: 1 }}>
|
||||
<View style={{ flex: 1, backgroundColor: theme.backgroundColor }}>
|
||||
<AppNav screens={appNavInit} />
|
||||
</View>
|
||||
<DropdownAlert ref={(ref:any) => this.dropdownAlert_ = ref} tapToCloseEnabled={true} />
|
||||
<Animated.View pointerEvents='none' style={{ position: 'absolute', backgroundColor: 'black', opacity: this.state.sideMenuContentOpacity, width: '100%', height: '120%' }}/>
|
||||
</SafeAreaView>
|
||||
</MenuContext>
|
||||
</SideMenu>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state:AppState) => {
|
||||
return {
|
||||
historyCanGoBack: state.historyCanGoBack,
|
||||
showSideMenu: state.showSideMenu,
|
||||
syncStarted: state.syncStarted,
|
||||
appState: state.appState,
|
||||
noteSelectionEnabled: state.noteSelectionEnabled,
|
||||
selectedFolderId: state.selectedFolderId,
|
||||
routeName: state.route.routeName,
|
||||
themeId: state.settings.theme,
|
||||
noteSideMenuOptions: state.noteSideMenuOptions,
|
||||
};
|
||||
};
|
||||
|
||||
const App = connect(mapStateToProps)(AppComponent);
|
||||
|
||||
export default class Root extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<App/>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
// So there's basically still a one way flux: React => SQLite => Redux => React
|
||||
|
||||
import { LogBox, AppRegistry } from 'react-native';
|
||||
const Root = require('./Root').default;
|
||||
const { Root } = require('./root.js');
|
||||
|
||||
// Seems JavaScript developers love adding warnings everywhere, even when these warnings can't be fixed
|
||||
// or don't really matter. Because we want important warnings to actually be fixed, we disable
|
||||
|
||||
@@ -18,7 +18,7 @@ const Note = require('lib/models/Note.js');
|
||||
const Tag = require('lib/models/Tag.js');
|
||||
const { splitCommandString } = require('lib/string-utils.js');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const BaseSyncTarget = require('lib/BaseSyncTarget.js');
|
||||
const reduxSharedMiddleware = require('lib/components/shared/reduxSharedMiddleware');
|
||||
const os = require('os');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const { Database } = require('lib/database.js');
|
||||
const uuid = require('lib/uuid').default;
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const Mutex = require('async-mutex').Mutex;
|
||||
|
||||
class BaseModel {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const Logger = require('lib/Logger').default;
|
||||
const shim = require('lib/shim').default;
|
||||
const JoplinError = require('lib/JoplinError');
|
||||
const { time } = require('lib/time-utils');
|
||||
const time = require('lib/time').default;
|
||||
const EventDispatcher = require('lib/EventDispatcher');
|
||||
|
||||
class DropboxApi {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const moment = require('moment');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { FsDriverDummy } = require('lib/fs-driver-dummy.js');
|
||||
|
||||
export enum TargetType {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
// whenever the update() function is called, and in mobile it's called for
|
||||
// example on the Redux action middleware or when the app gets focus.
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
|
||||
type IntervalId = number;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ const ResourceLocalState = require('lib/models/ResourceLocalState.js');
|
||||
const MasterKey = require('lib/models/MasterKey.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const { sprintf } = require('sprintf-js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const JoplinError = require('lib/JoplinError');
|
||||
const TaskQueue = require('lib/TaskQueue');
|
||||
const { Dirnames } = require('lib/services/synchronizer/utils/types');
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const Setting = require('lib/models/Setting').default;
|
||||
const Logger = require('lib/Logger').default;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const shim = require('lib/shim').default;
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const Mustache = require('mustache');
|
||||
|
||||
const TemplateUtils = {};
|
||||
|
||||
@@ -5,7 +5,7 @@ const { View, Button, Text } = require('react-native');
|
||||
|
||||
const PopupDialog = require('react-native-popup-dialog').default;
|
||||
const { DialogTitle, DialogButton } = require('react-native-popup-dialog');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const DateTimePickerModal = require('react-native-modal-datetime-picker').default;
|
||||
|
||||
export default class SelectDateTimeDialog extends React.PureComponent<any, any> {
|
||||
|
||||
@@ -4,7 +4,7 @@ const { connect } = require('react-redux');
|
||||
const { Text, TouchableOpacity, View, StyleSheet } = require('react-native');
|
||||
const { Checkbox } = require('lib/components/checkbox.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { themeStyle } = require('lib/components/global-style.js');
|
||||
|
||||
class NoteItemComponent extends Component {
|
||||
|
||||
@@ -4,7 +4,7 @@ const { connect } = require('react-redux');
|
||||
const { FlatList, Text, StyleSheet, Button, View } = require('react-native');
|
||||
const { _ } = require('lib/locale');
|
||||
const { NoteItem } = require('lib/components/note-item.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { themeStyle } = require('lib/components/global-style.js');
|
||||
|
||||
class NoteListComponent extends Component {
|
||||
|
||||
@@ -26,7 +26,7 @@ const { fileExtension, safeFileExtension } = require('lib/path-utils');
|
||||
const mimeUtils = require('lib/mime-utils.js').mime;
|
||||
const { ScreenHeader } = require('lib/components/screen-header.js');
|
||||
const NoteTagsDialog = require('lib/components/screens/NoteTagsDialog');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { Checkbox } = require('lib/components/checkbox.js');
|
||||
const { _ } = require('lib/locale');
|
||||
const { reg } = require('lib/registry.js');
|
||||
@@ -211,7 +211,9 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
};
|
||||
|
||||
this.useBetaEditor = () => {
|
||||
return Setting.value('editor.beta') && Platform.OS !== 'android';
|
||||
// Disable for now
|
||||
return false;
|
||||
// return Setting.value('editor.beta') && Platform.OS !== 'android';
|
||||
};
|
||||
|
||||
this.takePhoto_onPress = this.takePhoto_onPress.bind(this);
|
||||
|
||||
@@ -14,7 +14,7 @@ const { reg } = require('lib/registry.js');
|
||||
const NavService = require('lib/services/NavService.js');
|
||||
const VersionInfo = require('react-native-version-info').default;
|
||||
const { ReportService } = require('lib/services/report.js');
|
||||
const { time } = require('lib/time-utils');
|
||||
const time = require('lib/time').default;
|
||||
const shim = require('lib/shim').default;
|
||||
const SearchEngine = require('lib/services/searchengine/SearchEngine');
|
||||
const RNFS = require('react-native-fs');
|
||||
|
||||
@@ -7,7 +7,7 @@ const { ScreenHeader } = require('lib/components/screen-header.js');
|
||||
const { _ } = require('lib/locale');
|
||||
const { BaseScreenComponent } = require('lib/components/base-screen.js');
|
||||
const { themeStyle } = require('lib/components/global-style.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const shared = require('lib/components/shared/encryption-config-shared.js');
|
||||
const { dialogs } = require('lib/dialogs.js');
|
||||
const DialogBox = require('react-native-dialogbox').default;
|
||||
|
||||
@@ -4,7 +4,7 @@ const { FlatList, View, Text, Button, StyleSheet, Platform } = require('react-na
|
||||
const { connect } = require('react-redux');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const { ScreenHeader } = require('lib/components/screen-header.js');
|
||||
const { time } = require('lib/time-utils');
|
||||
const time = require('lib/time').default;
|
||||
const { themeStyle } = require('lib/components/global-style.js');
|
||||
const Logger = require('lib/Logger').default;
|
||||
const { BaseScreenComponent } = require('lib/components/base-screen.js');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const Logger = require('lib/Logger').default;
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const Mutex = require('async-mutex').Mutex;
|
||||
const shim = require('lib/shim').default;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const shim = require('lib/shim').default;
|
||||
const JoplinError = require('lib/JoplinError');
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const fs = require('fs-extra');
|
||||
const { basicDelta } = require('lib/file-api');
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ const shim = require('lib/shim').default;
|
||||
const BaseItem = require('lib/models/BaseItem.js');
|
||||
const JoplinError = require('lib/JoplinError');
|
||||
const ArrayUtils = require('lib/ArrayUtils');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { sprintf } = require('sprintf-js');
|
||||
const Mutex = require('async-mutex').Mutex;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const { filename, fileExtension } = require('lib/path-utils');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const Setting = require('lib/models/Setting').default;
|
||||
const md5 = require('md5');
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { resolve as nodeResolve } from 'path';
|
||||
|
||||
const fs = require('fs-extra');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const FsDriverBase = require('lib/fs-driver-base');
|
||||
|
||||
export default class FsDriverNode extends FsDriverBase {
|
||||
|
||||
@@ -8,7 +8,7 @@ const Setting = require('lib/models/Setting').default;
|
||||
const { MarkupToHtml } = require('lib/joplin-renderer');
|
||||
const { enexXmlToMd } = require('./import-enex-md-gen.js');
|
||||
const { enexXmlToHtml } = require('./import-enex-html-gen.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const Levenshtein = require('levenshtein');
|
||||
const md5 = require('md5');
|
||||
const { Base64Decode } = require('base64-stream');
|
||||
|
||||
@@ -4,6 +4,10 @@ interface StringToStringMap {
|
||||
[key:string]: string,
|
||||
}
|
||||
|
||||
interface CodeToCountryMap {
|
||||
[key:string]: string[],
|
||||
}
|
||||
|
||||
const codeToLanguageE_:StringToStringMap = {};
|
||||
codeToLanguageE_['aa'] = 'Afar';
|
||||
codeToLanguageE_['ab'] = 'Abkhazian';
|
||||
@@ -176,12 +180,257 @@ codeToLanguage_['et'] = 'Eesti Keel';
|
||||
codeToLanguage_['vi'] = 'Tiếng Việt';
|
||||
codeToLanguage_['hu'] = 'Magyar';
|
||||
|
||||
const codeToCountry_:StringToStringMap = {};
|
||||
codeToCountry_['BR'] = 'Brasil';
|
||||
codeToCountry_['CR'] = 'Costa Rica';
|
||||
codeToCountry_['CN'] = '中国';
|
||||
codeToCountry_['GB'] = 'UK';
|
||||
codeToCountry_['US'] = 'US';
|
||||
const codeToCountry_:CodeToCountryMap = {
|
||||
AD: ['Andorra', 'Andorra'],
|
||||
AE: ['United Arab Emirates', 'دولة الإمارات العربيّة المتّحدة'],
|
||||
AF: ['Afghanistan', 'د افغانستان اسلامي دولتدولت اسلامی افغانستان, جمهوری اسلامی افغانستان'],
|
||||
AG: ['Antigua and Barbuda', 'Antigua and Barbuda'],
|
||||
AI: ['Anguilla', 'Anguilla'],
|
||||
AL: ['Albania', 'Shqipëria'],
|
||||
AM: ['Armenia', 'Հայաստան'],
|
||||
AO: ['Angola', 'Angola'],
|
||||
AQ: ['Antarctica', 'Antarctica, Antártico, Antarctique, Антарктике'],
|
||||
AR: ['Argentina', 'Argentina'],
|
||||
AS: ['American Samoa', 'American Samoa'],
|
||||
AT: ['Austria', 'Österreich'],
|
||||
AU: ['Australia', 'Australia'],
|
||||
AW: ['Aruba', 'Aruba'],
|
||||
AX: ['Aland Islands', 'Åland'],
|
||||
AZ: ['Azerbaijan', 'Azərbaycan'],
|
||||
BA: ['Bosnia and Herzegovina', 'Bosna i Hercegovina'],
|
||||
BB: ['Barbados', 'Barbados'],
|
||||
BD: ['Bangladesh', 'গণপ্রজাতন্ত্রী বাংলাদেশ'],
|
||||
BE: ['Belgium', 'België, Belgique, Belgien'],
|
||||
BF: ['Burkina Faso', 'Burkina Faso'],
|
||||
BG: ['Bulgaria', 'България'],
|
||||
BH: ['Bahrain', 'البحرين'],
|
||||
BI: ['Burundi', 'Burundi'],
|
||||
BJ: ['Benin', 'Bénin'],
|
||||
BL: ['Saint-Barthélemy', 'Saint-Barthélemy'],
|
||||
BM: ['Bermuda', 'Bermuda'],
|
||||
BN: ['Brunei Darussalam', 'Brunei Darussalam'],
|
||||
BO: ['Bolivia', 'Bolivia, Bulibiya, Volívia, Wuliwya'],
|
||||
BQ: ['Caribbean Netherlands', 'Caribisch Nederland'],
|
||||
BR: ['Brazil', 'Brasil'],
|
||||
BS: ['Bahamas', 'Bahamas'],
|
||||
BT: ['Bhutan', 'འབྲུག་ཡུལ'],
|
||||
BV: ['Bouvet Island', 'Bouvetøya'],
|
||||
BW: ['Botswana', 'Botswana'],
|
||||
BY: ['Belarus', 'Беларусь'],
|
||||
BZ: ['Belize', 'Belize'],
|
||||
CA: ['Canada', 'Canada'],
|
||||
CC: ['Cocos (Keeling) Islands', 'Cocos (Keeling) Islands'],
|
||||
CD: ['Democratic Republic of the Congo (Congo-Kinshasa, former Zaire)', 'République Démocratique du Congo'],
|
||||
CF: ['Centrafrican Republic', 'République centrafricaine, Ködörösêse tî Bêafrîka'],
|
||||
CG: ['Republic of the Congo (Congo-Brazzaville)', 'République du Congo'],
|
||||
CH: ['Switzerland', 'Schweiz, Suisse, Svizzera, Svizra'],
|
||||
CI: ['Côte d\'Ivoire', 'Côte d\'Ivoire'],
|
||||
CK: ['Cook Islands', 'Cook Islands, Kūki ʻĀirani'],
|
||||
CL: ['Chile', 'Chile'],
|
||||
CM: ['Cameroon', 'Cameroun, Cameroon'],
|
||||
CN: ['China', '中国'],
|
||||
CO: ['Colombia', 'Colombia'],
|
||||
CR: ['Costa Rica', 'Costa Rica'],
|
||||
CU: ['Cuba', 'Cuba'],
|
||||
CV: ['Cabo Verde', 'Cabo Verde'],
|
||||
CW: ['Curaçao', 'Curaçao'],
|
||||
CX: ['Christmas Island', 'Christmas Island'],
|
||||
CY: ['Cyprus', 'Κύπρος, Kibris'],
|
||||
CZ: ['Czech Republic', 'Česká republika'],
|
||||
DE: ['Germany', 'Deutschland'],
|
||||
DJ: ['Djibouti', 'Djibouti, جيبوتي, Jabuuti, Gabuutih'],
|
||||
DK: ['Denmark', 'Danmark'],
|
||||
DM: ['Dominica', 'Dominica'],
|
||||
DO: ['Dominican Republic', 'República Dominicana'],
|
||||
DZ: ['Algeria', 'الجزائر'],
|
||||
EC: ['Ecuador', 'Ecuador'],
|
||||
EE: ['Estonia', 'Eesti'],
|
||||
EG: ['Egypt', 'مصر'],
|
||||
EH: ['Western Sahara', 'Sahara Occidental'],
|
||||
ER: ['Eritrea', 'ኤርትራ, إرتريا, Eritrea'],
|
||||
ES: ['Spain', 'España'],
|
||||
ET: ['Ethiopia', 'ኢትዮጵያ, Itoophiyaa'],
|
||||
FI: ['Finland', 'Suomi'],
|
||||
FJ: ['Fiji', 'Fiji'],
|
||||
FK: ['Falkland Islands', 'Falkland Islands'],
|
||||
FM: ['Micronesia (Federated States of)', 'Micronesia'],
|
||||
FO: ['Faroe Islands', 'Føroyar, Færøerne'],
|
||||
FR: ['France', 'France'],
|
||||
GA: ['Gabon', 'Gabon'],
|
||||
GB: ['United Kingdom', 'United Kingdom'],
|
||||
GD: ['Grenada', 'Grenada'],
|
||||
GE: ['Georgia', 'საქართველო'],
|
||||
GF: ['French Guiana', 'Guyane française'],
|
||||
GG: ['Guernsey', 'Guernsey'],
|
||||
GH: ['Ghana', 'Ghana'],
|
||||
GI: ['Gibraltar', 'Gibraltar'],
|
||||
GL: ['Greenland', 'Kalaallit Nunaat, Grønland'],
|
||||
GM: ['The Gambia', 'The Gambia'],
|
||||
GN: ['Guinea', 'Guinée'],
|
||||
GP: ['Guadeloupe', 'Guadeloupe'],
|
||||
GQ: ['Equatorial Guinea', 'Guiena ecuatorial, Guinée équatoriale, Guiné Equatorial'],
|
||||
GR: ['Greece', 'Ελλάδα'],
|
||||
GS: ['South Georgia and the South Sandwich Islands', 'South Georgia and the South Sandwich Islands'],
|
||||
GT: ['Guatemala', 'Guatemala'],
|
||||
GU: ['Guam', 'Guam, Guåhån'],
|
||||
GW: ['Guinea Bissau', 'Guiné-Bissau'],
|
||||
GY: ['Guyana', 'Guyana'],
|
||||
HK: ['Hong Kong (SAR of China)', '香港, Hong Kong'],
|
||||
HM: ['Heard Island and McDonald Islands', 'Heard Island and McDonald Islands'],
|
||||
HN: ['Honduras', 'Honduras'],
|
||||
HR: ['Croatia', 'Hrvatska'],
|
||||
HT: ['Haiti', 'Haïti, Ayiti'],
|
||||
HU: ['Hungary', 'Magyarország'],
|
||||
ID: ['Indonesia', 'Indonesia'],
|
||||
IE: ['Ireland', 'Ireland, Éire'],
|
||||
IL: ['Israel', 'ישראל'],
|
||||
IM: ['Isle of Man', 'Isle of Man'],
|
||||
IN: ['India', 'भारत, India'],
|
||||
IO: ['British Indian Ocean Territory', 'British Indian Ocean Territory'],
|
||||
IQ: ['Iraq', 'العراق, Iraq'],
|
||||
IR: ['Iran', 'ایران'],
|
||||
IS: ['Iceland', 'Ísland'],
|
||||
IT: ['Italy', 'Italia'],
|
||||
JE: ['Jersey', 'Jersey'],
|
||||
JM: ['Jamaica', 'Jamaica'],
|
||||
JO: ['Jordan', 'الأُرْدُن'],
|
||||
JP: ['Japan', '日本'],
|
||||
KE: ['Kenya', 'Kenya'],
|
||||
KG: ['Kyrgyzstan', 'Кыргызстан, Киргизия'],
|
||||
KH: ['Cambodia', 'កម្ពុជា'],
|
||||
KI: ['Kiribati', 'Kiribati'],
|
||||
KM: ['Comores', 'ﺍﻟﻘﻤﺮي, Comores, Komori'],
|
||||
KN: ['Saint Kitts and Nevis', 'Saint Kitts and Nevis'],
|
||||
KP: ['North Korea', '북조선'],
|
||||
KR: ['South Korea', '대한민국'],
|
||||
KW: ['Kuwait', 'الكويت'],
|
||||
KY: ['Cayman Islands', 'Cayman Islands'],
|
||||
KZ: ['Kazakhstan', 'Қазақстан, Казахстан'],
|
||||
LA: ['Laos', 'ປະຊາຊົນລາວ'],
|
||||
LB: ['Lebanon', 'لبنان, Liban'],
|
||||
LC: ['Saint Lucia', 'Saint Lucia'],
|
||||
LI: ['Liechtenstein', 'Liechtenstein'],
|
||||
LK: ['Sri Lanka', 'ශ්රී ලංකා, இலங்கை'],
|
||||
LR: ['Liberia', 'Liberia'],
|
||||
LS: ['Lesotho', 'Lesotho'],
|
||||
LT: ['Lithuania', 'Lietuva'],
|
||||
LU: ['Luxembourg', 'Lëtzebuerg, Luxembourg, Luxemburg'],
|
||||
LV: ['Latvia', 'Latvija'],
|
||||
LY: ['Libya', 'ليبيا'],
|
||||
MA: ['Morocco', 'Maroc, ⵍⵎⵖⵔⵉⴱ, المغرب'],
|
||||
MC: ['Monaco', 'Monaco'],
|
||||
MD: ['Moldova', 'Moldova, Молдавия'],
|
||||
ME: ['Montenegro', 'Crna Gora, Црна Гора'],
|
||||
MF: ['Saint Martin (French part)', 'Saint-Martin'],
|
||||
MG: ['Madagascar', 'Madagasikara, Madagascar'],
|
||||
MH: ['Marshall Islands', 'Marshall Islands'],
|
||||
MK: ['North Macedonia', 'Северна Македонија'],
|
||||
ML: ['Mali', 'Mali'],
|
||||
MM: ['Myanmar', 'မြန်မာ'],
|
||||
MN: ['Mongolia', 'Монгол Улс'],
|
||||
MO: ['Macao (SAR of China)', '澳門, Macau'],
|
||||
MP: ['Northern Mariana Islands', 'Northern Mariana Islands'],
|
||||
MQ: ['Martinique', 'Martinique'],
|
||||
MR: ['Mauritania', 'موريتانيا, Mauritanie'],
|
||||
MS: ['Montserrat', 'Montserrat'],
|
||||
MT: ['Malta', 'Malta'],
|
||||
MU: ['Mauritius', 'Maurice, Mauritius'],
|
||||
MV: ['Maldives', ''],
|
||||
MW: ['Malawi', 'Malawi'],
|
||||
MX: ['Mexico', 'México'],
|
||||
MY: ['Malaysia', ''],
|
||||
MZ: ['Mozambique', 'Mozambique'],
|
||||
NA: ['Namibia', 'Namibia'],
|
||||
NC: ['New Caledonia', 'Nouvelle-Calédonie'],
|
||||
NE: ['Niger', 'Niger'],
|
||||
NF: ['Norfolk Island', 'Norfolk Island'],
|
||||
NG: ['Nigeria', 'Nigeria'],
|
||||
NI: ['Nicaragua', 'Nicaragua'],
|
||||
NL: ['The Netherlands', 'Nederland'],
|
||||
NO: ['Norway', 'Norge, Noreg'],
|
||||
NP: ['Nepal', ''],
|
||||
NR: ['Nauru', 'Nauru'],
|
||||
NU: ['Niue', 'Niue'],
|
||||
NZ: ['New Zealand', 'New Zealand'],
|
||||
OM: ['Oman', 'سلطنة عُمان'],
|
||||
PA: ['Panama', 'Panama'],
|
||||
PE: ['Peru', 'Perú'],
|
||||
PF: ['French Polynesia', 'Polynésie française'],
|
||||
PG: ['Papua New Guinea', 'Papua New Guinea'],
|
||||
PH: ['Philippines', 'Philippines'],
|
||||
PK: ['Pakistan', 'پاکستان'],
|
||||
PL: ['Poland', 'Polska'],
|
||||
PM: ['Saint Pierre and Miquelon', 'Saint-Pierre-et-Miquelon'],
|
||||
PN: ['Pitcairn', 'Pitcairn'],
|
||||
PR: ['Puerto Rico', 'Puerto Rico'],
|
||||
PS: ['Palestinian Territory', 'Palestinian Territory'],
|
||||
PT: ['Portugal', 'Portugal'],
|
||||
PW: ['Palau', 'Palau'],
|
||||
PY: ['Paraguay', 'Paraguay'],
|
||||
QA: ['Qatar', 'قطر'],
|
||||
RE: ['Reunion', 'La Réunion'],
|
||||
RO: ['Romania', 'România'],
|
||||
RS: ['Serbia', 'Србија'],
|
||||
RU: ['Russia', 'Россия'],
|
||||
RW: ['Rwanda', 'Rwanda'],
|
||||
SA: ['Saudi Arabia', 'السعودية'],
|
||||
SB: ['Solomon Islands', 'Solomon Islands'],
|
||||
SC: ['Seychelles', 'Seychelles'],
|
||||
SD: ['Sudan', 'السودان'],
|
||||
SE: ['Sweden', 'Sverige'],
|
||||
SG: ['Singapore', 'Singapore'],
|
||||
SH: ['Saint Helena', 'Saint Helena'],
|
||||
SI: ['Slovenia', 'Slovenija'],
|
||||
SJ: ['Svalbard and Jan Mayen', 'Svalbard and Jan Mayen'],
|
||||
SK: ['Slovakia', 'Slovensko'],
|
||||
SL: ['Sierra Leone', 'Sierra Leone'],
|
||||
SM: ['San Marino', 'San Marino'],
|
||||
SN: ['Sénégal', 'Sénégal'],
|
||||
SO: ['Somalia', 'Somalia, الصومال'],
|
||||
SR: ['Suriname', 'Suriname'],
|
||||
ST: ['São Tomé and Príncipe', 'São Tomé e Príncipe'],
|
||||
SS: ['South Sudan', 'South Sudan'],
|
||||
SV: ['El Salvador', 'El Salvador'],
|
||||
SX: ['Saint Martin (Dutch part)', 'Sint Maarten'],
|
||||
SY: ['Syria', 'سوريا, Sūriyya'],
|
||||
SZ: ['eSwatini', 'eSwatini'],
|
||||
TC: ['Turks and Caicos Islands', 'Turks and Caicos Islands'],
|
||||
TD: ['Chad', 'Tchad, تشاد'],
|
||||
TF: ['French Southern and Antarctic Lands', 'Terres australes et antarctiques françaises'],
|
||||
TG: ['Togo', 'Togo'],
|
||||
TH: ['Thailand', 'ประเทศไทย'],
|
||||
TJ: ['Tajikistan', ','],
|
||||
TK: ['Tokelau', 'Tokelau'],
|
||||
TL: ['Timor-Leste', 'Timor-Leste'],
|
||||
TM: ['Turkmenistan', 'Türkmenistan'],
|
||||
TN: ['Tunisia', 'تونس, Tunisie'],
|
||||
TO: ['Tonga', 'Tonga'],
|
||||
TR: ['Turkey', 'Türkiye'],
|
||||
TT: ['Trinidad and Tobago', 'Trinidad and Tobago'],
|
||||
TV: ['Tuvalu', 'Tuvalu'],
|
||||
TW: ['Taiwan', 'Taiwan'],
|
||||
TZ: ['Tanzania', 'Tanzania'],
|
||||
UA: ['Ukraine', 'Україна'],
|
||||
UG: ['Uganda', 'Uganda'],
|
||||
UM: ['United States Minor Outlying Islands', 'United States Minor Outlying Islands'],
|
||||
US: ['United States of America', 'United States of America'],
|
||||
UY: ['Uruguay', 'Uruguay'],
|
||||
UZ: ['Uzbekistan', ''],
|
||||
VA: ['City of the Vatican', 'Città del Vaticano'],
|
||||
VC: ['Saint Vincent and the Grenadines', 'Saint Vincent and the Grenadines'],
|
||||
VE: ['Venezuela', 'Venezuela'],
|
||||
VG: ['British Virgin Islands', 'British Virgin Islands'],
|
||||
VI: ['United States Virgin Islands', 'United States Virgin Islands'],
|
||||
VN: ['Vietnam', 'Việt Nam'],
|
||||
VU: ['Vanuatu', 'Vanuatu'],
|
||||
WF: ['Wallis and Futuna', 'Wallis-et-Futuna'],
|
||||
WS: ['Samoa', 'Samoa'],
|
||||
YE: ['Yemen', 'اليَمَن'],
|
||||
YT: ['Mayotte', 'Mayotte'],
|
||||
ZA: ['South Africa', 'South Africa'],
|
||||
ZM: ['Zambia', 'Zambia'],
|
||||
ZW: ['Zimbabwe', 'Zimbabwe'],
|
||||
};
|
||||
|
||||
let supportedLocales_:any = null;
|
||||
let localeStats_:any = null;
|
||||
@@ -249,7 +498,9 @@ function closestSupportedLocale(canonicalName:string, defaultToEnglish:boolean =
|
||||
}
|
||||
|
||||
function countryName(countryCode:string) {
|
||||
return codeToCountry_[countryCode] ? codeToCountry_[countryCode] : '';
|
||||
const r = codeToCountry_[countryCode] ? codeToCountry_[countryCode] : null;
|
||||
if (!r) return '';
|
||||
return r.length > 1 && !!r[1] ? r[1] : r[0];
|
||||
}
|
||||
|
||||
function languageNameInEnglish(languageCode:string) {
|
||||
@@ -290,7 +541,14 @@ function countryDisplayName(canonicalName:string) {
|
||||
|
||||
if (languageCode == 'zh' && (countryCode == '' || countryCode == 'TW')) extraString = '繁體'; // "Traditional" in "Traditional Chinese"
|
||||
|
||||
if (extraString) output += ` (${extraString})`;
|
||||
if (extraString) {
|
||||
output += ` (${extraString})`;
|
||||
} else if (countryCode) {
|
||||
// If we have a country code but couldn't match it to a country name,
|
||||
// just display the full canonical name (applies for example to es-419
|
||||
// for Latin American Spanish).
|
||||
output += ` (${canonicalName})`;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ const { Database } = require('lib/database.js');
|
||||
const Setting = require('lib/models/Setting').default;
|
||||
const ItemChange = require('lib/models/ItemChange.js');
|
||||
const JoplinError = require('lib/JoplinError.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { sprintf } = require('sprintf-js');
|
||||
const { _ } = require('lib/locale');
|
||||
const moment = require('moment');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const Note = require('lib/models/Note.js');
|
||||
const { Database } = require('lib/database.js');
|
||||
const { _ } = require('lib/locale');
|
||||
|
||||
@@ -6,7 +6,7 @@ const Resource = require('lib/models/Resource.js');
|
||||
const Setting = require('lib/models/Setting').default;
|
||||
const shim = require('lib/shim').default;
|
||||
const { pregQuote } = require('lib/string-utils.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { _ } = require('lib/locale');
|
||||
const ArrayUtils = require('lib/ArrayUtils.js');
|
||||
const lodash = require('lodash');
|
||||
|
||||
@@ -3,7 +3,7 @@ import { _, supportedLocalesToLanguages, defaultLocale } from 'lib/locale';
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const { Database } = require('lib/database.js');
|
||||
const SyncTargetRegistry = require('lib/SyncTargetRegistry.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { sprintf } = require('sprintf-js');
|
||||
const ObjectUtils = require('lib/ObjectUtils');
|
||||
const { toTitleCase } = require('lib/string-utils.js');
|
||||
@@ -508,11 +508,16 @@ class Setting extends BaseModel {
|
||||
'folders.sortOrder.reverse': { value: false, type: SettingItemType.Bool, public: true, label: () => _('Reverse sort order'), appTypes: ['cli'] },
|
||||
trackLocation: { value: true, type: SettingItemType.Bool, section: 'note', public: true, label: () => _('Save geo-location with notes') },
|
||||
|
||||
// 2020-10-29: For now disable the beta editor due to
|
||||
// underlying bugs in the TextInput component which we cannot
|
||||
// fix. Also the editor crashes in Android and in some cases in
|
||||
// iOS.
|
||||
// https://discourse.joplinapp.org/t/anyone-using-the-beta-editor-on-ios/11658/9
|
||||
'editor.beta': {
|
||||
value: false,
|
||||
type: SettingItemType.Bool,
|
||||
section: 'note',
|
||||
public: mobilePlatform === 'ios',
|
||||
public: false, // mobilePlatform === 'ios',
|
||||
appTypes: ['mobile'],
|
||||
label: () => 'Opt-in to the editor beta',
|
||||
description: () => 'This beta adds list continuation, Markdown preview, and Markdown shortcuts. If you find bugs, please report them in the Discourse forum.',
|
||||
@@ -830,6 +835,9 @@ class Setting extends BaseModel {
|
||||
'camera.type': { value: 0, type: SettingItemType.Int, public: false, appTypes: ['mobile'] },
|
||||
'camera.ratio': { value: '4:3', type: SettingItemType.String, public: false, appTypes: ['mobile'] },
|
||||
|
||||
'spellChecker.enabled': { value: true, type: SettingItemType.Bool, public: false },
|
||||
'spellChecker.language': { value: '', type: SettingItemType.String, public: false },
|
||||
|
||||
windowContentZoomFactor: {
|
||||
value: 100,
|
||||
type: SettingItemType.Int,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const shim = require('lib/shim').default;
|
||||
const { stringify } = require('query-string');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const Logger = require('lib/Logger').default;
|
||||
const { _ } = require('lib/locale');
|
||||
const urlUtils = require('lib/urlUtils.js');
|
||||
|
||||
@@ -144,8 +144,17 @@ export default class CommandService extends BaseService {
|
||||
return output;
|
||||
}
|
||||
|
||||
public commandNames() {
|
||||
return Object.keys(this.commands_);
|
||||
public commandNames(publicOnly:boolean = false) {
|
||||
if (publicOnly) {
|
||||
const output = [];
|
||||
for (const name in this.commands_) {
|
||||
if (!this.isPublic(name)) continue;
|
||||
output.push(name);
|
||||
}
|
||||
return output;
|
||||
} else {
|
||||
return Object.keys(this.commands_);
|
||||
}
|
||||
}
|
||||
|
||||
public commandByName(name:string, options:CommandByNameOptions = null):Command {
|
||||
@@ -230,6 +239,10 @@ export default class CommandService extends BaseService {
|
||||
return stateToWhenClauseContext(this.store_.getState());
|
||||
}
|
||||
|
||||
public isPublic(commandName:string) {
|
||||
return !!this.label(commandName);
|
||||
}
|
||||
|
||||
// When looping on commands and checking their enabled state, the whenClauseContext
|
||||
// should be specified (created using currentWhenClauseContext) to avoid having
|
||||
// to re-create it on each call.
|
||||
|
||||
@@ -106,8 +106,9 @@ class DecryptionWorker {
|
||||
if (!('errorHandler' in options)) options.errorHandler = 'log';
|
||||
|
||||
if (this.state_ !== 'idle') {
|
||||
this.logger().debug(`DecryptionWorker: cannot start because state is "${this.state_}"`);
|
||||
return;
|
||||
const msg = `DecryptionWorker: cannot start because state is "${this.state_}"`;
|
||||
this.logger().debug(msg);
|
||||
return { error: new Error(msg) };
|
||||
}
|
||||
|
||||
// Note: the logic below is an optimisation to avoid going through the loop if no master key exists
|
||||
@@ -115,7 +116,8 @@ class DecryptionWorker {
|
||||
// "throw" and "dispatch" logic.
|
||||
const loadedMasterKeyCount = await this.encryptionService().loadedMasterKeysCount();
|
||||
if (!loadedMasterKeyCount) {
|
||||
this.logger().info('DecryptionWorker: cannot start because no master key is currently loaded.');
|
||||
const msg = 'DecryptionWorker: cannot start because no master key is currently loaded.';
|
||||
this.logger().info(msg);
|
||||
const ids = await MasterKey.allIds();
|
||||
|
||||
if (ids.length) {
|
||||
@@ -130,7 +132,7 @@ class DecryptionWorker {
|
||||
});
|
||||
}
|
||||
}
|
||||
return;
|
||||
return { error: new Error(msg) };
|
||||
}
|
||||
|
||||
this.logger().info('DecryptionWorker: starting decryption...');
|
||||
|
||||
@@ -8,7 +8,7 @@ const { fileExtension, basename } = require('lib/path-utils');
|
||||
const spawn = require('child_process').spawn;
|
||||
const chokidar = require('chokidar');
|
||||
const bridge = require('electron').remote.require('./bridge').default;
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const time = require('lib/time').default;
|
||||
const { ErrorNotFound } = require('./rest/errors');
|
||||
|
||||
class ExternalEditWatcher {
|
||||
|
||||
@@ -16,7 +16,6 @@ const defaultKeymapItems = {
|
||||
{ accelerator: 'Cmd+N', command: 'newNote' },
|
||||
{ accelerator: 'Cmd+T', command: 'newTodo' },
|
||||
{ accelerator: 'Cmd+S', command: 'synchronize' },
|
||||
{ accelerator: '', command: 'print' },
|
||||
{ accelerator: 'Cmd+H', command: 'hideApp' },
|
||||
{ accelerator: 'Cmd+Q', command: 'quit' },
|
||||
{ accelerator: 'Cmd+,', command: 'config' },
|
||||
@@ -51,7 +50,6 @@ const defaultKeymapItems = {
|
||||
{ accelerator: 'Ctrl+N', command: 'newNote' },
|
||||
{ accelerator: 'Ctrl+T', command: 'newTodo' },
|
||||
{ accelerator: 'Ctrl+S', command: 'synchronize' },
|
||||
{ accelerator: null, command: 'print' },
|
||||
{ accelerator: 'Ctrl+Q', command: 'quit' },
|
||||
{ accelerator: 'Ctrl+Alt+I', command: 'insertTemplate' },
|
||||
{ accelerator: 'Ctrl+C', command: 'textCopy' },
|
||||
@@ -103,29 +101,42 @@ export default class KeymapService extends BaseService {
|
||||
super();
|
||||
|
||||
this.lastSaveTime_ = Date.now();
|
||||
|
||||
// By default, initialize for the current platform
|
||||
// Manual initialization allows testing for other platforms
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
public get lastSaveTime():number {
|
||||
return this.lastSaveTime_;
|
||||
}
|
||||
|
||||
public initialize(platform: string = shim.platformName()) {
|
||||
// `additionalDefaultCommandNames` will be added to the default keymap
|
||||
// **except** if they are already in it. Basically this is a mechanism
|
||||
// to add all the commands from the command service to the default
|
||||
// keymap.
|
||||
public initialize(additionalDefaultCommandNames:string[] = [], platform: string = shim.platformName()) {
|
||||
this.platform = platform;
|
||||
|
||||
switch (platform) {
|
||||
case 'darwin':
|
||||
this.defaultKeymapItems = defaultKeymapItems.darwin;
|
||||
this.defaultKeymapItems = defaultKeymapItems.darwin.slice();
|
||||
this.modifiersRegExp = modifiersRegExp.darwin;
|
||||
break;
|
||||
default:
|
||||
this.defaultKeymapItems = defaultKeymapItems.default;
|
||||
this.defaultKeymapItems = defaultKeymapItems.default.slice();
|
||||
this.modifiersRegExp = modifiersRegExp.default;
|
||||
}
|
||||
|
||||
for (const name of additionalDefaultCommandNames) {
|
||||
if (this.defaultKeymapItems.find((item:KeymapItem) => item.command === name)) continue;
|
||||
this.defaultKeymapItems.push({
|
||||
command: name,
|
||||
accelerator: null,
|
||||
});
|
||||
}
|
||||
|
||||
this.resetKeymap();
|
||||
}
|
||||
|
||||
// Reset keymap back to its default values
|
||||
public resetKeymap() {
|
||||
this.keymap = {};
|
||||
for (let i = 0; i < this.defaultKeymapItems.length; i++) {
|
||||
// Keep the original defaultKeymapItems array untouched
|
||||
@@ -140,7 +151,9 @@ export default class KeymapService extends BaseService {
|
||||
if (await shim.fsDriver().exists(customKeymapPath)) {
|
||||
this.logger().info(`KeymapService: Loading keymap from file: ${customKeymapPath}`);
|
||||
|
||||
const customKeymapFile = await shim.fsDriver().readFile(customKeymapPath, 'utf-8');
|
||||
const customKeymapFile = (await shim.fsDriver().readFile(customKeymapPath, 'utf-8')).trim();
|
||||
if (!customKeymapFile) return;
|
||||
|
||||
// Custom keymaps are supposed to contain an array of keymap items
|
||||
this.overrideKeymap(JSON.parse(customKeymapFile));
|
||||
}
|
||||
@@ -183,8 +196,8 @@ export default class KeymapService extends BaseService {
|
||||
|
||||
if (!commandName) throw new Error('Cannot register an accelerator without a command name');
|
||||
|
||||
const validatedAccelerator = this.convertToPlatform(accelerator);
|
||||
this.validateAccelerator(validatedAccelerator);
|
||||
const validatedAccelerator = accelerator ? this.convertToPlatform(accelerator) : null;
|
||||
if (validatedAccelerator) this.validateAccelerator(validatedAccelerator);
|
||||
|
||||
this.keymap[commandName] = {
|
||||
command: commandName,
|
||||
@@ -264,7 +277,7 @@ export default class KeymapService extends BaseService {
|
||||
// Throws whenever there are duplicate Accelerators used in the keymap
|
||||
this.validateKeymap();
|
||||
} catch (err) {
|
||||
this.initialize(); // Discard all the changes if there are any issues
|
||||
this.resetKeymap(); // Discard all the changes if there are any issues
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -339,7 +352,10 @@ export default class KeymapService extends BaseService {
|
||||
|
||||
public domToElectronAccelerator(event: KeyboardEvent<HTMLDivElement>) {
|
||||
const parts = [];
|
||||
const { key, ctrlKey, metaKey, altKey, shiftKey } = event;
|
||||
|
||||
// We use the "keyCode" and not "key" because the modifier keys
|
||||
// would change the "key" value. eg "Option+U" would give "º" as a key instead of "U"
|
||||
const { keyCode, ctrlKey, metaKey, altKey, shiftKey } = event;
|
||||
|
||||
// First, the modifiers
|
||||
if (ctrlKey) parts.push('Ctrl');
|
||||
@@ -355,7 +371,7 @@ export default class KeymapService extends BaseService {
|
||||
}
|
||||
|
||||
// Finally, the key
|
||||
const electronKey = KeymapService.domToElectronKey(key);
|
||||
const electronKey = KeymapService.domToElectronKey(String.fromCharCode(keyCode));
|
||||
if (electronKey) parts.push(electronKey);
|
||||
|
||||
return parts.join('+');
|
||||
|
||||
@@ -198,7 +198,7 @@ export default class InteropService {
|
||||
if (moduleMetadata.isCustom) {
|
||||
output = this.newModuleFromCustomFactory(moduleMetadata);
|
||||
} else {
|
||||
const ModuleClass = shim.require(this.modulePath(moduleMetadata)).default;
|
||||
const ModuleClass = require(this.modulePath(moduleMetadata)).default;
|
||||
output = new ModuleClass();
|
||||
}
|
||||
|
||||
@@ -231,7 +231,7 @@ export default class InteropService {
|
||||
if (moduleMetadata.isCustom) {
|
||||
output = this.newModuleFromCustomFactory(moduleMetadata);
|
||||
} else {
|
||||
const ModuleClass = shim.require(modulePath).default;
|
||||
const ModuleClass = require(modulePath).default;
|
||||
output = new ModuleClass();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* eslint @typescript-eslint/no-unused-vars: 0, no-unused-vars: 0 */
|
||||
|
||||
import shim from 'lib/shim';
|
||||
import { ImportExportResult } from './types';
|
||||
|
||||
const Setting = require('lib/models/Setting').default;
|
||||
@@ -29,7 +28,7 @@ export default class InteropService_Importer_Base {
|
||||
async temporaryDirectory_(createIt:boolean) {
|
||||
const md5 = require('md5');
|
||||
const tempDir = `${Setting.value('tempDir')}/${md5(Math.random() + Date.now())}`;
|
||||
if (createIt) await shim.fsDriver().mkdir(tempDir);
|
||||
if (createIt) await require('fs-extra').mkdirp(tempDir);
|
||||
return tempDir;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user