Skip to content

Instantly share code, notes, and snippets.

@palesz
Last active March 24, 2016 16:06
Show Gist options
  • Save palesz/ed4857259d6d79016b9e to your computer and use it in GitHub Desktop.
Save palesz/ed4857259d6d79016b9e to your computer and use it in GitHub Desktop.
A simple Kanban view on top of workflowy (implemented as a Greasemonkey/Tampermonkey user script)
// ==UserScript==
// @name workflowy-kanban
// @namespace http://palesz.org/
// @include https://workflowy.com/*
// @version 1
// @grant none
// ==/UserScript==
function escapeRegExp(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}
function matchAll(str, regex) {
var res = [];
var m;
while (m = regex.exec(str)) {
res.push(m[1]);
}
return res;
}
function traverse(node,func,level) {
var l = (level ? level : 0);
if (!node) {
return;
}
func(node,l);
for (var i in node.ch) {
traverse(node.ch[i],func,l+1);
}
}
// PROJECT_TREE_DATA.mainProjectTreeInfo.rootProjectChildren[2]
function accNodesUsingPredicate(rootNodes,predicate) {
var acc = [];
traverse({nm:"", ch:rootNodes}, function(n,l) {
if (!predicate || predicate(n,l)) {
acc.push(n);
}
});
return acc;
}
function prefixPredicate(prefixes) {
return function(n,l) {
for (var i in prefixes) {
if (n.nm.indexOf(prefixes[i]) > -1) {
return true;
}
}
return false;
};
}
function completionPredicate(notCompletedOnly) {
return function(n,l) {
if (notCompletedOnly) {
return (n.cp ? false : true);
}
return true;
};
}
function notPredicate(p) {
return function() {
return !p.apply(this||window, arguments);
};
}
function andPredicate(preds) {
return function() {
for (var i in preds) {
if (!preds[i].apply(this||window, arguments)) {
return false;
}
}
return true;
};
}
function allNodesMatchingAnyPrefix(rootNodes,predicate) {
if (!predicate) {
return [];
}
return accNodesUsingPredicate(rootNodes,predicate);
}
function extractTagData(n, tagPrefix) {
re = new RegExp(escapeRegExp(tagPrefix) + "([^ \n\r\t]+)");
m = re.exec(n);
if (m) {
return m[1];
} else {
return null;
}
}
function findNodesAndDates(nodes,prefixes) {
var res = [];
var m;
var d;
var re;
var matches;
for (n in nodes) {
matches = {};
dates = [];
for (var p in prefixes) {
m = extractTagData(nodes[n].nm, prefixes[p]);
if (!m) {
m = "no deadline";
}
if (m) {
dates.push(m);
matches[prefixes[p]] = m;
}
}
res.push({
"n": nodes[n],
"by-prefix": matches,
"dates": dates
});
}
return res;
}
function searchPredicate(prefixes) {
return andPredicate(prefixes.map(function(p) {
if (p.startsWith("-")) {
p = p.substring(1);
return notPredicate(prefixPredicate([p]));
} else {
return prefixPredicate([p]);
}
}));
}
function populateWithTasks(divId, p, dataPrefixes) {
var predicates = andPredicate([p, completionPredicate(true)]);
var nodes = allNodesMatchingAnyPrefix(PROJECT_TREE_DATA.mainProjectTreeInfo.rootProjectChildren,predicates);
var nodesAndDates = findNodesAndDates(nodes,dataPrefixes);
console.log(nodesAndDates);
var d,n;
var nodesByDate = {};
for (var i in nodesAndDates) {
for (var j in nodesAndDates[i].dates) {
d = nodesAndDates[i].dates[j];
if (!nodesByDate[d]) {
nodesByDate[d] = [];
}
nodesByDate[d].push(nodesAndDates[i]);
}
}
var sortedDates = Object.keys(nodesByDate).sort();
var str = "",tasks = "";
for (var d in sortedDates) {
tasks = nodesByDate[sortedDates[d]].map(function(i) {
return `<li style="background-color: #eee; margin: 10px; border-radius: 5px; padding: 5px;"><a href="#/${i.n.id}" onclick="wfagenda.toggleVisibility();">${i.n.nm}</a></li>`;
}).join("");
str += `<li style="margin-top: 10px;">
<b>${sortedDates[d]}</b><br/>
<ul style="list-style-type: none; list-style-position: inside;">
${tasks}
</ul>
</li>`;
}
$(divId).html(`<h3>${divId}</h3><ul>${str}</ul>`);
}
function searchPredicateFromStr(searchStr) {
var s = searchStr.split(/[ \t]+/).filter(function(i) { return i; });
return searchPredicate(s);
}
function refresh() {
var prefixes = $('#wfagenda-date-tags').val().split(/[ \t]+/).filter(function(i) { return i; });
var overallFilter = $('#wfagenda-filter-overall').val();
var boxes = [
{divId: "wfagenda-important-urgent", src: "-#fup -#wip #important #urgent"},
{divId: "wfagenda-important-not-urgent", src: "-#fup -#wip #important -#urgent"},
{divId: "wfagenda-not-important-urgent", src: "-#fup -#wip -#important #urgent"},
{divId: "wfagenda-not-important-not-urgent", src: "-#fup -#wip #t -#important -#urgent"},
{divId: "wfagenda-wip", src: "#wip"},
{divId: "wfagenda-fup", src: "#fup"}
];
for (var b in boxes) {
console.log("doing: ", boxes[b]);
populateWithTasks("#" + boxes[b].divId, searchPredicateFromStr(overallFilter + " " + boxes[b].src), prefixes);
}
}
function toggleVisibility() {
var newLinkText;
$('#wfagenda-content').toggle();
if ($('#wfagenda-content').is(':visible')) {
newLinkText = "hide agenda";
refresh();
} else {
newLinkText = "show agenda";
}
$('#wfagenda-toggle-link').html(newLinkText);
}
$(document).ready(function(){
$('body').append(`
<div id="wfagenda-content" style="padding: 10px; border: 1px solid #666; background-color: #ddd; display: none; position:absolute; z-index: 100; top: 0px; margin: 10px; height: 90vh;">
<div style="text-align: right;">
overall filter: <input id="wfagenda-filter-overall" type="text" placeholder="#todo @work, ..." value="#todo" onchange="wfagenda.refresh();"/>
date tag prefixes: <input id="wfagenda-date-tags" type="text" placeholder="#due (date tag prefix(es))" value="#d-" onchange="wfagenda.refresh();"/>
<button onClick="wfagenda.refresh();">Refresh</button>
</div>
<div id="wfagenda-agenda-view" style="margin-top: 20px; height: 90%;">
<table style="width: 100%; height: 600px;">
<tr>
<td style="width: 35%; padding: 5px;">
<div id="wfagenda-not-important-urgent" style="overflow-y: auto; height: 300px;"></div> <!-- -#important #urgent -->
<div id="wfagenda-not-important-not-urgent" style="overflow-y: auto; height: 300px;"></div> <!-- -#important -#urgent -->
</td>
<td style="width: 35%; padding: 5px;">
<div id="wfagenda-important-urgent" style="overflow-y: auto; height: 300px;"></div> <!-- #important #urgent -->
<div id="wfagenda-important-not-urgent" style="overflow-y: auto; height: 300px;"></div> <!-- #important -#urgent -->
</td>
<td style="width: 30%; padding: 5px;">
<div id="wfagenda-wip" style="height: 300px; overflow-y: auto; "></div> <!-- #wip -->
<div id="wfagenda-fup" style="height: 300px; overflow-y: auto; "></div> <!-- #wip -->
</td>
</tr>
</table>
</div>
</div>
<div style="text-align: right; padding: 10px; border: 1px solid #666; background-color: #ddd; position:fixed; z-index: 100; bottom:0px; right:0px;"><a id="wfagenda-toggle-link" href="#" onclick="wfagenda.toggleVisibility(); return false;">show agenda</a></div>
`);
window.wfagenda = {
"refresh": refresh,
"toggleVisibility": toggleVisibility,
"andPredicate": andPredicate,
"searchPredicate": searchPredicate,
"completionPredicate": completionPredicate,
"prefixPredicate": prefixPredicate,
"notPredicate": notPredicate,
"allNodesMatchingAnyPrefix": allNodesMatchingAnyPrefix
};
//delayedRefresh();
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment