読者です 読者をやめる 読者になる 読者になる

arinoth's memo

arinothのメモ

Electron製Markdownプレビュー&コンバートツール制作中の続き

前回に引き続きツール制作を進め、何とか必要最低限の作業はできるようにしました。 後は実戦でテストしながら仕上げていくつもりです。

f:id:arinoth:20160509002231g:plain

github.com

ざっくりとした使い方

  1. GitHubリポジトリ内のpackagesディレクトリの中から対応するバージョンのzipファイルをダウンロードします(Mac版はdarwin)。
  2. zipを解凍して、Winならmdpreview.exe、Macならmdpreview.appをダブルクリックして起動します。他のファイルも必須なので、実行ファイルの場所は移動せず、ショートカットなどを適宜作成してください。
  3. vivliostyle.jsをダウンロードして解凍し、中のviewerフォルダを、プレビューしたいMarkdownファイルと同じ場所に配置してください。
  4. template.htmlやpostReplaceList.json、_template.htmlで読み込むCSSファイルなどを配置します。配置構成についてはtestディレクトリを参考にしてください。
  5. [File] -> [Open]を選択してMarkdownファイルを開きます。
  6. 後はMarkdownファイルの内容を変更して上書き保存すれば自動的にプレビューが更新されます。

f:id:arinoth:20160509014239p:plain

※(2016/5/9)パッケージングが失敗していて再アップしました。package.jsonにnode-staticをsaveし忘れていて、まとめてくれなかったようです。

制作メモ

メニューからHTMLプレビューを拡大する

近頃小さい字を見ると妙に目がツライのでHTMLに拡大・縮小機能を付けることに。メニューからHTMLを読み込ませているiframeを直接操作する方法がなかなか見つからず、いろいろ調べたところ、別プロセス通信が必要なのでした。

techium.hatenablog.com

つまり、こういう感じです。

main.js(呼び出し側)
    label: 'View',
    submenu: [
……中略……
      { label: 'Zoom In', accelerator: 'CmdOrCtrl+Plus', click: function(){ mainWindow.webContents.send('main-process-message', 'Zoom In');}},
      { label: 'Zoom Out', accelerator: 'CmdOrCtrl+-', click: function(){ mainWindow.webContents.send('main-process-message', 'Zoom Out');}},
    ]
index.js(呼び出される側)
// 非同期でメインプロセスからのメッセージを受信する
var g_zoomrate = 1.0;
ipc.on('main-process-message', function(event, arg) {
    console.log(arg);
    if(arg == 'Zoom In'){
        //HTMLプレビューの拡大
        var iframe = document.getElementById('html-preview');
        g_zoomrate += 0.25;
        if(g_zoomrate > 6.0) g_zoomrate = 6.0;
        iframe.style.transformOrigin = '0 0';
        iframe.style.transform = 'scale('+ g_zoomrate + ')';        
    } else if(arg == 'Zoom Out'){
        //HTMLプレビューの縮小
        var iframe = document.getElementById('html-preview');
        g_zoomrate -= 0.25;
        if(g_zoomrate < 1.0) g_zoomrate = 1.0;
        iframe.style.transformOrigin = '0 0';
        iframe.style.transform = 'scale('+ g_zoomrate + ')';        
    } else if(arg== 'VSPreview Reload'){
        var vsiframe =  document.getElementById('vs-preview');
        vsiframe.contentDocument.location.reload(true);        
    }

});

HTML表示機能の追加

Markdownを表示するタブを削って、HTMLのコードをそのまま表示するタブを追加しました。 生成されるHTMLを見られれば__postReplaceList.jsonを書く助けになります。

f:id:arinoth:20160509015629p:plain

_postReplaceList.jsonを利用した後置換

次のような_postReplaceList.jsonファイルをMarkdownファイルと同じ階層に置いておくと、配列の正規表現に基づいて、パース後のHTMLを置換します。

この機能はMarkdownに足りない範囲指定などを行うために使用します。その他に、空白行を入れたいときにMarkdownファイル内に「【▼▽空白行△▲】」と書いておいて「<hr class="blankline">」に置換するといった使い方もできます。

_postReplaceList.json
[
    {
        "f": "<p>【▼▽カバーページ▽▼】</p>",
        "r": "<div class='coverpage'>"
    },
    {
        "f": "<p>【▲△カバーページ△▲】</p>",
        "r": "</div>"
    },
    {
        "f": "<p>【▼▽セクションヘッダー▽▼】</p>",
        "r": "<header class='sectionheader'>"
    },
    {
        "f": "<p>【▲△セクションヘッダー△▲】</p>",
        "r": "</header>"
    }
]

ファイルを開いてJSONをオブジェクトにパースし、それを1要素ずつとりだして置換します。

置換コード
    // _postReplaceList.jsonがあれば後置換を実行
    try{
      var replisttext = fs.readFileSync( 
        path.join(workfolder, '_postReplaceList.json'), 'utf-8');      
      var replist = JSON.parse(replisttext);
      for(var i=0; i<replist.length; i++){
        html = html.replace(new RegExp(replist[i].f, 'g'), replist[i].r);
      }
    } catch(err){
      require('dialog').showErrorBox('RepList Open Error', err.message);
      console.log('no replist');
    }

Vivliostyleを使ったプレビュー

VivliostylePreviewは目玉機能ですが、表示するまでに時間がかかるので、普段の編集中はHTMLPreviewを使い、レイアウトを見たいときだけVivliostylePreviewを利用するのがおすすめです。

f:id:arinoth:20160509022634p:plain

また、ウインドウサイズが小さいとうまく表示できないときがあり、その場合いったんリロードするといいことがあります。ただ、index.html全体のリロードは時間がかかるので、[View] -> [VSPreview Reload]を選択してVSPreviewだけをリロードする機能も追加しました。

それと、Vivilostyleはキャッシュが効きすぎて中々変更が反映されないので、[View] -> [Toggle DevTools]を選択して開発者ツールを表示し、[Network] タブの [Disable Cache] にチェックを入れてキャッシュを無効にすることをおすすめします。 f:id:arinoth:20160509021227p:plain

仕組みとしてはMarkdownファイルをロードしたときに、そのファイルがあるフォルダをルートディレクトリとしてWebサーバを起動しています。

qiita.com

起動時にしかルートディレクトリを指定できないので、ロード(またはリロード)するたびにWebサーバを作り直しています。そんなことして大丈夫かなとも思ったのですが、大丈夫なようです。

        // サーバを起動
        var l = htmlfilepath.lastIndexOf(path.sep);
        var workfolder = htmlfilepath.substring(0, l);    
        var file = new nodeStatic.Server(workfolder, { cache: 0 });
        http.createServer(function (request, response) {
            request.addListener('end', function () {
                file.serve(request, response);
            }).resume();
        }).listen(8080);

        var vsiframe =  document.getElementById('vs-preview');
        vsiframe.src = 'http://localhost:8080/viewer/vivliostyle-viewer.html#x=../' + htmlfilepath.substr(l+1);
        vsiframe.style.height = (dh - 80) + 'px';

インストーラーは諦めてパッケージのみに

インストーラーの作り方を調べていたのですが、証明書的なものが必要だったりとかなり面倒なことがわかりました。 nulab-inc.com

そこまでやる必要もなさそうなので必要ファイルを単純にまとめたパッケージを作るだけにしました。 ちなみにWindowsではMac用のパッケージは作れないらしいです。

npm i electron-packager -g
electron-packager ./app mdpreview --platform=win32 --arch=x64,ia32 --version=0.37.8
electron-packager ./app mdpreview --platform=darwin --arch=x64 --version=0.37.8
Windowsの64ビット版Package

f:id:arinoth:20160509025217p:plain

その他

XML書き出しはその内実装。たぶんJSONでの置換処理とたいして変わらんでしょう。気が向くまでしばらくは既存のGruntで代用。

前半は会社のMacで、後半は自宅のWindowsで作業しましたが、ホントどっちを使ってもいい時代になりました。