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';
|
2020-12-23 23:17:12 +00:00
|
|
|
import convertToScreenCoordinates from '../../../../utils/convertToScreenCoordinates';
|
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.
|
|
|
|
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;
|
|
|
|
|
2021-01-07 21:44:31 +00:00
|
|
|
const iframeRect = convertToScreenCoordinates(Setting.value('windowContentZoomFactor'), iframes[0].getBoundingClientRect());
|
2020-11-14 00:02:17 +00:00
|
|
|
|
|
|
|
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 };
|
|
|
|
|
2023-06-30 10:30:29 +01:00
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-types -- 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
|
|
|
|
|
|
|
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
|
|
|
}
|