root/websites/twicli/twicli.html @ 35762

Revision 35762, 40.6 kB (checked in by NeoCat, 4 years ago)

*favorites APIのURL修正
*URL正規表現の修正
*その他

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