OSDN Git Service

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