| 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="viewport" content="width=device-width"> |
|---|
| 6 | <title>twicli</title> |
|---|
| 7 | <style> |
|---|
| 8 | body {background-color: transparent; margin: 1px; min-height: 500px;} |
|---|
| 9 | #tw, #tw_p { font-size: small; } |
|---|
| 10 | #tw div div, #tw_p div div { padding: 2px; } |
|---|
| 11 | img { border: 0; } |
|---|
| 12 | .status { text-decoration: none; color: black; cursor: pointer; } |
|---|
| 13 | .date { color: #999; font-size: x-small; } |
|---|
| 14 | .fromme { background-color: #cfc; } |
|---|
| 15 | .tome { background-color: #ccf; } |
|---|
| 16 | #loading {opacity: 0.5; position: absolute; left: 50px; top: 2px; width: 220px; height: 19; } |
|---|
| 17 | #fst { position: absolute; left: 56px; top: 0px; width: 208px; } |
|---|
| 18 | #rst { position: absolute; left: 264px; top: 6px; text-decoration: none; } |
|---|
| 19 | .inreply { text-decoration: none; } |
|---|
| 20 | #rep { display: none; background-color: #fee; position: absolute; width: 90%; left: 4%; top: 200px; border: #666 4px solid; } |
|---|
| 21 | hr { margin: 0; padding: 0; } |
|---|
| 22 | form { margin: 5px; } |
|---|
| 23 | </style> |
|---|
| 24 | <script> |
|---|
| 25 | var myname = null; |
|---|
| 26 | function twAuth(a) { |
|---|
| 27 | if (a.error) return alert(a.error); |
|---|
| 28 | myname = a.screen_name; |
|---|
| 29 | } |
|---|
| 30 | </script> |
|---|
| 31 | <script src="http://twitter.com/account/verify_credentials.json?callback=twAuth"></script> |
|---|
| 32 | </head> |
|---|
| 33 | <body onLoad="init()"> |
|---|
| 34 | <iframe style="display:none;" name="tx"></iframe> |
|---|
| 35 | <a href="http://twitter.com/home" target="twitter"><b><small>Twitter</small></b></a> |
|---|
| 36 | <!--発言フォーム--> |
|---|
| 37 | <form name="frm" action="http://twitter.com/statuses/update.xml" method="POST" onSubmit="return twSend();" target="tx"> |
|---|
| 38 | <small><input id="fst" type="text" name="status"><input type="hidden" name="source" value="twicli"> |
|---|
| 39 | <a id="rst" href="javascript:void(resetFrm())"><img src="clr.png"></a> |
|---|
| 40 | </small><img id="loading" src="loading.gif"></form> |
|---|
| 41 | <!--favフォーム--> |
|---|
| 42 | <form name="favf" action="" method="POST" target="tx"></form> |
|---|
| 43 | |
|---|
| 44 | <div id="tw"><hr></div> |
|---|
| 45 | <div id="rep"><a href="javascript:closeRep()" style="color: red;">[x]</a><p id="reps"><p></div> |
|---|
| 46 | |
|---|
| 47 | <script> |
|---|
| 48 | var nr_limit = 50; // 表示する更新回数の上限 |
|---|
| 49 | |
|---|
| 50 | var seq = (new Date).getTime(); |
|---|
| 51 | var since_id = false; |
|---|
| 52 | var in_reply_to_user = false; |
|---|
| 53 | var myname = false; |
|---|
| 54 | var update_ele = false; |
|---|
| 55 | var reply_ele = false; |
|---|
| 56 | var rep_top = 0; |
|---|
| 57 | var failover_timeout = false; |
|---|
| 58 | // クロスドメインなJavaScriptを呼び出し |
|---|
| 59 | function loadXDomainScript(url, ele) { |
|---|
| 60 | if (ele) |
|---|
| 61 | ele.parentNode.removeChild(ele); |
|---|
| 62 | ele = document.createElement("script"); |
|---|
| 63 | ele.src = url; |
|---|
| 64 | document.body.appendChild(ele); |
|---|
| 65 | return ele; |
|---|
| 66 | } |
|---|
| 67 | // 要素の位置を取得 |
|---|
| 68 | function cumulativeOffset(ele) { |
|---|
| 69 | var top = 0, left = 0; |
|---|
| 70 | do { |
|---|
| 71 | top += ele.offsetTop || 0; |
|---|
| 72 | left += ele.offsetLeft || 0; |
|---|
| 73 | ele = ele.offsetParent; |
|---|
| 74 | } while (ele); |
|---|
| 75 | return [left, top]; |
|---|
| 76 | } |
|---|
| 77 | // ローディング表示ON、twit更新 |
|---|
| 78 | function reload(to) { |
|---|
| 79 | document.getElementById("loading").style.display = "block"; |
|---|
| 80 | setTimeout("update()", to); |
|---|
| 81 | } |
|---|
| 82 | // 発言 |
|---|
| 83 | function twSend() { |
|---|
| 84 | if (document.frm.status.value == '') { // 空欄であればTimeline更新のみ |
|---|
| 85 | reload(0); |
|---|
| 86 | return false; |
|---|
| 87 | } |
|---|
| 88 | if (!document.frm.status.value.match("@"+in_reply_to_user)) // @ユーザが含まれているときのみ返信先を指定 |
|---|
| 89 | setReplyId(false); |
|---|
| 90 | in_reply_to_user = ""; |
|---|
| 91 | document.frm.status.select(); |
|---|
| 92 | setTimeout(resetFrm, 10); // twit送信後にフォームをクリア |
|---|
| 93 | reload(3000); // 送信3秒後にTimelineを更新 |
|---|
| 94 | } |
|---|
| 95 | // フォームの初期化 |
|---|
| 96 | function resetFrm() { |
|---|
| 97 | document.frm.reset(); |
|---|
| 98 | setReplyId(false); |
|---|
| 99 | } |
|---|
| 100 | // reply先の設定/解除 |
|---|
| 101 | function setReplyId(id) { |
|---|
| 102 | var repid = document.getElementById('in_reply_to_status_id'); |
|---|
| 103 | if (repid) |
|---|
| 104 | repid.parentNode.removeChild(repid); |
|---|
| 105 | if (id) { |
|---|
| 106 | repid = document.createElement('input'); |
|---|
| 107 | repid.type = 'hidden'; |
|---|
| 108 | repid.id = repid.name = 'in_reply_to_status_id'; |
|---|
| 109 | repid.value = id; |
|---|
| 110 | document.frm.appendChild(repid) |
|---|
| 111 | } |
|---|
| 112 | } |
|---|
| 113 | // reply |
|---|
| 114 | function replyTo(user, id) { |
|---|
| 115 | if (window.getSelection().toString() == '') { |
|---|
| 116 | in_reply_to_user = user; |
|---|
| 117 | document.frm.status.value += "@" + user + " "; |
|---|
| 118 | setReplyId(id); |
|---|
| 119 | document.frm.status.focus(); |
|---|
| 120 | } |
|---|
| 121 | } |
|---|
| 122 | // reply先を表示 |
|---|
| 123 | function dispReply(id,ele) { |
|---|
| 124 | var d = document.getElementById(id); |
|---|
| 125 | if (!d) { |
|---|
| 126 | rep_top = cumulativeOffset(ele)[1] + 20; |
|---|
| 127 | reply_ele = loadXDomainScript('http://twitter.com/statuses/show/'+id+'.json?seq='+(seq++)+'&callback=dispReply2', reply_ele); |
|---|
| 128 | return; |
|---|
| 129 | } |
|---|
| 130 | closeRep(); |
|---|
| 131 | var top = cumulativeOffset(d)[1]; |
|---|
| 132 | var h = d.offsetHeight; |
|---|
| 133 | var sc_top = window.pageYOffset || document.body.scrollTop; |
|---|
| 134 | var win_h = window.innerHeight; |
|---|
| 135 | if (top < sc_top) scrollTo(0, top); |
|---|
| 136 | if (sc_top+win_h < top+h) scrollTo(0, top+h-win_h); |
|---|
| 137 | var old = d.style.backgroundColor; |
|---|
| 138 | d.style.backgroundColor = '#fcc'; |
|---|
| 139 | setTimeout(function(){d.style.backgroundColor = old}, 2000); |
|---|
| 140 | } |
|---|
| 141 | // reply先をoverlay表示 (Timelineに無い場合) |
|---|
| 142 | function dispReply2(tw) { |
|---|
| 143 | document.getElementById('reps').innerHTML = makeHTML(tw); |
|---|
| 144 | document.getElementById('rep').style.display = "block"; |
|---|
| 145 | document.getElementById('rep').style.top = rep_top; |
|---|
| 146 | } |
|---|
| 147 | // replyのoverlay表示を閉じる |
|---|
| 148 | function closeRep() { |
|---|
| 149 | document.getElementById('rep').style.display='none'; |
|---|
| 150 | } |
|---|
| 151 | // 最新twitを取得 |
|---|
| 152 | function update() { |
|---|
| 153 | update_ele = loadXDomainScript('http://twitter.com/statuses/friends_timeline.json?seq=' + (seq++) + |
|---|
| 154 | '&callback=twShow&count=200' + (since_id ? '&since_id='+since_id : ''), update_ele); |
|---|
| 155 | } |
|---|
| 156 | // twitのHTML表現を生成 |
|---|
| 157 | function makeHTML(tw) { |
|---|
| 158 | function d2(dig) { return (dig>9?"":"0") + dig } |
|---|
| 159 | var d = new Date(tw.created_at); |
|---|
| 160 | d = d.getDate() + "日 " + d.getHours() + ":" + d2(d.getMinutes()) + ":" + d2(d.getSeconds()); |
|---|
| 161 | //ユーザアイコン |
|---|
| 162 | return (tw.user.url ? '<a target="twitter" href="'+tw.user.url+'">' : '') + |
|---|
| 163 | '<img border="0" align="left" width="24" height="24" alt="' + tw.user.name + |
|---|
| 164 | '" src="' + tw.user.profile_image_url + '">' + (tw.user.url ? '</a>' : '') + |
|---|
| 165 | //fav |
|---|
| 166 | '<img align="right" src="http://assets3.twitter.com/images/icon_star_'+(tw.favorited?'full':'empty')+'.gif" ' + |
|---|
| 167 | 'onClick="fav(this,' + tw.id + ')">' + |
|---|
| 168 | //名前(Twitter本家のリンク) |
|---|
| 169 | '<a target="twitter" href="http://twitter.com/' + tw.user.screen_name + '">' + tw.user.screen_name + |
|---|
| 170 | /*プロフィールの名前*/ (tw.user.name!=tw.user.screen_name ? '('+tw.user.name+')' : '') + '</a>' + |
|---|
| 171 | /* protected? */ (tw.user.protected ? '<img src="http://assets0.twitter.com/images/icon_red_lock.gif">' : '') + |
|---|
| 172 | //本文クリックで@追加 |
|---|
| 173 | " <span onClick=\"replyTo('" + tw.user.screen_name + "'," + tw.id + ")\" class=\"status\">" + |
|---|
| 174 | //本文 (https〜をリンクに置換 + @をtwitter本家へのリンクに置換) |
|---|
| 175 | tw.text.replace(/(https?:\/\/[^ ]*)/g, " <a onClick=\"event.stopPropagation();\" target=\"_blank\" href=\"$1\">$1</a>") |
|---|
| 176 | .replace(/@([0-9A-Za-z_\-]+)/g, "<a onClick=\"event.stopPropagation();\" target=\"twitter\" " + |
|---|
| 177 | "href=\"http://twitter.com/$1\">@$1</a>") + '</span>' + |
|---|
| 178 | //日付 |
|---|
| 179 | '<span class="date">' + d + '</span>' + |
|---|
| 180 | //返信元へのリンク |
|---|
| 181 | (tw.in_reply_to_status_id ? ' <a class="inreply" href="#" onClick="dispReply('+tw.in_reply_to_status_id+',this);return false;">☞</a>' : '') + |
|---|
| 182 | '<br clear="left">'; |
|---|
| 183 | } |
|---|
| 184 | // favoriteの追加(f=true)/削除(f=false) |
|---|
| 185 | function fav(img, id) { |
|---|
| 186 | var f = img.src.match('empty'); |
|---|
| 187 | document.favf.action = 'http://twitter.com/favourings/' + (f ? 'create' : 'destroy') + '/' + id + '.xml'; |
|---|
| 188 | document.favf.submit(); |
|---|
| 189 | img.src = 'http://assets3.twitter.com/images/icon_star_' + (f ? 'full' : 'empty') + '.gif'; |
|---|
| 190 | } |
|---|
| 191 | // 受信twitを表示 |
|---|
| 192 | function twShow(tw) { |
|---|
| 193 | document.getElementById('loading').style.display='none'; |
|---|
| 194 | if (failover_timeout) clearTimeout(failover_timeout); |
|---|
| 195 | failover_timeout = false; |
|---|
| 196 | var len = tw.length; |
|---|
| 197 | if (len == 0) return; |
|---|
| 198 | var twNode = document.getElementById('tw'); |
|---|
| 199 | var pNode = document.createElement('div'); |
|---|
| 200 | var dummy = pNode.appendChild(document.createElement('div')); |
|---|
| 201 | for (i = len-1; i >= 0; i--) { |
|---|
| 202 | if (since_id && since_id >= tw[i].id) |
|---|
| 203 | continue; |
|---|
| 204 | if (tw[i].user.profile_image_url) { |
|---|
| 205 | var s = document.createElement('div'); |
|---|
| 206 | s.id = tw[i].id; |
|---|
| 207 | s.innerHTML = makeHTML(tw[i]); |
|---|
| 208 | if (tw[i].text.match('@'+myname+' ')) |
|---|
| 209 | s.className = "tome"; |
|---|
| 210 | if (tw[i].user.screen_name == myname) |
|---|
| 211 | s.className = "fromme"; |
|---|
| 212 | pNode.insertBefore(s, pNode.childNodes[0]); |
|---|
| 213 | pNode.insertBefore(document.createElement('hr'), s); |
|---|
| 214 | } |
|---|
| 215 | } |
|---|
| 216 | pNode.removeChild(dummy); |
|---|
| 217 | twNode.insertBefore(pNode, twNode.childNodes[0]); |
|---|
| 218 | var maxH = pNode.clientHeight; |
|---|
| 219 | pNode.style.overflow = "hidden"; |
|---|
| 220 | if ((window.pageYOffset || document.body.scrollTop) > 10) |
|---|
| 221 | scrollBy(0, maxH); |
|---|
| 222 | else |
|---|
| 223 | animate(pNode, maxH, (new Date).getTime()); |
|---|
| 224 | while (twNode.childNodes.length > nr_limit) |
|---|
| 225 | twNode.removeChild(twNode.childNodes[nr_limit]); |
|---|
| 226 | since_id = tw[0].id; |
|---|
| 227 | } |
|---|
| 228 | // 新規twitの出現アニメーション処理 |
|---|
| 229 | function animate(elem, max, start) { |
|---|
| 230 | var t = (new Date).getTime(); |
|---|
| 231 | if (start+1000 <= t) |
|---|
| 232 | return elem.style.maxHeight = 'none'; |
|---|
| 233 | elem.style.maxHeight = Math.ceil(max*(1-Math.cos((t-start)/1000*Math.PI))/2); |
|---|
| 234 | setTimeout(function(){ animate(elem, max, start) }, 50); |
|---|
| 235 | } |
|---|
| 236 | function init() { |
|---|
| 237 | setTimeout(scrollTo, 0, 0, 1); |
|---|
| 238 | // 初回アップデート |
|---|
| 239 | setTimeout(update, 0); |
|---|
| 240 | setInterval(update, 60*1000); |
|---|
| 241 | } |
|---|
| 242 | |
|---|
| 243 | // ホイールの回転でスクロール (スクロール不可のウィンドウ・フレーム内で有効) |
|---|
| 244 | function wheel(event) { |
|---|
| 245 | var delta = 0; |
|---|
| 246 | if (!event) event = window.event; |
|---|
| 247 | if (event.wheelDelta) { |
|---|
| 248 | delta = event.wheelDelta/10; |
|---|
| 249 | } else if (event.detail) |
|---|
| 250 | delta = -event.detail; |
|---|
| 251 | if (navigator.userAgent.indexOf("Firefox") != -1) |
|---|
| 252 | delta *= 10; |
|---|
| 253 | if (delta) |
|---|
| 254 | scrollBy(0, -delta); |
|---|
| 255 | if (event.preventDefault) |
|---|
| 256 | event.preventDefault(); |
|---|
| 257 | event.returnValue = false; |
|---|
| 258 | } |
|---|
| 259 | if (window.addEventListener) window.addEventListener('DOMMouseScroll', wheel, false); |
|---|
| 260 | window.onmousewheel = document.onmousewheel = wheel; |
|---|
| 261 | </script> |
|---|
| 262 | </body> |
|---|
| 263 | </html> |
|---|