mirror of
https://github.com/zadam/trilium.git
synced 2025-06-05 09:28:45 +02:00
spell check context menu
This commit is contained in:
parent
16f42dd4ab
commit
1239293435
@ -1,5 +1,4 @@
|
||||
import glob from './services/glob.js';
|
||||
import contextMenu from './services/tree_context_menu.js';
|
||||
import link from './services/link.js';
|
||||
import ws from './services/ws.js';
|
||||
import noteType from './widgets/note_type.js';
|
||||
@ -66,7 +65,7 @@ import RenderTypeWidget from "./widgets/type_widgets/render.js";
|
||||
import RelationMapTypeWidget from "./widgets/type_widgets/relation_map.js";
|
||||
import ProtectedSessionTypeWidget from "./widgets/type_widgets/protected_session.js";
|
||||
import BookTypeWidget from "./widgets/type_widgets/book.js";
|
||||
import contextMenuService from "./services/context_menu.js";
|
||||
import contextMenu from "./services/context_menu.js";
|
||||
|
||||
if (utils.isElectron()) {
|
||||
require('electron').ipcRenderer.on('globalShortcut', async function(event, actionName) {
|
||||
@ -87,39 +86,107 @@ noteTooltipService.setupGlobalTooltip();
|
||||
noteAutocompleteService.init();
|
||||
|
||||
if (utils.isElectron()) {
|
||||
const {webContents} = require('electron').remote.getCurrentWindow();
|
||||
const electron = require('electron');
|
||||
const {webContents} = electron.remote.getCurrentWindow();
|
||||
|
||||
webContents.on('context-menu', (event, params) => {
|
||||
const items = [
|
||||
{title: "Hello", cmd: "openNoteInNewTab", uiIcon: "arrow-up-right"}
|
||||
];
|
||||
const {editFlags} = params;
|
||||
const hasText = params.selectionText.trim().length > 0;
|
||||
const isMac = process.platform === "darwin";
|
||||
const platformModifier = isMac ? 'Meta' : 'Ctrl';
|
||||
|
||||
const items = [];
|
||||
|
||||
if (params.misspelledWord) {
|
||||
items.push({
|
||||
title: `Misspelled "<strong>${params.misspelledWord}</strong>"`,
|
||||
cmd: "openNoteInNewTab",
|
||||
uiIcon: ""
|
||||
});
|
||||
|
||||
for (const suggestion of params.dictionarySuggestions) {
|
||||
items.push({
|
||||
title: suggestion,
|
||||
command: "replaceMisspelling",
|
||||
spellingSuggestion: suggestion,
|
||||
uiIcon: ""
|
||||
uiIcon: "empty"
|
||||
});
|
||||
}
|
||||
|
||||
items.push({
|
||||
title: `Add "${params.misspelledWord}" to dictionary`,
|
||||
uiIcon: "plus",
|
||||
handler: () => webContents.session.addWordToSpellCheckerDictionary(params.misspelledWord)
|
||||
});
|
||||
|
||||
items.push({ title: `----` });
|
||||
}
|
||||
|
||||
contextMenuService.initContextMenu({
|
||||
if (params.isEditable) {
|
||||
items.push({
|
||||
enabled: editFlags.canCut && hasText,
|
||||
title: `Cut <kbd>${platformModifier}+X`,
|
||||
uiIcon: "cut",
|
||||
handler: () => webContents.cut()
|
||||
});
|
||||
}
|
||||
|
||||
if (params.isEditable || hasText) {
|
||||
items.push({
|
||||
enabled: editFlags.canCopy && hasText,
|
||||
title: `Copy <kbd>${platformModifier}+C`,
|
||||
uiIcon: "copy",
|
||||
handler: () => webContents.copy()
|
||||
});
|
||||
}
|
||||
|
||||
if (params.linkURL.length !== 0 && params.mediaType === 'none') {
|
||||
items.push({
|
||||
title: `Copy link`,
|
||||
uiIcon: "copy",
|
||||
handler: () => {
|
||||
electron.clipboard.write({
|
||||
bookmark: params.linkText,
|
||||
text: params.linkURL
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (params.isEditable) {
|
||||
items.push({
|
||||
enabled: editFlags.canPaste,
|
||||
title: `Paste <kbd>${platformModifier}+V`,
|
||||
uiIcon: "paste",
|
||||
handler: () => webContents.paste()
|
||||
});
|
||||
}
|
||||
|
||||
if (params.isEditable) {
|
||||
items.push({
|
||||
enabled: editFlags.canPaste,
|
||||
title: `Paste as plain text <kbd>${platformModifier}+Shift+V`,
|
||||
uiIcon: "paste",
|
||||
handler: () => webContents.pasteAndMatchStyle()
|
||||
});
|
||||
}
|
||||
|
||||
if (hasText) {
|
||||
const shortenedSelection = params.selectionText.length > 15
|
||||
? (params.selectionText.substr(0, 13) + "…")
|
||||
: params.selectionText;
|
||||
|
||||
items.push({
|
||||
enabled: editFlags.canPaste,
|
||||
title: `Search for "${shortenedSelection}" with DuckDuckGo`,
|
||||
uiIcon: "search-alt",
|
||||
handler: () => electron.shell.openExternal(`https://duckduckgo.com/?q=${encodeURIComponent(params.selectionText)}`)
|
||||
});
|
||||
}
|
||||
|
||||
contextMenu.show({
|
||||
x: params.x,
|
||||
y: params.y,
|
||||
items,
|
||||
selectContextMenuItem: (e, {command, spellingSuggestion}) => {
|
||||
selectMenuItemHandler: ({command, spellingSuggestion}) => {
|
||||
if (command === 'replaceMisspelling') {
|
||||
console.log("Replacing missspeling", spellingSuggestion);
|
||||
|
||||
require('electron').remote.getCurrentWindow().webContents.insertText(spellingSuggestion);
|
||||
webContents.insertText(spellingSuggestion);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1,10 +1,48 @@
|
||||
import keyboardActionService from './keyboard_actions.js';
|
||||
const $contextMenuContainer = $("#context-menu-container");
|
||||
|
||||
let dateContextMenuOpenedMs = 0;
|
||||
class ContextMenu {
|
||||
constructor() {
|
||||
this.$widget = $("#context-menu-container");
|
||||
this.dateContextMenuOpenedMs = 0;
|
||||
|
||||
async function initContextMenu(options) {
|
||||
function addItems($parent, items) {
|
||||
$(document).on('click', () => this.hide());
|
||||
}
|
||||
|
||||
async show(options) {
|
||||
this.options = options;
|
||||
|
||||
this.$widget.empty();
|
||||
|
||||
this.addItems(this.$widget, options.items);
|
||||
|
||||
keyboardActionService.updateDisplayedShortcuts(this.$widget);
|
||||
|
||||
this.positionMenu();
|
||||
|
||||
this.dateContextMenuOpenedMs = Date.now();
|
||||
}
|
||||
|
||||
positionMenu() {
|
||||
// code below tries to detect when dropdown would overflow from page
|
||||
// in such case we'll position it above click coordinates so it will fit into client
|
||||
const clientHeight = document.documentElement.clientHeight;
|
||||
const contextMenuHeight = this.$widget.outerHeight() + 30;
|
||||
let top;
|
||||
|
||||
if (this.options.y + contextMenuHeight > clientHeight) {
|
||||
top = clientHeight - contextMenuHeight - 10;
|
||||
} else {
|
||||
top = this.options.y - 10;
|
||||
}
|
||||
|
||||
this.$widget.css({
|
||||
display: "block",
|
||||
top: top,
|
||||
left: this.options.x - 20
|
||||
}).addClass("show");
|
||||
}
|
||||
|
||||
addItems($parent, items) {
|
||||
for (const item of items) {
|
||||
if (item.title === '----') {
|
||||
$parent.append($("<div>").addClass("dropdown-divider"));
|
||||
@ -25,14 +63,20 @@ async function initContextMenu(options) {
|
||||
const $item = $("<li>")
|
||||
.addClass("dropdown-item")
|
||||
.append($link)
|
||||
.on('mousedown', function (e) {
|
||||
// important to use mousedown instead of click since the former does not change focus
|
||||
// (especially important for focused text for spell check)
|
||||
.on('mousedown', (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
hideContextMenu();
|
||||
this.hide();
|
||||
|
||||
e.originalTarget = event.target;
|
||||
|
||||
options.selectContextMenuItem(e, item);
|
||||
if (item.handler) {
|
||||
item.handler(item, e);
|
||||
}
|
||||
|
||||
this.options.selectMenuItemHandler(item, e);
|
||||
|
||||
// it's important to stop the propagation especially for sub-menus, otherwise the event
|
||||
// might be handled again by top-level menu
|
||||
@ -49,7 +93,7 @@ async function initContextMenu(options) {
|
||||
|
||||
const $subMenu = $("<ul>").addClass("dropdown-menu");
|
||||
|
||||
addItems($subMenu, item.items);
|
||||
this.addItems($subMenu, item.items);
|
||||
|
||||
$item.append($subMenu);
|
||||
}
|
||||
@ -59,45 +103,16 @@ async function initContextMenu(options) {
|
||||
}
|
||||
}
|
||||
|
||||
$contextMenuContainer.empty();
|
||||
|
||||
addItems($contextMenuContainer, options.items);
|
||||
|
||||
keyboardActionService.updateDisplayedShortcuts($contextMenuContainer);
|
||||
|
||||
// code below tries to detect when dropdown would overflow from page
|
||||
// in such case we'll position it above click coordinates so it will fit into client
|
||||
const clientHeight = document.documentElement.clientHeight;
|
||||
const contextMenuHeight = $contextMenuContainer.outerHeight() + 30;
|
||||
let top;
|
||||
|
||||
if (options.y + contextMenuHeight > clientHeight) {
|
||||
top = clientHeight - contextMenuHeight - 10;
|
||||
} else {
|
||||
top = options.y - 10;
|
||||
}
|
||||
|
||||
dateContextMenuOpenedMs = Date.now();
|
||||
|
||||
$contextMenuContainer.css({
|
||||
display: "block",
|
||||
top: top,
|
||||
left: options.x - 20
|
||||
}).addClass("show");
|
||||
}
|
||||
|
||||
$(document).on('click', () => hideContextMenu());
|
||||
|
||||
function hideContextMenu() {
|
||||
// this date checking comes from change in FF66 - https://github.com/zadam/trilium/issues/468
|
||||
// "contextmenu" event also triggers "click" event which depending on the timing can close just opened context menu
|
||||
// we might filter out right clicks, but then it's better if even right clicks close the context menu
|
||||
if (Date.now() - dateContextMenuOpenedMs > 300) {
|
||||
$contextMenuContainer.hide();
|
||||
hide() {
|
||||
// this date checking comes from change in FF66 - https://github.com/zadam/trilium/issues/468
|
||||
// "contextmenu" event also triggers "click" event which depending on the timing can close just opened context menu
|
||||
// we might filter out right clicks, but then it's better if even right clicks close the context menu
|
||||
if (Date.now() - this.dateContextMenuOpenedMs > 300) {
|
||||
this.$widget.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
initContextMenu,
|
||||
hideContextMenu
|
||||
}
|
||||
const contextMenu = new ContextMenu();
|
||||
|
||||
export default contextMenu;
|
@ -1,7 +1,7 @@
|
||||
import hoistedNoteService from "../services/hoisted_note.js";
|
||||
import treeService from "../services/tree.js";
|
||||
import utils from "../services/utils.js";
|
||||
import contextMenuWidget from "../services/context_menu.js";
|
||||
import contextMenu from "../services/context_menu.js";
|
||||
import treeCache from "../services/tree_cache.js";
|
||||
import treeBuilder from "../services/tree_builder.js";
|
||||
import TreeContextMenu from "../services/tree_context_menu.js";
|
||||
@ -97,7 +97,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
},
|
||||
activate: async (event, data) => {
|
||||
// click event won't propagate so let's close context menu manually
|
||||
contextMenuWidget.hideContextMenu();
|
||||
contextMenu.hide();
|
||||
|
||||
const notePath = treeService.getNotePath(data.node);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user