novelで書いたmarkdownを保存できるようにする

2023年11月28日(火) 16時48分34秒 | 67 view |

novel

markdownエディタにnovelを採用し、ローカルに構築してみました

※テキストは Good Morning #01 inowayさんの章を拝借しています
リポジトリ
https://github.com/utautattaro/novel
デプロイしてみた
https://novel.utautattaro.com/

novel


novelはvercelの方たちが中心となって開発したオープンソースのnotionライクなmarkdownエディタです。ライブデモで動作を確認できます。


notionライクな書き味とlocalStorageを利用したリアルタイムセーブなんかが良いです。

現在ライブデモとVSCode拡張で提供されていますが、ライブデモはmarkdownエクスポートができなかったり、VSCodeは見た目が小さかったり画像のアップができなかったりと、色々残念なポイントがあります。
今回はnovelのブランチをフォークして自前の環境を作り、markdownエクスポートを実現したので共有します。

markdown export

\novel\packages\core\src\ui\editor\index.tsxに機能的な処理が記載されています
onUpdate()がアップデートされたときに呼ばれるコールバックとなっていて、そちらにエクスポート機能を追加します。
エクスポートのトリガは /saveと入力した際にされるようにします。

const lastFive = getPrevText(e.editor, {
  chars: 5
})
if (lastFive === "/save" && !isLoading ){
 const id = Math.random().toString(32).substring(2);
 e.editor.commands.deleteRange({
  from: selection.from - 5,
  to: selection.from,
 });

 let editorContent = e.editor.storage.markdown.getMarkdown();

 const blob = new Blob([editorContent], {type: 'text/plain'}); // Blob オブジェクトの作成
 const link = document.createElement('a');
 link.download = 'novel-'+id+'.md'; // ダウンロードファイル名称
 link.href = URL.createObjectURL(blob); // オブジェクト URL を生成
 link.click(); // クリックイベントを発生させる
}


これで/saveコマンドを打つとmarkdownがダウンロードされるようになります。

base64化解除

ただ上記の状態だと、埋め込まれた画像が全てbase64化され本文に記述されているため、容量が非常に多くなっています。
画像ファイルはbase64からpngデータに変更して単一のファイルとして保存されるように以下の処理を追加します。

let req = /\(data:image.*?\)/g //base64化された画像のみ()ごと抜き出す
let imgs = editorContent.match(req);
for(let i in imgs){
  var a = document.createElement("a"); //Create <a>
  let base64 = imgs[i].slice(1);
  base64 = base64.slice(0,-1)
  a.href = base64 //Image Base64 Goes here
  a.download = i + "-" + id +".png"; //File name Here
  a.click(); //Downloaded file

  editorContent = editorContent.replace(base64,i+ "-"+id+".png")
}


これでエクスポート時に画像ファイルがダウンロードされるようになり、markdownには画像ファイル名が記述されるようになりました。

課題

本当はJSZipなんかでzip化したほうがいいんだと思います。めんどくさくてやってません
localstorageに残っているデータは現状デベロッパーツールで消すかすべて選択して消すかしかないので/deleteなんかを定義してもいいのかもしれません。事故が怖いですが。
novelのせいなのか私の書いたコードのせいなのかたまにオートセーブが効かなくなります。エラーを見る限りローカルストレージに保存する際のパースエラーっぽいんですが、続けて書いていれば消えて問題なく保存再開されるので現状無視してます
update()の返り値のeから最終的にmarkdownが平文で取得できるので、頑張ればchrome拡張なんかでも提供できそうです
そもそも書こうと思っていた記事があったのに環境構築から始めたせいで結局執筆は一ミリも進んでおらずこんな時間です

続報

スラッシュメニュー化&json形式でのセーブ・ロードを実装しました。

公式ではフルスタックアプリ化を避けるためこういった機能はマージしないそうなのでissueにて展開済み


参考