2 * FeedBlog CoreScript
\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
10 // ブログ本体のHTMLファイルのURL
\r
19 // ログのリストが書かれたXMLのファイルパス
\r
26 * XMLファイルから読み込んだファイルのバリデートモード
\r
27 * 0 = 改行コード部分に<br/>を挿入
\r
28 * 1 = 改行コード部分に<br/>を挿入しない
\r
35 // fetchEntries 用のセマフォ
\r
36 var fetchEntriesSemaphore = new Semaphore();
\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
45 function generatePanel(entry, drawitem, renderto, closed) {
\r
47 if( typeof ($("#" + renderto).feedblog_contents_plugin) == "function") {
\r
48 $("#" + renderto).feedblog_contents_plugin({
\r
50 mainPageUrl : mainPageUrl,
\r
51 searchPageUrl : searchPageUrl
\r
56 var htmlBuffer = [];
\r
59 var feedblogContentId = "" + renderto + "_content_div";
\r
62 $("#" + drawitem).html(entry.content);
\r
64 // ヘッダパネルを生成 class= .feedblog_header
\r
65 htmlBuffer.push("<div class='feedblog_header' onclick='closePanel(\"" + feedblogContentId + "\")'><span>" + entry.title + "</span></div>" +
\r
67 // 本体記事を作成 class= .feedblog_content
\r
68 "<div class='feedblog_content' id='" + feedblogContentId + "'><span>" + document.getElementById(renderto).innerHTML + "</span></div>");
\r
71 $("#" + renderto).html(htmlBuffer.join(""));
\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
81 function generateSystemPanel(entry, drawitem, renderto, closed) {
\r
83 var htmlBuffer = [];
\r
86 var feedblogContentId = "" + renderto + "_content_div";
\r
89 $("#" + drawitem).html(entry.content);
\r
91 // ヘッダパネルを生成 class= .feedblog_header
\r
92 htmlBuffer.push("<div class='feedblog_header' onclick='closePanel(\"" + feedblogContentId + "\")'><span>" + entry.title + "</span></div>" +
\r
94 // 本体記事を作成 class= .feedblog_content
\r
95 "<div class='feedblog_content' id='" + feedblogContentId + "'><span>" + document.getElementById(renderto).innerHTML + "</span></div>");
\r
97 $("#" + renderto).html(htmlBuffer.join(""));
\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
112 validateMode = $("#feedblog_validatemode").val();
\r
116 * jQueryへのイベント登録です。すべてのDOMが利用可能になった時点で実行されます。
\r
118 $(document).ready(function() {
\r
122 // 制御に必要な各種パラメタを取得する
\r
123 var tag = getTagFromUrl();
\r
124 var urlhash = getHashFromUrl();
\r
126 // ハッシュが空か、ハッシュ形式の正規表現に一致しないようなら通常モードで実行
\r
127 if(urlhash.length == 0 && tag.length == 0) {
\r
128 fullWriteMode(latestXml);
\r
130 } else if(urlhash.length == 0) {
\r
131 // タグが指定されているのでタグ探索モード
\r
132 searchTagMode(tag);
\r
135 // ハッシュ形式の正規表現に一致したら探索モード
\r
136 searchHashMode(urlhash);
\r
142 * jQueryでのパネル開閉を制御します
\r
144 function closePanel(id) {
\r
145 $("#" + id).slideToggle();
\r
150 * @param {Object} obj entry 要素の DOM オブジェクト
\r
152 function Entry(obj) {
\r
153 this.title = $("title:first", obj).text();
\r
154 if(this.title == "")
\r
155 requiredElementError(obj, "title");
\r
156 this.title = validateText(this.title);
\r
157 this.content = $("content:first", obj).text();
\r
158 this.content = validateText(this.content);
\r
159 this.id = $("id:first", obj).text();
\r
161 requiredElementError(obj, "id");
\r
162 this.date = $("updated:first", obj).text();
\r
163 if(this.date == "")
\r
164 requiredElementError(obj, "updated");
\r
165 this.date = validateData(this.date);
\r
166 this.category = $("category", obj);
\r
171 * @param {Object} obj entry 要素の DOM オブジェクト
\r
173 function SystemEntry(obj) {
\r
174 this.title = $("title:first", obj).text();
\r
175 this.title = validateText(this.title);
\r
176 this.content = $("content:first", obj).text();
\r
177 this.content = validateText(this.content);
\r
178 this.id = $("id:first", obj).text();
\r
179 this.date = $("updated:first", obj).text();
\r
180 this.date = validateData(this.date);
\r
181 this.category = $("category", obj);
\r
185 * 呼び出すとDIV:id名:feedblog_writearea上のHTMLを削除し、ロードエフェクトを表示します
\r
187 function loadingEffect() {
\r
188 $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem"> <\/div><\/div>');
\r
191 var systemEntry = new SystemEntry();
\r
192 systemEntry.title = "Now Loading .....";
\r
193 systemEntry.content = '<br/>長時間画面が切り替わらない場合はページをリロードしてください。<br/><br/>';
\r
194 generateSystemPanel(systemEntry, "feedblog_drawitem", "feedblog_drawpanel", false);
\r
198 * 記事データのエラー時の処理を行います
\r
200 function showError() {
\r
201 $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem"> <\/div><\/div>');
\r
204 var systemEntry = new SystemEntry();
\r
205 systemEntry.title = "エラー";
\r
206 systemEntry.content = '<br/>記事ファイルのロードに失敗しました!<br/><br/>';
\r
207 generateSystemPanel(systemEntry, "feedblog_drawitem", "feedblog_drawpanel", false);
\r
209 alert("記事ファイルが読み込めません!");
\r
213 * 記事データのエラー時の処理を行います
\r
215 function notFoundError() {
\r
216 $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem"> <\/div><\/div>');
\r
219 var systemEntry = new SystemEntry();
\r
220 systemEntry.title = "検索失敗";
\r
221 systemEntry.content = '<br/>検索条件に一致する記事は見つかりませんでした。<br/><br/>';
\r
222 generateSystemPanel(systemEntry, "feedblog_drawitem", "feedblog_drawpanel", false);
\r
226 * 記事データのエラー時の処理を行います
\r
228 function requiredElementError(parent, name) {
\r
229 alert(parent.ownerDocument.URL + ": 必須な要素 " + name + " が存在しないか空な " + parent.tagName + " 要素が存在します");
\r
232 function xmlAttrContentEscape(str) {
\r
233 return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/^[ ]+/mg, " ").replace(/^[\t]+/mg, "");
\r
237 * 日付のHTML表示用バリデーション処理を行います
\r
238 * @param {String} data RFC3339形式のdate-time文字列
\r
240 function validateData(data) {
\r
241 var regT = new RegExp("T", "gm");
\r
242 data = data.replace(regT, " ");
\r
244 // 秒数の小数点以下の部分はカットする
\r
245 data = data.substring(0, 19);
\r
251 * 記事本文のバリデーション処理を行います
\r
252 * @param {String} contents 記事の本文が格納されている文字列
\r
254 function validateText(contents) {
\r
256 if(validateMode == 0) {
\r
257 contents = contents.replace(/[\n\r]|\r\n/g, "<br />");
\r
264 * URLからタグを取得するための関数
\r
266 function getTagFromUrl() {
\r
267 // GETパラメタよりタグを取得する
\r
269 if(location.search.length > 1) {
\r
270 var queries = location.search.substring(1).split('&');
\r
271 for(var i = 0; i < queries.length; i++) {
\r
272 if(("" + queries[i].split('=')[0]) == "tag") {
\r
273 tag = "" + queries[i].split('=')[1];
\r
282 * URLからハッシュを取得するための関数
\r
284 function getHashFromUrl() {
\r
285 return "" + location.hash.substring(1);
\r
290 * @param {String} a 比較対象(1)
\r
291 * @param {String} b 比較対象(2)
\r
293 function compareLengthDecrease(a, b) {
\r
296 return a > b ? -1 : a < b ? 1 : 0;
\r
302 function Semaphore() {
\r
312 Semaphore.prototype.init = function() {
\r
313 while(this.xhrs.length > 0) {
\r
314 this.xhrs.shift().abort();
\r
316 this.id = Math.random();
\r
321 * ログファイル選択用のコンボボックスをid名:feedblog_logselecterに生成します
\r
322 * class - .feedblog_logform, .feedblog_logselecter
\r
324 function logXMLLoader() {
\r
327 url : logXmlUrl + '?time=' + (+new Date()),
\r
330 success : function(xmlData) {
\r
331 var separateTag = xmlData.getElementsByTagName("file");
\r
333 // 読み込んだ要素をStoreに格納して表示
\r
334 var boxBuffer = [];
\r
335 boxBuffer.push("<form class='feedblog_logselecter' name='feedblog_logform'><select class='feedblog_logselecter' id='feedblog_logbox' onchange='fullWriteMode(this.options[this.selectedIndex].value)'>");
\r
336 for(var i = 0; i < separateTag.length; i++) {
\r
337 boxBuffer.push("<option value='" + separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue + "'>" + separateTag[i].getElementsByTagName("display")[0].firstChild.nodeValue + "</option>");
\r
339 boxBuffer.push("</select></form>");
\r
342 $("#feedblog_logselecter").html(boxBuffer.join(""));
\r
348 * 記事のデータが記述されたXMLデータを読み込むロジックを生成します
\r
349 * @param {String} fileName 読み込み記事のデータが記述されているXMLファイルのパス
\r
351 function fullWriteMode(fileName) {
\r
355 var url = fileName;
\r
358 var loader = new jQuery.ajax({
\r
359 url : url + '?time=' + (+new Date()),
\r
361 success : function(xmlData) {
\r
362 var separateTag = xmlData.getElementsByTagName("entry");
\r
363 var stringBuffer = [];
\r
364 // メモリ上での保持変数を初期化します
\r
365 loadedEntries = [];
\r
367 // メモリ上の変数に全ての記事要素を格納します
\r
368 for(var i = 0; i < separateTag.length; i++) {
\r
369 loadedEntries.push(new Entry(separateTag[i]));
\r
373 showEntriesRange(showLength, 0);
\r
380 * 渡された文字列と一致するfeed1.0:updated要素を持った記事を検索し、表示します
\r
381 * @param {String} urlhash feed1.0:updated要素と一致する文字列
\r
383 function searchHashMode(urlhash) {
\r
388 var loader = new jQuery.ajax({
\r
389 url : logXmlUrl + '?time=' + (+new Date()),
\r
392 success : function(xmlData) {
\r
393 // ファイルパスの要素のみを抽出する
\r
394 var separateTag = xmlData.getElementsByTagName("file");
\r
395 var urls = new Array(separateTag.length);
\r
397 // すべてのファイルパスを配列に格納する
\r
398 for(var i = 0; i < separateTag.length; i++) {
\r
400 urls[i] = separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue;
\r
404 fetchEntriesSemaphore.init();
\r
405 fetchEntriesSemaphore.urls = urls;
\r
406 fetchEntriesSemaphore.count = urls.length;
\r
408 // ファイルパス配列に格納されているすべての記事に対し、探索を開始する
\r
409 for(var i = 0; i < separateTag.length; i++) {
\r
410 // ファイルパス配列の要素からリクエストを生成し、対象データをロードする
\r
411 var xhr = new jQuery.ajax({
\r
414 success : fetchHashEntries
\r
416 fetchEntriesSemaphore.xhrs.push(xhr);
\r
423 * URLハッシュ検索用のjQueryコールバック関数
\r
425 function fetchHashEntries(xmlData) {
\r
427 var urlhash = getHashFromUrl();
\r
430 var entries = xmlData.getElementsByTagName("entry");
\r
432 for(var i = 0; i < entries.length; i++) {
\r
433 // entryタグ内部のidノードの値のみ抽出し、入力されたhashと比較を行う
\r
434 var entry = new Entry(entries[i]);
\r
437 if(urlhash == entry.id) {
\r
438 // 一致した場合は該当記事を表示する
\r
439 $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem"> <\/div><\/div>');
\r
440 generatePanel(entry, "feedblog_drawitem", "feedblog_drawpanel", false);
\r
445 // セマフォのカウンタを減少させます (Ajaxとの同期のため)
\r
446 fetchEntriesSemaphore.count--;
\r
448 // 最後のファイルまで探索しても記事が見つからなかった場合はエラーを表示します。
\r
449 if(fetchEntriesSemaphore.count == 0) {
\r
450 var entries = fetchEntriesSemaphore.buf;
\r
452 if(entries.length == 0) {
\r
461 * 渡された文字列と一致するfeed1.0:category:termタグ要素を持った記事を検索し、表示します
\r
462 * @param {String} urlhash feed1.0:category:term要素と一致する文字列
\r
464 function searchTagMode(tag) {
\r
469 var loader = new jQuery.ajax({
\r
470 url : logXmlUrl + '?time=' + (+new Date()),
\r
473 success : function(xmlData) {
\r
474 // ファイルパスの要素のみを抽出する
\r
475 var separateTag = xmlData.getElementsByTagName("file");
\r
476 var urls = new Array(separateTag.length);
\r
478 // すべてのファイルパスを配列に格納する
\r
479 for(var i = 0; i < separateTag.length; i++) {
\r
481 urls[i] = separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue;
\r
485 fetchEntriesSemaphore.init();
\r
486 fetchEntriesSemaphore.urls = urls;
\r
487 fetchEntriesSemaphore.count = urls.length;
\r
489 // ファイルパス配列に格納されているすべての記事に対し、探索を開始する
\r
490 for(var i = 0; i < separateTag.length; i++) {
\r
491 // ファイルパス配列の要素からリクエストを生成し、対象データをロードする
\r
492 var xhr = new jQuery.ajax({
\r
495 success : fetchTagEntries
\r
497 fetchEntriesSemaphore.xhrs.push(xhr);
\r
504 * タグ検索用のjQueryコールバック関数
\r
506 function fetchTagEntries(xmlData) {
\r
508 var tag = getTagFromUrl();
\r
511 var entries = xmlData.getElementsByTagName("entry");
\r
513 for(var j = 0; j < entries.length; j++) {
\r
514 var entry = new Entry(entries[j]);
\r
516 for(var k = 0; k < entry.category.length; k++) {
\r
518 if(tag == entry.category.eq(k).attr("term")) {
\r
520 fetchEntriesSemaphore.buf.push(entry);
\r
525 // セマフォのカウンタを減少させます (Ajaxとの同期のため)
\r
526 fetchEntriesSemaphore.count--;
\r
528 if(fetchEntriesSemaphore.count == 0) {
\r
529 var entries = fetchEntriesSemaphore.buf;
\r
531 if(entries.length == 0) {
\r
537 entries = entries.sort(function(a, b) {
\r
540 return a > b ? -1 : a < b ? 1 : 0
\r
543 loadedEntries = entries;
\r
546 showEntriesRange(showLength, 0);
\r
552 * class - div.feedblog_pager, ul.feedblog_pager, li.feedblog_pager などなど
\r
553 * @param {int} showLength 一回の画面に表示する記事数
\r
554 * @param {int} startIndex 表示を開始する記事のインデックス
\r
556 function showEntriesRange(showLength, startIndex) {
\r
558 var entries = loadedEntries;
\r
560 // 表示インデックスが範囲外の場合はエラーパネルを表示して終了
\r
561 if(startIndex < 0 || (entries.length <= startIndex && entries.length != 0)) {
\r
566 var stringBuffer = [];
\r
569 var loopLimit = (showLength + startIndex > entries.length) ? entries.length : showLength + startIndex;
\r
570 var indexShowEntries = loopLimit + 1;
\r
573 var menuInfoBuffer = [];
\r
574 menuInfoBuffer.push("<div class='feedblog_pager_shownumber'>");
\r
575 var viewStartIndex = 0;
\r
576 entries.length == 0 ? viewStartIndex = 0 : viewStartIndex = startIndex + 1
\r
577 menuInfoBuffer.push(viewStartIndex + "件~" + loopLimit + "件(全" + entries.length + "件)目の記事を表示中<br/>");
\r
578 menuInfoBuffer.push("</div>");
\r
580 // ページ移動メニュー表示用バッファです
\r
581 var menuMoveBuffer = [];
\r
582 menuMoveBuffer.push("<ul class='feedblog_pager'>");
\r
585 menuMoveBuffer.push("<li class='feedblog_pager_blank'></li>");
\r
588 if(startIndex - showLength >= 0) {
\r
589 menuMoveBuffer.push("\<li class='feedblog_pager_goback'><span class='feedblog_pager_goback' onclick='showEntriesRange(" + showLength + ", " + (startIndex - showLength) + "); return false;'>\< 前の" + showLength + "件を表示</span\></li>");
\r
591 menuMoveBuffer.push("\<li class='feedblog_pager_goback'>\< 前の" + showLength + "件を表示</a\></li>");
\r
595 menuMoveBuffer.push("<li class='feedblog_pager_center'>[ ");
\r
596 var menuNumbers = Math.ceil(entries.length / showLength);
\r
597 for( i = 0; i < menuNumbers; i++) {
\r
598 if(startIndex / showLength == i) {
\r
599 menuMoveBuffer.push(i + " ");
\r
601 menuMoveBuffer.push("<span class='feedblog_pager_center' onclick='showEntriesRange(" + showLength + ", " + (i * showLength) + "); return false;'>");
\r
602 menuMoveBuffer.push(i);
\r
603 menuMoveBuffer.push("</span> ");
\r
606 menuMoveBuffer.push("]</li>");
\r
609 if(entries.length > startIndex + showLength) {
\r
610 menuMoveBuffer.push("\<li class='feedblog_pager_gonext'><span class='feedblog_pager_gonext' onclick='showEntriesRange(" + showLength + ", " + (startIndex + showLength) + "); return false;'>\次の" + showLength + "件を表示 \></span\></li>");
\r
612 menuMoveBuffer.push("\<li class='feedblog_pager_gonext'>次の" + showLength + "件を表示 \></li>");
\r
616 menuMoveBuffer.push("<li class='feedblog_pager_blank'></li>");
\r
618 menuMoveBuffer.push("</ul>");
\r
620 // メニューを結合してパネル(前方)に組み込みます
\r
621 stringBuffer.push("<div class='feedblog_pager_wrapper'>");
\r
622 stringBuffer.push(menuInfoBuffer.join(""));
\r
623 stringBuffer.push(menuMoveBuffer.join(""));
\r
624 stringBuffer.push("</div>");
\r
626 // 記事描画部分のパネルを生成します
\r
627 for(var i = startIndex; i < loopLimit; i++) {
\r
628 stringBuffer.push('<div class="feedblog_drawpanel" id="feedblog_drawpanel');
\r
629 stringBuffer.push(i);
\r
630 stringBuffer.push('"><div class="feedblog_drawitem" id="feedblog_drawitem');
\r
631 stringBuffer.push(i);
\r
632 stringBuffer.push('"><\/div><\/div>')
\r
635 // メニューを結合してパネル(後方)に組み込みます
\r
636 stringBuffer.push("<div class='feedblog_pager_wrapper'>");
\r
637 stringBuffer.push(menuMoveBuffer.join(""));
\r
638 stringBuffer.push(menuInfoBuffer.join(""));
\r
639 stringBuffer.push("</div>");
\r
641 $("#feedblog_writearea").html(stringBuffer.join(""));
\r
643 for(var i = startIndex; i < loopLimit; i++) {
\r
644 // 各要素をオブジェクトに格納します
\r
645 var entry = entries[i];
\r
647 // すべてのパネルをオープン状態で生成します
\r
648 generatePanel(entry, "feedblog_drawitem" + i, "feedblog_drawpanel" + i, false);
\r