PCページだけですが、トップページなどのヘッダーバーから、直接しおりを開くことができるようになりました。
スマホで無効化されているのは、単に画面のサイズに収まりにくかったからです…
なにか良いレイアウトを思いついたら、スマホからも使えるようにしたいと思います。
エディターに字下げ、引用、台詞、補足、改ページボタンなどを追加しました。
改ページについては説明不要かと思いますので、それ以外について少し補足します。
字下げ、引用は選択範囲を選んでから押してみてください。
「字下げ」と「引用」の表示の違いはこんな感じです。
補足を使うと、次のようにクリックで説明ダイアログが出るリンクを表示できます。
ここで「パソコン」をクリックすると
のようなダイアログが出ます。
使い方としては、対象のテキストを選択してから「補足」ボタンを押します。
すると次のようなダイアログが出るので、そこに補足説明を入力して下さい。
キャラクタを作成して、作品に「キャスティング」すると、エディタに次のようなドロップダウンボタンが出現します。
台詞を言わせたいキャラクタをクリックして、文章の部分を追加すると、次のように脚本形式のような表示になります。
これで各キャラクタをクリックすると、キャラクタの説明が出ます。
外部への埋め込み作品では、台詞表示とチップ表示が出来ません。
タイトルで全てを言い尽くしてしまいましたが、経緯をば。
不具合報告で「IEで画像のアップロードができない」ってのがあったので調査してみたら、readAsBinaryString
でコケてました。
調べてみると、どうもIE系列ではreadAsBinaryString
そのものが存在しないらしく…
なんでなのかというと、そもそもこのAPI、将来的になくなる方向らしいのです(実際にw3cのドキュメントからも消えてる)。
というわけで、readAsBinaryString
をreadAsArrayBuffer
に置き換えたら、IE11でも(もちろんChrome/Firefoxでも)問題なく動いたのですが…
で、なんで非推奨となったのかというと、単に効率が悪いからだそうで。
https://lists.w3.org/Archives/Public/public-webapps/2011OctDec/1497.html
要約すると「このメソッドは型付きの配列(ArrayBuffer)が標準化される前に作られた実装で、バイナリデータを文字列に変換するから効率が悪い」とのこと。
今は型付きの配列は既に実装されていて、それを読み取る関数(readAsArrayBuffer
)も実装済み。したがって、非効率的な古いAPIに存在意義はない(から削除するべきだ)、ということみたいです。
というわけで、これからはreadAsArrayBuffer
なのです。
Blobを作らなくても、fileオブジェクトならそのまま渡せます。
inga(因果)は、軽量で拡張可能なリアクティブ・アダプターです。RxJSとvirtual-domを使っています。
よくあるTodo-MVCは100行ぐらいです。
例えばクリックする度にカウンタが増える、みたいなのは次のようになります。
var Inga = require("inga"); Inga.define({ domRoot:"#app", dataSource:new Inga.ActionStateStream({ initialState:{clickCount:0} }), virtualView:function(ctx){ return h("button", { "ev-click":function(ev){ ctx.action.emitUpdater(function(state){ state.clickCount++; }); } }, ctx.state.clickCount); } });
例えばinga/plugins/scroll-pos
を組み込むと、こんな感じでレンダリング・コンテキストの状態オブジェクトにscrollPos
が合成されます。
var Inga = require("inga"); Inga.define({ domRoot:"#app", dataSource:new Inga.ActionStateStream({ initialState:{scrollPos:{x:0, y:0}}, // plugin 'scroll-pos' combines 'scrollPos' to status object. plugins:[ {module:require("inga/plugins/scroll-pos"), options:{combine:true}} ] }), virtualView:function(ctx){ return h("div", [ h("p", "x = " + ctx.state.scrollPos.x), h("p", "y = " + ctx.state.scrollPos.y) ]); } });
デフォルトのストリームに、自分で作ったストリームを組み込む場合は次のようにcombine
アクションを定義します。
var Rx = require("rx"); var Inga = require("inga"); Inga.define({ domRoot:"#app", dataSource:new Inga.ActionStateStream({ initialState:{width:window.innerWidth}, actions:{ // extend default stream by your own source. combine:function(initial_state, upstream$){ var width$ = new Rx.Observable.fromEvent(window, "resize") .debounce(250) .map(function(event){ return window.innerWidth }) .startWith(window.innerWidth); return upstream$.combineLatest(width$, function(state, width){ state.width = width; return state; }); } } }), virtualView:function(ctx){ return h("p", "current window width = " + ctx.state.width); } });
ライセンスはMITです。
管理画面リニューアルのお知らせです。
スマホとPCでUIを統一し、どちらからも同じUIで使用できるようになりました。
エントリーの一覧には、カードUIを使用しています。
PCユーザーからすると、元のテーブルレイアウトのほうが一覧性が高いかもしれませんが、どうしてもスマホとの共存が難しくなってしまうので、今回はカードUIを採用しました。
最近はアクセスの半分近くがスマホ経由で、執筆作業もスマホで行う人が増えてきていたので、それに対応した感じです。
新管理画面には、まだスタイル編集画面が入っていません。
通常のメジャーなブラウザは動作を確認していますが、古いAndroid系のブラウザなどでは、わかりません。
ちなみにIE8以下、Safari5以下のサポートはやめました
もし不具合等がありましたらメールフォームから連絡くださると助かります。
nehan.jsのセレクタのマッチング処理を高速化しました。
先日サポートした行末揃えは、有効にすると20%ほど遅くなりましたが、今回の修正によって15%ほど速くできたので、少し戻すことができました。
前々から、機会があったらやっておこうと思っていた処理でもあったし、ちゃんと効果があって良かったです。
やっていることは単純で、セレクタのマッチング結果を(キャッシュできるものに限り)キャッシュしただけです。
ただしマッチ結果には、キャッシュしやすいものとしにくいものがあるので、その辺のことについてちょっと説明します。
属性セレクタは、マークアップや親が同じでも、マークアップが現れる相対的な文脈によってマッチしたりしなかったりするので、常に同じ値が返ってくるとは限りません。
これに対応するためには、属性クラスの情報をキャッシュキーに盛り込む必要があります。
しかし例えば次のようなマークアップを考えてください。
<div> <p class="foo">text0</p> <p class="foo">text1</p> <p class="foo">text2</p> </div> <div> <p class="foo">text3</p> <p class="foo">text4</p> <p class="foo">text5</p> <p class="foo">text6</p> </div>
ここでtext2
とtext6
を内容に含むpタグをそれぞれp2
、 p6
と名付けることにします。
さて、p2
とp6
のキャッシュキーはどうなるでしょうか。
両者の特徴をフルに含んだキャッシュキーを設計するとなれば(擬似的な記述ですが)こんなかんじになりそうです。
// p2のキャッシュキー div>p.foo[nth-child=2, first-of-type=false, last-child=true, last-of-type=true, ...] // p6のキャッシュキー div>p.foo[nth-child=3, first-of-type=false, last-child=true, last-of-type=true, ...]
殆ど同じキャッシュキーですが、nth-child
の値だけが異なります。
しかし両者は木構造の位置として、性質的には殆ど同じものです(divの直下にあるpタグの最後の要素)。
なので、p2
、p6
のどちらも、次のセレクタにマッチします。
div>p.foo:last-child{ font-size:2em }
しかし、nth-child
の値が違うだけで、違うキャッシュキーとなるわけです。
これを同じキャッシュキーにするわけにはいきません。なぜなら属性クラスはnth-childを使っている「かも」しれないからです。 属性クラスをキャッシュして(他の場所で)再利用するなら、確実に属性クラスが表現しうる全ての特徴が一致している、という保証が必要なのです。
というわけで、仮にp2
のマッチ結果をキャッシュしたところで、同じ結果がp6
のマッチング時に再利用されることはありません。
キャッシュが効果的なのは、どれだけ再利用されるかにかかっているのですが、こうやってキャッシュが分散すると、ヒット率が落ちる上に容量も食うわけで、かえって性能を損ねるおそれが出てきます。
というわけで、キャッシュキーが分散しがちな属性セレクタについては、都度マッチする方針で統一しました。
ただし、属性クラスを含むセレクタは別の領域に保存しておくなどして、前もって検索範囲を狭めておく、というようなことはしています。
しかし、上記のようなp2
とp6
で、同じマッチ結果がキャッシュされるようなセレクタが書きたい! と思いませんか?
nehan.jsならこれができます。
nehan.jsでは、cssプロパティに関数値を設定できるので、先ほどと同じ場所(div>p.foo:last-child
)を、属性クラスを使わずに指定することがでるのです。
例えば以下のように、属性クラスの判定を遅延評価の関数に追い出すことで、セレクタの詳細度が減った「キャッシュヒット率の高い」セレクタを宣言することができます。
"div>p.foo":{ onload:function(ctx){ if(ctx.isLastChild()){ // is last-child? return {fontSize:"2em"}; } return null; } }
大まかなものでマッチさせておくことでセレクタのマッチング結果をキャッシュさせ、last-child
のような詳細で決まるスタイルについては、遅延評価で取得させるわけです。
一見すると、スタイル値の取得に際し関数呼び出しのオーバーヘッドが発生するように見えますが、これは属性クラスの条件判定をセレクタ検索時に計算するか、検索後に計算するかの差でしか無いので、性能差には結びつきません。
とはいえ、セレクタ数が少ないケースでは、どちらの書き方をしても、たいして性能の差はありませんでした。
字数が40万字、セレクタ数が5000個ぐらいのラインを超えると、さすがにキャッシュの効く遅延評価バージョンの方が22%ぐらい速かったですが…
マッチしたセレクタは、specificity
の順にソートし、最終的なスタイル値へとマージされます。
この最終的なスタイル値をキャッシュできると更に良いのは言うまでもないことですが、nehan.jsではcssの値に関数を指定できる仕様がある関係上、これが難しくなっています。
関数値で指定された値は実行時に値が決まるサンクなので、ある特定のタイミングで呼ばれて返ってきた結果を、永続的な最終値としてキャッシュすることはできないからです。
そもそも仮にキャッシュしたとしても、スタイル構築で時間がかかるのは圧倒的にセレクタ検索の部分なので、大した効果は期待できないと思われます。