OSDN Git Service

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