mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 18:08:33 +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 glob from './services/glob.js';
|
||||||
import contextMenu from './services/tree_context_menu.js';
|
|
||||||
import link from './services/link.js';
|
import link from './services/link.js';
|
||||||
import ws from './services/ws.js';
|
import ws from './services/ws.js';
|
||||||
import noteType from './widgets/note_type.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 RelationMapTypeWidget from "./widgets/type_widgets/relation_map.js";
|
||||||
import ProtectedSessionTypeWidget from "./widgets/type_widgets/protected_session.js";
|
import ProtectedSessionTypeWidget from "./widgets/type_widgets/protected_session.js";
|
||||||
import BookTypeWidget from "./widgets/type_widgets/book.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()) {
|
if (utils.isElectron()) {
|
||||||
require('electron').ipcRenderer.on('globalShortcut', async function(event, actionName) {
|
require('electron').ipcRenderer.on('globalShortcut', async function(event, actionName) {
|
||||||
@ -87,39 +86,107 @@ noteTooltipService.setupGlobalTooltip();
|
|||||||
noteAutocompleteService.init();
|
noteAutocompleteService.init();
|
||||||
|
|
||||||
if (utils.isElectron()) {
|
if (utils.isElectron()) {
|
||||||
const {webContents} = require('electron').remote.getCurrentWindow();
|
const electron = require('electron');
|
||||||
|
const {webContents} = electron.remote.getCurrentWindow();
|
||||||
|
|
||||||
webContents.on('context-menu', (event, params) => {
|
webContents.on('context-menu', (event, params) => {
|
||||||
const items = [
|
const {editFlags} = params;
|
||||||
{title: "Hello", cmd: "openNoteInNewTab", uiIcon: "arrow-up-right"}
|
const hasText = params.selectionText.trim().length > 0;
|
||||||
];
|
const isMac = process.platform === "darwin";
|
||||||
|
const platformModifier = isMac ? 'Meta' : 'Ctrl';
|
||||||
|
|
||||||
|
const items = [];
|
||||||
|
|
||||||
if (params.misspelledWord) {
|
if (params.misspelledWord) {
|
||||||
items.push({
|
|
||||||
title: `Misspelled "<strong>${params.misspelledWord}</strong>"`,
|
|
||||||
cmd: "openNoteInNewTab",
|
|
||||||
uiIcon: ""
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const suggestion of params.dictionarySuggestions) {
|
for (const suggestion of params.dictionarySuggestions) {
|
||||||
items.push({
|
items.push({
|
||||||
title: suggestion,
|
title: suggestion,
|
||||||
command: "replaceMisspelling",
|
command: "replaceMisspelling",
|
||||||
spellingSuggestion: suggestion,
|
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,
|
x: params.x,
|
||||||
y: params.y,
|
y: params.y,
|
||||||
items,
|
items,
|
||||||
selectContextMenuItem: (e, {command, spellingSuggestion}) => {
|
selectMenuItemHandler: ({command, spellingSuggestion}) => {
|
||||||
if (command === 'replaceMisspelling') {
|
if (command === 'replaceMisspelling') {
|
||||||
console.log("Replacing missspeling", spellingSuggestion);
|
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';
|
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) {
|
$(document).on('click', () => this.hide());
|
||||||
function addItems($parent, items) {
|
}
|
||||||
|
|
||||||
|
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) {
|
for (const item of items) {
|
||||||
if (item.title === '----') {
|
if (item.title === '----') {
|
||||||
$parent.append($("<div>").addClass("dropdown-divider"));
|
$parent.append($("<div>").addClass("dropdown-divider"));
|
||||||
@ -25,14 +63,20 @@ async function initContextMenu(options) {
|
|||||||
const $item = $("<li>")
|
const $item = $("<li>")
|
||||||
.addClass("dropdown-item")
|
.addClass("dropdown-item")
|
||||||
.append($link)
|
.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();
|
e.stopPropagation();
|
||||||
|
|
||||||
hideContextMenu();
|
this.hide();
|
||||||
|
|
||||||
e.originalTarget = event.target;
|
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
|
// it's important to stop the propagation especially for sub-menus, otherwise the event
|
||||||
// might be handled again by top-level menu
|
// might be handled again by top-level menu
|
||||||
@ -49,7 +93,7 @@ async function initContextMenu(options) {
|
|||||||
|
|
||||||
const $subMenu = $("<ul>").addClass("dropdown-menu");
|
const $subMenu = $("<ul>").addClass("dropdown-menu");
|
||||||
|
|
||||||
addItems($subMenu, item.items);
|
this.addItems($subMenu, item.items);
|
||||||
|
|
||||||
$item.append($subMenu);
|
$item.append($subMenu);
|
||||||
}
|
}
|
||||||
@ -59,45 +103,16 @@ async function initContextMenu(options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$contextMenuContainer.empty();
|
hide() {
|
||||||
|
// this date checking comes from change in FF66 - https://github.com/zadam/trilium/issues/468
|
||||||
addItems($contextMenuContainer, options.items);
|
// "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
|
||||||
keyboardActionService.updateDisplayedShortcuts($contextMenuContainer);
|
if (Date.now() - this.dateContextMenuOpenedMs > 300) {
|
||||||
|
this.$widget.hide();
|
||||||
// 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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
const contextMenu = new ContextMenu();
|
||||||
initContextMenu,
|
|
||||||
hideContextMenu
|
export default contextMenu;
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
import hoistedNoteService from "../services/hoisted_note.js";
|
import hoistedNoteService from "../services/hoisted_note.js";
|
||||||
import treeService from "../services/tree.js";
|
import treeService from "../services/tree.js";
|
||||||
import utils from "../services/utils.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 treeCache from "../services/tree_cache.js";
|
||||||
import treeBuilder from "../services/tree_builder.js";
|
import treeBuilder from "../services/tree_builder.js";
|
||||||
import TreeContextMenu from "../services/tree_context_menu.js";
|
import TreeContextMenu from "../services/tree_context_menu.js";
|
||||||
@ -97,7 +97,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
|||||||
},
|
},
|
||||||
activate: async (event, data) => {
|
activate: async (event, data) => {
|
||||||
// click event won't propagate so let's close context menu manually
|
// click event won't propagate so let's close context menu manually
|
||||||
contextMenuWidget.hideContextMenu();
|
contextMenu.hide();
|
||||||
|
|
||||||
const notePath = treeService.getNotePath(data.node);
|
const notePath = treeService.getNotePath(data.node);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user