<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>patches/form.TwitterRecipients.js</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -1,3 +1,10 @@
+update(Date, {
+	TIME_SECOND : 1000,
+	TIME_MINUTE : 1000 * 60,
+	TIME_HOUR   : 1000 * 60 * 60,
+	TIME_DAY    : 1000 * 60 * 60 * 24,
+})
+
 if(typeof(update)=='undefined'){
 	function update(t, s){
 		for(var p in s)</diff>
      <filename>xpi/chrome/content/library/00_prototype.js</filename>
    </modified>
    <modified>
      <diff>@@ -1071,6 +1071,53 @@ function addAround(target, methodNames, advice){
 	});
 }
 
+function cache(fn, ms){
+	var executed;
+	var res;
+	
+	var deferred = false;
+	var waiting = false;
+	var pendings;
+	return function(){
+		// &#12461;&#12515;&#12483;&#12471;&#12517;&#12364;&#21033;&#29992;&#12391;&#12365;&#12427;&#12363;?
+		var now = Date.now();
+		if(executed &amp;&amp; (executed + ms) &gt; now){
+			// Deferred&#12398;&#32080;&#26524;&#12364;&#26410;&#30906;&#23450;&#12363;?
+			if(waiting){
+				var d = new Deferred();
+				pendings.push(d);
+				
+				return d;
+			}
+			
+			return deferred? succeed(res) : res;
+		}
+		
+		executed = now;
+		res = fn.apply(null, arguments)
+		
+		if(res instanceof Deferred){
+			deferred = true;
+			waiting  = true;
+			pendings = [];
+			
+			return res.addCallback(function(result){
+				res = result;
+				waiting = false;
+				
+				// &#32080;&#26524;&#12398;&#30906;&#23450;&#24453;&#12385;&#12364;&#12354;&#12428;&#12400;&#12381;&#12428;&#12425;&#12434;&#20808;&#12395;&#21628;&#12403;&#20986;&#12377;(&#38918;&#24207;&#12364;&#36870;&#36578;&#12377;&#12427;)
+				pendings.forEach(function(d){
+					d.callback(res);
+				});
+				
+				return res;
+			});
+		} else {
+			return res;
+		}
+	}
+}
+
 /**
  * &#37197;&#21015;&#12434;&#32080;&#21512;&#12375;&#25991;&#23383;&#21015;&#12434;&#20316;&#25104;&#12377;&#12427;&#12290;
  * &#31354;&#35201;&#32032;&#12399;&#38500;&#22806;&#12373;&#12428;&#12427;&#12290;
@@ -1506,7 +1553,7 @@ function appendMenuItem(menu, label, image, hasChildren){
 	item.setAttribute('label', label);
 	
 	if(image){
-		item.setAttribute('class', 'menuitem-iconic');
+		item.setAttribute('class', hasChildren? 'menu-iconic' : 'menuitem-iconic');
 		item.setAttribute('image', image);
 	}
 	</diff>
      <filename>xpi/chrome/content/library/01_utility.js</filename>
    </modified>
    <modified>
      <diff>@@ -625,16 +625,18 @@ models.register({
 models.register({
 	name : 'Twitter',
 	ICON : 'http://twitter.com/favicon.ico',
+	URL  : 'http://twitter.com',
 	
 	check : function(ps){
 		return (/(regular|photo|quote|link|conversation|video)/).test(ps.type) &amp;&amp; !ps.file;
 	},
 	
 	post : function(ps){
+		var self = this;
 		return Twitter.getToken().addCallback(function(token){
 			// FIXME: 403&#12364;&#30330;&#29983;&#12377;&#12427;&#12371;&#12392;&#12364;&#12354;&#12387;&#12383;&#12383;&#12417; redirectionLimit:0 &#12434;&#22806;&#12377;
 			token.status = joinText([ps.item, ps.itemUrl, ps.body, ps.description], ' ', true);
-			return request('http://twitter.com/status/update', update({
+			return request(self.URL + '/status/update', update({
 				sendContent : token,
 			}));
 		});
@@ -644,9 +646,8 @@ models.register({
 		return this.addFavorite(ps.favorite.id);
 	},
 	
-	
 	getToken : function(){
-		return request('http://twitter.com/account/settings').addCallback(function(res){
+		return request(this.URL + '/account/settings').addCallback(function(res){
 			var html = res.responseText;
 			if(~html.indexOf('class=&quot;signin&quot;'))
 				throw new Error(getMessage('error.notLoggedin'));
@@ -659,25 +660,36 @@ models.register({
 	},
 	
 	remove : function(id){
+		var self = this;
 		return Twitter.getToken().addCallback(function(ps){
 			ps._method = 'delete';
-			return request('http://twitter.com/status/destroy/' + id, {
+			return request(self.URL + '/status/destroy/' + id, {
 				redirectionLimit : 0,
-				referrer : 'http://twitter.com/home',
+				referrer : self.URL + '/',
 				sendContent : ps,
 			});
 		});
 	},
 	
 	addFavorite : function(id){
+		var self = this;
 		return Twitter.getToken().addCallback(function(ps){
-			return request('http://twitter.com/favourings/create/' + id, {
+			return request(self.URL + '/favourings/create/' + id, {
 				redirectionLimit : 0,
-				referrer : 'http://twitter.com/home',
+				referrer : self.URL + '/',
 				sendContent : ps,
 			});
 		});
 	},
+	
+	getRecipients : function(){
+		var self = this;
+		return request(this.URL + '/direct_messages/recipients_list?twttr=true').addCallback(function(res){
+			return map(function([id, name]){
+				return {id:id, name:name};
+			}, evalInSandbox('(' + res.responseText + ')', self.URL));
+		});
+	},
 });
 
 </diff>
      <filename>xpi/chrome/content/library/20_model.js</filename>
    </modified>
    <modified>
      <diff>@@ -968,22 +968,31 @@ DescriptionBox.prototype = {
 		// input&#35201;&#32032;&#12398;&#21462;&#24471;&#12364;&#32066;&#12431;&#12387;&#12383;&#12425;&#21021;&#26399;&#35373;&#23450;&#12398;&#38750;&#34920;&#31034;&#29366;&#24907;&#12395;&#25147;&#12377;
 		this.elmBox.hidden = this.hidden;
 		
-		this.loadExtensionMenu();
-	},
-	
-	loadExtensionMenu : function(){
 		this.elmContext = document.getAnonymousElementByAttribute(
 			this.elmInput.parentNode, 'anonid', 'input-box-contextmenu');
+		this.elmContext.addEventListener('popupshowing', bind('onPopupShowing', this), true);
+	},
+	
+	onPopupShowing : function(event){
+		if(event.eventPhase != Event.AT_TARGET)
+			return;
 		
-		var df = document.createDocumentFragment();
 		var self = this;
+		
+		if(this.customMenus)
+			forEach(this.customMenus, removeElement);
+		this.customMenus = [];
+		
+		var df = document.createDocumentFragment();
 		(function(menus, parent){
 			var me = arguments.callee;
 			menus.forEach(function(menu){
 				var elmItem = appendMenuItem(parent, menu.name, menu.icon, !!menu.children);
+				self.customMenus.push(elmItem);
+				
 				if(menu.execute){
 					elmItem.addEventListener('command', function(){
-						var d = menu.execute(self.elmDescription);
+						var d = menu.execute(self.elmDescription, self);
 						
 						// &#38750;&#21516;&#26399;&#20966;&#29702;&#12398;&#22580;&#21512;&#12289;&#12459;&#12540;&#12477;&#12523;&#12434;&#30722;&#26178;&#35336;&#12395;&#12377;&#12427;
 						if(d instanceof Deferred){
@@ -1000,8 +1009,7 @@ DescriptionBox.prototype = {
 					me(menu.children, elmItem.appendChild(document.createElement('menupopup')));
 			});
 		})(QuickPostForm.descriptionContextMenus, df);
-		
-		appendMenuItem(df, '----');
+		self.customMenus.push(appendMenuItem(df, '----'));
 		
 		this.elmContext.insertBefore(df, this.elmContext.firstChild);
 	},
@@ -1014,6 +1022,18 @@ DescriptionBox.prototype = {
 		return this.elmDescription.value;
 	},
 	
+	replaceSelection : function(text){
+		var elm = this.elmDescription;
+		var value = elm.value;
+		var start = elm.selectionStart;
+		
+		elm.value = 
+			value.substr(0, elm.selectionStart) + 
+			text + 
+			value.substr(elm.selectionEnd);
+		elm.selectionStart = elm.selectionEnd = start + text.length;
+	},
+	
 	refreshLength : function(){
 		this.elmLength.value = this.elmDescription.value.length;
 	},
@@ -1083,20 +1103,11 @@ DescriptionBox.prototype = {
 		if(sel.isCollapsed || reason != ISelectionListener.MOUSEUP_REASON)
 			return;
 		
-		var elm = this.elmDescription;
-		var value = elm.value;
-		var start = elm.selectionStart;
-		sel = sel.toString().trim();
-		
-		elm.value = 
-			value.substr(0, elm.selectionStart) + 
-			sel + 
-			value.substr(elm.selectionEnd);
-		elm.selectionStart = elm.selectionEnd = start + sel.length;
+		this.replaceSelection(sel.toString().trim());
 		
 		// &#21029;&#12454;&#12451;&#12531;&#12489;&#12454;&#12398;&#12383;&#12417;&#19968;&#24230;&#12454;&#12451;&#12531;&#12489;&#12454;&#12398;&#12501;&#12457;&#12540;&#12459;&#12473;&#12418;&#25147;&#12377;
 		window.focus();
-		elm.focus();
+		this.elmDescription.focus();
 		
 		// value&#12434;&#22793;&#12360;&#12427;&#12392;&#20808;&#38957;&#12395;&#25147;&#12387;&#12390;&#12375;&#12414;&#12358;&#12383;&#12417;&#26368;&#24460;&#12395;&#31227;&#21205;&#12375;&#30452;&#12377;
 		this.elmInput.scrollTop = this.elmInput.scrollHeight;</diff>
      <filename>xpi/chrome/content/quickPostForm.js</filename>
    </modified>
    <modified>
      <diff>@@ -276,9 +276,17 @@ try {
 			description : '&#12509;&#12540;&#12479;&#12523;&#12469;&#12452;&#12488;',
 		});
 	});
+	
 	d.addCallback(function(){
 		return Twitter.remove(847473592);
 	});
+	d.addCallback(function(){
+		return Twitter.getRecipients();
+	});
+	d.addCallback(function(recipients){
+		ok(recipients.length &gt; 3);
+		ok(recipients[0].name);
+	});
 	*/
 	
 	/*</diff>
      <filename>xpi/chrome/content/test/test_model.html</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>1a1fefd7f403c52545805f7191d774b9a9807f76</id>
    </parent>
  </parents>
  <author>
    <name>to</name>
    <email>ryutaro.kamitsu@gmail.com</email>
  </author>
  <url>http://github.com/to/tombloo/commit/bc590079acf783c4d9f970936b1bcb36c58328d6</url>
  <id>bc590079acf783c4d9f970936b1bcb36c58328d6</id>
  <committed-date>2009-05-09T08:52:52-07:00</committed-date>
  <authored-date>2009-05-09T08:52:52-07:00</authored-date>
  <message> * &#12501;&#12457;&#12540;&#12512;&#12467;&#12531;&#12486;&#12461;&#12473;&#12488;&#12513;&#12491;&#12517;&#12540;&#25313;&#24373;&#12395;Twitter&#12398;&#23451;&#20808;&#12522;&#12473;&#12488;&#36861;&#21152;(&#23455;&#39443;)
 * &#12501;&#12457;&#12540;&#12512;&#12467;&#12531;&#12486;&#12461;&#12473;&#12488;&#12398;&#26356;&#26032;&#12434;&#12509;&#12483;&#12503;&#12450;&#12483;&#12503;&#34920;&#31034;&#26178;&#12395;&#22793;&#26356;(&#19978;&#35352;&#12395;&#20184;&#38543;)
 * &#38306;&#25968;&#12398;&#12461;&#12515;&#12483;&#12471;&#12517;&#21270;&#38306;&#25968;&#36861;&#21152;(&#26399;&#38480;&#20184;&#12365;&#12398;&#12513;&#12514;&#21270;/Deferred&#23550;&#24540;)</message>
  <tree>62dd33b1b0d71848f359ac542ee6022dc8aefbcb</tree>
  <committer>
    <name>to</name>
    <email>ryutaro.kamitsu@gmail.com</email>
  </committer>
</commit>
