banner
isolcat

isolcat

I am not afraid of storms, for I am learning how to sail my ship
github

Let's use Electron to create a note-taking application that supports Markdown!

Preface#

Recently, I switched to a MacBook Air and found that it came with a built-in "Notes" app, which I thought was pretty good. I could use it to make daily plans and such. However, the one thing that really bothered me was that it didn't support Markdown! Every time I instinctively tried to use Markdown syntax, it felt awkward, like this:

Untitled

It just looks weird, and the styling isn't very appealing either. So, why not DIY one myself? Let's get started with Electron!

Setting up the Framework#

I won't go into too much detail about this part. Our main focus is on implementing the Markdown note with support and optimizing its functionality. You can refer to the official documentation here: https://www.electronjs.org/zh/docs/latest/tutorial/quick-start

Implementing the Functionality#

Requirements analysis: Markdown note with support

Based on the requirements we created for ourselves, let's create a simple flowchart:

Mermaid Loading...

To control the size, let's not introduce any frameworks and just use Electron to implement the relevant functionality:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Markdown Notepad</title>
  <style>
    body {
      display: flex;
      height: 100vh;
      margin: 0;
      font-family: Arial, sans-serif;
    }

    #markdown-input,
    #markdown-preview {
      flex: 1;
      padding: 20px;
      box-sizing: border-box;
      height: 100%;
      overflow-y: auto;
    }

    #markdown-input {
      border-right: 1px solid #ddd;
    }

    #markdown-preview {
      padding-left: 40px;
    }
  </style>
</head>

<body>
  <textarea id="markdown-input" placeholder="Enter Markdown here..."></textarea>
  <div id="markdown-preview"></div>
</body>

</html>

At this point, we notice that there is always a window bar at the top, which is not very elegant compared to the Notes app on Mac. We can modify the window we created by adjusting main.js (responsible for creating windows and handling system events):

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

let win; // Modify variable name to match its usage in the following code

function createWindow() {
  // Create a borderless browser window
  win = new BrowserWindow({
    width: 320,
    height: 320,
    frame: false, // Set window to be borderless
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false,
    }
  });

By setting the frame property in BrowserWindow, we can make the window border disappear.

Demo

Next, let's move on to the next step, which is to introduce a library for parsing Markdown syntax. Here, we choose Marked. Marked is a powerful JavaScript library that can parse Markdown text into HTML. Its advantages include speed, lightweight, support for GitHub-style Markdown syntax, and custom rendering. These advantages make Marked our preferred choice.

npm install marked

Let's handle the frontend logic in renderer.js:

mdInput.addEventListener('input', function () {
  const renderedHtml = marked.parse(mdInput.value);
  mdPreview.innerHTML = renderedHtml;
});

OK, let's make a slight modification to the HTML and see if the functionality is implemented correctly:

test

Great, the functionality is working! It renders successfully!

Further Optimization#

Do you feel that something is still not quite right? Having the left side and the right side like this doesn't look elegant at all. So, let's make some modifications and add buttons to toggle between the editing and preview modes:

  <div id="app-container">
    <textarea id="markdown-input" placeholder="Enter Markdown here..."></textarea>
    <div id="markdown-preview"></div>
    <div class="buttons-container">
      <button id="toggle-pin" class="toggle-button">📌 Toggle Pin</button>
      <button id="toggle-preview" class="toggle-button">👌🏻 Preview</button>
    </div>
  </div>

We'll still write the frontend logic in renderer.js and handle this logic:

togglePreviewBtn.addEventListener('click', () => {
  isPreviewMode = !isPreviewMode; // Toggle preview mode state directly
  if (isPreviewMode) {
    mdInput.style.display = 'none';
    mdPreview.style.display = 'block';
    togglePreviewBtn.textContent = '✏️  Edit';
  } else {
    mdInput.style.display = 'block';
    mdPreview.style.display = 'none';
    togglePreviewBtn.textContent = '👌🏻 Preview';
  }
});

OK, the functionality is implemented. I'm sure attentive readers have noticed that there is now an additional "📌 Toggle Pin" button. Since we want it to function as a sticky note, we naturally want it to have a "pin to top" feature. Here, we can use an important feature of Electron - Inter-Process Communication (IPC).

Inter-Process Communication (IPC) is a key part of building feature-rich desktop applications in Electron. Due to the different responsibilities of the main process and renderer processes in Electron's process model, IPC is the only way to perform many common tasks, such as calling native APIs from the UI or triggering changes to web content from native menus.

Our main idea here is that the "Toggle Pin" feature listens for button clicks in the renderer process, toggles the window's "always on top" state, and sends a message to the main process through Electron's IPC mechanism to perform the actual pinning operation. Here is the actual code:

// main.js

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

//.....

// Listen for the "toggle-pin" message from the renderer process
ipcMain.on('toggle-pin', (event, shouldPin) => {
  if (win) {
    win.setAlwaysOnTop(shouldPin); // Set window to be always on top
  }
});

// renderer.js
togglePinBtn.addEventListener('click', () => {
  isWindowPinned = !isWindowPinned;
  ipcRenderer.send('toggle-pin', isWindowPinned);
  togglePinBtn.textContent = isWindowPinned ? '📌 Toggle Pin' : '📄 Just Paper';
});

Let's make some slight style optimizations and run Electron to see the final functionality:

image

At this point, all the functionality has been implemented.

Packaging and Building#

We will use electron-builder for packaging. Configure it in package.json:

"build": {
    "appId": "com.yourdomain.memomark",
    "mac": {
      "category": "public.app-category.productivity",
      "icon": "assets/MemoMark.icns" // Your logo
    },
    "dmg": {
      "title": "MemoMark",
      "icon": "assets/MemoMark.icns",
      "window": {
        "width": 600,
        "height": 400
      }
    },

Note that the logo format for Mac is icns, so be careful not to make a mistake~

Run the packaging and building command:

npm run dist

Packaging successful! After installation, you can use your own developed Electron application locally~

Summary and Some Regrets#

When choosing a framework for building desktop applications, not only the functionality needs to be considered, but also factors such as performance, runtime size, and development experience. Here is a detailed comparison between Electron and Tauri:

ElectronTauri
SizeThe minimum executable file size of Electron is about 50MB, due to its dependency on the complete Chromium and Node.js runtime.Tauri has a significant advantage in terms of size. The size of a basic Tauri application ranges from a few hundred KB to 1-2MB.
SpeedThe speed of Electron is affected by its large size, as it needs to load the complete Chromium and Node.js runtime.Tauri, written in Rust, is fast and has a short startup time.
SecurityThe security of Electron depends on how it is used. Improper usage may lead to security vulnerabilities, such as directly using Node.js API in the renderer process.Tauri is designed with security in mind and adopts a strict security model, including prohibiting direct access to Node.js API from the renderer.
CommunityElectron is developed by GitHub and has a large user base and mature community. It has extensive documentation and tutorials.Tauri is a relatively new project, and its community is rapidly growing, but it is not as mature as Electron at the moment.
CompatibilityElectron supports Windows, macOS, and Linux.Tauri also supports Windows, macOS, and Linux.
Development ExperienceElectron supports HTML, CSS, and JavaScript, so frontend developers can quickly get started.Tauri allows the use of any frontend framework that can be compiled into static HTML, CSS, and JavaScript, making it very flexible for frontend developers.
Memory UsageElectron has higher memory usage because each Electron application needs to run its own Chromium and Node.js instances.Tauri has relatively lower memory usage because it does not depend on heavyweight runtime environments.

For the Markdown note app I implemented, the size has already reached 237MB (although it could be due to my lack of optimization). If I were to use Tauri, the size might be better controlled, which is the biggest regret of this project.

If you are interested in this project, maybe you can take a look at it here: https://github.com/isolcat/MemoMark. If you can download it, try it out, and provide issues or PRs, that would be even better. I hope this Markdown-supported Electron note-taking app can help you~

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.