OSDN Git Service

- modified notfound message.
[feedblog/feedblog.git] / js / lunardial / feedblog_search.js
1 /**
2  * FeedBlog SearchScript
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 // 現在の検索語のキャッシュ
39 var currentSearchWords;
40
41 // ログのファイルリストを格納するグローバル変数です
42 var logData;
43
44 // コンボボックスのオブジェクトを格納するグローバル変数です
45 var comboBox;
46
47 // URL末尾用文字列(スクリプトを開いた瞬間のミリ秒を記録)
48 var urlSuffix;
49
50 /**
51  * 記事を実際に生成します。この部分を編集することでデザインを変更可能です。
52  * @param {Entry} entry 記事の情報が代入されたEntryオブジェクト
53  * @param {String} drawitem 「本文」を描画すべきパネルのDIV要素のid
54  * @param {String} renderto 「タイトル・更新日時・本文」の1日分の記事データを最終的に描画すべきパネルのDIV要素のid
55  * @param {String} closed (Ext jsパネルオプション)記事をクローズ状態で生成するか否か
56  */
57 function generatePanel(entry, drawitem, renderto, closed) {
58         // プラグインを実行
59         if ( typeof ($("#" + renderto).feedblog_contents_plugin) == "function") {
60                 $("#" + renderto).feedblog_contents_plugin({
61                         entry : entry
62                 });
63         }
64
65         // HTML用の配列を用意する
66         var htmlBuffer = [];
67
68         // 内部的に描画先IDを生成
69         var feedblogContentId = "" + renderto + "_content_div";
70
71         // 各要素をオブジェクトに描画
72         $("#" + drawitem).html(entry.content);
73
74         // ヘッダパネルを生成 class= .feedblog_header
75         htmlBuffer.push("<div class='feedblog_header' onclick='closePanel(\"" + feedblogContentId + "\")'><span>" + entry.title + "</span></div>" +
76
77         // 本体記事を作成 class= .feedblog_content
78         "<div class='feedblog_content' id='" + feedblogContentId + "'><span>" + document.getElementById(renderto).innerHTML + "</span></div>");
79
80         // 最終描画実施
81         $("#" + renderto).html(htmlBuffer.join(""));
82 }
83
84 /**
85  * システム表示画面を実際に生成します。この部分を編集することでデザインを変更可能です。
86  * @param {Entry} entry 記事の情報が代入されたEntryオブジェクト
87  * @param {String} drawitem パネルの本文を格納したDIV要素のid
88  * @param {String} renderto 「タイトル・更新日時・本文」の1日分の記事データを焼き付けるDIV要素のid
89  * @param {String} closed (Ext jsパネルオプション)記事をクローズ状態で生成するか否か
90  */
91 function generateSystemPanel(entry, drawitem, renderto, closed) {
92         // HTMLを生成する
93         var htmlBuffer = [];
94
95         // 描画先IDを生成
96         var feedblogContentId = "" + renderto + "_content_div";
97
98         // 各要素をオブジェクトに描画
99         $("#" + drawitem).html(entry.content);
100
101         // ヘッダパネルを生成 class= .feedblog_header
102         htmlBuffer.push("<div class='feedblog_header' onclick='closePanel(\"" + feedblogContentId + "\")'><span>" + entry.title + "</span></div>" +
103
104         // 本体記事を作成 class= .feedblog_content
105         "<div class='feedblog_content' id='" + feedblogContentId + "'><span>" + document.getElementById(renderto).innerHTML + "</span></div>");
106
107         $("#" + renderto).html(htmlBuffer.join(""));
108 }
109
110 /**
111  * 検索フォーム及び結果表示フォームを生成するメソッドです。
112  */
113 function generateForm() {
114         var formBuffer = [];
115         formBuffer.push("<form class='feedblog_searchform' onsubmit='javascript: searchDiary(); return false;'>▼ 検索語句を入力してください (語句を半角で区切るとAND、|で区切るとORで検索します)<br/>");
116         formBuffer.push("<input type='text' id='feedblog_searchword' class='feedblog_searchword'><input type='submit' id='feedblog_execsearch' value='検索'><br/>");
117         formBuffer.push("<input type='checkbox' id='feedblog_regexpOptionI' class='feedblog_regexpOptionI' checked='checked'/><label class='feedblog_searchform' for='feedblog_regexpOptionI'>大文字、小文字を区別しない</label><br/>");
118         formBuffer.push("<br/>");
119         formBuffer.push("▼ 検索対象ログ選択<br/>");
120         formBuffer.push("<div id='feedblog_logselecter'></div>");
121         formBuffer.push("<input type='checkbox' id='feedblog_allsearchcheck' class='feedblog_allsearchcheck' checked='checked'/>");
122         formBuffer.push("<label class='feedblog_searchform' for='feedblog_allsearchcheck'>すべてのログに対して検索を行う</label>");
123         formBuffer.push("<br/>");
124         formBuffer.push("</form>");
125         $("#feedblog_searchform").html(formBuffer.join(""));
126
127         var resultAreaBuffer = "<div class='feedblog_result_status'></div>";
128
129         $("#feedblog_resultwritearea").html(resultAreaBuffer);
130 }
131
132 /**
133  * 全ての定数を取得・セットします
134  */
135 function initialize() {
136         // 初期値をhiddenパラメータより読み込みます
137         mainPageUrl = $("#feedblog_mainpageurl").val();
138         searchPageUrl = $("#feedblog_searchpageurl").val();
139         latestXml = $("#feedblog_latestxml").val();
140         logXmlUrl = $("#feedblog_loglistxmlurl").val();
141         showLength = parseInt($("#feedblog_showlength").val());
142         if (isNaN(showLength)) {
143                 showLength = 1;
144         }
145         validateMode = $("#feedblog_validatemode").val();
146
147         // 初期値を設定します
148         urlSuffix = +new Date();
149
150         // 必要な環境を確認します
151         var errorBuf = [];
152         // 変数確認
153         if (mainPageUrl === undefined) {
154                 errorBuf.push("設定値「feedblog_mainpageurl」が欠落しています。");
155         }
156         if (searchPageUrl === undefined) {
157                 errorBuf.push("設定値「feedblog_searchpageurl」が欠落しています。");
158         }
159         if (latestXml === undefined) {
160                 errorBuf.push("設定値「feedblog_latestxml」が欠落しています。");
161         }
162         if (logXmlUrl === undefined) {
163                 errorBuf.push("設定値「feedblog_loglistxmlurl」が欠落しています。");
164         }
165         if (showLength === undefined) {
166                 errorBuf.push("設定値「feedblog_showlength」が欠落しています。");
167         }
168         if (validateMode === undefined) {
169                 errorBuf.push("設定値「feedblog_validatemode」が欠落しています。");
170         }
171         // SHA-1関数確認
172         try {
173                 if ( typeof (CryptoJS.SHA1) != "function") {
174                         errorBuf.push("crypt-jsモジュール(hmac-sha1.js)が読み込まれていません。");
175                 }
176         } catch (ex) {
177                 errorBuf.push("crypt-jsモジュール(hmac-sha1.js)が読み込まれていません。");
178         }
179
180         // 描画エリアチェック
181         if ($("#feedblog_searchform").length == 0) {
182                 errorBuf.push("描画エリア「feedblog_searchform」が存在しません。");
183         }
184         if ($("#feedblog_resultwritearea").length == 0) {
185                 errorBuf.push("描画エリア「feedblog_resultwritearea」が存在しません。");
186         }
187         if ($("#feedblog_writearea").length == 0) {
188                 errorBuf.push("描画エリア「feedblog_writearea」が存在しません。");
189         }
190
191         // エラーがある場合は以降の処理を継続しない
192         if (errorBuf.length > 0) {
193                 alert("初期設定値に誤りがあります。\n詳細:\n" + errorBuf.join("\n"));
194                 return false;
195         }
196
197         return true;
198 }
199
200 /**
201  * jQueryへのイベント登録です
202  */
203 $(document).ready(function() {
204         // 初期処理を実施
205         if (!initialize()) {
206                 return false;
207         }
208
209         // ログ一覧のXMLをロードします
210         logXMLLoader();
211
212         // 各種パネルを生成します
213         generateForm();
214 });
215
216 /**
217  * jQueryでのパネル開閉を制御します
218  */
219 function closePanel(id) {
220         $("#" + id).slideToggle();
221 }
222
223 /**
224  * 記事クラス
225  * @param {Object} obj entry 要素の DOM オブジェクト
226  */
227 function Entry(obj) {
228         this.title = $("title:first", obj).text();
229         if (this.title == "")
230                 requiredElementError(obj, "title");
231         this.title = validateText(this.title);
232         this.content = $("content:first", obj).text();
233         this.content = validateText(this.content);
234         this.id = $("id:first", obj).text();
235         if (this.id == "")
236                 requiredElementError(obj, "id");
237         this.date = $("updated:first", obj).text();
238         if (this.date == "")
239                 requiredElementError(obj, "updated");
240         this.date = validateData(this.date);
241         this.category = $("category", obj);
242 }
243
244 /**
245  * システム用記事クラス
246  * @param {Object} obj entry 要素の DOM オブジェクト
247  */
248 function SystemEntry(obj) {
249         this.title = $("title:first", obj).text();
250         this.title = validateText(this.title);
251         this.content = $("content:first", obj).text();
252         this.content = validateText(this.content);
253         this.id = $("id:first", obj).text();
254         this.date = $("updated:first", obj).text();
255         this.date = validateData(this.date);
256         this.category = $("category", obj);
257 }
258
259 /**
260  * 記事内が単語群を全て含んでいるか
261  * @param {Array} keywords 単語群
262  * @param {String} regexpType 正規表現の検索モードを示す文字列
263  * @return {boolean} bool 全て含んでいれば true、さもなくば false
264  */
265 Entry.prototype.hasKeywords = function(keywords, regexpType) {
266         // 正規表現が一致するかという判定"のみ"を行います
267         for (var i = 0; i < keywords.length; i++) {
268                 // 正規表現チェック用のオブジェクトを用意します(OR条件は一時的に条件を置換)
269                 var reg = new RegExp('(?:' + keywords[i] + ')(?![^<>]*>)', regexpType);
270                 // 一致しなかったらその時点で脱出
271                 if (!reg.test(this.content) && !reg.test(this.title))
272                         return false;
273         }
274         return true;
275 };
276
277 /**
278  * 呼び出すとDIV:id名:feedblog_writearea上のHTMLを削除し、ロードエフェクトを表示します
279  */
280 function loadingEffect() {
281         $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
282
283         // ロード表示用のパネルを生成
284         var systemEntry = new SystemEntry();
285         systemEntry.title = "Now Loading .....";
286         systemEntry.content = '<br/>長時間画面が切り替わらない場合はページをリロードしてください。<br/><br/>';
287         generateSystemPanel(systemEntry, "feedblog_drawitem", "feedblog_drawpanel", false);
288
289         // 結果表示エリアをリセット
290         $("#feedblog_resultwritearea").html("<div class='feedblog_result_status'></div>");
291 }
292
293 /**
294  * 記事データのエラー時の処理を行います
295  */
296 function showErrorEffect() {
297         $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
298
299         // エラー内容をパネルに描画
300         var systemEntry = new SystemEntry();
301         systemEntry.title = "エラー";
302         var errorContent = [];
303         errorContent.push('<br/>記事ファイル(XML)の取得に失敗しました。以下のような原因が考えられます。<br/><br/>');
304         errorContent.push('・設定値「feedblog_latestxml」に正しいパスが設定されていない。<br/>');
305         errorContent.push('・設定値「feedblog_loglistxmlurl」に正しいパスが設定されていない。<br/>');
306         errorContent.push('・ローカル環境で起動している(必ずサーバにアップロードして実行してください)。<br/>');
307         errorContent.push('<br/>');
308         systemEntry.content = errorContent.join("\n");
309         generateSystemPanel(systemEntry, "feedblog_drawitem", "feedblog_drawpanel", false);
310
311         // 結果表示エリアをリセット
312         $("#feedblog_resultwritearea").html("<div class='feedblog_result_status'></div>");
313 }
314
315 /**
316  * 記事データのエラー時の処理を行います
317  */
318 function notFoundErrorEffect() {
319         $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
320         $("#feedblog_drawitem").html('<br/>検索条件に一致する記事は見つかりませんでした。<br/>或いはキャッシュが残っている可能性があります。ブラウザの「更新」ボタンを押して下さい。<br/><br/>');
321
322         // エラー内容をパネルに描画
323         var systemEntry = new SystemEntry();
324         systemEntry.title = "検索結果";
325         systemEntry.content = '<br/>検索条件に一致する記事は見つかりませんでした。<br/><br/>';
326         generateSystemPanel(systemEntry, "feedblog_drawitem", "feedblog_drawpanel", false);
327
328         // 結果表示エリアをリセット
329         $("#feedblog_resultwritearea").html("<div class='feedblog_result_status'></div>");
330 }
331
332 /**
333  * 記事データのエラー時の処理を行います
334  */
335 function requiredElementError(parent, name) {
336         alert(parent.ownerDocument.URL + ": 必須な要素 " + name + " が存在しないか空な " + parent.tagName + " 要素が存在します");
337 }
338
339 /**
340  * 日付のHTML表示用バリデーション処理を行います
341  * @param {String} data RFC3339形式のdate-time文字列
342  */
343 function validateData(data) {
344         data = data.replace(/T/g, " ");
345
346         // 秒数の小数点以下の部分はカットする
347         data = data.substring(0, 19);
348
349         return data;
350 }
351
352 /**
353  * 記事本文のバリデーション処理を行います
354  * @param {String} contents 記事の本文が格納されている文字列
355  */
356 function validateText(contents) {
357         // <br/>タグを挿入する
358         if (validateMode == 0) {
359                 contents = contents.replace(/[\n\r]|\r\n/g, "<br />");
360         }
361
362         return contents;
363 }
364
365 /**
366  * XML用に要素をエスケープします
367  * @param {String} str エスケープを行いたい文字列
368  */
369 function xmlAttrContentEscape(str) {
370         // return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
371         return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/^[ ]+/mg, "&nbsp;").replace(/^[\t]+/mg, "");
372 }
373
374 /**
375  * 記事本文に日付を付加します
376  * @param {String} contents 記事の本文が格納されている文字列
377  * @param {String} id 記事の初公開日を示す日付文字列
378  */
379 function contentsWithid(contents, id) {
380         // リンク用文末作成
381         var hashTag = '<br/><div class="feedblog_content_footer"><a href="' + xmlAttrContentEscape(mainPageUrl) + '#' + xmlAttrContentEscape(id) + '" target="_blank">- この日の記事にリンクする -<\/a><\/div>';
382         return contents + hashTag;
383 }
384
385 /**
386  * 強調タグを追加します
387  * @param {String} word 強調したい語句
388  */
389 function emphasizeWord(word) {
390         return '<span style="background-color: red;">' + word + '</span>';
391 }
392
393 /**
394  * 長い順に並べるための比較関数です
395  * @param {String} a 比較対象(1)
396  * @param {String} b 比較対象(2)
397  */
398 function compareLengthDecrease(a, b) {
399         a = a.length;
400         b = b.length;
401         return a > b ? -1 : a < b ? 1 : 0;
402 }
403
404 /**
405  * セマフォ制御用のオブジェクトです
406  */
407 function Semaphore() {
408         this.id = null;
409         this.count = 0;
410         this.buf = [];
411         this.xhrs = [];
412 }
413
414 /**
415  * セマフォ初期化用の関数です
416  */
417 Semaphore.prototype.init = function() {
418         while (this.xhrs.length > 0) {
419                 this.xhrs.shift().abort();
420         }
421         this.id = Math.random();
422         this.count = 0;
423         this.buf = [];
424 };
425
426 /**
427  * ログファイル選択用のコンボボックスをid名:feedblog_logselecterに生成します
428  */
429 function logXMLLoader() {
430         // ログ用のXMLを読み込みます
431         jQuery.ajax({
432                 url : logXmlUrl + '?time=' + urlSuffix,
433                 method : "GET",
434                 error : showErrorEffect,
435                 success : function(xmlData) {
436                         var separateTag = xmlData.getElementsByTagName("file");
437                         logData = new Array(separateTag.length);
438
439                         // 読み込んだ要素をStoreに格納して表示
440                         var boxBuffer = [];
441                         boxBuffer.push("<select class='feedblog_logselecter' id='feedblog_logbox'>");
442                         for (var i = 0; i < separateTag.length; i++) {
443                                 boxBuffer.push("<option value='" + separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue + "'>" + separateTag[i].getElementsByTagName("display")[0].firstChild.nodeValue + "</option>");
444                                 logData[i] = separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue;
445                         }
446                         boxBuffer.push("</select>");
447
448                         // コンボボックス要素を生成
449                         $("#feedblog_logselecter").html(boxBuffer.join(""));
450                 }
451         });
452 }
453
454 /**
455  * 検索単語を取得します
456  */
457 function getSearchWords() {
458         var searchWord = document.getElementById("feedblog_searchword").value;
459         if (searchWord == "")
460                 return null;
461         var searchWords = [];
462
463         // 検索単語をサニタイジングします
464         // HTMLのメタ文字
465         searchWord = xmlAttrContentEscape(searchWord);
466         // 正規表現のメタ文字
467         searchWord = searchWord.replace(/([$()*+.?\[\\\]^{}])/g, '\\$1');
468         // 半角スペースで配列に分割
469         searchWords = searchWord.replace(/^\s+|\+$/g, '').split(/\s+/);
470         // 正規表現の選択を長い順に並び替えます(AND条件)
471         searchWords.sort(compareLengthDecrease);
472
473         return searchWords.length == 0 ? null : searchWords;
474 }
475
476 /**
477  * 文章内を特定の単語で検索し、一致した部分を強調表示タグで置き換えます
478  * @param {String} searchWord 探索する単語
479  * @param {String} plainText 探索を行う文章
480  * @param {String} regexpType 正規表現の検索モードを示す文字列
481  */
482 function complexEmphasize(searchWord, plainText, regexpType) {
483         // 正規表現の選択を長い順に並び替える
484         searchWord = searchWord.split('|').sort(compareLengthDecrease).join('|');
485         // タグの内側でないことを確認する正規表現を追加
486         var pattern = new RegExp('(?:' + searchWord + ')(?![^<>]*>)', regexpType);
487
488         var result = [];
489         var currentIndex = -1;
490         // 現在マッチしている部分文字列の開始位置
491         var currentLastIndex = -1;
492         // 現在マッチしている部分文字列の、現在の末尾
493         var m;
494         // 正規表現マッチの結果配列
495         while ( m = pattern.exec(plainText)) {
496                 if (m.index > currentLastIndex) {
497                         // 新しい部分文字列へのマッチが始まったので、そこまでの文字列をバッファに書き出す
498                         if (currentIndex < currentLastIndex)
499                                 result.push(emphasizeWord(plainText.substring(currentIndex, currentLastIndex)));
500                         result.push(plainText.substring(currentLastIndex, m.index));
501                         // 開始位置の更新
502                         currentIndex = m.index;
503                 }
504                 // 末尾位置を更新
505                 currentLastIndex = pattern.lastIndex;
506                 // 次の正規表現マッチは今マッチした文字の次の文字から
507                 pattern.lastIndex = m.index + 1;
508         }
509         // 残った文字列を書き出す
510         if (currentIndex < currentLastIndex)
511                 result.push(emphasizeWord(plainText.substring(currentIndex, currentLastIndex)));
512         result.push(plainText.substring(currentLastIndex));
513
514         // 結合して返す
515         return result.join('');
516 }
517
518 /**
519  * 検索結果を分割して表示します(2回目以降呼び出し)
520  * @param {int} showLength 一回の画面に表示する記事数
521  * @param {int} startIndex 表示を開始する記事のインデックス
522  */
523 function showEntriesRange(showLength, startIndex) {
524         // メモリ上から記事データをロード
525         var entries = loadedEntries;
526
527         // 表示インデックスが範囲外の場合はエラーパネルを表示して終了
528         if (startIndex < 0 || entries.length <= startIndex) {
529                 showErrorEffect();
530                 return;
531         }
532
533         var stringBuffer = [];
534
535         // リミッターを設定する
536         var loopLimit = (showLength + startIndex > entries.length) ? entries.length : showLength + startIndex;
537         var indexShowEntries = loopLimit + 1;
538
539         for (var i = startIndex; i < loopLimit; i++) {
540                 stringBuffer.push('<div class="feedblog_drawpanel" id="feedblog_drawpanel');
541                 stringBuffer.push(i);
542                 stringBuffer.push('"><div class="feedblog_drawitem" id="feedblog_drawitem');
543                 stringBuffer.push(i);
544                 stringBuffer.push('"><\/div><\/div>');
545         }
546         $("#feedblog_writearea").html(stringBuffer.join(''));
547
548         stringBuffer.length = 0;
549         for ( i = startIndex; i < loopLimit; i++) {
550                 var entry = entries[i];
551                 generatePanel(entry, "feedblog_drawitem" + i, "feedblog_drawpanel" + i, false);
552         }
553
554         // メニュー表示用バッファ
555         var menuBuffer = [];
556         menuBuffer.push("<div class='feedblog_pager_wrapper'>");
557         menuBuffer.push("<ul class='feedblog_pager'>");
558
559         // ブランクエリアを挟む
560         menuBuffer.push("<li class='feedblog_pager_blank'></li>");
561
562         // 左パネルの表示制御
563         if (startIndex - showLength >= 0) {
564                 menuBuffer.push("\<li class='feedblog_pager_goback'><span class='feedblog_pager_goback' onclick='showEntriesRange(" + showLength + ", " + (startIndex - showLength) + "); return false;'>\< 前の" + showLength + "件を表示</span\></li>");
565         } else {
566                 menuBuffer.push("\<li class='feedblog_pager_goback'>\< 前の" + showLength + "件を表示</a\></li>");
567         }
568
569         // 中央のパネルの表示制御
570         menuBuffer.push("<li class='feedblog_pager_center'>[ ");
571         var menuNumbers = Math.ceil(entries.length / showLength);
572         for ( i = 0; i < menuNumbers; i++) {
573                 if (startIndex / showLength == i) {
574                         menuBuffer.push(i + " ");
575                 } else {
576                         menuBuffer.push("<span class='feedblog_pager_center' onclick='showEntriesRange(" + showLength + ", " + (i * showLength) + "); return false;'>");
577                         menuBuffer.push(i);
578                         menuBuffer.push("</span> ");
579                 }
580         }
581         menuBuffer.push("]</li>");
582
583         // 右パネルの表示制御
584         if (entries.length > startIndex + showLength) {
585                 menuBuffer.push("\<li class='feedblog_pager_gonext'><span class='feedblog_pager_gonext' onclick='showEntriesRange(" + showLength + ", " + (startIndex + showLength) + "); return false;'>次の" + showLength + "件を表示 \></span\></li>");
586         } else {
587                 menuBuffer.push("\<li class='feedblog_pager_gonext'>次の" + showLength + "件を表示 \></li>");
588         }
589
590         // ブランクエリアを挟む
591         menuBuffer.push("<li class='feedblog_pager_blank'></li>");
592
593         menuBuffer.push("</ul></div>");
594
595         // 検索結果を表示します
596         $("#feedblog_resultwritearea").html("<div class='feedblog_result_status'>" + entries.length + "件の記事が該当しました /  " + (startIndex + 1) + "~" + loopLimit + "件目までを表示中<br/></div>" + menuBuffer.join(""));
597 }
598
599 /**
600  * 検索時のjQuery.ajaxのcallback関数
601  */
602 function fetchEntries(xmlData) {
603         // 大文字小文字を区別するかを取得します
604         var regexpOptionI = document.getElementById("feedblog_regexpOptionI");
605         var regexpType = regexpOptionI ? "ig" : "g";
606
607         // entry要素のみを切り出します
608         var entries = xmlData.getElementsByTagName("entry");
609
610         // entry要素の回数だけ実行します
611         for (var j = 0; j < entries.length; j++) {
612                 var entry = new Entry(entries[j]);
613
614                 // 正規表現が一致した場合は、強調表現処理を行います
615                 if (entry.hasKeywords(currentSearchWords, regexpType)) {
616                         // 強調表現を実行します
617                         entry.title = complexEmphasize(currentSearchWords.join("|"), entry.title, regexpType);
618                         entry.content = complexEmphasize(currentSearchWords.join("|"), entry.content, regexpType);
619
620                         fetchEntriesSemaphore.buf.push(entry);
621                 }
622         }
623
624         // セマフォのカウンタを減少させます (Ajaxとの同期のため)
625         fetchEntriesSemaphore.count--;
626
627         // 全てのログを読み終わったら表示
628         if (fetchEntriesSemaphore.count == 0) {
629                 var entries = fetchEntriesSemaphore.buf;
630
631                 // 一軒も検索にヒットしなかった場合は専用のパネルを表示して終了
632                 if (entries.length == 0) {
633                         notFoundErrorEffect();
634                         return;
635                 }
636
637                 // entryを更新時間でソート
638                 entries = entries.sort(function(a, b) {
639                         a = a.updated;
640                         b = b.updated;
641                         return a > b ? -1 : a < b ? 1 : 0;
642                 });
643
644                 loadedEntries = entries;
645
646                 // 表示ロジック呼び出し
647                 showEntriesRange(showLength, 0);
648         }
649 }
650
651 /**
652  * 「探索」ボタンを押されたときに呼び出されるメソッドです
653  */
654 function searchDiary() {
655         // 検索結果フィールドをクリアします
656         document.getElementById("feedblog_writearea").innerHTML = "";
657
658         // 探索したい単語を取得します
659         currentSearchWords = getSearchWords();
660         if (!currentSearchWords) {
661                 alert("検索対象の単語が入力されていません");
662                 // 検索結果の欄をリセットします
663                 $("#feedblog_resultwritearea").html("<div class='feedblog_result_status'></div>");
664                 return;
665         }
666
667         // ロードエフェクトを表示します
668         loadingEffect();
669
670         // 全チェックを取得します
671         var allCheckedFlag = document.getElementById("feedblog_allsearchcheck").checked;
672
673         // セマフォを初期化
674         fetchEntriesSemaphore.init();
675         // 記事が全検索モードか否かをチェックします
676         var urls = null;
677         if (allCheckedFlag == true) {
678                 // 全記事検索なので全てのログのURL
679                 urls = logData;
680         } else {
681                 // 単独記事探索なので、選んだログのURL
682                 urls = [document.getElementById("feedblog_logbox").options[document.getElementById("feedblog_logbox").selectedIndex].value];
683         }
684         fetchEntriesSemaphore.urls = urls;
685         fetchEntriesSemaphore.count = urls.length;
686         for ( i = 0; i < urls.length; i++) {
687                 var xhr = new jQuery.ajax({
688                         url : urls[i] + '?time=' + urlSuffix,
689                         method : "GET",
690                         async : true,
691                         success : fetchEntries,
692                         error : showErrorEffect
693                 });
694                 fetchEntriesSemaphore.xhrs.push(xhr);
695         }
696 }