静的サイトジェネレーターをGUIで操作できるアプリをElectronで作成しました


静的サイトジェネレーターをGUIで操作できるアプリ「らい帳」をElectronで作成しました。

ターミナルでのコマンド操作に不慣れな人やノンプログラマーでも簡単にブログが作成できるように本アプリを作成しました。

紹介ページ: https://raychonote.com/download.html

アプリで使用している技術や工夫したポイントなどを紹介いたします。

使用ライブラリ

主な使用ライブラリは以下になります。

Electron

ベースとなるプロジェクトはelectron-vueを使用しました。

electron-vueはElectronなどのライブラリのバージョンが古いため、ライブラリは随時自分でバージョンを上げながら開発を進めていきました。

Vue

本アプリではVueを使用しています。

また、Vueに関連するライブラリとして、以下も使用しています。

  • Vuetify
  • Vuex
  • Vue Router
  • Vue i18n
  • Vee Validate

VuetifyはUI部分の実装にとても役立ちました。ほぼすべての要素はVuetifyのコンポーネントを使用しており、divやspanなどのプレーンなhtmlタグを実装することはほとんどありませんでした。

monaco-editor

エディタにはmonaco-editorを使用しています。

WebエディタといえばAceやCodemirrorがよく使われていると思いますが、monaco-editorはVSCodeでも使用されているブラウザで動作するエディタですので、VSCodeと同じ操作性を簡単にブラウザ上で実現できます。

marked

本アプリでは、記事はmarkdownで記載します。

markdownのライブラリはmarkedを使用し、markedをベースに独自記法も追加しています。(独自記法は使用しない人からすれば煩わしいと思うので、無効にする機能もあります)

highlight.js

markdownのシンタックスハイライトのライブラリはhighlight.jsを使用しています。

highlight.jsを各記事に組み込んでハイライトさせるのではなく、本アプリでビルド時に、ハイライトされた状態の静的なhtmlを出力します。

そのため、各記事のページでJavaScriptを実行する必要がないため、読み込み時間の短縮になります。

nunjucks

テンプレートエンジンはnunjucksを使用しています。

nunjucksはmozilla製のJavaScriptテンプレートエンジンで、ベーステンプレートの継承機能など、便利な機能がたくさんあります。

工夫したポイント

記事作成画面

記事作成画面では、Qiitaの投稿画面のようにプレビュー画面を並べて記事を作成できるようにしました。また、markdownに不慣れな人向けに、ツールバーを用意しました。

ツールバーのボタンをクリックしたらエディタに文字を挿入しますが、monaco-editorでは以下のように簡単に実装できます。

// this.editorはEditorオブジェクト

// 選択範囲を取得
const sel = this.editor.getSelection();

// 選択したテキストを取得
const selectedText = this.editor.getModel().getValueInRange(sel);

// 指定した範囲にテキストを設定
this.editor.executeEdits('', [
  {
    range: new monaco.Range(
      sel.startLineNumber,
      sel.startColumn,
      sel.endLineNumber,
      sel.endColumn
    ),
    text: `**${selectedText}**`, // 選択中のテキストに強調スタイルを適用
  },
]);

画像を貼り付け

画像は以下の操作で追加できるようにしました。

  • ファイル選択ダイアログ
  • ドラッグアンドドロップ
  • クリップボードにコピーした画像を貼り付け

クリップボードにコピーした画像を貼り付ける機能はslackなどを使っていてとても便利だと感じたので実装しました。

// pasteイベント
async pasteImage(e) {
  if (
    !e.clipboardData ||
    !e.clipboardData.types ||
    e.clipboardData.types.length !== 1 ||
    e.clipboardData.types[0] !== 'Files'
  ) {
    // 貼り付けたデータがファイルでない場合は処理終了
    return;
  }
  const { items } = e.clipboardData;
  const blob = items[0].getAsFile();
  // ファイルデータを読み込み
  const data = await new Promise((resolve) => {
    const reader = new FileReader();
    reader.onload = (event) => {
      resolve(event.currentTarget.result);
    };
    reader.readAsDataURL(blob);
  });

  // 以降、画像を追加する処理

まとめ

Electronを使うのは初めてでしたが、WEBアプリと同じように実装を進められました。

WEBの技術でデスクトップアプリが作れるのはとても便利ですね。