1.はじめに
55歳を過ぎてWebの勉強を始め、縦書きエディタを開発した前回の記事が、思いのほかよく読まれたので、勝手に続編を上げさせてもらいます。コーディングも開発も技術系記事の執筆もなにもかも手探り。おかしい面も多々あることかと思いますが、遠慮なくご指摘ください。
2.縦書きにこだわる理由
さて前回、縦書きエディタを開発した理由にGIGAスクールが関係していると書きました。児童・生徒が学校で使うChromebookのGoogle系ツールに縦書き機能がなく、国語の先生が困っているという話です。
Googleは欧米の企業なので、Googleドキュメントなどが横書きなのは自然なことです。問題は日本語が縦書きも横書きもOKなことです。日本で出版される小説や文庫本、新書などは縦書きが主流ですが、技術系の雑誌や本はほとんどが横書きです。縦書きの文化を担ってきた出版、雑誌、新聞などのマスコミ業界はいずれも斜陽で、今や情報流通の主流となっているウェブサイトやSNS、メールを通じて、日々、横書きの文章が世の中に大量生産されています。
なにをいいたいかというと、情報技術の進展により、いずれ縦書きは廃れていってしまうのではないかと懸念しています。自分自身もこの文章をはじめ、日ごろ書く文章はほとんど横書きですが、読む文章としては縦書きが美しく、残ってほしいと思います。そしてマスメディア以外で、「縦書きが当然」「縦書きでなければ」と思われているのが日本語教育の現場です。国語が縦書き文化を守る砦となるのではと思っています。
押し寄せるGIGAスクールの波の中、縦書き文化を守ろうと日々頑張っている国語の先生の助けになれば、とウェブアプリとして開発したのが縦書きエディタです。
3.原稿をファイルに入出力
前回、localStorage
を使って打ち込んだ原稿をブラウザに保存し、保存した原稿を読み込む方法をご紹介しました。今回は、打ち込んだ文章をファイルにして入出力する方法をご説明します。
こだわったのは「プレーンテキスト」として出力し、出力したプレーンテキストを読み込めるようにするということです。なぜプレーンテキストにこだわったかというと、他の文書作成アプリや文書を使ったウェブサービス、他の端末との文書データのやりとりなど連携をやりやすくするためです。
3.1.くせ者“contenteditable属性”
contenteditable属性を付与したdiv要素に打ち込まれた原稿は、そのままでは、divタグが付いたままなので、整形しないといけません。しかし、そのcontenteditable属性がなかなかのくせ者だったのです。
以下のコードブロックをごらんください。
Chrome,Edge,Safari
<div id="article" contenteditable="true"> 第一段落<div> 第二段落</div><div> 第三段落</div></div>
FireFox
<div id="article" contenteditable="true"><div> 第一段落</div><div> 第二段落</div><div> 第三段落<br></div></div>
原稿を打ち込んだ時、構成されるタグの構造が、Chrome、Edge、Safariはなぜか第一段落だけが親タグの直下にあり、第二段落以下は子タグの中にあります。FireFoxは第一段落から子タグにはさまれていますが、なぜか最終段落の末尾に<br>
タグが混入(?)しています。こうした“謎”構造のせいで先々苦しめられますが、とりあえず整形してみましょう。
3.2.replace関数で整形・復元
まずはプレーンテキストへの整形です。replace関数を使ってタグを削除するとともに改行を入れています。
Chrome,Edge,Safari
text = text.replace('<div>', '\n').replace(/<div>/g, '').replace(/<\/div>/g, '\n');
FireFox
text = text.replace(/<div>/g, '').replace(/<\/div>/g, '\n').replace(/<br>/, '');
続いてプレーンテキストにタグ付けします。divタグに流し込むため、replace関数を使ってタグをつけます。
Chrome,Edge,Safari
text = text.replace('\r?\n', '<div>').replace(/\r?\n/g, '</div><div>').replace(/<div>$/, '');
FireFox
text = '<div>' + text.replace(/\r?\n/g, '</div><div>').replace(/<div>$/, '');
3.3.ダウンロード
整形の準備ができたので実装に入ります。まずはダウンロードです。
プレーンテキストとしてローカルにダウンロードするため、JavaScriptでバイナリーデータを扱うことができるBlobオブジェクトに変換してアンカータグに仕込み、アンカータグをクリックすることでダウンロードします。これは教則本通りです。
ここで、ダウンロードするファイル名を入力できるようにするため、HTMLにcontenteditable属性を付けた入力窓をid="txt_title"
で設けています。
<textarea id="txt_title" placeholder="ファイル名を入力" contenteditable="true"></textarea>
function download(){
let text = document.getElementById("article").innerHTML;
const filename = document.getElementById("txt_title").value;
if(!filename) {
alert("ファイル名を入力してください");
return;
}
const agent = window.navigator.userAgent.toLowerCase();
// ブラウザがFireFoxの場合
if (agent.indexOf("firefox") != -1) {
// 原稿txtを整形。<div>はすべて削除、</div>はすべて改行、文末の<br>を削除
text = text.replace(/<div>/g, '').replace(/<\/div>/g, '\n').replace(/<br>/, '');
// FireFox以外
} else {
// 原稿txtを整形。<div>について先頭を改行に、残りは削除。</div>はすべて改行に
text = text.replace('<div>', '\n').replace(/<div>/g, '').replace(/<\/div>/g, '\n');
}
const blob = new Blob([text],{type:"text/plan"});
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = `${filename}.txt`;
link.click();
}
3.3.アップロード
今度はプレーンテキストを取り込むため、FileReaderを使ってBlobデータを読み込みます。
その際、HTMLにtype属性「file」を指定したinputタグを仕込みます。できたボタンを押すとファイル選択ダイアログが出て、ローカル上のファイルを選択することができるようになります。このへんも教則本通りです。
その際、つまづいたのは読み込んだ後の処理をreader.onload = function(ev){……}
のブロック文の中に書き込まなければならないということです。
ファイルを読み込むのはパソコンにとって比較的時間のかかる処理なので、他に処理がある場合はそちらを先にやってくださいという「非同期処理」というやつです。
例えば今回の場合、プレーンテキストを読み込んだ後、タグ付けをする作業があるのですが、それをこのブロック文の後に書き込むと、まだテキストデータが読み込んでいない段階で処理が走り、データがないよとエラーが出ます。
典型的な初心者あるあるですが、ウェブ開発をする上で非同期処理は避けては通れない難題の一つのようです。鋭意勉強中です。
<input type="file" id="loadfile" />ファイルを選択
const obj = document.getElementById("loadfile");
obj.addEventListener("change" , function(evt){ // ダイアログでファイルが選択された時
const file = evt.target.files;
const reader = new FileReader(); //FileReaderの作成
reader.readAsText(file[0]); //テキスト形式で読み込む
//読込終了後の処理
reader.onload = function(ev){
let text = reader.result;
const agent = window.navigator.userAgent.toLowerCase();
// ブラウザがFireFoxの場合
if (agent.indexOf("firefox") != -1) {
text = '<div>' + text.replace(/\r?\n/g, '</div><div>').replace(/<div>$/, '');
} else {
text = text.replace('\r?\n', '<div>').replace(/\r?\n/g, '</div><div>').replace(/<div>$/, '');
}
document.getElementById("article").innerHTML = text; //テキストエリアに表示する
}
},false);
4.終わりに
今回はファイル保存を中心にご説明しました。contenteditable属性に関してはその後も苦しめられました。縦書きエディタという特性上しかたないのかもしれませんが、特にキャレット(カーソル)移動に関しては、縦横と左右が逆になるという根本的な問題を解決する際にも障害として立ちふさがりました。気力が続けばいつかご紹介したいと思います。