1. はじめに
ListPagesモジュールによるページ一覧の生成において、その表示順をorderというオプションで決定できることはよく知られている。このオプションには、投稿日時順や更新日時順といった基本的なソートを始めとして、投票数順や文字数順などのニッチな需要にも答えてくれるほどの豊富なバリエーションの指定が可能である。しかし、何故かコメントが新しい順というソートだけが存在していない。モジュールの表示項目には、コメント投稿者や最終コメント日時、コメント数などが指定可能なのにもかかわらずである。
恐らくコメントが0のページの存在を想定して作っていないのではないかと考えられるが、下書き・批評システムの作成という現在の命題に対してこの機能が無いのは非常に困る。そこで、これまでに蓄積してきたwikidot構文の技術を駆使して擬似的なコメントが新しいページのリストを作成してしまおうと考えた。
2. Feedモジュールによる最近の投稿の情報の取得
リストの並べ替えの前提として、まずコメントの投稿順のリストを手に入れる必要がある。これを取得可能なのは、唯一フォーラムスレッドカテゴリごとの最新のポストのRSSのみである。スレッド単位のリストでは、スレッドが作成された順のRSSしか存在せず、FrontForumモジュールも同様のリストしか生成できない。また、発行されているRSSは最新の20ポストのみしか含まれていないため、ポストが偏ると取得できる情報も自ずと少なくなってしまうという制約がある。しかしこれが取得できる情報の限界であるため、この制限の範囲内のページのリスト作成を目指すこととなった。
RSSファイルは、Feedモジュールによってwikidotページ内に情報を展開できる。ListPages内に%%content{0}%%を使ってこのモジュールを置くと、当然ListPagesの表示数×RSSのデータ数の要素が生成されることになる。この際ListPagesモジュールのseparateをnoに設定した上でFeedモジュールを直下に置くと、Feedモジュール内に置いた要素全てがプレーンなdivブロックに囲まれた状態でhtmlに書き出される。このdivブロック群は、フィードモジュールによってRSSの新しいコメントの順番に並んだものが、更にListPagesの要素数だけ繰り返されるという2重構造になっている。一方で全てのdivブロックは同じ階層に存在しているため、全てのブロックを兄弟要素として扱うことが出来る。これがこのシステムの最も重要な点である。
3. CSSによるソート
兄弟要素として扱うことが出来ると、CSSセレクタの:nth-of-type()が使用可能になる。:nth-of-type()は:nth-of-type(20n - 19)といったように連続した要素の内特定の数ごとの要素に特定のスタイルを適用できる。一方でflexbox内の要素においては、order: p;というスタイルを適用することで、その要素の表示位置をhtml上の位置に関係なく指定し、移動させることが出来る。前述のように、フィードモジュールによって生成されたdiv要素群はRSS内の20個の要素の順番に並んでおり、この順番が最新のコメントの順番である。これらに対して、次のようなスタイルを適用すると
.flexList div:nth-of-type(20n - 19){order: 1 !important;}
.flexList div:nth-of-type(20n - 18){order: 2 !important;}
︙
.flexList div:nth-of-type(20n - 1){order: 19 !important;}
.flexList div:nth-of-type(20n ){order: 20 !important;}
div要素の表示順は、同じコメントを含むもの同士が固まった状態で、コメントが新しい順番に並べ変わる。ちょうど行列が転置した状態である。これによってコメントが新しい順にdiv要素を並べ替えることは出来たので、次からは必要な要素のみを表示するための工程に入る。
4. 必要な要素の表示:コメント日時
表示する要素の絞り込みには、ListPagesモジュールとFeedモジュールがそれぞれ表示できる要素の内、共通の値を取るものを利用する。RSSで最新のコメントを取得している今回の場合、Feed側ではその日付%%date%%、ListPages側では最新のコメント日時である%%commented_at%%がそれに該当する。しかしそれぞれの要素はhtml中に数値や文字列などのデータとしては書き出すことが出来ず、必ずspanブロックの形でしか扱うことが出来ない。そこで、一工夫を行う。まずはListPagesとFeedモジュールの二重構造の下に、以下のような構造を作る。
[[module ListPages separate="no" category="draft-test" wrapper="no" tags="@URL|批評中" perPage="50" limit="50" order="@URL|created_at desc"]]
[[%%content{0}%%module Feed src="http://shitake-crude-production.wikidot.com/feed/forum/cp-2181188.xml" separate="no"]]
%%da%%content{0}%%te%%[[div style="display:none"]]
︙
[[/div]]
[[%%content{0}%%/module]]
[[module]]
%%da%%content{0}%%te%%は最初外側にあるListPagesに従って%%content{0}%%が書き出され、%%date%%となる。これによってFeedモジュールが反応し、コメント日時が入ったspanブロックが生成される。これらは以下のような形でhtmlとして書き出される。
<div>
<span class="odate time_1519825195 format_%25e%20%25b%20%25Y%2C%20%25H%3A%25M%7Cagohover" style="cursor: help; display: inline;">28 Feb 2018, 22:39</span>
<div style="display:none">
︙
</div>
</div>
︙
以下繰り返し
ここで重要なのは、spanブロック内に"time_1519825195"というクラスが入っており、更にこのspanブロックがdivブロックと隣接している点である。1519825195はRSSから得られたコメント日時のUNIXタイム表記であり、この要素をターゲティングした上で隣接するdivブロックを指定すれば、このブロック内の要素をdisplay:blockに切り替えることが可能となるのである。
さてこのtime_1519825195を指定するためには任意のCSSを生成しなければならないが、その為には二段階の手順を踏む必要がある。まずはListPages側からの最新のコメント日時の取得であり、この情報を一旦htmlに取り込む必要がある。htmlブロックを利用した方法ではページ数だけhtmlが生成されてしまい、フレーム間通信で情報を集約する必要があるので、今回はembedとiframeを使った以下の方法を採用した。
[[module ListPages separate="no" category="draft-test" wrapper="no" tags="批評中" perPage="50" limit="50" order="updated_at desc"]]
[[head]]
[[embed]]
<iframe src='http://shitake-crude-production.wdfiles.com/local--code/advanced-sandbox-system/1#
[[/head]]
[[body]]
%%commented_at%%
[[/body]]
[[foot]]
' class="html-block-iframe" style="width:100%; height:3em;" allowtransparency="true" frameborder="0"></iframe>
[[/embed]]
[[/foot]]
[[/module]]
embed内のiframeでは要素の指定が''(シングルクォーテーション)でも可能であり、内部の要素に""(ダブルクォーテーション)が入っていても文字列で扱うことが出来る。このため、%%commented_at%%がhtml上にspanブロックとして書き出されたとしても、
<iframe src='http://shitake-crude-production.wdfiles.com/local--code/advanced-sandbox-system/1#<span class="odate time_1519825195 format_%25e%20%25b%20%25Y%2C%20%25H%3A%25M%7Cagohover">27 Feb 2018 15:10</span>…' class="html-block-iframe" style="width:100%; height:3em;" allowtransparency="true" frameborder="0"></iframe>
という形で1519825195という文字列を含んだままURLのアンカー文字列内に収まってくれる。この読み込まれたフレーム内で以下のhtmlを働かせることで、アンカー文字列内に格納されたListPages上のコメント日時の数値の一覧を取得することが出来る。
<style>
@import url(http://d3g0gp89917ko0.cloudfront.net/v--de24f08b1628/common--theme/base/css/style.css);
@import url(http://d3g0gp89917ko0.cloudfront.net/v--de24f08b1628/common--theme/shiny/css/style.css);
@import url(http://scp-jp.wdfiles.com/local--theme/scp-sigma-9-off-canvas/style.css);
@import url(http://scp-jp.wdfiles.com/local--files/japanese-syntax/scp-WikidotCss.css);
body {
background-color: transparent;
}
</style>
<script type="text/javascript">
window.onload = function(){
var URL = decodeURI(location.href);
var i = 0;
var text = "";
URL = URL.substring(URL.indexOf("#") ,URL.length );
while(URL.indexOf("time_") > 0){
i++;
URL = URL.substring(URL.indexOf("time_") + 5,URL.length );
text += "/index" + i + "_limit/" + URL.substring(0, URL.indexOf(" format_") );
}
document.getElementById("link").innerHTML = "<a href='http://shitake-crude-production.wikidot.com/advanced-sandbox-system/tags/xxxxx" + text + "#CommentList' target='_parent'>コメントが新しい順</a> (実験的な機能です)"
}
</script>
<div id="link" style='text-align: left;'></div>
このコードでは、最終的に以下のリンクが生成されるようになっている。
http://shitake-crude-production.wikidot.com/advanced-sandbox-system/index1_limit/1519825195/index2_limit/1519823036…#CommentList
index1_limitの部分はurlAttrPrefix="index1"に指定したListPagesモジュールのlimitに1519825195を代入するというコマンドであり、これを利用して数値をCSSモジュールに受け渡すことが出来る。この際CSSを生成するための構文は、以下のようになる。
[[module ListPages category="*" range="." limit="@URL|0" urlAttrPrefix="index1"]]
[[%%content{0}%%module css]]
.flexList .time_%%limit%% + div {display:block !important;}
[[%%content{0}%%/module]]
[[/module]]
︙
range="."によって1ページ分のみのモジュールが生成されるが、%%limit%%にはURLで指定した数値がそのまま代入されるため、time_1519825195というクラスに対してその隣に存在するdivブロックの要素をdisplay:blockに変更するというスタイルが完成する。これがListPagesの各ページの%%comented_at%%に対応する数だけ生成される事で、divブロックのリスト内に存在するコメントの内、そのページの最新のものだけが表示されるようになる。
しかしこの絞り込みでは、まだListPagesで生成された数だけの最新のコメントが残った状態であり、ここから更に絞り込みを行う必要がある。
5. 必要な要素の表示:タイトル
次に絞り込む必要があるのは、ListPagesから得られたタグや作成日時、作者などの情報と、RSSのコメントのページの一致である。これらを一致させるための情報はページタイトルである。絞り込みのための方法は、ListPagesから得られたタイトルとFeedから得られたタイトルを並べ、CSSモジュールで設定した文字列と合致するものを表示するという形式である。以下にこの実現のための構文のコアの部分を、前述の構文の内側に表示する。
[[module ListPages separate="no" category="draft-test" wrapper="no" tags="@URL|批評中" perPage="50" limit="50" order="@URL|created_at desc"]]
[[%%content{0}%%module Feed src="http://shitake-crude-production.wikidot.com/feed/forum/cp-2181188.xml" separate="no"]]
%%da%%content{0}%%te%%[[div style="display:none"]]
[[div class="%%title%%%%ti%%content{0}%%tle%%" style="display: none"]]
︙
[[/div]]
[[/div]]
[[%%content{0}%%/module]]
[[%%content{0}%%module css]]
.%%title%%%%title%%\: {display:block !important;}
[[%%content{0}%%/module]]
[[module]]
重要なのは最も内側のdivブロックのクラスとして指定した%%title%%%%ti%%content{0}%%tle%%である。左側のタイトルは、最初にListPagesに反応してそのままページのタイトルの文字列に変換される。右側は%%content{0}%%が最初に処理され%%title%%に変化することで、今度はこちらがFeedモジュールに反応してコメントのタイトルに変化する。こうすることで、ListPagesの全てのページタイトルとFeedで得られたコメントのタイトル全ての全通りの組み合わせのクラスのdivブロックが生まれる。この中で、情報を一致させるために必要なのは同じタイトル同士の組み合わせのみであり、下のCSSモジュールを用いてdisplay:blockで表示状態にしている。「\:」は、Feedモジュールで取得できるタイトルが元のタイトルにこのコロンを付けたものであるためである。
以上によりListPagesで取得したページのタイトルと一致し、かつ最新のコメントを含むFeedモジュールの要素のみが最終的に表示されることとなり、更にorderの設定によってこれらがコメントの新しい順番に並ぶため、最終的に最新のコメントが投稿されたページのリストが表示されることとなる。実際にリスト上に表示される要素については、
[[div class="%%title%%%%ti%%content{0}%%tle%%" style="display: none"]]
︙
[[/div]]
の中の部分を弄ることで変更が可能になっている。この中身の部分は基本的に並べ替えのシステムに影響しないため、自由に変更が可能である。