OSDN Git Service

- enhance tag search speed.
[feedblog/feedblog.git] / js / lunardial / feedblog.js
1 /**
2  * FeedBlog CoreScript
3  *
4  * @copyright 2013 FeedBlog Project (http://sourceforge.jp/projects/feedblog/)
5  * @author Kureha Hisame (http://lunardial.sakura.ne.jp/) & Yui Naruse (http://airemix.com/)
6  * @since 2009/02/27
7  * @version 4.2.1.0
8  */
9
10 // ブログ本体のHTMLファイルのURL
11 var mainPageUrl;
12
13 // 検索用ページURL
14 var searchPageUrl;
15
16 // 最新の記事を示すパスへの文字列
17 var latestXml;
18
19 // ログのリストが書かれたXMLのファイルパス
20 var logXmlUrl;
21
22 // 一画面あたりの表示記事数
23 var showLength;
24
25 /**
26  * XMLファイルから読み込んだファイルのバリデートモード
27  * 0 = 改行コード部分に<br/>を挿入
28  * 1 = 改行コード部分に<br/>を挿入しない
29  */
30 var validateMode;
31
32 // 検索結果をメモリ上に保持する変数
33 var loadedEntries;
34
35 // fetchEntries 用のセマフォ
36 var fetchEntriesSemaphore = new Semaphore();
37
38 // URL末尾用文字列(スクリプトを開いた瞬間のミリ秒を記録)
39 var urlSuffix;
40
41 /**
42  * 記事を実際に生成します。この部分を編集することでデザインを変更可能です。
43  * @param {Entry} entry 記事の情報が代入されたEntryオブジェクト
44  * @param {String} drawitem 「本文」を描画すべきパネルのDIV要素のid
45  * @param {String} renderto 「タイトル・更新日時・本文」の1日分の記事データを最終的に描画すべきパネルのDIV要素のid
46  * @param {String} closed (Ext jsパネルオプション)記事をクローズ状態で生成するか否か
47  */
48 function generatePanel(entry, drawitem, renderto, closed) {
49         // プラグインを実行
50         if ( typeof ($("#" + renderto).feedblog_contents_plugin) == "function") {
51                 $("#" + renderto).feedblog_contents_plugin({
52                         entry : entry
53                 });
54         }
55
56         // HTML用の配列を用意する
57         var htmlBuffer = [];
58
59         // 内部的に描画先IDを生成
60         var feedblogContentId = "" + renderto + "_content_div";
61
62         // 各要素をオブジェクトに描画
63         $("#" + drawitem).html(entry.content);
64
65         // ヘッダパネルを生成 class= .feedblog_header
66         htmlBuffer.push("<div class='feedblog_header' onclick='closePanel(\"" + feedblogContentId + "\")'><span>" + entry.title + "</span></div>" +
67
68         // 本体記事を作成 class= .feedblog_content
69         "<div class='feedblog_content' id='" + feedblogContentId + "'><span>" + document.getElementById(renderto).innerHTML + "</span></div>");
70
71         // 最終描画実施
72         $("#" + renderto).html(htmlBuffer.join(""));
73 }
74
75 /**
76  * システム表示画面を実際に生成します。この部分を編集することでデザインを変更可能です。
77  * @param {Entry} entry 記事の情報が代入されたEntryオブジェクト
78  * @param {String} drawitem パネルの本文を格納したDIV要素のid
79  * @param {String} renderto 「タイトル・更新日時・本文」の1日分の記事データを焼き付けるDIV要素のid
80  * @param {String} closed (Ext jsパネルオプション)記事をクローズ状態で生成するか否か
81  */
82 function generateSystemPanel(entry, drawitem, renderto, closed) {
83         // HTMLを生成する
84         var htmlBuffer = [];
85
86         // 描画先IDを生成
87         var feedblogContentId = "" + renderto + "_content_div";
88
89         // 各要素をオブジェクトに描画
90         $("#" + drawitem).html(entry.content);
91
92         // ヘッダパネルを生成 class= .feedblog_header
93         htmlBuffer.push("<div class='feedblog_header' onclick='closePanel(\"" + feedblogContentId + "\")'><span>" + entry.title + "</span></div>" +
94
95         // 本体記事を作成 class= .feedblog_content
96         "<div class='feedblog_content' id='" + feedblogContentId + "'><span>" + document.getElementById(renderto).innerHTML + "</span></div>");
97
98         $("#" + renderto).html(htmlBuffer.join(""));
99 }
100
101 /**
102  * 全ての定数を取得・セットします
103  */
104 function initialize() {
105         // 初期値をhiddenパラメータより読み込みます
106         mainPageUrl = $("#feedblog_mainpageurl").val();
107         searchPageUrl = $("#feedblog_searchpageurl").val();
108         latestXml = $("#feedblog_latestxml").val();
109         logXmlUrl = $("#feedblog_loglistxmlurl").val();
110         showLength = parseInt($("#feedblog_showlength").val());
111         if (isNaN(showLength)) {
112                 showLength = 1;
113         }
114         validateMode = $("#feedblog_validatemode").val();
115
116         // 初期値を設定します
117         urlSuffix = +new Date();
118
119         // 必要な環境を確認します
120         var errorBuf = [];
121         // 変数確認
122         if (mainPageUrl === undefined) {
123                 errorBuf.push("設定値「feedblog_mainpageurl」が欠落しています。");
124         }
125         if (searchPageUrl === undefined) {
126                 errorBuf.push("設定値「feedblog_searchpageurl」が欠落しています。");
127         }
128         if (latestXml === undefined) {
129                 errorBuf.push("設定値「feedblog_latestxml」が欠落しています。");
130         }
131         if (logXmlUrl === undefined) {
132                 errorBuf.push("設定値「feedblog_loglistxmlurl」が欠落しています。");
133         }
134         if (showLength === undefined) {
135                 errorBuf.push("設定値「feedblog_showlength」が欠落しています。");
136         }
137         if (validateMode === undefined) {
138                 errorBuf.push("設定値「feedblog_validatemode」が欠落しています。");
139         }
140         // SHA-1関数確認
141         try {
142                 if ( typeof (CryptoJS.SHA1) != "function") {
143                         errorBuf.push("crypt-jsモジュール(hmac-sha1.js)が読み込まれていません。");
144                 }
145         } catch (ex) {
146                 errorBuf.push("crypt-jsモジュール(hmac-sha1.js)が読み込まれていません。");
147         }
148
149         // 描画エリアチェック
150         if ($("#feedblog_writearea").length == 0) {
151                 errorBuf.push("描画エリア「feedblog_writearea」が存在しません。");
152         }
153         if ($("#feedblog_logselecter").length == 0) {
154                 errorBuf.push("描画エリア「feedblog_logselecter」が存在しません。");
155         }
156
157         // エラーがある場合は以降の処理を継続しない
158         if (errorBuf.length > 0) {
159                 alert("初期設定値に誤りがあります。\n詳細:\n" + errorBuf.join("\n"));
160                 return false;
161         }
162
163         return true;
164 }
165
166 /**
167  * jQueryへのイベント登録です。すべてのDOMが利用可能になった時点で実行されます。
168  */
169 $(document).ready(function() {
170         // 初期処理を実施
171         if (!initialize()) {
172                 return false;
173         }
174
175         // 制御に必要な各種パラメタを取得する
176         var tag = getParamFromUrl("tag");
177         var id = getParamFromUrl("id");
178         var urlhash = getHashFromUrl();
179
180         // ハッシュが空か、ハッシュ形式の正規表現に一致しないようなら通常モードで実行
181         if (urlhash.length == 0 && tag.length == 0 && id.length == 0) {
182                 fullWriteMode(latestXml);
183                 logXMLLoader();
184         } else if (urlhash.length == 0 && id.length == 0) {
185                 // タグが指定されているのでタグ探索モード
186                 searchTagMode(tag, true);
187                 logXMLLoader();
188         } else if (urlhash.length == 0) {
189                 // IDが指定されているのでID探索モード
190                 searchIdMode(id);
191                 logXMLLoader();
192         } else {
193                 // ハッシュ形式の正規表現に一致したら探索モード
194                 searchHashMode(urlhash);
195                 logXMLLoader();
196         }
197 });
198
199 /**
200  * jQueryでのパネル開閉を制御します
201  */
202 function closePanel(id) {
203         $("#" + id).slideToggle();
204 }
205
206 /**
207  * 記事クラス
208  * @param {Object} obj entry 要素の DOM オブジェクト
209  */
210 function Entry(obj) {
211         this.title = $("title:first", obj).text();
212         if (this.title == "")
213                 requiredElementError(obj, "title");
214         this.title = validateText(this.title);
215         this.content = $("content:first", obj).text();
216         this.content = validateText(this.content);
217         this.id = $("id:first", obj).text();
218         if (this.id == "")
219                 requiredElementError(obj, "id");
220         this.date = $("updated:first", obj).text();
221         if (this.date == "")
222                 requiredElementError(obj, "updated");
223         this.date = validateData(this.date);
224         this.category = $("category", obj);
225 }
226
227 /**
228  * システム用記事クラス
229  * @param {Object} obj entry 要素の DOM オブジェクト
230  */
231 function SystemEntry(obj) {
232         this.title = $("title:first", obj).text();
233         this.title = validateText(this.title);
234         this.content = $("content:first", obj).text();
235         this.content = validateText(this.content);
236         this.id = $("id:first", obj).text();
237         this.date = $("updated:first", obj).text();
238         this.date = validateData(this.date);
239         this.category = $("category", obj);
240 }
241
242 /**
243  * 呼び出すとDIV:id名:feedblog_writearea上のHTMLを削除し、ロードエフェクトを表示します
244  */
245 function loadingEffect() {
246         $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
247
248         // ロード表示用のパネルを生成
249         var systemEntry = new SystemEntry();
250         systemEntry.title = "Now Loading .....";
251         systemEntry.content = '<br/>長時間画面が切り替わらない場合はページをリロードしてください。<br/><br/>';
252         generateSystemPanel(systemEntry, "feedblog_drawitem", "feedblog_drawpanel", false);
253 }
254
255 /**
256  * 呼び出すとDIV:id名:feedblog_writearea上の先頭に、処理中の記事を表示します
257  */
258 function executingEffect() {
259         $("#feedblog_writearea").html('<div id="feedblog_drawpanel_info" class="feedblog_drawpanel"><div id="feedblog_drawitem_info" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
260
261         // ロード表示用のパネルを生成
262         var systemEntry = new SystemEntry();
263         systemEntry.title = "検索処理中";
264         systemEntry.content = '<br/>該当するタグに属する記事を検索中です。<br/><br/>';
265         generateSystemPanel(systemEntry, "feedblog_drawitem_info", "feedblog_drawpanel_info", false);
266 }
267
268 /**
269  * 呼び出すとDIV:id名:feedblog_drawpanel_info上に、処理完了の記事を表示します
270  */
271 function executingEffectCompleteEffect() {
272         $("#feedblog_drawpanel_info").html('<div id="feedblog_drawitem_info" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
273
274         // 検索処理完了用のパネルを生成
275         var systemEntry = new SystemEntry();
276         systemEntry.title = "検索完了";
277         systemEntry.content = '<br/>検索処理が完了しました。(該当記事:' + fetchEntriesSemaphore.entryCount + '件)<br/><br/>';
278         generateSystemPanel(systemEntry, "feedblog_drawitem_info", "feedblog_drawpanel_info", false);
279 }
280
281 /**
282  * 呼び出すとDIV:id名:feedblog_drawpanel_abort上に、省略記事がある旨を描画します
283  */
284 function showLimitedInfoEffect() {
285         $("#feedblog_writearea").html($("#feedblog_writearea").html() + '<div id="feedblog_drawpanel_abort" class="feedblog_drawpanel"><div id="feedblog_drawitem_abort" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
286
287         // 記事省略用のパネルを生成
288         var systemEntry = new SystemEntry();
289         systemEntry.title = "以降、" + (fetchEntriesSemaphore.entryCount - fetchEntriesSemaphore.entryIndex) + "件の記事が省略されています。";
290         systemEntry.content = '<br/><a style="cursor: pointer;" onclick="javascript: fetchTagHiddenEntries();">全ての記事を表示する。</a><br/><br/>';
291         generateSystemPanel(systemEntry, "feedblog_drawitem_abort", "feedblog_drawpanel_abort", false);
292 }
293
294 /**
295  * 呼び出すとDIV:id名:feedblog_drawpanel_abortを消去します
296  */
297 function showLimitedInfoRemoveEffect() {
298         $("#feedblog_drawpanel_abort").remove();
299 }
300
301 /**
302  * 記事データのエラー時の処理を行います
303  */
304 function showErrorEffect() {
305         $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
306
307         // エラー内容をパネルに描画
308         var systemEntry = new SystemEntry();
309         systemEntry.title = "エラー";
310         var errorContent = [];
311         errorContent.push('<br/>記事ファイル(XML)の取得に失敗しました。以下のような原因が考えられます。<br/><br/>');
312         errorContent.push('・設定値「feedblog_latestxml」に正しいパスが設定されていない。<br/>');
313         errorContent.push('・設定値「feedblog_loglistxmlurl」に正しいパスが設定されていない。<br/>');
314         errorContent.push('・ローカル環境で起動している(必ずサーバにアップロードして実行してください)。<br/>');
315         errorContent.push('<br/>');
316         systemEntry.content = errorContent.join("\n");
317         generateSystemPanel(systemEntry, "feedblog_drawitem", "feedblog_drawpanel", false);
318
319         // 結果表示エリアをリセット
320         $("#feedblog_resultwritearea").html("<div class='feedblog_result_status'></div>");
321 }
322
323 /**
324  * 記事データのエラー時の処理を行います
325  */
326 function notFoundErrorEffect() {
327         $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
328
329         // エラー内容をパネルに描画
330         var systemEntry = new SystemEntry();
331         systemEntry.title = "検索失敗";
332         systemEntry.content = '<br/>検索条件に一致する記事は見つかりませんでした。<br/><br/>';
333         generateSystemPanel(systemEntry, "feedblog_drawitem", "feedblog_drawpanel", false);
334 }
335
336 /**
337  * 記事データのエラー時の処理を行います
338  */
339 function requiredElementError(parent, name) {
340         alert(parent.ownerDocument.URL + ": 必須な要素 " + name + " が存在しないか空な " + parent.tagName + " 要素が存在します");
341 }
342
343 function xmlAttrContentEscape(str) {
344         return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/^[ ]+/mg, "&nbsp;").replace(/^[\t]+/mg, "");
345 }
346
347 /**
348  * 日付のHTML表示用バリデーション処理を行います
349  * @param {String} data RFC3339形式のdate-time文字列
350  */
351 function validateData(data) {
352         var regT = new RegExp("T", "gm");
353         data = data.replace(regT, " ");
354
355         // 秒数の小数点以下の部分はカットする
356         data = data.substring(0, 19);
357
358         return data;
359 }
360
361 /**
362  * 記事本文のバリデーション処理を行います
363  * @param {String} contents 記事の本文が格納されている文字列
364  */
365 function validateText(contents) {
366         // <br/>タグを挿入する
367         if (validateMode == 0) {
368                 contents = contents.replace(/[\n\r]|\r\n/g, "<br />");
369         }
370
371         return contents;
372 }
373
374 /**
375  * URLからパラメタを取得するための関数
376  */
377 function getParamFromUrl(paramName) {
378         // GETパラメタよりタグを取得する
379         var tag = "";
380         if (location.search.length > 1) {
381                 var queries = location.search.substring(1).split('&');
382                 for (var i = 0; i < queries.length; i++) {
383                         if (("" + queries[i].split('=')[0]) == paramName) {
384                                 tag = "" + queries[i].split('=')[1];
385                         }
386                 }
387         }
388
389         return tag;
390 }
391
392 /**
393  * URLからハッシュを取得するための関数
394  */
395 function getHashFromUrl() {
396         return "" + location.hash.substring(1);
397 }
398
399 /**
400  * 長い順に並べるための比較関数です
401  * @param {String} a 比較対象(1)
402  * @param {String} b 比較対象(2)
403  */
404 function compareLengthDecrease(a, b) {
405         a = a.length;
406         b = b.length;
407         return a > b ? -1 : a < b ? 1 : 0;
408 }
409
410 /**
411  * セマフォ制御用のオブジェクトです
412  */
413 function Semaphore() {
414         this.id = null;
415         this.count = 0;
416         this.buf = [];
417         this.xhrs = [];
418 }
419
420 /**
421  * セマフォ初期化用の関数です
422  */
423 Semaphore.prototype.init = function() {
424         while (this.xhrs.length > 0) {
425                 this.xhrs.shift().abort();
426         }
427         this.id = Math.random();
428         this.count = 0;
429         this.buf = [];
430 };
431
432 /**
433  * ログファイル選択用のコンボボックスをid名:feedblog_logselecterに生成します
434  * class - .feedblog_logform, .feedblog_logselecter
435  */
436 function logXMLLoader() {
437         // ログ用のXMLを読み込みます
438         jQuery.ajax({
439                 url : logXmlUrl + '?time=' + urlSuffix,
440                 method : "GET",
441                 error : showErrorEffect,
442                 success : function(xmlData) {
443                         var separateTag = xmlData.getElementsByTagName("file");
444
445                         // 読み込んだ要素をStoreに格納して表示
446                         var boxBuffer = [];
447                         boxBuffer.push("<form class='feedblog_logselecter' name='feedblog_logform'><select class='feedblog_logselecter' id='feedblog_logbox' onchange='fullWriteMode(this.options[this.selectedIndex].value)'>");
448                         for (var i = 0; i < separateTag.length; i++) {
449                                 boxBuffer.push("<option value='" + separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue + "'>" + separateTag[i].getElementsByTagName("display")[0].firstChild.nodeValue + "</option>");
450                         }
451                         boxBuffer.push("</select></form>");
452
453                         // コンボボックス要素を生成
454                         $("#feedblog_logselecter").html(boxBuffer.join(""));
455                 }
456         });
457 }
458
459 /**
460  * 記事のデータが記述されたXMLデータを読み込むロジックを生成します
461  * @param {String} fileName 読み込み記事のデータが記述されているXMLファイルのパス
462  */
463 function fullWriteMode(fileName) {
464         // ロードエフェクトに切り替え
465         loadingEffect();
466
467         var url = fileName;
468
469         // 記事をロードします
470         var loader = new jQuery.ajax({
471                 url : url + '?time=' + urlSuffix,
472                 method : "GET",
473                 success : function(xmlData) {
474                         var separateTag = xmlData.getElementsByTagName("entry");
475                         var stringBuffer = [];
476                         // メモリ上での保持変数を初期化します
477                         loadedEntries = [];
478
479                         // メモリ上の変数に全ての記事要素を格納します
480                         for (var i = 0; i < separateTag.length; i++) {
481                                 loadedEntries.push(new Entry(separateTag[i]));
482                         }
483
484                         // 表示ロジック呼び出し
485                         showEntriesRange(showLength, 0);
486                 },
487                 error : showErrorEffect
488         });
489 }
490
491 /**
492  * 渡された文字列と一致するfeed1.0:updated要素を持った記事を検索し、表示します
493  * @param {String} urlhash feed1.0:updated要素と一致する文字列
494  */
495 function searchHashMode(urlhash) {
496         // ロードエフェクト表示
497         loadingEffect();
498
499         // ログXMLファイルを読み込む
500         var loader = new jQuery.ajax({
501                 url : logXmlUrl + '?time=' + urlSuffix,
502                 method : "GET",
503                 error : showErrorEffect,
504                 success : function(xmlData) {
505                         // ファイルパスの要素のみを抽出する
506                         var separateTag = xmlData.getElementsByTagName("file");
507                         var urls = new Array(separateTag.length);
508
509                         // すべてのファイルパスを配列に格納する
510                         for (var i = 0; i < separateTag.length; i++) {
511                                 // "path"ノードの値を格納
512                                 urls[i] = separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue;
513                         }
514
515                         // セマフォを初期化
516                         fetchEntriesSemaphore.init();
517                         fetchEntriesSemaphore.urls = urls;
518                         fetchEntriesSemaphore.count = urls.length;
519
520                         // ファイルパス配列に格納されているすべての記事に対し、探索を開始する
521                         for (var i = 0; i < separateTag.length; i++) {
522                                 // ファイルパス配列の要素からリクエストを生成し、対象データをロードする
523                                 var xhr = new jQuery.ajax({
524                                         url : urls[i],
525                                         method : "GET",
526                                         success : fetchHashEntries
527                                 });
528                                 fetchEntriesSemaphore.xhrs.push(xhr);
529                         }
530                 }
531         });
532 }
533
534 /**
535  * URLハッシュ検索用のjQueryコールバック関数
536  */
537 function fetchHashEntries(xmlData) {
538         // 既に検索結果が算出されていた場合は、何もしない
539         if (fetchEntriesSemaphore.buf > 0) {
540                 return true;
541         }
542
543         // ハッシュを取得
544         var urlhash = getHashFromUrl();
545
546         // entry要素のみを切り出す
547         var entries = xmlData.getElementsByTagName("entry");
548
549         for (var i = 0; i < entries.length; i++) {
550                 // entryタグ内部のidノードの値のみ抽出し、入力されたhashと比較を行う
551                 var entry = new Entry(entries[i]);
552
553                 // idの値と比較を行う
554                 if (urlhash == entry.id) {
555                         // 一致した場合は該当記事を表示する
556                         $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
557                         generatePanel(entry, "feedblog_drawitem", "feedblog_drawpanel", false);
558
559                         fetchEntriesSemaphore.buf.push(entry);
560                         return true;
561                 }
562         }
563
564         // セマフォのカウンタを減少させます (Ajaxとの同期のため)
565         fetchEntriesSemaphore.count--;
566
567         // 最後のファイルまで探索しても記事が見つからなかった場合はエラーを表示します。
568         if (fetchEntriesSemaphore.count == 0) {
569                 var entries = fetchEntriesSemaphore.buf;
570
571                 if (entries.length == 0) {
572                         notFoundErrorEffect();
573                         return false;
574                 }
575         }
576 }
577
578 /**
579  * 渡された文字列と一致するfeed1.0:id(sha-1)要素を持った記事を検索し、表示します
580  * @param {String} urlhash feed1.0:id(sha-1)要素と一致する文字列
581  */
582 function searchIdMode(urlhash) {
583         // ロードエフェクト表示
584         loadingEffect();
585
586         // ログXMLファイルを読み込む
587         var loader = new jQuery.ajax({
588                 url : logXmlUrl + '?time=' + urlSuffix,
589                 method : "GET",
590                 error : showErrorEffect,
591                 success : function(xmlData) {
592                         // ファイルパスの要素のみを抽出する
593                         var separateTag = xmlData.getElementsByTagName("file");
594                         var urls = new Array(separateTag.length);
595
596                         // すべてのファイルパスを配列に格納する
597                         for (var i = 0; i < separateTag.length; i++) {
598                                 // "path"ノードの値を格納
599                                 urls[i] = separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue;
600                         }
601
602                         // セマフォを初期化
603                         fetchEntriesSemaphore.init();
604                         fetchEntriesSemaphore.urls = urls;
605                         fetchEntriesSemaphore.count = urls.length;
606
607                         // ファイルパス配列に格納されているすべての記事に対し、探索を開始する
608                         for (var i = 0; i < separateTag.length; i++) {
609                                 // ファイルパス配列の要素からリクエストを生成し、対象データをロードする
610                                 var xhr = new jQuery.ajax({
611                                         url : urls[i],
612                                         method : "GET",
613                                         success : fetchIdEntries
614                                 });
615                                 fetchEntriesSemaphore.xhrs.push(xhr);
616                         }
617                 }
618         });
619 }
620
621 /**
622  * ID検索用のjQueryコールバック関数
623  */
624 function fetchIdEntries(xmlData) {
625         // 既に検索結果が算出されていた場合は、何もしない
626         if (fetchEntriesSemaphore.buf > 0) {
627                 return true;
628         }
629
630         // IDを取得
631         var id = getParamFromUrl("id");
632
633         // entry要素のみを切り出す
634         var entries = xmlData.getElementsByTagName("entry");
635
636         for (var i = 0; i < entries.length; i++) {
637                 // entryタグ内部のidノードの値のみ抽出し、入力されたhashと比較を行う
638                 var entry = new Entry(entries[i]);
639
640                 // idの値と比較を行う
641                 if (id == CryptoJS.SHA1(entry.id).toString()) {
642                         // 一致した場合は該当記事を表示する
643                         $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
644                         generatePanel(entry, "feedblog_drawitem", "feedblog_drawpanel", false);
645
646                         fetchEntriesSemaphore.buf.push(entry);
647                         return true;
648                 }
649
650         }
651
652         // セマフォのカウンタを減少させます (Ajaxとの同期のため)
653         fetchEntriesSemaphore.count--;
654
655         // 最後のファイルまで探索しても記事が見つからなかった場合はエラーを表示します。
656         if (fetchEntriesSemaphore.count == 0) {
657                 var entries = fetchEntriesSemaphore.buf;
658
659                 if (entries.length == 0) {
660                         notFoundErrorEffect();
661                         return false;
662                 }
663         }
664
665 }
666
667 /**
668  * 渡された文字列と一致するfeed1.0:category:termタグ要素を持った記事を検索し、表示します
669  * @param {String} tag feed1.0:category:term要素と一致する文字列
670  * @param {boolean} showLimit 省略表示をする場合はtrue,全件表示する場合はfalse
671  */
672 function searchTagMode(tag, showLimit) {
673         // 処理中エフェクト表示
674         executingEffect();
675
676         // ログXMLファイルを読み込む
677         var loader = new jQuery.ajax({
678                 url : logXmlUrl + '?time=' + urlSuffix,
679                 method : "GET",
680                 error : showErrorEffect,
681                 success : function(xmlData) {
682                         // ファイルパスの要素のみを抽出する
683                         var separateTag = xmlData.getElementsByTagName("file");
684                         var urls = new Array(separateTag.length);
685
686                         // すべてのファイルパスを配列に格納する
687                         for (var i = 0; i < separateTag.length; i++) {
688                                 // "path"ノードの値を格納
689                                 urls[i] = separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue;
690                         }
691
692                         // セマフォを初期化
693                         fetchEntriesSemaphore.init();
694                         fetchEntriesSemaphore.urls = urls;
695                         fetchEntriesSemaphore.count = urls.length;
696                         fetchEntriesSemaphore.entryIndex = 0;
697                         fetchEntriesSemaphore.entryCount = 0;
698                         fetchEntriesSemaphore.showLimit = showLimit;
699                         fetchEntriesSemaphore.hiddenEntries = [];
700
701                         // ファイルパス配列に格納されているすべての記事に対し、探索を開始する
702                         for (var i = 0; i < separateTag.length; i++) {
703                                 // ファイルパス配列の要素からリクエストを生成し、対象データをロードする
704                                 var xhr = new jQuery.ajax({
705                                         url : urls[i],
706                                         method : "GET",
707                                         success : fetchTagEntries
708                                 });
709                                 fetchEntriesSemaphore.xhrs.push(xhr);
710                         }
711                 }
712         });
713 }
714
715 /**
716  * タグ検索用のjQueryコールバック関数
717  */
718 function fetchTagEntries(xmlData) {
719         // 既に記事の表示が行われている場合、何も実施しない
720         if (fetchEntriesSemaphore.buf.length > 0) {
721                 return true;
722         }
723
724         // タグを取得する
725         var tag = getParamFromUrl("tag");
726
727         // entry要素のみを切り出す
728         var entries = xmlData.getElementsByTagName("entry");
729
730         for (var j = 0; j < entries.length; j++) {
731                 var entry = new Entry(entries[j]);
732
733                 for (var k = 0; k < entry.category.length; k++) {
734                         // タグのIDが一致したら格納
735                         if (tag == entry.category.eq(k).attr("term")) {
736                                 // 表示数限界を超えていた場合、表示処理を実施しない
737                                 if (fetchEntriesSemaphore.showLimit == true && fetchEntriesSemaphore.entryIndex >= showLength) {
738                                         // 検索一致件数をカウント
739                                         fetchEntriesSemaphore.entryCount++;
740                                         // entryを非表示配列に格納する
741                                         fetchEntriesSemaphore.hiddenEntries.push(entry);
742                                 } else {
743                                         // 検索一致件数をカウント
744                                         fetchEntriesSemaphore.entryCount++;
745                                         // entryを格納する
746                                         fetchEntriesSemaphore.entryIndex = showEntriesAdd(entry, fetchEntriesSemaphore.entryIndex);
747                                 }
748                         }
749                 }
750         }
751
752         // セマフォのカウンタを減少させます (Ajaxとの同期のため)
753         fetchEntriesSemaphore.count--;
754
755         if (fetchEntriesSemaphore.count == 0) {
756                 if (fetchEntriesSemaphore.entryIndex == 0) {
757                         notFoundErrorEffect();
758                         return false;
759                 } else {
760                         executingEffectCompleteEffect();
761                         // 省略記事がある場合は、省略パネルを表示する
762                         if (fetchEntriesSemaphore.entryIndex != fetchEntriesSemaphore.entryCount) {
763                                 showLimitedInfoEffect();
764                         }
765                         return true;
766                 }
767         }
768 }
769
770 /**
771  * 省略表示されている記事一覧を描画します
772  */
773 function fetchTagHiddenEntries() {
774         // 省略パネルを消去する
775         showLimitedInfoRemoveEffect();
776
777         // 省略されている記事を描画する
778         for (var i = 0; i < fetchEntriesSemaphore.hiddenEntries.length; i++) {
779                 fetchEntriesSemaphore.entryIndex = showEntriesAdd(fetchEntriesSemaphore.hiddenEntries[i], fetchEntriesSemaphore.entryIndex);
780         }
781 }
782
783 /**
784  * 検索結果を分割して表示します
785  * class - div.feedblog_pager, ul.feedblog_pager, li.feedblog_pager などなど
786  * @param {int} showLength 一回の画面に表示する記事数
787  * @param {int} startIndex 表示を開始する記事のインデックス
788  */
789 function showEntriesRange(showLength, startIndex) {
790         // メモリ上から記事データをロード
791         var entries = loadedEntries;
792
793         // 表示インデックスが範囲外の場合はエラーパネルを表示して終了
794         if (startIndex < 0 || (entries.length <= startIndex && entries.length != 0)) {
795                 showErrorEffect();
796                 return;
797         }
798
799         var stringBuffer = [];
800
801         // リミッターを設定する
802         var loopLimit = (showLength + startIndex > entries.length) ? entries.length : showLength + startIndex;
803         var indexShowEntries = loopLimit + 1;
804
805         // 情報メニュー表示用バッファです
806         var menuInfoBuffer = [];
807         menuInfoBuffer.push("<div class='feedblog_pager_shownumber'>");
808         var viewStartIndex = 0;
809         entries.length == 0 ? viewStartIndex = 0 : viewStartIndex = startIndex + 1;
810         menuInfoBuffer.push(viewStartIndex + "件~" + loopLimit + "件(全" + entries.length + "件)目の記事を表示中<br/>");
811         menuInfoBuffer.push("</div>");
812
813         // ページ移動メニュー表示用バッファです
814         var menuMoveBuffer = [];
815         menuMoveBuffer.push("<ul class='feedblog_pager'>");
816
817         // ブランクエリアを挟む
818         menuMoveBuffer.push("<li class='feedblog_pager_blank'></li>");
819
820         // 左パネルの表示制御
821         if (startIndex - showLength >= 0) {
822                 menuMoveBuffer.push("\<li class='feedblog_pager_goback'><span class='feedblog_pager_goback' onclick='showEntriesRange(" + showLength + ", " + (startIndex - showLength) + "); return false;'>\< 前の" + showLength + "件を表示</span\></li>");
823         } else {
824                 menuMoveBuffer.push("\<li class='feedblog_pager_goback'>\< 前の" + showLength + "件を表示</a\></li>");
825         }
826
827         // 中央のパネルの表示制御
828         menuMoveBuffer.push("<li class='feedblog_pager_center'>[ ");
829         var menuNumbers = Math.ceil(entries.length / showLength);
830         for ( i = 0; i < menuNumbers; i++) {
831                 if (startIndex / showLength == i) {
832                         menuMoveBuffer.push(i + " ");
833                 } else {
834                         menuMoveBuffer.push("<span class='feedblog_pager_center' onclick='showEntriesRange(" + showLength + ", " + (i * showLength) + "); return false;'>");
835                         menuMoveBuffer.push(i);
836                         menuMoveBuffer.push("</span> ");
837                 }
838         }
839         menuMoveBuffer.push("]</li>");
840
841         // 右パネルの表示制御
842         if (entries.length > startIndex + showLength) {
843                 menuMoveBuffer.push("\<li class='feedblog_pager_gonext'><span class='feedblog_pager_gonext' onclick='showEntriesRange(" + showLength + ", " + (startIndex + showLength) + "); return false;'>\次の" + showLength + "件を表示 \></span\></li>");
844         } else {
845                 menuMoveBuffer.push("\<li class='feedblog_pager_gonext'>次の" + showLength + "件を表示 \></li>");
846         }
847
848         // ブランクエリアを挟む
849         menuMoveBuffer.push("<li class='feedblog_pager_blank'></li>");
850
851         menuMoveBuffer.push("</ul>");
852
853         // メニューを結合してパネル(前方)に組み込みます
854         stringBuffer.push("<div class='feedblog_pager_wrapper'>");
855         stringBuffer.push(menuInfoBuffer.join(""));
856         stringBuffer.push(menuMoveBuffer.join(""));
857         stringBuffer.push("</div>");
858
859         // 記事描画部分のパネルを生成します
860         for (var i = startIndex; i < loopLimit; i++) {
861                 stringBuffer.push('<div class="feedblog_drawpanel" id="feedblog_drawpanel');
862                 stringBuffer.push(i);
863                 stringBuffer.push('"><div class="feedblog_drawitem" id="feedblog_drawitem');
864                 stringBuffer.push(i);
865                 stringBuffer.push('"><\/div><\/div>');
866         }
867
868         // メニューを結合してパネル(後方)に組み込みます
869         stringBuffer.push("<div class='feedblog_pager_wrapper'>");
870         stringBuffer.push(menuMoveBuffer.join(""));
871         stringBuffer.push(menuInfoBuffer.join(""));
872         stringBuffer.push("</div>");
873
874         $("#feedblog_writearea").html(stringBuffer.join(""));
875
876         for (var i = startIndex; i < loopLimit; i++) {
877                 // 各要素をオブジェクトに格納します
878                 var entry = entries[i];
879
880                 // すべてのパネルをオープン状態で生成します
881                 generatePanel(entry, "feedblog_drawitem" + i, "feedblog_drawpanel" + i, false);
882         }
883 }
884
885 /**
886  * Entry要素を追加表示する為の関数
887  * class - div.feedblog_pager, ul.feedblog_pager, li.feedblog_pager などなど
888  * @param {Entry} 末尾に追加するEntryオブジェクト
889  * @param {int} 現在のEntry数
890  * @return {int} 新規記事を追加し終えた後のEntry数
891  */
892 function showEntriesAdd(entry, entryIndex) {
893
894         var stringBuffer = [];
895
896         // 記事描画部分のパネルを生成します
897         var newIndex = entryIndex + 1;
898         stringBuffer.push('<div class="feedblog_drawpanel" id="feedblog_drawpanel');
899         stringBuffer.push(newIndex);
900         stringBuffer.push('"><div class="feedblog_drawitem" id="feedblog_drawitem');
901         stringBuffer.push(newIndex);
902         stringBuffer.push('"><\/div><\/div>');
903
904         // 記事をパネルに描画します
905         $("#feedblog_writearea").html($("#feedblog_writearea").html() + stringBuffer.join(""));
906         generatePanel(entry, "feedblog_drawitem" + newIndex, "feedblog_drawpanel" + newIndex, false);
907
908         return newIndex;
909 }
910