Quill: How to handle keyboard events on custom blot - javascript

I've created custom inline-blot and want to handle keyboard events on it.
In constructor i wrote code like this:
class FooBlot extends Inline {
constructor(domNode, value){
super(domNode, value);
domNode.addEventListener('keydown', (event) => {this.keydown_handler(event)});
domNode.addEventListener('click', (event) => {this.click_handler(event)});
};
When i try to do something with my blot, only click event was handled, not keydown event.
You can see code example here.
Open console, click on sometext and you will see "clicked" in console.
But if you try to press some keyboard buttons, e.g. arrows, you will not see anything.
What the right way to handle keyboard events on my custom blot?

The right way to handle keyboard events is to use Keyboard Module
Simple example to handle Enter key:
const bindings = {
enter: {
key: 13,
shiftKey: null,
handler: (range, context) => {
// Handle enter
}
}
};
this.quill = new Quill('#editor-container', {
modules: {
keyboard: {
bindings
},
toolbar: '#toolbar'
},
theme: 'snow'
});
UPDATE
Another way:
quill.root.addEventListener('keydown', evt => {
// Your code goes here
});

Related

Leaflet geoman "Button with this name already exists" error when creating a new button in react

I am trying to create a custom button for arrows in the drawing tool of leaflet-geoman.
The idea was to work with the copyDrawControl function, and to use Line as a model to make Polylines with arrow tips.
I wrote a code mostly inspired from this demonstration https://codesandbox.io/s/394eq?file=/src/index.js and modified it for my goals. Here is the code :
import { useEffect } from "react";
import { useLeafletContext } from "#react-leaflet/core";
import "#geoman-io/leaflet-geoman-free";
import "#geoman-io/leaflet-geoman-free/dist/leaflet-geoman.css";
const Geoman = () => {
const context = useLeafletContext();
useEffect(() => {
const leafletContainer = context.layerContainer || context.map;
leafletContainer.pm.setGlobalOptions({ pmIgnore: false });
//draw control options
leafletContainer.pm.addControls({
positions: {
draw: 'topleft',
edit: 'topright',
},
drawMarker: false,
rotateMode: false,
cutPolygon: false,
position: "bottomright"
});
//new button
leafletContainer.pm.Toolbar.copyDrawControl('Line', {
name: 'SoonToBeArrow',
block: 'draw',
title: 'Display text on hover button',
actions: [
// uses the default 'cancel' action
'cancel',
],
});
return () => {
leafletContainer.pm.removeControls();
leafletContainer.pm.setGlobalOptions({ pmIgnore: true });
};
}, [context]);
return null;
};
export default Geoman;
When trying to add the copyDrawControl, I faced a bug that would announce that "Button with this name already exists"
I suspect its because I add the button inside a useEffect that gets called several times, but it's also the only way to access leafletContainer, since it must be updated everytime the context changes.
I tried creating another useEffect that contains the same context and my new button, but it did not work.
Does anyone have any suggestion on how to solve this ?
Thnak you in advance
You only want to run this effect once, just after context becomes available. In order to do this, we can make a state variable to track whether or not you've already added the control:
const Geoman = () => {
const context = useLeafletContext();
const [added, setAdded] = useState(false);
useEffect(() => {
const leafletContainer = context.layerContainer || context.map;
// if the context is ready, and we've not yet added the control
if (leafletContainer && !added){
leafletContainer.pm.setGlobalOptions({ pmIgnore: false });
//draw control options
leafletContainer.pm.addControls({
// ...
});
//new button
leafletContainer.pm.Toolbar.copyDrawControl('Line', {
// ...
});
// register that we've already added the control
setAdded(true);
}
return () => {
leafletContainer.pm.removeControls();
leafletContainer.pm.setGlobalOptions({ pmIgnore: true });
};
}, [context]);
return null;
};
In this way, you effect will run whenever context changes - once context is ready, you add the control. You register that you've added the control, but then your if statement will make sure that further changes in context will not try to keep adding controls again and again.
BTW, a second option to using leaflet geoman with react leaflet is to use the official createControlComponent hook to create custom controls. This is not at all straightforward with leaflet-geoman, as createControlComponent requires you to feed it an instance of an L.Control that has all the required hooks and initializer methods. geoman does not have these - it is quite different in the way it initializes and adds to a map. However, you can create an L.Control from geoman methods, and then feed it to createControlComponent.
Create the L.Control:
/**
* Class abstraction wrapper around geoman, so that we can create an instance
* that is an extension of L.Control, so that react-leaflet can call all
* L.PM methods using the expected L.Control lifecycle event handlers
*/
const GeomanControl = L.Control.extend({
initialize(options: Props) {
L.PM.setOptIn(options.optIn ?? false);
L.setOptions(this, options);
},
addTo(map: L.Map) {
const { globalOptions, events } = this.options;
// This should never happen, but its better than crashing the page
if (!map.pm) return;
map.pm.addControls(toolbarOptions);
map.pm.setGlobalOptions({
pmIgnore: false,
...globalOptions,
});
// draw control options
map.pm.addControls({
// ...
});
// new button
map.pm.Toolbar.copyDrawControl('Line', {
// ...
});
Object.entries(events ?? {}).forEach(([eventName, handler]) => {
map.on(eventName, handler);
});
},
});
Then simply use createControlComponent
const createControl = (props) => {
return new GeomanControl(props);
};
export const Geoman = createControlComponent(createControl);
You can add quite a lot of logic into the addTo method, and base a lot of its behaviors off the props you feed to <Geoman />. This is another flexible way of adapting geoman for react-leaflet v4.

How can I add conditional icon in to the user massage in conversejs

I am creating a chat room using the converse js package on my react project. I am trying to add an icon in every massage of the user like the screenshot. The condition is this icon can see only the admin.
I was trying to do that by event but I think there is no event for this, My main purpose is I want to add an icon that can see by the admin if the admin clicks on the icon then a drop menu will show.
export const restrictUserOnLoadPlugin = () => {
window.converse.plugins.add('restrict-user', {
dependencies: [],
initialize: function () {
const _converse = this._converse;
// _converse.api.listen.on('getMessageActionButtons', (el, buttons) => {
// console.log(buttons);
// buttons.push({
// i18n_text: 'Foo',
// handler: (ev) => alert('Foo!'),
// button_class: 'chat-msg__action-foo',
// icon_class: 'fa fa-check',
// name: 'foo',
// });
// console.log(buttons);
// return buttons;
// });
},
});
};
is there any event for adding the icon? or any other way?

Send IPC message from main process to renderer process Electron

Currently I have an Electron menu with a save button on it. When this save button is pressed I wish to send an event to the renderer process, for the renderer to handle the event.
Here is what I have attempted:
Menu Source
const menuTemplate = [
{
label: "File",
submenu: [
{
label: "Save",
accelerator: "Ctrl+S",
click: () => {
BrowserWindow.getFocusedWindow().webContents.send("save");
}
},
]
},
]
Renderer Source
ipc.on("save", () => {
console.log("save");
})
Preload source
import { contextBridge, ipcRenderer } from "electron";
contextBridge.exposeInMainWorld("ipc", { on: ipcRenderer.on });
When trying this I get no output whatsoever when pressing the save button, including no errors. I can confirm that the correct menu is being utilised by Electron and that the click() function is executing. I can also confirm that ipc.on is indeed defined in the renderer.
How can I get this working? Thanks in advance.
Try setting the this manually in the on function.
contextBridge.exposeInMainWorld("ipc", { on: ipcRenderer.on.bind(ipcRenderer) });
or make a new function that passes the args:
contextBridge.exposeInMainWorld("ipc", { on(event, fn) { ipcRenderer.on(event, fn) } });

Are there events for when an Electron app is shown and hidden?

I have been looking for Electron app events for when the application is shown or hidden. I see in the docs that there is 'browser-window-blur' and 'browser-window-focus' but those do not do what I want.
I would like to know when the user has switched to another application or switched back to my app. The above events get triggered if the user switches between browser windows – including the "developer's tools" window.
The code in main.js
app.on('browser-window-focus', () => {
if (mainWindow) {
console.log('browser-window-focus');
mainWindow.webContents.send('projectMsg', { "event": "focus" });
}
});
app.on('browser-window-blur', () => {
console.log('browser-window-blur');
if (mainWindow) {
mainWindow.webContents.send('projectMsg', { "event": "blur" });
}
});
It seems to me that it works exactly as you described, so maybe the requirements are different.
This code
const {app, BrowserWindow} = require('electron')
app.on('browser-window-focus', (event, win) => {
console.log('browser-window-focus', win.webContents.id)
})
app.on('browser-window-blur', (event, win) => {
if (win.webContents.isDevToolsFocused()) {
console.log('Ignore this case')
} else {
console.log('browser-window-blur', win.webContents.id)
}
})
app.once('ready', () => {
new BrowserWindow()
new BrowserWindow().webContents.openDevTools({detach: true})
})
works the following way (in 3.0.3) given that nothing is focused initially:
Clicking on window 1 prints browser-window-focus 1
Clicking on window 2 prints browser-window-blur 1 browser-window-focus 2
Clicking on devtools window prints browser-window-blur 2 Ignore this case
So as far as I see devtool is not included in these events, windows are getting blurred for any other window focused (including devtool)
There is also show and hide, though you have to explicitly show/hide the app with win.show() and win.hide() to trigger these events.
Check out of these BrowserWindow's events:
Event: 'blur': Emitted when the window loses focus.
Event: 'show': Emitted when the window is shown.
For example:
app.once('ready', () => {
let mainWindow = new BrowserWindow({show: false}) //Create main window
mainWindow.on('show', () => {
//Do something
})
})
Hope this help.

enzyme wrapper.find(..).simulate keypress doesnt trigger event listener

I am trying to press enter on one of the input boxes. Doing it manually triggers the event listener, however, while trying with enzyme, the event listener is not triggered. What am I doing wrong here?
Event Listener
this.input.addEventListener('keypress', function(event){
debugger;
onEnter(event);
});
Enzyme
function setup(store, props) {
return mount(<Provider store={store}>
<component{...props}/>
</Provider>
);
}
beforeEach(() => {
wrapper = setup(store, {});
searchBar = wrapper.find('searchBar');
searchInput = searchBar.find("input");
});
it("when enter is pressed, event should be triggered", ()=> {
let wait = false;
runs(()=> {
searchInput.simulate('change', {target: {value: 'helloWorld'}});
searchInput.simulate('keyPress', {which: 13});
setTimeout(()=> {
wait = true;
}, 1000);
})
waitsFor(()=> {
return wait;
}, "", 1500);
})
I was struggling with this problem too. But now I`ve found a solution.
In addition to { which: 13 } parameter, you need to specify at least key parameter, so your simulate expression will be like:
searchInput.simulate('keyPress', {
key: 'Enter',
keyCode: 13,
which: 13,
});
Enzyme, built for React testing, likely doesn't know about your native JavaScript event listener. If you bind the event with JSX, Enzyme should be able to pick it up. https://reactjs.org/docs/handling-events.html
Change this:
this.input.addEventListener('keypress', function(event){
debugger;
onEnter(event);
});
To this:
<searchBar>
<input onKeypress={this.onEnter.bind(this)} aria-label="Search" />
</searchBar>

Categories

Resources