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

Compare commits

..

1 Commits

Author SHA1 Message Date
Laurent Cozic
24263d12e2 Trying to add plugin support for mobile app 2020-10-30 18:44:39 +00:00
46 changed files with 2867 additions and 1192 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

@@ -867,8 +867,9 @@ msgid "New version: %s"
msgstr "새 버전: %s"
#: ElectronClient/checkForUpdates.js:154
#, fuzzy
msgid "Download"
msgstr "다운로드"
msgstr "다운로드"
#: ElectronClient/checkForUpdates.js:154
msgid "Full Release Notes"
@@ -2247,8 +2248,9 @@ msgid "Error opening note in editor: %s"
msgstr "편집기에서 노트를 열 수 없는 오류가 발생하였습니다: %s"
#: ElectronClient/commands/openProfileDirectory.js:18
#, fuzzy
msgid "Open profile directory"
msgstr "프로필 디렉터리 열기"
msgstr "템플릿 디렉터리 열기"
#: ElectronClient/app.js:345
#, javascript-format

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

@@ -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
@@ -30,14 +30,9 @@ const getLabel = (commandName: string):string => {
return _('Command palette');
case 'config':
return shim.isMac() ? _('Preferences') : _('Options');
default:
throw new Error(`Command: ${commandName} is unknown`);
}
// We don't throw an error if a command is not found because if for
// example a command is removed from one version to the next, or a
// command is renamed, we still want the keymap editor to work. So in
// that case, we simply display the command name and it is up to the
// user to fix the shortcut if needed.
return `${commandName} (${_('Invalid')})`;
};
export default getLabel;

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.18",
"version": "1.3.10",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "Joplin",
"version": "1.3.18",
"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

@@ -28,7 +28,7 @@ Linux | <a href='https://github.com/laurent22/joplin/releases/download/
Operating System | Download | Alt. Download
-----------------|----------|----------------
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://joplinapp.org/images/BadgeAndroid.png'/></a> | or download the APK file: [64-bit](https://github.com/laurent22/joplin-android/releases/download/android-v1.3.13/joplin-v1.3.13.apk) [32-bit](https://github.com/laurent22/joplin-android/releases/download/android-v1.3.13/joplin-v1.3.13-32bit.apk)
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://joplinapp.org/images/BadgeAndroid.png'/></a> | or download the APK file: [64-bit](https://github.com/laurent22/joplin-android/releases/download/android-v1.3.10/joplin-v1.3.10.apk) [32-bit](https://github.com/laurent22/joplin-android/releases/download/android-v1.3.10/joplin-v1.3.10-32bit.apk)
iOS | <a href='https://itunes.apple.com/us/app/joplin/id1315599797'><img alt='Get it on the App Store' height="40px" src='https://joplinapp.org/images/BadgeIOS.png'/></a> | -
## Terminal application

876
ReactNativeClient/Root.tsx Normal file
View File

@@ -0,0 +1,876 @@
import setUpQuickActions from './setUpQuickActions';
import PluginAssetsLoader from './PluginAssetsLoader';
import reducer, { defaultState, State } from 'lib/reducer';
import AlarmService from 'lib/services/AlarmService';
import AlarmServiceDriver from 'lib/services/AlarmServiceDriver.ios';
import Alarm from 'lib/models/Alarm';
import Logger, { TargetType } from 'lib/Logger';
import BaseService from 'lib/services/BaseService';
import NoteScreen from 'lib/components/screens/Note';
import UpgradeSyncTargetScreen from 'lib/components/screens/UpgradeSyncTargetScreen';
import Setting from 'lib/models/Setting';
import PoorManIntervals from 'lib/PoorManIntervals';
import ShareExtension from 'lib/ShareExtension';
import handleShared from 'lib/shareHandler';
import uuid from 'lib/uuid';
import KeychainServiceDriverMobile from 'lib/services/keychain/KeychainServiceDriver.mobile';
import shim from 'lib/shim';
import PluginService from 'lib/services/plugins/PluginService';
import PluginRunner from './services/plugins/PluginRunner';
const React = require('react');
const { AppState, Keyboard, NativeModules, BackHandler, Animated, View, StatusBar } = require('react-native');
const SafeAreaView = require('lib/components/SafeAreaView');
const { connect, Provider } = require('react-redux');
const { BackButtonService } = require('lib/services/back-button.js');
const NavService = require('lib/services/NavService.js');
const { createStore, applyMiddleware } = require('redux');
const reduxSharedMiddleware = require('lib/components/shared/reduxSharedMiddleware');
const { shimInit } = require('lib/shim-init-react.js');
const { time } = require('lib/time-utils.js');
const { AppNav } = require('lib/components/app-nav.js');
const Note = require('lib/models/Note.js');
const Folder = require('lib/models/Folder.js');
const BaseSyncTarget = require('lib/BaseSyncTarget.js');
const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
const Resource = require('lib/models/Resource.js');
const Tag = require('lib/models/Tag.js');
const NoteTag = require('lib/models/NoteTag.js');
const BaseItem = require('lib/models/BaseItem.js');
const MasterKey = require('lib/models/MasterKey.js');
const Revision = require('lib/models/Revision.js');
const BaseModel = require('lib/BaseModel.js');
const ResourceService = require('lib/services/ResourceService');
const RevisionService = require('lib/services/RevisionService');
const KvStore = require('lib/services/KvStore');
const { JoplinDatabase } = require('lib/joplin-database.js');
const { Database } = require('lib/database.js');
const { NotesScreen } = require('lib/components/screens/notes.js');
const { TagsScreen } = require('lib/components/screens/tags.js');
const { ConfigScreen } = require('lib/components/screens/config.js');
const { FolderScreen } = require('lib/components/screens/folder.js');
const { LogScreen } = require('lib/components/screens/log.js');
const { StatusScreen } = require('lib/components/screens/status.js');
const { SearchScreen } = require('lib/components/screens/search.js');
const { OneDriveLoginScreen } = require('lib/components/screens/onedrive-login.js');
const { EncryptionConfigScreen } = require('lib/components/screens/encryption-config.js');
const { DropboxLoginScreen } = require('lib/components/screens/dropbox-login.js');
const { MenuContext } = require('react-native-popup-menu');
const { SideMenu } = require('lib/components/side-menu.js');
const { SideMenuContent } = require('lib/components/side-menu-content.js');
const { SideMenuContentNote } = require('lib/components/side-menu-content-note.js');
const { DatabaseDriverReactNative } = require('lib/database-driver-react-native');
const { reg } = require('lib/registry.js');
const { setLocale, closestSupportedLocale, defaultLocale } = require('lib/locale');
const { FileApiDriverLocal } = require('lib/file-api-driver-local.js');
const RNFetchBlob = require('rn-fetch-blob').default;
const DropdownAlert = require('react-native-dropdownalert').default;
const ResourceFetcher = require('lib/services/ResourceFetcher');
const SearchEngine = require('lib/services/searchengine/SearchEngine');
const WelcomeUtils = require('lib/WelcomeUtils');
const { themeStyle } = require('lib/components/global-style.js');
const { loadKeychainServiceAndSettings } = require('lib/services/SettingUtils');
const SyncTargetRegistry = require('lib/SyncTargetRegistry.js');
const SyncTargetOneDrive = require('lib/SyncTargetOneDrive.js');
const SyncTargetFilesystem = require('lib/SyncTargetFilesystem.js');
const SyncTargetNextcloud = require('lib/SyncTargetNextcloud.js');
const SyncTargetWebDAV = require('lib/SyncTargetWebDAV.js');
const SyncTargetDropbox = require('lib/SyncTargetDropbox.js');
const SyncTargetAmazonS3 = require('lib/SyncTargetAmazonS3.js');
SyncTargetRegistry.addClass(SyncTargetOneDrive);
SyncTargetRegistry.addClass(SyncTargetNextcloud);
SyncTargetRegistry.addClass(SyncTargetWebDAV);
SyncTargetRegistry.addClass(SyncTargetDropbox);
SyncTargetRegistry.addClass(SyncTargetFilesystem);
SyncTargetRegistry.addClass(SyncTargetAmazonS3);
const FsDriverRN = require('lib/fs-driver-rn.js').FsDriverRN;
const DecryptionWorker = require('lib/services/DecryptionWorker');
const EncryptionService = require('lib/services/EncryptionService');
const MigrationService = require('lib/services/MigrationService');
const DEFAULT_ROUTE = {
type: 'NAV_GO',
routeName: 'Notes',
smartFilterId: 'c3176726992c11e9ac940492261af972',
};
export interface AppState extends State {
sideMenuOpenPercent: number,
route: any,
noteSelectionEnabled: boolean,
noteSideMenuOptions: any,
}
const appDefaultState:AppState = {
...defaultState,
sideMenuOpenPercent: 0,
route: DEFAULT_ROUTE,
noteSelectionEnabled: false,
noteSideMenuOptions: null,
}
let storeDispatch = function(_action:any) {};
const logReducerAction = function(action:any) {
if (['SIDE_MENU_OPEN_PERCENT', 'SYNC_REPORT_UPDATE'].indexOf(action.type) >= 0) return;
const msg = [action.type];
if (action.routeName) msg.push(action.routeName);
// reg.logger().debug('Reducer action', msg.join(', '));
};
const generalMiddleware = (store:any) => (next:any) => async (action:any) => {
logReducerAction(action);
PoorManIntervals.update(); // This function needs to be called regularly so put it here
const result = next(action);
const newState:AppState = store.getState();
await reduxSharedMiddleware(store, next, action);
if (action.type == 'NAV_GO') Keyboard.dismiss();
if (['NOTE_UPDATE_ONE', 'NOTE_DELETE', 'FOLDER_UPDATE_ONE', 'FOLDER_DELETE'].indexOf(action.type) >= 0) {
if (!await reg.syncTarget().syncStarted()) reg.scheduleSync(5 * 1000, { syncSteps: ['update_remote', 'delete_remote'] });
SearchEngine.instance().scheduleSyncTables();
}
if (['EVENT_NOTE_ALARM_FIELD_CHANGE', 'NOTE_DELETE'].indexOf(action.type) >= 0) {
await AlarmService.updateNoteNotification(action.id, action.type === 'NOTE_DELETE');
}
if (action.type == 'SETTING_UPDATE_ONE' && action.key == 'sync.interval' || action.type == 'SETTING_UPDATE_ALL') {
reg.setupRecurrentSync();
}
if ((action.type == 'SETTING_UPDATE_ONE' && (action.key == 'dateFormat' || action.key == 'timeFormat')) || (action.type == 'SETTING_UPDATE_ALL')) {
time.setDateFormat(Setting.value('dateFormat'));
time.setTimeFormat(Setting.value('timeFormat'));
}
if (action.type == 'SETTING_UPDATE_ONE' && action.key == 'locale' || action.type == 'SETTING_UPDATE_ALL') {
setLocale(Setting.value('locale'));
}
if ((action.type == 'SETTING_UPDATE_ONE' && (action.key.indexOf('encryption.') === 0)) || (action.type == 'SETTING_UPDATE_ALL')) {
await EncryptionService.instance().loadMasterKeysFromSettings();
DecryptionWorker.instance().scheduleStart();
const loadedMasterKeyIds = EncryptionService.instance().loadedMasterKeyIds();
storeDispatch({
type: 'MASTERKEY_REMOVE_NOT_LOADED',
ids: loadedMasterKeyIds,
});
// Schedule a sync operation so that items that need to be encrypted
// are sent to sync target.
reg.scheduleSync();
}
if (action.type == 'NAV_GO' && action.routeName == 'Notes') {
Setting.setValue('activeFolderId', newState.selectedFolderId);
}
if (action.type === 'SYNC_GOT_ENCRYPTED_ITEM') {
DecryptionWorker.instance().scheduleStart();
}
if (action.type === 'SYNC_CREATED_OR_UPDATED_RESOURCE') {
ResourceFetcher.instance().autoAddResources();
}
return result;
};
const navHistory:any[] = [];
function historyCanGoBackTo(route:any) {
if (route.routeName === 'Note') return false;
if (route.routeName === 'Folder') return false;
// There's no point going back to these screens in general and, at least in OneDrive case,
// it can be buggy to do so, due to incorrectly relying on global state (reg.syncTarget...)
if (route.routeName === 'OneDriveLogin') return false;
if (route.routeName === 'DropboxLogin') return false;
return true;
}
const appReducer = (state:AppState = appDefaultState, action:any) => {
let newState = state;
let historyGoingBack = false;
try {
switch (action.type) {
// @ts-ignore
case 'NAV_BACK':
{
if (!navHistory.length) break;
let newAction = null;
while (navHistory.length) {
newAction = navHistory.pop();
if (newAction.routeName != state.route.routeName) break;
}
action = newAction ? newAction : navHistory.pop();
historyGoingBack = true;
}
// Fall throught
case 'NAV_GO':
{
const currentRoute = state.route;
if (!historyGoingBack && historyCanGoBackTo(currentRoute)) {
// If the route *name* is the same (even if the other parameters are different), we
// overwrite the last route in the history with the current one. If the route name
// is different, we push a new history entry.
if (currentRoute.routeName == action.routeName) {
// nothing
} else {
navHistory.push(currentRoute);
}
}
// HACK: whenever a new screen is loaded, all the previous screens of that type
// are overwritten with the new screen parameters. This is because the way notes
// are currently loaded is not optimal (doesn't retain history properly) so
// this is a simple fix without doing a big refactoring to change the way notes
// are loaded. Might be good enough since going back to different folders
// is probably not a common workflow.
for (let i = 0; i < navHistory.length; i++) {
const n = navHistory[i];
if (n.routeName == action.routeName) {
navHistory[i] = Object.assign({}, action);
}
}
newState = Object.assign({}, state);
newState.selectedNoteHash = '';
if ('noteId' in action) {
newState.selectedNoteIds = action.noteId ? [action.noteId] : [];
}
if ('folderId' in action) {
newState.selectedFolderId = action.folderId;
newState.notesParentType = 'Folder';
}
if ('tagId' in action) {
newState.selectedTagId = action.tagId;
newState.notesParentType = 'Tag';
}
if ('smartFilterId' in action) {
newState.selectedSmartFilterId = action.smartFilterId;
newState.notesParentType = 'SmartFilter';
}
if ('itemType' in action) {
newState.selectedItemType = action.itemType;
}
if ('noteHash' in action) {
newState.selectedNoteHash = action.noteHash;
}
if ('sharedData' in action) {
newState.sharedData = action.sharedData;
} else {
newState.sharedData = null;
}
newState.route = action;
newState.historyCanGoBack = !!navHistory.length;
}
break;
case 'SIDE_MENU_TOGGLE':
newState = Object.assign({}, state);
newState.showSideMenu = !newState.showSideMenu;
break;
case 'SIDE_MENU_OPEN':
newState = Object.assign({}, state);
newState.showSideMenu = true;
break;
case 'SIDE_MENU_CLOSE':
newState = Object.assign({}, state);
newState.showSideMenu = false;
break;
case 'SIDE_MENU_OPEN_PERCENT':
newState = Object.assign({}, state);
newState.sideMenuOpenPercent = action.value;
break;
case 'NOTE_SELECTION_TOGGLE':
{
newState = Object.assign({}, state);
const noteId = action.id;
const newSelectedNoteIds = state.selectedNoteIds.slice();
const existingIndex = state.selectedNoteIds.indexOf(noteId);
if (existingIndex >= 0) {
newSelectedNoteIds.splice(existingIndex, 1);
} else {
newSelectedNoteIds.push(noteId);
}
newState.selectedNoteIds = newSelectedNoteIds;
newState.noteSelectionEnabled = !!newSelectedNoteIds.length;
}
break;
case 'NOTE_SELECTION_START':
if (!state.noteSelectionEnabled) {
newState = Object.assign({}, state);
newState.noteSelectionEnabled = true;
newState.selectedNoteIds = [action.id];
}
break;
case 'NOTE_SELECTION_END':
newState = Object.assign({}, state);
newState.noteSelectionEnabled = false;
newState.selectedNoteIds = [];
break;
case 'NOTE_SIDE_MENU_OPTIONS_SET':
newState = Object.assign({}, state);
newState.noteSideMenuOptions = action.options;
break;
}
} catch (error) {
error.message = `In reducer: ${error.message} Action: ${JSON.stringify(action)}`;
throw error;
}
return reducer(newState, action);
};
const store = createStore(appReducer, applyMiddleware(generalMiddleware));
storeDispatch = store.dispatch;
function resourceFetcher_downloadComplete(event:any) {
if (event.encrypted) {
DecryptionWorker.instance().scheduleStart();
}
}
function decryptionWorker_resourceMetadataButNotBlobDecrypted() {
ResourceFetcher.instance().scheduleAutoAddResources();
}
async function initialize(dispatch:Function) {
shimInit();
Setting.setConstant('env', __DEV__ ? 'dev' : 'prod');
Setting.setConstant('appId', 'net.cozic.joplin-mobile');
Setting.setConstant('appType', 'mobile');
Setting.setConstant('resourceDir', RNFetchBlob.fs.dirs.DocumentDir);
Setting.setConstant('pluginDir', Setting.value('resourceDir') + '/plugins');
const logDatabase = new Database(new DatabaseDriverReactNative());
await logDatabase.open({ name: 'log.sqlite' });
await logDatabase.exec(Logger.databaseCreateTableSql());
const mainLogger = new Logger();
mainLogger.addTarget(TargetType.Database, { database: logDatabase, source: 'm' });
mainLogger.setLevel(Logger.LEVEL_INFO);
if (Setting.value('env') == 'dev') {
mainLogger.addTarget(TargetType.Console);
mainLogger.setLevel(Logger.LEVEL_DEBUG);
}
reg.setLogger(mainLogger);
reg.setShowErrorMessageBoxHandler((message:string) => { alert(message); });
BaseService.logger_ = mainLogger;
// require('lib/ntpDate').setLogger(reg.logger());
reg.logger().info('====================================');
reg.logger().info(`Starting application ${Setting.value('appId')} (${Setting.value('env')})`);
const dbLogger = new Logger();
dbLogger.addTarget(TargetType.Database, { database: logDatabase, source: 'm' });
if (Setting.value('env') == 'dev') {
dbLogger.addTarget(TargetType.Console);
dbLogger.setLevel(Logger.LEVEL_INFO); // Set to LEVEL_DEBUG for full SQL queries
} else {
dbLogger.setLevel(Logger.LEVEL_INFO);
}
const db = new JoplinDatabase(new DatabaseDriverReactNative());
db.setLogger(dbLogger);
reg.setDb(db);
reg.dispatch = dispatch;
BaseModel.dispatch = dispatch;
FoldersScreenUtils.dispatch = dispatch;
BaseSyncTarget.dispatch = dispatch;
NavService.dispatch = dispatch;
BaseModel.setDb(db);
KvStore.instance().setDb(reg.db());
BaseItem.loadClass('Note', Note);
BaseItem.loadClass('Folder', Folder);
BaseItem.loadClass('Resource', Resource);
BaseItem.loadClass('Tag', Tag);
BaseItem.loadClass('NoteTag', NoteTag);
BaseItem.loadClass('MasterKey', MasterKey);
BaseItem.loadClass('Revision', Revision);
const fsDriver = new FsDriverRN();
Resource.fsDriver_ = fsDriver;
FileApiDriverLocal.fsDriver_ = fsDriver;
AlarmService.setDriver(new AlarmServiceDriver(mainLogger));
AlarmService.setLogger(mainLogger);
try {
if (Setting.value('env') == 'prod') {
await db.open({ name: 'joplin.sqlite' });
} else {
await db.open({ name: 'joplin-76.sqlite' });
// await db.clearForTesting();
}
reg.logger().info('Database is ready.');
reg.logger().info('Loading settings...');
await loadKeychainServiceAndSettings(KeychainServiceDriverMobile);
if (!Setting.value('clientId')) Setting.setValue('clientId', uuid.create());
if (Setting.value('firstStart')) {
let locale = NativeModules.I18nManager.localeIdentifier;
if (!locale) locale = defaultLocale();
Setting.setValue('locale', closestSupportedLocale(locale));
Setting.setValue('firstStart', 0);
}
if (Setting.value('db.ftsEnabled') === -1) {
const ftsEnabled = await db.ftsEnabled();
Setting.setValue('db.ftsEnabled', ftsEnabled ? 1 : 0);
reg.logger().info('db.ftsEnabled = ', Setting.value('db.ftsEnabled'));
}
if (Setting.value('env') === 'dev') {
Setting.setValue('welcome.enabled', false);
}
PluginAssetsLoader.instance().setLogger(mainLogger);
await PluginAssetsLoader.instance().importAssets();
// eslint-disable-next-line require-atomic-updates
BaseItem.revisionService_ = RevisionService.instance();
// Note: for now we hard-code the folder sort order as we need to
// create a UI to allow customisation (started in branch mobile_add_sidebar_buttons)
Setting.setValue('folders.sortOrder.field', 'title');
Setting.setValue('folders.sortOrder.reverse', false);
reg.logger().info(`Sync target: ${Setting.value('sync.target')}`);
setLocale(Setting.value('locale'));
// ----------------------------------------------------------------
// E2EE SETUP
// ----------------------------------------------------------------
EncryptionService.fsDriver_ = fsDriver;
EncryptionService.instance().setLogger(mainLogger);
// eslint-disable-next-line require-atomic-updates
BaseItem.encryptionService_ = EncryptionService.instance();
DecryptionWorker.instance().dispatch = dispatch;
DecryptionWorker.instance().setLogger(mainLogger);
DecryptionWorker.instance().setKvStore(KvStore.instance());
DecryptionWorker.instance().setEncryptionService(EncryptionService.instance());
await EncryptionService.instance().loadMasterKeysFromSettings();
DecryptionWorker.instance().on('resourceMetadataButNotBlobDecrypted', decryptionWorker_resourceMetadataButNotBlobDecrypted);
// ----------------------------------------------------------------
// / E2EE SETUP
// ----------------------------------------------------------------
reg.logger().info('Loading folders...');
await FoldersScreenUtils.refreshFolders();
const tags = await Tag.allWithNotes();
dispatch({
type: 'TAG_UPDATE_ALL',
items: tags,
});
const masterKeys = await MasterKey.all();
dispatch({
type: 'MASTERKEY_UPDATE_ALL',
items: masterKeys,
});
const folderId = Setting.value('activeFolderId');
let folder = await Folder.load(folderId);
if (!folder) folder = await Folder.defaultFolder();
dispatch({
type: 'FOLDER_SET_COLLAPSED_ALL',
ids: Setting.value('collapsedFolderIds'),
});
if (!folder) {
dispatch(DEFAULT_ROUTE);
} else {
dispatch({
type: 'NAV_GO',
routeName: 'Notes',
folderId: folder.id,
});
}
setUpQuickActions(dispatch, folderId);
} catch (error) {
alert(`Initialization error: ${error.message}`);
reg.logger().error('Initialization error:', error);
}
reg.setupRecurrentSync();
PoorManIntervals.setTimeout(() => {
AlarmService.garbageCollect();
}, 1000 * 60 * 60);
ResourceService.runInBackground();
ResourceFetcher.instance().setFileApi(() => { return reg.syncTarget().fileApi(); });
ResourceFetcher.instance().setLogger(reg.logger());
ResourceFetcher.instance().dispatch = dispatch;
ResourceFetcher.instance().on('downloadComplete', resourceFetcher_downloadComplete);
ResourceFetcher.instance().start();
SearchEngine.instance().setDb(reg.db());
SearchEngine.instance().setLogger(reg.logger());
SearchEngine.instance().scheduleSyncTables();
await MigrationService.instance().run();
// When the app starts we want the full sync to
// start almost immediately to get the latest data.
reg.scheduleSync(1000).then(() => {
// Wait for the first sync before updating the notifications, since synchronisation
// might change the notifications.
AlarmService.updateAllNotifications();
DecryptionWorker.instance().scheduleStart();
});
await WelcomeUtils.install(dispatch);
// Collect revisions more frequently on mobile because it doesn't auto-save
// and it cannot collect anything when the app is not active.
RevisionService.instance().runInBackground(1000 * 30);
const pluginRunner = new PluginRunner();
PluginService.instance().setLogger(reg.logger());
PluginService.instance().initialize({
joplin: {
workspace: {},
},
}, pluginRunner, store);
try {
if (await shim.fsDriver().exists(Setting.value('pluginDir'))) await PluginService.instance().loadAndRunPlugins(Setting.value('pluginDir'));
} catch (error) {
this.logger().error(`There was an error loading plugins from ${Setting.value('pluginDir')}:`, error);
}
try {
if (Setting.value('plugins.devPluginPaths')) {
const paths = Setting.value('plugins.devPluginPaths').split(',').map((p:string) => p.trim());
await PluginService.instance().loadAndRunPlugins(paths);
}
// Also load dev plugins that have passed via command line arguments
if (Setting.value('startupDevPlugins')) {
await PluginService.instance().loadAndRunPlugins(Setting.value('startupDevPlugins'));
}
} catch (error) {
this.logger().error(`There was an error loading plugins from ${Setting.value('plugins.devPluginPaths')}:`, error);
}
// const pluginString = `
// /* joplin-manifest:
// {
// "manifest_version": 1,
// "name": "JS Bundle test",
// "description": "JS Bundle Test plugin",
// "version": "1.0.0",
// "author": "Laurent Cozic",
// "homepage_url": "https://joplinapp.org"
// }
// */
// joplin.plugins.register({
// onStart: async function() {
// const folder = await joplin.data.post(['folders'], null, { title: "my plugin folder" });
// await joplin.data.post(['notes'], null, { parent_id: folder.id, title: "testing plugin!" });
// },
// });
// `;
// await shim.fsDriver().writeFile(Setting.value('pluginDir') + '/simple.js', pluginString, 'utf8');
// console.info(await shim.fsDriver().readFile(Setting.value('pluginDir') + '/simple.js', 'utf8'));
reg.logger().info('Application initialized');
}
class AppComponent extends React.Component {
constructor() {
super();
this.state = {
sideMenuContentOpacity: new Animated.Value(0),
};
this.lastSyncStarted_ = defaultState.syncStarted;
this.backButtonHandler_ = () => {
return this.backButtonHandler();
};
this.onAppStateChange_ = () => {
PoorManIntervals.update();
};
}
// 2020-10-08: It seems the initialisation code is quite fragile in general and should be kept simple.
// For example, adding a loading screen as was done in this commit: https://github.com/laurent22/joplin/commit/569355a3182bc12e50a54249882e3d68a72c2b28.
// had for effect that sharing with the app would create multiple instances of the app, thus breaking
// database access and so on. It's unclear why it happens and how to fix it but reverting that commit
// fixed the issue for now.
//
// Changing app launch mode doesn't help.
//
// It's possible that it's a bug in React Native, or perhaps the framework expects that the whole app can be
// mounted/unmounted or multiple ones can be running at the same time, but the app was not designed in this
// way.
//
// More reports and info about the multiple instance bug:
//
// https://github.com/laurent22/joplin/issues/3800
// https://github.com/laurent22/joplin/issues/3804
// https://github.com/laurent22/joplin/issues/3807
// https://discourse.joplinapp.org/t/webdav-config-encryption-config-randomly-lost-on-android/11364
// https://discourse.joplinapp.org/t/android-keeps-on-resetting-my-sync-and-theme/11443
async componentDidMount() {
if (this.props.appState == 'starting') {
this.props.dispatch({
type: 'APP_STATE_SET',
state: 'initializing',
});
await initialize(this.props.dispatch);
this.props.dispatch({
type: 'APP_STATE_SET',
state: 'ready',
});
}
BackButtonService.initialize(this.backButtonHandler_);
AlarmService.setInAppNotificationHandler(async (alarmId:string) => {
const alarm = await Alarm.load(alarmId);
const notification = await Alarm.makeNotification(alarm);
this.dropdownAlert_.alertWithType('info', notification.title, notification.body ? notification.body : '');
});
AppState.addEventListener('change', this.onAppStateChange_);
const sharedData = await ShareExtension.data();
if (sharedData) {
reg.logger().info('Received shared data');
if (this.props.selectedFolderId) {
handleShared(sharedData, this.props.selectedFolderId, this.props.dispatch);
} else {
reg.logger.info('Cannot handle share - default folder id is not set');
}
}
}
componentWillUnmount() {
AppState.removeEventListener('change', this.onAppStateChange_);
}
componentDidUpdate(prevProps:any) {
if (this.props.showSideMenu !== prevProps.showSideMenu) {
Animated.timing(this.state.sideMenuContentOpacity, {
toValue: this.props.showSideMenu ? 0.5 : 0,
duration: 600,
}).start();
}
}
async backButtonHandler() {
if (this.props.noteSelectionEnabled) {
this.props.dispatch({ type: 'NOTE_SELECTION_END' });
return true;
}
if (this.props.showSideMenu) {
this.props.dispatch({ type: 'SIDE_MENU_CLOSE' });
return true;
}
if (this.props.historyCanGoBack) {
this.props.dispatch({ type: 'NAV_BACK' });
return true;
}
BackHandler.exitApp();
return false;
}
UNSAFE_componentWillReceiveProps(newProps:any) {
if (newProps.syncStarted != this.lastSyncStarted_) {
if (!newProps.syncStarted) FoldersScreenUtils.refreshFolders();
this.lastSyncStarted_ = newProps.syncStarted;
}
}
sideMenu_change(isOpen:boolean) {
// Make sure showSideMenu property of state is updated
// when the menu is open/closed.
this.props.dispatch({
type: isOpen ? 'SIDE_MENU_OPEN' : 'SIDE_MENU_CLOSE',
});
}
render() {
if (this.props.appState != 'ready') return null;
const theme = themeStyle(this.props.themeId);
let sideMenuContent = null;
let menuPosition = 'left';
if (this.props.routeName === 'Note') {
sideMenuContent = <SafeAreaView style={{ flex: 1, backgroundColor: theme.backgroundColor }}><SideMenuContentNote options={this.props.noteSideMenuOptions}/></SafeAreaView>;
menuPosition = 'right';
} else {
sideMenuContent = <SafeAreaView style={{ flex: 1, backgroundColor: theme.backgroundColor }}><SideMenuContent/></SafeAreaView>;
}
const appNavInit = {
Notes: { screen: NotesScreen },
Note: { screen: NoteScreen },
Tags: { screen: TagsScreen },
Folder: { screen: FolderScreen },
OneDriveLogin: { screen: OneDriveLoginScreen },
DropboxLogin: { screen: DropboxLoginScreen },
EncryptionConfig: { screen: EncryptionConfigScreen },
UpgradeSyncTarget: { screen: UpgradeSyncTargetScreen },
Log: { screen: LogScreen },
Status: { screen: StatusScreen },
Search: { screen: SearchScreen },
Config: { screen: ConfigScreen },
};
const statusBarStyle = theme.appearance === 'light' ? 'dark-content' : 'light-content';
return (
<View style={{ flex: 1, backgroundColor: theme.backgroundColor }}>
<SideMenu
menu={sideMenuContent}
edgeHitWidth={5}
menuPosition={menuPosition}
onChange={(isOpen:boolean) => this.sideMenu_change(isOpen)}
onSliding={(percent:number) => {
this.props.dispatch({
type: 'SIDE_MENU_OPEN_PERCENT',
value: percent,
});
}}
>
<StatusBar barStyle={statusBarStyle} />
<MenuContext style={{ flex: 1, backgroundColor: theme.backgroundColor }}>
<SafeAreaView style={{ flex: 1 }}>
<View style={{ flex: 1, backgroundColor: theme.backgroundColor }}>
<AppNav screens={appNavInit} />
</View>
<DropdownAlert ref={(ref:any) => this.dropdownAlert_ = ref} tapToCloseEnabled={true} />
<Animated.View pointerEvents='none' style={{ position: 'absolute', backgroundColor: 'black', opacity: this.state.sideMenuContentOpacity, width: '100%', height: '120%' }}/>
</SafeAreaView>
</MenuContext>
</SideMenu>
</View>
);
}
}
const mapStateToProps = (state:AppState) => {
return {
historyCanGoBack: state.historyCanGoBack,
showSideMenu: state.showSideMenu,
syncStarted: state.syncStarted,
appState: state.appState,
noteSelectionEnabled: state.noteSelectionEnabled,
selectedFolderId: state.selectedFolderId,
routeName: state.route.routeName,
themeId: state.settings.theme,
noteSideMenuOptions: state.noteSideMenuOptions,
};
};
const App = connect(mapStateToProps)(AppComponent);
export default class Root extends React.Component {
render() {
return (
<Provider store={store}>
<App/>
</Provider>
);
}
}

View File

@@ -132,8 +132,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2097602
versionName "1.3.13"
versionCode 2097598
versionName "1.3.10"
ndk {
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}

View File

@@ -7,7 +7,7 @@
// So there's basically still a one way flux: React => SQLite => Redux => React
import { LogBox, AppRegistry } from 'react-native';
const { Root } = require('./root.js');
const Root = require('./Root').default;
// Seems JavaScript developers love adding warnings everywhere, even when these warnings can't be fixed
// or don't really matter. Because we want important warnings to actually be fixed, we disable

View File

@@ -338,13 +338,13 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 56;
CURRENT_PROJECT_VERSION = 55;
DEVELOPMENT_TEAM = A9BXAFS6CT;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Joplin/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 10.3.1;
MARKETING_VERSION = 10.3.0;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -365,12 +365,12 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 56;
CURRENT_PROJECT_VERSION = 55;
DEVELOPMENT_TEAM = A9BXAFS6CT;
INFOPLIST_FILE = Joplin/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 10.3.1;
MARKETING_VERSION = 10.3.0;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",

View File

@@ -3,9 +3,6 @@
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <UserNotifications/UserNotifications.h>
#import <RNCPushNotificationIOS.h>
#import "RNQuickActionManager.h"
#ifdef FB_SONARKIT_ENABLED
#import <FlipperKit/FlipperClient.h>
@@ -14,6 +11,9 @@
#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>
#import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>
#import <FlipperKitReactPlugin/FlipperKitReactPlugin.h>
#import "RNQuickActionManager.h"
#import <UserNotifications/UserNotifications.h>
#import <RNCPushNotificationIOS.h>
static void InitializeFlipper(UIApplication *application) {
FlipperClient *client = [FlipperClient sharedClient];

View File

@@ -45,29 +45,12 @@
<string>To add geo-location information to a note. Can be disabled in app.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>To add geo-location information to a note. Can be disabled in app.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>The images will be displayed on your notes.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>To allow attaching images to a note</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>The images will be displayed on your notes.</string>
<key>UIAppFonts</key>
<array>
<string>AntDesign.ttf</string>
<string>Entypo.ttf</string>
<string>EvilIcons.ttf</string>
<string>Feather.ttf</string>
<string>FontAwesome.ttf</string>
<string>FontAwesome5_Brands.ttf</string>
<string>FontAwesome5_Regular.ttf</string>
<string>FontAwesome5_Solid.ttf</string>
<string>Foundation.ttf</string>
<string>Ionicons.ttf</string>
<string>MaterialIcons.ttf</string>
<string>MaterialCommunityIcons.ttf</string>
<string>SimpleLineIcons.ttf</string>
<string>Octicons.ttf</string>
<string>Zocial.ttf</string>
<string>Fontisto.ttf</string>
</array>
<array/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
@@ -91,5 +74,24 @@
<string>Light</string>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>UIAppFonts</key>
<array>
<string>AntDesign.ttf</string>
<string>Entypo.ttf</string>
<string>EvilIcons.ttf</string>
<string>Feather.ttf</string>
<string>FontAwesome.ttf</string>
<string>FontAwesome5_Brands.ttf</string>
<string>FontAwesome5_Regular.ttf</string>
<string>FontAwesome5_Solid.ttf</string>
<string>Foundation.ttf</string>
<string>Ionicons.ttf</string>
<string>MaterialIcons.ttf</string>
<string>MaterialCommunityIcons.ttf</string>
<string>SimpleLineIcons.ttf</string>
<string>Octicons.ttf</string>
<string>Zocial.ttf</string>
<string>Fontisto.ttf</string>
</array>
</dict>
</plist>

View File

@@ -21,8 +21,6 @@ export default function useOnMessage(onCheckboxChange:Function, noteBody:string,
onJoplinLinkClick(msg);
} else if (msg.startsWith('error:')) {
console.error(`Webview injected script error: ${msg}`);
} else {
onJoplinLinkClick(msg);
}
}, [onCheckboxChange, noteBody, onMarkForDownload, onJoplinLinkClick, onResourceLongPress]);
}

View File

@@ -116,7 +116,6 @@ export default function useSource(noteBody:string, noteMarkupLanguage:number, th
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
${assetsToHeaders(result.pluginAssets, { asHtml: true })}
</head>

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

@@ -51,13 +51,6 @@ shared.renderFolders = function(props, renderItem) {
shared.renderTags = function(props, renderItem) {
const tags = props.tags.slice();
tags.sort((a, b) => {
// It seems title can sometimes be undefined (perhaps when syncing
// and before tag has been decrypted?). It would be best to find
// the root cause but for now that will do.
//
// Fixes https://github.com/laurent22/joplin/issues/4051
if (!a || !a.title || !b || !b.title) return 0;
// Note: while newly created tags are normalized and lowercase
// imported tags might be any case, so we need to do case-insensitive
// sort.

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.',

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;
}
}

View File

@@ -198,7 +198,7 @@ export default class InteropService {
if (moduleMetadata.isCustom) {
output = this.newModuleFromCustomFactory(moduleMetadata);
} else {
const ModuleClass = require(this.modulePath(moduleMetadata)).default;
const ModuleClass = shim.require(this.modulePath(moduleMetadata)).default;
output = new ModuleClass();
}
@@ -231,7 +231,7 @@ export default class InteropService {
if (moduleMetadata.isCustom) {
output = this.newModuleFromCustomFactory(moduleMetadata);
} else {
const ModuleClass = require(modulePath).default;
const ModuleClass = shim.require(modulePath).default;
output = new ModuleClass();
}

View File

@@ -1,5 +1,6 @@
/* eslint @typescript-eslint/no-unused-vars: 0, no-unused-vars: 0 */
import shim from 'lib/shim';
import { ImportExportResult } from './types';
const Setting = require('lib/models/Setting').default;
@@ -28,7 +29,7 @@ export default class InteropService_Importer_Base {
async temporaryDirectory_(createIt:boolean) {
const md5 = require('md5');
const tempDir = `${Setting.value('tempDir')}/${md5(Math.random() + Date.now())}`;
if (createIt) await require('fs-extra').mkdirp(tempDir);
if (createIt) await shim.fsDriver().mkdir(tempDir);
return tempDir;
}
}

View File

@@ -1,9 +1,9 @@
import shim from 'lib/shim';
import { ImportExportResult } from './types';
const InteropService_Importer_Base = require('lib/services/interop/InteropService_Importer_Base').default;
const InteropService_Importer_Raw = require('lib/services/interop/InteropService_Importer_Raw').default;
const { filename } = require('lib/path-utils');
const fs = require('fs-extra');
export default class InteropService_Importer_Jex extends InteropService_Importer_Base {
async exec(result:ImportExportResult) {
@@ -29,7 +29,7 @@ export default class InteropService_Importer_Jex extends InteropService_Importer
await importer.init(tempDir, this.options_);
result = await importer.exec(result);
await fs.remove(tempDir);
await shim.fsDriver().remove(tempDir);
return result;
}

View File

@@ -1,11 +1,7 @@
import Plugin from '../Plugin';
import Joplin from './Joplin';
import Logger from 'lib/Logger';
/**
* @ignore
*/
const builtinModules = require('builtin-modules');
import shim from 'lib/shim';
/**
* @ignore
@@ -43,7 +39,7 @@ export default class Global {
private requireWhiteList():string[] {
if (!this.requireWhiteList_) {
this.requireWhiteList_ = builtinModules.slice();
this.requireWhiteList_ = shim.builtinModules().slice();
this.requireWhiteList_.push('fs-extra');
}
return this.requireWhiteList_;
@@ -55,7 +51,7 @@ export default class Global {
require(filePath:string):any {
if (!this.requireWhiteList().includes(filePath)) throw new Error(`Path not allowed: ${filePath}`);
return require(filePath);
return shim.require(filePath);
}
// To get webpack to work with Node module we need to set the parameter `target: "node"`, however

View File

@@ -2,37 +2,27 @@ import CommandService, { CommandContext, CommandDeclaration, CommandRuntime } fr
import { Command } from './types';
/**
* This class allows executing or registering new Joplin commands. Commands
* can be executed or associated with
* {@link JoplinViewsToolbarButtons | toolbar buttons} or
* {@link JoplinViewsMenuItems | menu items}.
* This class allows executing or registering new Joplin commands. Commands can be executed or associated with
* {@link JoplinViewsToolbarButtons | toolbar buttons} or {@link JoplinViewsMenuItems | menu items}.
*
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/CliClient/tests/support/plugins/register_command)
*
* ## Executing Joplin's internal commands
*
* It is also possible to execute internal Joplin's commands which, as of
* now, are not well documented. You can find the list directly on GitHub
* though at the following locations:
* It is also possible to execute internal Joplin's commands which, as of now, are not well documented.
* You can find the list directly on GitHub though at the following locations:
*
* * [Main screen commands](https://github.com/laurent22/joplin/tree/dev/ElectronClient/gui/MainScreen/commands)
* * [Global commands](https://github.com/laurent22/joplin/tree/dev/ElectronClient/commands)
* * [Editor commands](https://github.com/laurent22/joplin/tree/dev/ElectronClient/gui/NoteEditor/commands/editorCommandDeclarations.ts)
*
* To view what arguments are supported, you can open any of these files
* and look at the `execute()` command.
* To view what arguments are supported, you can open any of these files and look at the `execute()` command.
*/
export default class JoplinCommands {
/**
* <span class="platform-desktop">desktop</span> Executes the given
* command.
*
* The command can take any number of arguments, and the supported
* arguments will vary based on the command. For custom commands, this
* is the `args` passed to the `execute()` function. For built-in
* commands, you can find the supported arguments by checking the links
* above.
* <span class="platform-desktop">desktop</span> Executes the given command.
* The `props` are the arguments passed to the command, and they vary based on the command
*
* ```typescript
* // Create a new note in the current notebook:

View File

@@ -1,6 +1,6 @@
import InteropService from 'lib/services/interop/InteropService';
import { Module, ModuleType } from 'lib/services/interop/types';
import { ExportModule, ImportModule } from './types';
// import InteropService from 'lib/services/interop/InteropService';
// import { Module, ModuleType } from 'lib/services/interop/types';
// import { ExportModule, ImportModule } from './types';
/**
* Provides a way to create modules to import external data into Joplin or to export notes into any arbitrary format.
@@ -16,26 +16,26 @@ import { ExportModule, ImportModule } from './types';
*/
export default class JoplinInterop {
async registerExportModule(module:ExportModule) {
const internalModule:Module = {
...module,
type: ModuleType.Exporter,
isCustom: true,
fileExtensions: module.fileExtensions ? module.fileExtensions : [],
};
async registerExportModule(_module:ExportModule) {
// const internalModule:Module = {
// ...module,
// type: ModuleType.Exporter,
// isCustom: true,
// fileExtensions: module.fileExtensions ? module.fileExtensions : [],
// };
return InteropService.instance().registerModule(internalModule);
// return InteropService.instance().registerModule(internalModule);
}
async registerImportModule(module:ImportModule) {
const internalModule:Module = {
...module,
type: ModuleType.Importer,
isCustom: true,
fileExtensions: module.fileExtensions ? module.fileExtensions : [],
};
async registerImportModule(_module:ImportModule) {
// const internalModule:Module = {
// ...module,
// type: ModuleType.Importer,
// isCustom: true,
// fileExtensions: module.fileExtensions ? module.fileExtensions : [],
// };
return InteropService.instance().registerModule(internalModule);
// return InteropService.instance().registerModule(internalModule);
}
}

View File

@@ -19,15 +19,22 @@ const md5 = require('md5');
const HtmlToMd = require('lib/HtmlToMd');
const urlUtils = require('lib/urlUtils.js');
const ArrayUtils = require('lib/ArrayUtils.js');
const { netUtils } = require('lib/net-utils');
// const { netUtils } = require('lib/net-utils');
const { fileExtension, safeFileExtension, safeFilename, filename } = require('lib/path-utils');
const ApiResponse = require('lib/services/rest/ApiResponse');
const SearchEngineUtils = require('lib/services/searchengine/SearchEngineUtils');
const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
const uri2path = require('file-uri-to-path');
// const uri2path = require('file-uri-to-path');
const { MarkupToHtml } = require('lib/joplin-renderer');
const { ErrorMethodNotAllowed, ErrorForbidden, ErrorBadRequest, ErrorNotFound } = require('./errors');
function mimeTypeFromHeaders(headers:any) {
if (!headers || !headers['content-type']) return null;
const splitted = headers['content-type'].split(';');
return splitted[0].trim().toLowerCase();
};
export default class Api {
private token_:string | Function;
@@ -510,38 +517,40 @@ export default class Api {
if (requestNote.body_html) {
if (requestNote.convert_to === 'html') {
const style = await this.buildNoteStyleSheet_(requestNote.stylesheets);
const minify = require('html-minifier').minify;
// const style = await this.buildNoteStyleSheet_(requestNote.stylesheets);
// const minify = require('html-minifier').minify;
const minifyOptions = {
// Remove all spaces and, especially, newlines from tag attributes, as that would
// break the rendering.
customAttrCollapse: /.*/,
// Need to remove all whitespaces because whitespace at a beginning of a line
// means a code block in Markdown.
collapseWhitespace: true,
minifyCSS: true,
maxLineLength: 300,
};
// const minifyOptions = {
// // Remove all spaces and, especially, newlines from tag attributes, as that would
// // break the rendering.
// customAttrCollapse: /.*/,
// // Need to remove all whitespaces because whitespace at a beginning of a line
// // means a code block in Markdown.
// collapseWhitespace: true,
// minifyCSS: true,
// maxLineLength: 300,
// };
const uglifycss = require('uglifycss');
const styleString = uglifycss.processString(style.join('\n'), {
// Need to set a max length because Ace Editor takes forever
// to display notes with long lines.
maxLineLen: 200,
});
// // const uglifycss = require('uglifycss');
// // const styleString = uglifycss.processString(style.join('\n'), {
// // // Need to set a max length because Ace Editor takes forever
// // // to display notes with long lines.
// // maxLineLen: 200,
// // });
const styleTag = style.length ? `<style>${styleString}</style>` + '\n' : '';
let minifiedHtml = '';
try {
minifiedHtml = minify(requestNote.body_html, minifyOptions);
} catch (error) {
console.warn('Could not minify HTML - using non-minified HTML instead', error);
minifiedHtml = requestNote.body_html;
}
output.body = styleTag + minifiedHtml;
output.body = htmlUtils.prependBaseUrl(output.body, baseUrl);
output.markup_language = MarkupToHtml.MARKUP_LANGUAGE_HTML;
// const styleString = style.join('\n'); // TODO
// const styleTag = style.length ? `<style>${styleString}</style>` + '\n' : '';
// let minifiedHtml = '';
// try {
// minifiedHtml = minify(requestNote.body_html, minifyOptions);
// } catch (error) {
// console.warn('Could not minify HTML - using non-minified HTML instead', error);
// minifiedHtml = requestNote.body_html;
// }
// output.body = styleTag + minifiedHtml;
// output.body = htmlUtils.prependBaseUrl(output.body, baseUrl);
// output.markup_language = MarkupToHtml.MARKUP_LANGUAGE_HTML;
} else {
// Convert to Markdown
// Parsing will not work if the HTML is not wrapped in a top level tag, which is not guaranteed
@@ -592,7 +601,7 @@ export default class Api {
}
async tryToGuessImageExtFromMimeType_(response:any, imagePath:string) {
const mimeType = netUtils.mimeTypeFromHeaders(response.headers);
const mimeType = mimeTypeFromHeaders(response.headers);
if (!mimeType) return imagePath;
const newExt = mimeUtils.toFileExtension(mimeType);
@@ -652,7 +661,7 @@ export default class Api {
} else if (urlUtils.urlProtocol(url).toLowerCase() === 'file:') {
// Can't think of any reason to disallow this at this point
// if (!allowFileProtocolImages) throw new Error('For security reasons, this URL with file:// protocol cannot be downloaded');
const localPath = uri2path(url);
const localPath = url; // TODO: uri2path(url);
await shim.fsDriver().copy(localPath, imagePath);
} else {
const response = await shim.fetchBlob(url, { path: imagePath, maxRetry: 1 });

View File

@@ -524,6 +524,14 @@ function shimInit() {
return timers.clearInterval(id);
};
shim.require = (filePath) => {
return require(filePath);
};
shim.builtinModules = () => {
return require('builtin-modules');
};
}
module.exports = { shimInit };

View File

@@ -273,6 +273,15 @@ const shim = {
clearInterval: (_id:any) => {
throw new Error('Not implemented');
},
require: (_filePath:string):any => {
// NOOP by default
return {};
},
builtinModules: ():string[] => {
return [];
},
};
export default shim;

File diff suppressed because it is too large Load Diff

View File

@@ -22,13 +22,17 @@
"color": "^3.1.2",
"diacritics": "^1.3.0",
"diff-match-patch": "^1.0.4",
"es6-promise-pool": "^2.5.0",
"events": "^1.1.1",
"file-uri-to-path": "^1.0.0",
"font-awesome-filetypes": "^2.1.0",
"form-data": "^2.1.4",
"highlight.js": "^10.2.1",
"html-entities": "^1.2.1",
"htmlparser2": "^4.1.0",
"immer": "^7.0.9",
"joplin-turndown": "^4.0.30",
"joplin-turndown-plugin-gfm": "^1.0.12",
"jsc-android": "241213.1.0",
"json-stringify-safe": "^5.0.1",
"katex": "^0.12.0",
@@ -85,6 +89,7 @@
"string-natural-compare": "^2.0.2",
"string-padding": "^1.0.2",
"timers": "^0.1.1",
"uglifycss": "0.0.29",
"url": "^0.11.0",
"url-parse": "^1.4.7",
"uslug": "git+https://github.com/laurent22/uslug.git#emoji-support",

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,127 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const BasePluginRunner_1 = require("lib/services/plugins/BasePluginRunner");
var PluginMessageTarget;
(function (PluginMessageTarget) {
PluginMessageTarget["MainWindow"] = "mainWindow";
PluginMessageTarget["Plugin"] = "plugin";
})(PluginMessageTarget || (PluginMessageTarget = {}));
let callbackIndex = 1;
const callbackPromises = {};
function mapEventIdsToHandlers(pluginId, arg) {
if (Array.isArray(arg)) {
for (let i = 0; i < arg.length; i++) {
arg[i] = mapEventIdsToHandlers(pluginId, arg[i]);
}
return arg;
}
else if (typeof arg === 'string' && arg.indexOf('___plugin_event_') === 0) {
const eventId = arg;
return (...args) => __awaiter(this, void 0, void 0, function* () {
const callbackId = `cb_${pluginId}_${Date.now()}_${callbackIndex++}`;
const promise = new Promise((resolve, reject) => {
callbackPromises[callbackId] = { resolve, reject };
});
// ipcRenderer.send('pluginMessage', {
// callbackId: callbackId,
// target: PluginMessageTarget.Plugin,
// pluginId: pluginId,
// eventId: eventId,
// args: args,
// });
return promise;
});
}
else if (arg === null) {
return null;
}
else if (arg === undefined) {
return undefined;
}
else if (typeof arg === 'object') {
for (const n in arg) {
arg[n] = mapEventIdsToHandlers(pluginId, arg[n]);
}
}
return arg;
}
class PluginRunner extends BasePluginRunner_1.default {
constructor() {
super();
this.eventHandlers_ = {};
this.eventHandler = this.eventHandler.bind(this);
}
eventHandler(eventHandlerId, args) {
return __awaiter(this, void 0, void 0, function* () {
const cb = this.eventHandlers_[eventHandlerId];
return cb(...args);
});
}
run(plugin, _pluginApi) {
return __awaiter(this, void 0, void 0, function* () {
console.info('RUNNING', plugin);
// const scriptPath = `${Setting.value('tempDir')}/plugin_${plugin.id}.js`;
// await shim.fsDriver().writeFile(scriptPath, plugin.scriptText, 'utf8');
// const pluginWindow = bridge().newBrowserWindow({
// show: false,
// webPreferences: {
// nodeIntegration: true,
// },
// });
// bridge().electronApp().registerPluginWindow(plugin.id, pluginWindow);
// pluginWindow.loadURL(`${require('url').format({
// pathname: require('path').join(__dirname, 'plugin_index.html'),
// protocol: 'file:',
// slashes: true,
// })}?pluginId=${encodeURIComponent(plugin.id)}&pluginScript=${encodeURIComponent(`file://${scriptPath}`)}`);
// pluginWindow.webContents.once('dom-ready', () => {
// pluginWindow.webContents.openDevTools();
// });
// ipcRenderer.on('pluginMessage', async (_event:any, message:PluginMessage) => {
// if (message.target !== PluginMessageTarget.MainWindow) return;
// if (message.pluginId !== plugin.id) return;
// if (message.mainWindowCallbackId) {
// const promise = callbackPromises[message.mainWindowCallbackId];
// if (!promise) {
// console.error('Got a callback without matching promise: ', message);
// return;
// }
// if (message.error) {
// promise.reject(message.error);
// } else {
// promise.resolve(message.result);
// }
// } else {
// const mappedArgs = mapEventIdsToHandlers(plugin.id, message.args);
// const fullPath = `joplin.${message.path}`;
// this.logger().debug(`PluginRunner: execute call: ${fullPath}: ${mappedArgs}`);
// let result:any = null;
// let error:any = null;
// try {
// result = await executeSandboxCall(plugin.id, pluginApi, fullPath, mappedArgs, this.eventHandler);
// } catch (e) {
// error = e ? e : new Error('Unknown error');
// }
// ipcRenderer.send('pluginMessage', {
// target: PluginMessageTarget.Plugin,
// pluginId: plugin.id,
// pluginCallbackId: message.callbackId,
// result: result,
// error: error,
// });
// }
// });
});
}
}
exports.default = PluginRunner;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUGx1Z2luUnVubmVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiUGx1Z2luUnVubmVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7O0FBQ0EsNEVBQXFFO0FBT3JFLElBQUssbUJBR0o7QUFIRCxXQUFLLG1CQUFtQjtJQUN2QixnREFBeUIsQ0FBQTtJQUN6Qix3Q0FBaUIsQ0FBQTtBQUNsQixDQUFDLEVBSEksbUJBQW1CLEtBQW5CLG1CQUFtQixRQUd2QjtBQWFELElBQUksYUFBYSxHQUFHLENBQUMsQ0FBQztBQUN0QixNQUFNLGdCQUFnQixHQUFPLEVBQUUsQ0FBQztBQUVoQyxTQUFTLHFCQUFxQixDQUFDLFFBQWUsRUFBRSxHQUFPO0lBQ3RELElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsRUFBRTtRQUN2QixLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsR0FBRyxDQUFDLE1BQU0sRUFBRSxDQUFDLEVBQUUsRUFBRTtZQUNwQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEdBQUcscUJBQXFCLENBQUMsUUFBUSxFQUFFLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1NBQ2pEO1FBQ0QsT0FBTyxHQUFHLENBQUM7S0FDWDtTQUFNLElBQUksT0FBTyxHQUFHLEtBQUssUUFBUSxJQUFJLEdBQUcsQ0FBQyxPQUFPLENBQUMsa0JBQWtCLENBQUMsS0FBSyxDQUFDLEVBQUU7UUFDNUUsTUFBTSxPQUFPLEdBQUcsR0FBRyxDQUFDO1FBRXBCLE9BQU8sQ0FBTyxHQUFHLElBQVUsRUFBRSxFQUFFO1lBQzlCLE1BQU0sVUFBVSxHQUFHLE1BQU0sUUFBUSxJQUFJLElBQUksQ0FBQyxHQUFHLEVBQUUsSUFBSSxhQUFhLEVBQUUsRUFBRSxDQUFDO1lBRXJFLE1BQU0sT0FBTyxHQUFHLElBQUksT0FBTyxDQUFDLENBQUMsT0FBTyxFQUFFLE1BQU0sRUFBRSxFQUFFO2dCQUMvQyxnQkFBZ0IsQ0FBQyxVQUFVLENBQUMsR0FBRyxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsQ0FBQztZQUNwRCxDQUFDLENBQUMsQ0FBQztZQUVILHNDQUFzQztZQUN0QywyQkFBMkI7WUFDM0IsdUNBQXVDO1lBQ3ZDLHVCQUF1QjtZQUN2QixxQkFBcUI7WUFDckIsZUFBZTtZQUNmLE1BQU07WUFFTixPQUFPLE9BQU8sQ0FBQztRQUNoQixDQUFDLENBQUEsQ0FBQztLQUNGO1NBQU0sSUFBSSxHQUFHLEtBQUssSUFBSSxFQUFFO1FBQ3hCLE9BQU8sSUFBSSxDQUFDO0tBQ1o7U0FBTSxJQUFJLEdBQUcsS0FBSyxTQUFTLEVBQUU7UUFDN0IsT0FBTyxTQUFTLENBQUM7S0FDakI7U0FBTSxJQUFJLE9BQU8sR0FBRyxLQUFLLFFBQVEsRUFBRTtRQUNuQyxLQUFLLE1BQU0sQ0FBQyxJQUFJLEdBQUcsRUFBRTtZQUNwQixHQUFHLENBQUMsQ0FBQyxDQUFDLEdBQUcscUJBQXFCLENBQUMsUUFBUSxFQUFFLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1NBQ2pEO0tBQ0Q7SUFFRCxPQUFPLEdBQUcsQ0FBQztBQUNaLENBQUM7QUFFRCxNQUFxQixZQUFhLFNBQVEsMEJBQWdCO0lBSXpEO1FBQ0MsS0FBSyxFQUFFLENBQUM7UUFIQyxtQkFBYyxHQUFpQixFQUFFLENBQUM7UUFLM0MsSUFBSSxDQUFDLFlBQVksR0FBRyxJQUFJLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUNsRCxDQUFDO0lBRWEsWUFBWSxDQUFDLGNBQXFCLEVBQUUsSUFBVTs7WUFDM0QsTUFBTSxFQUFFLEdBQUcsSUFBSSxDQUFDLGNBQWMsQ0FBQyxjQUFjLENBQUMsQ0FBQztZQUMvQyxPQUFPLEVBQUUsQ0FBQyxHQUFHLElBQUksQ0FBQyxDQUFDO1FBQ3BCLENBQUM7S0FBQTtJQUVLLEdBQUcsQ0FBQyxNQUFhLEVBQUUsVUFBaUI7O1lBQ3pDLE9BQU8sQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFLE1BQU0sQ0FBQyxDQUFDO1lBRWhDLDJFQUEyRTtZQUMzRSwwRUFBMEU7WUFFMUUsbURBQW1EO1lBQ25ELGdCQUFnQjtZQUNoQixxQkFBcUI7WUFDckIsMkJBQTJCO1lBQzNCLE1BQU07WUFDTixNQUFNO1lBRU4sd0VBQXdFO1lBRXhFLGtEQUFrRDtZQUNsRCxtRUFBbUU7WUFDbkUsc0JBQXNCO1lBQ3RCLGtCQUFrQjtZQUNsQiw4R0FBOEc7WUFFOUcscURBQXFEO1lBQ3JELDRDQUE0QztZQUM1QyxNQUFNO1lBRU4saUZBQWlGO1lBQ2pGLGtFQUFrRTtZQUNsRSwrQ0FBK0M7WUFFL0MsdUNBQXVDO1lBQ3ZDLG9FQUFvRTtZQUVwRSxvQkFBb0I7WUFDcEIsMEVBQTBFO1lBQzFFLGFBQWE7WUFDYixNQUFNO1lBRU4seUJBQXlCO1lBQ3pCLG9DQUFvQztZQUNwQyxhQUFhO1lBQ2Isc0NBQXNDO1lBQ3RDLE1BQU07WUFDTixZQUFZO1lBQ1osdUVBQXVFO1lBQ3ZFLCtDQUErQztZQUUvQyxtRkFBbUY7WUFFbkYsMkJBQTJCO1lBQzNCLDBCQUEwQjtZQUMxQixVQUFVO1lBQ1YsdUdBQXVHO1lBQ3ZHLGtCQUFrQjtZQUNsQixpREFBaUQ7WUFDakQsTUFBTTtZQUVOLHdDQUF3QztZQUN4Qyx5Q0FBeUM7WUFDekMsMEJBQTBCO1lBQzFCLDJDQUEyQztZQUMzQyxxQkFBcUI7WUFDckIsbUJBQW1CO1lBQ25CLFFBQVE7WUFDUixLQUFLO1lBQ0wsTUFBTTtRQUNQLENBQUM7S0FBQTtDQUVEO0FBbEZELCtCQWtGQyJ9

View File

@@ -0,0 +1,149 @@
import Plugin from 'lib/services/plugins/Plugin';
import BasePluginRunner from 'lib/services/plugins/BasePluginRunner';
import executeSandboxCall from 'lib/services/plugins/utils/executeSandboxCall';
import Global from 'lib/services/plugins/api/Global';
import Setting from 'lib/models/Setting';
import { EventHandlers } from 'lib/services/plugins/utils/mapEventHandlersToIds';
import shim from 'lib/shim';
enum PluginMessageTarget {
MainWindow = 'mainWindow',
Plugin = 'plugin',
}
export interface PluginMessage {
target: PluginMessageTarget,
pluginId: string,
callbackId?: string,
path?: string,
args?: any[],
result?: any,
error?: any,
mainWindowCallbackId?: string,
}
let callbackIndex = 1;
const callbackPromises:any = {};
function mapEventIdsToHandlers(pluginId:string, arg:any) {
if (Array.isArray(arg)) {
for (let i = 0; i < arg.length; i++) {
arg[i] = mapEventIdsToHandlers(pluginId, arg[i]);
}
return arg;
} else if (typeof arg === 'string' && arg.indexOf('___plugin_event_') === 0) {
const eventId = arg;
return async (...args:any[]) => {
const callbackId = `cb_${pluginId}_${Date.now()}_${callbackIndex++}`;
const promise = new Promise((resolve, reject) => {
callbackPromises[callbackId] = { resolve, reject };
});
// ipcRenderer.send('pluginMessage', {
// callbackId: callbackId,
// target: PluginMessageTarget.Plugin,
// pluginId: pluginId,
// eventId: eventId,
// args: args,
// });
return promise;
};
} else if (arg === null) {
return null;
} else if (arg === undefined) {
return undefined;
} else if (typeof arg === 'object') {
for (const n in arg) {
arg[n] = mapEventIdsToHandlers(pluginId, arg[n]);
}
}
return arg;
}
export default class PluginRunner extends BasePluginRunner {
protected eventHandlers_:EventHandlers = {};
constructor() {
super();
this.eventHandler = this.eventHandler.bind(this);
}
private async eventHandler(eventHandlerId:string, args:any[]) {
const cb = this.eventHandlers_[eventHandlerId];
return cb(...args);
}
async run(plugin:Plugin, _pluginApi:Global) {
console.info('RUNNING', plugin);
// const scriptPath = `${Setting.value('tempDir')}/plugin_${plugin.id}.js`;
// await shim.fsDriver().writeFile(scriptPath, plugin.scriptText, 'utf8');
// const pluginWindow = bridge().newBrowserWindow({
// show: false,
// webPreferences: {
// nodeIntegration: true,
// },
// });
// bridge().electronApp().registerPluginWindow(plugin.id, pluginWindow);
// pluginWindow.loadURL(`${require('url').format({
// pathname: require('path').join(__dirname, 'plugin_index.html'),
// protocol: 'file:',
// slashes: true,
// })}?pluginId=${encodeURIComponent(plugin.id)}&pluginScript=${encodeURIComponent(`file://${scriptPath}`)}`);
// pluginWindow.webContents.once('dom-ready', () => {
// pluginWindow.webContents.openDevTools();
// });
// ipcRenderer.on('pluginMessage', async (_event:any, message:PluginMessage) => {
// if (message.target !== PluginMessageTarget.MainWindow) return;
// if (message.pluginId !== plugin.id) return;
// if (message.mainWindowCallbackId) {
// const promise = callbackPromises[message.mainWindowCallbackId];
// if (!promise) {
// console.error('Got a callback without matching promise: ', message);
// return;
// }
// if (message.error) {
// promise.reject(message.error);
// } else {
// promise.resolve(message.result);
// }
// } else {
// const mappedArgs = mapEventIdsToHandlers(plugin.id, message.args);
// const fullPath = `joplin.${message.path}`;
// this.logger().debug(`PluginRunner: execute call: ${fullPath}: ${mappedArgs}`);
// let result:any = null;
// let error:any = null;
// try {
// result = await executeSandboxCall(plugin.id, pluginApi, fullPath, mappedArgs, this.eventHandler);
// } catch (e) {
// error = e ? e : new Error('Unknown error');
// }
// ipcRenderer.send('pluginMessage', {
// target: PluginMessageTarget.Plugin,
// pluginId: plugin.id,
// pluginCallbackId: message.callbackId,
// result: result,
// error: error,
// });
// }
// });
}
}

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

@@ -18,11 +18,6 @@
"clean": "gulp clean",
"postinstall": "cd Tools && npm i && cd .. && npm run clean && cd ReactNativeClient && npm i && cd .. && cd ElectronClient && npm i && cd .. && cd CliClient && npm i && cd .. && gulp build"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/laurent22/joplin.git"