root/websites/twicli/twicli.html @ 35193

Revision 35193, 39.1 kB (checked in by NeoCat, 4 years ago)

プラグインを一括ロード

  • Property svn:mime-type set to text/html; charset=UTF-8
Line 
1<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
2<html>
3<head>
4<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
5<meta name="copyright" content="&copy; 2008-2009 NeoCat">
6<meta name="description" content="JavaScript-based Twitter Client">
7<meta name="viewport" content="width=280,user-scalable=no">
8<link rel="apple-touch-icon" href="icon.png">
9<link rel="shortcut icon" href="favicon.ico">
10<title>twicli</title>
11<style type="text/css"><!--
12body { background-image: url(block_bg.png); background-attachment: fixed; margin: 1px; min-height: 500px; }
13img { border: 0 none; }
14hr { margin: 0; padding: 0; }
15iframe { display: none; }
16
17#control { position: fixed; top: 0; left: 0; width: 100%; height: 53px; border-bottom: 1px solid black; background-color: white; z-index: 3; background-color: #eee; }
18#loading { opacity: 0.5; filter: alpha(opacity=50); position: absolute; top: 6px; width: 100%; height: 20px; z-index: 4; text-align: center; }
19#fst { position: absolute; left: 1px; top: 1px; width: 94%; height: 30px; font-size: small; overflow: hidden; }
20#go { text-decoration: none; position: absolute; left: 95%; top: 1px; }
21#rst { text-decoration: none; position: absolute; left: 95%; top: 16px; }
22#menu { position: absolute; left: 0px; top: 33px; height: 20px; }
23#menu a { display: inline-block; font-size: 13px; height: 18px; line-height: 18px; border: 1px solid black; border-bottom: 0 none; background-color: #aaa; color: #242; padding: 0 4px; margin: 0; text-decoration: none; font-family: sans-serif;  -moz-border-radius: 5px 5px 0 0; -webkit-border-top-left-radius: 5px; -webkit-border-top-right-radius: 5px; margin-right: 2px; }
24#menu a.sel { height: 20px; background-color: #ffe; color: #002; border-bottom-color: #fff; }
25#menu a.new { background-color: #fcc; }
26
27#tw, #tw2 { position: absolute; left: 0px; top: 54px; width: 100%; font-size: small; }
28#tw2 { background-color: #fec; display: none; min-height: 448px; }
29#tw > div { border-bottom: 1px solid #777; }
30#tw > div > div, #tw2c > div > div { padding: 1px; border-bottom: 1px solid #999; }
31.dummy { border-bottom: 0; padding: 0; height: 0; clear: both; }
32.uicon { float: left; width: 32px; height: 32px; }
33.fav { float: right; }
34.dir { color: #679; }
35.status { text-decoration: none; color: black; }
36.utils { white-space: nowrap; text-align: right }
37.prop, .prop a { color: #999; font-size: x-small; }
38.fromme { background-color: #cfc; }
39.tome { background-color: #ccf; }
40.emp { background-color: #fcc; }
41.button { color: #f29; border: 1px solid #ddd; text-decoration: none; font-size: medium; -webkit-text-size-adjust:140%; }
42.green { color: #195; }
43.popup { padding: 0 4px; color: #888; }
44.lock { position: relative; top: 2px; left: 0; }
45.close { color: red; }
46#get_old, #next { text-align: center; background-color: #999; color: #fec; cursor: pointer; }
47#rep { display: none; background-color: #fee; position: absolute; width: 90%; left: 4%; top: 200px; border: 4px solid #666; z-index: 2; padding: 2px; font-size: small; }
48#reps { margin-top: 5px; }
49#popup { display: none; background-color: #eee; position: absolute; left: 0; top: 200px; border: 2px solid #666; z-index: 6; width: 180px; font-size: small; }
50#popup a { display: block; background-color: #fff; color: black; text-decoration: none; padding: 3px; border-bottom: 1px solid #888; }
51#popup a:hover { background-color: #33f; color: #fff; text-decoration: none; }
52#popup a.row2 { background-color: #eee; }
53#popup_hide { display: none; position: absolute; top: 0; left: 0; width: 100%; height: 100%; opacity: 0.2; filter: alpha(opacity=20); background-color: black; z-index: 5; }
54#user_info { margin: 1px; border: 1px solid #888; }
55#user_info td { font-size: small; }
56
57#counter-div { display: none; width: 3em; position: fixed; top: 33px; right: 6%; z-index: 9; opacity: 0.80; filter: alpha(opacity=80); }
58#counter-p1 { border-top: solid 9px transparent; width: 0; height: 0; border-right: solid 5px #99f; float: left; margin-left: 8px; }
59#counter-p2 { border-top: solid 9px transparent; width: 0; height: 0; border-left: solid 5px #99f; float: left; }
60#counter { font-family: Georgia; font-size: 11pt; font-weight: bold; font-style: italic; background-color: #99f; color: white; height: 1.1em; text-align: center; padding: 3px 0; clear: left; }
61
62@media screen and (max-device-width: 480px) {
63        #control { padding-bottom: 5px; }
64        #menu { height: 24px; }
65        #menu a { padding: 1px 5px; height: 22px; line-height: 22px; }
66        #menu a.sel { height: 24px; }
67        #tw, #tw2 { margin-top: 5px; }
68}
69--></style>
70<style id="fst_css"></style>
71<script type="text/javascript">
72if (navigator.userAgent.indexOf("Opera") >= 0)
73        document.write("<style>#control { height: 58px; } #menu { top: 38px; } #tw, #tw2 { top: 58px; }</style>");
74function $(id) { return document.getElementById(id); }
75function setFstHeight(h) {
76        if (no_resize_fst) return;
77        var exh = (navigator.userAgent.indexOf("Opera") >= 0) ? 1 : 0;
78        $("fst").style.height = h;
79        $("menu").style.top = $("counter-div").style.top = h+3+exh*5;
80        $("control").style.height = h+23+exh*5;
81        $("tw").style.top = $("tw2").style.top = h+24+exh*4;
82}
83// クロスドメインJavaScript呼び出し
84function loadXDomainScript(url, ele) {
85        if (ele && ele.parentNode)
86                ele.parentNode.removeChild(ele);
87        ele = document.createElement("script");
88        ele.src = url;
89        ele.type = "text/javascript";
90        document.body.appendChild(ele);
91        return ele;
92}
93// クロスドメインJavaScript呼び出し(クラスバージョン)
94function XDomainScript() {
95        this.cb_cnt = (new Date).getTime();
96}
97XDomainScript.prototype = {
98        load: function(url, callback) {
99                var id = this.cb_cnt++;
100                var ele = document.createElement("script");
101                ele.src = url + (url.indexOf('?') < 0 ? '?' : '&') + 'callback=xds.cb' + id;
102                ele.type = "text/javascript";
103                this['cbe' + id] = ele;
104                this['cb' + id] = function(){ this.abort(id); callback.apply(this, arguments); };
105                document.body.appendChild(ele);
106                return id;
107        },
108        abort: function(id) {
109                var ele = this['cbe' + id];
110                if (ele && ele.parentNode) ele.parentNode.removeChild(ele);
111                if (this['cb' + id]) delete this['cb' + id];
112                if (this['cbe' + id]) delete this['cbe' + id];
113        }
114};
115var xds = new XDomainScript;
116// 動的にフレームを生成してPOSTを投げる
117var postQueue = [];
118function enqueuePost(url, done, err) {
119        postQueue.push([url, done, err]);
120        if (postQueue.length > 1) // 複数リクエストを同時に投げないようキューイング
121                return;
122        postNext();
123}
124function postNext() {
125        if (postQueue.length) {
126                postInIFrame(postQueue[0][0], postQueue[0][1], postQueue[0][2]);
127        }
128}
129var postSeq = 0;
130function postInIFrame(url, done, err) {
131        var frm = document.createElement("form");    // POST用のフォームを生成
132        frm.action = url;
133        frm.method = "POST";
134        frm.target = "pfr" + seq;
135        document.body.appendChild(frm);
136        var pfr = document.createElement("iframe"); // formのtargetとなるiframeを生成
137        pfr.name = "pfr" + seq;
138        pfr.src = "about:blank";
139        pfr.style.display = "none";
140        var errTimer = false;
141        if (err) {  // 10秒で正常終了しなければエラーとみなす
142                errTimer = setTimeout(function(){
143                        err();
144                        frm.parentNode.removeChild(frm);
145                        pfr.parentNode.removeChild(pfr);
146                        postQueue.shift();
147                        postNext();
148                }, 100000);
149        }
150        var cnt = 0;
151        var onload = pfr.onload = function(){
152                if (cnt++ == 0) {
153                        setTimeout(function(){frm.submit();}, 0);
154                } else {
155                        clearTimeout(errTimer);
156                        done();
157                        setTimeout(function(){
158                                frm.parentNode.removeChild(frm);
159                                pfr.parentNode.removeChild(pfr);
160                                postQueue.shift();
161                                postNext();
162                        }, 0);
163                }
164        };
165        if ('v'=='\v') pfr.onreadystatechange = function(){ /* for IE */
166                if (this.readyState == "complete") {
167                        pfr.contentWindow.name = pfr.name;
168                        onload();
169                }
170        };
171        document.body.appendChild(pfr);
172}
173// 要素の位置を取得
174function cumulativeOffset(ele) {
175        var top = 0, left = 0;
176        do {
177                top += ele.offsetTop  || 0;
178                left += ele.offsetLeft || 0;
179                ele = ele.offsetParent;
180        } while (ele);
181        return [left, top];
182}
183// スクロール
184function getScrollY() { return window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop; }
185function scrollToY(y,y0,start) {
186        var t = (new Date).getTime();
187        start = start || t;
188        y0 = y0 || getScrollY();
189        if (start+500 <= t)
190                return scrollTo(0,y);
191        scrollTo(0, Math.ceil(y0 + (y-y0)*(1-Math.cos((t-start)/500*Math.PI))/2));
192        setTimeout(function(){scrollToY(y, y0, start)}, 20);
193}
194// DOM Storage (or Cookie)
195if (!window.localStorage) window.localStorage = window.globalStorage && window.globalStorage[location.hostname];
196function readCookie(key) {
197        if (window.localStorage && window.localStorage["twicli_"+key])
198                return String(window.localStorage["twicli_"+key]);
199        key += "=";
200        var scookie = document.cookie + ";";
201        start = scookie.indexOf(key);
202        if (start >= 0) {
203                var end = scookie.indexOf(";", start);
204                return unescape(scookie.substring(start + key.length, end));
205        }
206        return null;
207}
208function writeCookie(key, val, days) {
209        if (window.localStorage)
210                window.localStorage["twicli_"+key] = val;
211        else {
212                var sday = new Date();
213                sday.setTime(sday.getTime() + (days * 1000 * 60 * 60 * 24));
214                document.cookie = key + "=" + escape(val) + ";expires=" + sday.toGMTString();
215        }
216}
217// Array.mapの再実装(Opera用)
218if (!Array.prototype.map) {
219        Array.prototype.map = function(fun) {
220                var len = this.length;
221                var res = new Array(len);
222                var thisp = arguments[1];
223                for (var i = 0; i < len; i++)
224                        if (i in this)
225                                res[i] = fun.call(thisp, this[i], i, this);
226                return res;
227        };
228}
229// user-defined CSS
230var user_style = readCookie('user_style') || "";
231document.write('<style>' + user_style + '</style>');
232</script>
233</head>
234<body onLoad="init()">
235<div id="control">
236<script type="text/javascript">resetFrm = function(){}; update = function(){};</script>
237<iframe name="tx" onload="resetFrm();update()"></iframe>
238<!--発言フォーム-->
239<form name="frm" action="http://twitter.com/statuses/update.xml" method="POST" target="tx">
240<textarea id="fst" name="status" onkeyup="updateCount(); if (!key_press_detected && this.value.indexOf('\n') >= 0) return press(1)" onkeypress="updateCount(); return press(event)" onfocus="updateCount();" onblur="$('counter-div').style.display='none';"></textarea>
241<input type="hidden" name="source" value="twicli">
242<a id="go" href="javascript:void press(1)"><img src="go.png"></a>
243<a id="rst" href="javascript:void resetFrm()"><img src="clr.png"></a>
244<div id="loading"><img src="loading.gif"></div></form>
245<!--メニュー-->
246<div id="menu"><nobr id='menu2'><a id="TL" class="sel" href="javascript:void switchTL()">TL</a><a id="reply" href="javascript:void switchReply()">Re</a><a id="user" href="javascript:void switchUser()">user</a><a id="direct" href="javascript:void switchDirect()">D</a><a id="misc" href="javascript:void switchMisc()">+</a></nobr></div>
247</div>
248<!--メインタイムライン-->
249<div id="tw"></div>
250<!--TL以外のタブ-->
251<div id="tw2"><div id="tw2h"></div><div id="tw2c"></div></div>
252<!--返信-->
253<div id="rep"><a href="javascript:closeRep()" class="close">[x]</a>
254        <a href="javascript:pickup2()" class="close">[⇔]</a><div id="reps"></div></div>
255<!--ポップアップメニュー-->
256<div id="popup" onClick="popup_hide()">
257<a id="popup_link_user" href="" target="twitter">Twitter / User</a>
258<a id="popup_link_status" href="" target="twitter">Twitter / Status</a>
259<a id="popup_status_delete" href="#" onClick="return deleteStatus()">Delete Status</a>
260<a id="popup_status_retweet" href="#" onClick="return retweetStatus()" target="twitter">ReTweet</a>
261</div>
262<!--ポップアップメニュー非表示用-->
263<div id="popup_hide" onClick="popup_hide();"></div>
264<!--文字数カウンタ-->
265<div id="counter-div"><div id="counter-p1"></div><div id="counter-p2"></div><div id="counter"></div></div>
266<!-- -->
267
268<script type="text/javascript">
269var twitterURL = 'http://twitter.com/';
270var myname = null;              // 自ユーザ名
271var myid = null;                // 自ユーザID
272var last_user = null;   // user TLに表示するユーザ名
273// 設定値
274var cookieVer = parseInt(readCookie('ver')) || 0;
275var updateInterval = (cookieVer>3) && parseInt(readCookie('update_interval')) || 60;
276var pluginstr = (cookieVer>5) && readCookie('tw_plugins') || ' regexp.js\noutputz.js\nsearch.js\nsearch2.js\nfavotter.js\nfollowers.js';
277pluginstr = pluginstr.substr(1);
278var plugins = new Array;
279var nr_limit = parseInt(readCookie('limit')) || 500;                            // 表示する発言数の上限
280var max_count = (cookieVer>3) && parseInt(readCookie('max_count')) || 50;
281var max_count_u = parseInt(readCookie('max_count_u')) || 50;
282var no_since_id = parseInt(readCookie('no_since_id') || "0");           // since_idを使用しない
283var no_counter = parseInt(readCookie('no_counter') || "0");                     // 発言文字数カウンタを無効化
284var no_resize_fst = parseInt(readCookie('no_resize_fst') || "0");       // フィールドの自動リサイズを無効化
285var replies_in_tl = parseInt(readCookie('replies_in_tl') || "1");       // フォロー外からのReplyをTLに表示
286var footer = readCookie('footer') || "";                                                        // フッタ文字列
287// TL管理用
288var nr_tw = 0;                                  // 現在のTLの発言数
289var cur_page = 1;                               // 現在表示中のページ
290var nr_page = 0;                                // 次に取得するページ
291var get_next_func = getOldTL;   // 次ページ取得関数
292var since_id = null;                    // TLの最終since_id
293var since_id_reply = null;              // Replyの最終since_id
294var in_reply_to_user = null;    // 発言の返信先
295// クロスドメイン通信関連
296var seq = (new Date).getTime();
297var users_log = [];
298var users_xds = [];
299var auth_ele = null;
300var update_ele = null;
301var update_ele2 = null;
302var reply_ele = null;
303var direct_ele1 = null;
304var direct_ele2 = null;
305var direct1 = null;
306var direct2 = null;
307var delayed_tw = null;
308// UI関連
309var user_pick1 = null;                  // [⇔]で表示するユーザ名1
310var user_pick2 = null;                  // [⇔]で表示するユーザ名2
311var popup_user = null;                  // ポップアップメニューが選択されたユーザ名
312var popup_id = null;                    // ポップアップメニューが選択された発言ID
313var fav_mode = 0;                               // fav表示中か
314var rep_top = 0;                                // replyのオーバーレイ位置
315var popup_top = 0;                              // ポップアップメニューの表示位置
316var selected_menu = $("TL");    // 選択中のタブ
317var update_timer = null;
318var update_reply_counter = 0;
319var key_press_detected = false;
320
321//ログイン・自ユーザ名受信
322function twAuth(a) {
323        if (a.error) return alert(a.error);
324        myname = last_user = a.screen_name;
325        myid = a.id;
326        $("user").innerHTML = last_user;
327        if (delayed_tw)
328                twShow(delayed_tw); // ログイン前に受信したTLを遅延評価
329}
330function auth() {
331        auth_ele = loadXDomainScript(twitterURL + "account/verify_credentials.json?callback=twAuth", auth_ele);
332}
333
334// enterキーで発言
335function press(e) {
336        if (e != 1) key_press_detected = true;
337        if (e != 1 && e.keyCode != 13 && e.keyCode != 10 || e.ctrlKey || e.shiftKey) return true;
338        if (!key_press_detected) document.frm.status.value = document.frm.status.value.replace(/\n/g, "");
339        if (document.frm.status.value == '') { // 空欄であればTimeline更新のみ
340                $("loading").style.display = "block";
341                update();
342                return false;
343        }
344        if (document.frm.status.value.indexOf("@"+in_reply_to_user) < 0) // @ユーザが含まれているときのみ返信先を指定
345                setReplyId(false);
346        in_reply_to_user = "";
347        callPlugins("post", document.frm.status.value);
348        document.frm.status.value += footer;
349        document.frm.status.select();
350        document.frm.submit();
351        return false;
352}
353// 発言文字数カウンタ表示・更新
354function updateCount() {
355        setFstHeight($("fst").value.length ? Math.max($("fst").scrollHeight+2,30) : 30);
356        if (no_counter) return;
357        $("counter-div").style.display = "block";
358        $("counter").innerHTML = 140 - footer.length - $("fst").value.length;
359}
360// フォームの初期化
361resetFrm = function() {
362        document.frm.reset();
363        setReplyId(false);
364        if ($("counter-div").style.display == "block") updateCount();
365        setFstHeight(30);
366}
367// reply先の設定/解除
368function setReplyId(id) {
369        var repid = $('in_reply_to_status_id');
370        if (repid && repid.parentNode)
371                repid.parentNode.removeChild(repid);
372        if (id) {
373                repid = document.createElement('input');
374                repid.type = 'hidden';
375                repid.id = repid.name = 'in_reply_to_status_id';
376                repid.value = id;
377                document.frm.appendChild(repid);
378        }
379}
380// reply先を設定
381function replyTo(user, id) {
382        in_reply_to_user = user;
383        document.frm.status.value = (selected_menu.id == "direct" ? "d " : "@") + user + " " + document.frm.status.value;
384        setReplyId(id);
385        document.frm.status.select();
386}
387// reply先を表示
388function dispReply(user, id, ele) {
389        user_pick1 = user;
390        var d = $((selected_menu.id == "TL" ? "tw" : "tw2c") + "-" + id);
391        if (!d || d.style.display == "none") {
392                rep_top = cumulativeOffset(ele)[1] + 20;
393                d = selected_menu.id != "TL" && $("tw" + "-" + id);
394                if (d) {
395                        $('reps').innerHTML = d.innerHTML;
396                        $('rep').style.display = "block";
397                        $('rep').style.top = rep_top;
398                        user_pick2 = d.screen_name;
399                        return;
400                }
401                $("loading").style.display = "block";
402                reply_ele = loadXDomainScript(twitterURL + 'statuses/show/'+id+'.json?callback=dispReply2', reply_ele);
403                return;
404        }
405        closeRep();
406        var top = cumulativeOffset(d)[1];
407        var h = d.offsetHeight;
408        var sc_top = document.body.scrollTop || document.documentElement.scrollTop;
409        var win_h = window.innerHeight || document.documentElement.clientHeight;
410        if (top < sc_top) scrollToY(top);
411        if (sc_top+win_h < top+h) scrollToY(top+h-win_h);
412        d.className += ' emp';
413        setTimeout(function(){d.className = d.className.replace(' emp','')}, 2000);
414}
415// reply先をoverlay表示 (Timelineに無い場合)
416function dispReply2(tw) {
417        $("loading").style.display = "none";
418        if (tw.error) return alert(tw.error);
419        $('reps').innerHTML = makeHTML(tw);
420        callPlugins("newMessageElement", $('reps'), tw);
421        $('rep').style.display = "block";
422        $('rep').style.top = rep_top;
423        user_pick2 = tw.user.screen_name;
424}
425// replyのoverlay表示を閉じる
426function closeRep() {
427        $('rep').style.display = 'none';
428}
429// replyからユーザ間のタイムラインを取得
430function pickup2() {
431        switchUser(user_pick1 + "," + user_pick2);
432}
433// ポップアップメニューを表示
434function popup_menu(user, id, ele) {
435        popup_user = user;
436        popup_id = id;
437        callPlugins("popup", $('popup'), user, id, ele);
438        $('popup_link_user').href = twitterURL + user;
439        $('popup_link_status').href = twitterURL + user + '/statuses/' + id;
440        $('popup_status_delete').style.display = (user == myname ? "block" : "none");
441        $('popup').style.display = "block";
442        var pos = cumulativeOffset(ele);
443        $('popup').style.left = pos[0] <  $('popup').offsetWidth - ele.offsetWidth ? 0 : pos[0] - $('popup').offsetWidth + ele.offsetWidth;
444        $('popup').style.top = popup_top = pos[1] + 20;
445        $('popup_hide').style.height = Math.max(document.body.scrollHeight, $("tw").offsetHeight+$("control").offsetHeight);
446        $('popup_hide').style.display = "block";
447}
448// ポップアップメニューを非表示
449function popup_hide() {
450        $('popup').style.display = 'none';
451        $('popup_hide').style.display = 'none';
452        popup_user = popup_id = null;
453}
454// 発言のReTweet
455function retweetStatus() {
456        if (!popup_id) return false;
457        if ($('lock-' + popup_id) && !confirm("This post is protected; Are you sure to RT?")) return false;
458        $('fst').value = "RT @"+popup_user+": " + $('text'+popup_id).innerHTML.replace(/<.*?>/g,'');
459        $('fst').focus(); $('fst').select();
460        return false;
461}
462// 発言の削除
463function deleteStatus() {
464        if (!popup_id) return false;
465        if (!confirm("Are you sure to delete this message (@"+popup_user+" / "+popup_id+")?")) return false;
466        $("loading").style.display = "block";
467        if ($("text" + popup_id)) $("text" + popup_id).style.textDecoration = "line-through";
468        enqueuePost(twitterURL + 'status/destroy/' + popup_id + '.xml',
469                function(){$("loading").style.display = "none";}, function(){$("loading").style.display = "none";});
470        return false;
471}
472// 最新タイムラインを取得
473update_inited = function() {
474        if (!myname) auth();
475        if (delayed_tw) return;
476        callPlugins("update");
477        update_ele = loadXDomainScript(twitterURL + 'statuses/friends_timeline.json?seq=' + (seq++) +
478                                                '&count=' + (since_id ? 200 : max_count) +
479                                                '&callback=twShow' + (!no_since_id && since_id ? '&since_id='+since_id : ''), update_ele);
480        resetUpdateTimer();
481}
482function resetUpdateTimer() {
483        if (update_timer) clearInterval(update_timer);
484        update_timer = setInterval(update, updateInterval*1000);
485}
486// twitのHTML表現を生成
487function dateFmt(d) {
488        d = new Date(typeof(d)=='string' ? d.replace('+','GMT+') : d);
489        function d2(dig) { return (dig>9?"":"0") + dig }
490        return (d.getMonth()+1) + "/" + d.getDate() + " " + d.getHours() + ":" + d2(d.getMinutes()) + ":" + d2(d.getSeconds());
491}
492function makeHTML(tw, no_name, pid) {
493        var un = tw.user.screen_name;
494        return /*fav*/ '<img class="fav" src="http://assets3.twitter.com/images/icon_star_'+(tw.favorited?'full':'empty')+'.gif" ' +
495                        'onClick="fav(this,' + tw.id + ')"' + (pid ? ' id="fav-'+pid+'-'+tw.id+'"' : '') + '>' +
496                 (!no_name ?
497                        //ユーザアイコン
498                        (tw.user.url ? '<a target="twitter" href="'+tw.user.url+'">' : '') +
499                        '<img class="uicon" src="' + tw.user.profile_image_url + '">' + (tw.user.url ? '</a>' : '') +
500                        //名前
501                        '<a href="http://twitter.com/' + un + '" onClick="switchUser(\'' + un + '\');return false"><span class="uid">' + un + '</span>' +
502                         /*プロフィールの名前*/ (tw.user.name!=un ? '<span class="uname">('+tw.user.name+'&#x202c;)</span>' : '') + '</a>'
503                : '') +
504                 /* protected? */ (tw.user.protected ? '<img id="lock-' + tw.id + '" class="lock" src="http://assets0.twitter.com/images/icon_lock.gif">' : '') +
505                /*ダイレクトメッセージの方向*/ (tw.d_dir == 1 ? '<span class="dir">→</span> ' : tw.d_dir == 2 ? '<span class="dir">←</span> ' : '') +
506                //本文 (https〜をリンクに置換 + @を本家リンク+JavaScriptに置換)
507                " <span id=\"text" + tw.id + "\" class=\"status\">" +
508                tw.text.replace(/https?:\/\/[\w!#$%&'()*+,.\/:;=?@~-]+|@([\w-]+)/g, function(_,id){
509                                if(!id) return "<a target=\"twitter\" href=\""+_+"\">"+_+"</a>";
510                                return "<a href=\"http://twitter.com/"+id+"\" onClick=\"switchUser('"+id+"'); return false;\" >"+_+"</a>";
511                        }).replace(/\r?\n|\r/g, "<br>") + '</span>' +
512                //日付
513                ' <span class="utils"><span class="prop"><a class="date" target="twitter" href="http://twitter.com/'+un+'/statuses/'+tw.id+'">' + dateFmt(tw.created_at) + '</a>' +
514                //クライアント
515                (tw.source ? '<span class="separator"> / </span><span class="source">' + tw.source.replace(/<a /,'<a target="twitter"') + '</span>' : '') + '</span>' +
516                //返信先を設定
517                ' <a class="button green" href="javascript:replyTo(\'' + un + "'," + tw.id + ')">↩</a>' +
518                //返信元へのリンク
519                (tw.in_reply_to_status_id ? ' <a class="button" href="#" onClick="dispReply(\'' + un + '\',' + tw.in_reply_to_status_id + ',this); return false;">☞</a>' : '') +
520                //popupメニュー表示
521                '&nbsp;&nbsp;&nbsp;<a class="button popup" href="#" onClick="popup_menu(\'' + un + "'," + tw.id + ', this); return false;"><small><small>▼</small></small></a>' +
522                '</span><div class="dummy"></div>';
523}
524// ユーザ情報のHTML表現を生成
525function makeUserInfoHTML(user) {
526        return '<table><tr><td><a target="twitter" href="http://twitter.com/account/profile_image/'+
527                        user.screen_name+'"><img align="left" src="' + user.profile_image_url + '"></a></td><td id="profile">' +
528                        (user.protected ? '<img src="http://assets0.twitter.com/images/icon_lock.gif">' : '') +
529                        '<b>' + user.screen_name + '</b> / <b>' + user.name + '</b><br>' +
530                        (user.location ? '<b>Location</b>: ' + user.location + '<br>' : '') +
531                        (user.url ? '<b>URL</b>: <a target="twitter" href="' + user.url + '">' + user.url + '</a><br>' : '') +
532                        (user.description ? user.description : '') +
533                        '<br><b>' + user.friends_count + '<small>following</small> / ' +
534                                                user.followers_count + '<small>followers</small>' +
535                        '<br>' + user.statuses_count + '<small>updates</small> / ' +
536                                                user.favourites_count + '<small>favs</small></b>' +
537                        '</td></tr></table><a target="twitter" href="http://twitter.com/' + user.screen_name + '">[Twitter]</a> '+
538                        '<a href="javascript:switchFav()">[fav]</a> ';
539}
540// 過去の発言取得ボタン(DOM)生成
541function nextButton(id, p) {
542        var ret = document.createElement('div');
543        ret.id = id;
544        ret.onclick = function() { getNext(this); };
545        ret.innerHTML = '▽' + (p ? '(' + p + ')' : '');
546        return ret;
547}
548// favoriteの追加/削除
549function fav(img, id) {
550        if (img.src.indexOf('throbber') >= 0) return;
551        var f = img.src.indexOf('empty') >= 0;
552        setFavIcon(img, id, -1);
553        enqueuePost(twitterURL + 'favourings/' + (f ? 'create' : 'destroy') + '/' + id + '.xml',
554                function(){ setFavIcon(img, id, f) }, function(){ setFavIcon(img, id, !f) });
555}
556// favアイコンの設定(f=0: 未fav, f=1:fav済, f=-1:通信中)
557function setFavIcon(img, id, f) {
558        var img_tl = $('fav-tw-' + id);
559        var img_url = (f==-1) ? twitterURL + 'images/icon_throbber.gif' :
560                                                'http://assets3.twitter.com/images/icon_star_' + (f ? 'full' : 'empty') + '.gif';
561        img.src = img_url;
562        if (img_tl) img_tl.src = img_url;
563        callPlugins("fav", id, f, img, img_tl);
564}
565// followとremove
566function follow(f) {
567        enqueuePost(twitterURL + 'friendships/' + (f ? 'create' : 'destroy') + '/' + last_user + '.xml', switchUser);
568        $("loading").style.display = "block";
569}
570// ユーザ情報を表示
571function twUserInfo(user) {
572        if (user.error) return alert(user.error);
573        var elem = $('user_info');
574        elem.innerHTML = makeUserInfoHTML(user);
575        callPlugins("newUserInfoElement", elem, user);
576        if (myname != user.screen_name) {
577                update_ele2 = loadXDomainScript(twitterURL + 'friendships/show.json?seq=' + (seq++) +
578                                        '&source_screen_name=' + myname + '&target_id=' + user.id +
579                                        '&callback=twRelation', update_ele2);
580        }
581}
582// ユーザ情報にフォロー関係を表示
583function twRelation(rel) {
584        var source = rel.relationship.source;
585        var elem = $("user_info");
586        elem.innerHTML += '<input type="button" value="' + (source.following ? 'Remove ' : 'Follow ') +  last_user +
587                                        '" onClick="follow('+!source.following+')">';
588        if (source.followed_by)
589                $("profile").innerHTML += "<br><b>" + rel.relationship.target.screen_name + ' is following you!</b>';
590        callPlugins("newUserRelationship", elem, rel);
591}
592// ダイレクトメッセージ一覧の受信
593function twDirect1(tw) {
594        if (tw.error) return alert(tw.error);
595        direct1 = tw;
596        if (direct2)
597                twDirectShow();
598}
599function twDirect2(tw) {
600        if (tw.error) return alert(tw.error);
601        direct2 = tw;
602        if (direct1)
603                twDirectShow();
604}
605function twDirectShow() {
606        var direct = direct1.concat(direct2).sort(function(a,b){return b.id - a.id});
607        direct = direct.map(function(d){
608                if (d.recipient_screen_name == myname) {
609                        d.user = d.sender;
610                        d.d_dir = 1;
611                } else {
612                        d.user = d.recipient;
613                        d.d_dir = 2;
614                }
615                return d;
616        });
617        twShow2(direct);
618        direct1 = direct2 = false;
619}
620// API制限情報の受信
621function twLimit(lim) {
622        $("loading").style.display = "none";
623        $("tw2c").innerHTML = "<b>Twitter API status:</b><br>" +
624                                        "hourly limit : " + lim.remaining_hits + " / " + lim.hourly_limit + "<br>" +
625                                        "reset at : " + dateFmt(lim.reset_time);
626}
627// 新着reply受信通知
628function noticeNewReply() {
629        if ($("reply").className.indexOf("new") < 0)
630                $("reply").className += " new";
631        callPlugins("noticeNewReply");
632}
633// 受信repliesを表示
634function twReplies(tw) {
635        if (tw.error) return alert(tw.error);
636        tw.reverse();
637        for (var j in tw) callPlugins("gotNewReply", tw[j]);
638        tw.reverse();
639        var nr = tw.length;
640        if (replies_in_tl)
641                nr = twShowToNode(tw, $("tw"), false, false, true);
642        if (nr > 0) noticeNewReply();
643        if (tw.length > 0) since_id_reply = tw[0].id;
644}
645// 受信twitを表示
646function twShow(tw) {
647        if (tw.error) return alert(tw.error);
648        if (!myname) {
649                delayed_tw = tw; // 認証完了後まで評価を遅延
650                return;
651        }
652        tw.reverse();
653        for (var j in tw) callPlugins("gotNewMessage", tw[j]);
654        if(!since_id_reply) since_id_reply = tw[0].id;
655        tw.reverse();
656        if (nr_page == 0) {
657                $("tw").appendChild(nextButton('get_old', 1));
658                nr_page = 1;
659        }
660        twShowToNode(tw, $("tw"), false, false, true, true);
661        if (since_id_reply && update_reply_counter-- <= 0) {
662                update_ele2 = loadXDomainScript(twitterURL + 'statuses/replies.json?seq=' + (seq++) +
663                                                '&count=' + (since_id ? 200 : max_count) +
664                                                '&callback=twReplies&since_id='+since_id_reply,
665                                        update_ele2);
666                update_reply_counter = 4;
667        }
668        callPlugins("noticeUpdate", tw);
669}
670function twOld(tw) {
671        if (tw.error) return alert(tw.error);
672        var tmp = $("tmp");
673        if (tmp && tmp.parentNode) tmp.parentNode.removeChild(tmp);
674        twShowToNode(tw, $("tw"), false, true);
675        $("tw").appendChild(nextButton('get_old', nr_page));
676}
677function twShow2(tw) {
678        if (tw.error) return alert(tw.error);
679        var tmp = $("tmp");
680        if (tmp && tmp.parentNode) tmp.parentNode.removeChild(tmp);
681        var user_info = $("user_info");
682        twShowToNode(tw, $("tw2c"), !!user_info && !fav_mode, cur_page > 1);
683        if (selected_menu.id == "reply" || selected_menu.id == "user" && last_user.indexOf(',') < 0) {
684                $("tw2c").appendChild(nextButton('next'));
685                get_next_func = getNextFuncCommon;
686        }
687        if (tw[0] && selected_menu.id == "user" && last_user.indexOf(',') < 0 && !fav_mode)
688                twUserInfo(tw[0].user);
689}
690function twShow3(tw) {
691        if (tw.error) return alert(tw.error);
692        users_log.push(tw);
693        if (users_log.length == last_user.split(',').length) {
694                var tws = [];
695                for (var i = 0; i < users_log.length; i++)
696                        tws = tws.concat(users_log[i]);
697                tws = tws.sort(function(a,b){return b.id - a.id});
698                twShow2(tws);
699        }
700}
701function twShowToNode(tw, twNode, no_name, after, animation, check_since) {
702        $('loading').style.display = 'none';
703        var len = tw.length;
704        if (len == 0) return 0;
705        var pNode = document.createElement('div');
706        var dummy = pNode.appendChild(document.createElement('div'));
707        var myname_r = new RegExp("@"+myname+"\\b","i");
708        var nr_show = 0;
709        for (var i = len-1; i >= 0; i--) {
710                if ($(twNode.id + "-" + tw[i].id))
711                        continue;
712                if (tw[i].user) {
713                        var s = document.createElement('div');
714                        s.id = twNode.id + "-" + tw[i].id;
715                        s.innerHTML = makeHTML(tw[i], no_name, twNode.id);
716                        s.screen_name = tw[i].user.screen_name;
717                        if (tw[i].d_dir == 1 || tw[i].text.match(myname_r)) {
718                                s.className = "tome";
719                                if (twNode.id == "tw")
720                                        noticeNewReply();
721                        }
722                        if (tw[i].d_dir == 2 || tw[i].user.screen_name == myname)
723                                s.className = "fromme";
724                        callPlugins("newMessageElement", s, tw[i], twNode.id);
725                        pNode.insertBefore(s, pNode.childNodes[0]);
726                        nr_show++;
727                }
728        }
729        pNode.removeChild(dummy);
730        if (pNode.childNodes.length == 0) return 0;
731        pNode.style.overflow = "hidden";
732        var animation2 = animation && getScrollY() < 10;
733        var maxH;
734        var curY = getScrollY();
735        if (animation2) { // get maxH
736                twNode.appendChild(pNode);
737                maxH = pNode.clientHeight;
738                twNode.removeChild(pNode);
739                pNode.style.minHeight = 0;
740        }
741        if (after || !twNode.childNodes[0])
742                twNode.appendChild(pNode);
743        else
744                twNode.insertBefore(pNode, twNode.childNodes[0]);
745        if (animation2)
746                animate(pNode, maxH, (new Date).getTime());
747        else if (animation) {
748                $('rep').style.top = (rep_top += pNode.clientHeight+1);
749                $('popup').style.top = (popup_top += pNode.clientHeight+1);
750                scrollTo(0, curY + pNode.clientHeight+1);
751        }
752        if (animation) {
753                nr_tw += nr_show;
754                while (nr_tw > nr_limit) {
755                        var last_node = twNode.childNodes[twNode.childNodes.length-1];
756                        nr_tw -= last_node.childNodes.length;
757                        twNode.removeChild(last_node);
758                }
759        }
760        for (var i = 0; check_since && i < len; i++) {
761                if (tw[i].user.screen_name != myname) {
762                        since_id = tw[i].id;
763                        break;
764                }
765        }
766        return nr_show;
767}
768// 新規twitの出現アニメーション処理
769function animate(elem, max, start) {
770        var t = (new Date).getTime();
771        if (start+1000 <= t)
772                return elem.style.maxHeight = 'none';
773        elem.style.maxHeight = Math.ceil(max*(1-Math.cos((t-start)/1000*Math.PI))/2);
774        setTimeout(function(){animate(elem, max, start)}, 20);
775}
776// 次ページ取得
777function getNext(ele) {
778        var tmp = document.createElement("div");
779        tmp.id = "tmp";
780        tmp.innerHTML = "<p></p>";
781        ele.parentNode.appendChild(tmp);
782        ele.parentNode.removeChild(ele);
783        $("loading").style.display = "block";
784        get_next_func();
785}
786function getOldTL() {
787        update_ele2 = loadXDomainScript(twitterURL + 'statuses/friends_timeline.json?seq=' + (seq++) +
788                                '&count=' + max_count + '&page=' + (nr_page++) + '&id=' + last_user +
789                                '&callback=twOld', update_ele2);
790}
791function getNextFuncCommon() {
792        if (selected_menu.id == "user" && !fav_mode)
793                update_ele2 = loadXDomainScript(twitterURL + 'statuses/user_timeline.json?seq=' + (seq++) +
794                                        '&count=' + max_count_u + '&page=' + (++cur_page) + '&id=' + last_user +
795                                        '&suppress_response_codes=true&callback=twShow2', update_ele2);
796        else if (selected_menu.id == "user" && fav_mode)
797                update_ele2 = loadXDomainScript(twitterURL + 'favorites/' + last_user + '.json?seq=' + (seq++) +
798                                        '&page=' + (++cur_page) + '&callback=twShow2', update_ele2);
799        else if (selected_menu.id == "reply")
800                update_ele2 = loadXDomainScript(twitterURL + 'statuses/replies.json?seq=' + (seq++) +
801                                        '&count=' + max_count_u + '&page=' + (++cur_page) +
802                                        '&callback=twShow2', update_ele2);
803}
804// タイムライン切り替え
805function switchTo(id) {
806        selected_menu.className = "";
807        selected_menu = $(id);
808        selected_menu.className = "sel";
809        $("tw").style.display = id=="TL"?"block":"none";
810        $("tw2h").innerHTML = "";
811        $("tw2c").innerHTML = "";
812        $("tw2").style.display = id!="TL"?"block":"none";
813        $("rep").style.display = "none";
814        scrollTo(0, 1); scrollTo(0, 0);
815        cur_page = 1;
816        fav_mode = 0;
817}
818function switchTL() {
819        get_next_func = getOldTL;
820        switchTo("TL");
821}
822function switchReply() {
823        switchTo("reply");
824        $("loading").style.display = "block";
825        update_ele2 = loadXDomainScript(twitterURL + 'statuses/replies.json?seq=' + (seq++) +
826                                '&count=' + max_count_u + '&callback=twShow2', update_ele2);
827}
828function switchUser(user) {
829        if (!user) user = last_user;
830        last_user = user;
831        $("user").innerHTML = user;
832        switchTo("user");
833        $("loading").style.display = "block";
834        var users = user.split(',');
835        if (users.length == 1) {
836                $("tw2h").innerHTML = "<div id=\"user_info\"></div>";
837                update_ele2 = loadXDomainScript(twitterURL + 'statuses/user_timeline.json?seq=' + (seq++) +
838                        '&count=' + max_count_u + '&id=' + user + '&callback=twShow2', update_ele2);
839        } else {
840                users_log = [];
841                for (var i = 0; i < users_xds.length; i++)
842                        xds.abort(users_xds[i]);
843                users_xds = users.map(function(u) {
844                        xds.load(twitterURL + 'statuses/user_timeline.json?id=' + u +
845                                                         '&suppress_response_codes=true&count=' + max_count_u, twShow3);
846                });
847        }
848}
849function switchFav() {
850        $("loading").style.display = "block";
851        cur_page = 1;
852        fav_mode = 1;
853        $("tw2c").innerHTML = "";
854        update_ele2 = loadXDomainScript(twitterURL + 'favorites/' + last_user + '.json?seq=' + (seq++) +
855                                                                                '&callback=twShow2', update_ele2);
856}
857function switchDirect() {
858        switchTo("direct");
859        $("loading").style.display = "block";
860        direct_ele1 = loadXDomainScript(twitterURL + 'direct_messages.json?seq=' + (seq++) +
861                                                                                '&callback=twDirect1', direct_ele1);
862        direct_ele2 = loadXDomainScript(twitterURL + 'direct_messages/sent.json?seq=' + (seq++) +
863                                                                                '&callback=twDirect2', direct_ele2);
864}
865function switchMisc() {
866        switchTo("misc");
867        $("tw2h").innerHTML = '<p><a target="twitter" href="index.html"><b>twicli</b></a> : browser-base Twitter client<br><small>Copyright &copy; 2008-2009 NeoCat</small></p><hr>' +
868                                        '<p><form onSubmit="switchUser($(\'user_id\').value); return false;">'+
869                                        'show user info : @<input type="text" size="15" id="user_id" value="' + myname + '"><input type="image" src="go.png"></form></p><hr>' +
870                                        '<p><a href="javascript:togglePreps()">▼<b>Preferences</b></a>' +
871                                        '<form id="preps" onSubmit="setPreps(this); return false;" style="display: none;">' +
872                                        'max #msgs in TL: <input name="limit" size="5" value="' + nr_limit + '"><br>' +
873                                        '#msgs in TL on update (max=200): <input name="maxc" size="3" value="' + max_count + '"><br>' +
874                                        '#msgs in user on update (max=200): <input name="maxu" size="3" value="' + max_count_u + '"><br>' +
875                                        'update interval: <input name="interval" size="3" value="' + updateInterval + '"> sec<br>' +
876                                        '<input type="checkbox" name="since_check"' + (no_since_id?"":" checked") + '>since_id check<br>' +
877                                        '<input type="checkbox" name="replies_in_tl"' + (replies_in_tl?" checked":"") + '>show not-following replies in TL<br>' +
878                                        '<input type="checkbox" name="counter"' + (no_counter?"":" checked") + '>POST length counter<br>' +
879                                        '<input type="checkbox" name="resize_fst"' + (no_resize_fst?"":" checked") + '>Auto-resize field<br>' +
880                                        'Footer: <input name="footer" size="10" value="' + footer + '"><br>' +
881                                        'Plugins:<br><textarea cols="30" rows="4" name="list">' + pluginstr + '</textarea><br>' +
882                                        'user stylesheet:<br><textarea cols="30" rows="4" name="user_style">' + user_style + '</textarea><br>' +
883                                        '<input type="submit" value="Save"></form></p><hr>';
884        callPlugins("miscTab", $("tw2h"));
885        $("loading").style.display = "block";
886        update_ele2 = loadXDomainScript(twitterURL + 'account/rate_limit_status.json?seq=' + (seq++) +
887                                                                                '&id=' + myname + '&callback=twLimit', update_ele2);
888}
889function togglePreps() {
890        $('preps').style.display = $('preps').style.display == 'block' ? 'none' : 'block';
891}
892function setPreps(frm) {
893        nr_limit = frm.limit.value;
894        max_count = frm.maxc.value;
895        max_count_u = frm.maxu.value;
896        updateInterval = frm.interval.value;
897        no_since_id = !frm.since_check.checked;
898        no_counter = !frm.counter.checked;
899        no_resize_fst = !frm.resize_fst.checked;
900        replies_in_tl = frm.replies_in_tl.checked;
901        footer = new String(frm.footer.value);
902        resetUpdateTimer();
903        writeCookie('ver', 6, 3652);
904        writeCookie('limit', nr_limit, 3652);
905        writeCookie('max_count', max_count, 3652);
906        writeCookie('max_count_u', max_count_u, 3652);
907        writeCookie('update_interval', updateInterval, 3652);
908        writeCookie('no_since_id', no_since_id?1:0, 3652);
909        writeCookie('no_counter', no_counter?1:0, 3652);
910        writeCookie('no_resize_fst', no_resize_fst?1:0, 3652);
911        writeCookie('replies_in_tl', replies_in_tl?1:0, 3652);
912        writeCookie('footer', footer, 3652);
913        writeCookie('tw_plugins', new String(" " + frm.list.value), 3652);
914        writeCookie('user_style', new String(frm.user_style.value), 3652);
915        callPlugins('savePrefs', frm);
916        alert("Your settings are saved. Please reload to apply plugins and CSS.");
917}
918// 初期化
919function init() {
920        setTimeout(function(){scrollTo(0, 1)}, 0);
921        // 初回アップデート
922        update = update_inited; // 初期化前にupdateが発生するのを防止
923        setTimeout(function(){update()}, 0);
924}
925// プラグイン
926function registerPlugin(obj) {
927        plugins.push(obj);
928}
929function callPlugins(name) {
930        var args = [].slice.apply(arguments);
931        args.shift();
932        for (var i in plugins)
933                if (typeof plugins[i][name] == "function")
934                        plugins[i][name].apply(plugins[i], args);
935}
936if (pluginstr) {
937        var st = '<scr'+'ipt type="text/javascript" src="';
938        var ed = '"></scr'+'ipt>';
939        document.write(st + pluginstr.split("\n").join(ed+st) + ed);
940}
941// ホイールの回転でスクロール (スクロール不可のウィンドウ・フレーム内で有効)
942function wheel(event) {
943        var delta = 0;
944        if (!event) event = window.event;
945        if (event.wheelDelta) {
946                delta = event.wheelDelta;
947        } else if (event.detail)
948                delta = -event.detail;
949        if (navigator.userAgent.indexOf("Gecko/") >= 0)
950                delta *= 10;
951        if (navigator.userAgent.indexOf("AppleWebKit/") >= 0)
952                delta /= 10;
953        if (delta)
954                scrollBy(0, -delta);
955        if (event.preventDefault)
956                event.preventDefault();
957        event.returnValue = false;
958}
959if (window.addEventListener) window.addEventListener('DOMMouseScroll', wheel, false);
960window.onmousewheel = document.onmousewheel = wheel;
961</script>
962</body>
963</html>
Note: See TracBrowser for help on using the browser.