diff options
-rw-r--r-- | twittperator/twlist-win.tw | 460 |
1 files changed, 460 insertions, 0 deletions
diff --git a/twittperator/twlist-win.tw b/twittperator/twlist-win.tw new file mode 100644 index 0000000..92ecc55 --- /dev/null +++ b/twittperator/twlist-win.tw @@ -0,0 +1,460 @@ +/* +ほぼ、マウス前提なので、Vimperatorらしからぬプラグインですが... +短縮URLはアイテムを選択すると展開されるはず、 +あと、画像っぽいURLも展開する(まだ出来るものが少ない) + +ToDo: YouTubeとかも展開出来るとイイね! + +== Settings == + +g:twittperator_plugin_twlist_win = 1 + $RUNTIMEDIR/plugin/twittperator に入れている場合は設定してください。 + +g:twittperator_screen_name = "<your screen name>" + +g:twlist_max_rows = num + 表示するアイテム数 (default: 50) + +== Command == + +:showtwin + ウィンドウの表示/非表示 + ToDo: 表示位置と幅、高さを維持したい + + */ +let win = null; +let winXML = +<window id="twlist-window" + pack="start" + title="Twittperator" + width="500" + height="600" + onload="init()" + onunload="twlist.onClose()" + xmlns={XUL} + xmlns:xhtml={XHTML}> +<script type="application/javascript; version=1.8"><![CDATA[ + const XUL = new Namespace("xul", "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"), + XHTML = new Namespace("xhtml", "http://www.w3.org/1999/xhtml"); + var liberator, twlist, timelineBox, mentionsBox, dmBox, tabBox; + function $(id) document.getElementById(id); + function init(){ + liberator = window.arguments[0]; + twlist = window.arguments[1]; + timelineBox = $("twlist-timeline"); + mentionsBox = $("twlist-mentions"); + dmBox = $("twlist-dm"); + tabBox = $("twlist-tabbox"); + + liberator.plugins.twittperator.Tweets.slice(0,twlist.maxRows).reverse().forEach(add); + } + function keepMaxRows(box) { + if (box.getRowCount() > twlist.maxRows){ + box.removeChild(box.lastChild); + } + } + function add (msg) { + let xml = twlist.getItemXML(msg); + let dom = xmlToDom(xml, XUL); + if ("direct_message" in msg){ + dmBox.insertBefore(dom, dmBox.firstChild); + keepMaxRows(dmBox); + setNewSymbol(2); + } else { + timelineBox.insertBefore(dom, timelineBox.firstChild); + if (twlist.screenName && msg.in_reply_to_screen_name == twlist.screenName) { + let repDom = dom.cloneNode(true); + mentionsBox.insertBefore(repDom, mentionsBox.firstChild); + keepMaxRows(mentionsBox); + setNewSymbol(1); + } + keepMaxRows(timelineBox); + } + } + function xmlToDom(xml, xmlns) { + XML.prettyPrinting = true; + XML.ignoreWhitespace = true; + var doc = (new DOMParser).parseFromString( + '<root xmlns="' + xmlns + '">' + xml.toXMLString() + "</root>", + "application/xml"); + var imported = document.importNode(doc.documentElement, true); + var range = document.createRange(); + range.selectNodeContents(imported); + var fragment = range.extractContents(); + range.detach(); + return fragment.childNodes.length > 1 ? fragment : fragment.firstChild; + } + function setNewSymbol(index){ + let tab = tabBox.tabs.getItemAtIndex(index); + if (tab.label.indexOf("*") == -1){ + tab.label = "*" + tab.label; + } + } + function onTabSelect(evt){ + let tab = $("twlist-tabbox").tabs.selectedItem; + if (tab.label.indexOf("*") == 0){ + tab.label = tab.label.substr(1); + } + } +]]></script> +<vbox id="twlist-box" flex="1"> + <tabbox id="twlist-tabbox" flex="1"> + <tabs id="twlist-tabs" onselect="onTabSelect(event)"> + <tab label="TimeLine"/> + <tab label="Mentions"/> + <tab label="DM"/> + </tabs> + <tabpanels id="twlist-panels" flex="1" style="background: transparent;"> + <tabpanel flex="1"> + <richlistbox id="twlist-timeline" contextmenu="contentAreaContextMenu" + flex="1" onselect="twlist.onSelect(event)"/> + </tabpanel> + <tabpanel flex="1"> + <richlistbox id="twlist-mentions" flex="1" + onselect="twlist.onSelect(event)"/> + </tabpanel> + <tabpanel flex="1"> + <richlistbox id="twlist-dm" flex="1" + onselect="twlist.onSelect(event)"/> + </tabpanel> + </tabpanels> + </tabbox> +</vbox> +<statusbar id="status-bar"> + <spacer flex="1"/> +</statusbar> +</window>.toXMLString(); + +let URL = "data:application/vnd.mozilla.xul+xml;base64," + + btoa('<?xml-stylesheet type="text/css" href="chrome://browser/skin/"?>' + winXML); + +function setStyleSheet() { + styles.addSheet(true, "twlist-styles", "data:*", + <><![CDATA[ + #twlist-panels { + background-color: transparent !important; + border: none !important; + padding: 0 !important; + } + .twlist-item-content { + -moz-user-select: -moz-all; + border-bottom: solid thin silver; + } + .twlist-item-content[selected=true] { + background-color: rgb(240,240,240) !important; + color: -moz-fieldtext !important; + } + .twlist-rt-mark { + color: white; font-weight: bold; background-color: gray; + padding: 2px 5px; margin: 0; + -moz-border-radius: 4px; + } + .twlist-reply, .twlist-retweet { + color: white; font-weight: bold; background-color: gray; + padding: 2px; margin:0; + -moz-border-radius: 2px; + } + .twlist-fav { + color: yellow; background-color: #E0E0E0; + padding: 2px; margin:0; + -moz-border-radius: 2px; + } + .twlist-text { margin: 2px 1em; } + .twlist-text>label { margin: 1px 2px 2px 2px !important; } + .twlist-screenname { font-weight: bold; } + .twlist-link { color: -moz-hyperlinktext; } + .twlist-link:hover { chrome://browser/content/browser.xul cursor: pointer !important; } + .twlist-hash { color: DarkGreen !important; } + .twlist-image { max-height: 300px; border:thin solid; } + ]]></>.toString()); +} + +function TweetItem(msg){ this.init.call(this, msg); } +TweetItem.prototype = { + init: function(msg) { + }, +}; +function getItemXML(msg) { + XML.prettyPrinting = true; + XML.ignoreWhitespace = true; + let xml; + if ("direct_message" in msg) { + xml = <richlistitem value={msg.direct_message.id} + searchlabel={msg.direct_message.sender_screen_name} + xmlns={XUL} class="twlist-item-content twlist-item-dm"> + <vbox class="twlist-profile-image"> + <image src={msg.direct_message.sender.profile_image_url} width="48" height="48"/> + <spacer flex="1"/> + </vbox> + <vbox flex="1" class="twlist-content"> + <hbox> + <label class="twlist-screenname">{msg.direct_message.sender.screen_name}</label> + <hbox class="twlist-matainfo"> + <label class="twlist-username">{"(" + msg.direct_message.sender.name + ")"}</label> + <label>{(new Date(msg.direct_message.sender.created_at)).toLocaleFormat()}</label> + </hbox> + </hbox> + {formatText(msg.direct_message.text)} + </vbox> + <vbox> + <spacer flex="1"/> + <label value={"\u21A9"} class="twlist-reply" onclick="twlist.onReply(this, true)"/> + <spacer flex="1"/> + </vbox> + </richlistitem>; + } else if ("retweeted_status" in msg) { + xml = + <richlistitem value={msg.retweeted_status.id} + searchlabel={msg.retweeted_status.user.screen_name+"#"+msg.retweeted_status.id} + xmlns={XUL} class="twlist-item-content twlist-item-rt"> + <vbox class="twlist-profile-image"> + <image src={msg.retweeted_status.user.profile_image_url} width="48" height="48"/> + <spacer flex="1"/> + </vbox> + <vbox flex="1" class="twlist-content"> + <hbox> + <label value={"\u21BB"} class="twlist-rt-mark"/> + <label class="twlist-screenname">{msg.retweeted_status.user.screen_name}</label> + <hbox class="twlist-metainfo"> + <label class="twlist-username">{"(" + msg.retweeted_status.user.name + ")"}</label> + <label>{(new Date(msg.created_at)).toLocaleFormat()}</label> + <label>{"By " + msg.user.screen_name}</label> + </hbox> + </hbox> + {formatText(msg.retweeted_status.text)} + </vbox> + <vbox> + <spacer flex="1"/> + <label value={"\u21A9"} class="twlist-reply" onclick="twlist.onReply(this)"/> + <label value={msg.favorited ? "\u2605" : "\u2606"} class="twlist-fav" onclick="twlist.onFav(this)"/> + <label value={"\u21BB"} class="twlist-retweet" onclick="twlist.onRetweet(this)"/> + <spacer flex="1"/> + </vbox> + </richlistitem>; + } else { + xml = + <richlistitem value={msg.id} searchlabel={msg.user.screen_name+"#"+msg.id} + xmlns={XUL} class="twlist-item-content"> + <vbox class="twlist-profile-image"> + <image src={msg.user.profile_image_url} width="48" height="48"/> + <spacer flex="1"/> + </vbox> + <vbox flex="1" class="twlist-content"> + <hbox> + <label class="twlist-screenname">{msg.user.screen_name}</label> + <hbox class="twlist-metainfo"> + <label class="twlist-username">{"(" + msg.user.name + ")"}</label> + <label>{(new Date(msg.created_at)).toLocaleFormat()}</label> + </hbox> + </hbox> + {formatText(msg.text)} + </vbox> + <vbox> + <spacer flex="1"/> + <label value={"\u21A9"} class="twlist-reply" onclick="twlist.onReply(this)"/> + <label value={msg.favorited ? "\u2605" : "\u2606"} class="twlist-fav" onclick="twlist.onFav(this)"/> + <label value={"\u21BB"} class="twlist-retweet" onclick="twlist.onRetweet(this)"/> + <spacer flex="1"/> + </vbox> + </richlistitem>; + } + return xml; +} + +function onLoad () { + let gv = liberator.globalVariables; + __context__.__defineGetter__("screenName", function() gv.twittperator_screen_name || ""); + __context__.__defineGetter__("maxRows", function() gv.twlist_max_rows || 50); + + setStyleSheet(); + + plugins.twittperator.ChirpUserStream.addListener(streamListener); + + commands.addUserCommand(["showtwin"], "popup/hide twittperator window", + function(arg){ + if (!win) { + open() + } else { + win.close(); + } + },{ + bang: true + }, true); +} + +function open(){ + win = openDialog(URL, null, "chrome", liberator, __context__ ); +} +function onClose(){ + win = null; +} + +function onUnload () { + if (win) + win.close(); + plugins.twittperator.ChirpUserStream.removeListener(streamListener); +} + +function streamListener(msg, raw) { + if (!win) + return; + if ((msg.text && msg.user) || ("direct_message" in msg)) { + win.add(msg); + } +} +function getMedia (uri) { + if (/\.gif$|\.jpe?g$|\.pi?ng$/.test(uri.path)) + return ["image", uri.spec]; + switch (uri.host) { + case "twitpic.com": + return ["image", "http://twitpic.com/show/thumb" + uri.path + ".jpg"]; + case "movapic.com": + return ["image", "http://image.movapic.com/pic/m_" + uri.path.substr(uri.path.lastIndexOf("/")+1) + ".jpeg"]; + case "gyazo.com": + return ["image", uri.spec]; + case "twittgoo.com": + let elm = util.httpGet(uri.spec + "/?format=atom").responseXML.getElementsByTagName("icon")[0]; + return ["image", elm.textContent]; + case "www.flickr.com": + case "f.hatena.ne.jp": + default: + return []; + } +} +function isShortenURL (uri) { + switch (uri.host) { + case "bit.ly": + case "is.gd": + case "j.mp": + case "goo.gl": + case "htn.to": + case "tinyurl.com": + case "ff.im": + case "youtu.be": + return true; + } + return false; +} +function getRedirectedURL (aURI, aElement, aCallback){ + if (!aURI.schemeIs("http") && !aURI.schemeIs("https")) + return; + + if (isShortenURL(aURI)){ + let x = new XMLHttpRequest; + x.open("HEAD", aURI.spec, true); + x.onreadystatechange = function(){ + if (x.readyState == 4){ + aCallback.call(aElement, x.channel.URI); + } + }; + x.send(null); + } else { + aCallback.call(aElement, aURI); + } +} +function onSelect (evt) { + let item = evt.target.selectedItem; + let links = item.querySelectorAll("a.twlist-url"); + + function detectMedia (uri) { + this.setAttribute("href", uri.spec); + this.textContent = uri.spec; + let [type, src] = getMedia(uri); + if (type && src) { + switch (type) { + case "image": + if (this.hasAttribute("shown") && this.getAttribute("shown") == "true") + break; + let img = document.createElementNS(XHTML, "img"); + img.setAttribute("src", src); + img.setAttribute("class", "twlist-image"); + img.setAttribute("align", "right"); + this.parentNode.appendChild(img); + this.setAttribute("shown", "true"); + break; + default: + } + } + } + for (let i=0; i < links.length; i++) { + let elm = links[i]; + let uri = util.newURI(elm.getAttribute("href")); + getRedirectedURL(uri, elm, detectMedia); + } +} + +function formatText (str) { + str = str.trim(); + let reg = /https?:\/\/[^\s]+|[#@]\w+/g; + XML.ignoreWhitespace = false; + let m, i = 0, buf = "", x = <xhtml:p class="twlist-text" xmlns:xhtml={XHTML}/>; + while((m=reg.exec(str))){ + buf = str.substring(i, m.index); + if (buf) + x.appendChild(buf); + let class = "twlist-link", href = ""; + switch (m[0].charAt(0)){ + case "@": + class += " twlist-user"; + href = "http://twitter.com/" + m[0].substr(1); + break; + case "#": + class += " twlist-hash"; + href = "http://twitter.com/search?q=%23" + m[0].substr(1); + break; + default: + class += " twlist-url"; + href = m[0]; + } + x.appendChild(<xhtml:a class={class} href={href} + onclick="twlist.onClick(event)" xmlns:xhtml={XHTML}>{m[0]}</xhtml:a>); + i=reg.lastIndex; + } + buf = str.substr(i); + if (buf) + x.appendChild(buf); + return x; +} + +function onClick (evt) { + if (evt.button == 2) + return; + evt.preventDefault(); + evt.stopPropagation(); + let where = (evt.ctrlKey || evt.button == 1) ? liberator.NEW_TAB : liberator.CURRENT_TAB; + let url = evt.target.getAttribute("href"); + liberator.open(url, {where: where}); +} +function onReply (elm, isDirectMessage) { + let item = elm.parentNode.parentNode; + let label = item.getAttribute("searchlabel"); + let cmd = "tw " + (isDirectMessage ? "D @" : "@") + label + " "; + commandline.open(":", cmd, modes.EX); + window.focus(); +} +function onRetweet(elm){ + let id = elm.parentNode.parentNode.value; + plugins.twittperator.OAuth.post("http://api.twitter.com/1/statues/retweet/" + id + ".json", + null, function(text){ + }); +} +function onFav (elm) { + let id = elm.parentNode.parentNode.value; + let fav = elm.value; + if (fav == "\u2605") { + plugins.twittperator.OAuth.post("http://api.twitter.com/1/favorites/destroy/" + id + ".json", + null, function(text){ + elm.value = "\u2606"; + }); + } else { + plugins.twittperator.OAuth.post("http://api.twitter.com/1/favorites/create/" + id + ".json", + null, function(text){ + elm.value = "\u2605"; + }); + } +} + +onLoad(); + + +// vim: sw=2 ts=2 et filetype=javascript: |