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

リアルタイム・プレビューに対応した新しい投稿フォームをリリースしました

久しぶりの機能アップデートですが、リアルタイム・プレビューに対応した新しい投稿フォームをリリースしました。

投稿フォームの上部にある、以下の案内リンクからお試しいただけます。

f:id:convertical:20150524105126p:plain

まだ試験段階で古いブラウザやPCだとどうなるのかわかりませんが、ご意見ご感想ありましたらメールフォームやらtwitterなどでお聞かせいただけるとありがたいです。

Matt-Esch/virtual-dom覚え書き

こちらの記事で知ったのですが、Reactで言うところのpropsしかないvirtual dom実装とのこと。

github.com

まさに探していたものなので、さっそく試してみたのですが、以下は覚え書きです。

escapeさせたくない中身はattributesinnerHTMLを設定

inlineなタグでも、そのまま設定するとエスケープされてしまうので。

h("div", "<b>escaped!</b>"); // タグ文字はエスケープされる
h("div", {innerHTML:"<b>not escaped!</b>"}); // innerHTMLなら、そのまま出力される

datasetはdata-xxじゃなくて、dataset:{xx:10}

attributesに直に設定しても動きません。dataset属性にオブジェクトとして設定します。

h("a", {"data-id":10}, "click me"); // NG
h("a", {dataset:{id:10}}, "click me"); // OK

差分パッチの対象から外す要素はtype:"Widget"

中身のDOMElementはinitコールバックで出力します。

h("div", {
  type:"Widget",
  init:function(){
    var dom = document.createElement("div");
    dom.innerHTML = "my custom element!";
    return dom;
  }
});

ev-xxxDOMDelegatorインスタンス化していないと動かない。

attributeにev-(event名)で書きますが、ソースのどこかでnew DOMDelegator()していないとイベント通知は動きません。

var _delegator = new DOMDelegator(); // ev-*を使うには、このインスタンス化が必要
h("a", {
  "ev-click":function(ev){
    alert("clicked!");
  }
});

あとすこし気になったのは、ev-clickreturn falseしても、クリックの戻り値として反映されないところですね。

nehan.jsのデモページをReact/Fluxで作ってみた

必然性は全くなかったのですが、Fluxを試してみたかったので作ってみました。

http://tb.antiscroll.com/static/nehan-demo/

わかったこと

facebook/flux付属のflux-todomvcを見ながら書いただけなのですが、以下はちょっとした躓きポイントでした。

1. Dispatchに登録したイベントハンドラの中でアクションを作ると「dispatchの途中でdispatchできないよ」と怒られる。

入れ子にせず、個々のアクションにして、外側のレイヤで個々に呼べということでしょう。

2. textareaの内容をタグで囲った場合は再描画の対象にならない

つまり

<textarea>{this.props.code}</textarea>

ではなく、

<textarea value={this.props.code} />

です。

参考URL

form要素のvalueを更新させるためには、Viewにstateを宣言し、それをvalueとして取り込む必要があり、その上で

<textarea value={this.state.code} onChange={this.onChangeTextarea} />

みたいにする必要があるみたいです(onChangeプロパティがないと中身が編集できない)。

というわけで、フォームの扱いはちょっと面倒かもしれません。

react-addonsのtwo way bindingをサポートするmixinを使っても、そこそこ面倒

使ってみた感想

あんまり感触は良くないかも…特にViewの中のsetStateとか。

あるいはFooConstatns.FOO_IDみたいな、StoreとActionで共有されるグローバルなIDを用意し、Storeでswitch caseする箇所とか。

ただ今はなんとなく全体像を掴んだかもっていう段階なので、もっと複雑なUIを作るとなったら、こういうアーキテクチャが生きてくるケースもあるのかも? しれません。

2005年にアジア〜中東〜北アフリカ〜ヨーロッパを旅行したロバ中山さんの旅行記がすごい

2005年、つまり今からちょうど10年ぐらい前、アジア〜中東〜アフリカ〜ヨーロッパを旅行したロバ中山さんの旅行記がすごく面白いです。

ロバ中山の旅日記 トップページ

今は大変な状況になってしまったシリアやイエメンにも行ってますし、イラン、ヨルダン、イスラエルにも行ってますね。

面白くて二度読みしている最中なのですが、現代の中東情勢を伺い知るには、もってこいの内容じゃないでしょうか。

ただし、そのままだと読みづらい気がしたので、自分は NehanReader で次のような設定をして読みました。

  1. 「涅」ボタンを右クリックして「オプション」
  2. 「table of selectors to main article」の欄に、以下の一行を追加。
www.sakaguti.org, td[width="491"], table>tbody>tr>td

こうして設定した後、NehanReader で読むと、こんな感じで読めます。

f:id:convertical:20150424202024p:plain

非常に長い日記ですが、最初から読む必要はなく、興味がある国だけ読んでも面白いと思います。

それにしてもイエメンのヤヒヤさんやアミンさんは無事なのだろうか…と思ってしまいますね。

Nehan Readerアップデート。サイトごとに変換対象を設定できるようになりました

Nehan Reader ver0.9.63から、サイトごとに変換対象を設定できるようになりました。

chrome.google.com

設定の仕方

「涅」のボタンを右クリック→「オプション」と進みます。

f:id:convertical:20150419133103p:plain

「table of selectors to main article」の欄に

[siteのURL], [selector]

を改行で区切って記述し「Save」ボタンで保存します。

サンプル設定

www3.nhk.or.jp, #news
toyokeizai.net/articles, #article-body
www.asahi.com/articles, #Main
togetter.com, .contents_main
ncode.syosetu.com, #novel_contents
lifehacker.jp, article
gigazine.net, #maincol
blog.livedoor.jp, .article-outer
news.yahoo.co.jp, #main
headlines.yahoo.co.jp, #main
hatenablog.com, #main
hatenablog.jp, #main
hateblo.jp, #main
hatenadiary.com, #main
hatenadiary.jp, #main
anond.hatelabo.jp, .day
www.huffingtonpost.jp, article.entry
wired.jp, .article_maincontents

例えば上の内容をコピペするだけで、ヤフーニュースを始めとするニュース記事や、小説家になろうの小説、はてなブログの記事などが読みやすくなります。

また変換するのがメインの文章だけになるので、高速にもなります。

css framework上の全てのセレクタに特定のprefixを付ける

chrome拡張とかでも、普段使っているcssフレームワークが使いたくなることがあります。

しかし大抵のcssフレームワークは、グローバルな名前空間でスタイルを宣言しています。そのまま導入すると、拡張機能CSSが読み込まれてしまった結果、訪問したサイトの本来のスタイルを崩してしまうでしょう。

もちろん拡張機能を特定のサイトでだけ動くようなポリシーにすれば防げます。しかし、NehanReaderのように、全てのサイトで使えるようにしている拡張機能も、たくさんあると思います。

そういう場合、フレームワークで宣言されている全てのセレクタに、強引に何かのプレフィックスを足してしまえば良いわけですが、手動でやるのは流石にキツイ、というか無理です。

だから「なんか良いツールないかなあ」と探して見つけたのがrework-mutate-selectorsです。

github.com

インストール

今回はgulpを使いたかったので、gulp-reworkも一緒に導入しました。

npm install rework
npm install rework-mutate-selectors
npm install gulp-rework

gulpやgulp-renameがない人は、先にインストールしておいて下さい。

使ってみる

var gulp = require("gulp");
var rework = require("gulp-rework");
var selectors = require("rework-mutate-selectors");
var rename = require("gulp-rename");

gulp.task("default", function(){
  return gulp.src("framework.css")
    .pipe(rework(selectors.prefix(".my-module"))) // prefixに".my-module"を追加
    .pipe(rename("my-module.framework.css"))
    .pipe(gulp.dest("."));
});

こうすると例えば

/* framework.css */
div a{ color:red }

が、

/* my-module.framework.css */
.my-module div a{ color:red }

と出力されます。

結論

一瞬、自分で作ろうかとも考えましたが、諦めずに探して良かったとです。

nehan.jsで拡張タグを作る

拡張タグを作るサンプルとして、次のようなタグを作ってみます。

<circular>
  <div>1時ですよ!</div>
  <div>2時ですよ!</div>
(省略)
  <div>11時ですよ!</div>
  <div>12時ですよ!</div>
</circular>

で、例えばこのタグを表示した時刻が1時だったら、次のようになるものとします。

f:id:convertical:20150408134943p:plain

動作デモ

実際に動作させてみた結果は、こんな感じになります。

デモを表示する、を押すと文字がくるっと回転しながら表示され、現在時刻だけが赤くなります。

上手く動作しない場合はページをリロードしてみてください。

基本方針

  1. Nehanのグローバルスタイルにてcircularタグをブロックタグとして登録
  2. circularの子供エレメントの中で、現在時刻に該当する子の文字色を赤に
  3. それぞれの子を円の中心に移動させてから、時刻数に応じて回転させる

すべてのソースは以下のリポジトリに登録しておきました。

github.com

なので、この記事ではポイントだけを解説します。

タグの登録

Nehan.setStyleを使うと、グローバルタグを登録できます。

組版エンジンごとに登録することもできるのですが、今回は解説しません。

circularをブロックタグとして登録したいので、次のようにしてみます。

Nehan.setStyle("circular", {
  display:"block",
  background:"wheat",
  measure:"280px",
  extent:"280px",
  "border-radius":"280px",
  margin:{after:"2em"},
  // 円の直径(280px)を表示する余白がなければ改ページ
  onload:function(ctx){
    var items = [];
    var rest_extent = ctx.getRestExtent();
    var extent = parseInt(ctx.getMarkup().getAttr("extent", "280px"), 10);
    if(rest_extent < extent){
      ctx.setCssAttr("break-before", "always");
    }
  }
});

上のonloadは、circularセレクタの読み出し完了をフックする関数です。

ここで「十分な余白があるかどうか」をチェックする処理が埋め込まれています。

なぜなら、このタグを表示するには、12個ある子供のdivがすべて表示されないといけません。

10時までしか表示されなかったら時計になりませんので。

というわけで、十分な余白がなければ、スタイルに改ページを追加する、という処理がされています。

時刻を記述した行の処理

各時刻(「〜時ですよ!」の行)を指すセレクタcircular divです。

Nehan.setStyle("child div", {
  "line-height":"1em",
  color:function(ctx){
    var child_index = ctx.getChildIndex();
    var cur_hour = new Date().getHours() % 12;
    return ((child_index + 1) % 12 === cur_hour)? "red" : "black";
  }
});

colorのところでは、各子供が現在時刻に該当する時刻を表す行なら赤を返すような「関数値」が設定されています。

各時刻を回転

あとは、各行が時計上で位置する場所へと回転させるだけです。

各行を親のボックスサイズのブロックレベルサイズの半分(*1)だけ移動し、そこからそれぞれ30度(=360/12)ずつずらして表示させたらよさそうです。

*1 - 正確に言うと親ブロックのブロックサイズの半分から、さらに行そのものの高さの半分を引きます。

Nehan.setStyle("child div", {
  onblock:function(ctx){
    var parent_style = ctx.getParentStyleContext();
    var is_vert = ctx.isTextVertical();
    var child_index = ctx.getChildIndex();
    var child_count = parent_style.getChildCount();
    var line_height = ctx.getStyleContext().getFontSize(); // line-height:"1em"
    var parent_extent = ctx.getParentBox().getContentExtent();
    var trans_extent = Math.floor((parent_extent - line_height) / 2);
    var unit_degree = Math.floor(360 / child_count);
    var rotate_degree = child_index * unit_degree + (is_vert? 30 : 120);
    var $dom = $(ctx.dom);
    var translate = is_vert? {translateX:trans_extent + "px"} : {translateY:trans_extent + "px"};
    var rotate = {
       opacity:1,
       rotateZ:rotate_degree + "deg"
    };
    $dom
    .css("position", "absolute")
    .css("opacity", 0)
    .velocity(translate)
    .velocity(rotate);
  }  
});

ちなみに、回転処理をアニメーションさせたかったので、その部分についてはvelocity.jsを利用しました。

onblockについては説明が必要でしょう。

circle divセレクタに対するonblockは、このセレクタを表すエレメントがブロックレベルとしてDOM化されたタイミングをフックしています。

どういうことかというと、実はブロックレベルの下には、匿名ラインボックスという更に下のレベルのDOM化があり、そちらはonlineとして読み出されるのですが、それと区別しているわけです。

例えば<p>hoge</p>というマークアップがあった場合、pはブロックの中にさらにhogeというテキストを表す匿名ラインボックスを含んでいます。

ちなみにoncreateを呼ぶと、ブロックレベルでもラインレベルでも共通で呼ばれます。

今回は行エレメントがブロックレベルで作成された時だけに興味があったので、onblockを使いました。

まとめ

このサンプルを見ればわかると思いますが、レイアウトエンジンレベルでセレクタ定義が出来ると、セレクタの定義とそのセレクタに対するアクションが同時に書けます。

jQueryなどでは、画面に既に表示されたものに対してセレクタ検索をかけますが、このセレクタの検索はレイアウトエンジンからすれば(スタイルを読み出す際に)既に検索済みなので、そのタイミングでjsのコードを埋め込めるのは効率面からも有利でしょう。

さらにスタイルの設定を読み出す際に、スタイルプロパティの値を関数にできるのも大きな強みです。

このサンプルでもcolorの設定は、new Dateして、現在時刻を見た上で色を決定していますね?

circleタグも、表示前に組版状況を見て、余白がなければ動的に改ページしています。

このようにjsと協調しながら、動的にスタイル設定を切り替えることができるのは非常に面白いと思うのですが、どうでしょうか。

nehan.js 5.1.0系

まだ安定していないのでリリースまではしてないのですが、自分の中で懸案だった幾つかのトラブルが解消したっぽいので、nehan.jsのバージョンを5.0.5から、5.1.0へと上げました。

github.com

ちなみに安定していなくても、縦書き文庫では問答無用で最新版を走らせています。

利用者がバグを見つけてくれることが多いからです…

変更点など

1. 複数ページに渡るフロート処理の改善

5.0系列では(主に横書きの時に)バグがあったのですが、5.1系では縦書き横書き共に複数ページに渡るfloatが正しく最後まで表示されるようになりました。

f:id:convertical:20150407202906p:plain f:id:convertical:20150407202917p:plain f:id:convertical:20150407202926p:plain

2. last-child系の取得ができるように

IE8を考慮したlexerの処理を刷新したことで、lexing中に取れる情報を利用できるようになり、結果としてlast-child系のpseudo-class(last-child/last-of-type/only-child/only-of-type)を利用できるようになりました。

これまでのLexerは、主にIE8でフリーズさせないために、バッファリングしながら処理させていたのですが、これだと同一階層のDOMにおける先のlexing内容が不定になるため、first-child/nth-child/nth-of-typeなどはとれても、末尾からの情報は取得できていませんでした。

3. 複数のInlineジェネレータをまたぐ禁則処理の対応

これまでは複数のInlineElementをまたぐ構造になる時、行末の禁則処理が正しく動作していなかった場合があったのですが、この不具合の大半(残念ながら全てではない…)を改善することが出来ました。

4. 複数ページに渡るテーブルのborder-collapseを改善

ページの途中で途切れた場合にもborderの最後が表示されてしまっていたのですが、これが消えるように(ただしたまに表示されることもある)なりました。

f:id:convertical:20150407203246p:plain f:id:convertical:20150407203251p:plain

5. 表示できないエレメントを適切にスキップ

どうやっても指定されたページの中に表示できないレイアウト(深すぎるテーブルとか)はスキップして続きのレイアウトから表示できるようになりました。

6. 処理速度の改善

一部のロールバックが必要だった処理をロールバック不要な処理に改善し、全体のパーススピードが上がりました。

7. セレクタに新しいコールバック関数を追加

onload/oncreateに加えて、onblock/online/ontextというコールバックが追加されました。

それぞれブロックレベルの作成タイミング、その中の匿名ラインボックスの作成タイミング、更にその中のテキストブロックの作成タイミングをフック出来ます。

この辺のことについては、いずれチュートリアルと共に別エントリで説明する予定です。