1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-12-26 23:38:08 +02:00

Compare commits

..

19 Commits

Author SHA1 Message Date
Laurent Cozic
05be96e0a6 Make sure old softbreaks setting default is set too, so that it is used in 27.js migration 2020-10-31 23:41:00 +00:00
Laurent Cozic
351ae3d47f Merge branch 'dev' into commonmark 2020-10-31 23:31:00 +00:00
Laurent Cozic
6d2bad01e6 Merge branch 'release-1.3' into dev 2020-10-31 16:49:50 +00:00
Laurent Cozic
3847831d80 Merge branch 'release-1.3' of github.com:laurent22/joplin into release-1.3 2020-10-31 16:45:17 +00:00
Robin
44462f4d71 All: Translation: Update nl_NL.po (#4012) 2020-10-31 12:38:54 -04:00
Laurent Cozic
4d20589773 Desktop: Remove from keymap editor commands that cannot be associated with a shortcut 2020-10-31 16:29:17 +00:00
Laurent Cozic
52140ec1a9 Merge branch 'release-1.3' into dev 2020-10-31 13:06:06 +00:00
Laurent Cozic
08c2a7ad64 Tools: Improved HTML escape in git-changelog 2020-10-31 13:05:46 +00:00
Laurent Cozic
d19796f14c Electron release v1.3.11 2020-10-31 13:02:14 +00:00
Laurent Cozic
e4f53a48d2 Desktop: Fixed handling of Option key for shortcuts in macOS 2020-10-31 13:01:40 +00:00
Laurent Cozic
154163bd6c Desktop: Fixes #4010: Add history backward and forward commands to keymap and menus 2020-10-31 12:46:55 +00:00
Laurent Cozic
4502414934 Desktop: Make sure all commands appear in keymap editor 2020-10-31 12:25:12 +00:00
Laurent Cozic
86c471afcd Cli: Fixes #4000: Display proper error message when decryption worker cannot be started 2020-10-29 23:37:19 +00:00
Laurent Cozic
db7d467ddc Merge branch 'dev' into commonmark 2020-10-29 17:16:45 +00:00
Laurent Cozic
5f2d90b7e4 Enable softbreaks by default 2020-10-29 17:09:41 +00:00
Laurent Cozic
8cfe4b0f82 Mobile: Disable beta editor for now due to bugs that cannot be fixed
and crashes.

See https://discourse.joplinapp.org/t/11658/9?u=laurent
2020-10-29 16:42:47 +00:00
Laurent Cozic
9491d81f5c Merge branch 'dev' into commonmark 2020-10-26 23:20:51 +00:00
Laurent Cozic
6bdeddaa1d Merge branch 'dev' into commonmark 2020-10-24 00:15:28 +01:00
Laurent Cozic
301adaa7d0 Made handling of links more standard 2020-10-24 00:00:56 +01:00
112 changed files with 615 additions and 1183 deletions

View File

@@ -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,10 +149,8 @@ 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
@@ -208,7 +207,6 @@ 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
@@ -332,8 +330,6 @@ 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
@@ -356,7 +352,6 @@ 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
View File

@@ -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,10 +143,8 @@ 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
@@ -202,7 +201,6 @@ 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
@@ -326,8 +324,6 @@ 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
@@ -350,7 +346,6 @@ 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

View File

@@ -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,10 +92,8 @@ 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
@@ -151,7 +150,6 @@ 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
@@ -275,8 +273,6 @@ 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
@@ -299,7 +295,6 @@ 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

View File

@@ -1,6 +1,6 @@
const yargParser = require('yargs-parser');
const { _ } = require('lib/locale');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const stringPadding = require('string-padding');
const Logger = require('lib/Logger').default;

View File

@@ -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').default;
const { time } = require('lib/time-utils.js');
class Command extends BaseCommand {
usage() {

View File

@@ -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));

View File

@@ -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').default;
const { time } = require('lib/time-utils.js');
const { cliUtils } = require('./cli-utils.js');
class Command extends BaseCommand {

View File

@@ -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').default;
const { time } = require('lib/time-utils.js');
class Command extends BaseCommand {
usage() {

View File

@@ -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').default;
const { time } = require('lib/time-utils.js');
class Command extends BaseCommand {
usage() {

View File

@@ -1,6 +1,6 @@
'use strict';
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const Logger = require('lib/Logger').default;
const Resource = require('lib/models/Resource.js');
const { dirname } = require('lib/path-utils');

View File

@@ -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"

View File

@@ -2,7 +2,7 @@
require('app-module-path').addPath(__dirname);
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const ArrayUtils = require('lib/ArrayUtils.js');

View File

@@ -3,7 +3,7 @@
require('app-module-path').addPath(__dirname);
const os = require('os');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
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');

View File

@@ -3,7 +3,7 @@
require('app-module-path').addPath(__dirname);
const os = require('os');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
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');

View File

@@ -3,7 +3,7 @@
require('app-module-path').addPath(__dirname);
const os = require('os');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
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');

View File

@@ -1,5 +1,5 @@
import InMemoryCache from 'lib/InMemoryCache';
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
describe('InMemoryCache', function() {

View File

@@ -1,8 +1,8 @@
import MdToHtml from 'lib/joplin-renderer/MdToHtml';
const os = require('os');
const { filename } = require('lib/path-utils');
const { asyncTest, setupDatabaseAndSynchronizer, switchClient } = require('test-utils.js');
const shim = require('lib/shim').default;
const MdToHtml = require('lib/joplin-renderer/MdToHtml').default;
const { themeStyle } = require('lib/theme');
function newTestMdToHtml(options:any = null) {
@@ -120,23 +120,54 @@ describe('MdToHtml', function() {
it('should return the rendered body only', asyncTest(async () => {
const mdToHtml = newTestMdToHtml();
// In this case, the HTML contains only the rendered markdown,
// with no wrapper and no style.
// The style is instead in the cssStrings property.
const result = await mdToHtml.render('just **testing**', null, { bodyOnly: true });
expect(result.cssStrings.length).toBeGreaterThan(0);
expect(result.html.trim()).toBe('just <strong>testing</strong>');
// In this case, the HTML contains only the rendered markdown, with
// no wrapper and no style. The style is instead in the cssStrings
// property.
{
const result = await mdToHtml.render('just **testing**', null, { bodyOnly: true });
expect(result.cssStrings.length).toBeGreaterThan(0);
expect(result.html.trim()).toBe('just <strong>testing</strong>');
}
// But it should not remove the wrapping <p> tags if there's more
// than one line
{
const result = await mdToHtml.render('one\n\ntwo', null, { bodyOnly: true });
expect(result.html.trim()).toBe('<p>one</p>\n<p>two</p>');
}
}));
it('should split HTML and CSS', asyncTest(async () => {
const mdToHtml = newTestMdToHtml();
// It is similar to the bodyOnly option, excepts that
// the rendered Markdown is wrapped in a DIV
// It is similar to the bodyOnly option, excepts that the rendered
// Markdown is wrapped in a DIV
const result = await mdToHtml.render('just **testing**', null, { splitted: true });
expect(result.cssStrings.length).toBeGreaterThan(0);
expect(result.html.trim()).toBe('<div id="rendered-md"><p>just <strong>testing</strong></p>\n</div>');
}));
it('should render links correctly', asyncTest(async () => {
const mdToHtml = newTestMdToHtml();
const testCases = [
// None of these should result in a link
['https://example.com', 'https://example.com'],
['file://C:\\AUTOEXEC.BAT', 'file://C:\\AUTOEXEC.BAT'],
['example.com', 'example.com'],
['oo.ps', 'oo.ps'],
['test@example.com', 'test@example.com'],
// Those should be converted to links
['<https://example.com>', '<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>https://example.com</a>'],
['[ok](https://example.com)', '<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>ok</a>'],
];
for (const testCase of testCases) {
const [input, expected] = testCase;
const actual = await mdToHtml.render(input, null, { bodyOnly: true, plainResourceRendering: true });
expect(actual.html).toBe(expected);
}
}));
});

View File

@@ -2,7 +2,7 @@
require('app-module-path').addPath(__dirname);
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
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');

View File

@@ -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').default;
const { time } = require('lib/time-utils.js');
let testApp = null;

View File

@@ -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').default;
const { time } = require('lib/time-utils.js');
const { ALL_NOTES_FILTER_ID } = require('lib/reserved-ids.js');
//

View File

@@ -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').default;
const { time } = require('lib/time-utils.js');
let testApp = null;

View File

@@ -3,7 +3,7 @@
require('app-module-path').addPath(__dirname);
const uuid = require('lib/uuid').default;
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const { asyncTest, sleep, fileApi, fileContentEqual, checkThrowAsync } = require('test-utils.js');
const shim = require('lib/shim').default;
const fs = require('fs-extra');

View File

@@ -2,7 +2,7 @@
require('app-module-path').addPath(__dirname);
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const mimeUtils = require('lib/mime-utils.js').mime;

View File

@@ -2,7 +2,7 @@
require('app-module-path').addPath(__dirname);
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');

View File

@@ -2,7 +2,7 @@
require('app-module-path').addPath(__dirname);
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
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');

View File

@@ -2,7 +2,7 @@
require('app-module-path').addPath(__dirname);
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
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');

View File

@@ -2,7 +2,7 @@
require('app-module-path').addPath(__dirname);
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
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');

View File

@@ -2,7 +2,7 @@
require('app-module-path').addPath(__dirname);
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
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');

View File

@@ -2,7 +2,7 @@
require('app-module-path').addPath(__dirname);
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');

View File

@@ -2,7 +2,7 @@
require('app-module-path').addPath(__dirname);
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');

View File

@@ -2,7 +2,7 @@
require('app-module-path').addPath(__dirname);
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');

View File

@@ -2,7 +2,7 @@
require('app-module-path').addPath(__dirname);
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');

View File

@@ -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' },

View File

@@ -2,7 +2,7 @@
require('app-module-path').addPath(__dirname);
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
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');

View File

@@ -2,7 +2,7 @@
require('app-module-path').addPath(__dirname);
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
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;

View File

@@ -3,7 +3,7 @@
require('app-module-path').addPath(__dirname);
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
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');

View File

@@ -3,7 +3,7 @@
require('app-module-path').addPath(__dirname);
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
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');

View File

@@ -3,7 +3,7 @@
// require('app-module-path').addPath(__dirname);
// const time = require('lib/time').default;
// const { time } = require('lib/time-utils.js');
// 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');

View File

@@ -2,7 +2,7 @@
require('app-module-path').addPath(__dirname);
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
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');

View File

@@ -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').default;
const { time } = require('lib/time-utils.js');
const { shimInit } = require('lib/shim-init-node.js');
const shim = require('lib/shim').default;
const uuid = require('lib/uuid').default;

View File

@@ -2,9 +2,9 @@
require('app-module-path').addPath(__dirname);
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const timeUtils = require('../../ReactNativeClient/lib/time');
const timeUtils = require('../../ReactNativeClient/lib/time-utils');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);

View File

@@ -86,7 +86,6 @@ 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
@@ -98,6 +97,14 @@ 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())) {

View File

@@ -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').default;
const { time } = require('lib/time-utils.js');
const md5 = require('md5');
const url = require('url');

View File

@@ -14,9 +14,7 @@ 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);
@@ -31,6 +29,7 @@ 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');
@@ -453,31 +452,6 @@ 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)) {
@@ -522,14 +496,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());
@@ -560,9 +526,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();
@@ -711,10 +689,6 @@ 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(() => {

View File

@@ -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').default;
const { time } = require('lib/time-utils.js');
const shim = require('lib/shim').default;
const dialogs = require('./dialogs');
const shared = require('lib/components/shared/encryption-config-shared.js');

View File

@@ -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

View File

@@ -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

View File

@@ -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').default;
const { time } = require('lib/time-utils.js');
const styled = require('styled-components').default;
const StyledUserWebviewDialogContainer = styled.div`

View File

@@ -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').default;
const { time } = require('lib/time-utils');
export const declaration:CommandDeclaration = {
name: 'editAlarm',

View File

@@ -14,7 +14,7 @@ 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');
@@ -85,43 +85,9 @@ 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();
@@ -252,10 +218,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();
@@ -302,16 +266,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: () => {
@@ -345,18 +311,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 = [{
@@ -374,8 +344,6 @@ 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') });
}
@@ -456,9 +424,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'],
},
@@ -523,12 +489,6 @@ function useMenu(props:Props) {
});
}
const separator = () => {
return {
type: 'separator',
};
};
const rootMenus:any = {
edit: {
id: 'edit',
@@ -591,11 +551,6 @@ function useMenu(props:Props) {
},
},
separator(),
{
label: _('Focus'),
submenu: focusItems,
},
separator(),
{
label: _('Actual Size'),
click: () => {
@@ -628,6 +583,18 @@ function useMenu(props:Props) {
accelerator: 'CommandOrControl+-',
}],
},
go: {
label: _('&Go'),
submenu: [
menuItemDic.historyBackward,
menuItemDic.historyForward,
separator(),
{
label: _('Focus'),
submenu: focusItems,
},
],
},
note: {
label: _('&Note'),
submenu: [
@@ -660,6 +627,8 @@ function useMenu(props:Props) {
click: () => _checkForUpdates(),
},
separator(),
syncStatusItem,
separator(),
{
id: 'help:toggleDevTools',
label: _('Toggle development tools'),
@@ -714,6 +683,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);
}
}
@@ -746,6 +716,7 @@ function useMenu(props:Props) {
rootMenus.file,
rootMenus.edit,
rootMenus.view,
rootMenus.go,
rootMenus.note,
rootMenus.tools,
rootMenus.help,
@@ -753,46 +724,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([
{
@@ -812,7 +743,7 @@ function useMenu(props:Props) {
} else {
setMenu(Menu.buildFromTemplate(template));
}
}, [props.routeName, props.pluginMenuItems, props.pluginMenus, keymapLastChangeTime, modulesLastChangeTime, props['spellChecker.language'], props['spellChecker.enabled']]);
}, [props.routeName, props.pluginMenuItems, props.pluginMenus, keymapLastChangeTime, modulesLastChangeTime]);
useEffect(() => {
const whenClauseContext = CommandService.instance().currentWhenClauseContext();
@@ -907,8 +838,6 @@ 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'],
};
};

View File

@@ -17,13 +17,12 @@ import { _ } from 'lib/locale';
import bridge from '../../../../services/bridge';
import markdownUtils from 'lib/markdownUtils';
import shim from 'lib/shim';
import SpellCheckerService from 'lib/services/spellChecker/SpellCheckerService';
const Note = require('lib/models/Note.js');
// const { clipboard } = require('electron');
const { clipboard } = require('electron');
const shared = require('lib/components/shared/note-screen-shared.js');
const Menu = bridge().Menu;
// const MenuItem = bridge().MenuItem;
const MenuItem = bridge().MenuItem;
const { reg } = require('lib/registry.js');
const dialogs = require('../../../dialogs');
const { themeStyle } = require('lib/theme');
@@ -223,78 +222,76 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
}
}, []);
// const editorCutText = useCallback(() => {
// if (editorRef.current) {
// const selections = editorRef.current.getSelections();
// if (selections.length > 0) {
// clipboard.writeText(selections[0]);
// // Easy way to wipe out just the first selection
// selections[0] = '';
// editorRef.current.replaceSelections(selections);
// }
// }
// }, []);
const editorCutText = useCallback(() => {
if (editorRef.current) {
const selections = editorRef.current.getSelections();
if (selections.length > 0) {
clipboard.writeText(selections[0]);
// Easy way to wipe out just the first selection
selections[0] = '';
editorRef.current.replaceSelections(selections);
}
}
}, []);
// const editorCopyText = useCallback(() => {
// if (editorRef.current) {
// const selections = editorRef.current.getSelections();
// if (selections.length > 0) {
// clipboard.writeText(selections[0]);
// }
// }
// }, []);
const editorCopyText = useCallback(() => {
if (editorRef.current) {
const selections = editorRef.current.getSelections();
if (selections.length > 0) {
clipboard.writeText(selections[0]);
}
}
}, []);
// const editorPasteText = useCallback(() => {
// if (editorRef.current) {
// editorRef.current.replaceSelection(clipboard.readText());
// }
// }, []);
const editorPasteText = useCallback(() => {
if (editorRef.current) {
editorRef.current.replaceSelection(clipboard.readText());
}
}, []);
const onEditorContextMenu = () => {};
const onEditorContextMenu = useCallback(() => {
const menu = new Menu();
// useCallback(() => {
// const menu = new Menu();
const hasSelectedText = editorRef.current && !!editorRef.current.getSelection() ;
const clipboardText = clipboard.readText();
// const hasSelectedText = editorRef.current && !!editorRef.current.getSelection() ;
// const clipboardText = clipboard.readText();
menu.append(
new MenuItem({
label: _('Cut'),
enabled: hasSelectedText,
click: async () => {
editorCutText();
},
})
);
// menu.append(
// new MenuItem({
// label: _('Cut'),
// enabled: hasSelectedText,
// click: async () => {
// editorCutText();
// },
// })
// );
menu.append(
new MenuItem({
label: _('Copy'),
enabled: hasSelectedText,
click: async () => {
editorCopyText();
},
})
);
// menu.append(
// new MenuItem({
// label: _('Copy'),
// enabled: hasSelectedText,
// click: async () => {
// editorCopyText();
// },
// })
// );
menu.append(
new MenuItem({
label: _('Paste'),
enabled: true,
click: async () => {
if (clipboardText) {
editorPasteText();
} else {
// To handle pasting images
onEditorPaste();
}
},
})
);
// menu.append(
// new MenuItem({
// label: _('Paste'),
// enabled: true,
// click: async () => {
// if (clipboardText) {
// editorPasteText();
// } else {
// // To handle pasting images
// onEditorPaste();
// }
// },
// })
// );
// menu.popup(bridge().window());
// }, [props.content, editorCutText, editorPasteText, editorCopyText, onEditorPaste]);
menu.popup(bridge().window());
}, [props.content, editorCutText, editorPasteText, editorCopyText, onEditorPaste]);
const loadScript = async (script:any) => {
return new Promise((resolve) => {
@@ -607,40 +604,6 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
editorRef.current.refresh();
}, [rootSize, styles.editor, props.visiblePanes]);
// The code below adds support for spell checker when CodeMirror "inputStyle" is "contenteditable"
// however this mode is too buggy to be used in production. Perhaps it can be used if CodeMirror
// contenteditable mode is fixed in a future version.
// https://github.com/laurent22/joplin/pull/3974#issuecomment-718936703
useEffect(() => {
function pointerInsideEditor(x:number, y:number) {
const elements = document.getElementsByClassName('codeMirrorEditor');
if (!elements.length) return null;
console.info(elements[0]);
const rect = elements[0].getBoundingClientRect();
return rect.x < x && rect.y < y && rect.right > x && rect.bottom > y;
}
function onContextMenu(_event:any, params:any) {
if (!pointerInsideEditor(params.x, params.y)) return;
const menu = new Menu();
const spellCheckerMenuItems = SpellCheckerService.instance().contextMenuItems(params.misspelledWord, params.dictionarySuggestions);
for (const item of spellCheckerMenuItems) {
menu.append(item);
}
menu.popup();
}
bridge().window().webContents.on('context-menu', onContextMenu);
return () => {
bridge().window().webContents.off('context-menu', onContextMenu);
};
}, []);
function renderEditor() {
return (

View File

@@ -157,7 +157,7 @@ function Editor(props: EditorProps, ref: any) {
mode: props.mode,
readOnly: props.readOnly,
autoCloseBrackets: props.autoMatchBraces,
inputStyle: 'contenteditable', // contenteditable loses cursor position on focus change, use textarea instead
inputStyle: 'textarea', // contenteditable loses cursor position on focus change, use textarea instead
lineWrapping: true,
lineNumbers: false,
indentWithTabs: true,
@@ -236,7 +236,7 @@ function Editor(props: EditorProps, ref: any) {
}
}, [props.keyMap]);
return <div className="codeMirrorEditor" contentEditable spellCheck style={props.style} ref={editorParent} />;
return <div style={props.style} ref={editorParent} />;
}
export default forwardRef(Editor);

View File

@@ -4,6 +4,7 @@ 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';
@@ -11,13 +12,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');
@@ -142,6 +143,8 @@ 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;
@@ -507,7 +510,19 @@ 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[] = [];
@@ -549,8 +564,7 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
language: ['en_US', 'en_GB'].includes(language) ? undefined : language,
toolbar: toolbar.join(' '),
localization_function: _,
contextmenu: false,
browser_spellcheck: true,
contextmenu: contextMenuItemNames.join(' '),
setup: (editor:any) => {
function openEditDialog(editable:any) {
@@ -666,7 +680,51 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
});
}
setupContextMenu(editor);
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 : '';
},
});
}
// TODO: remove event on unmount?
editor.on('DblClick', (event:any) => {

View File

@@ -1,91 +0,0 @@
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();
});
}

View File

@@ -1,9 +1,11 @@
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';
@@ -25,15 +27,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');
@@ -43,6 +45,10 @@ const commands = [
require('./commands/showRevisions'),
];
const toolbarStyle = {
marginBottom: 0,
};
const toolbarButtonUtils = new ToolbarButtonUtils(CommandService.instance());
function NoteEditor(props: NoteEditorProps) {
@@ -238,20 +244,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();
@@ -345,6 +351,14 @@ 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}
@@ -363,6 +377,26 @@ 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 = {
@@ -512,15 +546,7 @@ function NoteEditor(props: NoteEditorProps) {
<div style={styles.root} onDrop={onDrop}>
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
{renderResourceWatchingNotification()}
<NoteTitleBar
titleInputRef={titleInputRef}
themeId={props.themeId}
isProvisional={props.isProvisional}
noteIsTodo={formNote.is_todo}
noteTitle={formNote.title}
noteUserUpdatedTime={formNote.user_updated_time}
onTitleChange={onTitleChange}
/>
{renderTitleBar()}
{renderSearchInfo()}
<div style={{ display: 'flex', flex: 1 }}>
{editor}

View File

@@ -1,98 +0,0 @@
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>
);
}

View File

@@ -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').default;
const { time } = require('lib/time-utils.js');
const { reg } = require('lib/registry.js');
const commandsWithDependencies = [

View File

@@ -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').default;
const { time } = require('lib/time-utils.js');
const { themeStyle } = require('lib/theme');
const BaseModel = require('lib/BaseModel');
const bridge = require('electron').remote.require('./bridge').default;

View File

@@ -1,7 +1,7 @@
const React = require('react');
const { _ } = require('lib/locale');
const { themeStyle } = require('lib/theme');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const DialogButtonRow = require('./DialogButtonRow.min');
const Datetime = require('react-datetime');
const Note = require('lib/models/Note');

View File

@@ -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').default;
const { time } = require('lib/time-utils.js');
const ReactTooltip = require('react-tooltip');
const { urlDecode, substrWithEllipsis } = require('lib/string-utils');
const bridge = require('electron').remote.require('./bridge').default;

View File

@@ -1,6 +1,6 @@
const React = require('react');
const { connect } = require('react-redux');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const { themeStyle } = require('lib/theme');
class NoteStatusBarComponent extends React.Component {

View File

@@ -1,7 +1,7 @@
const React = require('react');
const { _ } = require('lib/locale');
const { themeStyle } = require('lib/theme');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const Datetime = require('react-datetime');
const CreatableSelect = require('react-select/lib/Creatable').default;
const Select = require('react-select').default;

View 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',
];
}

View File

@@ -1,6 +1,6 @@
{
"name": "Joplin",
"version": "1.3.10",
"version": "1.3.11",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "Joplin",
"version": "1.3.10",
"version": "1.3.11",
"description": "Joplin for Desktop",
"main": "main.js",
"scripts": {

View File

@@ -557,7 +557,7 @@ GotoAnything.manifest = {
menuItems: [
{
name: 'main',
parent: 'tools',
parent: 'go',
label: _('Goto Anything...'),
accelerator: () => KeymapService.instance().getAccelerator('gotoAnything'),
screens: ['Main'],

View File

@@ -1,39 +0,0 @@
// 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);
}
}

View File

@@ -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').default;
const { time } = require('lib/time-utils.js');
const BaseSyncTarget = require('lib/BaseSyncTarget.js');
const reduxSharedMiddleware = require('lib/components/shared/reduxSharedMiddleware');
const os = require('os');

View File

@@ -1,6 +1,6 @@
const { Database } = require('lib/database.js');
const uuid = require('lib/uuid').default;
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const Mutex = require('async-mutex').Mutex;
class BaseModel {

View File

@@ -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').default;
const { time } = require('lib/time-utils');
const EventDispatcher = require('lib/EventDispatcher');
class DropboxApi {

View File

@@ -1,5 +1,5 @@
const moment = require('moment');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const { FsDriverDummy } = require('lib/fs-driver-dummy.js');
export enum TargetType {

View File

@@ -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').default;
const { time } = require('lib/time-utils.js');
type IntervalId = number;

View File

@@ -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').default;
const { time } = require('lib/time-utils.js');
const JoplinError = require('lib/JoplinError');
const TaskQueue = require('lib/TaskQueue');
const { Dirnames } = require('lib/services/synchronizer/utils/types');

View File

@@ -1,4 +1,4 @@
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const Setting = require('lib/models/Setting').default;
const Logger = require('lib/Logger').default;

View File

@@ -1,5 +1,5 @@
const shim = require('lib/shim').default;
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const Mustache = require('mustache');
const TemplateUtils = {};

View File

@@ -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').default;
const { time } = require('lib/time-utils.js');
const DateTimePickerModal = require('react-native-modal-datetime-picker').default;
export default class SelectDateTimeDialog extends React.PureComponent<any, any> {

View File

@@ -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').default;
const { time } = require('lib/time-utils.js');
const { themeStyle } = require('lib/components/global-style.js');
class NoteItemComponent extends Component {

View File

@@ -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').default;
const { time } = require('lib/time-utils.js');
const { themeStyle } = require('lib/components/global-style.js');
class NoteListComponent extends Component {

View File

@@ -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').default;
const { time } = require('lib/time-utils.js');
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);

View File

@@ -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').default;
const { time } = require('lib/time-utils');
const shim = require('lib/shim').default;
const SearchEngine = require('lib/services/searchengine/SearchEngine');
const RNFS = require('react-native-fs');

View File

@@ -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').default;
const { time } = require('lib/time-utils.js');
const shared = require('lib/components/shared/encryption-config-shared.js');
const { dialogs } = require('lib/dialogs.js');
const DialogBox = require('react-native-dialogbox').default;

View File

@@ -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').default;
const { time } = require('lib/time-utils');
const { themeStyle } = require('lib/components/global-style.js');
const Logger = require('lib/Logger').default;
const { BaseScreenComponent } = require('lib/components/base-screen.js');

View File

@@ -1,5 +1,5 @@
const Logger = require('lib/Logger').default;
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const Mutex = require('async-mutex').Mutex;
const shim = require('lib/shim').default;

View File

@@ -1,4 +1,4 @@
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const shim = require('lib/shim').default;
const JoplinError = require('lib/JoplinError');

View File

@@ -1,4 +1,4 @@
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const fs = require('fs-extra');
const { basicDelta } = require('lib/file-api');

View File

@@ -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').default;
const { time } = require('lib/time-utils.js');
const { sprintf } = require('sprintf-js');
const Mutex = require('async-mutex').Mutex;

View File

@@ -1,5 +1,5 @@
const { filename, fileExtension } = require('lib/path-utils');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const Setting = require('lib/models/Setting').default;
const md5 = require('md5');

View File

@@ -1,7 +1,7 @@
import { resolve as nodeResolve } from 'path';
const fs = require('fs-extra');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const FsDriverBase = require('lib/fs-driver-base');
export default class FsDriverNode extends FsDriverBase {

View File

@@ -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').default;
const { time } = require('lib/time-utils.js');
const Levenshtein = require('levenshtein');
const md5 = require('md5');
const { Base64Decode } = require('base64-stream');

View File

@@ -318,9 +318,14 @@ export default class MdToHtml {
return output;
}
// The string we are looking for is: <p></p>\n
private removeMarkdownItWrappingParagraph_(html:string) {
// <p></p>\n
if (html.length < 8) return html;
// If there are multiple <p> tags, we keep them because it's multiple lines
// and removing the first and last tag will result in invalid HTML.
if ((html.match(/<\/p>/g) || []).length > 1) return html;
if (html.substr(0, 3) !== '<p>') return html;
if (html.slice(-5) !== '</p>\n') return html;
return html.substring(3, html.length - 5);
@@ -376,7 +381,7 @@ export default class MdToHtml {
const markdownIt = new MarkdownIt({
breaks: !this.pluginEnabled('softbreaks'),
typographer: this.pluginEnabled('typographer'),
linkify: true,
// linkify: true,
html: true,
highlight: (str:string, lang:string) => {
let outputCodeHtml = '';

View File

@@ -72,11 +72,24 @@ function plugin(markdownIt:any, ruleOptions:RuleOptions) {
if (hrefAttr.indexOf('#') === 0 && href.indexOf('#') === 0) js = ''; // If it's an internal anchor, don't add any JS since the webview is going to handle navigating to the right place
const attrHtml = [];
attrHtml.push('data-from-md');
if (resourceIdAttr) attrHtml.push(resourceIdAttr);
if (title) attrHtml.push(`title='${htmlentities(title)}'`);
if (mime) attrHtml.push(`type='${htmlentities(mime)}'`);
if (ruleOptions.plainResourceRendering || ruleOptions.linkRenderingType === 2) {
return `<a data-from-md ${resourceIdAttr} title='${htmlentities(title)}' href='${htmlentities(href)}' type='${htmlentities(mime)}'>`;
icon = '';
attrHtml.push(`href='${htmlentities(href)}'`);
// return `<a data-from-md ${resourceIdAttr} title='${htmlentities(title)}' href='${htmlentities(href)}' type='${htmlentities(mime)}'>`;
} else {
return `<a data-from-md ${resourceIdAttr} title='${htmlentities(title)}' href='${hrefAttr}' ${js} type='${htmlentities(mime)}'>${icon}`;
attrHtml.push(`href='${hrefAttr}'`);
if (js) attrHtml.push(js);
// return `<a data-from-md ${resourceIdAttr} title='${htmlentities(title)}' href='${hrefAttr}' ${js} type='${htmlentities(mime)}'>${icon}`;
}
return `<a ${attrHtml.join(' ')}>${icon}`;
};
}

View File

@@ -4,10 +4,6 @@ interface StringToStringMap {
[key:string]: string,
}
interface CodeToCountryMap {
[key:string]: string[],
}
const codeToLanguageE_:StringToStringMap = {};
codeToLanguageE_['aa'] = 'Afar';
codeToLanguageE_['ab'] = 'Abkhazian';
@@ -180,257 +176,12 @@ codeToLanguage_['et'] = 'Eesti Keel';
codeToLanguage_['vi'] = 'Tiếng Việt';
codeToLanguage_['hu'] = 'Magyar';
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'],
};
const codeToCountry_:StringToStringMap = {};
codeToCountry_['BR'] = 'Brasil';
codeToCountry_['CR'] = 'Costa Rica';
codeToCountry_['CN'] = '中国';
codeToCountry_['GB'] = 'UK';
codeToCountry_['US'] = 'US';
let supportedLocales_:any = null;
let localeStats_:any = null;
@@ -498,9 +249,7 @@ function closestSupportedLocale(canonicalName:string, defaultToEnglish:boolean =
}
function countryName(countryCode:string) {
const r = codeToCountry_[countryCode] ? codeToCountry_[countryCode] : null;
if (!r) return '';
return r.length > 1 && !!r[1] ? r[1] : r[0];
return codeToCountry_[countryCode] ? codeToCountry_[countryCode] : '';
}
function languageNameInEnglish(languageCode:string) {
@@ -541,14 +290,7 @@ function countryDisplayName(canonicalName:string) {
if (languageCode == 'zh' && (countryCode == '' || countryCode == 'TW')) extraString = '繁體'; // "Traditional" in "Traditional Chinese"
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})`;
}
if (extraString) output += ` (${extraString})`;
return output;
}

View File

@@ -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').default;
const { time } = require('lib/time-utils.js');
const { sprintf } = require('sprintf-js');
const { _ } = require('lib/locale');
const moment = require('moment');

View File

@@ -1,5 +1,5 @@
const BaseModel = require('lib/BaseModel.js');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const Note = require('lib/models/Note.js');
const { Database } = require('lib/database.js');
const { _ } = require('lib/locale');

View File

@@ -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').default;
const { time } = require('lib/time-utils.js');
const { _ } = require('lib/locale');
const ArrayUtils = require('lib/ArrayUtils.js');
const lodash = require('lodash');

View File

@@ -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').default;
const { time } = require('lib/time-utils.js');
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.',
@@ -560,11 +565,11 @@ class Setting extends BaseModel {
},
// Deprecated - use markdown.plugin.*
'markdown.softbreaks': { value: false, type: SettingItemType.Bool, public: false, appTypes: ['mobile', 'desktop'] },
'markdown.softbreaks': { value: true, type: SettingItemType.Bool, public: false, appTypes: ['mobile', 'desktop'] },
'markdown.typographer': { value: false, type: SettingItemType.Bool, public: false, appTypes: ['mobile', 'desktop'] },
// Deprecated
'markdown.plugin.softbreaks': { value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable soft breaks')}${wysiwygYes}` },
'markdown.plugin.softbreaks': { value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable soft breaks')}${wysiwygYes}` },
'markdown.plugin.typographer': { value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable typographer support')}${wysiwygYes}` },
'markdown.plugin.katex': { value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable math expressions')}${wysiwygYes}` },
'markdown.plugin.fountain': { value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable Fountain syntax support')}${wysiwygYes}` },
@@ -830,9 +835,6 @@ 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,

View File

@@ -1,6 +1,6 @@
const shim = require('lib/shim').default;
const { stringify } = require('query-string');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const Logger = require('lib/Logger').default;
const { _ } = require('lib/locale');
const urlUtils = require('lib/urlUtils.js');

View File

@@ -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.

View File

@@ -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...');

View File

@@ -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').default;
const { time } = require('lib/time-utils.js');
const { ErrorNotFound } = require('./rest/errors');
class ExternalEditWatcher {

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