1
0
mirror of https://github.com/laurent22/joplin.git synced 2026-01-08 00:14:28 +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
108 changed files with 2915 additions and 1839 deletions

View File

@@ -148,10 +148,8 @@ ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useListIdent.js
ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useScrollUtils.js
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/styles/index.js
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/utils/setupContextMenu.js
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/utils/useScroll.js
ElectronClient/gui/NoteEditor/NoteEditor.js
ElectronClient/gui/NoteEditor/NoteTitle/NoteTitleBar.js
ElectronClient/gui/NoteEditor/styles/index.js
ElectronClient/gui/NoteEditor/utils/contextMenu.js
ElectronClient/gui/NoteEditor/utils/index.js
@@ -208,7 +206,6 @@ ElectronClient/services/plugins/PluginRunner.js
ElectronClient/services/plugins/UserWebview.js
ElectronClient/services/plugins/UserWebviewDialog.js
ElectronClient/services/plugins/UserWebviewDialogButtonBar.js
ElectronClient/services/spellChecker/SpellCheckerServiceDriverNative.js
ReactNativeClient/lib/AsyncActionQueue.js
ReactNativeClient/lib/BaseApplication.js
ReactNativeClient/lib/checkPermissions.js
@@ -332,8 +329,6 @@ ReactNativeClient/lib/services/rest/errors.js
ReactNativeClient/lib/services/searchengine/filterParser.js
ReactNativeClient/lib/services/searchengine/queryBuilder.js
ReactNativeClient/lib/services/SettingUtils.js
ReactNativeClient/lib/services/spellChecker/SpellCheckerService.js
ReactNativeClient/lib/services/spellChecker/SpellCheckerServiceDriverBase.js
ReactNativeClient/lib/services/synchronizer/gui/useSyncTargetUpgrade.js
ReactNativeClient/lib/services/synchronizer/LockHandler.js
ReactNativeClient/lib/services/synchronizer/MigrationHandler.js
@@ -356,7 +351,6 @@ ReactNativeClient/lib/themes/oledDark.js
ReactNativeClient/lib/themes/solarizedDark.js
ReactNativeClient/lib/themes/solarizedLight.js
ReactNativeClient/lib/themes/type.js
ReactNativeClient/lib/time.js
ReactNativeClient/lib/uuid.js
ReactNativeClient/lib/versionInfo.js
ReactNativeClient/PluginAssetsLoader.js

6
.gitignore vendored
View File

@@ -142,10 +142,8 @@ ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useListIdent.js
ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useScrollUtils.js
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/styles/index.js
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/utils/setupContextMenu.js
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/utils/useScroll.js
ElectronClient/gui/NoteEditor/NoteEditor.js
ElectronClient/gui/NoteEditor/NoteTitle/NoteTitleBar.js
ElectronClient/gui/NoteEditor/styles/index.js
ElectronClient/gui/NoteEditor/utils/contextMenu.js
ElectronClient/gui/NoteEditor/utils/index.js
@@ -202,7 +200,6 @@ ElectronClient/services/plugins/PluginRunner.js
ElectronClient/services/plugins/UserWebview.js
ElectronClient/services/plugins/UserWebviewDialog.js
ElectronClient/services/plugins/UserWebviewDialogButtonBar.js
ElectronClient/services/spellChecker/SpellCheckerServiceDriverNative.js
ReactNativeClient/lib/AsyncActionQueue.js
ReactNativeClient/lib/BaseApplication.js
ReactNativeClient/lib/checkPermissions.js
@@ -326,8 +323,6 @@ ReactNativeClient/lib/services/rest/errors.js
ReactNativeClient/lib/services/searchengine/filterParser.js
ReactNativeClient/lib/services/searchengine/queryBuilder.js
ReactNativeClient/lib/services/SettingUtils.js
ReactNativeClient/lib/services/spellChecker/SpellCheckerService.js
ReactNativeClient/lib/services/spellChecker/SpellCheckerServiceDriverBase.js
ReactNativeClient/lib/services/synchronizer/gui/useSyncTargetUpgrade.js
ReactNativeClient/lib/services/synchronizer/LockHandler.js
ReactNativeClient/lib/services/synchronizer/MigrationHandler.js
@@ -350,7 +345,6 @@ ReactNativeClient/lib/themes/oledDark.js
ReactNativeClient/lib/themes/solarizedDark.js
ReactNativeClient/lib/themes/solarizedLight.js
ReactNativeClient/lib/themes/type.js
ReactNativeClient/lib/time.js
ReactNativeClient/lib/uuid.js
ReactNativeClient/lib/versionInfo.js
ReactNativeClient/PluginAssetsLoader.js

View File

@@ -91,10 +91,8 @@ ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useListIdent.js
ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useScrollUtils.js
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/styles/index.js
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/utils/setupContextMenu.js
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/utils/useScroll.js
ElectronClient/gui/NoteEditor/NoteEditor.js
ElectronClient/gui/NoteEditor/NoteTitle/NoteTitleBar.js
ElectronClient/gui/NoteEditor/styles/index.js
ElectronClient/gui/NoteEditor/utils/contextMenu.js
ElectronClient/gui/NoteEditor/utils/index.js
@@ -151,7 +149,6 @@ ElectronClient/services/plugins/PluginRunner.js
ElectronClient/services/plugins/UserWebview.js
ElectronClient/services/plugins/UserWebviewDialog.js
ElectronClient/services/plugins/UserWebviewDialogButtonBar.js
ElectronClient/services/spellChecker/SpellCheckerServiceDriverNative.js
ReactNativeClient/lib/AsyncActionQueue.js
ReactNativeClient/lib/BaseApplication.js
ReactNativeClient/lib/checkPermissions.js
@@ -275,8 +272,6 @@ ReactNativeClient/lib/services/rest/errors.js
ReactNativeClient/lib/services/searchengine/filterParser.js
ReactNativeClient/lib/services/searchengine/queryBuilder.js
ReactNativeClient/lib/services/SettingUtils.js
ReactNativeClient/lib/services/spellChecker/SpellCheckerService.js
ReactNativeClient/lib/services/spellChecker/SpellCheckerServiceDriverBase.js
ReactNativeClient/lib/services/synchronizer/gui/useSyncTargetUpgrade.js
ReactNativeClient/lib/services/synchronizer/LockHandler.js
ReactNativeClient/lib/services/synchronizer/MigrationHandler.js
@@ -299,7 +294,6 @@ ReactNativeClient/lib/themes/oledDark.js
ReactNativeClient/lib/themes/solarizedDark.js
ReactNativeClient/lib/themes/solarizedLight.js
ReactNativeClient/lib/themes/type.js
ReactNativeClient/lib/time.js
ReactNativeClient/lib/uuid.js
ReactNativeClient/lib/versionInfo.js
ReactNativeClient/PluginAssetsLoader.js

View File

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

View File

@@ -3,7 +3,7 @@ const { app } = require('./app.js');
const { _ } = require('lib/locale');
const BaseModel = require('lib/BaseModel.js');
const Note = require('lib/models/Note.js');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
class Command extends BaseCommand {
usage() {

View File

@@ -6,7 +6,7 @@ const Folder = require('lib/models/Folder.js');
const Setting = require('lib/models/Setting').default;
const Note = require('lib/models/Note.js');
const { sprintf } = require('sprintf-js');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const { cliUtils } = require('./cli-utils.js');
class Command extends BaseCommand {

View File

@@ -3,7 +3,7 @@ const { app } = require('./app.js');
const { _ } = require('lib/locale');
const Tag = require('lib/models/Tag.js');
const BaseModel = require('lib/BaseModel.js');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
class Command extends BaseCommand {
usage() {

View File

@@ -3,7 +3,7 @@ const { app } = require('./app.js');
const { _ } = require('lib/locale');
const BaseModel = require('lib/BaseModel.js');
const Note = require('lib/models/Note.js');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
class Command extends BaseCommand {
usage() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ const Setting = require('lib/models/Setting').default;
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
let testApp = null;

View File

@@ -5,7 +5,7 @@ const Setting = require('lib/models/Setting').default;
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const { ALL_NOTES_FILTER_ID } = require('lib/reserved-ids.js');
//

View File

@@ -5,7 +5,7 @@ const Setting = require('lib/models/Setting').default;
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
let testApp = null;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
require('app-module-path').addPath(__dirname);
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, asyncTest, db, synchronizer, fileApi, sleep, createNTestNotes, switchClient, createNTestFolders } = require('test-utils.js');
const SearchEngine = require('lib/services/searchengine/SearchEngine');
const Note = require('lib/models/Note');

View File

@@ -3,7 +3,7 @@
// require('app-module-path').addPath(__dirname);
// const time = require('lib/time').default;
// const { time } = require('lib/time-utils.js');
// const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, asyncTest, db, synchronizer, fileApi, sleep, createNTestNotes, switchClient, createNTestFolders } = require('test-utils.js');
// const SearchEngine = require('lib/services/searchengine/SearchEngine');
// const Note = require('lib/models/Note');

View File

@@ -2,7 +2,7 @@
require('app-module-path').addPath(__dirname);
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const { setupDatabase, synchronizerStart, syncTargetName, allSyncTargetItemsEncrypted, tempFilePath, resourceFetcher, kvStore, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, encryptionService, loadEncryptionMasterKey, fileContentEqual, decryptionWorker, checkThrowAsync, asyncTest } = require('test-utils.js');
const shim = require('lib/shim').default;
const fs = require('fs-extra');

View File

@@ -25,7 +25,7 @@ const { FileApiDriverOneDrive } = require('lib/file-api-driver-onedrive.js');
const { FileApiDriverAmazonS3 } = require('lib/file-api-driver-amazon-s3.js');
const BaseService = require('lib/services/BaseService').default;
const FsDriverNode = require('lib/fs-driver-node').default;
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const { shimInit } = require('lib/shim-init-node.js');
const shim = require('lib/shim').default;
const uuid = require('lib/uuid').default;

View File

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

View File

@@ -86,7 +86,6 @@ export default class ElectronAppWrapper {
backgroundColor: '#fff', // required to enable sub pixel rendering, can't be in css
webPreferences: {
nodeIntegration: true,
spellcheck: true,
},
webviewTag: true,
// We start with a hidden window, which is then made visible depending on the showTrayIcon setting
@@ -98,6 +97,14 @@ export default class ElectronAppWrapper {
// Fix: https://github.com/electron-userland/electron-builder/issues/2269
if (shim.isLinux()) windowOptions.icon = path.join(__dirname, '..', 'build/icons/128x128.png');
require('electron-context-menu')({
shouldShowMenu: (_event:any, params:any) => {
// params.inputFieldType === 'none' when right-clicking the text editor. This is a bit of a hack to detect it because in this
// case we don't want to use the built-in context menu but a custom one.
return params.isEditable && params.inputFieldType !== 'none';
},
});
this.win_ = new BrowserWindow(windowOptions);
if (!screen.getDisplayMatching(this.win_.getBounds())) {

View File

@@ -8,7 +8,7 @@ const bridge = require('electron').remote.require('./bridge').default;
const Setting = require('lib/models/Setting').default;
const Note = require('lib/models/Note.js');
const { friendlySafeFilename } = require('lib/path-utils');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const md5 = require('md5');
const url = require('url');

View File

@@ -14,9 +14,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 SpellCheckerService from 'lib/services/spellChecker/SpellCheckerService';
import SpellCheckerServiceDriverNative from './services/spellChecker/SpellCheckerServiceDriverNative';
import bridge from './services/bridge';
require('app-module-path').addPath(__dirname);
@@ -31,6 +28,7 @@ const DecryptionWorker = require('lib/services/DecryptionWorker');
const ResourceService = require('lib/services/ResourceService');
const ClipperServer = require('lib/ClipperServer');
const ExternalEditWatcher = require('lib/services/ExternalEditWatcher');
const bridge = require('electron').remote.require('./bridge').default;
const { webFrame } = require('electron');
const Menu = bridge().Menu;
const PluginManager = require('lib/services/PluginManager');
@@ -453,31 +451,6 @@ class Application extends BaseApplication {
document.head.appendChild(styleTag);
}
setupContextMenu() {
// The context menu must be setup in renderer process because that's where
// the spell checker service lives.
require('electron-context-menu')({
shouldShowMenu: (_event:any, params:any) => {
// params.inputFieldType === 'none' when right-clicking the text editor. This is a bit of a hack to detect it because in this
// case we don't want to use the built-in context menu but a custom one.
return params.isEditable && params.inputFieldType !== 'none';
},
menu: (actions:any, props:any) => {
const spellCheckerMenuItems = SpellCheckerService.instance().contextMenuItems(props.misspelledWord, props.dictionarySuggestions);
const output = [
actions.cut(),
actions.copy(),
actions.paste(),
...spellCheckerMenuItems,
];
return output;
},
});
}
async loadCustomCss(filePath:string) {
let cssString = '';
if (await fs.pathExists(filePath)) {
@@ -711,10 +684,6 @@ class Application extends BaseApplication {
this.logger().error(`There was an error loading plugins from ${Setting.value('plugins.devPluginPaths')}:`, error);
}
this.setupContextMenu();
await SpellCheckerService.instance().initialize(new SpellCheckerServiceDriverNative());
// await populateDatabase(reg.db());
// setTimeout(() => {

View File

@@ -4,7 +4,7 @@ const Setting = require('lib/models/Setting').default;
const EncryptionService = require('lib/services/EncryptionService');
const { themeStyle } = require('lib/theme');
const { _ } = require('lib/locale');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const shim = require('lib/shim').default;
const dialogs = require('./dialogs');
const shared = require('lib/components/shared/encryption-config-shared.js');

View File

@@ -27,7 +27,7 @@ const bridge = require('electron').remote.require('./bridge').default;
const PluginManager = require('lib/services/PluginManager');
const EncryptionService = require('lib/services/EncryptionService');
const ipcRenderer = require('electron').ipcRenderer;
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const styled = require('styled-components').default;
const StyledUserWebviewDialogContainer = styled.div`

View File

@@ -3,7 +3,7 @@ import eventManager from 'lib/eventManager';
import { _ } from 'lib/locale';
import { stateUtils } from 'lib/reducer';
const Note = require('lib/models/Note');
const time = require('lib/time').default;
const { time } = require('lib/time-utils');
export const declaration:CommandDeclaration = {
name: 'editAlarm',

View File

@@ -14,7 +14,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 SpellCheckerService from 'lib/services/spellChecker/SpellCheckerService';
const { connect } = require('react-redux');
const { reg } = require('lib/registry.js');
@@ -85,8 +84,6 @@ interface Props {
showCompletedTodos: boolean,
pluginMenuItems: any[],
pluginMenus: any[],
['spellChecker.enabled']: boolean,
['spellChecker.language']: string,
}
const commandNames:string[] = [
@@ -374,8 +371,6 @@ function useMenu(props:Props) {
}
toolsItems = toolsItems.concat(toolsItemsAll);
toolsItems.push(SpellCheckerService.instance().spellCheckerConfigMenuItem(props['spellChecker.language'], props['spellChecker.enabled']));
function _checkForUpdates() {
bridge().checkForUpdates(false, bridge().window(), `${Setting.value('profileDir')}/log-autoupdater.txt`, { includePreReleases: Setting.value('autoUpdate.includePreReleases') });
}
@@ -812,7 +807,7 @@ function useMenu(props:Props) {
} else {
setMenu(Menu.buildFromTemplate(template));
}
}, [props.routeName, props.pluginMenuItems, props.pluginMenus, keymapLastChangeTime, modulesLastChangeTime, props['spellChecker.language'], props['spellChecker.enabled']]);
}, [props.routeName, props.pluginMenuItems, props.pluginMenus, keymapLastChangeTime, modulesLastChangeTime]);
useEffect(() => {
const whenClauseContext = CommandService.instance().currentWhenClauseContext();
@@ -907,8 +902,6 @@ const mapStateToProps = (state:AppState) => {
showCompletedTodos: state.settings.showCompletedTodos,
pluginMenuItems: stateUtils.selectArrayShallow({ array: pluginUtils.viewsByType(state.pluginService.plugins, 'menuItem') }, 'menuBar.pluginMenuItems'),
pluginMenus: stateUtils.selectArrayShallow({ array: pluginUtils.viewsByType(state.pluginService.plugins, 'menu') }, 'menuBar.pluginMenus'),
['spellChecker.language']: state.settings['spellChecker.language'],
['spellChecker.enabled']: state.settings['spellChecker.enabled'],
};
};

View File

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

View File

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

View File

@@ -4,6 +4,7 @@ import { ScrollOptions, ScrollOptionTypes, EditorCommand, NoteBodyEditorProps }
import { resourcesStatus, commandAttachFileToBody, handlePasteEvent } from '../../utils/resourceHandling';
import useScroll from './utils/useScroll';
import styles_ from './styles';
import { menuItems, ContextMenuOptions, ContextMenuItemType } from '../../utils/contextMenu';
import CommandService from 'lib/services/CommandService';
import { ToolbarButtonInfo } from 'lib/services/commands/ToolbarButtonUtils';
import ToggleEditorsButton, { Value as ToggleEditorsButtonValue } from '../../../ToggleEditorsButton/ToggleEditorsButton';
@@ -11,13 +12,13 @@ import ToolbarButton from '../../../../gui/ToolbarButton/ToolbarButton';
import usePluginServiceRegistration from '../../utils/usePluginServiceRegistration';
import { utils as pluginUtils } from 'lib/services/plugins/reducer';
import { _, closestSupportedLocale } from 'lib/locale';
import setupContextMenu from './utils/setupContextMenu';
const { MarkupToHtml } = require('lib/joplin-renderer');
const taboverride = require('taboverride');
const { reg } = require('lib/registry.js');
const BaseItem = require('lib/models/BaseItem');
const shim = require('lib/shim').default;
const Resource = require('lib/models/Resource');
const { themeStyle } = require('lib/theme');
const { clipboard } = require('electron');
const supportedLocales = require('./supportedLocales');
@@ -142,6 +143,8 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
const props_onDrop = useRef(null);
props_onDrop.current = props.onDrop;
const contextMenuActionOptions = useRef<ContextMenuOptions>(null);
const markupToHtml = useRef(null);
markupToHtml.current = props.markupToHtml;
@@ -507,7 +510,19 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
loadedCssFiles_ = [];
loadedJsFiles_ = [];
function contextMenuItemNameWithNamespace(name:string) {
// For unknown reasons, TinyMCE converts all context menu names to
// lowercase when setting them in the init method, so we need to
// make them lowercase too, to make sure that the update() method
// addContextMenu is triggered.
return (`joplin${name}`).toLowerCase();
}
const loadEditor = async () => {
const contextMenuItems = menuItems();
const contextMenuItemNames = [];
for (const name in contextMenuItems) contextMenuItemNames.push(contextMenuItemNameWithNamespace(name));
const language = closestSupportedLocale(props.locale, true, supportedLocales);
const pluginCommandNames:string[] = [];
@@ -549,8 +564,7 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
language: ['en_US', 'en_GB'].includes(language) ? undefined : language,
toolbar: toolbar.join(' '),
localization_function: _,
contextmenu: false,
browser_spellcheck: true,
contextmenu: contextMenuItemNames.join(' '),
setup: (editor:any) => {
function openEditDialog(editable:any) {
@@ -666,7 +680,51 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
});
}
setupContextMenu(editor);
for (const itemName in contextMenuItems) {
const item = contextMenuItems[itemName];
const itemNameNS = contextMenuItemNameWithNamespace(itemName);
editor.ui.registry.addMenuItem(itemNameNS, {
text: item.label,
onAction: () => {
item.onAction(contextMenuActionOptions.current);
},
});
editor.ui.registry.addContextMenu(itemNameNS, {
update: function(element:any) {
let itemType:ContextMenuItemType = ContextMenuItemType.None;
let resourceId = '';
let linkToCopy = null;
if (element.nodeName === 'IMG') {
itemType = ContextMenuItemType.Image;
resourceId = Resource.pathToId(element.src);
} else if (element.nodeName === 'A') {
resourceId = Resource.pathToId(element.href);
itemType = resourceId ? ContextMenuItemType.Resource : ContextMenuItemType.Link;
linkToCopy = element.getAttribute('href') || '';
} else {
itemType = ContextMenuItemType.Text;
}
contextMenuActionOptions.current = {
itemType,
resourceId,
linkToCopy,
textToCopy: null,
htmlToCopy: editor.selection ? editor.selection.getContent() : '',
insertContent: (content:string) => {
editor.insertContent(content);
},
isReadOnly: false,
};
return item.isActive(itemType, contextMenuActionOptions.current) ? itemNameNS : '';
},
});
}
// TODO: remove event on unmount?
editor.on('DblClick', (event:any) => {

View File

@@ -1,91 +0,0 @@
import SpellCheckerService from 'lib/services/spellChecker/SpellCheckerService';
import bridge from '../../../../../services/bridge';
import { menuItems, ContextMenuOptions, ContextMenuItemType } from '../../../utils/contextMenu';
const Resource = require('lib/models/Resource');
// x and y are the absolute coordinates, as returned by the context-menu event
// handler on the webContent. This function will return null if the point is
// not within the TinyMCE editor.
function contextMenuElement(editor:any, x:number, y:number) {
const iframes = document.getElementsByClassName('tox-edit-area__iframe');
if (!iframes.length) return null;
const iframeRect = iframes[0].getBoundingClientRect();
if (iframeRect.x < x && iframeRect.y < y && iframeRect.right > x && iframeRect.bottom > y) {
const relativeX = x - iframeRect.x;
const relativeY = y - iframeRect.y;
return editor.getDoc().elementFromPoint(relativeX, relativeY);
}
return null;
}
interface ContextMenuActionOptions {
current: ContextMenuOptions,
}
const contextMenuActionOptions:ContextMenuActionOptions = { current: null };
export default function(editor:any) {
const contextMenuItems = menuItems();
bridge().window().webContents.on('context-menu', (_event:any, params:any) => {
const element = contextMenuElement(editor, params.x, params.y);
if (!element) return;
const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem;
let itemType:ContextMenuItemType = ContextMenuItemType.None;
let resourceId = '';
let linkToCopy = null;
if (element.nodeName === 'IMG') {
itemType = ContextMenuItemType.Image;
resourceId = Resource.pathToId(element.src);
} else if (element.nodeName === 'A') {
resourceId = Resource.pathToId(element.href);
itemType = resourceId ? ContextMenuItemType.Resource : ContextMenuItemType.Link;
linkToCopy = element.getAttribute('href') || '';
} else {
itemType = ContextMenuItemType.Text;
}
contextMenuActionOptions.current = {
itemType,
resourceId,
linkToCopy,
textToCopy: null,
htmlToCopy: editor.selection ? editor.selection.getContent() : '',
insertContent: (content:string) => {
editor.insertContent(content);
},
isReadOnly: false,
};
const menu = new Menu();
for (const itemName in contextMenuItems) {
const item = contextMenuItems[itemName];
if (!item.isActive(itemType, contextMenuActionOptions.current)) continue;
menu.append(new MenuItem({
label: item.label,
click: () => {
item.onAction(contextMenuActionOptions.current);
},
}));
}
const spellCheckerMenuItems = SpellCheckerService.instance().contextMenuItems(params.misspelledWord, params.dictionarySuggestions);
for (const item of spellCheckerMenuItems) {
menu.append(item);
}
menu.popup();
});
}

View File

@@ -1,9 +1,11 @@
import * as React from 'react';
import { useState, useEffect, useCallback, useRef } from 'react';
// eslint-disable-next-line no-unused-vars
import TinyMCE from './NoteBody/TinyMCE/TinyMCE';
import CodeMirror from './NoteBody/CodeMirror/CodeMirror';
import { connect } from 'react-redux';
import MultiNoteActions from '../MultiNoteActions';
import NoteToolbar from '../NoteToolbar/NoteToolbar';
import { htmlToMarkdown, formNoteToNote } from './utils';
import useSearchMarkers from './utils/useSearchMarkers';
import useNoteSearchBar from './utils/useNoteSearchBar';
@@ -25,15 +27,15 @@ import ToolbarButtonUtils from 'lib/services/commands/ToolbarButtonUtils';
import { _ } from 'lib/locale';
import stateToWhenClauseContext from 'lib/services/commands/stateToWhenClauseContext';
import TagList from '../TagList';
import NoteTitleBar from './NoteTitle/NoteTitleBar';
import markupLanguageUtils from 'lib/markupLanguageUtils';
import usePrevious from 'lib/hooks/usePrevious';
import Setting from 'lib/models/Setting';
const { themeStyle } = require('lib/theme');
const { substrWithEllipsis } = require('lib/string-utils');
const NoteSearchBar = require('../NoteSearchBar.min.js');
const { reg } = require('lib/registry.js');
const { time } = require('lib/time-utils.js');
const markupLanguageUtils = require('lib/markupLanguageUtils').default;
const usePrevious = require('lib/hooks/usePrevious').default;
const Setting = require('lib/models/Setting').default;
const Note = require('lib/models/Note.js');
const bridge = require('electron').remote.require('./bridge').default;
const ExternalEditWatcher = require('lib/services/ExternalEditWatcher');
@@ -43,6 +45,10 @@ const commands = [
require('./commands/showRevisions'),
];
const toolbarStyle = {
marginBottom: 0,
};
const toolbarButtonUtils = new ToolbarButtonUtils(CommandService.instance());
function NoteEditor(props: NoteEditorProps) {
@@ -238,20 +244,20 @@ function NoteEditor(props: NoteEditorProps) {
const onTitleChange = useCallback((event: any) => onFieldChange('title', event.target.value), [onFieldChange]);
// const onTitleKeydown = useCallback((event:any) => {
// const keyCode = event.keyCode;
const onTitleKeydown = useCallback((event:any) => {
const keyCode = event.keyCode;
// if (keyCode === 9) {
// // TAB
// event.preventDefault();
if (keyCode === 9) {
// TAB
event.preventDefault();
// if (event.shiftKey) {
// CommandService.instance().execute('focusElement', 'noteList');
// } else {
// CommandService.instance().execute('focusElement', 'noteBody');
// }
// }
// }, [props.dispatch]);
if (event.shiftKey) {
CommandService.instance().execute('focusElement', 'noteList');
} else {
CommandService.instance().execute('focusElement', 'noteBody');
}
}
}, [props.dispatch]);
const onBodyWillChange = useCallback((event: any) => {
handleProvisionalFlag();
@@ -345,6 +351,14 @@ function NoteEditor(props: NoteEditorProps) {
return <div style={emptyDivStyle}></div>;
}
function renderNoteToolbar() {
return <NoteToolbar
themeId={props.themeId}
// note={formNote}
style={toolbarStyle}
/>;
}
function renderTagButton() {
return <ToolbarButton
themeId={props.themeId}
@@ -363,6 +377,26 @@ function NoteEditor(props: NoteEditorProps) {
);
}
function renderTitleBar() {
const theme = themeStyle(props.themeId);
const titleBarDate = <span style={styles.titleDate}>{time.formatMsToLocal(formNote.user_updated_time)}</span>;
return (
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', height: theme.topRowHeight }}>
<input
type="text"
ref={titleInputRef}
placeholder={props.isProvisional ? _('Creating new %s...', formNote.is_todo ? _('to-do') : _('note')) : ''}
style={styles.titleInput}
onChange={onTitleChange}
onKeyDown={onTitleKeydown}
value={formNote.title}
/>
{titleBarDate}
{renderNoteToolbar()}
</div>
);
}
const searchMarkers = useSearchMarkers(showLocalSearch, localSearchMarkerOptions, props.searches, props.selectedSearchId, props.highlightedWords);
const editorProps:NoteBodyEditorProps = {
@@ -512,15 +546,7 @@ function NoteEditor(props: NoteEditorProps) {
<div style={styles.root} onDrop={onDrop}>
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
{renderResourceWatchingNotification()}
<NoteTitleBar
titleInputRef={titleInputRef}
themeId={props.themeId}
isProvisional={props.isProvisional}
noteIsTodo={formNote.is_todo}
noteTitle={formNote.title}
noteUserUpdatedTime={formNote.user_updated_time}
onTitleChange={onTitleChange}
/>
{renderTitleBar()}
{renderSearchInfo()}
<div style={{ display: 'flex', flex: 1 }}>
{editor}

View File

@@ -1,98 +0,0 @@
import * as React from 'react';
import { _ } from 'lib/locale';
import CommandService from 'lib/services/CommandService';
import { ChangeEvent, useCallback } from 'react';
import NoteToolbar from '../../NoteToolbar/NoteToolbar';
import { buildStyle } from 'lib/theme';
import time from 'lib/time';
interface Props {
themeId: number,
noteUserUpdatedTime: number,
noteTitle: string,
noteIsTodo: number,
isProvisional: boolean,
titleInputRef: any,
onTitleChange(event: ChangeEvent<HTMLInputElement>):void,
}
function styles_(props: Props) {
return buildStyle(['NoteEditorTitleBar'], props.themeId, (theme: any) => {
return {
root: {
display: 'flex', flexDirection: 'row', alignItems: 'center', height: theme.topRowHeight,
},
titleInput: {
flex: 1,
display: 'inline-block',
paddingTop: 5,
minHeight: 35,
boxSizing: 'border-box',
fontWeight: 'bold',
paddingBottom: 5,
paddingLeft: 0,
paddingRight: 8,
marginLeft: 5,
color: theme.textStyle.color,
fontSize: Math.round(theme.textStyle.fontSize * 1.5),
backgroundColor: theme.backgroundColor,
border: 'none',
},
titleDate: {
...theme.textStyle,
color: theme.colorFaded,
paddingLeft: 10,
paddingRight: 10,
},
toolbarStyle: {
marginBottom: 0,
},
};
});
}
export default function NoteTitleBar(props:Props) {
const styles = styles_(props);
const onTitleKeydown = useCallback((event:any) => {
const keyCode = event.keyCode;
if (keyCode === 9) { // TAB
event.preventDefault();
if (event.shiftKey) {
CommandService.instance().execute('focusElement', 'noteList');
} else {
CommandService.instance().execute('focusElement', 'noteBody');
}
}
}, []);
function renderTitleBarDate() {
return <span style={styles.titleDate}>{time.formatMsToLocal(props.noteUserUpdatedTime)}</span>;
}
function renderNoteToolbar() {
return <NoteToolbar
themeId={props.themeId}
style={styles.toolbarStyle}
/>;
}
return (
<div style={styles.root}>
<input
type="text"
ref={props.titleInputRef}
placeholder={props.isProvisional ? _('Creating new %s...', props.noteIsTodo ? _('to-do') : _('note')) : ''}
style={styles.titleInput}
onChange={props.onTitleChange}
onKeyDown={onTitleKeydown}
value={props.noteTitle}
/>
{renderTitleBarDate()}
{renderNoteToolbar()}
</div>
);
}

View File

@@ -2,7 +2,7 @@ import { useEffect } from 'react';
import { FormNote, ScrollOptionTypes } from './types';
import editorCommandDeclarations from '../commands/editorCommandDeclarations';
import CommandService, { CommandDeclaration, CommandRuntime, CommandContext } from 'lib/services/CommandService';
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const { reg } = require('lib/registry.js');
const commandsWithDependencies = [

View File

@@ -5,7 +5,7 @@ import { _ } from 'lib/locale';
const { ItemList } = require('../ItemList.min.js');
const React = require('react');
const { connect } = require('react-redux');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const { themeStyle } = require('lib/theme');
const BaseModel = require('lib/BaseModel');
const bridge = require('electron').remote.require('./bridge').default;

View File

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

View File

@@ -11,7 +11,7 @@ const Setting = require('lib/models/Setting').default;
const RevisionService = require('lib/services/RevisionService');
const shared = require('lib/components/shared/note-screen-shared.js');
const { MarkupToHtml } = require('lib/joplin-renderer');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const ReactTooltip = require('react-tooltip');
const { urlDecode, substrWithEllipsis } = require('lib/string-utils');
const bridge = require('electron').remote.require('./bridge').default;

View File

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

View File

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

View File

@@ -1,39 +0,0 @@
// Provides spell checking feature via the native Electron built-in spell checker
import SpellCheckerServiceDriverBase from 'lib/services/spellChecker/SpellCheckerServiceDriverBase';
import bridge from '../bridge';
export default class SpellCheckerServiceDriverNative extends SpellCheckerServiceDriverBase {
private session():any {
return bridge().window().webContents.session;
}
public get availableLanguages():string[] {
return this.session().availableSpellCheckerLanguages;
}
// Language can be set to '' to disable spell-checking
public setLanguage(v:string) {
// If we pass an empty array, it disables spell checking
// https://github.com/electron/electron/issues/25228
this.session().setSpellCheckerLanguages(v ? [v] : []);
}
public get language():string {
const languages = this.session().getSpellCheckerLanguages();
return languages.length ? languages[0] : '';
}
public makeMenuItem(item:any):any {
const MenuItem = bridge().MenuItem;
return new MenuItem(item);
}
public addWordToSpellCheckerDictionary(_language:string, word:string) {
// Actually on Electron all languages share the same dictionary, or
// perhaps it's added to the currently active language.
this.session().addWordToSpellCheckerDictionary(word);
}
}

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

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

@@ -18,7 +18,7 @@ const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');
const { splitCommandString } = require('lib/string-utils.js');
const { reg } = require('lib/registry.js');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const BaseSyncTarget = require('lib/BaseSyncTarget.js');
const reduxSharedMiddleware = require('lib/components/shared/reduxSharedMiddleware');
const os = require('os');

View File

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

View File

@@ -1,7 +1,7 @@
const Logger = require('lib/Logger').default;
const shim = require('lib/shim').default;
const JoplinError = require('lib/JoplinError');
const time = require('lib/time').default;
const { time } = require('lib/time-utils');
const EventDispatcher = require('lib/EventDispatcher');
class DropboxApi {

View File

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

View File

@@ -6,7 +6,7 @@
// whenever the update() function is called, and in mobile it's called for
// example on the Redux action middleware or when the app gets focus.
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
type IntervalId = number;

View File

@@ -15,7 +15,7 @@ const ResourceLocalState = require('lib/models/ResourceLocalState.js');
const MasterKey = require('lib/models/MasterKey.js');
const BaseModel = require('lib/BaseModel.js');
const { sprintf } = require('sprintf-js');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const JoplinError = require('lib/JoplinError');
const TaskQueue = require('lib/TaskQueue');
const { Dirnames } = require('lib/services/synchronizer/utils/types');

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ const { View, Button, Text } = require('react-native');
const PopupDialog = require('react-native-popup-dialog').default;
const { DialogTitle, DialogButton } = require('react-native-popup-dialog');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const DateTimePickerModal = require('react-native-modal-datetime-picker').default;
export default class SelectDateTimeDialog extends React.PureComponent<any, any> {

View File

@@ -4,7 +4,7 @@ const { connect } = require('react-redux');
const { Text, TouchableOpacity, View, StyleSheet } = require('react-native');
const { Checkbox } = require('lib/components/checkbox.js');
const Note = require('lib/models/Note.js');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const { themeStyle } = require('lib/components/global-style.js');
class NoteItemComponent extends Component {

View File

@@ -4,7 +4,7 @@ const { connect } = require('react-redux');
const { FlatList, Text, StyleSheet, Button, View } = require('react-native');
const { _ } = require('lib/locale');
const { NoteItem } = require('lib/components/note-item.js');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const { themeStyle } = require('lib/components/global-style.js');
class NoteListComponent extends Component {

View File

@@ -26,7 +26,7 @@ const { fileExtension, safeFileExtension } = require('lib/path-utils');
const mimeUtils = require('lib/mime-utils.js').mime;
const { ScreenHeader } = require('lib/components/screen-header.js');
const NoteTagsDialog = require('lib/components/screens/NoteTagsDialog');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const { Checkbox } = require('lib/components/checkbox.js');
const { _ } = require('lib/locale');
const { reg } = require('lib/registry.js');

View File

@@ -14,7 +14,7 @@ const { reg } = require('lib/registry.js');
const NavService = require('lib/services/NavService.js');
const VersionInfo = require('react-native-version-info').default;
const { ReportService } = require('lib/services/report.js');
const time = require('lib/time').default;
const { time } = require('lib/time-utils');
const shim = require('lib/shim').default;
const SearchEngine = require('lib/services/searchengine/SearchEngine');
const RNFS = require('react-native-fs');

View File

@@ -7,7 +7,7 @@ const { ScreenHeader } = require('lib/components/screen-header.js');
const { _ } = require('lib/locale');
const { BaseScreenComponent } = require('lib/components/base-screen.js');
const { themeStyle } = require('lib/components/global-style.js');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const shared = require('lib/components/shared/encryption-config-shared.js');
const { dialogs } = require('lib/dialogs.js');
const DialogBox = require('react-native-dialogbox').default;

View File

@@ -4,7 +4,7 @@ const { FlatList, View, Text, Button, StyleSheet, Platform } = require('react-na
const { connect } = require('react-redux');
const { reg } = require('lib/registry.js');
const { ScreenHeader } = require('lib/components/screen-header.js');
const time = require('lib/time').default;
const { time } = require('lib/time-utils');
const { themeStyle } = require('lib/components/global-style.js');
const Logger = require('lib/Logger').default;
const { BaseScreenComponent } = require('lib/components/base-screen.js');

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ const shim = require('lib/shim').default;
const BaseItem = require('lib/models/BaseItem.js');
const JoplinError = require('lib/JoplinError');
const ArrayUtils = require('lib/ArrayUtils');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const { sprintf } = require('sprintf-js');
const Mutex = require('async-mutex').Mutex;

View File

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

View File

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

View File

@@ -8,7 +8,7 @@ const Setting = require('lib/models/Setting').default;
const { MarkupToHtml } = require('lib/joplin-renderer');
const { enexXmlToMd } = require('./import-enex-md-gen.js');
const { enexXmlToHtml } = require('./import-enex-html-gen.js');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const Levenshtein = require('levenshtein');
const md5 = require('md5');
const { Base64Decode } = require('base64-stream');

View File

@@ -4,10 +4,6 @@ interface StringToStringMap {
[key:string]: string,
}
interface CodeToCountryMap {
[key:string]: string[],
}
const codeToLanguageE_:StringToStringMap = {};
codeToLanguageE_['aa'] = 'Afar';
codeToLanguageE_['ab'] = 'Abkhazian';
@@ -180,257 +176,12 @@ codeToLanguage_['et'] = 'Eesti Keel';
codeToLanguage_['vi'] = 'Tiếng Việt';
codeToLanguage_['hu'] = 'Magyar';
const codeToCountry_:CodeToCountryMap = {
AD: ['Andorra', 'Andorra'],
AE: ['United Arab Emirates', 'دولة الإمارات العربيّة المتّحدة'],
AF: ['Afghanistan', 'د افغانستان اسلامي دولتدولت اسلامی افغانستان, جمهوری اسلامی افغانستان'],
AG: ['Antigua and Barbuda', 'Antigua and Barbuda'],
AI: ['Anguilla', 'Anguilla'],
AL: ['Albania', 'Shqipëria'],
AM: ['Armenia', 'Հայաստան'],
AO: ['Angola', 'Angola'],
AQ: ['Antarctica', 'Antarctica, Antártico, Antarctique, Антарктике'],
AR: ['Argentina', 'Argentina'],
AS: ['American Samoa', 'American Samoa'],
AT: ['Austria', 'Österreich'],
AU: ['Australia', 'Australia'],
AW: ['Aruba', 'Aruba'],
AX: ['Aland Islands', 'Åland'],
AZ: ['Azerbaijan', 'Azərbaycan'],
BA: ['Bosnia and Herzegovina', 'Bosna i Hercegovina'],
BB: ['Barbados', 'Barbados'],
BD: ['Bangladesh', 'গণপ্রজাতন্ত্রী বাংলাদেশ'],
BE: ['Belgium', 'België, Belgique, Belgien'],
BF: ['Burkina Faso', 'Burkina Faso'],
BG: ['Bulgaria', 'България'],
BH: ['Bahrain', 'البحرين'],
BI: ['Burundi', 'Burundi'],
BJ: ['Benin', 'Bénin'],
BL: ['Saint-Barthélemy', 'Saint-Barthélemy'],
BM: ['Bermuda', 'Bermuda'],
BN: ['Brunei Darussalam', 'Brunei Darussalam'],
BO: ['Bolivia', 'Bolivia, Bulibiya, Volívia, Wuliwya'],
BQ: ['Caribbean Netherlands', 'Caribisch Nederland'],
BR: ['Brazil', 'Brasil'],
BS: ['Bahamas', 'Bahamas'],
BT: ['Bhutan', 'འབྲུག་ཡུལ'],
BV: ['Bouvet Island', 'Bouvetøya'],
BW: ['Botswana', 'Botswana'],
BY: ['Belarus', 'Беларусь'],
BZ: ['Belize', 'Belize'],
CA: ['Canada', 'Canada'],
CC: ['Cocos (Keeling) Islands', 'Cocos (Keeling) Islands'],
CD: ['Democratic Republic of the Congo (Congo-Kinshasa, former Zaire)', 'République Démocratique du Congo'],
CF: ['Centrafrican Republic', 'République centrafricaine, Ködörösêse tî Bêafrîka'],
CG: ['Republic of the Congo (Congo-Brazzaville)', 'République du Congo'],
CH: ['Switzerland', 'Schweiz, Suisse, Svizzera, Svizra'],
CI: ['Côte d\'Ivoire', 'Côte d\'Ivoire'],
CK: ['Cook Islands', 'Cook Islands, Kūki ʻĀirani'],
CL: ['Chile', 'Chile'],
CM: ['Cameroon', 'Cameroun, Cameroon'],
CN: ['China', '中国'],
CO: ['Colombia', 'Colombia'],
CR: ['Costa Rica', 'Costa Rica'],
CU: ['Cuba', 'Cuba'],
CV: ['Cabo Verde', 'Cabo Verde'],
CW: ['Curaçao', 'Curaçao'],
CX: ['Christmas Island', 'Christmas Island'],
CY: ['Cyprus', 'Κύπρος, Kibris'],
CZ: ['Czech Republic', 'Česká republika'],
DE: ['Germany', 'Deutschland'],
DJ: ['Djibouti', 'Djibouti, جيبوتي, Jabuuti, Gabuutih'],
DK: ['Denmark', 'Danmark'],
DM: ['Dominica', 'Dominica'],
DO: ['Dominican Republic', 'República Dominicana'],
DZ: ['Algeria', 'الجزائر'],
EC: ['Ecuador', 'Ecuador'],
EE: ['Estonia', 'Eesti'],
EG: ['Egypt', 'مصر'],
EH: ['Western Sahara', 'Sahara Occidental'],
ER: ['Eritrea', 'ኤርትራ, إرتريا, Eritrea'],
ES: ['Spain', 'España'],
ET: ['Ethiopia', 'ኢትዮጵያ, Itoophiyaa'],
FI: ['Finland', 'Suomi'],
FJ: ['Fiji', 'Fiji'],
FK: ['Falkland Islands', 'Falkland Islands'],
FM: ['Micronesia (Federated States of)', 'Micronesia'],
FO: ['Faroe Islands', 'Føroyar, Færøerne'],
FR: ['France', 'France'],
GA: ['Gabon', 'Gabon'],
GB: ['United Kingdom', 'United Kingdom'],
GD: ['Grenada', 'Grenada'],
GE: ['Georgia', 'საქართველო'],
GF: ['French Guiana', 'Guyane française'],
GG: ['Guernsey', 'Guernsey'],
GH: ['Ghana', 'Ghana'],
GI: ['Gibraltar', 'Gibraltar'],
GL: ['Greenland', 'Kalaallit Nunaat, Grønland'],
GM: ['The Gambia', 'The Gambia'],
GN: ['Guinea', 'Guinée'],
GP: ['Guadeloupe', 'Guadeloupe'],
GQ: ['Equatorial Guinea', 'Guiena ecuatorial, Guinée équatoriale, Guiné Equatorial'],
GR: ['Greece', 'Ελλάδα'],
GS: ['South Georgia and the South Sandwich Islands', 'South Georgia and the South Sandwich Islands'],
GT: ['Guatemala', 'Guatemala'],
GU: ['Guam', 'Guam, Guåhån'],
GW: ['Guinea Bissau', 'Guiné-Bissau'],
GY: ['Guyana', 'Guyana'],
HK: ['Hong Kong (SAR of China)', '香港, Hong Kong'],
HM: ['Heard Island and McDonald Islands', 'Heard Island and McDonald Islands'],
HN: ['Honduras', 'Honduras'],
HR: ['Croatia', 'Hrvatska'],
HT: ['Haiti', 'Haïti, Ayiti'],
HU: ['Hungary', 'Magyarország'],
ID: ['Indonesia', 'Indonesia'],
IE: ['Ireland', 'Ireland, Éire'],
IL: ['Israel', 'ישראל'],
IM: ['Isle of Man', 'Isle of Man'],
IN: ['India', 'भारत, India'],
IO: ['British Indian Ocean Territory', 'British Indian Ocean Territory'],
IQ: ['Iraq', 'العراق, Iraq'],
IR: ['Iran', 'ایران'],
IS: ['Iceland', 'Ísland'],
IT: ['Italy', 'Italia'],
JE: ['Jersey', 'Jersey'],
JM: ['Jamaica', 'Jamaica'],
JO: ['Jordan', 'الأُرْدُن'],
JP: ['Japan', '日本'],
KE: ['Kenya', 'Kenya'],
KG: ['Kyrgyzstan', 'Кыргызстан, Киргизия'],
KH: ['Cambodia', 'កម្ពុជា'],
KI: ['Kiribati', 'Kiribati'],
KM: ['Comores', 'ﺍﻟﻘﻤﺮي, Comores, Komori'],
KN: ['Saint Kitts and Nevis', 'Saint Kitts and Nevis'],
KP: ['North Korea', '북조선'],
KR: ['South Korea', '대한민국'],
KW: ['Kuwait', 'الكويت'],
KY: ['Cayman Islands', 'Cayman Islands'],
KZ: ['Kazakhstan', 'Қазақстан, Казахстан'],
LA: ['Laos', 'ປະຊາຊົນລາວ'],
LB: ['Lebanon', 'لبنان, Liban'],
LC: ['Saint Lucia', 'Saint Lucia'],
LI: ['Liechtenstein', 'Liechtenstein'],
LK: ['Sri Lanka', 'ශ්‍රී ලංකා, இலங்கை'],
LR: ['Liberia', 'Liberia'],
LS: ['Lesotho', 'Lesotho'],
LT: ['Lithuania', 'Lietuva'],
LU: ['Luxembourg', 'Lëtzebuerg, Luxembourg, Luxemburg'],
LV: ['Latvia', 'Latvija'],
LY: ['Libya', 'ليبيا'],
MA: ['Morocco', 'Maroc, ⵍⵎⵖⵔⵉⴱ, المغرب'],
MC: ['Monaco', 'Monaco'],
MD: ['Moldova', 'Moldova, Молдавия'],
ME: ['Montenegro', 'Crna Gora, Црна Гора'],
MF: ['Saint Martin (French part)', 'Saint-Martin'],
MG: ['Madagascar', 'Madagasikara, Madagascar'],
MH: ['Marshall Islands', 'Marshall Islands'],
MK: ['North Macedonia', 'Северна Македонија'],
ML: ['Mali', 'Mali'],
MM: ['Myanmar', 'မြန်မာ'],
MN: ['Mongolia', 'Монгол Улс'],
MO: ['Macao (SAR of China)', '澳門, Macau'],
MP: ['Northern Mariana Islands', 'Northern Mariana Islands'],
MQ: ['Martinique', 'Martinique'],
MR: ['Mauritania', 'موريتانيا, Mauritanie'],
MS: ['Montserrat', 'Montserrat'],
MT: ['Malta', 'Malta'],
MU: ['Mauritius', 'Maurice, Mauritius'],
MV: ['Maldives', ''],
MW: ['Malawi', 'Malawi'],
MX: ['Mexico', 'México'],
MY: ['Malaysia', ''],
MZ: ['Mozambique', 'Mozambique'],
NA: ['Namibia', 'Namibia'],
NC: ['New Caledonia', 'Nouvelle-Calédonie'],
NE: ['Niger', 'Niger'],
NF: ['Norfolk Island', 'Norfolk Island'],
NG: ['Nigeria', 'Nigeria'],
NI: ['Nicaragua', 'Nicaragua'],
NL: ['The Netherlands', 'Nederland'],
NO: ['Norway', 'Norge, Noreg'],
NP: ['Nepal', ''],
NR: ['Nauru', 'Nauru'],
NU: ['Niue', 'Niue'],
NZ: ['New Zealand', 'New Zealand'],
OM: ['Oman', 'سلطنة عُمان'],
PA: ['Panama', 'Panama'],
PE: ['Peru', 'Perú'],
PF: ['French Polynesia', 'Polynésie française'],
PG: ['Papua New Guinea', 'Papua New Guinea'],
PH: ['Philippines', 'Philippines'],
PK: ['Pakistan', 'پاکستان'],
PL: ['Poland', 'Polska'],
PM: ['Saint Pierre and Miquelon', 'Saint-Pierre-et-Miquelon'],
PN: ['Pitcairn', 'Pitcairn'],
PR: ['Puerto Rico', 'Puerto Rico'],
PS: ['Palestinian Territory', 'Palestinian Territory'],
PT: ['Portugal', 'Portugal'],
PW: ['Palau', 'Palau'],
PY: ['Paraguay', 'Paraguay'],
QA: ['Qatar', 'قطر'],
RE: ['Reunion', 'La Réunion'],
RO: ['Romania', 'România'],
RS: ['Serbia', 'Србија'],
RU: ['Russia', 'Россия'],
RW: ['Rwanda', 'Rwanda'],
SA: ['Saudi Arabia', 'السعودية'],
SB: ['Solomon Islands', 'Solomon Islands'],
SC: ['Seychelles', 'Seychelles'],
SD: ['Sudan', 'السودان'],
SE: ['Sweden', 'Sverige'],
SG: ['Singapore', 'Singapore'],
SH: ['Saint Helena', 'Saint Helena'],
SI: ['Slovenia', 'Slovenija'],
SJ: ['Svalbard and Jan Mayen', 'Svalbard and Jan Mayen'],
SK: ['Slovakia', 'Slovensko'],
SL: ['Sierra Leone', 'Sierra Leone'],
SM: ['San Marino', 'San Marino'],
SN: ['Sénégal', 'Sénégal'],
SO: ['Somalia', 'Somalia, الصومال'],
SR: ['Suriname', 'Suriname'],
ST: ['São Tomé and Príncipe', 'São Tomé e Príncipe'],
SS: ['South Sudan', 'South Sudan'],
SV: ['El Salvador', 'El Salvador'],
SX: ['Saint Martin (Dutch part)', 'Sint Maarten'],
SY: ['Syria', 'سوريا, Sūriyya'],
SZ: ['eSwatini', 'eSwatini'],
TC: ['Turks and Caicos Islands', 'Turks and Caicos Islands'],
TD: ['Chad', 'Tchad, تشاد'],
TF: ['French Southern and Antarctic Lands', 'Terres australes et antarctiques françaises'],
TG: ['Togo', 'Togo'],
TH: ['Thailand', 'ประเทศไทย'],
TJ: ['Tajikistan', ','],
TK: ['Tokelau', 'Tokelau'],
TL: ['Timor-Leste', 'Timor-Leste'],
TM: ['Turkmenistan', 'Türkmenistan'],
TN: ['Tunisia', 'تونس, Tunisie'],
TO: ['Tonga', 'Tonga'],
TR: ['Turkey', 'Türkiye'],
TT: ['Trinidad and Tobago', 'Trinidad and Tobago'],
TV: ['Tuvalu', 'Tuvalu'],
TW: ['Taiwan', 'Taiwan'],
TZ: ['Tanzania', 'Tanzania'],
UA: ['Ukraine', 'Україна'],
UG: ['Uganda', 'Uganda'],
UM: ['United States Minor Outlying Islands', 'United States Minor Outlying Islands'],
US: ['United States of America', 'United States of America'],
UY: ['Uruguay', 'Uruguay'],
UZ: ['Uzbekistan', ''],
VA: ['City of the Vatican', 'Città del Vaticano'],
VC: ['Saint Vincent and the Grenadines', 'Saint Vincent and the Grenadines'],
VE: ['Venezuela', 'Venezuela'],
VG: ['British Virgin Islands', 'British Virgin Islands'],
VI: ['United States Virgin Islands', 'United States Virgin Islands'],
VN: ['Vietnam', 'Việt Nam'],
VU: ['Vanuatu', 'Vanuatu'],
WF: ['Wallis and Futuna', 'Wallis-et-Futuna'],
WS: ['Samoa', 'Samoa'],
YE: ['Yemen', 'اليَمَن'],
YT: ['Mayotte', 'Mayotte'],
ZA: ['South Africa', 'South Africa'],
ZM: ['Zambia', 'Zambia'],
ZW: ['Zimbabwe', 'Zimbabwe'],
};
const codeToCountry_:StringToStringMap = {};
codeToCountry_['BR'] = 'Brasil';
codeToCountry_['CR'] = 'Costa Rica';
codeToCountry_['CN'] = '中国';
codeToCountry_['GB'] = 'UK';
codeToCountry_['US'] = 'US';
let supportedLocales_:any = null;
let localeStats_:any = null;
@@ -498,9 +249,7 @@ function closestSupportedLocale(canonicalName:string, defaultToEnglish:boolean =
}
function countryName(countryCode:string) {
const r = codeToCountry_[countryCode] ? codeToCountry_[countryCode] : null;
if (!r) return '';
return r.length > 1 && !!r[1] ? r[1] : r[0];
return codeToCountry_[countryCode] ? codeToCountry_[countryCode] : '';
}
function languageNameInEnglish(languageCode:string) {
@@ -541,14 +290,7 @@ function countryDisplayName(canonicalName:string) {
if (languageCode == 'zh' && (countryCode == '' || countryCode == 'TW')) extraString = '繁體'; // "Traditional" in "Traditional Chinese"
if (extraString) {
output += ` (${extraString})`;
} else if (countryCode) {
// If we have a country code but couldn't match it to a country name,
// just display the full canonical name (applies for example to es-419
// for Latin American Spanish).
output += ` (${canonicalName})`;
}
if (extraString) output += ` (${extraString})`;
return output;
}

View File

@@ -3,7 +3,7 @@ const { Database } = require('lib/database.js');
const Setting = require('lib/models/Setting').default;
const ItemChange = require('lib/models/ItemChange.js');
const JoplinError = require('lib/JoplinError.js');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const { sprintf } = require('sprintf-js');
const { _ } = require('lib/locale');
const moment = require('moment');

View File

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

View File

@@ -6,7 +6,7 @@ const Resource = require('lib/models/Resource.js');
const Setting = require('lib/models/Setting').default;
const shim = require('lib/shim').default;
const { pregQuote } = require('lib/string-utils.js');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const { _ } = require('lib/locale');
const ArrayUtils = require('lib/ArrayUtils.js');
const lodash = require('lodash');

View File

@@ -3,7 +3,7 @@ import { _, supportedLocalesToLanguages, defaultLocale } from 'lib/locale';
const BaseModel = require('lib/BaseModel.js');
const { Database } = require('lib/database.js');
const SyncTargetRegistry = require('lib/SyncTargetRegistry.js');
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const { sprintf } = require('sprintf-js');
const ObjectUtils = require('lib/ObjectUtils');
const { toTitleCase } = require('lib/string-utils.js');
@@ -830,9 +830,6 @@ class Setting extends BaseModel {
'camera.type': { value: 0, type: SettingItemType.Int, public: false, appTypes: ['mobile'] },
'camera.ratio': { value: '4:3', type: SettingItemType.String, public: false, appTypes: ['mobile'] },
'spellChecker.enabled': { value: true, type: SettingItemType.Bool, public: false },
'spellChecker.language': { value: '', type: SettingItemType.String, public: false },
windowContentZoomFactor: {
value: 100,
type: SettingItemType.Int,

View File

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

View File

@@ -8,7 +8,7 @@ const { fileExtension, basename } = require('lib/path-utils');
const spawn = require('child_process').spawn;
const chokidar = require('chokidar');
const bridge = require('electron').remote.require('./bridge').default;
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
const { ErrorNotFound } = require('./rest/errors');
class ExternalEditWatcher {

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

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

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

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

@@ -1,4 +1,4 @@
const time = require('lib/time').default;
const { time } = require('lib/time-utils.js');
interface Term {
name: string

View File

@@ -1,147 +0,0 @@
import Setting from 'lib/models/Setting';
import CommandService from '../CommandService';
import SpellCheckerServiceDriverBase from './SpellCheckerServiceDriverBase';
import { _, countryDisplayName } from 'lib/locale';
export default class SpellCheckerService {
private driver_:SpellCheckerServiceDriverBase;
private static instance_:SpellCheckerService;
public static instance():SpellCheckerService {
if (this.instance_) return this.instance_;
this.instance_ = new SpellCheckerService();
return this.instance_;
}
public async initialize(driver:SpellCheckerServiceDriverBase) {
this.driver_ = driver;
this.setupDefaultLanguage();
this.applyStateToDriver();
}
private get defaultLanguage():string {
return 'en-US';
}
public setupDefaultLanguage() {
if (!Setting.value('spellChecker.language')) {
const l = this.driver_.language;
this.setLanguage(l ? l : this.defaultLanguage);
}
}
public get availableLanguages():string[] {
return this.driver_.availableLanguages;
}
private applyStateToDriver() {
this.driver_.setLanguage(this.enabled ? this.language : '');
}
public setLanguage(language:string) {
Setting.setValue('spellChecker.language', language);
this.applyStateToDriver();
}
public get language():string {
return Setting.value('spellChecker.language');
}
public get enabled():boolean {
return Setting.value('spellChecker.enabled');
}
public toggleEnabled() {
Setting.toggle('spellChecker.enabled');
this.applyStateToDriver();
}
private makeMenuItem(item:any):any {
return this.driver_.makeMenuItem(item);
}
private async addToDictionary(language:string, word:string) {
this.driver_.addWordToSpellCheckerDictionary(language, word);
}
public contextMenuItems<T>(misspelledWord:string, dictionarySuggestions:string[]):T[] {
if (!misspelledWord) return [];
const output = [];
output.push(this.makeMenuItem({ type: 'separator' }));
if (dictionarySuggestions.length) {
for (const suggestion of dictionarySuggestions) {
output.push(this.makeMenuItem({
label: suggestion,
click: () => {
CommandService.instance().execute('replaceSelection', suggestion);
},
}));
}
} else {
output.push(this.makeMenuItem({
label: `(${_('No suggestions')})`,
enabled: false,
click: () => {},
}));
}
output.push(this.makeMenuItem({ type: 'separator' }));
output.push(this.makeMenuItem({
label: _('Add to dictionary'),
click: () => {
this.addToDictionary(this.language, misspelledWord);
},
}));
return output;
}
private changeLanguageMenuItems(selectedLanguage:string, enabled:boolean) {
const languageMenuItems = [];
for (const locale of this.driver_.availableLanguages) {
languageMenuItems.push({
label: countryDisplayName(locale),
type: 'radio',
checked: locale === selectedLanguage,
enabled: enabled,
click: () => {
this.setLanguage(locale);
},
});
}
languageMenuItems.sort((a:any, b:any) => {
return a.label < b.label ? -1 : +1;
});
return languageMenuItems.map((item:any) => this.makeMenuItem(item));
}
public spellCheckerConfigMenuItem(selectedLanguage:string, useSpellChecker:boolean) {
return this.makeMenuItem({
label: _('Spell checker'),
submenu: [
this.makeMenuItem({
label: _('Use spell checker'),
type: 'checkbox',
checked: useSpellChecker,
click: () => {
this.toggleEnabled();
},
}),
this.makeMenuItem({
type: 'separator',
}),
...this.changeLanguageMenuItems(selectedLanguage, useSpellChecker),
],
});
}
}

View File

@@ -1,23 +0,0 @@
export default class SpellCheckerServiceDriverBase {
public get availableLanguages():string[] {
throw new Error('Not implemented');
}
public setLanguage(_v:string) {
throw new Error('Not implemented');
}
public get language():string {
throw new Error('Not implemented');
}
public makeMenuItem(_item:any):any {
throw new Error('Not implemented');
}
public addWordToSpellCheckerDictionary(_language:string, _word:string) {
throw new Error('Not implemented');
}
}

View File

@@ -2,7 +2,7 @@ import { Dirnames } from './utils/types';
import shim from 'lib/shim';
const JoplinError = require('lib/JoplinError');
const time = require('lib/time').default;
const { time } = require('lib/time-utils');
const { fileExtension, filename } = require('lib/path-utils');
export enum LockType {

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

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