• R/O
  • HTTP
  • SSH
  • HTTPS

feedblog: Commit

JavaScriptのみで記述されたブログツール・ソフトウェアです。


Commit MetaInfo

Revision13fa988320f30fdae2125440a7f53de14fff9cc1 (tree)
Time2014-03-15 14:04:08
AuthorKureha Hisame <kureha@gmai...>
CommiterKureha Hisame

Log Message

- update tag execution and optimize.

Change Summary

Incremental Difference

--- a/js/lunardial/feedblog.js
+++ b/js/lunardial/feedblog.js
@@ -1,815 +1,879 @@
1-/**
2- * FeedBlog CoreScript
3- *
4- * @copyright 2013 FeedBlog Project (http://sourceforge.jp/projects/feedblog/)
5- * @author Kureha Hisame (http://lunardial.sakura.ne.jp/) & Yui Naruse (http://airemix.com/)
6- * @since 2009/02/27
7- * @version 4.2.0.0
8- */
9-
10-// ブログ本体のHTMLファイルのURL
11-var mainPageUrl;
12-
13-// 検索用ページURL
14-var searchPageUrl;
15-
16-// 最新の記事を示すパスへの文字列
17-var latestXml;
18-
19-// ログのリストが書かれたXMLのファイルパス
20-var logXmlUrl;
21-
22-// 一画面あたりの表示記事数
23-var showLength;
24-
25-/**
26- * XMLファイルから読み込んだファイルのバリデートモード
27- * 0 = 改行コード部分に<br/>を挿入
28- * 1 = 改行コード部分に<br/>を挿入しない
29- */
30-var validateMode;
31-
32-// 検索結果をメモリ上に保持する変数
33-var loadedEntries;
34-
35-// fetchEntries 用のセマフォ
36-var fetchEntriesSemaphore = new Semaphore();
37-
38-// URL末尾用文字列(スクリプトを開いた瞬間のミリ秒を記録)
39-var urlSuffix;
40-
41-/**
42- * 記事を実際に生成します。この部分を編集することでデザインを変更可能です。
43- * @param {Entry} entry 記事の情報が代入されたEntryオブジェクト
44- * @param {String} drawitem 「本文」を描画すべきパネルのDIV要素のid
45- * @param {String} renderto 「タイトル・更新日時・本文」の1日分の記事データを最終的に描画すべきパネルのDIV要素のid
46- * @param {String} closed (Ext jsパネルオプション)記事をクローズ状態で生成するか否か
47- */
48-function generatePanel(entry, drawitem, renderto, closed) {
49- // プラグインを実行
50- if ( typeof ($("#" + renderto).feedblog_contents_plugin) == "function") {
51- $("#" + renderto).feedblog_contents_plugin({
52- entry : entry
53- });
54- }
55-
56- // HTML用の配列を用意する
57- var htmlBuffer = [];
58-
59- // 内部的に描画先IDを生成
60- var feedblogContentId = "" + renderto + "_content_div";
61-
62- // 各要素をオブジェクトに描画
63- $("#" + drawitem).html(entry.content);
64-
65- // ヘッダパネルを生成 class= .feedblog_header
66- htmlBuffer.push("<div class='feedblog_header' onclick='closePanel(\"" + feedblogContentId + "\")'><span>" + entry.title + "</span></div>" +
67-
68- // 本体記事を作成 class= .feedblog_content
69- "<div class='feedblog_content' id='" + feedblogContentId + "'><span>" + document.getElementById(renderto).innerHTML + "</span></div>");
70-
71- // 最終描画実施
72- $("#" + renderto).html(htmlBuffer.join(""));
73-}
74-
75-/**
76- * システム表示画面を実際に生成します。この部分を編集することでデザインを変更可能です。
77- * @param {Entry} entry 記事の情報が代入されたEntryオブジェクト
78- * @param {String} drawitem パネルの本文を格納したDIV要素のid
79- * @param {String} renderto 「タイトル・更新日時・本文」の1日分の記事データを焼き付けるDIV要素のid
80- * @param {String} closed (Ext jsパネルオプション)記事をクローズ状態で生成するか否か
81- */
82-function generateSystemPanel(entry, drawitem, renderto, closed) {
83- // HTMLを生成する
84- var htmlBuffer = [];
85-
86- // 描画先IDを生成
87- var feedblogContentId = "" + renderto + "_content_div";
88-
89- // 各要素をオブジェクトに描画
90- $("#" + drawitem).html(entry.content);
91-
92- // ヘッダパネルを生成 class= .feedblog_header
93- htmlBuffer.push("<div class='feedblog_header' onclick='closePanel(\"" + feedblogContentId + "\")'><span>" + entry.title + "</span></div>" +
94-
95- // 本体記事を作成 class= .feedblog_content
96- "<div class='feedblog_content' id='" + feedblogContentId + "'><span>" + document.getElementById(renderto).innerHTML + "</span></div>");
97-
98- $("#" + renderto).html(htmlBuffer.join(""));
99-}
100-
101-/**
102- * 全ての定数を取得・セットします
103- */
104-function initialize() {
105- // 初期値をhiddenパラメータより読み込みます
106- mainPageUrl = $("#feedblog_mainpageurl").val();
107- searchPageUrl = $("#feedblog_searchpageurl").val();
108- latestXml = $("#feedblog_latestxml").val();
109- logXmlUrl = $("#feedblog_loglistxmlurl").val();
110- showLength = parseInt($("#feedblog_showlength").val());
111- if (isNaN(showLength)) {
112- showLength = 1;
113- }
114- validateMode = $("#feedblog_validatemode").val();
115-
116- // 初期値を設定します
117- urlSuffix = +new Date();
118-
119- // 必要な環境を確認します
120- var errorBuf = [];
121- // 変数確認
122- if (mainPageUrl === undefined) {
123- errorBuf.push("設定値「feedblog_mainpageurl」が欠落しています。");
124- }
125- if (searchPageUrl === undefined) {
126- errorBuf.push("設定値「feedblog_searchpageurl」が欠落しています。");
127- }
128- if (latestXml === undefined) {
129- errorBuf.push("設定値「feedblog_latestxml」が欠落しています。");
130- }
131- if (logXmlUrl === undefined) {
132- errorBuf.push("設定値「feedblog_loglistxmlurl」が欠落しています。");
133- }
134- if (showLength === undefined) {
135- errorBuf.push("設定値「feedblog_showlength」が欠落しています。");
136- }
137- if (validateMode === undefined) {
138- errorBuf.push("設定値「feedblog_validatemode」が欠落しています。");
139- }
140- // SHA-1関数確認
141- try {
142- if ( typeof (CryptoJS.SHA1) != "function") {
143- errorBuf.push("crypt-jsモジュール(hmac-sha1.js)が読み込まれていません。");
144- }
145- } catch (ex) {
146- errorBuf.push("crypt-jsモジュール(hmac-sha1.js)が読み込まれていません。");
147- }
148-
149- // 描画エリアチェック
150- if ($("#feedblog_writearea").length == 0) {
151- errorBuf.push("描画エリア「feedblog_writearea」が存在しません。");
152- }
153- if ($("#feedblog_logselecter").length == 0) {
154- errorBuf.push("描画エリア「feedblog_logselecter」が存在しません。");
155- }
156-
157- // エラーがある場合は以降の処理を継続しない
158- if (errorBuf.length > 0) {
159- alert("初期設定値に誤りがあります。\n詳細:\n" + errorBuf.join("\n"));
160- return false;
161- }
162-
163- return true;
164-}
165-
166-/**
167- * jQueryへのイベント登録です。すべてのDOMが利用可能になった時点で実行されます。
168- */
169-$(document).ready(function() {
170- // 初期処理を実施
171- if (!initialize()) {
172- return false;
173- }
174-
175- // 制御に必要な各種パラメタを取得する
176- var tag = getParamFromUrl("tag");
177- var id = getParamFromUrl("id");
178- var urlhash = getHashFromUrl();
179-
180- // ハッシュが空か、ハッシュ形式の正規表現に一致しないようなら通常モードで実行
181- if (urlhash.length == 0 && tag.length == 0 && id.length == 0) {
182- fullWriteMode(latestXml);
183- logXMLLoader();
184- } else if (urlhash.length == 0 && id.length == 0) {
185- // タグが指定されているのでタグ探索モード
186- searchTagMode(tag);
187- logXMLLoader();
188- } else if (urlhash.length == 0) {
189- // IDが指定されているのでID探索モード
190- searchIdMode(id);
191- logXMLLoader();
192- } else {
193- // ハッシュ形式の正規表現に一致したら探索モード
194- searchHashMode(urlhash);
195- logXMLLoader();
196- }
197-});
198-
199-/**
200- * jQueryでのパネル開閉を制御します
201- */
202-function closePanel(id) {
203- $("#" + id).slideToggle();
204-}
205-
206-/**
207- * 記事クラス
208- * @param {Object} obj entry 要素の DOM オブジェクト
209- */
210-function Entry(obj) {
211- this.title = $("title:first", obj).text();
212- if (this.title == "")
213- requiredElementError(obj, "title");
214- this.title = validateText(this.title);
215- this.content = $("content:first", obj).text();
216- this.content = validateText(this.content);
217- this.id = $("id:first", obj).text();
218- if (this.id == "")
219- requiredElementError(obj, "id");
220- this.date = $("updated:first", obj).text();
221- if (this.date == "")
222- requiredElementError(obj, "updated");
223- this.date = validateData(this.date);
224- this.category = $("category", obj);
225-}
226-
227-/**
228- * システム用記事クラス
229- * @param {Object} obj entry 要素の DOM オブジェクト
230- */
231-function SystemEntry(obj) {
232- this.title = $("title:first", obj).text();
233- this.title = validateText(this.title);
234- this.content = $("content:first", obj).text();
235- this.content = validateText(this.content);
236- this.id = $("id:first", obj).text();
237- this.date = $("updated:first", obj).text();
238- this.date = validateData(this.date);
239- this.category = $("category", obj);
240-}
241-
242-/**
243- * 呼び出すとDIV:id名:feedblog_writearea上のHTMLを削除し、ロードエフェクトを表示します
244- */
245-function loadingEffect() {
246- $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
247-
248- // ロード表示用のパネルを生成
249- var systemEntry = new SystemEntry();
250- systemEntry.title = "Now Loading .....";
251- systemEntry.content = '<br/>長時間画面が切り替わらない場合はページをリロードしてください。<br/><br/>';
252- generateSystemPanel(systemEntry, "feedblog_drawitem", "feedblog_drawpanel", false);
253-}
254-
255-/**
256- * 記事データのエラー時の処理を行います
257- */
258-function showError() {
259- $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
260-
261- // エラー内容をパネルに描画
262- var systemEntry = new SystemEntry();
263- systemEntry.title = "エラー";
264- var errorContent = [];
265- errorContent.push('<br/>記事ファイル(XML)の取得に失敗しました。以下のような原因が考えられます。<br/><br/>');
266- errorContent.push('・設定値「feedblog_latestxml」に正しいパスが設定されていない。<br/>');
267- errorContent.push('・設定値「feedblog_loglistxmlurl」に正しいパスが設定されていない。<br/>');
268- errorContent.push('・ローカル環境で起動している(必ずサーバにアップロードして実行してください)。<br/>');
269- errorContent.push('<br/>');
270- systemEntry.content = errorContent.join("\n");
271- generateSystemPanel(systemEntry, "feedblog_drawitem", "feedblog_drawpanel", false);
272-
273- // 結果表示エリアをリセット
274- $("#feedblog_resultwritearea").html("<div class='feedblog_result_status'></div>");
275-}
276-
277-/**
278- * 記事データのエラー時の処理を行います
279- */
280-function notFoundError() {
281- $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
282-
283- // エラー内容をパネルに描画
284- var systemEntry = new SystemEntry();
285- systemEntry.title = "検索失敗";
286- systemEntry.content = '<br/>検索条件に一致する記事は見つかりませんでした。<br/><br/>';
287- generateSystemPanel(systemEntry, "feedblog_drawitem", "feedblog_drawpanel", false);
288-}
289-
290-/**
291- * 記事データのエラー時の処理を行います
292- */
293-function requiredElementError(parent, name) {
294- alert(parent.ownerDocument.URL + ": 必須な要素 " + name + " が存在しないか空な " + parent.tagName + " 要素が存在します");
295-}
296-
297-function xmlAttrContentEscape(str) {
298- return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/^[ ]+/mg, "&nbsp;").replace(/^[\t]+/mg, "");
299-}
300-
301-/**
302- * 日付のHTML表示用バリデーション処理を行います
303- * @param {String} data RFC3339形式のdate-time文字列
304- */
305-function validateData(data) {
306- var regT = new RegExp("T", "gm");
307- data = data.replace(regT, " ");
308-
309- // 秒数の小数点以下の部分はカットする
310- data = data.substring(0, 19);
311-
312- return data;
313-}
314-
315-/**
316- * 記事本文のバリデーション処理を行います
317- * @param {String} contents 記事の本文が格納されている文字列
318- */
319-function validateText(contents) {
320- // <br/>タグを挿入する
321- if (validateMode == 0) {
322- contents = contents.replace(/[\n\r]|\r\n/g, "<br />");
323- }
324-
325- return contents;
326-}
327-
328-/**
329- * URLからパラメタを取得するための関数
330- */
331-function getParamFromUrl(paramName) {
332- // GETパラメタよりタグを取得する
333- var tag = "";
334- if (location.search.length > 1) {
335- var queries = location.search.substring(1).split('&');
336- for (var i = 0; i < queries.length; i++) {
337- if (("" + queries[i].split('=')[0]) == paramName) {
338- tag = "" + queries[i].split('=')[1];
339- }
340- }
341- }
342-
343- return tag;
344-}
345-
346-/**
347- * URLからハッシュを取得するための関数
348- */
349-function getHashFromUrl() {
350- return "" + location.hash.substring(1);
351-}
352-
353-/**
354- * 長い順に並べるための比較関数です
355- * @param {String} a 比較対象(1)
356- * @param {String} b 比較対象(2)
357- */
358-function compareLengthDecrease(a, b) {
359- a = a.length;
360- b = b.length;
361- return a > b ? -1 : a < b ? 1 : 0;
362-}
363-
364-/**
365- * セマフォ制御用のオブジェクトです
366- */
367-function Semaphore() {
368- this.id = null;
369- this.count = 0;
370- this.buf = [];
371- this.xhrs = [];
372-}
373-
374-/**
375- * セマフォ初期化用の関数です
376- */
377-Semaphore.prototype.init = function() {
378- while (this.xhrs.length > 0) {
379- this.xhrs.shift().abort();
380- }
381- this.id = Math.random();
382- this.count = 0;
383- this.buf = [];
384-};
385-
386-/**
387- * ログファイル選択用のコンボボックスをid名:feedblog_logselecterに生成します
388- * class - .feedblog_logform, .feedblog_logselecter
389- */
390-function logXMLLoader() {
391- // ログ用のXMLを読み込みます
392- jQuery.ajax({
393- url : logXmlUrl + '?time=' + urlSuffix,
394- method : "GET",
395- error : showError,
396- success : function(xmlData) {
397- var separateTag = xmlData.getElementsByTagName("file");
398-
399- // 読み込んだ要素をStoreに格納して表示
400- var boxBuffer = [];
401- boxBuffer.push("<form class='feedblog_logselecter' name='feedblog_logform'><select class='feedblog_logselecter' id='feedblog_logbox' onchange='fullWriteMode(this.options[this.selectedIndex].value)'>");
402- for (var i = 0; i < separateTag.length; i++) {
403- boxBuffer.push("<option value='" + separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue + "'>" + separateTag[i].getElementsByTagName("display")[0].firstChild.nodeValue + "</option>");
404- }
405- boxBuffer.push("</select></form>");
406-
407- // コンボボックス要素を生成
408- $("#feedblog_logselecter").html(boxBuffer.join(""));
409- }
410- });
411-}
412-
413-/**
414- * 記事のデータが記述されたXMLデータを読み込むロジックを生成します
415- * @param {String} fileName 読み込み記事のデータが記述されているXMLファイルのパス
416- */
417-function fullWriteMode(fileName) {
418- // ロードエフェクトに切り替え
419- loadingEffect();
420-
421- var url = fileName;
422-
423- // 記事をロードします
424- var loader = new jQuery.ajax({
425- url : url + '?time=' + urlSuffix,
426- method : "GET",
427- success : function(xmlData) {
428- var separateTag = xmlData.getElementsByTagName("entry");
429- var stringBuffer = [];
430- // メモリ上での保持変数を初期化します
431- loadedEntries = [];
432-
433- // メモリ上の変数に全ての記事要素を格納します
434- for (var i = 0; i < separateTag.length; i++) {
435- loadedEntries.push(new Entry(separateTag[i]));
436- }
437-
438- // 表示ロジック呼び出し
439- showEntriesRange(showLength, 0);
440- },
441- error : showError
442- });
443-}
444-
445-/**
446- * 渡された文字列と一致するfeed1.0:updated要素を持った記事を検索し、表示します
447- * @param {String} urlhash feed1.0:updated要素と一致する文字列
448- */
449-function searchHashMode(urlhash) {
450- // ロードエフェクト表示
451- loadingEffect();
452-
453- // ログXMLファイルを読み込む
454- var loader = new jQuery.ajax({
455- url : logXmlUrl + '?time=' + urlSuffix,
456- method : "GET",
457- error : showError,
458- success : function(xmlData) {
459- // ファイルパスの要素のみを抽出する
460- var separateTag = xmlData.getElementsByTagName("file");
461- var urls = new Array(separateTag.length);
462-
463- // すべてのファイルパスを配列に格納する
464- for (var i = 0; i < separateTag.length; i++) {
465- // "path"ノードの値を格納
466- urls[i] = separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue;
467- }
468-
469- // セマフォを初期化
470- fetchEntriesSemaphore.init();
471- fetchEntriesSemaphore.urls = urls;
472- fetchEntriesSemaphore.count = urls.length;
473-
474- // ファイルパス配列に格納されているすべての記事に対し、探索を開始する
475- for (var i = 0; i < separateTag.length; i++) {
476- // ファイルパス配列の要素からリクエストを生成し、対象データをロードする
477- var xhr = new jQuery.ajax({
478- url : urls[i],
479- method : "GET",
480- success : fetchHashEntries
481- });
482- fetchEntriesSemaphore.xhrs.push(xhr);
483- }
484- }
485- });
486-}
487-
488-/**
489- * URLハッシュ検索用のjQueryコールバック関数
490- */
491-function fetchHashEntries(xmlData) {
492- // 既に検索結果が算出されていた場合は、何もしない
493- if (fetchEntriesSemaphore.buf > 0) {
494- return true;
495- }
496-
497- // ハッシュを取得
498- var urlhash = getHashFromUrl();
499-
500- // entry要素のみを切り出す
501- var entries = xmlData.getElementsByTagName("entry");
502-
503- for (var i = 0; i < entries.length; i++) {
504- // entryタグ内部のidノードの値のみ抽出し、入力されたhashと比較を行う
505- var entry = new Entry(entries[i]);
506-
507- // idの値と比較を行う
508- if (urlhash == entry.id) {
509- // 一致した場合は該当記事を表示する
510- $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
511- generatePanel(entry, "feedblog_drawitem", "feedblog_drawpanel", false);
512-
513- fetchEntriesSemaphore.buf.push(entry);
514- return true;
515- }
516-
517- // セマフォのカウンタを減少させます (Ajaxとの同期のため)
518- fetchEntriesSemaphore.count--;
519-
520- // 最後のファイルまで探索しても記事が見つからなかった場合はエラーを表示します。
521- if (fetchEntriesSemaphore.count == 0) {
522- var entries = fetchEntriesSemaphore.buf;
523-
524- if (entries.length == 0) {
525- notFoundError();
526- return false;
527- }
528- }
529- }
530-}
531-
532-/**
533- * 渡された文字列と一致するfeed1.0:id(sha-1)要素を持った記事を検索し、表示します
534- * @param {String} urlhash feed1.0:id(sha-1)要素と一致する文字列
535- */
536-function searchIdMode(urlhash) {
537- // ロードエフェクト表示
538- loadingEffect();
539-
540- // ログXMLファイルを読み込む
541- var loader = new jQuery.ajax({
542- url : logXmlUrl + '?time=' + urlSuffix,
543- method : "GET",
544- error : showError,
545- success : function(xmlData) {
546- // ファイルパスの要素のみを抽出する
547- var separateTag = xmlData.getElementsByTagName("file");
548- var urls = new Array(separateTag.length);
549-
550- // すべてのファイルパスを配列に格納する
551- for (var i = 0; i < separateTag.length; i++) {
552- // "path"ノードの値を格納
553- urls[i] = separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue;
554- }
555-
556- // セマフォを初期化
557- fetchEntriesSemaphore.init();
558- fetchEntriesSemaphore.urls = urls;
559- fetchEntriesSemaphore.count = urls.length;
560-
561- // ファイルパス配列に格納されているすべての記事に対し、探索を開始する
562- for (var i = 0; i < separateTag.length; i++) {
563- // ファイルパス配列の要素からリクエストを生成し、対象データをロードする
564- var xhr = new jQuery.ajax({
565- url : urls[i],
566- method : "GET",
567- success : fetchIdEntries
568- });
569- fetchEntriesSemaphore.xhrs.push(xhr);
570- }
571- }
572- });
573-}
574-
575-/**
576- * ID検索用のjQueryコールバック関数
577- */
578-function fetchIdEntries(xmlData) {
579- // 既に検索結果が算出されていた場合は、何もしない
580- if (fetchEntriesSemaphore.buf > 0) {
581- return true;
582- }
583-
584- // IDを取得
585- var id = getParamFromUrl("id");
586-
587- // entry要素のみを切り出す
588- var entries = xmlData.getElementsByTagName("entry");
589-
590- for (var i = 0; i < entries.length; i++) {
591- // entryタグ内部のidノードの値のみ抽出し、入力されたhashと比較を行う
592- var entry = new Entry(entries[i]);
593-
594- // idの値と比較を行う
595- if (id == CryptoJS.SHA1(entry.id).toString()) {
596- // 一致した場合は該当記事を表示する
597- $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
598- generatePanel(entry, "feedblog_drawitem", "feedblog_drawpanel", false);
599-
600- fetchEntriesSemaphore.buf.push(entry);
601- return true;
602- }
603-
604- // セマフォのカウンタを減少させます (Ajaxとの同期のため)
605- fetchEntriesSemaphore.count--;
606-
607- // 最後のファイルまで探索しても記事が見つからなかった場合はエラーを表示します。
608- if (fetchEntriesSemaphore.count == 0) {
609- var entries = fetchEntriesSemaphore.buf;
610-
611- if (entries.length == 0) {
612- notFoundError();
613- return false;
614- }
615- }
616- }
617-}
618-
619-/**
620- * 渡された文字列と一致するfeed1.0:category:termタグ要素を持った記事を検索し、表示します
621- * @param {String} urlhash feed1.0:category:term要素と一致する文字列
622- */
623-function searchTagMode(tag) {
624- // ロードエフェクト表示
625- loadingEffect();
626-
627- // ログXMLファイルを読み込む
628- var loader = new jQuery.ajax({
629- url : logXmlUrl + '?time=' + urlSuffix,
630- method : "GET",
631- error : showError,
632- success : function(xmlData) {
633- // ファイルパスの要素のみを抽出する
634- var separateTag = xmlData.getElementsByTagName("file");
635- var urls = new Array(separateTag.length);
636-
637- // すべてのファイルパスを配列に格納する
638- for (var i = 0; i < separateTag.length; i++) {
639- // "path"ノードの値を格納
640- urls[i] = separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue;
641- }
642-
643- // セマフォを初期化
644- fetchEntriesSemaphore.init();
645- fetchEntriesSemaphore.urls = urls;
646- fetchEntriesSemaphore.count = urls.length;
647-
648- // ファイルパス配列に格納されているすべての記事に対し、探索を開始する
649- for (var i = 0; i < separateTag.length; i++) {
650- // ファイルパス配列の要素からリクエストを生成し、対象データをロードする
651- var xhr = new jQuery.ajax({
652- url : urls[i],
653- method : "GET",
654- success : fetchTagEntries
655- });
656- fetchEntriesSemaphore.xhrs.push(xhr);
657- }
658- }
659- });
660-}
661-
662-/**
663- * タグ検索用のjQueryコールバック関数
664- */
665-function fetchTagEntries(xmlData) {
666- // 既に記事の表示が行われている場合、何も実施しない
667- if (fetchEntriesSemaphore.buf.length > 0) {
668- return true;
669- }
670-
671- // タグを取得する
672- var tag = getParamFromUrl("tag");
673-
674- // entry要素のみを切り出す
675- var entries = xmlData.getElementsByTagName("entry");
676-
677- for (var j = 0; j < entries.length; j++) {
678- var entry = new Entry(entries[j]);
679-
680- for (var k = 0; k < entry.category.length; k++) {
681- // タグのIDが一致したら格納
682- if (tag == entry.category.eq(k).attr("term")) {
683- // entryを格納する
684- fetchEntriesSemaphore.buf.push(entry);
685- }
686- }
687- }
688-
689- // セマフォのカウンタを減少させます (Ajaxとの同期のため)
690- fetchEntriesSemaphore.count--;
691-
692- if (fetchEntriesSemaphore.count == 0) {
693- var entries = fetchEntriesSemaphore.buf;
694-
695- if (entries.length == 0) {
696- notFoundError();
697- return false;
698- }
699-
700- // entryをidでソート
701- entries = entries.sort(function(a, b) {
702- a = a.id;
703- b = b.id;
704- return a > b ? -1 : a < b ? 1 : 0;
705- });
706-
707- loadedEntries = entries;
708-
709- // 表示ロジック呼び出し
710- showEntriesRange(showLength, 0);
711- }
712-}
713-
714-/**
715- * 検索結果を分割して表示します
716- * class - div.feedblog_pager, ul.feedblog_pager, li.feedblog_pager などなど
717- * @param {int} showLength 一回の画面に表示する記事数
718- * @param {int} startIndex 表示を開始する記事のインデックス
719- */
720-function showEntriesRange(showLength, startIndex) {
721- // メモリ上から記事データをロード
722- var entries = loadedEntries;
723-
724- // 表示インデックスが範囲外の場合はエラーパネルを表示して終了
725- if (startIndex < 0 || (entries.length <= startIndex && entries.length != 0)) {
726- showError();
727- return;
728- }
729-
730- var stringBuffer = [];
731-
732- // リミッターを設定する
733- var loopLimit = (showLength + startIndex > entries.length) ? entries.length : showLength + startIndex;
734- var indexShowEntries = loopLimit + 1;
735-
736- // 情報メニュー表示用バッファです
737- var menuInfoBuffer = [];
738- menuInfoBuffer.push("<div class='feedblog_pager_shownumber'>");
739- var viewStartIndex = 0;
740- entries.length == 0 ? viewStartIndex = 0 : viewStartIndex = startIndex + 1;
741- menuInfoBuffer.push(viewStartIndex + "件~" + loopLimit + "件(全" + entries.length + "件)目の記事を表示中<br/>");
742- menuInfoBuffer.push("</div>");
743-
744- // ページ移動メニュー表示用バッファです
745- var menuMoveBuffer = [];
746- menuMoveBuffer.push("<ul class='feedblog_pager'>");
747-
748- // ブランクエリアを挟む
749- menuMoveBuffer.push("<li class='feedblog_pager_blank'></li>");
750-
751- // 左パネルの表示制御
752- if (startIndex - showLength >= 0) {
753- menuMoveBuffer.push("\<li class='feedblog_pager_goback'><span class='feedblog_pager_goback' onclick='showEntriesRange(" + showLength + ", " + (startIndex - showLength) + "); return false;'>\< 前の" + showLength + "件を表示</span\></li>");
754- } else {
755- menuMoveBuffer.push("\<li class='feedblog_pager_goback'>\< 前の" + showLength + "件を表示</a\></li>");
756- }
757-
758- // 中央のパネルの表示制御
759- menuMoveBuffer.push("<li class='feedblog_pager_center'>[ ");
760- var menuNumbers = Math.ceil(entries.length / showLength);
761- for ( i = 0; i < menuNumbers; i++) {
762- if (startIndex / showLength == i) {
763- menuMoveBuffer.push(i + " ");
764- } else {
765- menuMoveBuffer.push("<span class='feedblog_pager_center' onclick='showEntriesRange(" + showLength + ", " + (i * showLength) + "); return false;'>");
766- menuMoveBuffer.push(i);
767- menuMoveBuffer.push("</span> ");
768- }
769- }
770- menuMoveBuffer.push("]</li>");
771-
772- // 右パネルの表示制御
773- if (entries.length > startIndex + showLength) {
774- menuMoveBuffer.push("\<li class='feedblog_pager_gonext'><span class='feedblog_pager_gonext' onclick='showEntriesRange(" + showLength + ", " + (startIndex + showLength) + "); return false;'>\次の" + showLength + "件を表示 \></span\></li>");
775- } else {
776- menuMoveBuffer.push("\<li class='feedblog_pager_gonext'>次の" + showLength + "件を表示 \></li>");
777- }
778-
779- // ブランクエリアを挟む
780- menuMoveBuffer.push("<li class='feedblog_pager_blank'></li>");
781-
782- menuMoveBuffer.push("</ul>");
783-
784- // メニューを結合してパネル(前方)に組み込みます
785- stringBuffer.push("<div class='feedblog_pager_wrapper'>");
786- stringBuffer.push(menuInfoBuffer.join(""));
787- stringBuffer.push(menuMoveBuffer.join(""));
788- stringBuffer.push("</div>");
789-
790- // 記事描画部分のパネルを生成します
791- for (var i = startIndex; i < loopLimit; i++) {
792- stringBuffer.push('<div class="feedblog_drawpanel" id="feedblog_drawpanel');
793- stringBuffer.push(i);
794- stringBuffer.push('"><div class="feedblog_drawitem" id="feedblog_drawitem');
795- stringBuffer.push(i);
796- stringBuffer.push('"><\/div><\/div>');
797- }
798-
799- // メニューを結合してパネル(後方)に組み込みます
800- stringBuffer.push("<div class='feedblog_pager_wrapper'>");
801- stringBuffer.push(menuMoveBuffer.join(""));
802- stringBuffer.push(menuInfoBuffer.join(""));
803- stringBuffer.push("</div>");
804-
805- $("#feedblog_writearea").html(stringBuffer.join(""));
806-
807- for (var i = startIndex; i < loopLimit; i++) {
808- // 各要素をオブジェクトに格納します
809- var entry = entries[i];
810-
811- // すべてのパネルをオープン状態で生成します
812- generatePanel(entry, "feedblog_drawitem" + i, "feedblog_drawpanel" + i, false);
813- }
814-}
815-
1+/**
2+ * FeedBlog CoreScript
3+ *
4+ * @copyright 2013 FeedBlog Project (http://sourceforge.jp/projects/feedblog/)
5+ * @author Kureha Hisame (http://lunardial.sakura.ne.jp/) & Yui Naruse (http://airemix.com/)
6+ * @since 2009/02/27
7+ * @version 4.2.1.0
8+ */
9+
10+// ブログ本体のHTMLファイルのURL
11+var mainPageUrl;
12+
13+// 検索用ページURL
14+var searchPageUrl;
15+
16+// 最新の記事を示すパスへの文字列
17+var latestXml;
18+
19+// ログのリストが書かれたXMLのファイルパス
20+var logXmlUrl;
21+
22+// 一画面あたりの表示記事数
23+var showLength;
24+
25+/**
26+ * XMLファイルから読み込んだファイルのバリデートモード
27+ * 0 = 改行コード部分に<br/>を挿入
28+ * 1 = 改行コード部分に<br/>を挿入しない
29+ */
30+var validateMode;
31+
32+// 検索結果をメモリ上に保持する変数
33+var loadedEntries;
34+
35+// fetchEntries 用のセマフォ
36+var fetchEntriesSemaphore = new Semaphore();
37+
38+// URL末尾用文字列(スクリプトを開いた瞬間のミリ秒を記録)
39+var urlSuffix;
40+
41+/**
42+ * 記事を実際に生成します。この部分を編集することでデザインを変更可能です。
43+ * @param {Entry} entry 記事の情報が代入されたEntryオブジェクト
44+ * @param {String} drawitem 「本文」を描画すべきパネルのDIV要素のid
45+ * @param {String} renderto 「タイトル・更新日時・本文」の1日分の記事データを最終的に描画すべきパネルのDIV要素のid
46+ * @param {String} closed (Ext jsパネルオプション)記事をクローズ状態で生成するか否か
47+ */
48+function generatePanel(entry, drawitem, renderto, closed) {
49+ // プラグインを実行
50+ if ( typeof ($("#" + renderto).feedblog_contents_plugin) == "function") {
51+ $("#" + renderto).feedblog_contents_plugin({
52+ entry : entry
53+ });
54+ }
55+
56+ // HTML用の配列を用意する
57+ var htmlBuffer = [];
58+
59+ // 内部的に描画先IDを生成
60+ var feedblogContentId = "" + renderto + "_content_div";
61+
62+ // 各要素をオブジェクトに描画
63+ $("#" + drawitem).html(entry.content);
64+
65+ // ヘッダパネルを生成 class= .feedblog_header
66+ htmlBuffer.push("<div class='feedblog_header' onclick='closePanel(\"" + feedblogContentId + "\")'><span>" + entry.title + "</span></div>" +
67+
68+ // 本体記事を作成 class= .feedblog_content
69+ "<div class='feedblog_content' id='" + feedblogContentId + "'><span>" + document.getElementById(renderto).innerHTML + "</span></div>");
70+
71+ // 最終描画実施
72+ $("#" + renderto).html(htmlBuffer.join(""));
73+}
74+
75+/**
76+ * システム表示画面を実際に生成します。この部分を編集することでデザインを変更可能です。
77+ * @param {Entry} entry 記事の情報が代入されたEntryオブジェクト
78+ * @param {String} drawitem パネルの本文を格納したDIV要素のid
79+ * @param {String} renderto 「タイトル・更新日時・本文」の1日分の記事データを焼き付けるDIV要素のid
80+ * @param {String} closed (Ext jsパネルオプション)記事をクローズ状態で生成するか否か
81+ */
82+function generateSystemPanel(entry, drawitem, renderto, closed) {
83+ // HTMLを生成する
84+ var htmlBuffer = [];
85+
86+ // 描画先IDを生成
87+ var feedblogContentId = "" + renderto + "_content_div";
88+
89+ // 各要素をオブジェクトに描画
90+ $("#" + drawitem).html(entry.content);
91+
92+ // ヘッダパネルを生成 class= .feedblog_header
93+ htmlBuffer.push("<div class='feedblog_header' onclick='closePanel(\"" + feedblogContentId + "\")'><span>" + entry.title + "</span></div>" +
94+
95+ // 本体記事を作成 class= .feedblog_content
96+ "<div class='feedblog_content' id='" + feedblogContentId + "'><span>" + document.getElementById(renderto).innerHTML + "</span></div>");
97+
98+ $("#" + renderto).html(htmlBuffer.join(""));
99+}
100+
101+/**
102+ * 全ての定数を取得・セットします
103+ */
104+function initialize() {
105+ // 初期値をhiddenパラメータより読み込みます
106+ mainPageUrl = $("#feedblog_mainpageurl").val();
107+ searchPageUrl = $("#feedblog_searchpageurl").val();
108+ latestXml = $("#feedblog_latestxml").val();
109+ logXmlUrl = $("#feedblog_loglistxmlurl").val();
110+ showLength = parseInt($("#feedblog_showlength").val());
111+ if (isNaN(showLength)) {
112+ showLength = 1;
113+ }
114+ validateMode = $("#feedblog_validatemode").val();
115+
116+ // 初期値を設定します
117+ urlSuffix = +new Date();
118+
119+ // 必要な環境を確認します
120+ var errorBuf = [];
121+ // 変数確認
122+ if (mainPageUrl === undefined) {
123+ errorBuf.push("設定値「feedblog_mainpageurl」が欠落しています。");
124+ }
125+ if (searchPageUrl === undefined) {
126+ errorBuf.push("設定値「feedblog_searchpageurl」が欠落しています。");
127+ }
128+ if (latestXml === undefined) {
129+ errorBuf.push("設定値「feedblog_latestxml」が欠落しています。");
130+ }
131+ if (logXmlUrl === undefined) {
132+ errorBuf.push("設定値「feedblog_loglistxmlurl」が欠落しています。");
133+ }
134+ if (showLength === undefined) {
135+ errorBuf.push("設定値「feedblog_showlength」が欠落しています。");
136+ }
137+ if (validateMode === undefined) {
138+ errorBuf.push("設定値「feedblog_validatemode」が欠落しています。");
139+ }
140+ // SHA-1関数確認
141+ try {
142+ if ( typeof (CryptoJS.SHA1) != "function") {
143+ errorBuf.push("crypt-jsモジュール(hmac-sha1.js)が読み込まれていません。");
144+ }
145+ } catch (ex) {
146+ errorBuf.push("crypt-jsモジュール(hmac-sha1.js)が読み込まれていません。");
147+ }
148+
149+ // 描画エリアチェック
150+ if ($("#feedblog_writearea").length == 0) {
151+ errorBuf.push("描画エリア「feedblog_writearea」が存在しません。");
152+ }
153+ if ($("#feedblog_logselecter").length == 0) {
154+ errorBuf.push("描画エリア「feedblog_logselecter」が存在しません。");
155+ }
156+
157+ // エラーがある場合は以降の処理を継続しない
158+ if (errorBuf.length > 0) {
159+ alert("初期設定値に誤りがあります。\n詳細:\n" + errorBuf.join("\n"));
160+ return false;
161+ }
162+
163+ return true;
164+}
165+
166+/**
167+ * jQueryへのイベント登録です。すべてのDOMが利用可能になった時点で実行されます。
168+ */
169+$(document).ready(function() {
170+ // 初期処理を実施
171+ if (!initialize()) {
172+ return false;
173+ }
174+
175+ // 制御に必要な各種パラメタを取得する
176+ var tag = getParamFromUrl("tag");
177+ var id = getParamFromUrl("id");
178+ var urlhash = getHashFromUrl();
179+
180+ // ハッシュが空か、ハッシュ形式の正規表現に一致しないようなら通常モードで実行
181+ if (urlhash.length == 0 && tag.length == 0 && id.length == 0) {
182+ fullWriteMode(latestXml);
183+ logXMLLoader();
184+ } else if (urlhash.length == 0 && id.length == 0) {
185+ // タグが指定されているのでタグ探索モード
186+ searchTagMode(tag, true);
187+ logXMLLoader();
188+ } else if (urlhash.length == 0) {
189+ // IDが指定されているのでID探索モード
190+ searchIdMode(id);
191+ logXMLLoader();
192+ } else {
193+ // ハッシュ形式の正規表現に一致したら探索モード
194+ searchHashMode(urlhash);
195+ logXMLLoader();
196+ }
197+});
198+
199+/**
200+ * jQueryでのパネル開閉を制御します
201+ */
202+function closePanel(id) {
203+ $("#" + id).slideToggle();
204+}
205+
206+/**
207+ * 記事クラス
208+ * @param {Object} obj entry 要素の DOM オブジェクト
209+ */
210+function Entry(obj) {
211+ this.title = $("title:first", obj).text();
212+ if (this.title == "")
213+ requiredElementError(obj, "title");
214+ this.title = validateText(this.title);
215+ this.content = $("content:first", obj).text();
216+ this.content = validateText(this.content);
217+ this.id = $("id:first", obj).text();
218+ if (this.id == "")
219+ requiredElementError(obj, "id");
220+ this.date = $("updated:first", obj).text();
221+ if (this.date == "")
222+ requiredElementError(obj, "updated");
223+ this.date = validateData(this.date);
224+ this.category = $("category", obj);
225+}
226+
227+/**
228+ * システム用記事クラス
229+ * @param {Object} obj entry 要素の DOM オブジェクト
230+ */
231+function SystemEntry(obj) {
232+ this.title = $("title:first", obj).text();
233+ this.title = validateText(this.title);
234+ this.content = $("content:first", obj).text();
235+ this.content = validateText(this.content);
236+ this.id = $("id:first", obj).text();
237+ this.date = $("updated:first", obj).text();
238+ this.date = validateData(this.date);
239+ this.category = $("category", obj);
240+}
241+
242+/**
243+ * 呼び出すとDIV:id名:feedblog_writearea上のHTMLを削除し、ロードエフェクトを表示します
244+ */
245+function loadingEffect() {
246+ $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
247+
248+ // ロード表示用のパネルを生成
249+ var systemEntry = new SystemEntry();
250+ systemEntry.title = "Now Loading .....";
251+ systemEntry.content = '<br/>長時間画面が切り替わらない場合はページをリロードしてください。<br/><br/>';
252+ generateSystemPanel(systemEntry, "feedblog_drawitem", "feedblog_drawpanel", false);
253+}
254+
255+/**
256+ * 呼び出すとDIV:id名:feedblog_writearea上の先頭に、処理中の記事を表示します
257+ */
258+function executingEffect() {
259+ $("#feedblog_writearea").html('<div id="feedblog_drawpanel_info" class="feedblog_drawpanel"><div id="feedblog_drawitem_info" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
260+
261+ // ロード表示用のパネルを生成
262+ var systemEntry = new SystemEntry();
263+ systemEntry.title = "検索処理中";
264+ systemEntry.content = '<br/>該当するタグに属する記事を検索中です。<br/><br/>';
265+ generateSystemPanel(systemEntry, "feedblog_drawitem_info", "feedblog_drawpanel_info", false);
266+}
267+
268+/**
269+ * 呼び出すとDIV:id名:feedblog_drawpanel_info上に、処理完了の記事を表示します
270+ */
271+function executingEffectComplete() {
272+ $("#feedblog_drawpanel_info").html('<div id="feedblog_drawitem_info" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
273+
274+ // 検索処理完了用のパネルを生成
275+ var systemEntry = new SystemEntry();
276+ systemEntry.title = "検索完了";
277+ systemEntry.content = '<br/>検索処理が完了しました。(該当記事:' + fetchEntriesSemaphore.entryCount + '件)<br/><br/>';
278+ generateSystemPanel(systemEntry, "feedblog_drawitem_info", "feedblog_drawpanel_info", false);
279+
280+ // 状況に応じて「全件表示」パネルを生成
281+ if (fetchEntriesSemaphore.showLimit == true && fetchEntriesSemaphore.entryIndex != fetchEntriesSemaphore.entryCount) {
282+ $("#feedblog_writearea").html($("#feedblog_writearea").html() + '<div id="feedblog_drawpanel_abort" class="feedblog_drawpanel"><div id="feedblog_drawitem_abort" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
283+ systemEntry = new SystemEntry();
284+ systemEntry.title = "以降、" + (fetchEntriesSemaphore.entryCount - fetchEntriesSemaphore.entryIndex) + "件の記事が省略されています。";
285+ systemEntry.content = '<br/><a style="cursor: pointer;" onclick="javascript: var tag = getParamFromUrl(\'tag\'); searchTagMode(tag, false);">全ての記事を表示する。</a><br/><br/>';
286+ generateSystemPanel(systemEntry, "feedblog_drawitem_abort", "feedblog_drawpanel_abort", false);
287+ }
288+}
289+
290+/**
291+ * 記事データのエラー時の処理を行います
292+ */
293+function showError() {
294+ $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
295+
296+ // エラー内容をパネルに描画
297+ var systemEntry = new SystemEntry();
298+ systemEntry.title = "エラー";
299+ var errorContent = [];
300+ errorContent.push('<br/>記事ファイル(XML)の取得に失敗しました。以下のような原因が考えられます。<br/><br/>');
301+ errorContent.push('・設定値「feedblog_latestxml」に正しいパスが設定されていない。<br/>');
302+ errorContent.push('・設定値「feedblog_loglistxmlurl」に正しいパスが設定されていない。<br/>');
303+ errorContent.push('・ローカル環境で起動している(必ずサーバにアップロードして実行してください)。<br/>');
304+ errorContent.push('<br/>');
305+ systemEntry.content = errorContent.join("\n");
306+ generateSystemPanel(systemEntry, "feedblog_drawitem", "feedblog_drawpanel", false);
307+
308+ // 結果表示エリアをリセット
309+ $("#feedblog_resultwritearea").html("<div class='feedblog_result_status'></div>");
310+}
311+
312+/**
313+ * 記事データのエラー時の処理を行います
314+ */
315+function notFoundError() {
316+ $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
317+
318+ // エラー内容をパネルに描画
319+ var systemEntry = new SystemEntry();
320+ systemEntry.title = "検索失敗";
321+ systemEntry.content = '<br/>検索条件に一致する記事は見つかりませんでした。<br/><br/>';
322+ generateSystemPanel(systemEntry, "feedblog_drawitem", "feedblog_drawpanel", false);
323+}
324+
325+/**
326+ * 記事データのエラー時の処理を行います
327+ */
328+function requiredElementError(parent, name) {
329+ alert(parent.ownerDocument.URL + ": 必須な要素 " + name + " が存在しないか空な " + parent.tagName + " 要素が存在します");
330+}
331+
332+function xmlAttrContentEscape(str) {
333+ return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/^[ ]+/mg, "&nbsp;").replace(/^[\t]+/mg, "");
334+}
335+
336+/**
337+ * 日付のHTML表示用バリデーション処理を行います
338+ * @param {String} data RFC3339形式のdate-time文字列
339+ */
340+function validateData(data) {
341+ var regT = new RegExp("T", "gm");
342+ data = data.replace(regT, " ");
343+
344+ // 秒数の小数点以下の部分はカットする
345+ data = data.substring(0, 19);
346+
347+ return data;
348+}
349+
350+/**
351+ * 記事本文のバリデーション処理を行います
352+ * @param {String} contents 記事の本文が格納されている文字列
353+ */
354+function validateText(contents) {
355+ // <br/>タグを挿入する
356+ if (validateMode == 0) {
357+ contents = contents.replace(/[\n\r]|\r\n/g, "<br />");
358+ }
359+
360+ return contents;
361+}
362+
363+/**
364+ * URLからパラメタを取得するための関数
365+ */
366+function getParamFromUrl(paramName) {
367+ // GETパラメタよりタグを取得する
368+ var tag = "";
369+ if (location.search.length > 1) {
370+ var queries = location.search.substring(1).split('&');
371+ for (var i = 0; i < queries.length; i++) {
372+ if (("" + queries[i].split('=')[0]) == paramName) {
373+ tag = "" + queries[i].split('=')[1];
374+ }
375+ }
376+ }
377+
378+ return tag;
379+}
380+
381+/**
382+ * URLからハッシュを取得するための関数
383+ */
384+function getHashFromUrl() {
385+ return "" + location.hash.substring(1);
386+}
387+
388+/**
389+ * 長い順に並べるための比較関数です
390+ * @param {String} a 比較対象(1)
391+ * @param {String} b 比較対象(2)
392+ */
393+function compareLengthDecrease(a, b) {
394+ a = a.length;
395+ b = b.length;
396+ return a > b ? -1 : a < b ? 1 : 0;
397+}
398+
399+/**
400+ * セマフォ制御用のオブジェクトです
401+ */
402+function Semaphore() {
403+ this.id = null;
404+ this.count = 0;
405+ this.buf = [];
406+ this.xhrs = [];
407+}
408+
409+/**
410+ * セマフォ初期化用の関数です
411+ */
412+Semaphore.prototype.init = function() {
413+ while (this.xhrs.length > 0) {
414+ this.xhrs.shift().abort();
415+ }
416+ this.id = Math.random();
417+ this.count = 0;
418+ this.buf = [];
419+};
420+
421+/**
422+ * ログファイル選択用のコンボボックスをid名:feedblog_logselecterに生成します
423+ * class - .feedblog_logform, .feedblog_logselecter
424+ */
425+function logXMLLoader() {
426+ // ログ用のXMLを読み込みます
427+ jQuery.ajax({
428+ url : logXmlUrl + '?time=' + urlSuffix,
429+ method : "GET",
430+ error : showError,
431+ success : function(xmlData) {
432+ var separateTag = xmlData.getElementsByTagName("file");
433+
434+ // 読み込んだ要素をStoreに格納して表示
435+ var boxBuffer = [];
436+ boxBuffer.push("<form class='feedblog_logselecter' name='feedblog_logform'><select class='feedblog_logselecter' id='feedblog_logbox' onchange='fullWriteMode(this.options[this.selectedIndex].value)'>");
437+ for (var i = 0; i < separateTag.length; i++) {
438+ boxBuffer.push("<option value='" + separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue + "'>" + separateTag[i].getElementsByTagName("display")[0].firstChild.nodeValue + "</option>");
439+ }
440+ boxBuffer.push("</select></form>");
441+
442+ // コンボボックス要素を生成
443+ $("#feedblog_logselecter").html(boxBuffer.join(""));
444+ }
445+ });
446+}
447+
448+/**
449+ * 記事のデータが記述されたXMLデータを読み込むロジックを生成します
450+ * @param {String} fileName 読み込み記事のデータが記述されているXMLファイルのパス
451+ */
452+function fullWriteMode(fileName) {
453+ // ロードエフェクトに切り替え
454+ loadingEffect();
455+
456+ var url = fileName;
457+
458+ // 記事をロードします
459+ var loader = new jQuery.ajax({
460+ url : url + '?time=' + urlSuffix,
461+ method : "GET",
462+ success : function(xmlData) {
463+ var separateTag = xmlData.getElementsByTagName("entry");
464+ var stringBuffer = [];
465+ // メモリ上での保持変数を初期化します
466+ loadedEntries = [];
467+
468+ // メモリ上の変数に全ての記事要素を格納します
469+ for (var i = 0; i < separateTag.length; i++) {
470+ loadedEntries.push(new Entry(separateTag[i]));
471+ }
472+
473+ // 表示ロジック呼び出し
474+ showEntriesRange(showLength, 0);
475+ },
476+ error : showError
477+ });
478+}
479+
480+/**
481+ * 渡された文字列と一致するfeed1.0:updated要素を持った記事を検索し、表示します
482+ * @param {String} urlhash feed1.0:updated要素と一致する文字列
483+ */
484+function searchHashMode(urlhash) {
485+ // ロードエフェクト表示
486+ loadingEffect();
487+
488+ // ログXMLファイルを読み込む
489+ var loader = new jQuery.ajax({
490+ url : logXmlUrl + '?time=' + urlSuffix,
491+ method : "GET",
492+ error : showError,
493+ success : function(xmlData) {
494+ // ファイルパスの要素のみを抽出する
495+ var separateTag = xmlData.getElementsByTagName("file");
496+ var urls = new Array(separateTag.length);
497+
498+ // すべてのファイルパスを配列に格納する
499+ for (var i = 0; i < separateTag.length; i++) {
500+ // "path"ノードの値を格納
501+ urls[i] = separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue;
502+ }
503+
504+ // セマフォを初期化
505+ fetchEntriesSemaphore.init();
506+ fetchEntriesSemaphore.urls = urls;
507+ fetchEntriesSemaphore.count = urls.length;
508+
509+ // ファイルパス配列に格納されているすべての記事に対し、探索を開始する
510+ for (var i = 0; i < separateTag.length; i++) {
511+ // ファイルパス配列の要素からリクエストを生成し、対象データをロードする
512+ var xhr = new jQuery.ajax({
513+ url : urls[i],
514+ method : "GET",
515+ success : fetchHashEntries
516+ });
517+ fetchEntriesSemaphore.xhrs.push(xhr);
518+ }
519+ }
520+ });
521+}
522+
523+/**
524+ * URLハッシュ検索用のjQueryコールバック関数
525+ */
526+function fetchHashEntries(xmlData) {
527+ // 既に検索結果が算出されていた場合は、何もしない
528+ if (fetchEntriesSemaphore.buf > 0) {
529+ return true;
530+ }
531+
532+ // ハッシュを取得
533+ var urlhash = getHashFromUrl();
534+
535+ // entry要素のみを切り出す
536+ var entries = xmlData.getElementsByTagName("entry");
537+
538+ for (var i = 0; i < entries.length; i++) {
539+ // entryタグ内部のidノードの値のみ抽出し、入力されたhashと比較を行う
540+ var entry = new Entry(entries[i]);
541+
542+ // idの値と比較を行う
543+ if (urlhash == entry.id) {
544+ // 一致した場合は該当記事を表示する
545+ $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
546+ generatePanel(entry, "feedblog_drawitem", "feedblog_drawpanel", false);
547+
548+ fetchEntriesSemaphore.buf.push(entry);
549+ return true;
550+ }
551+ }
552+
553+ // セマフォのカウンタを減少させます (Ajaxとの同期のため)
554+ fetchEntriesSemaphore.count--;
555+
556+ // 最後のファイルまで探索しても記事が見つからなかった場合はエラーを表示します。
557+ if (fetchEntriesSemaphore.count == 0) {
558+ var entries = fetchEntriesSemaphore.buf;
559+
560+ if (entries.length == 0) {
561+ notFoundError();
562+ return false;
563+ }
564+ }
565+}
566+
567+/**
568+ * 渡された文字列と一致するfeed1.0:id(sha-1)要素を持った記事を検索し、表示します
569+ * @param {String} urlhash feed1.0:id(sha-1)要素と一致する文字列
570+ */
571+function searchIdMode(urlhash) {
572+ // ロードエフェクト表示
573+ loadingEffect();
574+
575+ // ログXMLファイルを読み込む
576+ var loader = new jQuery.ajax({
577+ url : logXmlUrl + '?time=' + urlSuffix,
578+ method : "GET",
579+ error : showError,
580+ success : function(xmlData) {
581+ // ファイルパスの要素のみを抽出する
582+ var separateTag = xmlData.getElementsByTagName("file");
583+ var urls = new Array(separateTag.length);
584+
585+ // すべてのファイルパスを配列に格納する
586+ for (var i = 0; i < separateTag.length; i++) {
587+ // "path"ノードの値を格納
588+ urls[i] = separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue;
589+ }
590+
591+ // セマフォを初期化
592+ fetchEntriesSemaphore.init();
593+ fetchEntriesSemaphore.urls = urls;
594+ fetchEntriesSemaphore.count = urls.length;
595+
596+ // ファイルパス配列に格納されているすべての記事に対し、探索を開始する
597+ for (var i = 0; i < separateTag.length; i++) {
598+ // ファイルパス配列の要素からリクエストを生成し、対象データをロードする
599+ var xhr = new jQuery.ajax({
600+ url : urls[i],
601+ method : "GET",
602+ success : fetchIdEntries
603+ });
604+ fetchEntriesSemaphore.xhrs.push(xhr);
605+ }
606+ }
607+ });
608+}
609+
610+/**
611+ * ID検索用のjQueryコールバック関数
612+ */
613+function fetchIdEntries(xmlData) {
614+ // 既に検索結果が算出されていた場合は、何もしない
615+ if (fetchEntriesSemaphore.buf > 0) {
616+ return true;
617+ }
618+
619+ // IDを取得
620+ var id = getParamFromUrl("id");
621+
622+ // entry要素のみを切り出す
623+ var entries = xmlData.getElementsByTagName("entry");
624+
625+ for (var i = 0; i < entries.length; i++) {
626+ // entryタグ内部のidノードの値のみ抽出し、入力されたhashと比較を行う
627+ var entry = new Entry(entries[i]);
628+
629+ // idの値と比較を行う
630+ if (id == CryptoJS.SHA1(entry.id).toString()) {
631+ // 一致した場合は該当記事を表示する
632+ $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
633+ generatePanel(entry, "feedblog_drawitem", "feedblog_drawpanel", false);
634+
635+ fetchEntriesSemaphore.buf.push(entry);
636+ return true;
637+ }
638+
639+ }
640+
641+ // セマフォのカウンタを減少させます (Ajaxとの同期のため)
642+ fetchEntriesSemaphore.count--;
643+
644+ // 最後のファイルまで探索しても記事が見つからなかった場合はエラーを表示します。
645+ if (fetchEntriesSemaphore.count == 0) {
646+ var entries = fetchEntriesSemaphore.buf;
647+
648+ if (entries.length == 0) {
649+ notFoundError();
650+ return false;
651+ }
652+ }
653+
654+}
655+
656+/**
657+ * 渡された文字列と一致するfeed1.0:category:termタグ要素を持った記事を検索し、表示します
658+ * @param {String} tag feed1.0:category:term要素と一致する文字列
659+ * @param {boolean} showLimit 省略表示をする場合はtrue,全件表示する場合はfalse
660+ */
661+function searchTagMode(tag, showLimit) {
662+ // 処理中エフェクト表示
663+ executingEffect();
664+
665+ // ログXMLファイルを読み込む
666+ var loader = new jQuery.ajax({
667+ url : logXmlUrl + '?time=' + urlSuffix,
668+ method : "GET",
669+ error : showError,
670+ success : function(xmlData) {
671+ // ファイルパスの要素のみを抽出する
672+ var separateTag = xmlData.getElementsByTagName("file");
673+ var urls = new Array(separateTag.length);
674+
675+ // すべてのファイルパスを配列に格納する
676+ for (var i = 0; i < separateTag.length; i++) {
677+ // "path"ノードの値を格納
678+ urls[i] = separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue;
679+ }
680+
681+ // セマフォを初期化
682+ fetchEntriesSemaphore.init();
683+ fetchEntriesSemaphore.urls = urls;
684+ fetchEntriesSemaphore.count = urls.length;
685+ fetchEntriesSemaphore.entryIndex = 0;
686+ fetchEntriesSemaphore.entryCount = 0;
687+ fetchEntriesSemaphore.showLimit = showLimit;
688+
689+ // ファイルパス配列に格納されているすべての記事に対し、探索を開始する
690+ for (var i = 0; i < separateTag.length; i++) {
691+ // ファイルパス配列の要素からリクエストを生成し、対象データをロードする
692+ var xhr = new jQuery.ajax({
693+ url : urls[i],
694+ method : "GET",
695+ success : fetchTagEntries
696+ });
697+ fetchEntriesSemaphore.xhrs.push(xhr);
698+ }
699+ }
700+ });
701+}
702+
703+/**
704+ * タグ検索用のjQueryコールバック関数
705+ */
706+function fetchTagEntries(xmlData) {
707+ // 既に記事の表示が行われている場合、何も実施しない
708+ if (fetchEntriesSemaphore.buf.length > 0) {
709+ return true;
710+ }
711+
712+ // タグを取得する
713+ var tag = getParamFromUrl("tag");
714+
715+ // entry要素のみを切り出す
716+ var entries = xmlData.getElementsByTagName("entry");
717+
718+ for (var j = 0; j < entries.length; j++) {
719+ var entry = new Entry(entries[j]);
720+
721+ for (var k = 0; k < entry.category.length; k++) {
722+ // タグのIDが一致したら格納
723+ if (tag == entry.category.eq(k).attr("term")) {
724+ // 表示数限界を超えていた場合、表示処理を実施しない
725+ if (fetchEntriesSemaphore.showLimit == true && fetchEntriesSemaphore.entryIndex >= showLength) {
726+ // 検索一致件数をカウント
727+ fetchEntriesSemaphore.entryCount++;
728+ } else {
729+ // 検索一致件数をカウント
730+ fetchEntriesSemaphore.entryCount++;
731+ // entryを格納する
732+ fetchEntriesSemaphore.entryIndex = showEntriesAdd(entry, fetchEntriesSemaphore.entryIndex);
733+ }
734+ }
735+ }
736+ }
737+
738+ // セマフォのカウンタを減少させます (Ajaxとの同期のため)
739+ fetchEntriesSemaphore.count--;
740+
741+ if (fetchEntriesSemaphore.count == 0) {
742+ if (fetchEntriesSemaphore.entryIndex == 0) {
743+ notFoundError();
744+ return false;
745+ } else {
746+ executingEffectComplete();
747+ return true;
748+ }
749+ }
750+}
751+
752+/**
753+ * 検索結果を分割して表示します
754+ * class - div.feedblog_pager, ul.feedblog_pager, li.feedblog_pager などなど
755+ * @param {int} showLength 一回の画面に表示する記事数
756+ * @param {int} startIndex 表示を開始する記事のインデックス
757+ */
758+function showEntriesRange(showLength, startIndex) {
759+ // メモリ上から記事データをロード
760+ var entries = loadedEntries;
761+
762+ // 表示インデックスが範囲外の場合はエラーパネルを表示して終了
763+ if (startIndex < 0 || (entries.length <= startIndex && entries.length != 0)) {
764+ showError();
765+ return;
766+ }
767+
768+ var stringBuffer = [];
769+
770+ // リミッターを設定する
771+ var loopLimit = (showLength + startIndex > entries.length) ? entries.length : showLength + startIndex;
772+ var indexShowEntries = loopLimit + 1;
773+
774+ // 情報メニュー表示用バッファです
775+ var menuInfoBuffer = [];
776+ menuInfoBuffer.push("<div class='feedblog_pager_shownumber'>");
777+ var viewStartIndex = 0;
778+ entries.length == 0 ? viewStartIndex = 0 : viewStartIndex = startIndex + 1;
779+ menuInfoBuffer.push(viewStartIndex + "件~" + loopLimit + "件(全" + entries.length + "件)目の記事を表示中<br/>");
780+ menuInfoBuffer.push("</div>");
781+
782+ // ページ移動メニュー表示用バッファです
783+ var menuMoveBuffer = [];
784+ menuMoveBuffer.push("<ul class='feedblog_pager'>");
785+
786+ // ブランクエリアを挟む
787+ menuMoveBuffer.push("<li class='feedblog_pager_blank'></li>");
788+
789+ // 左パネルの表示制御
790+ if (startIndex - showLength >= 0) {
791+ menuMoveBuffer.push("\<li class='feedblog_pager_goback'><span class='feedblog_pager_goback' onclick='showEntriesRange(" + showLength + ", " + (startIndex - showLength) + "); return false;'>\< 前の" + showLength + "件を表示</span\></li>");
792+ } else {
793+ menuMoveBuffer.push("\<li class='feedblog_pager_goback'>\< 前の" + showLength + "件を表示</a\></li>");
794+ }
795+
796+ // 中央のパネルの表示制御
797+ menuMoveBuffer.push("<li class='feedblog_pager_center'>[ ");
798+ var menuNumbers = Math.ceil(entries.length / showLength);
799+ for ( i = 0; i < menuNumbers; i++) {
800+ if (startIndex / showLength == i) {
801+ menuMoveBuffer.push(i + " ");
802+ } else {
803+ menuMoveBuffer.push("<span class='feedblog_pager_center' onclick='showEntriesRange(" + showLength + ", " + (i * showLength) + "); return false;'>");
804+ menuMoveBuffer.push(i);
805+ menuMoveBuffer.push("</span> ");
806+ }
807+ }
808+ menuMoveBuffer.push("]</li>");
809+
810+ // 右パネルの表示制御
811+ if (entries.length > startIndex + showLength) {
812+ menuMoveBuffer.push("\<li class='feedblog_pager_gonext'><span class='feedblog_pager_gonext' onclick='showEntriesRange(" + showLength + ", " + (startIndex + showLength) + "); return false;'>\次の" + showLength + "件を表示 \></span\></li>");
813+ } else {
814+ menuMoveBuffer.push("\<li class='feedblog_pager_gonext'>次の" + showLength + "件を表示 \></li>");
815+ }
816+
817+ // ブランクエリアを挟む
818+ menuMoveBuffer.push("<li class='feedblog_pager_blank'></li>");
819+
820+ menuMoveBuffer.push("</ul>");
821+
822+ // メニューを結合してパネル(前方)に組み込みます
823+ stringBuffer.push("<div class='feedblog_pager_wrapper'>");
824+ stringBuffer.push(menuInfoBuffer.join(""));
825+ stringBuffer.push(menuMoveBuffer.join(""));
826+ stringBuffer.push("</div>");
827+
828+ // 記事描画部分のパネルを生成します
829+ for (var i = startIndex; i < loopLimit; i++) {
830+ stringBuffer.push('<div class="feedblog_drawpanel" id="feedblog_drawpanel');
831+ stringBuffer.push(i);
832+ stringBuffer.push('"><div class="feedblog_drawitem" id="feedblog_drawitem');
833+ stringBuffer.push(i);
834+ stringBuffer.push('"><\/div><\/div>');
835+ }
836+
837+ // メニューを結合してパネル(後方)に組み込みます
838+ stringBuffer.push("<div class='feedblog_pager_wrapper'>");
839+ stringBuffer.push(menuMoveBuffer.join(""));
840+ stringBuffer.push(menuInfoBuffer.join(""));
841+ stringBuffer.push("</div>");
842+
843+ $("#feedblog_writearea").html(stringBuffer.join(""));
844+
845+ for (var i = startIndex; i < loopLimit; i++) {
846+ // 各要素をオブジェクトに格納します
847+ var entry = entries[i];
848+
849+ // すべてのパネルをオープン状態で生成します
850+ generatePanel(entry, "feedblog_drawitem" + i, "feedblog_drawpanel" + i, false);
851+ }
852+}
853+
854+/**
855+ * Entry要素を追加表示する為の関数
856+ * class - div.feedblog_pager, ul.feedblog_pager, li.feedblog_pager などなど
857+ * @param {Entry} 末尾に追加するEntryオブジェクト
858+ * @param {int} 現在のEntry数
859+ * @return {int} 新規記事を追加し終えた後のEntry数
860+ */
861+function showEntriesAdd(entry, entryIndex) {
862+
863+ var stringBuffer = [];
864+
865+ // 記事描画部分のパネルを生成します
866+ var newIndex = entryIndex + 1;
867+ stringBuffer.push('<div class="feedblog_drawpanel" id="feedblog_drawpanel');
868+ stringBuffer.push(newIndex);
869+ stringBuffer.push('"><div class="feedblog_drawitem" id="feedblog_drawitem');
870+ stringBuffer.push(newIndex);
871+ stringBuffer.push('"><\/div><\/div>');
872+
873+ // 記事をパネルに描画します
874+ $("#feedblog_writearea").html($("#feedblog_writearea").html() + stringBuffer.join(""));
875+ generatePanel(entry, "feedblog_drawitem" + newIndex, "feedblog_drawpanel" + newIndex, false);
876+
877+ return newIndex;
878+}
879+
--- a/js/lunardial/feedblog_generator.js
+++ b/js/lunardial/feedblog_generator.js
@@ -1,647 +1,647 @@
1-/**
2- * FeedBlog Generator
3- *
4- * @copyright 2009 FeedBlog Project (http://sourceforge.jp/projects/feedblog/)
5- * @author Kureha Hisame (http://lunardial.sakura.ne.jp/) & Yui Naruse (http://airemix.com/)
6- * @since 2009/06/03
7- * @version 4.2.0.0
8- */
9-
10-// Feex XMLの<content>要素で、<br>を使用しているか?
11-var inputValidateMode = 1;
12-// 出力時に<content>要素に<br>を付加するか否かを格納する変数
13-var outputValidateMode = 1;
14-// ログのリストが書かれたXMLのファイルパスを記入してください
15-var logXmlUrl;
16-
17-// フィードの基本情報を記録する変数
18-var feedInfo;
19-// 記事リストを格納する変数
20-var entryList;
21-// 現在編集中の記事の位置を示す変数
22-var editIndex;
23-// feedblogの設置アドレスを格納する変数
24-var pageAddr;
25-
26-/**
27- * 編集中の内容を反映し、画面に出力します
28- */
29-function applyChange() {
30- feedInfo = applyFeedinfo();
31- pageAddr = feedInfo.alternate;
32-
33- if (document.getElementById("entry_title").value == "" || document.getElementById("entry_stdin").value == "") {
34- if (confirm("タイトルか記事が空白です!FeedBlogでの表示時にエラーが出ますがよろしいですか?") == false) {
35- return;
36- }
37- }
38-
39- if (editIndex < 0) {
40- var entry = new Object();
41- var dateRfc3339 = getDate();
42- entry.id = pageAddr + "?" + dateRfc3339;
43- entry.title = document.getElementById("entry_title").value;
44- entry.summary = "";
45- entry.published = dateRfc3339;
46- entry.updated = dateRfc3339;
47- entry.link = pageAddr + "#" + entry.id;
48- entry.content = document.getElementById("entry_stdin").value.replace(/\r\n/g, "\n");
49- entry.category = getTags();
50- entryList.unshift(entry);
51-
52- // ログ一覧を更新する
53- refleshEntrylistBox();
54-
55- // 更新後、選択されている項目を、先刻追加した日記に移動する
56- document.getElementById("logBox").selectedIndex = 1;
57- editIndex = 0;
58- } else {
59- entryList[editIndex].title = document.getElementById("entry_title").value;
60- entryList[editIndex].updated = getDate();
61- entryList[editIndex].content = document.getElementById("entry_stdin").value.replace(/\r\n/g, "\n");
62- entryList[editIndex].category = getTags();
63-
64- document.getElementById("logBox").options[parseInt(editIndex) + 1].text = entryList[editIndex].title;
65- }
66-
67- document.getElementById("stdout").value = toXml(feedInfo, entryList);
68-
69- // プレビューエリアにHTMLを表示します
70- document.getElementById("previewArea").innerHTML = entryList[editIndex].content.replace(/\n/g, "<br>");
71-}
72-
73-/**
74- * mixi用に記事を変換し、ウインドウを立ち上げて表示します
75- */
76-function openMixiWindow() {
77- // 日記が選択されていない場合は何もしない
78- if (editIndex < 0) {
79- return;
80- }
81-
82- // HTMLを生成します
83- var window_str = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>FeedBlog Mixi用変換</title><style type="text/css">body {font-size: 12px;line-height: 18px;color: #004488;margin-top: 10px; margin-bottom: 10px;}td.default {font-size: 12px;line-height: 18px;color: #004488;border: 1px solid #8888ff;text-align: left;vertical-align: top;}div {text-align: left;}div.code {background: #f8f8ff;border: 1px solid #c8c8ff;padding: 10px;margin: 10px;margin-left: 0px;border-left: 5px solid #e8e8ff;font-size: 12px;line-height: 18px;}table.pager {padding: 0px;margin: 0px;border: 1px solid #8888ff;}td.pager {border: 1px solid #8888ff;padding-left: 3px;}td.formheader {font-size: 9pt;line-height: 13pt;font-family: sans-serif;font-weight: bold;padding: 3px;border: 1px solid #C5C5DE;background-color: #FAFAFF;}td.forminput {font-size: 9pt;line-height: 13pt;font-family: sans-serif;padding: 5px;border: 1px dotted #C5C5DE;}input {font-size: 9pt;line-height: 13pt;font-family: sans-serif;color: #1E4080;border: 1px dotted #BABAFE;background-color: #F5F5FF;}textarea {font-size: 9pt;line-height: 13pt;font-family: sans-serif;color: #1E4080;border: 1px dotted #BABAFE;background-color: #F5F5FF;}</style></head><body><center><table align="center" style="width: 800px;"><tbody><tr><td class="default" colspan="2" style="padding: 5px; text-align: center; vertical-align: bottom;"><table style="width: 100%"><tbody><tr><td class="formheader" style="text-align: center; width: 100px;">タイトル</td><td class="forminput" style="padding: 0px 5px 0px 3px;"><input type="text" id="entry_title" style="width: 100%" value="' + entryList[editIndex].title + '"></td></tr><tr><td class="formheader" style="text-align: center;">本文</td><td class="forminput" style="padding: 0px 5px 0px 3px;"><textarea id="entry_stdin" rows="20" style="width: 100%">' + mixiWrapper(entryList[editIndex].content) + '</textarea></td></tr></tbody></table></td></tr></tbody></table><br><input type="button" value="ウインドウを閉じる" onclick="javascript: window.close();"></center></body></html>';
84-
85- // ウインドウに書き出します
86- var mixi_win = window.open('', 'feedblog_mixi_wrapper', 'width = 850, height = 490, scrollbars=yes');
87- mixi_win.document.open();
88- mixi_win.document.write(window_str);
89- mixi_win.document.close();
90-}
91-
92-/**
93- * mixi用に通常のHTMLを変換します
94- * @param {String} contents 日記の本文が格納されている文字列
95- */
96-function mixiWrapper(contents) {
97- // 置換に使用する変数
98- var target_tag;
99- var target_element;
100-
101- // ニコニコ動画のタグを置換
102- while ( target_tag = contents.match(/<iframe[^>]*src=["']http:\/\/ext.nicovideo.jp\/thumb\/[A-Za-z]*\d*["'][^>]*>[^<]*<\/iframe>/i)) {
103- // src="..."の部分だけ抜き出す
104- target_element = target_tag[0].match(/src=["'][^"']*["']/i);
105- target_element = '&lt;externalvideo src=&quot;NC:' + target_element[0].replace(/src=["']http:\/\/ext.nicovideo.jp\/thumb\/|["']/ig, "") + ':D&quot;&gt;';
106- // iframeタグ全体を置換する
107- contents = contents.replace(target_tag, target_element);
108- }
109-
110- // Aタグを変換する
111- while (contents.match(/<a[^>]*>/)) {
112- // 置換対象のAタグを抽出する
113- target_tag = contents.match(/<a[^>]*>/i);
114- // href="..."の部分のみを抜き出す
115- target_element = target_tag[0].match(/href=["'][^"']*["']/i);
116- // 相対URIが検出された場合、フルに置換する
117- var baseUri = document.location.href.replace(/[^\/]+$/, '');
118- target_element = target_element[0].replace(/\.\//, baseUri).replace(/\.\.\//g, "");
119- // Aタグ全体を消去し、再度Aクローズタグの置換を行う
120- contents = contents.replace(target_tag, "");
121- contents = contents.replace(/<\/a>/i, " ( " + target_element.replace(/href=|["']/g, "") + " ) ");
122- }
123-
124- // ブロック要素のタグが存在した場合、改行をその後に挿入します。
125- if (document.getElementById("isCoverBlockTag").checked) {
126- contents = contents.replace(/<(div|h\d)[^>]*>/ig, "-----------------------------------------------------------------------------\n");
127- contents = contents.replace(/(\n|)<\/(div|h\d)>/ig, "\n-----------------------------------------------------------------------------\n");
128- } else {
129- contents = contents.replace(/<\/(div|h\d|p)>/ig, "\n");
130- }
131-
132- // li要素を置換する
133- contents = contents.replace(/<[\/]{0,1}ul>/ig, "");
134- contents = contents.replace(/<li>/ig, "・");
135- contents = contents.replace(/<\/li>/ig, "\n");
136-
137- // 通常のタグすべてを削除する
138- contents = contents.replace(/<[^>]*>|<\/[^>]*>/ig, "");
139-
140- // 通常のタグ置換後、ニコニコ動画のタグを元に戻す
141- contents = contents.replace(/&lt;externalvideo src=&quot;NC:/g, "<externalvideo src='NC:");
142- contents = contents.replace(/:D&quot;&gt;/, ":D'>");
143-
144- // 半角を置換する
145- contents = contents.replace(/&nbsp;/g, " ");
146-
147- return contents;
148-}
149-
150-/**
151- * 選択中の記事を削除します
152- * @param {int} index entryListから削除される記事のインデックス
153- */
154-function removeEntry(index) {
155- if (editIndex == 0) {
156- entryList.splice(editIndex, 1);
157- refleshEntrylistBox();
158- editIndex = -1;
159-
160- document.getElementById("stdout").value = "";
161- document.getElementById("entry_title").value = "";
162- $("#entry_stdin").jqte();
163- document.getElementById("entry_stdin").value = "";
164- $("#entry_stdin").jqte();
165- }
166- if (editIndex > 0) {
167- var prevIndex = document.getElementById("logBox").selectedIndex - 1;
168- entryList.splice(editIndex, 1);
169- refleshEntrylistBox();
170- document.getElementById("logBox").selectedIndex = prevIndex;
171- editIndex = editIndex - 1;
172-
173- entryLoader(editIndex);
174- }
175-
176- // プレビューエリアをクリアします
177- document.getElementById("previewArea").innerHTML = "";
178-}
179-
180-/**
181- * すべての記事を削除します
182- */
183-function allRemoveEntry() {
184- entryList = [];
185-
186- editIndex = -1;
187- refleshEntrylistBox();
188-
189- document.getElementById("stdout").value = "";
190- document.getElementById("entry_title").value = "";
191- $("#entry_stdin").jqte();
192- document.getElementById("entry_stdin").value = "";
193- $("#entry_stdin").jqte();
194-}
195-
196-/**
197- * 全ての定数を取得・セットします
198- */
199-function initialize() {
200- inputValidateMode = $("#feedblog_inputvalidatemode").val();
201- outputValidateMode = $("#feedblog_outputvalidatemode").val();
202- logXmlUrl = $("#feedblog_loglistxmlurl").val();
203-
204- if (outputValidateMode == "1") {
205- document.getElementById("feedblog_addcontentbr").checked = true;
206- } else {
207- document.getElementById("feedblog_addcontentbr").checked = false;
208- }
209-}
210-
211-/**
212- * 全DOMが使用可能になり次第、自動的に呼ばれる関数
213- */
214-$(document).ready(function() {
215- // 初期処理を実施
216- initialize();
217-
218- // jquery-teを初期として適用します
219- $("#entry_stdin").jqte();
220-
221- // ログ一覧のXMLをロードします
222- logXMLLoader();
223-});
224-
225-/**
226- * ログファイル選択用のコンボボックスをid名:feedblog_logselecterに生成します
227- */
228-function logXMLLoader() {
229- // ログ用のXMLを読み込みます
230- jQuery.ajax({
231- url : logXmlUrl + "?time=" + (+new Date()),
232- method : "GET",
233- success : function(xmlData) {
234- var separateTag = xmlData.getElementsByTagName("file");
235- var urls = new Array(separateTag.length);
236- var initUrl;
237-
238- // 読み込んだ要素をStoreに格納して表示
239- var boxBuffer = [];
240- boxBuffer.push("<form name='logform'><select name='logbox' onchange='xmlLoader(this.options[this.selectedIndex].value)'>");
241- for (var i = 0; i < separateTag.length; i++) {
242- if (i == 0) {
243- initUrl = separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue;
244- }
245- boxBuffer.push("<option value='" + separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue + "'/>" + separateTag[i].getElementsByTagName("display")[0].firstChild.nodeValue + " (" + separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue + ")" + "</option>");
246- }
247- boxBuffer.push("</select></form>");
248-
249- // コンボボックス要素を生成
250- document.getElementById("feedblog_logselecter").innerHTML = boxBuffer.join("");
251-
252- // 最新の日記をローディングする
253- xmlLoader(initUrl);
254- },
255- error : showError
256- });
257-}
258-
259-/**
260- * URLを指定し、指定されたFeedXmlを読み込み、解析を行います
261- * @param {String} url
262- */
263-function xmlLoader(url) {
264- // 記事本体のデータをローディングする
265- var loader = new jQuery.ajax({
266- url : url + "?time=" + (+new Date()),
267- method : "GET",
268- success : analyzeTargetXml,
269- error : showError
270- });
271-}
272-
273-/**
274- * 引数に存在するXMLデータを解析し、画面に反映します
275- * @param {Object} xmlData
276- */
277-function analyzeTargetXml(xmlData) {
278- var rootTag = xmlData.getElementsByTagName("feed");
279- var entryTag = xmlData.getElementsByTagName("entry");
280-
281- // グローバル変数を初期化
282- feedInfo = null;
283- entryList = [];
284-
285- feedInfo = new FeedInfo(rootTag[0]);
286- for (var i = 0; i < entryTag.length; i++) {
287- entryList.push(new Entry(entryTag[i]));
288- }
289-
290- feedinfoLoader(feedInfo);
291- pageAddr = feedInfo.alternate;
292-
293- refleshEntrylistBox();
294- document.getElementById("entry_title").value = "";
295- $("#entry_stdin").jqte();
296- document.getElementById("entry_stdin").value = "";
297- $("#entry_stdin").jqte();
298- document.getElementById("entry_category").innerHTML = "";
299- document.getElementById("stdout").value = "";
300- editIndex = -1;
301-
302- // プレビューエリアをクリアします
303- document.getElementById("previewArea").innerHTML = "";
304-}
305-
306-/**
307- * feedInfo変数の内容をHTMLに反映します
308- * @param {FeedInfo} finfo 反映するfeedInfo変数
309- */
310-function feedinfoLoader(finfo) {
311- document.getElementById("feed_title").value = finfo.title;
312- document.getElementById("feed_subtitle").value = finfo.subtitle;
313- document.getElementById("feed_self").value = finfo.self;
314- document.getElementById("feed_alternate").value = finfo.alternate;
315- document.getElementById("feed_id").value = finfo.id;
316- document.getElementById("feed_rights").value = finfo.rights;
317- document.getElementById("feed_authorname").value = finfo.authorname;
318- document.getElementById("feed_authoremail").value = finfo.authoremail;
319-}
320-
321-/**
322- * HTMLの内容をFeedInfoに変換します
323- */
324-function applyFeedinfo() {
325- var finfo = new Object();
326- finfo.title = document.getElementById("feed_title").value;
327- finfo.subtitle = document.getElementById("feed_subtitle").value;
328- finfo.self = document.getElementById("feed_self").value;
329- finfo.alternate = document.getElementById("feed_alternate").value;
330- finfo.id = document.getElementById("feed_id").value;
331- finfo.rights = document.getElementById("feed_rights").value;
332- finfo.authorname = document.getElementById("feed_authorname").value;
333- finfo.authoremail = document.getElementById("feed_authoremail").value;
334-
335- return finfo;
336-}
337-
338-/**
339- * 指定したEntryList上のインデックスの記事をロードします
340- * @param {int} index
341- */
342-function entryLoader(index) {
343- if (index < 0) {
344- document.getElementById("entry_title").value = "";
345- $("#entry_stdin").jqte();
346- document.getElementById("entry_stdin").value = "";
347- $("#entry_stdin").jqte();
348- addTagSelectBoxFromCategory([]);
349- editIndex = -1;
350- } else {
351- document.getElementById("entry_title").value = entryList[index].title;
352- $("#entry_stdin").jqte();
353- document.getElementById("entry_stdin").value = entryList[index].content;
354- $("#entry_stdin").jqte();
355- addTagSelectBoxFromCategory(entryList[index].category);
356- editIndex = index;
357- }
358-
359- // プレビューエリアをクリアします
360- document.getElementById("previewArea").innerHTML = "";
361-}
362-
363-/**
364- * 記事一覧の情報を表示するセレクトボックスにentryListの情報を反映させます
365- */
366-function refleshEntrylistBox() {
367- var stringBuffer = [];
368- stringBuffer.push("<form name='logform'><select id='logBox' onchange='entryLoader(this.options[this.selectedIndex].value)'>");
369- stringBuffer.push("<option value='-1'>新規作成</option>");
370- for (var i = 0; i < entryList.length; i++) {
371- stringBuffer.push("<option value='" + i + "'/>" + entryList[i].title + "</option>");
372- }
373- stringBuffer.push("</select></form>");
374- // コンボボックス要素を生成
375- document.getElementById("feedblog_entryselect").innerHTML = stringBuffer.join("");
376-}
377-
378-/**
379- * Feed基本情報保持クラス
380- * @param {Object} obj
381- */
382-function FeedInfo(obj) {
383- this.title = $("title:first", obj).text();
384- this.subtitle = $("subtitle:first", obj).text();
385- this.self = $("link[rel=self]", obj).attr("href");
386- this.alternate = $("link[rel=alternate]", obj).attr("href");
387- this.updated = $("updated:first", obj).text();
388- this.id = $("id:first", obj).text();
389- this.rights = $("rights:first", obj).text();
390- this.authorname = $("author>name", obj).text();
391- this.authoremail = $("author>email", obj).text();
392-}
393-
394-/**
395- * 記事クラス
396- * @param {Object} obj entry 要素の DOM オブジェクト
397- */
398-function Entry(obj) {
399- this.id = $("id:first", obj).text();
400- this.title = $("title:first", obj).text();
401- this.summary = $("summary:first", obj).text();
402- this.published = $("published:first", obj).text();
403- this.updated = $("updated:first", obj).text();
404- this.link = $("link:first", obj).attr("href");
405- this.content = $("content:first", obj).text();
406-
407- var categoryList = [];
408- var categories = $("category", obj);
409- var tempCategory = {};
410- for (var i = 0; i < categories.length; i++) {
411- tempCategory = {
412- "term" : categories.eq(i).attr("term"),
413- "label" : categories.eq(i).attr("label")
414- };
415- categoryList.push(tempCategory);
416- }
417- this.category = categoryList;
418-
419- if (inputValidateMode == 1) {
420- this.content = this.content.replace(/[\r\n]|\r\n/g, "");
421- this.content = this.content.replace(/<br[ \/]*>/ig, "\n");
422- this.content = this.content.replace(/^[ \t]*/mg, "");
423- }
424-}
425-
426-/**
427- * グローバル変数からXMLを生成し、返却します
428- * @param {FeedInfo} finfo
429- * @param {Entry[]} elist
430- */
431-function toXml(finfo, elist) {
432- var stringBuffer = [];
433-
434- stringBuffer.push('<?xml version="1.0" encoding="utf-8"?>');
435- stringBuffer.push('<feed xml:lang="ja-jp" xmlns="http://www.w3.org/2005/Atom">');
436- stringBuffer.push('');
437-
438- stringBuffer.push('<title type="text">' + finfo.title + '</title>');
439- stringBuffer.push('<subtitle type="text">' + finfo.subtitle + '</subtitle>');
440- stringBuffer.push('<link rel="self" type="application/atom+xml" href="' + finfo.self + '" />');
441- stringBuffer.push('<link rel="alternate" type="text/html" href="' + finfo.alternate + '" />');
442- stringBuffer.push('<updated>' + getDate() + '</updated>');
443- stringBuffer.push('<id>' + finfo.id + '</id>');
444- stringBuffer.push('<rights type="text">' + finfo.rights + '</rights>');
445- stringBuffer.push('<author>');
446- stringBuffer.push(' <name>' + finfo.authorname + '</name>');
447- stringBuffer.push(' <email>' + finfo.authoremail + '</email>');
448- stringBuffer.push('</author>');
449- stringBuffer.push('');
450-
451- for (var i = 0; i < elist.length; i++) {
452- var temp_content;
453- if (document.getElementById("feedblog_addcontentbr").checked) {
454- temp_content = xmlAttrContentEscape(convertContent(elist[i].content));
455- } else {
456- temp_content = xmlAttrContentEscape(elist[i].content);
457- }
458-
459- stringBuffer.push('<entry>');
460- stringBuffer.push('<id>' + elist[i].id + '</id>');
461- stringBuffer.push('<title>' + elist[i].title + '</title>');
462- stringBuffer.push('<summary>' + elist[i].summary + '</summary>');
463- stringBuffer.push('<published>' + elist[i].published + '</published>');
464- stringBuffer.push('<updated>' + elist[i].updated + '</updated>');
465- stringBuffer.push('<link href="' + elist[i].link + '" />');
466- stringBuffer.push('<content type="html">' + temp_content + '</content>');
467-
468- for (var j = 0; j < elist[i].category.length; j++) {
469- var tmpElist = elist[i].category[j];
470- stringBuffer.push('<category term="' + tmpElist["term"] + '" label="' + tmpElist["label"] + '"/>');
471- }
472-
473- stringBuffer.push('</entry>');
474- stringBuffer.push('');
475- }
476-
477- stringBuffer.push('</feed>');
478-
479- return stringBuffer.join("\n");
480-}
481-
482-/**
483- * <content>要素の変換を行います
484- * @param {String} content
485- */
486-function convertContent(content) {
487- if (document.getElementById("feedblog_addcontentbr").checked) {
488- content = content.replace(/[\n\r]|\r\n/g, "<br>\n");
489- } else {
490- content = content.replace(/<br>/ig, "\n");
491- }
492-
493- return content;
494-}
495-
496-/**
497- * XMLのエスケープを行う関数
498- * @param {String} str エスケープを行う文字列
499- */
500-function xmlAttrContentEscape(str) {
501- return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/^[ ]+/mg, "&nbsp;").replace(/^[\t]+/mg, "");
502-}
503-
504-/**
505- * XMLの逆エスケープを行う関数
506- * @param {String} str 逆エスケープを行う文字列
507- */
508-function xmlAttrContentUnescape(str) {
509- return str.replace(/^[\t]+/mg, "").replace(/^[ ]+/mg, "&nbsp;").replace(/&quot;/g, '"').replace(/&gt;/g, ">").replace(/&lt;/g, "<").replace(/&amp;/g, "&");
510-}
511-
512-/**
513- * jQueryでのパネル開閉を制御します
514- * @param {String} id 開閉するオブジェクトのid
515- */
516-function closePanel(id) {
517- $("#" + id).slideToggle();
518-}
519-
520-/**
521- * エラー画面を表示します
522- */
523-function showError() {
524- alert("XMLファイルのローディング中にエラーが発生しました!");
525-}
526-
527-/**
528- * RFC3339形式の日時を出力します
529- */
530-function getDate() {
531- var dNow = new Date();
532- var sYear = dNow.getFullYear();
533- var sMonth = dNow.getMonth() + 1;
534- var sDate = dNow.getDate();
535- var sHour = dNow.getHours();
536- var sMinute = dNow.getMinutes();
537- var sSecond = dNow.getSeconds();
538-
539- // 10以下の時は頭に"0"を挿入
540- if (sMonth < 10)
541- sMonth = "0" + sMonth;
542- if (sDate < 10)
543- sDate = "0" + sDate;
544- if (sHour < 10)
545- sHour = "0" + sHour;
546- if (sMinute < 10)
547- sMinute = "0" + sMinute;
548- if (sSecond < 10)
549- sSecond = "0" + sSecond;
550-
551- return sYear + "-" + sMonth + "-" + sDate + "T" + sHour + ":" + sMinute + ":" + sSecond + "+09:00";
552-}
553-
554-/**
555- * タグ情報一覧を返却する関数です
556- */
557-function getTags() {
558- var tagList = [];
559- var tagListHtml = $("*[name=tag]");
560- var tagTemp = {};
561- for (var i = 0; i < tagListHtml.length; i++) {
562- // 各tag要素を取得して配列に格納
563- if (tagListHtml.eq(i).val() != "") {
564- tagTemp = {
565- "term" : tagListHtml.eq(i).val(),
566- "label" : tagListHtml.eq(i).find(":selected").text()
567- };
568- tagList.push(tagTemp);
569- }
570- }
571-
572- return tagList;
573-}
574-
575-/**
576- * デフォルトのタグ一覧を取得する関数です
577- */
578-function getDefaultTags() {
579- // デフォルトのタグ一覧を取得
580- // $term1,$label1|$term2,$label2 ... で定義されている
581- var plainTagText = $("#feedblog_tagdefine").val();
582- var tagList = [];
583- var tagTemp = {};
584- // 値が空のタグを初期値として先頭に追加
585- tagList.push({
586- "term" : "",
587- "label" : "タグを選択してください"
588- });
589- for (var i = 0; i < plainTagText.split('|').length; i++) {
590- // 各tag要素を取得して配列に格納
591- tagTemp = {
592- "term" : plainTagText.split('|')[i].split(',')[0],
593- "label" : plainTagText.split('|')[i].split(',')[1]
594- };
595- tagList.push(tagTemp);
596- }
597-
598- return tagList;
599-}
600-
601-/**
602- * HTMLにタグ選択用のセレクトボックスを追加します
603- */
604-function addTagSelectBox() {
605- var tagList = getDefaultTags();
606- var addHtml = [];
607-
608- addHtml.push('<select name="tag">');
609- for (var i = 0; i < tagList.length; i++) {
610- addHtml.push('<option value="' + tagList[i]["term"] + '">' + tagList[i]["label"] + '</option>');
611- }
612- addHtml.push('</select>');
613-
614- $("#entry_category").append(addHtml.join('') + "<br>");
615-}
616-
617-/**
618- * 現在のカテゴリーオブジェクトに従いセレクトボックスを追加します
619- */
620-function addTagSelectBoxFromCategory(categoryList) {
621- $("#entry_category").html("");
622- for (var j = 0; j < categoryList.length; j++) {
623- // 選択対象を取得
624- var selectedTagTerm = categoryList[j]["term"];
625- var selectedTagLabel = categoryList[j]["label"];
626- var selectedTagSetFlag = false;
627-
628- var tagList = getDefaultTags();
629- var addHtml = [];
630- addHtml.push('<select name="tag">');
631- for (var i = 0; i < tagList.length; i++) {
632- if (tagList[i]["term"] == selectedTagTerm && tagList[i]["label"] == selectedTagLabel) {
633- addHtml.push('<option value="' + tagList[i]["term"] + '" selected="selected">' + tagList[i]["label"] + '</option>');
634- selectedTagSetFlag = true;
635- } else {
636- addHtml.push('<option value="' + tagList[i]["term"] + '">' + tagList[i]["label"] + '</option>');
637- }
638- }
639-
640- if (!selectedTagSetFlag) {
641- addHtml.push('<option value="' + selectedTagTerm + '" selected="selected">' + selectedTagLabel + '</option>');
642- }
643-
644- addHtml.push('</select>');
645- $("#entry_category").append(addHtml.join('') + "<br>");
646- }
647-}
1+/**
2+ * FeedBlog Generator
3+ *
4+ * @copyright 2009 FeedBlog Project (http://sourceforge.jp/projects/feedblog/)
5+ * @author Kureha Hisame (http://lunardial.sakura.ne.jp/) & Yui Naruse (http://airemix.com/)
6+ * @since 2009/06/03
7+ * @version 4.2.1.0
8+ */
9+
10+// Feex XMLの<content>要素で、<br>を使用しているか?
11+var inputValidateMode = 1;
12+// 出力時に<content>要素に<br>を付加するか否かを格納する変数
13+var outputValidateMode = 1;
14+// ログのリストが書かれたXMLのファイルパスを記入してください
15+var logXmlUrl;
16+
17+// フィードの基本情報を記録する変数
18+var feedInfo;
19+// 記事リストを格納する変数
20+var entryList;
21+// 現在編集中の記事の位置を示す変数
22+var editIndex;
23+// feedblogの設置アドレスを格納する変数
24+var pageAddr;
25+
26+/**
27+ * 編集中の内容を反映し、画面に出力します
28+ */
29+function applyChange() {
30+ feedInfo = applyFeedinfo();
31+ pageAddr = feedInfo.alternate;
32+
33+ if (document.getElementById("entry_title").value == "" || document.getElementById("entry_stdin").value == "") {
34+ if (confirm("タイトルか記事が空白です!FeedBlogでの表示時にエラーが出ますがよろしいですか?") == false) {
35+ return;
36+ }
37+ }
38+
39+ if (editIndex < 0) {
40+ var entry = new Object();
41+ var dateRfc3339 = getDate();
42+ entry.id = pageAddr + "?" + dateRfc3339;
43+ entry.title = document.getElementById("entry_title").value;
44+ entry.summary = "";
45+ entry.published = dateRfc3339;
46+ entry.updated = dateRfc3339;
47+ entry.link = pageAddr + "#" + entry.id;
48+ entry.content = document.getElementById("entry_stdin").value.replace(/\r\n/g, "\n");
49+ entry.category = getTags();
50+ entryList.unshift(entry);
51+
52+ // ログ一覧を更新する
53+ refleshEntrylistBox();
54+
55+ // 更新後、選択されている項目を、先刻追加した日記に移動する
56+ document.getElementById("logBox").selectedIndex = 1;
57+ editIndex = 0;
58+ } else {
59+ entryList[editIndex].title = document.getElementById("entry_title").value;
60+ entryList[editIndex].updated = getDate();
61+ entryList[editIndex].content = document.getElementById("entry_stdin").value.replace(/\r\n/g, "\n");
62+ entryList[editIndex].category = getTags();
63+
64+ document.getElementById("logBox").options[parseInt(editIndex) + 1].text = entryList[editIndex].title;
65+ }
66+
67+ document.getElementById("stdout").value = toXml(feedInfo, entryList);
68+
69+ // プレビューエリアにHTMLを表示します
70+ document.getElementById("previewArea").innerHTML = entryList[editIndex].content.replace(/\n/g, "<br>");
71+}
72+
73+/**
74+ * mixi用に記事を変換し、ウインドウを立ち上げて表示します
75+ */
76+function openMixiWindow() {
77+ // 日記が選択されていない場合は何もしない
78+ if (editIndex < 0) {
79+ return;
80+ }
81+
82+ // HTMLを生成します
83+ var window_str = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>FeedBlog Mixi用変換</title><style type="text/css">body {font-size: 12px;line-height: 18px;color: #004488;margin-top: 10px; margin-bottom: 10px;}td.default {font-size: 12px;line-height: 18px;color: #004488;border: 1px solid #8888ff;text-align: left;vertical-align: top;}div {text-align: left;}div.code {background: #f8f8ff;border: 1px solid #c8c8ff;padding: 10px;margin: 10px;margin-left: 0px;border-left: 5px solid #e8e8ff;font-size: 12px;line-height: 18px;}table.pager {padding: 0px;margin: 0px;border: 1px solid #8888ff;}td.pager {border: 1px solid #8888ff;padding-left: 3px;}td.formheader {font-size: 9pt;line-height: 13pt;font-family: sans-serif;font-weight: bold;padding: 3px;border: 1px solid #C5C5DE;background-color: #FAFAFF;}td.forminput {font-size: 9pt;line-height: 13pt;font-family: sans-serif;padding: 5px;border: 1px dotted #C5C5DE;}input {font-size: 9pt;line-height: 13pt;font-family: sans-serif;color: #1E4080;border: 1px dotted #BABAFE;background-color: #F5F5FF;}textarea {font-size: 9pt;line-height: 13pt;font-family: sans-serif;color: #1E4080;border: 1px dotted #BABAFE;background-color: #F5F5FF;}</style></head><body><center><table align="center" style="width: 800px;"><tbody><tr><td class="default" colspan="2" style="padding: 5px; text-align: center; vertical-align: bottom;"><table style="width: 100%"><tbody><tr><td class="formheader" style="text-align: center; width: 100px;">タイトル</td><td class="forminput" style="padding: 0px 5px 0px 3px;"><input type="text" id="entry_title" style="width: 100%" value="' + entryList[editIndex].title + '"></td></tr><tr><td class="formheader" style="text-align: center;">本文</td><td class="forminput" style="padding: 0px 5px 0px 3px;"><textarea id="entry_stdin" rows="20" style="width: 100%">' + mixiWrapper(entryList[editIndex].content) + '</textarea></td></tr></tbody></table></td></tr></tbody></table><br><input type="button" value="ウインドウを閉じる" onclick="javascript: window.close();"></center></body></html>';
84+
85+ // ウインドウに書き出します
86+ var mixi_win = window.open('', 'feedblog_mixi_wrapper', 'width = 850, height = 490, scrollbars=yes');
87+ mixi_win.document.open();
88+ mixi_win.document.write(window_str);
89+ mixi_win.document.close();
90+}
91+
92+/**
93+ * mixi用に通常のHTMLを変換します
94+ * @param {String} contents 日記の本文が格納されている文字列
95+ */
96+function mixiWrapper(contents) {
97+ // 置換に使用する変数
98+ var target_tag;
99+ var target_element;
100+
101+ // ニコニコ動画のタグを置換
102+ while ( target_tag = contents.match(/<iframe[^>]*src=["']http:\/\/ext.nicovideo.jp\/thumb\/[A-Za-z]*\d*["'][^>]*>[^<]*<\/iframe>/i)) {
103+ // src="..."の部分だけ抜き出す
104+ target_element = target_tag[0].match(/src=["'][^"']*["']/i);
105+ target_element = '&lt;externalvideo src=&quot;NC:' + target_element[0].replace(/src=["']http:\/\/ext.nicovideo.jp\/thumb\/|["']/ig, "") + ':D&quot;&gt;';
106+ // iframeタグ全体を置換する
107+ contents = contents.replace(target_tag, target_element);
108+ }
109+
110+ // Aタグを変換する
111+ while (contents.match(/<a[^>]*>/)) {
112+ // 置換対象のAタグを抽出する
113+ target_tag = contents.match(/<a[^>]*>/i);
114+ // href="..."の部分のみを抜き出す
115+ target_element = target_tag[0].match(/href=["'][^"']*["']/i);
116+ // 相対URIが検出された場合、フルに置換する
117+ var baseUri = document.location.href.replace(/[^\/]+$/, '');
118+ target_element = target_element[0].replace(/\.\//, baseUri).replace(/\.\.\//g, "");
119+ // Aタグ全体を消去し、再度Aクローズタグの置換を行う
120+ contents = contents.replace(target_tag, "");
121+ contents = contents.replace(/<\/a>/i, " ( " + target_element.replace(/href=|["']/g, "") + " ) ");
122+ }
123+
124+ // ブロック要素のタグが存在した場合、改行をその後に挿入します。
125+ if (document.getElementById("isCoverBlockTag").checked) {
126+ contents = contents.replace(/<(div|h\d)[^>]*>/ig, "-----------------------------------------------------------------------------\n");
127+ contents = contents.replace(/(\n|)<\/(div|h\d)>/ig, "\n-----------------------------------------------------------------------------\n");
128+ } else {
129+ contents = contents.replace(/<\/(div|h\d|p)>/ig, "\n");
130+ }
131+
132+ // li要素を置換する
133+ contents = contents.replace(/<[\/]{0,1}ul>/ig, "");
134+ contents = contents.replace(/<li>/ig, "・");
135+ contents = contents.replace(/<\/li>/ig, "\n");
136+
137+ // 通常のタグすべてを削除する
138+ contents = contents.replace(/<[^>]*>|<\/[^>]*>/ig, "");
139+
140+ // 通常のタグ置換後、ニコニコ動画のタグを元に戻す
141+ contents = contents.replace(/&lt;externalvideo src=&quot;NC:/g, "<externalvideo src='NC:");
142+ contents = contents.replace(/:D&quot;&gt;/, ":D'>");
143+
144+ // 半角を置換する
145+ contents = contents.replace(/&nbsp;/g, " ");
146+
147+ return contents;
148+}
149+
150+/**
151+ * 選択中の記事を削除します
152+ * @param {int} index entryListから削除される記事のインデックス
153+ */
154+function removeEntry(index) {
155+ if (editIndex == 0) {
156+ entryList.splice(editIndex, 1);
157+ refleshEntrylistBox();
158+ editIndex = -1;
159+
160+ document.getElementById("stdout").value = "";
161+ document.getElementById("entry_title").value = "";
162+ $("#entry_stdin").jqte();
163+ document.getElementById("entry_stdin").value = "";
164+ $("#entry_stdin").jqte();
165+ }
166+ if (editIndex > 0) {
167+ var prevIndex = document.getElementById("logBox").selectedIndex - 1;
168+ entryList.splice(editIndex, 1);
169+ refleshEntrylistBox();
170+ document.getElementById("logBox").selectedIndex = prevIndex;
171+ editIndex = editIndex - 1;
172+
173+ entryLoader(editIndex);
174+ }
175+
176+ // プレビューエリアをクリアします
177+ document.getElementById("previewArea").innerHTML = "";
178+}
179+
180+/**
181+ * すべての記事を削除します
182+ */
183+function allRemoveEntry() {
184+ entryList = [];
185+
186+ editIndex = -1;
187+ refleshEntrylistBox();
188+
189+ document.getElementById("stdout").value = "";
190+ document.getElementById("entry_title").value = "";
191+ $("#entry_stdin").jqte();
192+ document.getElementById("entry_stdin").value = "";
193+ $("#entry_stdin").jqte();
194+}
195+
196+/**
197+ * 全ての定数を取得・セットします
198+ */
199+function initialize() {
200+ inputValidateMode = $("#feedblog_inputvalidatemode").val();
201+ outputValidateMode = $("#feedblog_outputvalidatemode").val();
202+ logXmlUrl = $("#feedblog_loglistxmlurl").val();
203+
204+ if (outputValidateMode == "1") {
205+ document.getElementById("feedblog_addcontentbr").checked = true;
206+ } else {
207+ document.getElementById("feedblog_addcontentbr").checked = false;
208+ }
209+}
210+
211+/**
212+ * 全DOMが使用可能になり次第、自動的に呼ばれる関数
213+ */
214+$(document).ready(function() {
215+ // 初期処理を実施
216+ initialize();
217+
218+ // jquery-teを初期として適用します
219+ $("#entry_stdin").jqte();
220+
221+ // ログ一覧のXMLをロードします
222+ logXMLLoader();
223+});
224+
225+/**
226+ * ログファイル選択用のコンボボックスをid名:feedblog_logselecterに生成します
227+ */
228+function logXMLLoader() {
229+ // ログ用のXMLを読み込みます
230+ jQuery.ajax({
231+ url : logXmlUrl + "?time=" + (+new Date()),
232+ method : "GET",
233+ success : function(xmlData) {
234+ var separateTag = xmlData.getElementsByTagName("file");
235+ var urls = new Array(separateTag.length);
236+ var initUrl;
237+
238+ // 読み込んだ要素をStoreに格納して表示
239+ var boxBuffer = [];
240+ boxBuffer.push("<form name='logform'><select name='logbox' onchange='xmlLoader(this.options[this.selectedIndex].value)'>");
241+ for (var i = 0; i < separateTag.length; i++) {
242+ if (i == 0) {
243+ initUrl = separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue;
244+ }
245+ boxBuffer.push("<option value='" + separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue + "'/>" + separateTag[i].getElementsByTagName("display")[0].firstChild.nodeValue + " (" + separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue + ")" + "</option>");
246+ }
247+ boxBuffer.push("</select></form>");
248+
249+ // コンボボックス要素を生成
250+ document.getElementById("feedblog_logselecter").innerHTML = boxBuffer.join("");
251+
252+ // 最新の日記をローディングする
253+ xmlLoader(initUrl);
254+ },
255+ error : showError
256+ });
257+}
258+
259+/**
260+ * URLを指定し、指定されたFeedXmlを読み込み、解析を行います
261+ * @param {String} url
262+ */
263+function xmlLoader(url) {
264+ // 記事本体のデータをローディングする
265+ var loader = new jQuery.ajax({
266+ url : url + "?time=" + (+new Date()),
267+ method : "GET",
268+ success : analyzeTargetXml,
269+ error : showError
270+ });
271+}
272+
273+/**
274+ * 引数に存在するXMLデータを解析し、画面に反映します
275+ * @param {Object} xmlData
276+ */
277+function analyzeTargetXml(xmlData) {
278+ var rootTag = xmlData.getElementsByTagName("feed");
279+ var entryTag = xmlData.getElementsByTagName("entry");
280+
281+ // グローバル変数を初期化
282+ feedInfo = null;
283+ entryList = [];
284+
285+ feedInfo = new FeedInfo(rootTag[0]);
286+ for (var i = 0; i < entryTag.length; i++) {
287+ entryList.push(new Entry(entryTag[i]));
288+ }
289+
290+ feedinfoLoader(feedInfo);
291+ pageAddr = feedInfo.alternate;
292+
293+ refleshEntrylistBox();
294+ document.getElementById("entry_title").value = "";
295+ $("#entry_stdin").jqte();
296+ document.getElementById("entry_stdin").value = "";
297+ $("#entry_stdin").jqte();
298+ document.getElementById("entry_category").innerHTML = "";
299+ document.getElementById("stdout").value = "";
300+ editIndex = -1;
301+
302+ // プレビューエリアをクリアします
303+ document.getElementById("previewArea").innerHTML = "";
304+}
305+
306+/**
307+ * feedInfo変数の内容をHTMLに反映します
308+ * @param {FeedInfo} finfo 反映するfeedInfo変数
309+ */
310+function feedinfoLoader(finfo) {
311+ document.getElementById("feed_title").value = finfo.title;
312+ document.getElementById("feed_subtitle").value = finfo.subtitle;
313+ document.getElementById("feed_self").value = finfo.self;
314+ document.getElementById("feed_alternate").value = finfo.alternate;
315+ document.getElementById("feed_id").value = finfo.id;
316+ document.getElementById("feed_rights").value = finfo.rights;
317+ document.getElementById("feed_authorname").value = finfo.authorname;
318+ document.getElementById("feed_authoremail").value = finfo.authoremail;
319+}
320+
321+/**
322+ * HTMLの内容をFeedInfoに変換します
323+ */
324+function applyFeedinfo() {
325+ var finfo = new Object();
326+ finfo.title = document.getElementById("feed_title").value;
327+ finfo.subtitle = document.getElementById("feed_subtitle").value;
328+ finfo.self = document.getElementById("feed_self").value;
329+ finfo.alternate = document.getElementById("feed_alternate").value;
330+ finfo.id = document.getElementById("feed_id").value;
331+ finfo.rights = document.getElementById("feed_rights").value;
332+ finfo.authorname = document.getElementById("feed_authorname").value;
333+ finfo.authoremail = document.getElementById("feed_authoremail").value;
334+
335+ return finfo;
336+}
337+
338+/**
339+ * 指定したEntryList上のインデックスの記事をロードします
340+ * @param {int} index
341+ */
342+function entryLoader(index) {
343+ if (index < 0) {
344+ document.getElementById("entry_title").value = "";
345+ $("#entry_stdin").jqte();
346+ document.getElementById("entry_stdin").value = "";
347+ $("#entry_stdin").jqte();
348+ addTagSelectBoxFromCategory([]);
349+ editIndex = -1;
350+ } else {
351+ document.getElementById("entry_title").value = entryList[index].title;
352+ $("#entry_stdin").jqte();
353+ document.getElementById("entry_stdin").value = entryList[index].content;
354+ $("#entry_stdin").jqte();
355+ addTagSelectBoxFromCategory(entryList[index].category);
356+ editIndex = index;
357+ }
358+
359+ // プレビューエリアをクリアします
360+ document.getElementById("previewArea").innerHTML = "";
361+}
362+
363+/**
364+ * 記事一覧の情報を表示するセレクトボックスにentryListの情報を反映させます
365+ */
366+function refleshEntrylistBox() {
367+ var stringBuffer = [];
368+ stringBuffer.push("<form name='logform'><select id='logBox' onchange='entryLoader(this.options[this.selectedIndex].value)'>");
369+ stringBuffer.push("<option value='-1'>新規作成</option>");
370+ for (var i = 0; i < entryList.length; i++) {
371+ stringBuffer.push("<option value='" + i + "'/>" + entryList[i].title + "</option>");
372+ }
373+ stringBuffer.push("</select></form>");
374+ // コンボボックス要素を生成
375+ document.getElementById("feedblog_entryselect").innerHTML = stringBuffer.join("");
376+}
377+
378+/**
379+ * Feed基本情報保持クラス
380+ * @param {Object} obj
381+ */
382+function FeedInfo(obj) {
383+ this.title = $("title:first", obj).text();
384+ this.subtitle = $("subtitle:first", obj).text();
385+ this.self = $("link[rel=self]", obj).attr("href");
386+ this.alternate = $("link[rel=alternate]", obj).attr("href");
387+ this.updated = $("updated:first", obj).text();
388+ this.id = $("id:first", obj).text();
389+ this.rights = $("rights:first", obj).text();
390+ this.authorname = $("author>name", obj).text();
391+ this.authoremail = $("author>email", obj).text();
392+}
393+
394+/**
395+ * 記事クラス
396+ * @param {Object} obj entry 要素の DOM オブジェクト
397+ */
398+function Entry(obj) {
399+ this.id = $("id:first", obj).text();
400+ this.title = $("title:first", obj).text();
401+ this.summary = $("summary:first", obj).text();
402+ this.published = $("published:first", obj).text();
403+ this.updated = $("updated:first", obj).text();
404+ this.link = $("link:first", obj).attr("href");
405+ this.content = $("content:first", obj).text();
406+
407+ var categoryList = [];
408+ var categories = $("category", obj);
409+ var tempCategory = {};
410+ for (var i = 0; i < categories.length; i++) {
411+ tempCategory = {
412+ "term" : categories.eq(i).attr("term"),
413+ "label" : categories.eq(i).attr("label")
414+ };
415+ categoryList.push(tempCategory);
416+ }
417+ this.category = categoryList;
418+
419+ if (inputValidateMode == 1) {
420+ this.content = this.content.replace(/[\r\n]|\r\n/g, "");
421+ this.content = this.content.replace(/<br[ \/]*>/ig, "\n");
422+ this.content = this.content.replace(/^[ \t]*/mg, "");
423+ }
424+}
425+
426+/**
427+ * グローバル変数からXMLを生成し、返却します
428+ * @param {FeedInfo} finfo
429+ * @param {Entry[]} elist
430+ */
431+function toXml(finfo, elist) {
432+ var stringBuffer = [];
433+
434+ stringBuffer.push('<?xml version="1.0" encoding="utf-8"?>');
435+ stringBuffer.push('<feed xml:lang="ja-jp" xmlns="http://www.w3.org/2005/Atom">');
436+ stringBuffer.push('');
437+
438+ stringBuffer.push('<title type="text">' + finfo.title + '</title>');
439+ stringBuffer.push('<subtitle type="text">' + finfo.subtitle + '</subtitle>');
440+ stringBuffer.push('<link rel="self" type="application/atom+xml" href="' + finfo.self + '" />');
441+ stringBuffer.push('<link rel="alternate" type="text/html" href="' + finfo.alternate + '" />');
442+ stringBuffer.push('<updated>' + getDate() + '</updated>');
443+ stringBuffer.push('<id>' + finfo.id + '</id>');
444+ stringBuffer.push('<rights type="text">' + finfo.rights + '</rights>');
445+ stringBuffer.push('<author>');
446+ stringBuffer.push(' <name>' + finfo.authorname + '</name>');
447+ stringBuffer.push(' <email>' + finfo.authoremail + '</email>');
448+ stringBuffer.push('</author>');
449+ stringBuffer.push('');
450+
451+ for (var i = 0; i < elist.length; i++) {
452+ var temp_content;
453+ if (document.getElementById("feedblog_addcontentbr").checked) {
454+ temp_content = xmlAttrContentEscape(convertContent(elist[i].content));
455+ } else {
456+ temp_content = xmlAttrContentEscape(elist[i].content);
457+ }
458+
459+ stringBuffer.push('<entry>');
460+ stringBuffer.push('<id>' + elist[i].id + '</id>');
461+ stringBuffer.push('<title>' + elist[i].title + '</title>');
462+ stringBuffer.push('<summary>' + elist[i].summary + '</summary>');
463+ stringBuffer.push('<published>' + elist[i].published + '</published>');
464+ stringBuffer.push('<updated>' + elist[i].updated + '</updated>');
465+ stringBuffer.push('<link href="' + elist[i].link + '" />');
466+ stringBuffer.push('<content type="html">' + temp_content + '</content>');
467+
468+ for (var j = 0; j < elist[i].category.length; j++) {
469+ var tmpElist = elist[i].category[j];
470+ stringBuffer.push('<category term="' + tmpElist["term"] + '" label="' + tmpElist["label"] + '"/>');
471+ }
472+
473+ stringBuffer.push('</entry>');
474+ stringBuffer.push('');
475+ }
476+
477+ stringBuffer.push('</feed>');
478+
479+ return stringBuffer.join("\n");
480+}
481+
482+/**
483+ * <content>要素の変換を行います
484+ * @param {String} content
485+ */
486+function convertContent(content) {
487+ if (document.getElementById("feedblog_addcontentbr").checked) {
488+ content = content.replace(/[\n\r]|\r\n/g, "<br>\n");
489+ } else {
490+ content = content.replace(/<br>/ig, "\n");
491+ }
492+
493+ return content;
494+}
495+
496+/**
497+ * XMLのエスケープを行う関数
498+ * @param {String} str エスケープを行う文字列
499+ */
500+function xmlAttrContentEscape(str) {
501+ return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/^[ ]+/mg, "&nbsp;").replace(/^[\t]+/mg, "");
502+}
503+
504+/**
505+ * XMLの逆エスケープを行う関数
506+ * @param {String} str 逆エスケープを行う文字列
507+ */
508+function xmlAttrContentUnescape(str) {
509+ return str.replace(/^[\t]+/mg, "").replace(/^[ ]+/mg, "&nbsp;").replace(/&quot;/g, '"').replace(/&gt;/g, ">").replace(/&lt;/g, "<").replace(/&amp;/g, "&");
510+}
511+
512+/**
513+ * jQueryでのパネル開閉を制御します
514+ * @param {String} id 開閉するオブジェクトのid
515+ */
516+function closePanel(id) {
517+ $("#" + id).slideToggle();
518+}
519+
520+/**
521+ * エラー画面を表示します
522+ */
523+function showError() {
524+ alert("XMLファイルのローディング中にエラーが発生しました!");
525+}
526+
527+/**
528+ * RFC3339形式の日時を出力します
529+ */
530+function getDate() {
531+ var dNow = new Date();
532+ var sYear = dNow.getFullYear();
533+ var sMonth = dNow.getMonth() + 1;
534+ var sDate = dNow.getDate();
535+ var sHour = dNow.getHours();
536+ var sMinute = dNow.getMinutes();
537+ var sSecond = dNow.getSeconds();
538+
539+ // 10以下の時は頭に"0"を挿入
540+ if (sMonth < 10)
541+ sMonth = "0" + sMonth;
542+ if (sDate < 10)
543+ sDate = "0" + sDate;
544+ if (sHour < 10)
545+ sHour = "0" + sHour;
546+ if (sMinute < 10)
547+ sMinute = "0" + sMinute;
548+ if (sSecond < 10)
549+ sSecond = "0" + sSecond;
550+
551+ return sYear + "-" + sMonth + "-" + sDate + "T" + sHour + ":" + sMinute + ":" + sSecond + "+09:00";
552+}
553+
554+/**
555+ * タグ情報一覧を返却する関数です
556+ */
557+function getTags() {
558+ var tagList = [];
559+ var tagListHtml = $("*[name=tag]");
560+ var tagTemp = {};
561+ for (var i = 0; i < tagListHtml.length; i++) {
562+ // 各tag要素を取得して配列に格納
563+ if (tagListHtml.eq(i).val() != "") {
564+ tagTemp = {
565+ "term" : tagListHtml.eq(i).val(),
566+ "label" : tagListHtml.eq(i).find(":selected").text()
567+ };
568+ tagList.push(tagTemp);
569+ }
570+ }
571+
572+ return tagList;
573+}
574+
575+/**
576+ * デフォルトのタグ一覧を取得する関数です
577+ */
578+function getDefaultTags() {
579+ // デフォルトのタグ一覧を取得
580+ // $term1,$label1|$term2,$label2 ... で定義されている
581+ var plainTagText = $("#feedblog_tagdefine").val();
582+ var tagList = [];
583+ var tagTemp = {};
584+ // 値が空のタグを初期値として先頭に追加
585+ tagList.push({
586+ "term" : "",
587+ "label" : "タグを選択してください"
588+ });
589+ for (var i = 0; i < plainTagText.split('|').length; i++) {
590+ // 各tag要素を取得して配列に格納
591+ tagTemp = {
592+ "term" : plainTagText.split('|')[i].split(',')[0],
593+ "label" : plainTagText.split('|')[i].split(',')[1]
594+ };
595+ tagList.push(tagTemp);
596+ }
597+
598+ return tagList;
599+}
600+
601+/**
602+ * HTMLにタグ選択用のセレクトボックスを追加します
603+ */
604+function addTagSelectBox() {
605+ var tagList = getDefaultTags();
606+ var addHtml = [];
607+
608+ addHtml.push('<select name="tag">');
609+ for (var i = 0; i < tagList.length; i++) {
610+ addHtml.push('<option value="' + tagList[i]["term"] + '">' + tagList[i]["label"] + '</option>');
611+ }
612+ addHtml.push('</select>');
613+
614+ $("#entry_category").append(addHtml.join('') + "<br>");
615+}
616+
617+/**
618+ * 現在のカテゴリーオブジェクトに従いセレクトボックスを追加します
619+ */
620+function addTagSelectBoxFromCategory(categoryList) {
621+ $("#entry_category").html("");
622+ for (var j = 0; j < categoryList.length; j++) {
623+ // 選択対象を取得
624+ var selectedTagTerm = categoryList[j]["term"];
625+ var selectedTagLabel = categoryList[j]["label"];
626+ var selectedTagSetFlag = false;
627+
628+ var tagList = getDefaultTags();
629+ var addHtml = [];
630+ addHtml.push('<select name="tag">');
631+ for (var i = 0; i < tagList.length; i++) {
632+ if (tagList[i]["term"] == selectedTagTerm && tagList[i]["label"] == selectedTagLabel) {
633+ addHtml.push('<option value="' + tagList[i]["term"] + '" selected="selected">' + tagList[i]["label"] + '</option>');
634+ selectedTagSetFlag = true;
635+ } else {
636+ addHtml.push('<option value="' + tagList[i]["term"] + '">' + tagList[i]["label"] + '</option>');
637+ }
638+ }
639+
640+ if (!selectedTagSetFlag) {
641+ addHtml.push('<option value="' + selectedTagTerm + '" selected="selected">' + selectedTagLabel + '</option>');
642+ }
643+
644+ addHtml.push('</select>');
645+ $("#entry_category").append(addHtml.join('') + "<br>");
646+ }
647+}
--- a/js/lunardial/feedblog_search.js
+++ b/js/lunardial/feedblog_search.js
@@ -1,696 +1,696 @@
1-/**
2- * FeedBlog SearchScript
3- *
4- * @copyright 2013 FeedBlog Project (http://sourceforge.jp/projects/feedblog/)
5- * @author Kureha Hisame (http://lunardial.sakura.ne.jp/) & Yui Naruse (http://airemix.com/)
6- * @since 2009/02/27
7- * @version 4.2.0.0
8- */
9-
10-// ブログ本体のHTMLファイルのURL
11-var mainPageUrl;
12-
13-// 検索用ページURL
14-var searchPageUrl;
15-
16-// 最新の記事を示すパスへの文字列
17-var latestXml;
18-
19-// ログのリストが書かれたXMLのファイルパス
20-var logXmlUrl;
21-
22-// 一画面あたりの表示記事数
23-var showLength;
24-
25-/**
26- * XMLファイルから読み込んだファイルのバリデートモード
27- * 0 = 改行コード部分に<br/>を挿入
28- * 1 = 改行コード部分に<br/>を挿入しない
29- */
30-var validateMode;
31-
32-// 検索結果をメモリ上に保持する変数です
33-var loadedEntries;
34-
35-// fetchEntries 用のセマフォ
36-var fetchEntriesSemaphore = new Semaphore();
37-
38-// 現在の検索語のキャッシュ
39-var currentSearchWords;
40-
41-// ログのファイルリストを格納するグローバル変数です
42-var logData;
43-
44-// コンボボックスのオブジェクトを格納するグローバル変数です
45-var comboBox;
46-
47-// URL末尾用文字列(スクリプトを開いた瞬間のミリ秒を記録)
48-var urlSuffix;
49-
50-/**
51- * 記事を実際に生成します。この部分を編集することでデザインを変更可能です。
52- * @param {Entry} entry 記事の情報が代入されたEntryオブジェクト
53- * @param {String} drawitem 「本文」を描画すべきパネルのDIV要素のid
54- * @param {String} renderto 「タイトル・更新日時・本文」の1日分の記事データを最終的に描画すべきパネルのDIV要素のid
55- * @param {String} closed (Ext jsパネルオプション)記事をクローズ状態で生成するか否か
56- */
57-function generatePanel(entry, drawitem, renderto, closed) {
58- // プラグインを実行
59- if ( typeof ($("#" + renderto).feedblog_contents_plugin) == "function") {
60- $("#" + renderto).feedblog_contents_plugin({
61- entry : entry
62- });
63- }
64-
65- // HTML用の配列を用意する
66- var htmlBuffer = [];
67-
68- // 内部的に描画先IDを生成
69- var feedblogContentId = "" + renderto + "_content_div";
70-
71- // 各要素をオブジェクトに描画
72- $("#" + drawitem).html(entry.content);
73-
74- // ヘッダパネルを生成 class= .feedblog_header
75- htmlBuffer.push("<div class='feedblog_header' onclick='closePanel(\"" + feedblogContentId + "\")'><span>" + entry.title + "</span></div>" +
76-
77- // 本体記事を作成 class= .feedblog_content
78- "<div class='feedblog_content' id='" + feedblogContentId + "'><span>" + document.getElementById(renderto).innerHTML + "</span></div>");
79-
80- // 最終描画実施
81- $("#" + renderto).html(htmlBuffer.join(""));
82-}
83-
84-/**
85- * システム表示画面を実際に生成します。この部分を編集することでデザインを変更可能です。
86- * @param {Entry} entry 記事の情報が代入されたEntryオブジェクト
87- * @param {String} drawitem パネルの本文を格納したDIV要素のid
88- * @param {String} renderto 「タイトル・更新日時・本文」の1日分の記事データを焼き付けるDIV要素のid
89- * @param {String} closed (Ext jsパネルオプション)記事をクローズ状態で生成するか否か
90- */
91-function generateSystemPanel(entry, drawitem, renderto, closed) {
92- // HTMLを生成する
93- var htmlBuffer = [];
94-
95- // 描画先IDを生成
96- var feedblogContentId = "" + renderto + "_content_div";
97-
98- // 各要素をオブジェクトに描画
99- $("#" + drawitem).html(entry.content);
100-
101- // ヘッダパネルを生成 class= .feedblog_header
102- htmlBuffer.push("<div class='feedblog_header' onclick='closePanel(\"" + feedblogContentId + "\")'><span>" + entry.title + "</span></div>" +
103-
104- // 本体記事を作成 class= .feedblog_content
105- "<div class='feedblog_content' id='" + feedblogContentId + "'><span>" + document.getElementById(renderto).innerHTML + "</span></div>");
106-
107- $("#" + renderto).html(htmlBuffer.join(""));
108-}
109-
110-/**
111- * 検索フォーム及び結果表示フォームを生成するメソッドです。
112- */
113-function generateForm() {
114- var formBuffer = [];
115- formBuffer.push("<form class='feedblog_searchform' onsubmit='javascript: searchDiary(); return false;'>▼ 検索語句を入力してください (語句を半角で区切るとAND、|で区切るとORで検索します)<br/>");
116- formBuffer.push("<input type='text' id='feedblog_searchword' class='feedblog_searchword'><input type='submit' id='feedblog_execsearch' value='検索'><br/>");
117- formBuffer.push("<input type='checkbox' id='feedblog_regexpOptionI' class='feedblog_regexpOptionI' checked='checked'/><label class='feedblog_searchform' for='feedblog_regexpOptionI'>大文字、小文字を区別しない</label><br/>");
118- formBuffer.push("<br/>");
119- formBuffer.push("▼ 検索対象ログ選択<br/>");
120- formBuffer.push("<div id='feedblog_logselecter'></div>");
121- formBuffer.push("<input type='checkbox' id='feedblog_allsearchcheck' class='feedblog_allsearchcheck' checked='checked'/>");
122- formBuffer.push("<label class='feedblog_searchform' for='feedblog_allsearchcheck'>すべてのログに対して検索を行う</label>");
123- formBuffer.push("<br/>");
124- formBuffer.push("</form>");
125- $("#feedblog_searchform").html(formBuffer.join(""));
126-
127- var resultAreaBuffer = "<div class='feedblog_result_status'></div>";
128-
129- $("#feedblog_resultwritearea").html(resultAreaBuffer);
130-}
131-
132-/**
133- * 全ての定数を取得・セットします
134- */
135-function initialize() {
136- // 初期値をhiddenパラメータより読み込みます
137- mainPageUrl = $("#feedblog_mainpageurl").val();
138- searchPageUrl = $("#feedblog_searchpageurl").val();
139- latestXml = $("#feedblog_latestxml").val();
140- logXmlUrl = $("#feedblog_loglistxmlurl").val();
141- showLength = parseInt($("#feedblog_showlength").val());
142- if (isNaN(showLength)) {
143- showLength = 1;
144- }
145- validateMode = $("#feedblog_validatemode").val();
146-
147- // 初期値を設定します
148- urlSuffix = +new Date();
149-
150- // 必要な環境を確認します
151- var errorBuf = [];
152- // 変数確認
153- if (mainPageUrl === undefined) {
154- errorBuf.push("設定値「feedblog_mainpageurl」が欠落しています。");
155- }
156- if (searchPageUrl === undefined) {
157- errorBuf.push("設定値「feedblog_searchpageurl」が欠落しています。");
158- }
159- if (latestXml === undefined) {
160- errorBuf.push("設定値「feedblog_latestxml」が欠落しています。");
161- }
162- if (logXmlUrl === undefined) {
163- errorBuf.push("設定値「feedblog_loglistxmlurl」が欠落しています。");
164- }
165- if (showLength === undefined) {
166- errorBuf.push("設定値「feedblog_showlength」が欠落しています。");
167- }
168- if (validateMode === undefined) {
169- errorBuf.push("設定値「feedblog_validatemode」が欠落しています。");
170- }
171- // SHA-1関数確認
172- try {
173- if ( typeof (CryptoJS.SHA1) != "function") {
174- errorBuf.push("crypt-jsモジュール(hmac-sha1.js)が読み込まれていません。");
175- }
176- } catch (ex) {
177- errorBuf.push("crypt-jsモジュール(hmac-sha1.js)が読み込まれていません。");
178- }
179-
180- // 描画エリアチェック
181- if ($("#feedblog_searchform").length == 0) {
182- errorBuf.push("描画エリア「feedblog_searchform」が存在しません。");
183- }
184- if ($("#feedblog_resultwritearea").length == 0) {
185- errorBuf.push("描画エリア「feedblog_resultwritearea」が存在しません。");
186- }
187- if ($("#feedblog_writearea").length == 0) {
188- errorBuf.push("描画エリア「feedblog_writearea」が存在しません。");
189- }
190-
191- // エラーがある場合は以降の処理を継続しない
192- if (errorBuf.length > 0) {
193- alert("初期設定値に誤りがあります。\n詳細:\n" + errorBuf.join("\n"));
194- return false;
195- }
196-
197- return true;
198-}
199-
200-/**
201- * jQueryへのイベント登録です
202- */
203-$(document).ready(function() {
204- // 初期処理を実施
205- if (!initialize()) {
206- return false;
207- }
208-
209- // ログ一覧のXMLをロードします
210- logXMLLoader();
211-
212- // 各種パネルを生成します
213- generateForm();
214-});
215-
216-/**
217- * jQueryでのパネル開閉を制御します
218- */
219-function closePanel(id) {
220- $("#" + id).slideToggle();
221-}
222-
223-/**
224- * 記事クラス
225- * @param {Object} obj entry 要素の DOM オブジェクト
226- */
227-function Entry(obj) {
228- this.title = $("title:first", obj).text();
229- if (this.title == "")
230- requiredElementError(obj, "title");
231- this.title = validateText(this.title);
232- this.content = $("content:first", obj).text();
233- this.content = validateText(this.content);
234- this.id = $("id:first", obj).text();
235- if (this.id == "")
236- requiredElementError(obj, "id");
237- this.date = $("updated:first", obj).text();
238- if (this.date == "")
239- requiredElementError(obj, "updated");
240- this.date = validateData(this.date);
241- this.category = $("category", obj);
242-}
243-
244-/**
245- * システム用記事クラス
246- * @param {Object} obj entry 要素の DOM オブジェクト
247- */
248-function SystemEntry(obj) {
249- this.title = $("title:first", obj).text();
250- this.title = validateText(this.title);
251- this.content = $("content:first", obj).text();
252- this.content = validateText(this.content);
253- this.id = $("id:first", obj).text();
254- this.date = $("updated:first", obj).text();
255- this.date = validateData(this.date);
256- this.category = $("category", obj);
257-}
258-
259-/**
260- * 記事内が単語群を全て含んでいるか
261- * @param {Array} keywords 単語群
262- * @param {String} regexpType 正規表現の検索モードを示す文字列
263- * @return {boolean} bool 全て含んでいれば true、さもなくば false
264- */
265-Entry.prototype.hasKeywords = function(keywords, regexpType) {
266- // 正規表現が一致するかという判定"のみ"を行います
267- for (var i = 0; i < keywords.length; i++) {
268- // 正規表現チェック用のオブジェクトを用意します(OR条件は一時的に条件を置換)
269- var reg = new RegExp('(?:' + keywords[i] + ')(?![^<>]*>)', regexpType);
270- // 一致しなかったらその時点で脱出
271- if (!reg.test(this.content) && !reg.test(this.title))
272- return false;
273- }
274- return true;
275-};
276-
277-/**
278- * 呼び出すとDIV:id名:feedblog_writearea上のHTMLを削除し、ロードエフェクトを表示します
279- */
280-function loadingEffect() {
281- $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
282-
283- // ロード表示用のパネルを生成
284- var systemEntry = new SystemEntry();
285- systemEntry.title = "Now Loading .....";
286- systemEntry.content = '<br/>長時間画面が切り替わらない場合はページをリロードしてください。<br/><br/>';
287- generateSystemPanel(systemEntry, "feedblog_drawitem", "feedblog_drawpanel", false);
288-
289- // 結果表示エリアをリセット
290- $("#feedblog_resultwritearea").html("<div class='feedblog_result_status'></div>");
291-}
292-
293-/**
294- * 記事データのエラー時の処理を行います
295- */
296-function showError() {
297- $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
298-
299- // エラー内容をパネルに描画
300- var systemEntry = new SystemEntry();
301- systemEntry.title = "エラー";
302- var errorContent = [];
303- errorContent.push('<br/>記事ファイル(XML)の取得に失敗しました。以下のような原因が考えられます。<br/><br/>');
304- errorContent.push('・設定値「feedblog_latestxml」に正しいパスが設定されていない。<br/>');
305- errorContent.push('・設定値「feedblog_loglistxmlurl」に正しいパスが設定されていない。<br/>');
306- errorContent.push('・ローカル環境で起動している(必ずサーバにアップロードして実行してください)。<br/>');
307- errorContent.push('<br/>');
308- systemEntry.content = errorContent.join("\n");
309- generateSystemPanel(systemEntry, "feedblog_drawitem", "feedblog_drawpanel", false);
310-
311- // 結果表示エリアをリセット
312- $("#feedblog_resultwritearea").html("<div class='feedblog_result_status'></div>");
313-}
314-
315-/**
316- * 記事データのエラー時の処理を行います
317- */
318-function notFoundError() {
319- $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
320- $("#feedblog_drawitem").html('<br/>検索条件に一致する記事は見つかりませんでした。<br/><br/>');
321-
322- // エラー内容をパネルに描画
323- var systemEntry = new SystemEntry();
324- systemEntry.title = "検索結果";
325- systemEntry.content = '<br/>検索条件に一致する記事は見つかりませんでした。<br/><br/>';
326- generateSystemPanel(systemEntry, "feedblog_drawitem", "feedblog_drawpanel", false);
327-
328- // 結果表示エリアをリセット
329- $("#feedblog_resultwritearea").html("<div class='feedblog_result_status'></div>");
330-}
331-
332-/**
333- * 記事データのエラー時の処理を行います
334- */
335-function requiredElementError(parent, name) {
336- alert(parent.ownerDocument.URL + ": 必須な要素 " + name + " が存在しないか空な " + parent.tagName + " 要素が存在します");
337-}
338-
339-/**
340- * 日付のHTML表示用バリデーション処理を行います
341- * @param {String} data RFC3339形式のdate-time文字列
342- */
343-function validateData(data) {
344- data = data.replace(/T/g, " ");
345-
346- // 秒数の小数点以下の部分はカットする
347- data = data.substring(0, 19);
348-
349- return data;
350-}
351-
352-/**
353- * 記事本文のバリデーション処理を行います
354- * @param {String} contents 記事の本文が格納されている文字列
355- */
356-function validateText(contents) {
357- // <br/>タグを挿入する
358- if (validateMode == 0) {
359- contents = contents.replace(/[\n\r]|\r\n/g, "<br />");
360- }
361-
362- return contents;
363-}
364-
365-/**
366- * XML用に要素をエスケープします
367- * @param {String} str エスケープを行いたい文字列
368- */
369-function xmlAttrContentEscape(str) {
370- // return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
371- return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/^[ ]+/mg, "&nbsp;").replace(/^[\t]+/mg, "");
372-}
373-
374-/**
375- * 記事本文に日付を付加します
376- * @param {String} contents 記事の本文が格納されている文字列
377- * @param {String} id 記事の初公開日を示す日付文字列
378- */
379-function contentsWithid(contents, id) {
380- // リンク用文末作成
381- var hashTag = '<br/><div class="feedblog_content_footer"><a href="' + xmlAttrContentEscape(mainPageUrl) + '#' + xmlAttrContentEscape(id) + '" target="_blank">- この日の記事にリンクする -<\/a><\/div>';
382- return contents + hashTag;
383-}
384-
385-/**
386- * 強調タグを追加します
387- * @param {String} word 強調したい語句
388- */
389-function emphasizeWord(word) {
390- return '<span style="background-color: red;">' + word + '</span>';
391-}
392-
393-/**
394- * 長い順に並べるための比較関数です
395- * @param {String} a 比較対象(1)
396- * @param {String} b 比較対象(2)
397- */
398-function compareLengthDecrease(a, b) {
399- a = a.length;
400- b = b.length;
401- return a > b ? -1 : a < b ? 1 : 0;
402-}
403-
404-/**
405- * セマフォ制御用のオブジェクトです
406- */
407-function Semaphore() {
408- this.id = null;
409- this.count = 0;
410- this.buf = [];
411- this.xhrs = [];
412-}
413-
414-/**
415- * セマフォ初期化用の関数です
416- */
417-Semaphore.prototype.init = function() {
418- while (this.xhrs.length > 0) {
419- this.xhrs.shift().abort();
420- }
421- this.id = Math.random();
422- this.count = 0;
423- this.buf = [];
424-};
425-
426-/**
427- * ログファイル選択用のコンボボックスをid名:feedblog_logselecterに生成します
428- */
429-function logXMLLoader() {
430- // ログ用のXMLを読み込みます
431- jQuery.ajax({
432- url : logXmlUrl + '?time=' + urlSuffix,
433- method : "GET",
434- error : showError,
435- success : function(xmlData) {
436- var separateTag = xmlData.getElementsByTagName("file");
437- logData = new Array(separateTag.length);
438-
439- // 読み込んだ要素をStoreに格納して表示
440- var boxBuffer = [];
441- boxBuffer.push("<select class='feedblog_logselecter' id='feedblog_logbox'>");
442- for (var i = 0; i < separateTag.length; i++) {
443- boxBuffer.push("<option value='" + separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue + "'>" + separateTag[i].getElementsByTagName("display")[0].firstChild.nodeValue + "</option>");
444- logData[i] = separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue;
445- }
446- boxBuffer.push("</select>");
447-
448- // コンボボックス要素を生成
449- $("#feedblog_logselecter").html(boxBuffer.join(""));
450- }
451- });
452-}
453-
454-/**
455- * 検索単語を取得します
456- */
457-function getSearchWords() {
458- var searchWord = document.getElementById("feedblog_searchword").value;
459- if (searchWord == "")
460- return null;
461- var searchWords = [];
462-
463- // 検索単語をサニタイジングします
464- // HTMLのメタ文字
465- searchWord = xmlAttrContentEscape(searchWord);
466- // 正規表現のメタ文字
467- searchWord = searchWord.replace(/([$()*+.?\[\\\]^{}])/g, '\\$1');
468- // 半角スペースで配列に分割
469- searchWords = searchWord.replace(/^\s+|\+$/g, '').split(/\s+/);
470- // 正規表現の選択を長い順に並び替えます(AND条件)
471- searchWords.sort(compareLengthDecrease);
472-
473- return searchWords.length == 0 ? null : searchWords;
474-}
475-
476-/**
477- * 文章内を特定の単語で検索し、一致した部分を強調表示タグで置き換えます
478- * @param {String} searchWord 探索する単語
479- * @param {String} plainText 探索を行う文章
480- * @param {String} regexpType 正規表現の検索モードを示す文字列
481- */
482-function complexEmphasize(searchWord, plainText, regexpType) {
483- // 正規表現の選択を長い順に並び替える
484- searchWord = searchWord.split('|').sort(compareLengthDecrease).join('|');
485- // タグの内側でないことを確認する正規表現を追加
486- var pattern = new RegExp('(?:' + searchWord + ')(?![^<>]*>)', regexpType);
487-
488- var result = [];
489- var currentIndex = -1;
490- // 現在マッチしている部分文字列の開始位置
491- var currentLastIndex = -1;
492- // 現在マッチしている部分文字列の、現在の末尾
493- var m;
494- // 正規表現マッチの結果配列
495- while ( m = pattern.exec(plainText)) {
496- if (m.index > currentLastIndex) {
497- // 新しい部分文字列へのマッチが始まったので、そこまでの文字列をバッファに書き出す
498- if (currentIndex < currentLastIndex)
499- result.push(emphasizeWord(plainText.substring(currentIndex, currentLastIndex)));
500- result.push(plainText.substring(currentLastIndex, m.index));
501- // 開始位置の更新
502- currentIndex = m.index;
503- }
504- // 末尾位置を更新
505- currentLastIndex = pattern.lastIndex;
506- // 次の正規表現マッチは今マッチした文字の次の文字から
507- pattern.lastIndex = m.index + 1;
508- }
509- // 残った文字列を書き出す
510- if (currentIndex < currentLastIndex)
511- result.push(emphasizeWord(plainText.substring(currentIndex, currentLastIndex)));
512- result.push(plainText.substring(currentLastIndex));
513-
514- // 結合して返す
515- return result.join('');
516-}
517-
518-/**
519- * 検索結果を分割して表示します(2回目以降呼び出し)
520- * @param {int} showLength 一回の画面に表示する記事数
521- * @param {int} startIndex 表示を開始する記事のインデックス
522- */
523-function showEntriesRange(showLength, startIndex) {
524- // メモリ上から記事データをロード
525- var entries = loadedEntries;
526-
527- // 表示インデックスが範囲外の場合はエラーパネルを表示して終了
528- if (startIndex < 0 || entries.length <= startIndex) {
529- showError();
530- return;
531- }
532-
533- var stringBuffer = [];
534-
535- // リミッターを設定する
536- var loopLimit = (showLength + startIndex > entries.length) ? entries.length : showLength + startIndex;
537- var indexShowEntries = loopLimit + 1;
538-
539- for (var i = startIndex; i < loopLimit; i++) {
540- stringBuffer.push('<div class="feedblog_drawpanel" id="feedblog_drawpanel');
541- stringBuffer.push(i);
542- stringBuffer.push('"><div class="feedblog_drawitem" id="feedblog_drawitem');
543- stringBuffer.push(i);
544- stringBuffer.push('"><\/div><\/div>');
545- }
546- $("#feedblog_writearea").html(stringBuffer.join(''));
547-
548- stringBuffer.length = 0;
549- for ( i = startIndex; i < loopLimit; i++) {
550- var entry = entries[i];
551- generatePanel(entry, "feedblog_drawitem" + i, "feedblog_drawpanel" + i, false);
552- }
553-
554- // メニュー表示用バッファ
555- var menuBuffer = [];
556- menuBuffer.push("<div class='feedblog_pager_wrapper'>");
557- menuBuffer.push("<ul class='feedblog_pager'>");
558-
559- // ブランクエリアを挟む
560- menuBuffer.push("<li class='feedblog_pager_blank'></li>");
561-
562- // 左パネルの表示制御
563- if (startIndex - showLength >= 0) {
564- menuBuffer.push("\<li class='feedblog_pager_goback'><span class='feedblog_pager_goback' onclick='showEntriesRange(" + showLength + ", " + (startIndex - showLength) + "); return false;'>\< 前の" + showLength + "件を表示</span\></li>");
565- } else {
566- menuBuffer.push("\<li class='feedblog_pager_goback'>\< 前の" + showLength + "件を表示</a\></li>");
567- }
568-
569- // 中央のパネルの表示制御
570- menuBuffer.push("<li class='feedblog_pager_center'>[ ");
571- var menuNumbers = Math.ceil(entries.length / showLength);
572- for ( i = 0; i < menuNumbers; i++) {
573- if (startIndex / showLength == i) {
574- menuBuffer.push(i + " ");
575- } else {
576- menuBuffer.push("<span class='feedblog_pager_center' onclick='showEntriesRange(" + showLength + ", " + (i * showLength) + "); return false;'>");
577- menuBuffer.push(i);
578- menuBuffer.push("</span> ");
579- }
580- }
581- menuBuffer.push("]</li>");
582-
583- // 右パネルの表示制御
584- if (entries.length > startIndex + showLength) {
585- menuBuffer.push("\<li class='feedblog_pager_gonext'><span class='feedblog_pager_gonext' onclick='showEntriesRange(" + showLength + ", " + (startIndex + showLength) + "); return false;'>次の" + showLength + "件を表示 \></span\></li>");
586- } else {
587- menuBuffer.push("\<li class='feedblog_pager_gonext'>次の" + showLength + "件を表示 \></li>");
588- }
589-
590- // ブランクエリアを挟む
591- menuBuffer.push("<li class='feedblog_pager_blank'></li>");
592-
593- menuBuffer.push("</ul></div>");
594-
595- // 検索結果を表示します
596- $("#feedblog_resultwritearea").html("<div class='feedblog_result_status'>" + entries.length + "件の記事が該当しました / " + (startIndex + 1) + "~" + loopLimit + "件目までを表示中<br/></div>" + menuBuffer.join(""));
597-}
598-
599-/**
600- * 検索時のjQuery.ajaxのcallback関数
601- */
602-function fetchEntries(xmlData) {
603- // 大文字小文字を区別するかを取得します
604- var regexpOptionI = document.getElementById("feedblog_regexpOptionI");
605- var regexpType = regexpOptionI ? "ig" : "g";
606-
607- // entry要素のみを切り出します
608- var entries = xmlData.getElementsByTagName("entry");
609-
610- // entry要素の回数だけ実行します
611- for (var j = 0; j < entries.length; j++) {
612- var entry = new Entry(entries[j]);
613-
614- // 正規表現が一致した場合は、強調表現処理を行います
615- if (entry.hasKeywords(currentSearchWords, regexpType)) {
616- // 強調表現を実行します
617- entry.title = complexEmphasize(currentSearchWords.join("|"), entry.title, regexpType);
618- entry.content = complexEmphasize(currentSearchWords.join("|"), entry.content, regexpType);
619-
620- fetchEntriesSemaphore.buf.push(entry);
621- }
622- }
623-
624- // セマフォのカウンタを減少させます (Ajaxとの同期のため)
625- fetchEntriesSemaphore.count--;
626-
627- // 全てのログを読み終わったら表示
628- if (fetchEntriesSemaphore.count == 0) {
629- var entries = fetchEntriesSemaphore.buf;
630-
631- // 一軒も検索にヒットしなかった場合は専用のパネルを表示して終了
632- if (entries.length == 0) {
633- notFoundError();
634- return;
635- }
636-
637- // entryを更新時間でソート
638- entries = entries.sort(function(a, b) {
639- a = a.updated;
640- b = b.updated;
641- return a > b ? -1 : a < b ? 1 : 0;
642- });
643-
644- loadedEntries = entries;
645-
646- // 表示ロジック呼び出し
647- showEntriesRange(showLength, 0);
648- }
649-}
650-
651-/**
652- * 「探索」ボタンを押されたときに呼び出されるメソッドです
653- */
654-function searchDiary() {
655- // 検索結果フィールドをクリアします
656- document.getElementById("feedblog_writearea").innerHTML = "";
657-
658- // 探索したい単語を取得します
659- currentSearchWords = getSearchWords();
660- if (!currentSearchWords) {
661- alert("検索対象の単語が入力されていません");
662- // 検索結果の欄をリセットします
663- $("#feedblog_resultwritearea").html("<div class='feedblog_result_status'></div>");
664- return;
665- }
666-
667- // ロードエフェクトを表示します
668- loadingEffect();
669-
670- // 全チェックを取得します
671- var allCheckedFlag = document.getElementById("feedblog_allsearchcheck").checked;
672-
673- // セマフォを初期化
674- fetchEntriesSemaphore.init();
675- // 記事が全検索モードか否かをチェックします
676- var urls = null;
677- if (allCheckedFlag == true) {
678- // 全記事検索なので全てのログのURL
679- urls = logData;
680- } else {
681- // 単独記事探索なので、選んだログのURL
682- urls = [document.getElementById("feedblog_logbox").options[document.getElementById("feedblog_logbox").selectedIndex].value];
683- }
684- fetchEntriesSemaphore.urls = urls;
685- fetchEntriesSemaphore.count = urls.length;
686- for ( i = 0; i < urls.length; i++) {
687- var xhr = new jQuery.ajax({
688- url : urls[i] + '?time=' + urlSuffix,
689- method : "GET",
690- async : true,
691- success : fetchEntries,
692- error : showError
693- });
694- fetchEntriesSemaphore.xhrs.push(xhr);
695- }
696-}
1+/**
2+ * FeedBlog SearchScript
3+ *
4+ * @copyright 2013 FeedBlog Project (http://sourceforge.jp/projects/feedblog/)
5+ * @author Kureha Hisame (http://lunardial.sakura.ne.jp/) & Yui Naruse (http://airemix.com/)
6+ * @since 2009/02/27
7+ * @version 4.2.1.0
8+ */
9+
10+// ブログ本体のHTMLファイルのURL
11+var mainPageUrl;
12+
13+// 検索用ページURL
14+var searchPageUrl;
15+
16+// 最新の記事を示すパスへの文字列
17+var latestXml;
18+
19+// ログのリストが書かれたXMLのファイルパス
20+var logXmlUrl;
21+
22+// 一画面あたりの表示記事数
23+var showLength;
24+
25+/**
26+ * XMLファイルから読み込んだファイルのバリデートモード
27+ * 0 = 改行コード部分に<br/>を挿入
28+ * 1 = 改行コード部分に<br/>を挿入しない
29+ */
30+var validateMode;
31+
32+// 検索結果をメモリ上に保持する変数です
33+var loadedEntries;
34+
35+// fetchEntries 用のセマフォ
36+var fetchEntriesSemaphore = new Semaphore();
37+
38+// 現在の検索語のキャッシュ
39+var currentSearchWords;
40+
41+// ログのファイルリストを格納するグローバル変数です
42+var logData;
43+
44+// コンボボックスのオブジェクトを格納するグローバル変数です
45+var comboBox;
46+
47+// URL末尾用文字列(スクリプトを開いた瞬間のミリ秒を記録)
48+var urlSuffix;
49+
50+/**
51+ * 記事を実際に生成します。この部分を編集することでデザインを変更可能です。
52+ * @param {Entry} entry 記事の情報が代入されたEntryオブジェクト
53+ * @param {String} drawitem 「本文」を描画すべきパネルのDIV要素のid
54+ * @param {String} renderto 「タイトル・更新日時・本文」の1日分の記事データを最終的に描画すべきパネルのDIV要素のid
55+ * @param {String} closed (Ext jsパネルオプション)記事をクローズ状態で生成するか否か
56+ */
57+function generatePanel(entry, drawitem, renderto, closed) {
58+ // プラグインを実行
59+ if ( typeof ($("#" + renderto).feedblog_contents_plugin) == "function") {
60+ $("#" + renderto).feedblog_contents_plugin({
61+ entry : entry
62+ });
63+ }
64+
65+ // HTML用の配列を用意する
66+ var htmlBuffer = [];
67+
68+ // 内部的に描画先IDを生成
69+ var feedblogContentId = "" + renderto + "_content_div";
70+
71+ // 各要素をオブジェクトに描画
72+ $("#" + drawitem).html(entry.content);
73+
74+ // ヘッダパネルを生成 class= .feedblog_header
75+ htmlBuffer.push("<div class='feedblog_header' onclick='closePanel(\"" + feedblogContentId + "\")'><span>" + entry.title + "</span></div>" +
76+
77+ // 本体記事を作成 class= .feedblog_content
78+ "<div class='feedblog_content' id='" + feedblogContentId + "'><span>" + document.getElementById(renderto).innerHTML + "</span></div>");
79+
80+ // 最終描画実施
81+ $("#" + renderto).html(htmlBuffer.join(""));
82+}
83+
84+/**
85+ * システム表示画面を実際に生成します。この部分を編集することでデザインを変更可能です。
86+ * @param {Entry} entry 記事の情報が代入されたEntryオブジェクト
87+ * @param {String} drawitem パネルの本文を格納したDIV要素のid
88+ * @param {String} renderto 「タイトル・更新日時・本文」の1日分の記事データを焼き付けるDIV要素のid
89+ * @param {String} closed (Ext jsパネルオプション)記事をクローズ状態で生成するか否か
90+ */
91+function generateSystemPanel(entry, drawitem, renderto, closed) {
92+ // HTMLを生成する
93+ var htmlBuffer = [];
94+
95+ // 描画先IDを生成
96+ var feedblogContentId = "" + renderto + "_content_div";
97+
98+ // 各要素をオブジェクトに描画
99+ $("#" + drawitem).html(entry.content);
100+
101+ // ヘッダパネルを生成 class= .feedblog_header
102+ htmlBuffer.push("<div class='feedblog_header' onclick='closePanel(\"" + feedblogContentId + "\")'><span>" + entry.title + "</span></div>" +
103+
104+ // 本体記事を作成 class= .feedblog_content
105+ "<div class='feedblog_content' id='" + feedblogContentId + "'><span>" + document.getElementById(renderto).innerHTML + "</span></div>");
106+
107+ $("#" + renderto).html(htmlBuffer.join(""));
108+}
109+
110+/**
111+ * 検索フォーム及び結果表示フォームを生成するメソッドです。
112+ */
113+function generateForm() {
114+ var formBuffer = [];
115+ formBuffer.push("<form class='feedblog_searchform' onsubmit='javascript: searchDiary(); return false;'>▼ 検索語句を入力してください (語句を半角で区切るとAND、|で区切るとORで検索します)<br/>");
116+ formBuffer.push("<input type='text' id='feedblog_searchword' class='feedblog_searchword'><input type='submit' id='feedblog_execsearch' value='検索'><br/>");
117+ formBuffer.push("<input type='checkbox' id='feedblog_regexpOptionI' class='feedblog_regexpOptionI' checked='checked'/><label class='feedblog_searchform' for='feedblog_regexpOptionI'>大文字、小文字を区別しない</label><br/>");
118+ formBuffer.push("<br/>");
119+ formBuffer.push("▼ 検索対象ログ選択<br/>");
120+ formBuffer.push("<div id='feedblog_logselecter'></div>");
121+ formBuffer.push("<input type='checkbox' id='feedblog_allsearchcheck' class='feedblog_allsearchcheck' checked='checked'/>");
122+ formBuffer.push("<label class='feedblog_searchform' for='feedblog_allsearchcheck'>すべてのログに対して検索を行う</label>");
123+ formBuffer.push("<br/>");
124+ formBuffer.push("</form>");
125+ $("#feedblog_searchform").html(formBuffer.join(""));
126+
127+ var resultAreaBuffer = "<div class='feedblog_result_status'></div>";
128+
129+ $("#feedblog_resultwritearea").html(resultAreaBuffer);
130+}
131+
132+/**
133+ * 全ての定数を取得・セットします
134+ */
135+function initialize() {
136+ // 初期値をhiddenパラメータより読み込みます
137+ mainPageUrl = $("#feedblog_mainpageurl").val();
138+ searchPageUrl = $("#feedblog_searchpageurl").val();
139+ latestXml = $("#feedblog_latestxml").val();
140+ logXmlUrl = $("#feedblog_loglistxmlurl").val();
141+ showLength = parseInt($("#feedblog_showlength").val());
142+ if (isNaN(showLength)) {
143+ showLength = 1;
144+ }
145+ validateMode = $("#feedblog_validatemode").val();
146+
147+ // 初期値を設定します
148+ urlSuffix = +new Date();
149+
150+ // 必要な環境を確認します
151+ var errorBuf = [];
152+ // 変数確認
153+ if (mainPageUrl === undefined) {
154+ errorBuf.push("設定値「feedblog_mainpageurl」が欠落しています。");
155+ }
156+ if (searchPageUrl === undefined) {
157+ errorBuf.push("設定値「feedblog_searchpageurl」が欠落しています。");
158+ }
159+ if (latestXml === undefined) {
160+ errorBuf.push("設定値「feedblog_latestxml」が欠落しています。");
161+ }
162+ if (logXmlUrl === undefined) {
163+ errorBuf.push("設定値「feedblog_loglistxmlurl」が欠落しています。");
164+ }
165+ if (showLength === undefined) {
166+ errorBuf.push("設定値「feedblog_showlength」が欠落しています。");
167+ }
168+ if (validateMode === undefined) {
169+ errorBuf.push("設定値「feedblog_validatemode」が欠落しています。");
170+ }
171+ // SHA-1関数確認
172+ try {
173+ if ( typeof (CryptoJS.SHA1) != "function") {
174+ errorBuf.push("crypt-jsモジュール(hmac-sha1.js)が読み込まれていません。");
175+ }
176+ } catch (ex) {
177+ errorBuf.push("crypt-jsモジュール(hmac-sha1.js)が読み込まれていません。");
178+ }
179+
180+ // 描画エリアチェック
181+ if ($("#feedblog_searchform").length == 0) {
182+ errorBuf.push("描画エリア「feedblog_searchform」が存在しません。");
183+ }
184+ if ($("#feedblog_resultwritearea").length == 0) {
185+ errorBuf.push("描画エリア「feedblog_resultwritearea」が存在しません。");
186+ }
187+ if ($("#feedblog_writearea").length == 0) {
188+ errorBuf.push("描画エリア「feedblog_writearea」が存在しません。");
189+ }
190+
191+ // エラーがある場合は以降の処理を継続しない
192+ if (errorBuf.length > 0) {
193+ alert("初期設定値に誤りがあります。\n詳細:\n" + errorBuf.join("\n"));
194+ return false;
195+ }
196+
197+ return true;
198+}
199+
200+/**
201+ * jQueryへのイベント登録です
202+ */
203+$(document).ready(function() {
204+ // 初期処理を実施
205+ if (!initialize()) {
206+ return false;
207+ }
208+
209+ // ログ一覧のXMLをロードします
210+ logXMLLoader();
211+
212+ // 各種パネルを生成します
213+ generateForm();
214+});
215+
216+/**
217+ * jQueryでのパネル開閉を制御します
218+ */
219+function closePanel(id) {
220+ $("#" + id).slideToggle();
221+}
222+
223+/**
224+ * 記事クラス
225+ * @param {Object} obj entry 要素の DOM オブジェクト
226+ */
227+function Entry(obj) {
228+ this.title = $("title:first", obj).text();
229+ if (this.title == "")
230+ requiredElementError(obj, "title");
231+ this.title = validateText(this.title);
232+ this.content = $("content:first", obj).text();
233+ this.content = validateText(this.content);
234+ this.id = $("id:first", obj).text();
235+ if (this.id == "")
236+ requiredElementError(obj, "id");
237+ this.date = $("updated:first", obj).text();
238+ if (this.date == "")
239+ requiredElementError(obj, "updated");
240+ this.date = validateData(this.date);
241+ this.category = $("category", obj);
242+}
243+
244+/**
245+ * システム用記事クラス
246+ * @param {Object} obj entry 要素の DOM オブジェクト
247+ */
248+function SystemEntry(obj) {
249+ this.title = $("title:first", obj).text();
250+ this.title = validateText(this.title);
251+ this.content = $("content:first", obj).text();
252+ this.content = validateText(this.content);
253+ this.id = $("id:first", obj).text();
254+ this.date = $("updated:first", obj).text();
255+ this.date = validateData(this.date);
256+ this.category = $("category", obj);
257+}
258+
259+/**
260+ * 記事内が単語群を全て含んでいるか
261+ * @param {Array} keywords 単語群
262+ * @param {String} regexpType 正規表現の検索モードを示す文字列
263+ * @return {boolean} bool 全て含んでいれば true、さもなくば false
264+ */
265+Entry.prototype.hasKeywords = function(keywords, regexpType) {
266+ // 正規表現が一致するかという判定"のみ"を行います
267+ for (var i = 0; i < keywords.length; i++) {
268+ // 正規表現チェック用のオブジェクトを用意します(OR条件は一時的に条件を置換)
269+ var reg = new RegExp('(?:' + keywords[i] + ')(?![^<>]*>)', regexpType);
270+ // 一致しなかったらその時点で脱出
271+ if (!reg.test(this.content) && !reg.test(this.title))
272+ return false;
273+ }
274+ return true;
275+};
276+
277+/**
278+ * 呼び出すとDIV:id名:feedblog_writearea上のHTMLを削除し、ロードエフェクトを表示します
279+ */
280+function loadingEffect() {
281+ $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
282+
283+ // ロード表示用のパネルを生成
284+ var systemEntry = new SystemEntry();
285+ systemEntry.title = "Now Loading .....";
286+ systemEntry.content = '<br/>長時間画面が切り替わらない場合はページをリロードしてください。<br/><br/>';
287+ generateSystemPanel(systemEntry, "feedblog_drawitem", "feedblog_drawpanel", false);
288+
289+ // 結果表示エリアをリセット
290+ $("#feedblog_resultwritearea").html("<div class='feedblog_result_status'></div>");
291+}
292+
293+/**
294+ * 記事データのエラー時の処理を行います
295+ */
296+function showError() {
297+ $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
298+
299+ // エラー内容をパネルに描画
300+ var systemEntry = new SystemEntry();
301+ systemEntry.title = "エラー";
302+ var errorContent = [];
303+ errorContent.push('<br/>記事ファイル(XML)の取得に失敗しました。以下のような原因が考えられます。<br/><br/>');
304+ errorContent.push('・設定値「feedblog_latestxml」に正しいパスが設定されていない。<br/>');
305+ errorContent.push('・設定値「feedblog_loglistxmlurl」に正しいパスが設定されていない。<br/>');
306+ errorContent.push('・ローカル環境で起動している(必ずサーバにアップロードして実行してください)。<br/>');
307+ errorContent.push('<br/>');
308+ systemEntry.content = errorContent.join("\n");
309+ generateSystemPanel(systemEntry, "feedblog_drawitem", "feedblog_drawpanel", false);
310+
311+ // 結果表示エリアをリセット
312+ $("#feedblog_resultwritearea").html("<div class='feedblog_result_status'></div>");
313+}
314+
315+/**
316+ * 記事データのエラー時の処理を行います
317+ */
318+function notFoundError() {
319+ $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
320+ $("#feedblog_drawitem").html('<br/>検索条件に一致する記事は見つかりませんでした。<br/><br/>');
321+
322+ // エラー内容をパネルに描画
323+ var systemEntry = new SystemEntry();
324+ systemEntry.title = "検索結果";
325+ systemEntry.content = '<br/>検索条件に一致する記事は見つかりませんでした。<br/><br/>';
326+ generateSystemPanel(systemEntry, "feedblog_drawitem", "feedblog_drawpanel", false);
327+
328+ // 結果表示エリアをリセット
329+ $("#feedblog_resultwritearea").html("<div class='feedblog_result_status'></div>");
330+}
331+
332+/**
333+ * 記事データのエラー時の処理を行います
334+ */
335+function requiredElementError(parent, name) {
336+ alert(parent.ownerDocument.URL + ": 必須な要素 " + name + " が存在しないか空な " + parent.tagName + " 要素が存在します");
337+}
338+
339+/**
340+ * 日付のHTML表示用バリデーション処理を行います
341+ * @param {String} data RFC3339形式のdate-time文字列
342+ */
343+function validateData(data) {
344+ data = data.replace(/T/g, " ");
345+
346+ // 秒数の小数点以下の部分はカットする
347+ data = data.substring(0, 19);
348+
349+ return data;
350+}
351+
352+/**
353+ * 記事本文のバリデーション処理を行います
354+ * @param {String} contents 記事の本文が格納されている文字列
355+ */
356+function validateText(contents) {
357+ // <br/>タグを挿入する
358+ if (validateMode == 0) {
359+ contents = contents.replace(/[\n\r]|\r\n/g, "<br />");
360+ }
361+
362+ return contents;
363+}
364+
365+/**
366+ * XML用に要素をエスケープします
367+ * @param {String} str エスケープを行いたい文字列
368+ */
369+function xmlAttrContentEscape(str) {
370+ // return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
371+ return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/^[ ]+/mg, "&nbsp;").replace(/^[\t]+/mg, "");
372+}
373+
374+/**
375+ * 記事本文に日付を付加します
376+ * @param {String} contents 記事の本文が格納されている文字列
377+ * @param {String} id 記事の初公開日を示す日付文字列
378+ */
379+function contentsWithid(contents, id) {
380+ // リンク用文末作成
381+ var hashTag = '<br/><div class="feedblog_content_footer"><a href="' + xmlAttrContentEscape(mainPageUrl) + '#' + xmlAttrContentEscape(id) + '" target="_blank">- この日の記事にリンクする -<\/a><\/div>';
382+ return contents + hashTag;
383+}
384+
385+/**
386+ * 強調タグを追加します
387+ * @param {String} word 強調したい語句
388+ */
389+function emphasizeWord(word) {
390+ return '<span style="background-color: red;">' + word + '</span>';
391+}
392+
393+/**
394+ * 長い順に並べるための比較関数です
395+ * @param {String} a 比較対象(1)
396+ * @param {String} b 比較対象(2)
397+ */
398+function compareLengthDecrease(a, b) {
399+ a = a.length;
400+ b = b.length;
401+ return a > b ? -1 : a < b ? 1 : 0;
402+}
403+
404+/**
405+ * セマフォ制御用のオブジェクトです
406+ */
407+function Semaphore() {
408+ this.id = null;
409+ this.count = 0;
410+ this.buf = [];
411+ this.xhrs = [];
412+}
413+
414+/**
415+ * セマフォ初期化用の関数です
416+ */
417+Semaphore.prototype.init = function() {
418+ while (this.xhrs.length > 0) {
419+ this.xhrs.shift().abort();
420+ }
421+ this.id = Math.random();
422+ this.count = 0;
423+ this.buf = [];
424+};
425+
426+/**
427+ * ログファイル選択用のコンボボックスをid名:feedblog_logselecterに生成します
428+ */
429+function logXMLLoader() {
430+ // ログ用のXMLを読み込みます
431+ jQuery.ajax({
432+ url : logXmlUrl + '?time=' + urlSuffix,
433+ method : "GET",
434+ error : showError,
435+ success : function(xmlData) {
436+ var separateTag = xmlData.getElementsByTagName("file");
437+ logData = new Array(separateTag.length);
438+
439+ // 読み込んだ要素をStoreに格納して表示
440+ var boxBuffer = [];
441+ boxBuffer.push("<select class='feedblog_logselecter' id='feedblog_logbox'>");
442+ for (var i = 0; i < separateTag.length; i++) {
443+ boxBuffer.push("<option value='" + separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue + "'>" + separateTag[i].getElementsByTagName("display")[0].firstChild.nodeValue + "</option>");
444+ logData[i] = separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue;
445+ }
446+ boxBuffer.push("</select>");
447+
448+ // コンボボックス要素を生成
449+ $("#feedblog_logselecter").html(boxBuffer.join(""));
450+ }
451+ });
452+}
453+
454+/**
455+ * 検索単語を取得します
456+ */
457+function getSearchWords() {
458+ var searchWord = document.getElementById("feedblog_searchword").value;
459+ if (searchWord == "")
460+ return null;
461+ var searchWords = [];
462+
463+ // 検索単語をサニタイジングします
464+ // HTMLのメタ文字
465+ searchWord = xmlAttrContentEscape(searchWord);
466+ // 正規表現のメタ文字
467+ searchWord = searchWord.replace(/([$()*+.?\[\\\]^{}])/g, '\\$1');
468+ // 半角スペースで配列に分割
469+ searchWords = searchWord.replace(/^\s+|\+$/g, '').split(/\s+/);
470+ // 正規表現の選択を長い順に並び替えます(AND条件)
471+ searchWords.sort(compareLengthDecrease);
472+
473+ return searchWords.length == 0 ? null : searchWords;
474+}
475+
476+/**
477+ * 文章内を特定の単語で検索し、一致した部分を強調表示タグで置き換えます
478+ * @param {String} searchWord 探索する単語
479+ * @param {String} plainText 探索を行う文章
480+ * @param {String} regexpType 正規表現の検索モードを示す文字列
481+ */
482+function complexEmphasize(searchWord, plainText, regexpType) {
483+ // 正規表現の選択を長い順に並び替える
484+ searchWord = searchWord.split('|').sort(compareLengthDecrease).join('|');
485+ // タグの内側でないことを確認する正規表現を追加
486+ var pattern = new RegExp('(?:' + searchWord + ')(?![^<>]*>)', regexpType);
487+
488+ var result = [];
489+ var currentIndex = -1;
490+ // 現在マッチしている部分文字列の開始位置
491+ var currentLastIndex = -1;
492+ // 現在マッチしている部分文字列の、現在の末尾
493+ var m;
494+ // 正規表現マッチの結果配列
495+ while ( m = pattern.exec(plainText)) {
496+ if (m.index > currentLastIndex) {
497+ // 新しい部分文字列へのマッチが始まったので、そこまでの文字列をバッファに書き出す
498+ if (currentIndex < currentLastIndex)
499+ result.push(emphasizeWord(plainText.substring(currentIndex, currentLastIndex)));
500+ result.push(plainText.substring(currentLastIndex, m.index));
501+ // 開始位置の更新
502+ currentIndex = m.index;
503+ }
504+ // 末尾位置を更新
505+ currentLastIndex = pattern.lastIndex;
506+ // 次の正規表現マッチは今マッチした文字の次の文字から
507+ pattern.lastIndex = m.index + 1;
508+ }
509+ // 残った文字列を書き出す
510+ if (currentIndex < currentLastIndex)
511+ result.push(emphasizeWord(plainText.substring(currentIndex, currentLastIndex)));
512+ result.push(plainText.substring(currentLastIndex));
513+
514+ // 結合して返す
515+ return result.join('');
516+}
517+
518+/**
519+ * 検索結果を分割して表示します(2回目以降呼び出し)
520+ * @param {int} showLength 一回の画面に表示する記事数
521+ * @param {int} startIndex 表示を開始する記事のインデックス
522+ */
523+function showEntriesRange(showLength, startIndex) {
524+ // メモリ上から記事データをロード
525+ var entries = loadedEntries;
526+
527+ // 表示インデックスが範囲外の場合はエラーパネルを表示して終了
528+ if (startIndex < 0 || entries.length <= startIndex) {
529+ showError();
530+ return;
531+ }
532+
533+ var stringBuffer = [];
534+
535+ // リミッターを設定する
536+ var loopLimit = (showLength + startIndex > entries.length) ? entries.length : showLength + startIndex;
537+ var indexShowEntries = loopLimit + 1;
538+
539+ for (var i = startIndex; i < loopLimit; i++) {
540+ stringBuffer.push('<div class="feedblog_drawpanel" id="feedblog_drawpanel');
541+ stringBuffer.push(i);
542+ stringBuffer.push('"><div class="feedblog_drawitem" id="feedblog_drawitem');
543+ stringBuffer.push(i);
544+ stringBuffer.push('"><\/div><\/div>');
545+ }
546+ $("#feedblog_writearea").html(stringBuffer.join(''));
547+
548+ stringBuffer.length = 0;
549+ for ( i = startIndex; i < loopLimit; i++) {
550+ var entry = entries[i];
551+ generatePanel(entry, "feedblog_drawitem" + i, "feedblog_drawpanel" + i, false);
552+ }
553+
554+ // メニュー表示用バッファ
555+ var menuBuffer = [];
556+ menuBuffer.push("<div class='feedblog_pager_wrapper'>");
557+ menuBuffer.push("<ul class='feedblog_pager'>");
558+
559+ // ブランクエリアを挟む
560+ menuBuffer.push("<li class='feedblog_pager_blank'></li>");
561+
562+ // 左パネルの表示制御
563+ if (startIndex - showLength >= 0) {
564+ menuBuffer.push("\<li class='feedblog_pager_goback'><span class='feedblog_pager_goback' onclick='showEntriesRange(" + showLength + ", " + (startIndex - showLength) + "); return false;'>\< 前の" + showLength + "件を表示</span\></li>");
565+ } else {
566+ menuBuffer.push("\<li class='feedblog_pager_goback'>\< 前の" + showLength + "件を表示</a\></li>");
567+ }
568+
569+ // 中央のパネルの表示制御
570+ menuBuffer.push("<li class='feedblog_pager_center'>[ ");
571+ var menuNumbers = Math.ceil(entries.length / showLength);
572+ for ( i = 0; i < menuNumbers; i++) {
573+ if (startIndex / showLength == i) {
574+ menuBuffer.push(i + " ");
575+ } else {
576+ menuBuffer.push("<span class='feedblog_pager_center' onclick='showEntriesRange(" + showLength + ", " + (i * showLength) + "); return false;'>");
577+ menuBuffer.push(i);
578+ menuBuffer.push("</span> ");
579+ }
580+ }
581+ menuBuffer.push("]</li>");
582+
583+ // 右パネルの表示制御
584+ if (entries.length > startIndex + showLength) {
585+ menuBuffer.push("\<li class='feedblog_pager_gonext'><span class='feedblog_pager_gonext' onclick='showEntriesRange(" + showLength + ", " + (startIndex + showLength) + "); return false;'>次の" + showLength + "件を表示 \></span\></li>");
586+ } else {
587+ menuBuffer.push("\<li class='feedblog_pager_gonext'>次の" + showLength + "件を表示 \></li>");
588+ }
589+
590+ // ブランクエリアを挟む
591+ menuBuffer.push("<li class='feedblog_pager_blank'></li>");
592+
593+ menuBuffer.push("</ul></div>");
594+
595+ // 検索結果を表示します
596+ $("#feedblog_resultwritearea").html("<div class='feedblog_result_status'>" + entries.length + "件の記事が該当しました / " + (startIndex + 1) + "~" + loopLimit + "件目までを表示中<br/></div>" + menuBuffer.join(""));
597+}
598+
599+/**
600+ * 検索時のjQuery.ajaxのcallback関数
601+ */
602+function fetchEntries(xmlData) {
603+ // 大文字小文字を区別するかを取得します
604+ var regexpOptionI = document.getElementById("feedblog_regexpOptionI");
605+ var regexpType = regexpOptionI ? "ig" : "g";
606+
607+ // entry要素のみを切り出します
608+ var entries = xmlData.getElementsByTagName("entry");
609+
610+ // entry要素の回数だけ実行します
611+ for (var j = 0; j < entries.length; j++) {
612+ var entry = new Entry(entries[j]);
613+
614+ // 正規表現が一致した場合は、強調表現処理を行います
615+ if (entry.hasKeywords(currentSearchWords, regexpType)) {
616+ // 強調表現を実行します
617+ entry.title = complexEmphasize(currentSearchWords.join("|"), entry.title, regexpType);
618+ entry.content = complexEmphasize(currentSearchWords.join("|"), entry.content, regexpType);
619+
620+ fetchEntriesSemaphore.buf.push(entry);
621+ }
622+ }
623+
624+ // セマフォのカウンタを減少させます (Ajaxとの同期のため)
625+ fetchEntriesSemaphore.count--;
626+
627+ // 全てのログを読み終わったら表示
628+ if (fetchEntriesSemaphore.count == 0) {
629+ var entries = fetchEntriesSemaphore.buf;
630+
631+ // 一軒も検索にヒットしなかった場合は専用のパネルを表示して終了
632+ if (entries.length == 0) {
633+ notFoundError();
634+ return;
635+ }
636+
637+ // entryを更新時間でソート
638+ entries = entries.sort(function(a, b) {
639+ a = a.updated;
640+ b = b.updated;
641+ return a > b ? -1 : a < b ? 1 : 0;
642+ });
643+
644+ loadedEntries = entries;
645+
646+ // 表示ロジック呼び出し
647+ showEntriesRange(showLength, 0);
648+ }
649+}
650+
651+/**
652+ * 「探索」ボタンを押されたときに呼び出されるメソッドです
653+ */
654+function searchDiary() {
655+ // 検索結果フィールドをクリアします
656+ document.getElementById("feedblog_writearea").innerHTML = "";
657+
658+ // 探索したい単語を取得します
659+ currentSearchWords = getSearchWords();
660+ if (!currentSearchWords) {
661+ alert("検索対象の単語が入力されていません");
662+ // 検索結果の欄をリセットします
663+ $("#feedblog_resultwritearea").html("<div class='feedblog_result_status'></div>");
664+ return;
665+ }
666+
667+ // ロードエフェクトを表示します
668+ loadingEffect();
669+
670+ // 全チェックを取得します
671+ var allCheckedFlag = document.getElementById("feedblog_allsearchcheck").checked;
672+
673+ // セマフォを初期化
674+ fetchEntriesSemaphore.init();
675+ // 記事が全検索モードか否かをチェックします
676+ var urls = null;
677+ if (allCheckedFlag == true) {
678+ // 全記事検索なので全てのログのURL
679+ urls = logData;
680+ } else {
681+ // 単独記事探索なので、選んだログのURL
682+ urls = [document.getElementById("feedblog_logbox").options[document.getElementById("feedblog_logbox").selectedIndex].value];
683+ }
684+ fetchEntriesSemaphore.urls = urls;
685+ fetchEntriesSemaphore.count = urls.length;
686+ for ( i = 0; i < urls.length; i++) {
687+ var xhr = new jQuery.ajax({
688+ url : urls[i] + '?time=' + urlSuffix,
689+ method : "GET",
690+ async : true,
691+ success : fetchEntries,
692+ error : showError
693+ });
694+ fetchEntriesSemaphore.xhrs.push(xhr);
695+ }
696+}
Show on old repository browser