2020-11-07 17:59:37 +02:00
import ResourceEditWatcher from '@joplin/lib/services/ResourceEditWatcher/index' ;
import CommandService from '@joplin/lib/services/CommandService' ;
import KeymapService from '@joplin/lib/services/KeymapService' ;
2021-08-05 13:48:39 +02:00
import PluginService , { PluginSettings } from '@joplin/lib/services/plugins/PluginService' ;
2020-11-07 17:59:37 +02:00
import resourceEditWatcherReducer , { defaultState as resourceEditWatcherDefaultState } from '@joplin/lib/services/ResourceEditWatcher/reducer' ;
2020-10-09 19:35:46 +02:00
import PluginRunner from './services/plugins/PluginRunner' ;
import PlatformImplementation from './services/plugins/PlatformImplementation' ;
2024-08-17 13:19:05 +02:00
import type ShimType from '@joplin/lib/shim' ;
const shim : typeof ShimType = require ( '@joplin/lib/shim' ) . default ;
2020-11-07 17:59:37 +02:00
import AlarmService from '@joplin/lib/services/AlarmService' ;
import AlarmServiceDriverNode from '@joplin/lib/services/AlarmServiceDriverNode' ;
2023-07-27 17:05:56 +02:00
import Logger , { TargetType } from '@joplin/utils/Logger' ;
2022-05-02 16:59:58 +02:00
import Setting from '@joplin/lib/models/Setting' ;
2020-11-07 17:59:37 +02:00
import actionApi from '@joplin/lib/services/rest/actionApi.desktop' ;
2024-02-07 20:04:29 +02:00
import BaseApplication , { StartOptions } from '@joplin/lib/BaseApplication' ;
2021-05-13 18:57:37 +02:00
import DebugService from '@joplin/lib/debug/DebugService' ;
2020-11-07 17:59:37 +02:00
import { _ , setLocale } from '@joplin/lib/locale' ;
import SpellCheckerService from '@joplin/lib/services/spellChecker/SpellCheckerService' ;
2020-11-05 18:58:23 +02:00
import SpellCheckerServiceDriverNative from './services/spellChecker/SpellCheckerServiceDriverNative' ;
import bridge from './services/bridge' ;
2020-10-31 18:29:17 +02:00
import menuCommandNames from './gui/menuCommandNames' ;
2020-11-13 19:09:28 +02:00
import stateToWhenClauseContext from './services/commands/stateToWhenClauseContext' ;
import ResourceService from '@joplin/lib/services/ResourceService' ;
2020-11-16 13:03:44 +02:00
import ExternalEditWatcher from '@joplin/lib/services/ExternalEditWatcher' ;
2021-09-04 13:37:22 +02:00
import appReducer , { createAppDefaultState } from './app.reducer' ;
2021-01-22 19:41:11 +02:00
import Folder from '@joplin/lib/models/Folder' ;
import Tag from '@joplin/lib/models/Tag' ;
2021-01-29 20:45:11 +02:00
import { reg } from '@joplin/lib/registry' ;
2023-11-11 19:38:16 +02:00
const packageInfo : PackageInfo = require ( './packageInfo.js' ) ;
2021-01-23 17:51:19 +02:00
import DecryptionWorker from '@joplin/lib/services/DecryptionWorker' ;
2021-06-22 20:57:04 +02:00
import ClipperServer from '@joplin/lib/ClipperServer' ;
2023-12-11 15:58:45 +02:00
import { ipcRenderer , webFrame } from 'electron' ;
2020-10-09 19:35:46 +02:00
const Menu = bridge ( ) . Menu ;
2020-11-07 17:59:37 +02:00
const PluginManager = require ( '@joplin/lib/services/PluginManager' ) ;
2021-01-23 17:51:19 +02:00
import RevisionService from '@joplin/lib/services/RevisionService' ;
import MigrationService from '@joplin/lib/services/MigrationService' ;
2021-07-23 12:05:21 +02:00
import { loadCustomCss , injectCustomStyles } from '@joplin/lib/CssUtils' ;
2021-09-04 14:43:25 +02:00
import mainScreenCommands from './gui/MainScreen/commands/index' ;
import noteEditorCommands from './gui/NoteEditor/commands/index' ;
import noteListCommands from './gui/NoteList/commands/index' ;
import noteListControlsCommands from './gui/NoteListControls/commands/index' ;
import sidebarCommands from './gui/Sidebar/commands/index' ;
import appCommands from './commands/index' ;
import libCommands from '@joplin/lib/commands/index' ;
2021-10-17 15:54:48 +02:00
import { homedir } from 'os' ;
2022-09-01 12:44:33 +02:00
import getDefaultPluginsInfo from '@joplin/lib/services/plugins/defaultPlugins/desktopDefaultPluginsInfo' ;
2021-10-01 20:35:27 +02:00
const electronContextMenu = require ( './services/electron-context-menu' ) ;
2021-01-22 19:41:11 +02:00
// import populateDatabase from '@joplin/lib/services/debug/populateDatabase';
2020-10-09 19:35:46 +02:00
2021-09-04 14:43:25 +02:00
const commands = mainScreenCommands
. concat ( noteEditorCommands )
. concat ( noteListCommands )
. concat ( noteListControlsCommands )
. concat ( sidebarCommands ) ;
2020-10-09 19:35:46 +02:00
// Commands that are not tied to any particular component.
// The runtime for these commands can be loaded when the app starts.
2021-09-04 14:43:25 +02:00
const globalCommands = appCommands . concat ( libCommands ) ;
2020-10-09 19:35:46 +02:00
2021-09-04 14:43:25 +02:00
import editorCommandDeclarations from './gui/NoteEditor/editorCommandDeclarations' ;
2021-11-11 17:33:37 +02:00
import PerFolderSortOrderService from './services/sortOrder/PerFolderSortOrderService' ;
2021-05-13 18:57:37 +02:00
import ShareService from '@joplin/lib/services/share/ShareService' ;
2024-08-09 12:45:08 +02:00
import checkForUpdates from './checkForUpdates' ;
2021-09-04 19:11:29 +02:00
import { AppState } from './app.reducer' ;
2021-10-17 19:50:56 +02:00
import syncDebugLog from '@joplin/lib/services/synchronizer/syncDebugLog' ;
2023-12-13 21:24:58 +02:00
import eventManager , { EventName } from '@joplin/lib/eventManager' ;
2022-09-01 12:44:33 +02:00
import path = require ( 'path' ) ;
2023-12-22 13:31:57 +02:00
import { afterDefaultPluginsLoaded , loadAndRunDefaultPlugins } from '@joplin/lib/services/plugins/defaultPlugins/defaultPluginsUtils' ;
2023-07-23 16:57:55 +02:00
import userFetcher , { initializeUserFetcher } from '@joplin/lib/utils/userFetcher' ;
2023-09-24 21:21:58 +02:00
import { parseNotesParent } from '@joplin/lib/reducer' ;
2023-12-13 21:24:58 +02:00
import OcrService from '@joplin/lib/services/ocr/OcrService' ;
import OcrDriverTesseract from '@joplin/lib/services/ocr/drivers/OcrDriverTesseract' ;
2024-01-05 17:03:23 +02:00
import SearchEngine from '@joplin/lib/services/search/SearchEngine' ;
2023-11-11 19:38:16 +02:00
import { PackageInfo } from '@joplin/lib/versionInfo' ;
2024-07-26 13:22:49 +02:00
import { CustomProtocolHandler } from './utils/customProtocols/handleCustomProtocols' ;
2024-01-06 19:21:51 +02:00
import { refreshFolders } from '@joplin/lib/folders-screen-utils' ;
2020-10-09 19:35:46 +02:00
const pluginClasses = [
2020-10-18 22:52:10 +02:00
require ( './plugins/GotoAnything' ) . default ,
2020-10-09 19:35:46 +02:00
] ;
2021-09-04 13:37:22 +02:00
const appDefaultState = createAppDefaultState (
bridge ( ) . windowContentSize ( ) ,
2023-08-22 12:58:53 +02:00
resourceEditWatcherDefaultState ,
2021-09-04 13:37:22 +02:00
) ;
2020-10-09 19:35:46 +02:00
class Application extends BaseApplication {
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2021-02-07 18:47:56 +02:00
private checkAllPluginStartedIID_ : any = null ;
2023-06-30 10:07:03 +02:00
private initPluginServiceDone_ = false ;
2023-12-13 21:24:58 +02:00
private ocrService_ : OcrService ;
2024-07-26 13:22:49 +02:00
private protocolHandler_ : CustomProtocolHandler ;
2021-02-07 18:47:56 +02:00
2021-10-03 17:00:49 +02:00
public constructor ( ) {
2020-10-09 19:35:46 +02:00
super ( ) ;
this . bridge_nativeThemeUpdated = this . bridge_nativeThemeUpdated . bind ( this ) ;
}
2021-10-03 17:00:49 +02:00
public hasGui() {
2020-10-09 19:35:46 +02:00
return true ;
}
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2021-10-03 17:00:49 +02:00
public reducer ( state : AppState = appDefaultState , action : any ) {
2021-09-04 13:37:22 +02:00
let newState = appReducer ( state , action ) ;
2020-10-09 19:35:46 +02:00
newState = resourceEditWatcherReducer ( newState , action ) ;
newState = super . reducer ( newState , action ) ;
return newState ;
}
2021-10-03 17:00:49 +02:00
public toggleDevTools ( visible : boolean ) {
2020-10-09 19:35:46 +02:00
if ( visible ) {
bridge ( ) . openDevTools ( ) ;
} else {
bridge ( ) . closeDevTools ( ) ;
}
}
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2021-10-03 17:00:49 +02:00
protected async generalMiddleware ( store : any , next : any , action : any ) {
2022-07-23 09:31:32 +02:00
if ( action . type === 'SETTING_UPDATE_ONE' && action . key === 'locale' || action . type === 'SETTING_UPDATE_ALL' ) {
2020-10-09 19:35:46 +02:00
setLocale ( Setting . value ( 'locale' ) ) ;
// The bridge runs within the main process, with its own instance of locale.js
// so it needs to be set too here.
bridge ( ) . setLocale ( Setting . value ( 'locale' ) ) ;
}
2022-07-23 09:31:32 +02:00
if ( action . type === 'SETTING_UPDATE_ONE' && action . key === 'showTrayIcon' || action . type === 'SETTING_UPDATE_ALL' ) {
2020-10-09 19:35:46 +02:00
this . updateTray ( ) ;
}
2023-12-13 21:24:58 +02:00
if ( action . type === 'SETTING_UPDATE_ONE' && action . key === 'ocr.enabled' || action . type === 'SETTING_UPDATE_ALL' ) {
2024-08-09 12:29:39 +02:00
void this . setupOcrService ( ) ;
2023-12-13 21:24:58 +02:00
}
2022-07-23 09:31:32 +02:00
if ( action . type === 'SETTING_UPDATE_ONE' && action . key === 'style.editor.fontFamily' || action . type === 'SETTING_UPDATE_ALL' ) {
2020-10-09 19:35:46 +02:00
this . updateEditorFont ( ) ;
}
2022-07-23 09:31:32 +02:00
if ( action . type === 'SETTING_UPDATE_ONE' && action . key === 'windowContentZoomFactor' || action . type === 'SETTING_UPDATE_ALL' ) {
2020-10-09 19:35:46 +02:00
webFrame . setZoomFactor ( Setting . value ( 'windowContentZoomFactor' ) / 100 ) ;
}
2024-04-03 19:57:52 +02:00
if ( action . type === 'SETTING_UPDATE_ONE' && action . key === 'linking.extraAllowedExtensions' || action . type === 'SETTING_UPDATE_ALL' ) {
bridge ( ) . extraAllowedOpenExtensions = Setting . value ( 'linking.extraAllowedExtensions' ) ;
}
2020-10-09 19:35:46 +02:00
if ( [ 'EVENT_NOTE_ALARM_FIELD_CHANGE' , 'NOTE_DELETE' ] . indexOf ( action . type ) >= 0 ) {
await AlarmService . updateNoteNotification ( action . id , action . type === 'NOTE_DELETE' ) ;
}
const result = await super . generalMiddleware ( store , next , action ) ;
const newState = store . getState ( ) ;
if ( [ 'NOTE_VISIBLE_PANES_TOGGLE' , 'NOTE_VISIBLE_PANES_SET' ] . indexOf ( action . type ) >= 0 ) {
Setting . setValue ( 'noteVisiblePanes' , newState . noteVisiblePanes ) ;
}
if ( [ 'NOTE_DEVTOOLS_TOGGLE' , 'NOTE_DEVTOOLS_SET' ] . indexOf ( action . type ) >= 0 ) {
this . toggleDevTools ( newState . devToolsVisible ) ;
}
if ( action . type === 'FOLDER_AND_NOTE_SELECT' ) {
await Folder . expandTree ( newState . folders , action . folderId ) ;
}
2022-07-23 09:31:32 +02:00
if ( this . hasGui ( ) && ( ( action . type === 'SETTING_UPDATE_ONE' && [ 'themeAutoDetect' , 'theme' , 'preferredLightTheme' , 'preferredDarkTheme' ] . includes ( action . key ) ) || action . type === 'SETTING_UPDATE_ALL' ) ) {
2020-10-09 19:35:46 +02:00
this . handleThemeAutoDetect ( ) ;
}
2024-07-26 13:22:49 +02:00
if ( action . type === 'PLUGIN_ADD' ) {
const plugin = PluginService . instance ( ) . pluginById ( action . plugin . id ) ;
this . protocolHandler_ . allowReadAccessToDirectory ( plugin . baseDir ) ;
this . protocolHandler_ . allowReadAccessToDirectory ( plugin . dataDir ) ;
}
2020-10-09 19:35:46 +02:00
return result ;
}
2021-10-03 17:00:49 +02:00
public handleThemeAutoDetect() {
2020-10-09 19:35:46 +02:00
if ( ! Setting . value ( 'themeAutoDetect' ) ) return ;
if ( bridge ( ) . shouldUseDarkColors ( ) ) {
Setting . setValue ( 'theme' , Setting . value ( 'preferredDarkTheme' ) ) ;
} else {
Setting . setValue ( 'theme' , Setting . value ( 'preferredLightTheme' ) ) ;
}
}
2021-10-03 17:00:49 +02:00
private bridge_nativeThemeUpdated() {
2020-10-09 19:35:46 +02:00
this . handleThemeAutoDetect ( ) ;
}
2021-10-03 17:00:49 +02:00
public updateTray() {
2020-10-09 19:35:46 +02:00
const app = bridge ( ) . electronApp ( ) ;
if ( app . trayShown ( ) === Setting . value ( 'showTrayIcon' ) ) return ;
if ( ! Setting . value ( 'showTrayIcon' ) ) {
app . destroyTray ( ) ;
} else {
const contextMenu = Menu . buildFromTemplate ( [
{ label : _ ( 'Open %s' , app . electronApp ( ) . name ) , click : ( ) = > { app . window ( ) . show ( ) ; } } ,
{ type : 'separator' } ,
2020-11-25 16:40:25 +02:00
{ label : _ ( 'Quit' ) , click : ( ) = > { void app . quit ( ) ; } } ,
2020-10-09 19:35:46 +02:00
] ) ;
app . createTray ( contextMenu ) ;
}
}
2021-10-03 17:00:49 +02:00
public updateEditorFont() {
2020-10-09 19:35:46 +02:00
const fontFamilies = [ ] ;
if ( Setting . value ( 'style.editor.fontFamily' ) ) fontFamilies . push ( ` " ${ Setting . value ( 'style.editor.fontFamily' ) } " ` ) ;
2024-07-05 19:58:09 +02:00
fontFamilies . push ( '\'Avenir Next\', Avenir, Arial, sans-serif' ) ;
2020-10-09 19:35:46 +02:00
// The '*' and '!important' parts are necessary to make sure Russian text is displayed properly
// https://github.com/laurent22/joplin/issues/155
2024-03-09 12:48:22 +02:00
//
// Note: Be careful about the specificity here. Incorrect specificity can break monospaced fonts in tables.
2020-10-09 19:35:46 +02:00
2024-03-09 12:48:22 +02:00
const css = ` .CodeMirror5 *, .cm-editor .cm-content { font-family: ${ fontFamilies . join ( ', ' ) } !important; } ` ;
2020-10-09 19:35:46 +02:00
const styleTag = document . createElement ( 'style' ) ;
styleTag . type = 'text/css' ;
styleTag . appendChild ( document . createTextNode ( css ) ) ;
document . head . appendChild ( styleTag ) ;
}
2021-10-03 17:00:49 +02:00
public setupContextMenu() {
2021-10-01 20:35:27 +02:00
// bridge().setupContextMenu((misspelledWord: string, dictionarySuggestions: string[]) => {
// let output = SpellCheckerService.instance().contextMenuItems(misspelledWord, dictionarySuggestions);
// console.info(misspelledWord, dictionarySuggestions);
// console.info(output);
// output = output.map(o => {
// delete o.click;
// return o;
// });
// return output;
// });
2020-11-08 03:08:33 +02:00
const MenuItem = bridge ( ) . MenuItem ;
2020-11-05 18:58:23 +02:00
// The context menu must be setup in renderer process because that's where
// the spell checker service lives.
2021-10-01 20:35:27 +02:00
electronContextMenu ( {
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2020-11-12 21:13:28 +02:00
shouldShowMenu : ( _event : any , params : any ) = > {
2020-11-05 18:58:23 +02:00
// 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' ;
} ,
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2020-11-12 21:13:28 +02:00
menu : ( actions : any , props : any ) = > {
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2020-11-12 21:13:28 +02:00
const spellCheckerMenuItems = SpellCheckerService . instance ( ) . contextMenuItems ( props . misspelledWord , props . dictionarySuggestions ) . map ( ( item : any ) = > new MenuItem ( item ) ) ;
2020-11-05 18:58:23 +02:00
const output = [
actions . cut ( ) ,
actions . copy ( ) ,
actions . paste ( ) ,
. . . spellCheckerMenuItems ,
] ;
return output ;
} ,
} ) ;
}
2020-11-13 19:09:28 +02:00
private async initPluginService() {
2023-06-05 21:05:00 +02:00
if ( this . initPluginServiceDone_ ) return ;
this . initPluginServiceDone_ = true ;
2020-11-19 14:34:49 +02:00
const service = PluginService . instance ( ) ;
2020-11-13 19:09:28 +02:00
const pluginRunner = new PluginRunner ( ) ;
2020-11-19 14:34:49 +02:00
service . initialize ( packageInfo . version , PlatformImplementation . instance ( ) , pluginRunner , this . store ( ) ) ;
2021-04-24 20:23:33 +02:00
service . isSafeMode = Setting . value ( 'isSafeMode' ) ;
2020-11-19 14:34:49 +02:00
2022-09-01 12:44:33 +02:00
let pluginSettings = service . unserializePluginSettings ( Setting . value ( 'plugins.states' ) ) ;
2021-08-05 13:48:39 +02:00
{
// Users can add and remove plugins from the config screen at any
// time, however we only effectively uninstall the plugin the next
// time the app is started. What plugin should be uninstalled is
// stored in the settings.
2023-12-22 13:31:57 +02:00
pluginSettings = service . clearUpdateState ( await service . uninstallPlugins ( pluginSettings ) ) ;
Setting . setValue ( 'plugins.states' , pluginSettings ) ;
2021-08-05 13:48:39 +02:00
}
2020-11-13 19:09:28 +02:00
try {
2020-11-19 14:34:49 +02:00
if ( await shim . fsDriver ( ) . exists ( Setting . value ( 'pluginDir' ) ) ) {
await service . loadAndRunPlugins ( Setting . value ( 'pluginDir' ) , pluginSettings ) ;
}
2020-11-13 19:09:28 +02:00
} catch ( error ) {
this . logger ( ) . error ( ` There was an error loading plugins from ${ Setting . value ( 'pluginDir' ) } : ` , error ) ;
}
try {
2024-03-09 13:03:57 +02:00
await service . loadAndRunDevPlugins ( pluginSettings ) ;
2020-11-13 19:09:28 +02:00
} catch ( error ) {
this . logger ( ) . error ( ` There was an error loading plugins from ${ Setting . value ( 'plugins.devPluginPaths' ) } : ` , error ) ;
}
2021-02-07 18:47:56 +02:00
2023-12-22 13:31:57 +02:00
// Load default plugins after loading other plugins -- this allows users
// to override built-in plugins with development versions with the same
// ID.
const defaultPluginsDir = path . join ( bridge ( ) . buildDir ( ) , 'defaultPlugins' ) ;
try {
pluginSettings = await loadAndRunDefaultPlugins ( service , defaultPluginsDir , getDefaultPluginsInfo ( ) , pluginSettings ) ;
} catch ( error ) {
this . logger ( ) . error ( ` There was an error loading plugins from ${ defaultPluginsDir } : ` , error ) ;
}
2021-08-05 13:48:39 +02:00
{
// Users can potentially delete files from /plugins or even delete
// the complete folder. When that happens, we still have the plugin
// info in the state, which can cause various issues, so to sort it
// out we remove from the state any plugin that has *not* been loaded
// above (meaning the file was missing).
// https://github.com/laurent22/joplin/issues/5253
2023-12-22 13:31:57 +02:00
const oldSettings = pluginSettings ;
2021-08-05 13:48:39 +02:00
const newSettings : PluginSettings = { } ;
for ( const pluginId of Object . keys ( oldSettings ) ) {
if ( ! service . pluginIds . includes ( pluginId ) ) {
this . logger ( ) . warn ( 'Found a plugin in the state that has not been loaded, which means the plugin might have been deleted outside Joplin - removing it from the state:' , pluginId ) ;
continue ;
}
newSettings [ pluginId ] = oldSettings [ pluginId ] ;
}
Setting . setValue ( 'plugins.states' , newSettings ) ;
2023-12-22 13:31:57 +02:00
pluginSettings = newSettings ;
2021-08-05 13:48:39 +02:00
}
2021-02-07 18:47:56 +02:00
this . checkAllPluginStartedIID_ = setInterval ( ( ) = > {
if ( service . allPluginsStarted ) {
clearInterval ( this . checkAllPluginStartedIID_ ) ;
this . dispatch ( {
type : 'STARTUP_PLUGINS_LOADED' ,
value : true ,
} ) ;
2023-12-11 15:58:45 +02:00
// Sends an event to the main process -- this is used by the Playwright
// tests to wait for plugins to load.
ipcRenderer . send ( 'startup-plugins-loaded' ) ;
2023-12-22 13:31:57 +02:00
void afterDefaultPluginsLoaded ( service . plugins , getDefaultPluginsInfo ( ) , pluginSettings ) ;
2021-02-07 18:47:56 +02:00
}
} , 500 ) ;
2020-11-13 19:09:28 +02:00
}
2022-05-02 16:59:58 +02:00
public crashDetectionHandler() {
// This handler conflicts with the single instance behaviour, so it's
// not used for now.
// https://discourse.joplinapp.org/t/pre-release-v2-8-is-now-available-updated-27-april/25158/56?u=laurent
2022-04-20 18:34:58 +02:00
if ( ! Setting . value ( 'wasClosedSuccessfully' ) ) {
const answer = confirm ( _ ( 'The application did not close properly. Would you like to start in safe mode?' ) ) ;
Setting . setValue ( 'isSafeMode' , ! ! answer ) ;
}
Setting . setValue ( 'wasClosedSuccessfully' , false ) ;
}
2024-08-09 12:29:39 +02:00
private async setupOcrService() {
if ( Setting . value ( 'ocr.clearLanguageDataCache' ) ) {
Setting . setValue ( 'ocr.clearLanguageDataCache' , false ) ;
try {
await OcrDriverTesseract . clearLanguageDataCache ( ) ;
} catch ( error ) {
this . logger ( ) . warn ( 'OCR: Failed to clear language data cache.' , error ) ;
}
}
2023-12-13 21:24:58 +02:00
if ( Setting . value ( 'ocr.enabled' ) ) {
2024-08-09 12:29:39 +02:00
2023-12-13 21:24:58 +02:00
if ( ! this . ocrService_ ) {
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2023-12-13 21:24:58 +02:00
const Tesseract = ( window as any ) . Tesseract ;
const driver = new OcrDriverTesseract (
{ createWorker : Tesseract.createWorker } ,
2024-08-09 12:29:39 +02:00
{
workerPath : ` ${ bridge ( ) . buildDir ( ) } /tesseract.js/worker.min.js ` ,
corePath : ` ${ bridge ( ) . buildDir ( ) } /tesseract.js-core ` ,
languageDataPath : Setting.value ( 'ocr.languageDataPath' ) || null ,
} ,
2023-12-13 21:24:58 +02:00
) ;
this . ocrService_ = new OcrService ( driver ) ;
}
void this . ocrService_ . runInBackground ( ) ;
} else {
if ( ! this . ocrService_ ) return ;
void this . ocrService_ . stopRunInBackground ( ) ;
}
const handleResourceChange = ( ) = > {
void this . ocrService_ . maintenance ( ) ;
} ;
eventManager . on ( EventName . ResourceCreate , handleResourceChange ) ;
eventManager . on ( EventName . ResourceChange , handleResourceChange ) ;
}
2024-09-21 14:02:22 +02:00
private setupAutoUpdaterService() {
2024-08-27 19:04:18 +02:00
if ( Setting . value ( 'featureFlag.autoUpdaterServiceEnabled' ) ) {
2024-09-21 14:02:22 +02:00
bridge ( ) . electronApp ( ) . initializeAutoUpdaterService (
2024-08-27 19:04:18 +02:00
Logger . create ( 'AutoUpdaterService' ) ,
Setting . value ( 'env' ) === 'dev' ,
Setting . value ( 'autoUpdate.includePreReleases' ) ,
) ;
}
}
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2024-02-07 20:04:29 +02:00
public async start ( argv : string [ ] , startOptions : StartOptions = null ) : Promise < any > {
2020-10-09 19:35:46 +02:00
// If running inside a package, the command line, instead of being "node.exe <path> <flags>" is "joplin.exe <flags>" so
// insert an extra argument so that they can be processed in a consistent way everywhere.
2021-10-01 20:35:27 +02:00
if ( ! bridge ( ) . electronIsDev ( ) ) argv . splice ( 1 , 0 , '.' ) ;
2020-10-09 19:35:46 +02:00
2024-02-07 20:04:29 +02:00
argv = await super . start ( argv , startOptions ) ;
2022-04-20 18:34:58 +02:00
2024-04-26 17:07:16 +02:00
bridge ( ) . setLogFilePath ( Logger . globalLogger . logFilePath ( ) ) ;
2020-10-09 19:35:46 +02:00
await this . applySettingsSideEffects ( ) ;
if ( Setting . value ( 'sync.upgradeState' ) === Setting . SYNC_UPGRADE_STATE_MUST_DO ) {
reg . logger ( ) . info ( 'app.start: doing upgradeSyncTarget action' ) ;
bridge ( ) . window ( ) . show ( ) ;
return { action : 'upgradeSyncTarget' } ;
}
reg . logger ( ) . info ( 'app.start: doing regular boot' ) ;
2021-10-15 13:24:22 +02:00
const dir : string = Setting . value ( 'profileDir' ) ;
syncDebugLog . enabled = false ;
if ( dir . endsWith ( 'dev-desktop-2' ) ) {
2021-10-17 15:54:48 +02:00
syncDebugLog . addTarget ( TargetType . File , {
path : ` ${ homedir ( ) } /synclog.txt ` ,
} ) ;
2021-10-15 13:24:22 +02:00
syncDebugLog . enabled = true ;
syncDebugLog . info ( ` Profile dir: ${ dir } ` ) ;
}
2020-10-09 19:35:46 +02:00
// Loads app-wide styles. (Markdown preview-specific styles loaded in app.js)
2022-04-16 14:51:17 +02:00
await injectCustomStyles ( 'appStyles' , Setting . customCssFilePath ( Setting . customCssFilenames . JOPLIN_APP ) ) ;
2020-10-09 19:35:46 +02:00
2024-09-21 14:02:22 +02:00
this . setupAutoUpdaterService ( ) ;
2020-10-09 19:35:46 +02:00
AlarmService . setDriver ( new AlarmServiceDriverNode ( { appName : packageInfo.build.appId } ) ) ;
AlarmService . setLogger ( reg . logger ( ) ) ;
2024-07-01 17:21:17 +02:00
reg . setDispatch ( this . dispatch . bind ( this ) ) ;
2020-11-12 21:13:28 +02:00
reg . setShowErrorMessageBoxHandler ( ( message : string ) = > { bridge ( ) . showErrorMessageBox ( message ) ; } ) ;
2020-10-09 19:35:46 +02:00
if ( Setting . value ( 'flagOpenDevTools' ) ) {
bridge ( ) . openDevTools ( ) ;
}
2024-07-26 13:22:49 +02:00
bridge ( ) . electronApp ( ) . initializeCustomProtocolHandler (
Logger . create ( 'handleCustomProtocols' ) ,
) ;
this . protocolHandler_ = bridge ( ) . electronApp ( ) . getCustomProtocolHandler ( ) ;
this . protocolHandler_ . allowReadAccessToDirectory ( __dirname ) ; // App bundle directory
this . protocolHandler_ . allowReadAccessToDirectory ( Setting . value ( 'cacheDir' ) ) ;
this . protocolHandler_ . allowReadAccessToDirectory ( Setting . value ( 'resourceDir' ) ) ;
// this.protocolHandler_.allowReadAccessTo(Setting.value('tempDir'));
// For now, this doesn't seem necessary:
// this.protocolHandler_.allowReadAccessTo(Setting.value('profileDir'));
// If it is needed, note that they decrease the security of the protcol
// handler, and, as such, it may make sense to also limit permissions of
// allowed pages with a Content Security Policy.
2020-10-09 19:35:46 +02:00
PluginManager . instance ( ) . dispatch_ = this . dispatch . bind ( this ) ;
PluginManager . instance ( ) . setLogger ( reg . logger ( ) ) ;
PluginManager . instance ( ) . register ( pluginClasses ) ;
this . initRedux ( ) ;
2021-11-11 17:33:37 +02:00
PerFolderSortOrderService . initialize ( ) ;
2022-07-23 09:31:32 +02:00
CommandService . instance ( ) . initialize ( this . store ( ) , Setting . value ( 'env' ) === 'dev' , stateToWhenClauseContext ) ;
2020-10-09 19:35:46 +02:00
for ( const command of commands ) {
CommandService . instance ( ) . registerDeclaration ( command . declaration ) ;
}
for ( const command of globalCommands ) {
CommandService . instance ( ) . registerDeclaration ( command . declaration ) ;
CommandService . instance ( ) . registerRuntime ( command . declaration . name , command . runtime ( ) ) ;
}
for ( const declaration of editorCommandDeclarations ) {
CommandService . instance ( ) . registerDeclaration ( declaration ) ;
}
2020-10-31 14:25:12 +02:00
const keymapService = KeymapService . instance ( ) ;
2020-10-31 18:29:17 +02:00
// We only add the commands that appear in the menu because only
// those can have a shortcut associated with them.
keymapService . initialize ( menuCommandNames ( ) ) ;
2020-10-31 14:25:12 +02:00
try {
await keymapService . loadCustomKeymap ( ` ${ dir } /keymap-desktop.json ` ) ;
} catch ( error ) {
reg . logger ( ) . error ( error ) ;
}
2020-10-31 18:29:17 +02:00
// Since the settings need to be loaded before the store is
// created, it will never receive the SETTING_UPDATE_ALL even,
// which mean state.settings will not be initialised. So we
// manually call dispatchUpdateAll() to force an update.
2020-10-09 19:35:46 +02:00
Setting . dispatchUpdateAll ( ) ;
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2024-04-27 10:54:47 +02:00
await refreshFolders ( ( action : any ) = > this . dispatch ( action ) , '' ) ;
2020-10-09 19:35:46 +02:00
const tags = await Tag . allWithNotes ( ) ;
this . dispatch ( {
type : 'TAG_UPDATE_ALL' ,
items : tags ,
} ) ;
2021-08-12 17:54:10 +02:00
// const masterKeys = await MasterKey.all();
2020-10-09 19:35:46 +02:00
2021-08-12 17:54:10 +02:00
// this.dispatch({
// type: 'MASTERKEY_UPDATE_ALL',
// items: masterKeys,
// });
2020-10-09 19:35:46 +02:00
2023-09-24 21:21:58 +02:00
const getNotesParent = async ( ) = > {
let notesParent = parseNotesParent ( Setting . value ( 'notesParent' ) , Setting . value ( 'activeFolderId' ) ) ;
if ( notesParent . type === 'Tag' && ! ( await Tag . load ( notesParent . selectedItemId ) ) ) {
notesParent = {
type : 'Folder' ,
selectedItemId : Setting.value ( 'activeFolderId' ) ,
} ;
}
return notesParent ;
} ;
const notesParent = await getNotesParent ( ) ;
if ( notesParent . type === 'SmartFilter' ) {
this . store ( ) . dispatch ( {
type : 'SMART_FILTER_SELECT' ,
id : notesParent.selectedItemId ,
} ) ;
} else if ( notesParent . type === 'Tag' ) {
this . store ( ) . dispatch ( {
type : 'TAG_SELECT' ,
id : notesParent.selectedItemId ,
} ) ;
} else {
this . store ( ) . dispatch ( {
type : 'FOLDER_SELECT' ,
id : notesParent.selectedItemId ,
} ) ;
}
2020-10-09 19:35:46 +02:00
this . store ( ) . dispatch ( {
type : 'FOLDER_SET_COLLAPSED_ALL' ,
ids : Setting.value ( 'collapsedFolderIds' ) ,
} ) ;
// Loads custom Markdown preview styles
2022-04-16 14:51:17 +02:00
const cssString = await loadCustomCss ( Setting . customCssFilePath ( Setting . customCssFilenames . RENDERED_MARKDOWN ) ) ;
2020-10-09 19:35:46 +02:00
this . store ( ) . dispatch ( {
2021-07-23 12:05:21 +02:00
type : 'CUSTOM_CSS_APPEND' ,
2020-10-09 19:35:46 +02:00
css : cssString ,
} ) ;
this . store ( ) . dispatch ( {
type : 'NOTE_DEVTOOLS_SET' ,
value : Setting.value ( 'flagOpenDevTools' ) ,
} ) ;
2024-08-09 12:45:08 +02:00
// Note: Auto-update is a misnomer in the code.
// The code below only checks, if a new version is available.
// We only allow Windows and macOS users to automatically check for updates
2024-08-17 13:19:05 +02:00
if ( ! Setting . value ( 'featureFlag.autoUpdaterServiceEnabled' ) ) {
if ( shim . isWindows ( ) || shim . isMac ( ) ) {
const runAutoUpdateCheck = ( ) = > {
if ( Setting . value ( 'autoUpdateEnabled' ) ) {
void checkForUpdates ( true , bridge ( ) . window ( ) , { includePreReleases : Setting.value ( 'autoUpdate.includePreReleases' ) } ) ;
}
} ;
2024-08-09 12:45:08 +02:00
2024-08-17 13:19:05 +02:00
// Initial check on startup
shim . setTimeout ( ( ) = > { runAutoUpdateCheck ( ) ; } , 5000 ) ;
// Then every x hours
shim . setInterval ( ( ) = > { runAutoUpdateCheck ( ) ; } , 12 * 60 * 60 * 1000 ) ;
}
2024-08-09 12:45:08 +02:00
}
2023-07-23 16:57:55 +02:00
initializeUserFetcher ( ) ;
shim . setInterval ( ( ) = > { void userFetcher ( ) ; } , 1000 * 60 * 60 ) ;
2023-07-18 21:15:45 +02:00
2020-10-09 19:35:46 +02:00
this . updateTray ( ) ;
shim . setTimeout ( ( ) = > {
2020-11-25 16:40:25 +02:00
void AlarmService . garbageCollect ( ) ;
2020-10-09 19:35:46 +02:00
} , 1000 * 60 * 60 ) ;
if ( Setting . value ( 'startMinimized' ) && Setting . value ( 'showTrayIcon' ) ) {
2023-07-10 12:59:09 +02:00
bridge ( ) . window ( ) . hide ( ) ;
2020-10-09 19:35:46 +02:00
} else {
bridge ( ) . window ( ) . show ( ) ;
}
2021-05-13 18:57:37 +02:00
void ShareService . instance ( ) . maintenance ( ) ;
2020-10-09 19:35:46 +02:00
ResourceService . runInBackground ( ) ;
if ( Setting . value ( 'env' ) === 'dev' ) {
2020-11-25 16:40:25 +02:00
void AlarmService . updateAllNotifications ( ) ;
2020-10-09 19:35:46 +02:00
} else {
2022-09-30 18:23:14 +02:00
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
2021-01-29 20:45:11 +02:00
void reg . scheduleSync ( 1000 ) . then ( ( ) = > {
2020-10-09 19:35:46 +02:00
// Wait for the first sync before updating the notifications, since synchronisation
// might change the notifications.
2020-11-25 16:40:25 +02:00
void AlarmService . updateAllNotifications ( ) ;
2020-10-09 19:35:46 +02:00
2021-01-22 19:41:11 +02:00
void DecryptionWorker . instance ( ) . scheduleStart ( ) ;
2020-10-09 19:35:46 +02:00
} ) ;
}
const clipperLogger = new Logger ( ) ;
clipperLogger . addTarget ( TargetType . File , { path : ` ${ Setting . value ( 'profileDir' ) } /log-clipper.txt ` } ) ;
clipperLogger . addTarget ( TargetType . Console ) ;
ClipperServer . instance ( ) . initialize ( actionApi ) ;
ClipperServer . instance ( ) . setLogger ( clipperLogger ) ;
ClipperServer . instance ( ) . setDispatch ( this . store ( ) . dispatch ) ;
if ( Setting . value ( 'clipperServer.autoStart' ) ) {
2021-06-22 20:57:04 +02:00
void ClipperServer . instance ( ) . start ( ) ;
2020-10-09 19:35:46 +02:00
}
ExternalEditWatcher . instance ( ) . setLogger ( reg . logger ( ) ) ;
2020-11-16 13:03:44 +02:00
ExternalEditWatcher . instance ( ) . initialize ( bridge , this . store ( ) . dispatch ) ;
2020-10-09 19:35:46 +02:00
2024-02-22 23:29:16 +02:00
ResourceEditWatcher . instance ( ) . initialize (
reg . logger ( ) ,
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2024-02-22 23:29:16 +02:00
( action : any ) = > { this . store ( ) . dispatch ( action ) ; } ,
( path : string ) = > bridge ( ) . openItem ( path ) ,
) ;
2020-10-09 19:35:46 +02:00
2021-12-30 12:25:39 +02:00
// Forwards the local event to the global event manager, so that it can
// be picked up by the plugin manager.
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2021-12-30 12:25:39 +02:00
ResourceEditWatcher . instance ( ) . on ( 'resourceChange' , ( event : any ) = > {
2023-12-13 21:24:58 +02:00
eventManager . emit ( EventName . ResourceChange , event ) ;
2021-12-30 12:25:39 +02:00
} ) ;
2020-10-09 19:35:46 +02:00
RevisionService . instance ( ) . runInBackground ( ) ;
// Make it available to the console window - useful to call revisionService.collectRevisions()
2021-05-13 18:57:37 +02:00
if ( Setting . value ( 'env' ) === 'dev' ) {
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2021-05-13 18:57:37 +02:00
( window as any ) . joplin = {
2020-10-09 19:35:46 +02:00
revisionService : RevisionService.instance ( ) ,
migrationService : MigrationService.instance ( ) ,
decryptionWorker : DecryptionWorker.instance ( ) ,
2021-01-08 20:06:33 +02:00
commandService : CommandService.instance ( ) ,
2022-04-19 16:52:32 +02:00
pluginService : PluginService.instance ( ) ,
2020-10-09 19:35:46 +02:00
bridge : bridge ( ) ,
2021-05-13 18:57:37 +02:00
debug : new DebugService ( reg . db ( ) ) ,
2022-06-13 19:07:13 +02:00
resourceService : ResourceService.instance ( ) ,
2023-12-13 21:24:58 +02:00
searchEngine : SearchEngine.instance ( ) ,
ocrService : ( ) = > this . ocrService_ ,
2020-10-09 19:35:46 +02:00
} ;
2021-05-13 18:57:37 +02:00
}
2020-10-09 19:35:46 +02:00
bridge ( ) . addEventListener ( 'nativeThemeUpdated' , this . bridge_nativeThemeUpdated ) ;
2024-04-03 19:57:52 +02:00
bridge ( ) . setOnAllowedExtensionsChangeListener ( ( newExtensions ) = > {
Setting . setValue ( 'linking.extraAllowedExtensions' , newExtensions ) ;
} ) ;
2020-10-09 19:35:46 +02:00
2023-06-06 17:31:31 +02:00
await this . initPluginService ( ) ;
2020-10-09 19:35:46 +02:00
2020-11-05 18:58:23 +02:00
this . setupContextMenu ( ) ;
await SpellCheckerService . instance ( ) . initialize ( new SpellCheckerServiceDriverNative ( ) ) ;
2023-08-08 17:02:05 +02:00
this . startRotatingLogMaintenance ( Setting . value ( 'profileDir' ) ) ;
2023-12-13 21:24:58 +02:00
await this . setupOcrService ( ) ;
2024-02-09 13:55:29 +02:00
eventManager . on ( EventName . OcrServiceResourcesProcessed , async ( ) = > {
await ResourceService . instance ( ) . indexNoteResources ( ) ;
} ) ;
eventManager . on ( EventName . NoteResourceIndexed , async ( ) = > {
2023-12-13 21:24:58 +02:00
SearchEngine . instance ( ) . scheduleSyncTables ( ) ;
} ) ;
2024-03-28 17:35:27 +02:00
// setTimeout(() => {
// void populateDatabase(reg.db(), {
// clearDatabase: true,
// folderCount: 200,
// noteCount: 3000,
// tagCount: 2000,
// tagsPerNote: 10,
// rootFolderCount: 20,
// subFolderDepth: 3,
// });
// }, 5000);
2020-10-09 19:35:46 +02:00
// setTimeout(() => {
// console.info(CommandService.instance().commandsToMarkdownTable(this.store().getState()));
// }, 2000);
2021-01-22 02:55:58 +02:00
// setTimeout(() => {
// this.dispatch({
// type: 'NAV_GO',
// routeName: 'Config',
// props: {
2021-08-17 13:03:19 +02:00
// defaultSection: 'encryption',
2021-01-22 02:55:58 +02:00
// },
// });
2021-08-17 13:03:19 +02:00
// }, 2000);
2020-11-19 14:34:49 +02:00
2021-08-16 16:20:14 +02:00
// setTimeout(() => {
// this.dispatch({
// type: 'DIALOG_OPEN',
2022-04-13 15:44:52 +02:00
// name: 'syncWizard',
2021-08-16 16:20:14 +02:00
// });
// }, 2000);
2022-04-13 15:44:52 +02:00
// setTimeout(() => {
// this.dispatch({
// type: 'DIALOG_OPEN',
// name: 'editFolder',
// });
// }, 3000);
2021-09-01 13:17:20 +02:00
// setTimeout(() => {
// this.dispatch({
// type: 'NAV_GO',
// routeName: 'Config',
// props: {
// defaultSection: 'plugins',
// },
// });
// }, 2000);
2021-10-03 17:00:49 +02:00
// await runIntegrationTests();
2020-10-09 19:35:46 +02:00
return null ;
}
}
2020-11-12 21:13:28 +02:00
let application_ : Application = null ;
2020-10-09 19:35:46 +02:00
function app() {
if ( ! application_ ) application_ = new Application ( ) ;
return application_ ;
}
export default app ;