OSDN Git Service

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