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

Compare commits

..

1 Commits

26 changed files with 226 additions and 317 deletions

View File

@@ -126,7 +126,6 @@ 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

1
.gitignore vendored
View File

@@ -120,7 +120,6 @@ 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

View File

@@ -69,7 +69,6 @@ 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

View File

@@ -45,12 +45,10 @@ 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

@@ -13,10 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.4.1\n"
"X-Generator: Poedit 2.3.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 ""
@@ -317,9 +315,8 @@ 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 "Sync target updaten naar de nieuwste versie."
msgstr ""
#: CliClient/app/command-sync.js:81 CliClient/app/command-sync.js:95
#: ElectronClient/gui/OneDriveLoginScreen.min.js:40
@@ -821,8 +818,6 @@ 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 ""
@@ -840,14 +835,15 @@ 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. Typ # gevolgd door de naam van een label, @ gevolgd door de naam "
"van een notitieboek, of : om te zoeken op commando's."
"springen. Of typ # gevolgd door de naam van een label, of @ gevolgd door de "
"naam van een notitieboek."
#: ElectronClient/plugins/GotoAnything.js:463
#: ElectronClient/gui/KeymapConfig/utils/getLabel.js:28
@@ -888,8 +884,9 @@ msgid "New version: %s"
msgstr "Nieuwe versie: %s"
#: ElectronClient/checkForUpdates.js:154
#, fuzzy
msgid "Download"
msgstr "Downloaden"
msgstr "Gedownload"
#: ElectronClient/checkForUpdates.js:154
msgid "Full Release Notes"
@@ -1238,15 +1235,13 @@ 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 "De volgende bijlagen worden gecontroleerd op wijzigingen:"
msgstr ""
#: 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
@@ -1363,9 +1358,9 @@ msgstr "Eigenschappen van notitie"
#: ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js:63
#: ReactNativeClient/lib/services/KeymapService.js:144
#, javascript-format
#, fuzzy, javascript-format
msgid "Error: %s"
msgstr "Fout: %s"
msgstr "Fout"
#: ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js:121
#: ElectronClient/gui/Root.min.js:91 ElectronClient/gui/MenuBar.js:391
@@ -1375,11 +1370,12 @@ msgstr "Importeren"
#: ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js:126
msgid "Command"
msgstr "Commando"
msgstr ""
#: ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js:127
#, fuzzy
msgid "Keyboard Shortcut"
msgstr "Sneltoets"
msgstr "Toetsenbordmodus"
#: ElectronClient/gui/KeymapConfig/utils/getLabel.js:14
#: ElectronClient/gui/MenuBar.js:178 ElectronClient/app.js:347
@@ -1402,8 +1398,9 @@ msgid "Website and documentation"
msgstr "Website en documentatie"
#: ElectronClient/gui/KeymapConfig/utils/getLabel.js:24
#, fuzzy
msgid "Hide Joplin"
msgstr "Joplin verbergen"
msgstr "Over Joplin"
#: ElectronClient/gui/KeymapConfig/utils/getLabel.js:26
#: ElectronClient/gui/MenuBar.js:428
@@ -1411,8 +1408,9 @@ 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
@@ -1422,15 +1420,13 @@ msgstr "Opties"
#: ElectronClient/gui/KeymapConfig/ShortcutRecorder.js:49
msgid "Press the shortcut"
msgstr "Druk op de sneltoetsen"
msgstr ""
#: 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
@@ -1440,20 +1436,17 @@ 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 "Opnieuw opstarten en bijwerken"
msgstr "Hoofdsleutels die moeten worden bijgewerkt"
#: ElectronClient/gui/MainScreen/MainScreen.js:424
#: ElectronClient/gui/MainScreen/MainScreen.min.js:313
@@ -2275,16 +2268,18 @@ msgstr[0] "Kopieer Deelbare Link"
msgstr[1] "Kopieer Deelbare Links"
#: ElectronClient/commands/toggleExternalEditing.js:18
#, fuzzy
msgid "Toggle external editing"
msgstr "Extern bewerken in- of uitschakelen"
msgstr "Klik om extern bewerken te stoppen"
#: ElectronClient/commands/toggleExternalEditing.js:37
msgid "Stop"
msgstr "Stop"
msgstr ""
#: ElectronClient/commands/copyDevCommand.js:18
#, fuzzy
msgid "Copy dev mode command to clipboard"
msgstr "Ontwikkelaarsmodus commando kopiëren naar klembord"
msgstr "Pad kopiëren naar klembord"
#: ElectronClient/commands/stopExternalEditing.js:18
msgid "Stop external editing"
@@ -2296,8 +2291,9 @@ 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 profiel map"
msgstr "Open sjabloon map"
#: ElectronClient/app.js:345
#, javascript-format
@@ -2343,7 +2339,7 @@ msgstr ""
#: ReactNativeClient/lib/SyncTargetAmazonS3.js:28
msgid "AWS S3"
msgstr "AWS S3"
msgstr ""
#: ReactNativeClient/lib/SyncTargetDropbox.js:25
msgid "Dropbox"
@@ -2487,19 +2483,19 @@ msgstr "WebDAV-wachtwoord"
#: ReactNativeClient/lib/models/Setting.js:215
msgid "AWS S3 bucket"
msgstr "AWS S3 bucket"
msgstr ""
#: ReactNativeClient/lib/models/Setting.js:226
msgid "AWS S3 URL"
msgstr "AWS S3 URL"
msgstr ""
#: ReactNativeClient/lib/models/Setting.js:237
msgid "AWS key"
msgstr "AWS key"
msgstr ""
#: ReactNativeClient/lib/models/Setting.js:247
msgid "AWS secret"
msgstr "AWS secret"
msgstr ""
#: ReactNativeClient/lib/models/Setting.js:259
msgid "Attachment download behaviour"
@@ -2959,8 +2955,9 @@ msgid "Web Clipper"
msgstr "Webclipper"
#: ReactNativeClient/lib/models/Setting.js:1310
#, fuzzy
msgid "Keyboard Shortcuts"
msgstr "Sneltoetsen"
msgstr "Toetsenbordmodus"
#: ReactNativeClient/lib/models/Setting.js:1317
msgid ""
@@ -3077,8 +3074,9 @@ 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
@@ -3116,9 +3114,8 @@ 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 "De sync target moet worden geüpdatet. Druk hier om door te gaan."
msgstr ""
#: ReactNativeClient/lib/components/side-menu-content.js:126
#, javascript-format
@@ -3242,9 +3239,8 @@ msgid "Refresh"
msgstr "Verversen"
#: ReactNativeClient/lib/components/screens/UpgradeSyncTargetScreen.js:42
#, fuzzy
msgid "Sync Target Upgrade"
msgstr "Sync target update"
msgstr ""
#: ReactNativeClient/lib/components/screens/NoteTagsDialog.js:163
msgid "New tags:"
@@ -3488,14 +3484,11 @@ 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 vereist"
msgstr "Toestemming om de camera te gebruiken"
#: ReactNativeClient/lib/components/screens/Note.js:583
#, javascript-format
@@ -3744,23 +3737,23 @@ msgstr "Geef een importformaat op voor %s"
#: ReactNativeClient/lib/services/KeymapService.js:240
msgid "command"
msgstr "commando"
msgstr ""
#: ReactNativeClient/lib/services/KeymapService.js:240
#: ReactNativeClient/lib/services/KeymapService.js:245
#, javascript-format
msgid "\"%s\" is missing the required \"%s\" property."
msgstr "\"%s\" mist de vereiste \"%s\" eigenschap."
msgstr ""
#: ReactNativeClient/lib/services/KeymapService.js:245
#: ReactNativeClient/lib/services/KeymapService.js:252
msgid "accelerator"
msgstr "versneller"
msgstr ""
#: ReactNativeClient/lib/services/KeymapService.js:252
#, javascript-format
#, fuzzy, javascript-format
msgid "Invalid %s: %s."
msgstr "Ongeldig %s: %s."
msgstr "Ongeldig antwoord: %s"
#: ReactNativeClient/lib/services/KeymapService.js:270
#, javascript-format
@@ -3768,13 +3761,11 @@ 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 "Versneller \"%s\" is niet geldig."
msgstr ""
#: ReactNativeClient/lib/services/report.js:121
msgid "Items that cannot be synchronised"

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,54 +120,23 @@ 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>');
}
// 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>');
}
// 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>');
}));
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

@@ -3,7 +3,6 @@ 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', () => {
@@ -32,7 +31,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();
});
@@ -70,7 +69,7 @@ describe('services_KeymapService', () => {
};
Object.entries(testCases).forEach(([platform, accelerators]) => {
keymapService.initialize([], platform);
keymapService.initialize(platform);
accelerators.forEach(accelerator => {
expect(() => keymapService.validateAccelerator(accelerator)).toThrow();
});
@@ -82,12 +81,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');
@@ -96,7 +95,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');
@@ -107,17 +106,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');
});
@@ -131,7 +130,7 @@ describe('services_KeymapService', () => {
beforeEach(() => keymapService.initialize());
it('should update the Accelerator', () => {
keymapService.initialize(['print'], 'darwin');
keymapService.initialize('darwin');
const testCases_Darwin = [
{ command: 'newNote', accelerator: 'Ctrl+Option+Shift+N' },
{ command: 'synchronize', accelerator: 'F11' },
@@ -148,7 +147,7 @@ describe('services_KeymapService', () => {
expect(keymapService.getAccelerator(command)).toEqual(accelerator);
});
keymapService.initialize(['print'], 'linux');
keymapService.initialize('linux');
const testCases_Linux = [
{ command: 'newNote', accelerator: 'Ctrl+Alt+Shift+N' },
{ command: 'synchronize', accelerator: 'F15' },
@@ -168,7 +167,7 @@ describe('services_KeymapService', () => {
});
describe('getDefaultAccelerator', () => {
beforeEach(() => keymapService.initialize(['print', 'linux']));
beforeEach(() => keymapService.initialize());
it('should return the default accelerator', () => {
const testCases = [
@@ -197,7 +196,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' },
@@ -218,7 +217,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

@@ -14,7 +14,6 @@ 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 menuCommandNames from './gui/menuCommandNames';
require('app-module-path').addPath(__dirname);
@@ -496,6 +495,14 @@ 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());
@@ -526,21 +533,9 @@ class Application extends BaseApplication {
CommandService.instance().registerDeclaration(declaration);
}
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.
// 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();

View File

@@ -5,7 +5,7 @@ import { _ } from 'lib/locale';
const commandService = CommandService.instance();
const getLabel = (commandName: string):string => {
const getLabel = (commandName: string) => {
if (commandService.exists(commandName)) return commandService.label(commandName, true);
// Some commands are not registered in CommandService at the moment

View File

@@ -1,22 +1,11 @@
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,
@@ -24,7 +13,7 @@ const useKeymap = (): [
(commandName: string, accelerator: string) => void,
(commandName: string) => void
] => {
const [keymapItems, setKeymapItems] = useState<KeymapItem[]>(() => allKeymapItems());
const [keymapItems, setKeymapItems] = useState<KeymapItem[]>(() => keymapService.getKeymapItems());
const [keymapError, setKeymapError] = useState<Error>(null);
const [mustSave, setMustSave] = useState(false);
@@ -53,7 +42,7 @@ const useKeymap = (): [
const overrideKeymapItems = (customKeymapItems: KeymapItem[]) => {
const oldKeymapItems = [...customKeymapItems];
keymapService.resetKeymap(); // Start with a fresh keymap
keymapService.initialize(); // Start with a fresh keymap
try {
// First, try to update the in-memory keymap of KeymapService

View File

@@ -14,7 +14,6 @@ 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 menuCommandNames from './menuCommandNames';
const { connect } = require('react-redux');
const { reg } = require('lib/registry.js');
@@ -87,7 +86,39 @@ interface Props {
pluginMenus: any[],
}
const commandNames:string[] = menuCommandNames();
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',
];
function menuItemSetChecked(id:string, checked:boolean) {
const menu = Menu.getApplicationMenu();
@@ -218,8 +249,10 @@ 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();
@@ -266,18 +299,16 @@ 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: () => {
@@ -311,22 +342,18 @@ function useMenu(props:Props) {
},
});
let toolsItems:any[] = [];
// we need this workaround, because on macOS the menu is different
const toolsItemsWindowsLinux:any[] = [
{
label: _('Options'),
accelerator: keymapService.getAccelerator('config'),
click: () => {
props.dispatch({
type: 'NAV_GO',
routeName: 'Config',
});
},
const toolsItemsWindowsLinux:any[] = toolsItemsFirst.concat([{
label: _('Options'),
visible: !shim.isMac(),
accelerator: !shim.isMac() && keymapService.getAccelerator('config'),
click: () => {
props.dispatch({
type: 'NAV_GO',
routeName: 'Config',
});
},
separator(),
];
} as any]);
// the following menu items will be available for all OS under Tools
const toolsItemsAll = [{
@@ -424,7 +451,9 @@ function useMenu(props:Props) {
menuItemDic.synchronize,
shim.isMac() ? noItem : printItem, {
shim.isMac() ? syncStatusItem : noItem, {
type: 'separator',
}, shim.isMac() ? noItem : printItem, {
type: 'separator',
platforms: ['darwin'],
},
@@ -489,6 +518,12 @@ function useMenu(props:Props) {
});
}
const separator = () => {
return {
type: 'separator',
};
};
const rootMenus:any = {
edit: {
id: 'edit',
@@ -551,6 +586,11 @@ function useMenu(props:Props) {
},
},
separator(),
{
label: _('Focus'),
submenu: focusItems,
},
separator(),
{
label: _('Actual Size'),
click: () => {
@@ -583,18 +623,6 @@ function useMenu(props:Props) {
accelerator: 'CommandOrControl+-',
}],
},
go: {
label: _('&Go'),
submenu: [
menuItemDic.historyBackward,
menuItemDic.historyForward,
separator(),
{
label: _('Focus'),
submenu: focusItems,
},
],
},
note: {
label: _('&Note'),
submenu: [
@@ -627,8 +655,6 @@ function useMenu(props:Props) {
click: () => _checkForUpdates(),
},
separator(),
syncStatusItem,
separator(),
{
id: 'help:toggleDevTools',
label: _('Toggle development tools'),
@@ -683,7 +709,6 @@ 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);
}
}
@@ -716,7 +741,6 @@ function useMenu(props:Props) {
rootMenus.file,
rootMenus.edit,
rootMenus.view,
rootMenus.go,
rootMenus.note,
rootMenus.tools,
rootMenus.help,
@@ -724,6 +748,46 @@ 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([
{

View File

@@ -1,37 +0,0 @@
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.11",
"version": "1.3.10",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

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

View File

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

View File

@@ -211,9 +211,7 @@ class NoteScreenComponent extends BaseScreenComponent {
};
this.useBetaEditor = () => {
// Disable for now
return false;
// return Setting.value('editor.beta') && Platform.OS !== 'android';
return Setting.value('editor.beta') && Platform.OS !== 'android';
};
this.takePhoto_onPress = this.takePhoto_onPress.bind(this);

View File

@@ -318,14 +318,9 @@ 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);
@@ -381,7 +376,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,24 +72,11 @@ 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) {
icon = '';
attrHtml.push(`href='${htmlentities(href)}'`);
// return `<a data-from-md ${resourceIdAttr} title='${htmlentities(title)}' href='${htmlentities(href)}' type='${htmlentities(mime)}'>`;
return `<a data-from-md ${resourceIdAttr} title='${htmlentities(title)}' href='${htmlentities(href)}' type='${htmlentities(mime)}'>`;
} else {
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 data-from-md ${resourceIdAttr} title='${htmlentities(title)}' href='${hrefAttr}' ${js} type='${htmlentities(mime)}'>${icon}`;
}
return `<a ${attrHtml.join(' ')}>${icon}`;
};
}

View File

@@ -508,16 +508,11 @@ 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: false, // mobilePlatform === 'ios',
public: 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.',
@@ -565,11 +560,11 @@ class Setting extends BaseModel {
},
// Deprecated - use markdown.plugin.*
'markdown.softbreaks': { value: true, type: SettingItemType.Bool, public: false, appTypes: ['mobile', 'desktop'] },
'markdown.softbreaks': { value: false, 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: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable soft breaks')}${wysiwygYes}` },
'markdown.plugin.softbreaks': { value: false, 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}` },

View File

@@ -144,17 +144,8 @@ export default class CommandService extends BaseService {
return output;
}
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 commandNames() {
return Object.keys(this.commands_);
}
public commandByName(name:string, options:CommandByNameOptions = null):Command {
@@ -239,10 +230,6 @@ 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,9 +106,8 @@ class DecryptionWorker {
if (!('errorHandler' in options)) options.errorHandler = 'log';
if (this.state_ !== 'idle') {
const msg = `DecryptionWorker: cannot start because state is "${this.state_}"`;
this.logger().debug(msg);
return { error: new Error(msg) };
this.logger().debug(`DecryptionWorker: cannot start because state is "${this.state_}"`);
return;
}
// Note: the logic below is an optimisation to avoid going through the loop if no master key exists
@@ -116,8 +115,7 @@ class DecryptionWorker {
// "throw" and "dispatch" logic.
const loadedMasterKeyCount = await this.encryptionService().loadedMasterKeysCount();
if (!loadedMasterKeyCount) {
const msg = 'DecryptionWorker: cannot start because no master key is currently loaded.';
this.logger().info(msg);
this.logger().info('DecryptionWorker: cannot start because no master key is currently loaded.');
const ids = await MasterKey.allIds();
if (ids.length) {
@@ -132,7 +130,7 @@ class DecryptionWorker {
});
}
}
return { error: new Error(msg) };
return;
}
this.logger().info('DecryptionWorker: starting decryption...');

View File

@@ -16,6 +16,7 @@ const defaultKeymapItems = {
{ accelerator: 'Cmd+N', command: 'newNote' },
{ accelerator: 'Cmd+T', command: 'newTodo' },
{ accelerator: 'Cmd+S', command: 'synchronize' },
{ accelerator: '', command: 'print' },
{ accelerator: 'Cmd+H', command: 'hideApp' },
{ accelerator: 'Cmd+Q', command: 'quit' },
{ accelerator: 'Cmd+,', command: 'config' },
@@ -50,6 +51,7 @@ const defaultKeymapItems = {
{ accelerator: 'Ctrl+N', command: 'newNote' },
{ accelerator: 'Ctrl+T', command: 'newTodo' },
{ accelerator: 'Ctrl+S', command: 'synchronize' },
{ accelerator: null, command: 'print' },
{ accelerator: 'Ctrl+Q', command: 'quit' },
{ accelerator: 'Ctrl+Alt+I', command: 'insertTemplate' },
{ accelerator: 'Ctrl+C', command: 'textCopy' },
@@ -101,42 +103,29 @@ export default class KeymapService extends BaseService {
super();
this.lastSaveTime_ = Date.now();
// By default, initialize for the current platform
// Manual initialization allows testing for other platforms
this.initialize();
}
public get lastSaveTime():number {
return this.lastSaveTime_;
}
// `additionalDefaultCommandNames` will be added to the default keymap
// **except** if they are already in it. Basically this is a mechanism
// to add all the commands from the command service to the default
// keymap.
public initialize(additionalDefaultCommandNames:string[] = [], platform: string = shim.platformName()) {
public initialize(platform: string = shim.platformName()) {
this.platform = platform;
switch (platform) {
case 'darwin':
this.defaultKeymapItems = defaultKeymapItems.darwin.slice();
this.defaultKeymapItems = defaultKeymapItems.darwin;
this.modifiersRegExp = modifiersRegExp.darwin;
break;
default:
this.defaultKeymapItems = defaultKeymapItems.default.slice();
this.defaultKeymapItems = defaultKeymapItems.default;
this.modifiersRegExp = modifiersRegExp.default;
}
for (const name of additionalDefaultCommandNames) {
if (this.defaultKeymapItems.find((item:KeymapItem) => item.command === name)) continue;
this.defaultKeymapItems.push({
command: name,
accelerator: null,
});
}
this.resetKeymap();
}
// Reset keymap back to its default values
public resetKeymap() {
this.keymap = {};
for (let i = 0; i < this.defaultKeymapItems.length; i++) {
// Keep the original defaultKeymapItems array untouched
@@ -151,9 +140,7 @@ export default class KeymapService extends BaseService {
if (await shim.fsDriver().exists(customKeymapPath)) {
this.logger().info(`KeymapService: Loading keymap from file: ${customKeymapPath}`);
const customKeymapFile = (await shim.fsDriver().readFile(customKeymapPath, 'utf-8')).trim();
if (!customKeymapFile) return;
const customKeymapFile = await shim.fsDriver().readFile(customKeymapPath, 'utf-8');
// Custom keymaps are supposed to contain an array of keymap items
this.overrideKeymap(JSON.parse(customKeymapFile));
}
@@ -196,8 +183,8 @@ export default class KeymapService extends BaseService {
if (!commandName) throw new Error('Cannot register an accelerator without a command name');
const validatedAccelerator = accelerator ? this.convertToPlatform(accelerator) : null;
if (validatedAccelerator) this.validateAccelerator(validatedAccelerator);
const validatedAccelerator = this.convertToPlatform(accelerator);
this.validateAccelerator(validatedAccelerator);
this.keymap[commandName] = {
command: commandName,
@@ -277,7 +264,7 @@ export default class KeymapService extends BaseService {
// Throws whenever there are duplicate Accelerators used in the keymap
this.validateKeymap();
} catch (err) {
this.resetKeymap(); // Discard all the changes if there are any issues
this.initialize(); // Discard all the changes if there are any issues
throw err;
}
}
@@ -352,10 +339,7 @@ export default class KeymapService extends BaseService {
public domToElectronAccelerator(event: KeyboardEvent<HTMLDivElement>) {
const parts = [];
// We use the "keyCode" and not "key" because the modifier keys
// would change the "key" value. eg "Option+U" would give "º" as a key instead of "U"
const { keyCode, ctrlKey, metaKey, altKey, shiftKey } = event;
const { key, ctrlKey, metaKey, altKey, shiftKey } = event;
// First, the modifiers
if (ctrlKey) parts.push('Ctrl');
@@ -371,7 +355,7 @@ export default class KeymapService extends BaseService {
}
// Finally, the key
const electronKey = KeymapService.domToElectronKey(String.fromCharCode(keyCode));
const electronKey = KeymapService.domToElectronKey(key);
if (electronKey) parts.push(electronKey);
return parts.join('+');

View File

@@ -10,13 +10,12 @@ const { execCommand, githubUsername } = require('./tool-utils.js');
// From https://stackoverflow.com/a/6234804/561309
function escapeHtml(unsafe) {
// We only escape <> as this is enough for Markdown
return unsafe
// .replace(/&/g, '&amp;')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
// .replace(/"/g, '&quot;')
// .replace(/'/g, '&#039;');
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
async function gitLog(sinceTag) {

View File

@@ -2,4 +2,5 @@
When experimenting with Joplin, for example when developing a plugin or trying a theme, you might want to run Joplin in development mode. Doing so means that Joplin will run using a different profile, so you can experiment without risking to accidentally change or delete your data.
To enable Development Mode, open Joplin as normal, then go to **Help => Copy dev mode command to clipboard**. This will copy a command to the clipboard. Now close Joplin, and start it again in dev mode using the command you've just copied.
To enable Development Mode, open Joplin as normal, then go to **Help => Copy dev mode command to clipboard**. This will copy a command to the clipboard.
Paste the content of the clipboard to your console, then close the "other" opened joplin.

View File

@@ -12,6 +12,6 @@
"**/node_modules",
"ElectronClient/dist/**/*",
"CliClient/tests/support/**/*",
"CliClient/tests-build/**/*",
"CliClient/tests-build/support/**/*",
],
}

View File

@@ -31,6 +31,6 @@
"**/node_modules",
"ElectronClient/dist/**/*",
"CliClient/tests/support/**/*",
"CliClient/tests-build/**/*",
"CliClient/tests-build/support/**/*",
],
}