2020-11-14 00:02:17 +00:00
import { MenuItemLocation } from '@joplin/lib/services/plugins/api/types' ;
import { PluginStates } from '@joplin/lib/services/plugins/reducer' ;
import SpellCheckerService from '@joplin/lib/services/spellChecker/SpellCheckerService' ;
import { useEffect } from 'react' ;
import bridge from '../../../../../services/bridge' ;
2022-03-28 21:40:29 +05:30
import { ContextMenuOptions , ContextMenuItemType } from '../../../utils/contextMenuUtils' ;
import { menuItems } from '../../../utils/contextMenu' ;
2020-11-14 00:02:17 +00:00
import MenuUtils from '@joplin/lib/services/commands/MenuUtils' ;
import CommandService from '@joplin/lib/services/CommandService' ;
2021-01-07 21:44:31 +00:00
import Setting from '@joplin/lib/models/Setting' ;
2020-11-14 00:02:17 +00:00
2021-01-22 17:41:11 +00:00
import Resource from '@joplin/lib/models/Resource' ;
2023-02-15 10:59:32 -03:00
import { TinyMceEditorEvents } from './types' ;
2024-01-26 19:11:05 +00:00
import { HtmlToMarkdownHandler , MarkupToHtmlHandler } from '../../../utils/types' ;
2020-11-14 00:02:17 +00:00
const menuUtils = new MenuUtils ( CommandService . instance ( ) ) ;
// 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.
2024-04-05 12:16:49 +01:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2020-11-14 00:02:17 +00:00
function contextMenuElement ( editor : any , x : number , y : number ) {
if ( ! editor || ! editor . getDoc ( ) ) return null ;
const iframes = document . getElementsByClassName ( 'tox-edit-area__iframe' ) ;
if ( ! iframes . length ) return null ;
2024-02-19 02:04:54 -08:00
const zoom = Setting . value ( 'windowContentZoomFactor' ) / 100 ;
const xScreen = x / zoom ;
const yScreen = y / zoom ;
2020-11-14 00:02:17 +00:00
2024-02-19 02:04:54 -08:00
// We use .elementFromPoint to handle the case where a dialog is covering
// part of the editor.
const targetElement = document . elementFromPoint ( xScreen , yScreen ) ;
if ( targetElement !== iframes [ 0 ] ) {
return null ;
2020-11-14 00:02:17 +00:00
}
2024-02-19 02:04:54 -08:00
const iframeRect = iframes [ 0 ] . getBoundingClientRect ( ) ;
const relativeX = xScreen - iframeRect . left ;
const relativeY = yScreen - iframeRect . top ;
return editor . getDoc ( ) . elementFromPoint ( relativeX , relativeY ) ;
2020-11-14 00:02:17 +00:00
}
interface ContextMenuActionOptions {
current : ContextMenuOptions ;
}
const contextMenuActionOptions : ContextMenuActionOptions = { current : null } ;
2024-04-05 12:16:49 +01:00
// eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-explicit-any -- Old code before rule was applied, Old code before rule was applied
2024-01-26 19:11:05 +00:00
export default function ( editor : any , plugins : PluginStates , dispatch : Function , htmlToMd : HtmlToMarkdownHandler , mdToHtml : MarkupToHtmlHandler ) {
2020-11-14 00:02:17 +00:00
useEffect ( ( ) = > {
if ( ! editor ) return ( ) = > { } ;
2024-01-26 19:11:05 +00:00
const contextMenuItems = menuItems ( dispatch , htmlToMd , mdToHtml ) ;
2020-11-14 00:02:17 +00:00
2024-04-05 12:16:49 +01:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2020-11-14 00:02:17 +00:00
function onContextMenu ( _event : any , params : any ) {
const element = contextMenuElement ( editor , params . x , params . y ) ;
if ( ! element ) return ;
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 ,
2022-03-28 21:40:29 +05:30
filename : null ,
mime : null ,
2020-11-14 00:02:17 +00:00
linkToCopy ,
textToCopy : null ,
htmlToCopy : editor.selection ? editor . selection . getContent ( ) : '' ,
insertContent : ( content : string ) = > {
editor . insertContent ( content ) ;
} ,
isReadOnly : false ,
2023-02-15 10:59:32 -03:00
fireEditorEvent : ( event : TinyMceEditorEvents ) = > {
editor . fire ( event ) ;
} ,
2024-01-26 19:11:05 +00:00
htmlToMd ,
mdToHtml ,
2020-11-14 00:02:17 +00:00
} ;
let template = [ ] ;
for ( const itemName in contextMenuItems ) {
const item = contextMenuItems [ itemName ] ;
if ( ! item . isActive ( itemType , contextMenuActionOptions . current ) ) continue ;
template . push ( {
label : item.label ,
click : ( ) = > {
item . onAction ( contextMenuActionOptions . current ) ;
} ,
} ) ;
}
const spellCheckerMenuItems = SpellCheckerService . instance ( ) . contextMenuItems ( params . misspelledWord , params . dictionarySuggestions ) ;
for ( const item of spellCheckerMenuItems ) {
template . push ( item ) ;
}
template = template . concat ( menuUtils . pluginContextMenuItems ( plugins , MenuItemLocation . EditorContextMenu ) ) ;
const menu = bridge ( ) . Menu . buildFromTemplate ( template ) ;
2023-02-22 18:15:21 +00:00
menu . popup ( { window : bridge ( ) . window ( ) } ) ;
2020-11-14 00:02:17 +00:00
}
bridge ( ) . window ( ) . webContents . on ( 'context-menu' , onContextMenu ) ;
return ( ) = > {
if ( bridge ( ) . window ( ) ? . webContents ? . off ) {
bridge ( ) . window ( ) . webContents . off ( 'context-menu' , onContextMenu ) ;
}
} ;
2024-01-26 19:11:05 +00:00
} , [ editor , plugins , dispatch , htmlToMd , mdToHtml ] ) ;
2020-11-14 00:02:17 +00:00
}