2 // @name A Bit Better RTM
3 // @namespace http://www.rememberthemilk.com
4 // @description Small improvements for Remember The Milk: display list tabs to the left; display tasks count; GoTo & MoveTo list quickly;
5 // @include http://www.rememberthemilk.com/*
6 // @include https://www.rememberthemilk.com/*
10 function Autocomplete(inputField
, box
, autocompleteStore
, callback
)
12 this.inputField
= inputField
;
14 this.autocompleteList
= new AutocompleteList(inputField
, autocompleteStore
, this);
15 this.callback
= callback
;
20 Autocomplete
.prototype.show = function()
22 this.box
.wrappedJSObject
.style
.display
= "block";
23 this.inputField
&& this.inputField
.focus();
26 Autocomplete
.prototype.hide = function()
28 this.box
.wrappedJSObject
.style
.display
= "none";
29 this.inputField
&& (this.inputField
.value
= "");
30 this.autocompleteList
.clearOutput();
33 Autocomplete
.prototype.doCallback = function()
35 var completion
= this.autocompleteList
.getCurrentCompletion();
37 this.callback(completion
);
40 Autocomplete
.prototype.bind = function()
42 var autocompleteList
= this.autocompleteList
;
43 var inputField
= this.inputField
;
46 var handleKeyPressEvent = function(event
)
48 currentText
= inputField
.value
;
49 if (event
.keyCode
== 9) // Tab
51 if (window
.wrappedJSObject
.utility
)
52 window
.wrappedJSObject
.utility
.stopEvent(event
);
55 else if (autocompleteList
.isVisible
)
57 if (event
.keyCode
== 13)// Enter
58 autocompleteList
.parent
.doCallback();
59 if (event
.keyCode
== 40)// Key down
60 autocompleteList
.highlightNextCompletion();
61 else if (event
.keyCode
== 38) // Key up
62 autocompleteList
.highlightPreviousCompletion();
63 else if (event
.keyCode
== 27) // Esc
64 autocompleteList
.clearOutput();
66 else if (event
.keyCode
== 27)
67 autocompleteList
.parent
.hide();
71 var handleKeyUpEvent = function(event
)
73 if (currentText
=== null || currentText
!= inputField
.value
)
74 autocompleteList
.update();
76 var handleClickEvent = function(event
)
78 if (autocompleteList
.isVisible
)
79 autocompleteList
.hideOutput();
81 if (window
.wrappedJSObject
.utility
)
82 window
.wrappedJSObject
.utility
.stopEvent(event
);
85 this.inputField
.setAttribute("autocomplete", "off");
86 this.inputField
.addEventListener("keypress", handleKeyPressEvent
, false);
87 this.inputField
.addEventListener("keyup", handleKeyUpEvent
, false);
88 this.box
.addEventListener("click", handleClickEvent
, false);
89 this.autocompleteList
.createOutput();
92 function AutocompleteList(inputField
, autocompleteStore
, parent
)
94 this.inputField
= inputField
;
95 this.autocompleteStore
= autocompleteStore
;
98 this.completions
= null;
100 this.isVisible
= false;
103 AutocompleteList
.prototype.createOutput = function()
105 if (!this.inputField
) return;
107 this.output
= document
.createElement('div');
108 this.output
.setAttribute('id','autoCompleteList_' + this.inputField
.id
);
109 this.output
.style
.border
= "solid 1px black";
110 this.output
.style
.background
= "white";
111 this.output
.style
.position
= "absolute";
112 this.output
.style
.width
= "150px";
113 this.output
.style
.visibility
= "hidden";
115 this.inputField
.parentNode
.insertBefore(this.output
, this.inputField
.nextSibling
);
118 AutocompleteList
.prototype.showOutput = function()
120 this.output
&& (this.output
.style
.visibility
= "visible");
121 this.isVisible
= true;
124 AutocompleteList
.prototype.hideOutput = function()
126 this.output
&& (this.output
.style
.visibility
= "hidden");
127 this.isVisible
= false;
130 AutocompleteList
.prototype.clearOutput = function()
132 while(this.output
&& this.output
.childNodes
.length
)
133 this.output
.removeChild(this.output
.firstChild
);
139 AutocompleteList
.prototype.addCompletion = function(completion
)
141 var autocompleteList
= this;
143 var mouseOverHandler = function(event
)
145 autocompleteList
.position
= this.position
;
146 autocompleteList
.highlightCompletion();
149 var mouseClickHandler = function(event
)
151 autocompleteList
.parent
.doCallback();
154 var completionBox
= document
.createElement("div");
155 completionBox
.style
.textAlign
= "left";
156 completionBox
.style
.paddingLeft
= "2px";
157 completionBox
.appendChild(document
.createTextNode(completion
));
158 completionBox
.wrappedJSObject
.position
= this.output
.childNodes
.length
;
159 completionBox
.addEventListener("mouseover", mouseOverHandler
, false);
160 completionBox
.addEventListener("click", mouseClickHandler
, false);
161 this.output
.appendChild(completionBox
);
164 AutocompleteList
.prototype.highlightCompletion = function()
166 if (this.output
&& this.output
.childNodes
)
168 for (var i
= 0; i
< this.output
.childNodes
.length
; ++i
)
170 if (i
== this.position
)
172 this.output
.childNodes
[i
].style
.color
= "white";
173 this.output
.childNodes
[i
].style
.background
= "#316ac5";
177 this.output
.childNodes
[i
].style
.color
= "black";
178 this.output
.childNodes
[i
].style
.background
= "white";
184 AutocompleteList
.prototype.highlightNextCompletion = function()
186 if (this.completions
&& this.completions
.length
> 0 && this.position
< this.completions
.length
- 1)
189 this.highlightCompletion();
193 AutocompleteList
.prototype.highlightPreviousCompletion = function()
195 if (this.completions
&& this.completions
.length
> 1 && this.position
> 0)
198 this.highlightCompletion();
202 AutocompleteList
.prototype.getCurrentCompletion = function()
204 if (this.completions
&& this.completions
.length
> 0)
205 return this.completions
[this.position
];
210 AutocompleteList
.prototype.update = function()
212 if (this.inputField
.value
.length
> 0)
214 this.completions
= this.autocompleteStore
.getCompletions(this.inputField
.value
);
215 if (this.completions
&& this.completions
.length
> 0)
218 for (var i
= 0; i
< this.completions
.length
; ++i
)
219 this.addCompletion(this.completions
[i
]);
222 this.highlightNextCompletion();
237 function ListAutocompleteStore()
242 ListAutocompleteStore
.prototype.getCompletions = function(text
)
244 if (window
.wrappedJSObject
.listTabs
&& window
.wrappedJSObject
.listTabs
.entries
&& window
.wrappedJSObject
.listTabs
.entries
.length
> 0)
246 var completions
= new Array();
248 for (var i
= 0; i
< window
.wrappedJSObject
.listTabs
.entries
.length
; ++i
)
250 if (window
.wrappedJSObject
.listTabs
.entries
[i
].toLowerCase().indexOf(text
.toLowerCase()) == 0)
251 completions
.push(window
.wrappedJSObject
.listTabs
.entries
[i
]);
260 window
.wrappedJSObject
.autocompletes
= {};
262 var createAutoComplete = function(name
, callback
)
264 var autocompleteBox
= document
.createElement('div');
266 autocompleteBox
.setAttribute("id", "autocompleteBox_" + name
);
267 autocompleteBox
.style
.width
= "240px";
268 autocompleteBox
.style
.position
= "absolute";
270 autocompleteBox
.innerHTML
= '<div class="white_rbroundbox"> <div class="white_rbtop"> <div> <div></div> </div> </div> <div class="white_rbcontentwrap"> <div class="white_rbcontent"> <div class="taskcloudcontent" style="padding: 0px 5px 0px 5px; height: 17px;" id="listtabscontainer"><div style="width: 70px; font-weight: bold; float: left; height: 17px; padding-top: 1px;">' + name
+ '</div><div style="text-align: right; float: right; width: 155px; padding-right: 2px;"><input type="text" id="autocompleteInputField_' + name
+ '" name="text" style="width: 151px; ";/></div> </div> </div> </div> <div class="white_rbbot"> <div><div></div> </div> </div> </div> ';
272 document
.body
.appendChild(autocompleteBox
);
273 var autocomplete
= new Autocomplete(document
.getElementById("autocompleteInputField_" + name
),
274 document
.getElementById("autocompleteBox_" + name
),
275 new ListAutocompleteStore(),
278 function centerAutoCompleteBox()
280 var left
= window
.wrappedJSObject
.innerWidth
/ 2 - 120 + window
.wrappedJSObject
.scrollX
;
281 var top
= window
.wrappedJSObject
.innerHeight
/ 2 - 10 + window
.wrappedJSObject
.scrollY
;
283 autocompleteBox
.style
.left
= left
+ "px";
284 autocompleteBox
.style
.top
= top
+ "px";
287 centerAutoCompleteBox();
288 window
.addEventListener("scroll", centerAutoCompleteBox
, false);
289 window
.addEventListener("resize", centerAutoCompleteBox
, false);
294 var createAutoCompletes = function()
296 window
.wrappedJSObject
.autocompletes
["goTo"] = createAutoComplete("GO TO: ", selectListByName
);
297 window
.wrappedJSObject
.autocompletes
["moveTo"] = createAutoComplete("MOVE TO: ", moveToListByName
);
300 var moveToListByName = function(text
)
302 if (!window
.wrappedJSObject
.listTabs
|| !window
.wrappedJSObject
.listTabs
.entries
)
305 for (var i
= 0; i
< window
.wrappedJSObject
.listTabs
.entries
.length
; ++i
)
307 if (window
.wrappedJSObject
.listTabs
.entries
[i
].toLowerCase() == text
.toLowerCase())
308 window
.wrappedJSObject
.control
.tasksSelectionChanged("", ["", "tasks.moveTo." + window
.wrappedJSObject
.listTabs
.data
[i
][1]]);
312 var selectListByName = function(text
)
314 if (!window
.wrappedJSObject
.listTabs
|| !window
.wrappedJSObject
.listTabs
.entries
)
317 for (var i
= 0; i
< window
.wrappedJSObject
.listTabs
.entries
.length
; ++i
)
319 if (window
.wrappedJSObject
.listTabs
.entries
[i
].toLowerCase() == text
.toLowerCase())
320 window
.wrappedJSObject
.listTabs
.selectTabByPosition(i
);
324 var hideAutocompletes = function()
326 if (window
.wrappedJSObject
.autocompletes
)
327 for (var name
in window
.wrappedJSObject
.autocompletes
)
328 window
.wrappedJSObject
.autocompletes
[name
].hide();
333 var createLeftColumn = function()
335 var leftColumn
= document
.createElement('div');
336 var appView
= document
.getElementById("appview");
337 var listBox
= document
.getElementById("listbox");
339 leftColumn
.setAttribute('id','leftColumn');
340 leftColumn
.style
.cssFloat
= "left";
341 leftColumn
.style
.paddingLeft
= "5px";
342 leftColumn
.style
.paddingRight
= "8px";
343 leftColumn
.style
.display
= "none";
345 if (appView
&& listBox
)
346 appView
.insertBefore(leftColumn
, listBox
);
349 var createListTabsContainer = function()
351 var listTabsBox
= document
.createElement('div');
352 var leftColumn
= document
.getElementById("leftColumn");
356 listTabsBox
.innerHTML
= '<div class="white_rbroundbox"> <div class="white_rbtop"> <div> <div></div> </div> </div> <div class="white_rbcontentwrap"> <div class="white_rbcontent"> <table><tr><td><div class="taskcloudcontent" style="padding: 0px 5px 0px 5px;" id="listtabscontainer"> </div></td></tr></table> </div> </div> <div class="white_rbbot"> <div><div></div> </div> </div> </div> ';
358 leftColumn
.appendChild(listTabsBox
);
362 var moveListTabs = function()
364 var listTabs
= document
.getElementById("listtabs");
367 listTabs
.className
= "";
368 listTabs
.style
.width
= "100%";
370 var listTabsContainer
= document
.getElementById("listtabscontainer");
372 if (listTabsContainer
)
373 listTabsContainer
.appendChild(listTabs
);
377 var hideLeftColumn = function()
379 var content
= document
.getElementById("content");
380 var leftColumn
= document
.getElementById("leftColumn");
384 leftColumn
.style
.display
= "none";
385 content
.style
.width
= "870px";
389 var showLeftColumn = function()
391 var content
= document
.getElementById("content");
392 var leftColumn
= document
.getElementById("leftColumn");
393 var listTabsContainer
= document
.getElementById("listtabscontainer");
397 leftColumn
.style
.display
= "block";
398 leftColumn
.style
.width
= Math
.round(listTabsContainer
.clientWidth
* 1.14) + "px";
399 content
.style
.width
= (863 + leftColumn
.clientWidth
) + "px";
403 var moveTabsToTheLeft = function()
405 var content
= document
.getElementById("content");
406 var listBox
= document
.getElementById("listbox");
407 var tools_spacer
= document
.getElementById("tools_spacer");
408 var sorting
= document
.getElementById("sorting");
409 var tools
= document
.getElementById("tools");
412 createListTabsContainer();
415 var leftColumn
= document
.getElementById("leftColumn");
417 var handleViewChanged = function(d
, e
)
419 if (e
[0][1] == "Tasks")
421 else if (e
[1][1] == "Tasks")
425 if (window
.wrappedJSObject
.messageBus
)
426 window
.wrappedJSObject
.messageBus
.subscribe(handleViewChanged
, window
.wrappedJSObject
.view
.getUniqueMessageBusName() + "viewChanged");
430 tools_spacer
.style
.paddingTop
= "1px";
431 tools_spacer
.style
.borderTop
= "1px solid #CACACA";
435 sorting
.style
.marginTop
= "0px";
438 tools
.style
.paddingTop
= "5px";
440 if (content
&& window
.wrappedJSObject
.view
)
442 if (window
.wrappedJSObject
.view
.getSelected() == "Tasks")
449 var handleKeyPressEvent = function(ev
, ignoreCombo
)
451 ev
|| (ev
= window
.event
);
458 if (window
.wrappedJSObject
.utility
)
459 target
= window
.wrappedJSObject
.utility
.getEventTarget(ev
);
464 var pressed
= (ev
.charCode
) ? ev
.charCode: ((ev
.which
) ? ev
.which: ev
.keyCode
);
466 if (target
!= null && target
.type
!= null && (target
.type
== "textarea" || target
.type
== "input" || target
.type
.indexOf("select") == 0 || target
.type
== "button" || target
.type
=== "submit" || target
.type
== "text" || target
.type
== "password" || (target
.id
!= null && target
.id
== "map")))
471 if (window
.wrappedJSObject
.view
)
472 tabs
= window
.wrappedJSObject
.view
.getViewTabs();
482 if (window
.wrappedJSObject
.utility
)
483 window
.wrappedJSObject
.utility
.stopEvent(ev
);
492 if (window
.wrappedJSObject
.utility
)
493 window
.wrappedJSObject
.utility
.stopEvent(ev
);
502 window
.wrappedJSObject
.autocompletes
["goTo"] && window
.wrappedJSObject
.autocompletes
["goTo"].show();
503 if (window
.wrappedJSObject
.utility
)
504 window
.wrappedJSObject
.utility
.stopEvent(ev
);
513 window
.wrappedJSObject
.autocompletes
["moveTo"] && window
.wrappedJSObject
.autocompletes
["moveTo"].show();
514 if (window
.wrappedJSObject
.utility
)
515 window
.wrappedJSObject
.utility
.stopEvent(ev
);
526 var overrideBodyKeyPressHandler = function()
528 if (window
.wrappedJSObject
.eventMgr
)
530 var oldBodyKeyPressHandler
= window
.wrappedJSObject
.eventMgr
.bodyKeyPressHandler
;
532 window
.wrappedJSObject
.eventMgr
.bodyKeyPressHandler = function(ev
, ignoreCombo
)
534 if (handleKeyPressEvent(ev
, ignoreCombo
))
535 return oldBodyKeyPressHandler
.call(window
.wrappedJSObject
.eventMgr
, ev
, ignoreCombo
);
542 var overrideListTabsBlitDiv = function()
544 if (window
.wrappedJSObject
.listTabs
)
546 var oldBlitDiv
= window
.wrappedJSObject
.listTabs
.blitDiv
;
548 window
.wrappedJSObject
.listTabs
.blitDiv = function()
550 oldBlitDiv
.call(window
.wrappedJSObject
.listTabs
);
551 refreshListTabsStyles();
556 window
.wrappedJSObject
.listTabs
.blitDiv();
560 var refreshListTabsStyles = function()
562 var divListTabs
= document
.getElementById("listtabs");
566 divListTabs
.firstChild
.style
.listStyle
= "none";
567 divListTabs
.firstChild
.style
.padding
= "0px 5px 0px 5px";
568 divListTabs
.firstChild
.style
.whiteSpace
= "nowrap";
572 var hideLists = function()
574 if (window
.wrappedJSObject
.listTabs
)
576 var listItems
= window
.wrappedJSObject
.listTabs
.div
.getElementsByTagName("li");
578 for (var i
= 0; i
< window
.wrappedJSObject
.listTabs
.data
.length
; ++i
)
580 if (window
.wrappedJSObject
.listTabs
.data
[i
] && isListHidden(window
.wrappedJSObject
.listTabs
.data
[i
][1]))
581 listItems
[i
].style
.display
= "none";
584 /*if (window.wrappedJSObject.view.getSelected() == "Tasks")
589 var overrideListTabsSelectLeft = function()
591 if (window
.wrappedJSObject
.listTabs
)
593 var oldListTabsSelectLeft
= window
.wrappedJSObject
.listTabs
.selectLeft
;
595 window
.wrappedJSObject
.listTabs
.selectLeft = function()
597 var position
= this.selected
- 1;
603 if (!isListHidden(this.data
[position
][1]))
609 position
= this.entries
.length
- 1;
612 this.selectTabByPosition(position
);
617 var overrideListTabsSelectRight = function()
619 if (window
.wrappedJSObject
.listTabs
)
621 var oldListTabsSelectRight
= window
.wrappedJSObject
.listTabs
.selectRight
;
623 window
.wrappedJSObject
.listTabs
.selectRight = function()
625 var position
= this.selected
+ 1;
629 if (position
< this.entries
.length
)
631 if (!isListHidden(this.data
[position
][1]))
640 this.selectTabByPosition(position
);
645 var showTasksCount = function()
647 if (window
.wrappedJSObject
.listTabs
)
649 var listItems
= window
.wrappedJSObject
.listTabs
.div
.getElementsByTagName("li");
651 for (var i
= 0; window
.wrappedJSObject
.listTabs
.data
&& window
.wrappedJSObject
.listTabs
.data
[i
]; ++i
)
655 if (window
.wrappedJSObject
.listTabs
.data
[i
][2])
657 var filter
= window
.wrappedJSObject
.listTabs
.data
[i
][2];
659 if (filter
&& filter
.indexOf("status:") < 0)
660 filter
= "(" + filter
+ ") and (status:incomplete)";
662 if (window
.wrappedJSObject
.overviewList
&& filter
)
663 tasksCount
= window
.wrappedJSObject
.overviewList
.getFilteredList(filter
).length
667 if (window
.wrappedJSObject
.format
)
668 tasksCount
= window
.wrappedJSObject
.format
.getListStatistics(window
.wrappedJSObject
.listTabs
.data
[i
][1])[5];
670 listItems
[i
].firstChild
.style
.color
= "black";
674 listItems
[i
].firstChild
.innerHTML
= listItems
[i
].firstChild
.innerHTML
+ " (" + tasksCount
+ ")";
679 var subscribeToFilterEditFinished = function()
681 if (window
.wrappedJSObject
.messageBus
)
682 window
.wrappedJSObject
.messageBus
.subscribe(window
.wrappedJSObject
.listTabs
.blitDiv
, window
.wrappedJSObject
.listList
.mbn
+ "setFilterSuccess");
685 var overrideTaskCloudUpdate = function()
687 if (window
.wrappedJSObject
.taskCloud
)
689 var oldTaskCloudUpdate
= window
.wrappedJSObject
.taskCloud
.update
;
691 window
.wrappedJSObject
.taskCloud
.update = function()
693 oldTaskCloudUpdate
.call(window
.wrappedJSObject
.taskCloud
);
695 if (window
.wrappedJSObject
.listTabs
)
696 window
.wrappedJSObject
.listTabs
.blitDiv();
701 var hiddenLists
= GM_getValue("hiddenLists", "");
703 var isListHidden = function(listID
)
706 return hiddenLists
.indexOf(listID
) >= 0;
711 var isListArchived = function(listID
)
713 if (window
.wrappedJSObject
.stateMgr
.lists
[listID
] && window
.wrappedJSObject
.stateMgr
.lists
[listID
].archived
)
719 var hideList = function(entry
)
721 hiddenLists
= (hiddenLists
|| "") + entry
[0] + ",";
723 GM_setValue("hiddenLists", hiddenLists
);
725 window
.wrappedJSObject
.listList
.list
.updateEntry(entry
);
726 window
.wrappedJSObject
.listTabs
.blitDiv();
729 var showList = function(entry
)
731 hiddenLists
= hiddenLists
.replace(entry
[0], "");
732 hiddenLists
= hiddenLists
.replace(",,", ",");
734 GM_setValue("hiddenLists", hiddenLists
);
736 window
.wrappedJSObject
.listList
.list
.updateEntry(entry
);
737 window
.wrappedJSObject
.listTabs
.blitDiv();
741 var overrideListListUpdateEntry = function()
743 if (window
.wrappedJSObject
.listList
)
745 var oldListListUpdateEntry
= window
.wrappedJSObject
.listList
.list
.updateEntry
;
747 window
.wrappedJSObject
.listList
.list
.updateEntry = function(entry
, D
)
749 oldListListUpdateEntry
.call(window
.wrappedJSObject
.listList
.list
, entry
, D
);
751 var index
= window
.wrappedJSObject
.listList
.list
.map
[entry
[0]];
752 var row
= window
.wrappedJSObject
.listList
.list
.table
.rows
[index
];
755 row
.rowText
.innerHTML
= "<div id='listName_" + entry
[0] + "' style='float: left;'>" + entry
[1] + "</div><div style='align: right; float: right;'><a href=\"# \" id='displayListLink_" + entry
[0] + "'></a></div>";
757 var displayListLink
= document
.getElementById('displayListLink_' + entry
[0]);
758 var listName
= document
.getElementById('listName_' + entry
[0]);
760 var listClickHandler = function(event
)
762 if (isListHidden(entry
[0]))
768 if (window
.wrappedJSObject
.utility
)
769 window
.wrappedJSObject
.utility
.stopEvent(event
);
772 displayListLink
.addEventListener('click', listClickHandler
, false);
774 if (!isListArchived(entry
[0]))
776 if (isListHidden(entry
[0]))
778 displayListLink
.innerHTML
= "show";
779 listName
.style
.color
= "#cacaca";
783 displayListLink
.innerHTML
= "hide";
784 listName
.style
.color
= "";
789 window
.wrappedJSObject
.listList
.doStyles();
793 window
.addEventListener('load', overrideListTabsBlitDiv
, false);
794 window
.addEventListener('load', moveTabsToTheLeft
, false);
795 window
.addEventListener('load', overrideBodyKeyPressHandler
, false);
796 window
.addEventListener('load', overrideTaskCloudUpdate
, false);
797 window
.addEventListener('load', overrideListTabsSelectLeft
, false);
798 window
.addEventListener('load', overrideListTabsSelectRight
, false);
799 window
.addEventListener('load', subscribeToFilterEditFinished
, false);
800 window
.addEventListener('load', createAutoCompletes
, false);
801 window
.addEventListener('click', hideAutocompletes
, false);
803 window
.addEventListener('load', overrideListListUpdateEntry
, false);