2022-04-11 17:49:32 +02:00
import { useEffect , useState , useRef , useCallback , useMemo } from 'react' ;
2021-09-04 19:11:29 +02:00
import { AppState } from '../app.reducer' ;
2020-11-07 17:59:37 +02:00
import InteropService from '@joplin/lib/services/interop/InteropService' ;
import { stateUtils } from '@joplin/lib/reducer' ;
import CommandService from '@joplin/lib/services/CommandService' ;
import MenuUtils from '@joplin/lib/services/commands/MenuUtils' ;
import KeymapService from '@joplin/lib/services/KeymapService' ;
import { PluginStates , utils as pluginUtils } from '@joplin/lib/services/plugins/reducer' ;
import shim from '@joplin/lib/shim' ;
import Setting from '@joplin/lib/models/Setting' ;
2023-11-11 19:38:16 +02:00
import versionInfo , { PackageInfo } from '@joplin/lib/versionInfo' ;
2023-10-24 13:20:54 +02:00
import makeDiscourseDebugUrl from '@joplin/lib/makeDiscourseDebugUrl' ;
2023-07-12 11:30:38 +02:00
import { ImportModule } from '@joplin/lib/services/interop/Module' ;
2020-10-09 19:35:46 +02:00
import InteropServiceHelper from '../InteropServiceHelper' ;
2020-11-07 17:59:37 +02:00
import { _ } from '@joplin/lib/locale' ;
2020-12-11 15:28:59 +02:00
import { isContextMenuItemLocation , MenuItem , MenuItemLocation } from '@joplin/lib/services/plugins/api/types' ;
2020-11-07 17:59:37 +02:00
import SpellCheckerService from '@joplin/lib/services/spellChecker/SpellCheckerService' ;
2020-10-31 18:29:17 +02:00
import menuCommandNames from './menuCommandNames' ;
2020-11-13 19:09:28 +02:00
import stateToWhenClauseContext from '../services/commands/stateToWhenClauseContext' ;
2020-11-19 23:01:19 +02:00
import bridge from '../services/bridge' ;
2021-05-14 11:29:06 +02:00
import checkForUpdates from '../checkForUpdates' ;
2020-10-09 19:35:46 +02:00
const { connect } = require ( 'react-redux' ) ;
2021-01-29 20:45:11 +02:00
import { reg } from '@joplin/lib/registry' ;
2022-04-14 10:58:34 +02:00
import { ProfileConfig } from '@joplin/lib/services/profileConfig/types' ;
2023-04-03 18:01:06 +02:00
import PluginService , { PluginSettings } from '@joplin/lib/services/plugins/PluginService' ;
2023-09-18 18:40:36 +02:00
import { getListRendererById , getListRendererIds } from '@joplin/lib/services/noteList/renderers' ;
import useAsyncEffect from '@joplin/lib/hooks/useAsyncEffect' ;
2023-12-13 21:24:58 +02:00
import { EventName } from '@joplin/lib/eventManager' ;
2024-09-21 14:02:22 +02:00
import { ipcRenderer } from 'electron' ;
2023-11-11 19:38:16 +02:00
const packageInfo : PackageInfo = require ( '../packageInfo.js' ) ;
2020-11-19 23:01:19 +02:00
const { clipboard } = require ( 'electron' ) ;
2020-10-09 19:35:46 +02:00
const Menu = bridge ( ) . Menu ;
2020-10-13 13:57:03 +02:00
const menuUtils = new MenuUtils ( CommandService . instance ( ) ) ;
2020-11-12 21:13:28 +02:00
function pluginMenuItemsCommandNames ( menuItems : MenuItem [ ] ) : string [ ] {
let output : string [ ] = [ ] ;
2020-10-13 13:57:03 +02:00
for ( const menuItem of menuItems ) {
if ( menuItem . submenu ) {
output = output . concat ( pluginMenuItemsCommandNames ( menuItem . submenu ) ) ;
} else {
if ( menuItem . commandName ) output . push ( menuItem . commandName ) ;
}
}
return output ;
}
2022-04-11 17:49:32 +02:00
function getPluginCommandNames ( plugins : PluginStates ) : string [ ] {
2020-11-12 21:13:28 +02:00
let output : string [ ] = [ ] ;
2020-10-13 13:57:03 +02:00
for ( const view of pluginUtils . viewsByType ( plugins , 'menu' ) ) {
output = output . concat ( pluginMenuItemsCommandNames ( view . menuItems ) ) ;
}
for ( const view of pluginUtils . viewsByType ( plugins , 'menuItem' ) ) {
if ( view . commandName ) output . push ( view . commandName ) ;
}
return output ;
}
2023-06-30 11:30:29 +02:00
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
2020-11-12 21:13:28 +02:00
function createPluginMenuTree ( label : string , menuItems : MenuItem [ ] , onMenuItemClick : Function ) {
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 output : any = {
2020-10-13 13:57:03 +02:00
label : label ,
submenu : [ ] ,
} ;
for ( const menuItem of menuItems ) {
if ( menuItem . submenu ) {
output . submenu . push ( createPluginMenuTree ( menuItem . label , menuItem . submenu , onMenuItemClick ) ) ;
} else {
output . submenu . push ( menuUtils . commandToMenuItem ( menuItem . commandName , onMenuItemClick ) ) ;
}
}
return output ;
}
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2022-04-11 17:49:32 +02:00
const useSwitchProfileMenuItems = ( profileConfig : ProfileConfig , menuItemDic : any ) = > {
return useMemo ( ( ) = > {
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2022-04-11 17:49:32 +02:00
const switchProfileMenuItems : any [ ] = [ ] ;
for ( let i = 0 ; i < profileConfig . profiles . length ; i ++ ) {
const profile = profileConfig . profiles [ i ] ;
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2022-04-11 17:49:32 +02:00
let menuItem : any = { } ;
const profileNum = i + 1 ;
if ( menuItemDic [ ` switchProfile ${ profileNum } ` ] ) {
menuItem = { . . . menuItemDic [ ` switchProfile ${ profileNum } ` ] } ;
} else {
menuItem = {
label : profile.name ,
click : ( ) = > {
2022-04-16 16:04:06 +02:00
void CommandService . instance ( ) . execute ( 'switchProfile' , profile . id ) ;
2022-04-11 17:49:32 +02:00
} ,
} ;
}
menuItem . label = profile . name ;
menuItem . type = 'checkbox' ;
2022-04-16 16:04:06 +02:00
menuItem . checked = profileConfig . currentProfileId === profile . id ;
2022-04-11 17:49:32 +02:00
switchProfileMenuItems . push ( menuItem ) ;
}
switchProfileMenuItems . push ( { type : 'separator' } ) ;
switchProfileMenuItems . push ( menuItemDic . addProfile ) ;
switchProfileMenuItems . push ( menuItemDic . editProfileConfig ) ;
return switchProfileMenuItems ;
} , [ profileConfig , menuItemDic ] ) ;
} ;
2023-09-18 18:40:36 +02:00
const useNoteListMenuItems = ( noteListRendererIds : string [ ] ) = > {
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2023-09-18 18:40:36 +02:00
const [ menuItems , setMenuItems ] = useState < any [ ] > ( [ ] ) ;
useAsyncEffect ( async ( event ) = > {
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2023-09-18 18:40:36 +02:00
const output : any [ ] = [ ] ;
for ( const id of noteListRendererIds ) {
const renderer = getListRendererById ( id ) ;
output . push ( {
id : ` noteListRenderer_ ${ id } ` ,
label : await renderer . label ( ) ,
type : 'checkbox' ,
click : ( ) = > {
Setting . setValue ( 'notes.listRendererId' , id ) ;
} ,
} ) ;
if ( event . cancelled ) return ;
}
setMenuItems ( output ) ;
} , [ noteListRendererIds ] ) ;
return menuItems ;
} ;
2020-10-09 19:35:46 +02:00
interface Props {
2023-06-30 11:30:29 +02:00
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
2020-11-12 21:29:22 +02:00
dispatch : Function ;
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:29:22 +02:00
menuItemProps : any ;
routeName : string ;
selectedFolderId : string ;
layoutButtonSequence : number ;
[ 'notes.sortOrder.field' ] : string ;
[ 'folders.sortOrder.field' ] : string ;
[ 'notes.sortOrder.reverse' ] : boolean ;
[ 'folders.sortOrder.reverse' ] : boolean ;
showNoteCounts : boolean ;
uncompletedTodosOnTop : boolean ;
showCompletedTodos : boolean ;
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:29:22 +02:00
pluginMenuItems : 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:29:22 +02:00
pluginMenus : any [ ] ;
[ 'spellChecker.enabled' ] : boolean ;
2022-08-27 13:05:44 +02:00
[ 'spellChecker.languages' ] : string [ ] ;
2020-12-19 19:42:18 +02:00
plugins : PluginStates ;
2021-05-19 15:00:16 +02:00
customCss : string ;
2021-12-31 08:50:32 +02:00
locale : string ;
2022-04-11 17:49:32 +02:00
profileConfig : ProfileConfig ;
2023-04-03 18:01:06 +02:00
pluginSettings : PluginSettings ;
2023-09-18 18:40:36 +02:00
noteListRendererIds : string [ ] ;
noteListRendererId : string ;
2024-05-07 11:57:02 +02:00
showMenuBar : boolean ;
2020-10-09 19:35:46 +02:00
}
2020-11-12 21:13:28 +02:00
const commandNames : string [ ] = menuCommandNames ( ) ;
2020-10-09 19:35:46 +02:00
2020-11-12 21:13:28 +02:00
function menuItemSetChecked ( id : string , checked : boolean ) {
2020-10-09 19:35:46 +02:00
const menu = Menu . getApplicationMenu ( ) ;
const menuItem = menu . getMenuItemById ( id ) ;
if ( ! menuItem ) return ;
menuItem . checked = checked ;
}
2020-11-12 21:13:28 +02:00
function menuItemSetEnabled ( id : string , enabled : boolean ) {
2020-10-09 19:35:46 +02:00
const menu = Menu . getApplicationMenu ( ) ;
const menuItem = menu . getMenuItemById ( id ) ;
if ( ! menuItem ) return ;
menuItem . enabled = enabled ;
}
2024-05-07 11:57:02 +02:00
const applyMenuBarVisibility = ( showMenuBar : boolean ) = > {
// The menu bar cannot be hidden on macOS
if ( shim . isMac ( ) ) return ;
const window = bridge ( ) . window ( ) ;
window . setAutoHideMenuBar ( ! showMenuBar ) ;
window . setMenuBarVisibility ( showMenuBar ) ;
} ;
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 12:09:28 +02:00
function useMenuStates ( menu : any , props : Props ) {
useEffect ( ( ) = > {
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 12:09:28 +02:00
let timeoutId : any = null ;
function scheduleUpdate() {
if ( ! timeoutId ) return ; // Was cancelled
timeoutId = null ;
const whenClauseContext = CommandService . instance ( ) . currentWhenClauseContext ( ) ;
for ( const commandName in props . menuItemProps ) {
const p = props . menuItemProps [ commandName ] ;
if ( ! p ) continue ;
const enabled = 'enabled' in p ? p.enabled : CommandService.instance ( ) . isEnabled ( commandName , whenClauseContext ) ;
menuItemSetEnabled ( commandName , enabled ) ;
}
const layoutButtonSequenceOptions = Setting . enumOptions ( 'layoutButtonSequence' ) ;
for ( const value in layoutButtonSequenceOptions ) {
menuItemSetChecked ( ` layoutButtonSequence_ ${ value } ` , props . layoutButtonSequence === Number ( value ) ) ;
}
2023-09-18 18:40:36 +02:00
const listRendererIds = getListRendererIds ( ) ;
for ( const id of listRendererIds ) {
menuItemSetChecked ( ` noteListRenderer_ ${ id } ` , props . noteListRendererId === id ) ;
}
2021-02-07 12:09:28 +02:00
function applySortItemCheckState ( type : string ) {
const sortOptions = Setting . enumOptions ( ` ${ type } .sortOrder.field ` ) ;
for ( const field in sortOptions ) {
if ( ! sortOptions . hasOwnProperty ( field ) ) continue ;
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 12:09:28 +02:00
menuItemSetChecked ( ` sort: ${ type } : ${ field } ` , ( props as any ) [ ` ${ type } .sortOrder.field ` ] === field ) ;
}
2022-07-23 11:33:12 +02:00
const id = type === 'notes' ? 'toggleNotesSortOrderReverse' : ` sort: ${ type } :reverse ` ;
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2021-11-11 17:33:37 +02:00
menuItemSetChecked ( id , ( props as any ) [ ` ${ type } .sortOrder.reverse ` ] ) ;
2021-02-07 12:09:28 +02:00
}
applySortItemCheckState ( 'notes' ) ;
applySortItemCheckState ( 'folders' ) ;
menuItemSetChecked ( 'showNoteCounts' , props . showNoteCounts ) ;
menuItemSetChecked ( 'uncompletedTodosOnTop' , props . uncompletedTodosOnTop ) ;
menuItemSetChecked ( 'showCompletedTodos' , props . showCompletedTodos ) ;
}
timeoutId = setTimeout ( scheduleUpdate , 150 ) ;
return ( ) = > {
clearTimeout ( timeoutId ) ;
timeoutId = null ;
} ;
2022-08-19 13:10:04 +02:00
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
2021-02-07 12:09:28 +02:00
} , [
props . menuItemProps ,
props . layoutButtonSequence ,
2022-08-19 13:10:04 +02:00
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
2021-02-07 12:09:28 +02:00
props [ 'notes.sortOrder.field' ] ,
2022-08-19 13:10:04 +02:00
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
2021-02-07 12:09:28 +02:00
props [ 'folders.sortOrder.field' ] ,
2022-08-19 13:10:04 +02:00
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
2021-02-07 12:09:28 +02:00
props [ 'notes.sortOrder.reverse' ] ,
2022-08-19 13:10:04 +02:00
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
2021-02-07 12:09:28 +02:00
props [ 'folders.sortOrder.reverse' ] ,
2023-09-18 18:40:36 +02:00
props . noteListRendererId ,
2021-02-07 12:09:28 +02:00
props . showNoteCounts ,
props . uncompletedTodosOnTop ,
props . showCompletedTodos ,
menu ,
] ) ;
}
2020-11-12 21:13:28 +02:00
function useMenu ( props : Props ) {
2020-10-09 19:35:46 +02:00
const [ menu , setMenu ] = useState ( null ) ;
const [ keymapLastChangeTime , setKeymapLastChangeTime ] = useState ( Date . now ( ) ) ;
const [ module sLastChangeTime , setModulesLastChangeTime ] = useState ( Date . now ( ) ) ;
2022-04-14 13:27:19 +02:00
// We use a ref here because the plugin state can change frequently when
// switching note since any plugin view might be rendered again. However we
// need this plugin state only in a click handler when exporting notes, and
// for that a ref is sufficient.
const pluginsRef = useRef ( props . plugins ) ;
2020-11-12 21:13:28 +02:00
const onMenuItemClick = useCallback ( ( commandName : string ) = > {
2020-11-25 16:40:25 +02:00
void CommandService . instance ( ) . execute ( commandName ) ;
2020-10-21 18:22:29 +02:00
} , [ ] ) ;
2020-10-09 19:35:46 +02:00
2023-07-12 11:30:38 +02:00
const onImportModuleClick = useCallback ( async ( module : ImportModule , module Source : string ) = > {
2020-10-09 19:35:46 +02:00
let path = null ;
if ( module Source === 'file' ) {
2021-11-01 09:38:06 +02:00
path = await bridge ( ) . showOpenDialog ( {
2020-10-09 19:35:46 +02:00
filters : [ { name : module.description , extensions : module.fileExtensions } ] ,
} ) ;
} else {
2021-11-01 09:38:06 +02:00
path = await bridge ( ) . showOpenDialog ( {
2020-10-09 19:35:46 +02:00
properties : [ 'openDirectory' , 'createDirectory' ] ,
} ) ;
}
if ( ! path || ( Array . isArray ( path ) && ! path . length ) ) return ;
if ( Array . isArray ( path ) ) path = path [ 0 ] ;
2021-01-23 17:51:19 +02:00
const modalMessage = _ ( 'Importing from "%s" as "%s" format. Please wait...' , path , module .format ) ;
2020-10-21 17:55:52 +02:00
2020-11-25 16:40:25 +02:00
void CommandService . instance ( ) . execute ( 'showModalMessage' , modalMessage ) ;
2020-10-09 19:35:46 +02:00
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2020-12-23 19:25:26 +02:00
const errors : any [ ] = [ ] ;
2020-10-09 19:35:46 +02:00
const importOptions = {
path ,
format : module.format ,
2020-10-17 12:35:51 +02:00
outputFormat : module.outputFormat ,
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
onProgress : ( status : any ) = > {
const statusStrings : string [ ] = Object . keys ( status ) . map ( ( key : string ) = > {
2020-10-21 17:55:52 +02:00
return ` ${ key } : ${ status [ key ] } ` ;
} ) ;
2020-11-25 16:40:25 +02:00
void CommandService . instance ( ) . execute ( 'showModalMessage' , ` ${ modalMessage } \ n \ n ${ statusStrings . join ( '\n' ) } ` ) ;
2020-10-21 17:55:52 +02:00
} ,
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2020-12-23 19:25:26 +02:00
onError : ( error : any ) = > {
errors . push ( error ) ;
console . warn ( error ) ;
} ,
2020-10-09 19:35:46 +02:00
destinationFolderId : ! module .isNoteArchive && module Source === 'file' ? props.selectedFolderId : null ,
} ;
const service = InteropService . instance ( ) ;
try {
const result = await service . import ( importOptions ) ;
2023-02-16 12:55:24 +02:00
// eslint-disable-next-line no-console
2020-10-09 19:35:46 +02:00
console . info ( 'Import result: ' , result ) ;
} catch ( error ) {
bridge ( ) . showErrorMessageBox ( error . message ) ;
}
2023-10-24 12:28:14 +02:00
void CommandService . instance ( ) . execute ( 'hideModalMessage' ) ;
2020-12-23 19:25:26 +02:00
if ( errors . length ) {
2023-10-24 13:20:54 +02:00
const response = bridge ( ) . showErrorMessageBox ( 'There was some errors importing the notes - check the console for more details.\n\nPlease consider sending a bug report to the forum!' , {
buttons : [ _ ( 'Close' ) , _ ( 'Send bug report' ) ] ,
} ) ;
2020-12-23 19:25:26 +02:00
props . dispatch ( { type : 'NOTE_DEVTOOLS_SET' , value : true } ) ;
2023-10-24 13:20:54 +02:00
if ( response === 1 ) {
const url = makeDiscourseDebugUrl (
` Error importing notes from format: ${ module .format } ` ,
` - Input format: ${ module .format } \ n- Output format: ${ module .outputFormat } ` ,
errors ,
packageInfo ,
PluginService . instance ( ) ,
props . pluginSettings ,
) ;
void bridge ( ) . openExternal ( url ) ;
}
2020-12-23 19:25:26 +02:00
}
2022-08-19 13:10:04 +02:00
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
2023-10-24 13:20:54 +02:00
} , [ props . selectedFolderId , props . pluginSettings ] ) ;
2020-10-09 19:35:46 +02:00
const onMenuItemClickRef = useRef ( null ) ;
onMenuItemClickRef . current = onMenuItemClick ;
const onImportModuleClickRef = useRef ( null ) ;
onImportModuleClickRef . current = onImportModuleClick ;
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2022-04-11 17:49:32 +02:00
const pluginCommandNames = useMemo ( ( ) = > props . pluginMenuItems . map ( ( view : any ) = > view . commandName ) , [ props . pluginMenuItems ] ) ;
const menuItemDic = useMemo ( ( ) = > {
return menuUtils . commandsToMenuItems (
commandNames . concat ( pluginCommandNames ) ,
( commandName : string ) = > onMenuItemClickRef . current ( commandName ) ,
2023-08-22 12:58:53 +02:00
props . locale ,
2022-04-11 17:49:32 +02:00
) ;
2022-08-19 13:10:04 +02:00
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
2022-04-11 17:49:32 +02:00
} , [ commandNames , pluginCommandNames , props . locale ] ) ;
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2022-04-11 17:49:32 +02:00
const switchProfileMenuItems : any [ ] = useSwitchProfileMenuItems ( props . profileConfig , menuItemDic ) ;
2023-09-18 18:40:36 +02:00
const noteListMenuItems = useNoteListMenuItems ( props . noteListRendererIds ) ;
2020-10-09 19:35:46 +02:00
useEffect ( ( ) = > {
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 12:09:28 +02:00
let timeoutId : any = null ;
2020-10-09 19:35:46 +02:00
2021-02-07 12:09:28 +02:00
function updateMenu() {
if ( ! timeoutId ) return ; // Has been cancelled
2020-10-09 19:35:46 +02:00
2021-02-07 12:09:28 +02:00
const keymapService = KeymapService . instance ( ) ;
2020-10-09 19:35:46 +02:00
2021-02-07 12:09:28 +02:00
const quitMenuItem = {
label : _ ( 'Quit' ) ,
accelerator : keymapService.getAccelerator ( 'quit' ) ,
click : ( ) = > { void bridge ( ) . electronApp ( ) . quit ( ) ; } ,
} ;
2020-10-09 19:35:46 +02:00
2021-02-07 12:09:28 +02:00
const sortNoteFolderItems = ( type : string ) = > {
const sortItems = [ ] ;
const sortOptions = Setting . enumOptions ( ` ${ type } .sortOrder.field ` ) ;
for ( const field in sortOptions ) {
if ( ! sortOptions . hasOwnProperty ( field ) ) continue ;
sortItems . push ( {
id : ` sort: ${ type } : ${ field } ` ,
label : sortOptions [ field ] ,
type : 'checkbox' ,
// checked: Setting.value(`${type}.sortOrder.field`) === field,
click : ( ) = > {
2021-11-11 17:33:37 +02:00
if ( type === 'notes' ) {
void CommandService . instance ( ) . execute ( 'toggleNotesSortOrderField' , field ) ;
} else {
Setting . setValue ( ` ${ type } .sortOrder.field ` , field ) ;
}
2020-10-09 19:35:46 +02:00
} ,
} ) ;
}
2021-02-07 12:09:28 +02:00
sortItems . push ( { type : 'separator' } ) ;
2020-10-09 19:35:46 +02:00
2022-07-23 11:33:12 +02:00
if ( type === 'notes' ) {
2021-11-11 17:33:37 +02:00
sortItems . push (
{ . . . menuItemDic . toggleNotesSortOrderReverse , type : 'checkbox' } ,
2023-08-22 12:58:53 +02:00
{ . . . menuItemDic . toggleNotesSortOrderField , visible : false } ,
2021-11-11 17:33:37 +02:00
) ;
} else {
sortItems . push ( {
id : ` sort: ${ type } :reverse ` ,
label : Setting.settingMetadata ( ` ${ type } .sortOrder.reverse ` ) . label ( ) ,
type : 'checkbox' ,
// checked: Setting.value(`${type}.sortOrder.reverse`),
click : ( ) = > {
Setting . setValue ( ` ${ type } .sortOrder.reverse ` , ! Setting . value ( ` ${ type } .sortOrder.reverse ` ) ) ;
} ,
} ) ;
}
2020-10-09 19:35:46 +02:00
2021-02-07 12:09:28 +02:00
return sortItems ;
2020-10-31 14:46:55 +02:00
} ;
2021-02-07 12:09:28 +02:00
const sortNoteItems = sortNoteFolderItems ( 'notes' ) ;
const sortFolderItems = sortNoteFolderItems ( 'folders' ) ;
const focusItems = [
menuItemDic . focusElementSideBar ,
menuItemDic . focusElementNoteList ,
menuItemDic . focusElementNoteTitle ,
menuItemDic . focusElementNoteBody ,
] ;
const importItems = [ ] ;
const exportItems = [ ] ;
const ioService = InteropService . instance ( ) ;
const ioModules = ioService . module s ( ) ;
for ( let i = 0 ; i < ioModules . length ; i ++ ) {
const module = ioModules [ i ] ;
if ( module .type === 'exporter' ) {
if ( module .isNoteArchive !== false ) {
exportItems . push ( {
label : module.fullLabel ( ) ,
click : async ( ) = > {
await InteropServiceHelper . export (
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 12:09:28 +02:00
( action : any ) = > props . dispatch ( action ) ,
module ,
2021-05-19 15:00:16 +02:00
{
2022-04-14 13:27:19 +02:00
plugins : pluginsRef.current ,
2021-05-19 15:00:16 +02:00
customCss : props.customCss ,
2023-08-22 12:58:53 +02:00
} ,
2021-02-07 12:09:28 +02:00
) ;
} ,
} ) ;
}
} else {
for ( let j = 0 ; j < module .sources.length ; j ++ ) {
const module Source = module .sources [ j ] ;
importItems . push ( {
label : module.fullLabel ( module Source ) ,
click : ( ) = > onImportModuleClickRef . current ( module , module Source ) ,
} ) ;
2023-09-11 18:25:12 +02:00
if ( module .separatorAfter ) importItems . push ( { type : 'separator' } ) ;
2021-02-07 12:09:28 +02:00
}
}
}
2020-10-09 19:35:46 +02:00
2022-02-10 12:54:23 +02:00
importItems . push ( { type : 'separator' } ) ;
importItems . push ( {
label : _ ( 'Other applications...' ) ,
click : ( ) = > { void bridge ( ) . openExternal ( 'https://discourse.joplinapp.org/t/importing-notes-from-other-notebook-applications/22425' ) ; } ,
} ) ;
2021-02-07 12:09:28 +02:00
exportItems . push (
2023-08-22 12:58:53 +02:00
menuItemDic . exportPdf ,
2021-02-07 12:09:28 +02:00
) ;
2020-10-09 19:35:46 +02:00
2021-02-07 12:09:28 +02:00
// We need a dummy entry, otherwise the ternary operator to show a
// menu item only on a specific OS does not work.
const noItem = {
type : 'separator' ,
visible : false ,
} ;
2020-10-31 14:46:55 +02:00
2021-02-07 12:09:28 +02:00
const syncStatusItem = {
label : _ ( 'Synchronisation Status' ) ,
2020-10-31 14:46:55 +02:00
click : ( ) = > {
props . dispatch ( {
type : 'NAV_GO' ,
2021-02-07 12:09:28 +02:00
routeName : 'Status' ,
2020-10-31 14:46:55 +02:00
} ) ;
} ,
2021-02-07 12:09:28 +02:00
} ;
2020-10-09 19:35:46 +02:00
2021-02-07 12:09:28 +02:00
const separator = ( ) = > {
return {
type : 'separator' ,
} ;
} ;
2020-11-05 18:58:23 +02:00
2021-02-07 12:09:28 +02:00
const newNoteItem = menuItemDic . newNote ;
const newTodoItem = menuItemDic . newTodo ;
const newFolderItem = menuItemDic . newFolder ;
const newSubFolderItem = menuItemDic . newSubFolder ;
const printItem = menuItemDic . print ;
2022-04-11 17:49:32 +02:00
const switchProfileItem = {
label : _ ( 'Switch profile' ) ,
submenu : switchProfileMenuItems ,
} ;
2020-10-09 19:35:46 +02:00
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 12:09:28 +02:00
let toolsItems : any [ ] = [ ] ;
2020-10-09 19:35:46 +02:00
2021-02-07 12:09:28 +02:00
// we need this workaround, because on macOS the menu is different
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 12:09:28 +02:00
const toolsItemsWindowsLinux : any [ ] = [
{
label : _ ( 'Options' ) ,
accelerator : keymapService.getAccelerator ( 'config' ) ,
click : ( ) = > {
props . dispatch ( {
type : 'NAV_GO' ,
routeName : 'Config' ,
} ) ;
} ,
} ,
separator ( ) ,
] ;
// the following menu items will be available for all OS under Tools
const toolsItemsAll = [ {
label : _ ( 'Note attachments...' ) ,
2020-10-09 19:35:46 +02:00
click : ( ) = > {
props . dispatch ( {
type : 'NAV_GO' ,
2021-02-07 12:09:28 +02:00
routeName : 'Resources' ,
2020-10-09 19:35:46 +02:00
} ) ;
} ,
2021-02-07 12:09:28 +02:00
} ] ;
2020-10-09 19:35:46 +02:00
2021-02-07 12:09:28 +02:00
if ( ! shim . isMac ( ) ) {
toolsItems = toolsItems . concat ( toolsItemsWindowsLinux ) ;
}
toolsItems = toolsItems . concat ( toolsItemsAll ) ;
2020-10-09 19:35:46 +02:00
2022-08-27 13:05:44 +02:00
toolsItems . push ( SpellCheckerService . instance ( ) . spellCheckerConfigMenuItem ( props [ 'spellChecker.languages' ] , props [ 'spellChecker.enabled' ] ) ) ;
2020-10-12 11:45:00 +02:00
2021-02-07 12:09:28 +02:00
function _checkForUpdates() {
2024-09-21 14:02:22 +02:00
if ( Setting . value ( 'featureFlag.autoUpdaterServiceEnabled' ) ) {
ipcRenderer . send ( 'check-for-updates' ) ;
} else {
void checkForUpdates ( false , bridge ( ) . window ( ) , { includePreReleases : Setting.value ( 'autoUpdate.includePreReleases' ) } ) ;
}
2021-02-07 12:09:28 +02:00
}
2020-10-12 11:45:00 +02:00
2021-02-07 12:09:28 +02:00
function _showAbout() {
2023-04-03 18:01:06 +02:00
const v = versionInfo ( packageInfo , PluginService . instance ( ) . enabledPlugins ( props . pluginSettings ) ) ;
2023-02-08 16:16:09 +02:00
2021-02-07 12:09:28 +02:00
const copyToClipboard = bridge ( ) . showMessageBox ( v . message , {
icon : ` ${ bridge ( ) . electronApp ( ) . buildDir ( ) } /icons/128x128.png ` ,
buttons : [ _ ( 'Copy' ) , _ ( 'OK' ) ] ,
cancelId : 1 ,
defaultId : 1 ,
} ) ;
if ( copyToClipboard === 0 ) {
clipboard . writeText ( v . body ) ;
}
}
const rootMenuFile = {
// Using a dummy entry for macOS here, because first menu
2024-02-26 12:16:23 +02:00
// becomes 'Joplin' and we need a menu called 'File' later.
2021-02-07 12:09:28 +02:00
label : shim.isMac ( ) ? '&JoplinMainMenu' : _ ( '&File' ) ,
// `&` before one of the char in the label name mean, that
2024-02-25 21:37:13 +02:00
// <Alt + F> will open this menu. It's needed because electron
2021-02-07 12:09:28 +02:00
// opens the first menu on Alt press if no hotkey assigned.
// Issue: https://github.com/laurent22/joplin/issues/934
submenu : [ {
label : _ ( 'About Joplin' ) ,
2023-02-21 19:10:53 +02:00
visible : ! ! shim . isMac ( ) ,
2021-02-07 12:09:28 +02:00
click : ( ) = > _showAbout ( ) ,
2020-10-09 19:35:46 +02:00
} , {
type : 'separator' ,
2023-02-21 19:10:53 +02:00
visible : ! ! shim . isMac ( ) ,
2021-02-07 12:09:28 +02:00
} , {
label : _ ( 'Preferences...' ) ,
2023-02-21 19:10:53 +02:00
visible : ! ! shim . isMac ( ) ,
2021-02-07 12:09:28 +02:00
accelerator : shim.isMac ( ) && keymapService . getAccelerator ( 'config' ) ,
click : ( ) = > {
props . dispatch ( {
type : 'NAV_GO' ,
routeName : 'Config' ,
} ) ;
} ,
} , {
label : _ ( 'Check for updates...' ) ,
2023-02-21 19:10:53 +02:00
visible : ! ! shim . isMac ( ) ,
2021-02-07 12:09:28 +02:00
click : ( ) = > _checkForUpdates ( ) ,
} , {
type : 'separator' ,
2023-02-21 19:10:53 +02:00
visible : ! ! shim . isMac ( ) ,
2021-02-07 12:09:28 +02:00
} ,
shim . isMac ( ) ? noItem : newNoteItem ,
shim . isMac ( ) ? noItem : newTodoItem ,
shim . isMac ( ) ? noItem : newFolderItem ,
shim . isMac ( ) ? noItem : newSubFolderItem ,
{
type : 'separator' ,
2023-02-21 19:10:53 +02:00
visible : ! shim . isMac ( ) ,
2020-10-09 19:35:46 +02:00
} , {
label : _ ( 'Import' ) ,
2023-02-21 19:10:53 +02:00
visible : ! shim . isMac ( ) ,
2020-10-09 19:35:46 +02:00
submenu : importItems ,
} , {
2020-12-19 19:42:18 +02:00
label : _ ( 'Export all' ) ,
2023-02-21 19:10:53 +02:00
visible : ! shim . isMac ( ) ,
2020-10-09 19:35:46 +02:00
submenu : exportItems ,
} , {
type : 'separator' ,
} ,
2021-02-07 12:09:28 +02:00
menuItemDic . synchronize ,
2020-10-09 19:35:46 +02:00
2021-02-07 12:09:28 +02:00
shim . isMac ( ) ? noItem : printItem , {
type : 'separator' ,
platforms : [ 'darwin' ] ,
2020-10-09 19:35:46 +02:00
} ,
2022-04-11 17:49:32 +02:00
shim . isMac ( ) ? noItem : switchProfileItem ,
2021-02-07 12:09:28 +02:00
shim . isMac ( ) ? {
label : _ ( 'Hide %s' , 'Joplin' ) ,
platforms : [ 'darwin' ] ,
accelerator : shim.isMac ( ) && keymapService . getAccelerator ( 'hideApp' ) ,
click : ( ) = > { bridge ( ) . electronApp ( ) . hide ( ) ; } ,
} : noItem ,
2021-06-07 00:49:44 +02:00
shim . isMac ( ) ? {
role : 'hideothers' ,
} : noItem ,
shim . isMac ( ) ? {
role : 'unhide' ,
} : noItem ,
2021-02-07 12:09:28 +02:00
{
type : 'separator' ,
} ,
quitMenuItem ] ,
} ;
const rootMenuFileMacOs = {
label : _ ( '&File' ) ,
2023-02-21 19:10:53 +02:00
visible : ! ! shim . isMac ( ) ,
2020-10-09 19:35:46 +02:00
submenu : [
2021-02-07 12:09:28 +02:00
newNoteItem ,
newTodoItem ,
newFolderItem ,
newSubFolderItem ,
2020-10-09 19:35:46 +02:00
{
2021-02-07 12:09:28 +02:00
label : _ ( 'Close Window' ) ,
platforms : [ 'darwin' ] ,
accelerator : shim.isMac ( ) && keymapService . getAccelerator ( 'closeWindow' ) ,
selector : 'performClose:' ,
2020-10-09 19:35:46 +02:00
} , {
2021-02-07 12:09:28 +02:00
type : 'separator' ,
2020-10-09 19:35:46 +02:00
} , {
2021-02-07 12:09:28 +02:00
label : _ ( 'Import' ) ,
submenu : importItems ,
} , {
label : _ ( 'Export all' ) ,
submenu : exportItems ,
} , {
type : 'separator' ,
2020-10-09 19:35:46 +02:00
} ,
2021-02-07 12:09:28 +02:00
printItem ,
2022-04-11 17:49:32 +02:00
switchProfileItem ,
2021-02-07 12:09:28 +02:00
] ,
} ;
const layoutButtonSequenceOptions = Setting . enumOptions ( 'layoutButtonSequence' ) ;
const layoutButtonSequenceMenuItems = [ ] ;
for ( const value in layoutButtonSequenceOptions ) {
layoutButtonSequenceMenuItems . push ( {
id : ` layoutButtonSequence_ ${ value } ` ,
label : layoutButtonSequenceOptions [ value ] ,
type : 'checkbox' ,
click : ( ) = > {
2024-06-25 15:01:39 +02:00
Setting . setValue ( 'layoutButtonSequence' , Number ( value ) ) ;
2021-02-07 12:09:28 +02:00
} ,
} ) ;
}
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 12:09:28 +02:00
const rootMenus : any = {
edit : {
id : 'edit' ,
label : _ ( '&Edit' ) ,
submenu : [
menuItemDic . textCopy ,
menuItemDic . textCut ,
menuItemDic . textPaste ,
2023-02-13 21:16:33 +02:00
menuItemDic . pasteAsText ,
2021-02-07 12:09:28 +02:00
menuItemDic . textSelectAll ,
separator ( ) ,
2022-03-03 15:53:11 +02:00
// Using the generic "undo"/"redo" roles mean the menu
// item will work in every text fields, whether it's the
// editor or a regular text field.
{
role : 'undo' ,
2022-05-05 16:13:27 +02:00
label : _ ( 'Undo' ) ,
2022-03-03 15:53:11 +02:00
} ,
{
role : 'redo' ,
2022-05-05 16:13:27 +02:00
label : _ ( 'Redo' ) ,
2022-03-03 15:53:11 +02:00
} ,
2021-02-07 12:09:28 +02:00
separator ( ) ,
menuItemDic . textBold ,
menuItemDic . textItalic ,
menuItemDic . textLink ,
menuItemDic . textCode ,
separator ( ) ,
menuItemDic . insertDateTime ,
menuItemDic . attachFile ,
separator ( ) ,
menuItemDic [ 'editor.deleteLine' ] ,
2021-04-25 10:57:35 +02:00
menuItemDic [ 'editor.duplicateLine' ] ,
2021-02-07 12:09:28 +02:00
menuItemDic [ 'editor.toggleComment' ] ,
menuItemDic [ 'editor.sortSelectedLines' ] ,
menuItemDic [ 'editor.indentLess' ] ,
menuItemDic [ 'editor.indentMore' ] ,
menuItemDic [ 'editor.swapLineDown' ] ,
menuItemDic [ 'editor.swapLineUp' ] ,
separator ( ) ,
menuItemDic . focusSearch ,
menuItemDic . showLocalSearch ,
] ,
} ,
view : {
label : _ ( '&View' ) ,
submenu : [
menuItemDic . toggleLayoutMoveMode ,
2023-02-17 15:07:18 +02:00
menuItemDic . resetLayout ,
2021-02-07 12:09:28 +02:00
separator ( ) ,
menuItemDic . toggleSideBar ,
2024-05-07 11:57:02 +02:00
shim . isMac ( ) ? noItem : menuItemDic.toggleMenuBar ,
2021-02-07 12:09:28 +02:00
menuItemDic . toggleNoteList ,
menuItemDic . toggleVisiblePanes ,
{
label : _ ( 'Layout button sequence' ) ,
submenu : layoutButtonSequenceMenuItems ,
2020-10-09 19:35:46 +02:00
} ,
2023-09-18 18:40:36 +02:00
{
label : _ ( 'Note list style' ) ,
submenu : noteListMenuItems ,
visible : noteListMenuItems.length > 1 ,
} ,
2021-02-07 12:09:28 +02:00
separator ( ) ,
{
label : Setting.settingMetadata ( 'notes.sortOrder.field' ) . label ( ) ,
submenu : sortNoteItems ,
} , {
label : Setting.settingMetadata ( 'folders.sortOrder.field' ) . label ( ) ,
submenu : sortFolderItems ,
} , {
id : 'showNoteCounts' ,
label : Setting.settingMetadata ( 'showNoteCounts' ) . label ( ) ,
type : 'checkbox' ,
click : ( ) = > {
Setting . setValue ( 'showNoteCounts' , ! Setting . value ( 'showNoteCounts' ) ) ;
} ,
} , {
id : 'uncompletedTodosOnTop' ,
label : Setting.settingMetadata ( 'uncompletedTodosOnTop' ) . label ( ) ,
type : 'checkbox' ,
click : ( ) = > {
Setting . setValue ( 'uncompletedTodosOnTop' , ! Setting . value ( 'uncompletedTodosOnTop' ) ) ;
} ,
} , {
id : 'showCompletedTodos' ,
label : Setting.settingMetadata ( 'showCompletedTodos' ) . label ( ) ,
type : 'checkbox' ,
click : ( ) = > {
Setting . setValue ( 'showCompletedTodos' , ! Setting . value ( 'showCompletedTodos' ) ) ;
} ,
2020-10-09 19:35:46 +02:00
} ,
2021-02-07 12:09:28 +02:00
separator ( ) ,
{
label : _ ( 'Actual Size' ) ,
click : ( ) = > {
Setting . setValue ( 'windowContentZoomFactor' , 100 ) ;
} ,
accelerator : 'CommandOrControl+0' ,
} , {
// There are 2 shortcuts for the action 'zoom in', mainly to increase the user experience.
// Most applications handle this the same way. These applications indicate Ctrl +, but actually mean Ctrl =.
// In fact they allow both: + and =. On the English keyboard layout - and = are used without the shift key.
// So to use Ctrl + would mean to use the shift key, but this is not the case in any of the apps that show Ctrl +.
// Additionally it allows the use of the plus key on the numpad.
label : _ ( 'Zoom In' ) ,
click : ( ) = > {
Setting . incValue ( 'windowContentZoomFactor' , 10 ) ;
} ,
accelerator : 'CommandOrControl+Plus' ,
} , {
label : _ ( 'Zoom In' ) ,
visible : false ,
click : ( ) = > {
Setting . incValue ( 'windowContentZoomFactor' , 10 ) ;
} ,
accelerator : 'CommandOrControl+=' ,
} , {
label : _ ( 'Zoom Out' ) ,
click : ( ) = > {
Setting . incValue ( 'windowContentZoomFactor' , - 10 ) ;
} ,
accelerator : 'CommandOrControl+-' ,
} ] ,
} ,
go : {
label : _ ( '&Go' ) ,
submenu : [
menuItemDic . historyBackward ,
menuItemDic . historyForward ,
separator ( ) ,
{
label : _ ( 'Focus' ) ,
submenu : focusItems ,
2020-10-09 19:35:46 +02:00
} ,
2021-02-07 12:09:28 +02:00
] ,
} ,
2021-05-16 15:21:55 +02:00
folder : {
label : _ ( 'Note&book' ) ,
submenu : [
menuItemDic . showShareFolderDialog ,
] ,
} ,
2021-02-07 12:09:28 +02:00
note : {
label : _ ( '&Note' ) ,
submenu : [
menuItemDic . toggleExternalEditing ,
menuItemDic . setTags ,
2021-05-16 15:21:55 +02:00
menuItemDic . showShareNoteDialog ,
2021-02-07 12:09:28 +02:00
separator ( ) ,
2023-10-23 14:05:06 +02:00
menuItemDic . showNoteProperties ,
2021-02-07 12:09:28 +02:00
menuItemDic . showNoteContentProperties ,
2024-03-02 16:25:27 +02:00
separator ( ) ,
menuItemDic . permanentlyDeleteNote ,
2021-02-07 12:09:28 +02:00
] ,
} ,
tools : {
label : _ ( '&Tools' ) ,
submenu : toolsItems ,
} ,
2024-02-06 18:19:52 +02:00
window : {
id : 'window' ,
// Adds the default MacOS actions (e.g. "tile left") to the menu.
//
// Note: If the dev tools are shown on startup, this adds additional tab-related
// actions to the menu that are not otherwise shown. See https://stackoverflow.com/a/77458809
role : 'windowMenu' ,
label : _ ( '&Window' ) ,
visible : ! ! shim . isMac ( ) ,
submenu : [
{
role : 'minimize' ,
accelerator : shim.isMac ( ) && keymapService . getAccelerator ( 'minimizeWindow' ) ,
} ,
] ,
} ,
2021-02-07 12:09:28 +02:00
help : {
label : _ ( '&Help' ) ,
role : 'help' , // Makes it add the "Search" field on macOS
submenu : [ {
label : _ ( 'Website and documentation' ) ,
accelerator : keymapService.getAccelerator ( 'help' ) ,
2021-12-28 15:17:59 +02:00
click() { void bridge ( ) . openExternal ( 'https://joplinapp.org' ) ; } ,
2020-10-09 19:35:46 +02:00
} , {
2021-02-07 12:09:28 +02:00
label : _ ( 'Joplin Forum' ) ,
2021-12-28 15:17:59 +02:00
click() { void bridge ( ) . openExternal ( 'https://discourse.joplinapp.org' ) ; } ,
2023-02-20 14:45:23 +02:00
} , {
label : _ ( 'Join us on Twitter' ) ,
click() { void bridge ( ) . openExternal ( 'https://twitter.com/joplinapp' ) ; } ,
2021-02-07 12:09:28 +02:00
} , {
label : _ ( 'Make a donation' ) ,
2021-12-28 15:17:59 +02:00
click() { void bridge ( ) . openExternal ( 'https://joplinapp.org/donate/' ) ; } ,
2021-02-07 12:09:28 +02:00
} , {
label : _ ( 'Check for updates...' ) ,
2023-02-21 19:10:53 +02:00
visible : ! shim . isMac ( ) ,
2021-02-07 12:09:28 +02:00
click : ( ) = > _checkForUpdates ( ) ,
2020-10-31 14:46:55 +02:00
} ,
2020-10-09 19:35:46 +02:00
separator ( ) ,
2021-02-07 12:09:28 +02:00
syncStatusItem ,
separator ( ) ,
2024-11-07 15:45:29 +02:00
menuItemDic . exportDeletionLog ,
2021-02-07 12:09:28 +02:00
{
id : 'help:toggleDevTools' ,
label : _ ( 'Toggle development tools' ) ,
click : ( ) = > {
props . dispatch ( {
type : 'NOTE_DEVTOOLS_TOGGLE' ,
} ) ;
} ,
2020-10-09 19:35:46 +02:00
} ,
2021-04-24 20:23:33 +02:00
menuItemDic . toggleSafeMode ,
2021-02-07 12:09:28 +02:00
menuItemDic . openProfileDirectory ,
menuItemDic . copyDevCommand ,
2020-10-09 19:35:46 +02:00
2021-02-07 12:09:28 +02:00
{
type : 'separator' ,
2023-02-21 19:10:53 +02:00
visible : ! shim . isMac ( ) ,
2021-02-07 12:09:28 +02:00
} , {
label : _ ( 'About Joplin' ) ,
2023-02-21 19:10:53 +02:00
visible : ! shim . isMac ( ) ,
2021-02-07 12:09:28 +02:00
click : ( ) = > _showAbout ( ) ,
} ] ,
} ,
} ;
2020-10-09 19:35:46 +02:00
2021-02-07 12:09:28 +02:00
if ( shim . isMac ( ) ) {
rootMenus . macOsApp = rootMenuFile ;
rootMenus . file = rootMenuFileMacOs ;
} else {
rootMenus . file = rootMenuFile ;
2020-10-09 19:35:46 +02:00
}
2021-02-07 12:09:28 +02:00
// It seems the "visible" property of separators is ignored by Electron, making
// it display separators that we want hidden. So this function iterates through
// them and remove them completely.
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 12:09:28 +02:00
const cleanUpSeparators = ( items : any [ ] ) = > {
const output = [ ] ;
for ( const item of items ) {
if ( 'visible' in item && item . type === 'separator' && ! item . visible ) continue ;
output . push ( item ) ;
}
return output ;
} ;
2020-10-09 19:35:46 +02:00
2021-02-07 12:09:28 +02:00
for ( const key in rootMenus ) {
if ( ! rootMenus . hasOwnProperty ( key ) ) continue ;
if ( ! rootMenus [ key ] . submenu ) continue ;
rootMenus [ key ] . submenu = cleanUpSeparators ( rootMenus [ key ] . submenu ) ;
2020-10-13 13:57:03 +02:00
}
2020-10-09 19:35:46 +02:00
2021-08-18 12:54:28 +02:00
rootMenus . go . submenu . push ( menuItemDic . gotoAnything ) ;
rootMenus . tools . submenu . push ( menuItemDic . commandPalette ) ;
2021-10-03 17:00:49 +02:00
rootMenus . tools . submenu . push ( menuItemDic . openMasterPasswordDialog ) ;
2020-10-13 13:57:03 +02:00
2021-02-07 12:09:28 +02:00
for ( const view of props . pluginMenuItems ) {
const location : MenuItemLocation = view . location ;
if ( isContextMenuItemLocation ( location ) ) continue ;
2020-10-09 19:35:46 +02:00
2021-02-07 12:09:28 +02:00
const itemParent = rootMenus [ location ] ;
2020-10-09 19:35:46 +02:00
2021-02-07 12:09:28 +02:00
if ( ! itemParent ) {
reg . logger ( ) . error ( 'Menu item location does not exist: ' , location , view ) ;
} else {
itemParent . submenu . push ( menuItemDic [ view . commandName ] ) ;
}
}
2020-10-09 19:35:46 +02:00
2021-02-07 12:09:28 +02:00
for ( const view of props . pluginMenus ) {
if ( isContextMenuItemLocation ( view . location ) ) continue ;
const itemParent = rootMenus [ view . location ] ;
2020-10-09 19:35:46 +02:00
2021-02-07 12:09:28 +02:00
if ( ! itemParent ) {
reg . logger ( ) . error ( 'Menu location does not exist: ' , location , view ) ;
} else {
itemParent . submenu . push ( createPluginMenuTree ( view . label , view . menuItems , ( commandName : string ) = > onMenuItemClickRef . current ( commandName ) ) ) ;
}
}
2020-10-18 22:52:10 +02:00
2021-02-07 12:09:28 +02:00
const template = [
2024-02-06 18:19:52 +02:00
shim . isMac ( ) ? rootMenus.macOsApp : null ,
2021-02-07 12:09:28 +02:00
rootMenus . file ,
rootMenus . edit ,
rootMenus . view ,
rootMenus . go ,
2021-05-16 15:21:55 +02:00
rootMenus . folder ,
2021-02-07 12:09:28 +02:00
rootMenus . note ,
rootMenus . tools ,
2024-02-06 18:19:52 +02:00
shim . isMac ( ) ? rootMenus.window : null ,
2021-02-07 12:09:28 +02:00
rootMenus . help ,
2024-02-06 18:19:52 +02:00
] . filter ( item = > item !== null ) ;
2020-10-09 19:35:46 +02:00
2021-02-07 12:09:28 +02:00
if ( props . routeName !== 'Main' ) {
setMenu ( Menu . buildFromTemplate ( [
{
label : _ ( '&File' ) ,
submenu : [ quitMenuItem ] ,
} ,
{
label : _ ( '&Edit' ) ,
submenu : [
menuItemDic . textCopy ,
menuItemDic . textCut ,
menuItemDic . textPaste ,
menuItemDic . textSelectAll ,
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-28 15:17:59 +02:00
] as any ,
2021-02-07 12:09:28 +02:00
} ,
] ) ) ;
} else {
setMenu ( Menu . buildFromTemplate ( template ) ) ;
2020-10-09 19:35:46 +02:00
}
}
2021-02-07 12:09:28 +02:00
timeoutId = setTimeout ( updateMenu , 50 ) ;
2020-10-09 19:35:46 +02:00
2021-02-07 12:09:28 +02:00
return ( ) = > {
clearTimeout ( timeoutId ) ;
timeoutId = null ;
} ;
2022-08-19 13:10:04 +02:00
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
2022-04-11 17:49:32 +02:00
} , [
props . routeName ,
props . pluginMenuItems ,
props . pluginMenus ,
keymapLastChangeTime ,
module sLastChangeTime ,
2022-08-19 13:10:04 +02:00
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
2022-08-27 13:05:44 +02:00
props [ 'spellChecker.languages' ] ,
2022-08-19 13:10:04 +02:00
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
2022-04-11 17:49:32 +02:00
props [ 'spellChecker.enabled' ] ,
2023-09-18 18:40:36 +02:00
noteListMenuItems ,
2023-04-03 18:01:06 +02:00
props . pluginSettings ,
2022-04-11 17:49:32 +02:00
props . customCss ,
props . locale ,
props . profileConfig ,
switchProfileMenuItems ,
menuItemDic ,
] ) ;
2021-02-07 12:09:28 +02:00
useMenuStates ( menu , props ) ;
2020-10-09 19:35:46 +02:00
useEffect ( ( ) = > {
function onKeymapChange() {
setKeymapLastChangeTime ( Date . now ( ) ) ;
}
2023-12-13 21:24:58 +02:00
KeymapService . instance ( ) . on ( EventName . KeymapChange , onKeymapChange ) ;
2020-10-09 19:35:46 +02:00
return ( ) = > {
2023-12-13 21:24:58 +02:00
KeymapService . instance ( ) . off ( EventName . KeymapChange , onKeymapChange ) ;
2020-10-09 19:35:46 +02:00
} ;
} , [ ] ) ;
useEffect ( ( ) = > {
function onModulesChanged() {
setModulesLastChangeTime ( Date . now ( ) ) ;
}
InteropService . instance ( ) . on ( 'modulesChanged' , onModulesChanged ) ;
return ( ) = > {
InteropService . instance ( ) . off ( 'modulesChanged' , onModulesChanged ) ;
} ;
} , [ ] ) ;
return menu ;
}
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
function MenuBar ( props : Props ) : any {
2020-10-09 19:35:46 +02:00
const menu = useMenu ( props ) ;
if ( menu ) Menu . setApplicationMenu ( menu ) ;
2024-05-07 11:57:02 +02:00
applyMenuBarVisibility ( props . showMenuBar ) ;
2020-10-09 19:35:46 +02:00
return null ;
}
2020-11-12 21:13:28 +02:00
const mapStateToProps = ( state : AppState ) = > {
2020-10-18 22:52:10 +02:00
const whenClauseContext = stateToWhenClauseContext ( state ) ;
2020-10-09 19:35:46 +02:00
return {
2022-04-11 17:49:32 +02:00
menuItemProps : menuUtils.commandsToMenuItemProps ( commandNames . concat ( getPluginCommandNames ( state . pluginService . plugins ) ) , whenClauseContext ) ,
2021-12-31 08:50:32 +02:00
locale : state.settings.locale ,
2020-10-09 19:35:46 +02:00
routeName : state.route.routeName ,
selectedFolderId : state.selectedFolderId ,
layoutButtonSequence : state.settings.layoutButtonSequence ,
[ 'notes.sortOrder.field' ] : state . settings [ 'notes.sortOrder.field' ] ,
[ 'folders.sortOrder.field' ] : state . settings [ 'folders.sortOrder.field' ] ,
[ 'notes.sortOrder.reverse' ] : state . settings [ 'notes.sortOrder.reverse' ] ,
[ 'folders.sortOrder.reverse' ] : state . settings [ 'folders.sortOrder.reverse' ] ,
2023-04-03 18:01:06 +02:00
pluginSettings : state.settings [ 'plugins.states' ] ,
2020-10-09 19:35:46 +02:00
showNoteCounts : state.settings.showNoteCounts ,
uncompletedTodosOnTop : state.settings.uncompletedTodosOnTop ,
showCompletedTodos : state.settings.showCompletedTodos ,
2020-10-13 13:57:03 +02:00
pluginMenuItems : stateUtils.selectArrayShallow ( { array : pluginUtils.viewsByType ( state . pluginService . plugins , 'menuItem' ) } , 'menuBar.pluginMenuItems' ) ,
pluginMenus : stateUtils.selectArrayShallow ( { array : pluginUtils.viewsByType ( state . pluginService . plugins , 'menu' ) } , 'menuBar.pluginMenus' ) ,
2022-08-27 13:05:44 +02:00
[ 'spellChecker.languages' ] : state . settings [ 'spellChecker.languages' ] ,
2020-11-05 18:58:23 +02:00
[ 'spellChecker.enabled' ] : state . settings [ 'spellChecker.enabled' ] ,
2020-12-19 19:42:18 +02:00
plugins : state.pluginService.plugins ,
2021-05-19 15:00:16 +02:00
customCss : state.customCss ,
2022-04-11 17:49:32 +02:00
profileConfig : state.profileConfig ,
2023-09-18 18:40:36 +02:00
noteListRendererIds : state.noteListRendererIds ,
noteListRendererId : state.settings [ 'notes.listRendererId' ] ,
2024-05-07 11:57:02 +02:00
showMenuBar : state.settings.showMenuBar ,
2020-10-09 19:35:46 +02:00
} ;
} ;
export default connect ( mapStateToProps ) ( MenuBar ) ;