OSDN Git Service

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