Creating a Popup Dictionary with Electron (Part 1)

John Dykes
4 min readNov 3, 2023

--

While studying Korean, I have found that it is very useful to be able to see the definition of a word just by selecting it with my mouse. There are extensions for web browsers like Chromium and Firefox that allow for this. One that I particularly enjoyed and use frequently when studying Korean is Dictionariez. I enjoyed this extension so much that I modified it to add better support for Korean dictionaries.

These popup dictionaries are great! They can simply embed an iframe of another dictionary website above your cursor, making them incredibly simple to add languages to. However, there is one problem. Being a browser extension, these pop-up dictionaries only work in the browser. When reading a pdf or epub book in a different program, one needs to find another solution.

So I thought of a solution—Electron! With Electron we can use the same iframe idea to take advantage of existing dictionaries on the web, but also have our popup dictionary work in any program — not just the web browser.

Without further ado, let’s see how I implemented the popup dictionary in electron!

We start with a basic electron app that starts hidden, and loads the HTML page at /views/naver/index.html .

// main.js

const { app, BrowserWindow } = require('electron');

const createWindow = () => {
const win = new BrowserWindow({windowSettings},
});
win.hide();

// hide window when it goes out of focus
win.on('blur', () => {
win.hide();
});

win.loadFile(path.join(__dirname, '../views/naver/index.html'));
return win;
};

app
.whenReady()
.then(() => {
return createWindow();
})
<!-- views/naver/index.html -->

<html lang="en">
<head>
<title>Naver Korean Dictionary</title>
</head>
<body>
<div class="container">
<div id="search-container">
...
</div>
<div id="iframe-container">
<iframe
src="about:blank"
frameborder="0"
id="my-iframe"
scrolling="yes"
>
</iframe>
</div>
</div>
</body>
</html>

This HTML basically gives us a search bar at the top of our screen, and an iframe at the bottom

Basic Dictionary Window

To make the search button actually work, we add a script to load the appropriate Naver Korean dictionary page on the iframe.

<!-- index.html -->

<head>
<script>
onSubmit = () => {
const text = document.getElementById('SearchBarText').value;
const url =
'https://ko.dict.naver.com/search.nhn?query=<<word>>&target=dic';
window.api.changeIframe(text, url);
};
</script>
<title>Naver Korean Dictionary</title>
</head>

Where this changeIframe function is added to our browser window as a preload in Electron

// main.js -- createWindow

const win = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'inject-css.js'),
},
});

And our function to change the iframe is

// inject-css.js

const changeIframe = (text, url) => {
text = encodeURIComponent(text);
url = url.replace('<<word>>', text);

// create new iframe
const ifrm = document.createElement('iframe');
ifrm.setAttribute('src', url);
ifrm.setAttribute('frameborder', '0');
ifrm.setAttribute('id', 'my-iframe');
ifrm.setAttribute('scrolling', 'yes');

// remove existing iframe
myiframe = document.getElementById('my-iframe');
myiframe.remove();

// add new iframe to dom
const ifrmContainer = document.getElementById('iframe-container');
ifrmContainer.appendChild(ifrm);
};

Now this is what our dictionary looks like

Searching ‘apple’ in our Korean dictionary

Clearly we have a problem here. The website is much wider than our window, and there is a lot of extra stuff at the top of the page that we don’t want (like a second search bar). We can solve these problems by injecting our own custom CSS into the iframe to make it look better.

// inject-css.js

const InjectCSS = () => {
let myiframe = document.getElementById('my-iframe');
let head = myiframe.contentWindow.document.head;
head.innerHTML += `<style>
div.option_area, div#header, div#footer, div#aside, div.component_socialplugin, div.tab_scroll_inner, div.section_suggestion, .section.section_etc{
display: none;
}

body {
background-color: #d5eded
}

.section {
padding-top: 10px;
}

div#container {
padding: 5px;
}

...

</style>
`;
};

We also have to modify our changeIframe function so that the CSS is actually injected.

const changeIframe = (text, url) => {
// ... same as before
// ...

// add new iframe to dom
const ifrmContainer = document.getElementById('iframe-container');
ifrmContainer.appendChild(ifrm);
ifrm.contentWindow.addEventListener(
'DOMContentLoaded',
(e) => {
InjectCSS();
},
true
);
};

This adds an event listener to the iframe so that the InjectCSS callback is run after the iframe loads.

Note that this CSS is site specific, so in the future if other dictionary websites were added, custom CSS for each site would need to be written.

With our custom CSS, the dictionary results looks like this:

Searching ‘apple’ in our Korean dictionary with embedded CSS

That’s much better!

Now that we have a basic working dictionary frame, in part 2 we will add the ‘popup’ part of the popup dictionary. Click here to see part 2.

You can view the full source code on github.

--

--

John Dykes

I'm a programmer and cryptographer, and I also have a passion for creating web apps.