-
Download an export of your board, rename it to
board.json
, and place it in the same directory as this README. For example:https://trello.com/board/4f19b3693754fb034106c89d/welcome-board.json
-
Run trello2todotxt
node trello2todotxt.js
-
Done! You should have
todo.txt
anddone.txt
in the same directory as this README. Merge it into your existingtodo.txt
anddone.txt
as you see fit.
Created
April 29, 2012 23:56
-
-
Save jschementi/2554173 to your computer and use it in GitHub Desktop.
Trello to Todo.txt converter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Date Format 1.2.3 | |
* (c) 2007-2009 Steven Levithan <stevenlevithan.com> | |
* MIT license | |
* | |
* Includes enhancements by Scott Trenda <scott.trenda.net> | |
* and Kris Kowal <cixar.com/~kris.kowal/> | |
* | |
* Accepts a date, a mask, or a date and a mask. | |
* Returns a formatted version of the given date. | |
* The date defaults to the current date/time. | |
* The mask defaults to dateFormat.masks.default. | |
*/ | |
var dateFormat = function () { | |
var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g, | |
timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g, | |
timezoneClip = /[^-+\dA-Z]/g, | |
pad = function (val, len) { | |
val = String(val); | |
len = len || 2; | |
while (val.length < len) val = "0" + val; | |
return val; | |
}; | |
// Regexes and supporting functions are cached through closure | |
return function (date, mask, utc) { | |
var dF = dateFormat; | |
// You can't provide utc if you skip other args (use the "UTC:" mask prefix) | |
if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) { | |
mask = date; | |
date = undefined; | |
} | |
// Passing date through Date applies Date.parse, if necessary | |
date = date ? new Date(date) : new Date; | |
if (isNaN(date)) throw SyntaxError("invalid date"); | |
mask = String(dF.masks[mask] || mask || dF.masks["default"]); | |
// Allow setting the utc argument via the mask | |
if (mask.slice(0, 4) == "UTC:") { | |
mask = mask.slice(4); | |
utc = true; | |
} | |
var _ = utc ? "getUTC" : "get", | |
d = date[_ + "Date"](), | |
D = date[_ + "Day"](), | |
m = date[_ + "Month"](), | |
y = date[_ + "FullYear"](), | |
H = date[_ + "Hours"](), | |
M = date[_ + "Minutes"](), | |
s = date[_ + "Seconds"](), | |
L = date[_ + "Milliseconds"](), | |
o = utc ? 0 : date.getTimezoneOffset(), | |
flags = { | |
d: d, | |
dd: pad(d), | |
ddd: dF.i18n.dayNames[D], | |
dddd: dF.i18n.dayNames[D + 7], | |
m: m + 1, | |
mm: pad(m + 1), | |
mmm: dF.i18n.monthNames[m], | |
mmmm: dF.i18n.monthNames[m + 12], | |
yy: String(y).slice(2), | |
yyyy: y, | |
h: H % 12 || 12, | |
hh: pad(H % 12 || 12), | |
H: H, | |
HH: pad(H), | |
M: M, | |
MM: pad(M), | |
s: s, | |
ss: pad(s), | |
l: pad(L, 3), | |
L: pad(L > 99 ? Math.round(L / 10) : L), | |
t: H < 12 ? "a" : "p", | |
tt: H < 12 ? "am" : "pm", | |
T: H < 12 ? "A" : "P", | |
TT: H < 12 ? "AM" : "PM", | |
Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""), | |
o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4), | |
S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10] | |
}; | |
return mask.replace(token, function ($0) { | |
return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1); | |
}); | |
}; | |
}(); | |
// Some common format strings | |
dateFormat.masks = { | |
"default": "ddd mmm dd yyyy HH:MM:ss", | |
shortDate: "m/d/yy", | |
mediumDate: "mmm d, yyyy", | |
longDate: "mmmm d, yyyy", | |
fullDate: "dddd, mmmm d, yyyy", | |
shortTime: "h:MM TT", | |
mediumTime: "h:MM:ss TT", | |
longTime: "h:MM:ss TT Z", | |
isoDate: "yyyy-mm-dd", | |
isoTime: "HH:MM:ss", | |
isoDateTime: "yyyy-mm-dd'T'HH:MM:ss", | |
isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'" | |
}; | |
// Internationalization strings | |
dateFormat.i18n = { | |
dayNames: [ | |
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", | |
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" | |
], | |
monthNames: [ | |
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", | |
"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" | |
] | |
}; | |
// For convenience... | |
Date.prototype.format = function (mask, utc) { | |
return dateFormat(this, mask, utc); | |
}; | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// trello2todotxt.js | |
// Jimmy Schementi | |
var _ = require('underscore'); | |
require('./date.format'); | |
function todotxt (card, board, lists) { | |
var pri = null; | |
var contexts = []; | |
var projs = []; | |
var due = null; | |
var closed = null; | |
var list = lists[card.idList]; | |
var ignoreLists = ["Doing", "Done", "To Do"]; | |
if (ignoreLists.indexOf(list.name) < 0) { | |
contexts.push(list.name.replace(/ /g, '').toLowerCase()); | |
} | |
var priorities = { | |
A: "Important!" | |
}; | |
var cardLabelNames = _.map(card.labels, function (l) { return l.name }); | |
for (var p in priorities) { | |
var pIndex = cardLabelNames.indexOf(priorities[p]); | |
if (pIndex > -1) { | |
cardLabelNames.splice(pIndex, 1); | |
pri = p; | |
break; | |
} | |
} | |
_.each(cardLabelNames, function (l) { contexts.push(l.replace(/ /g, '').toLowerCase()); }); | |
if (card.closed) { | |
var cardActions = _.filter(board.actions, function (a) { return a.data && a.data.card && a.data.card.id && a.data.card.id == card.id }); | |
var archiveActions = _.filter(cardActions, function (a) { return a.data.old && a.data.old.closed == false && a.data.card.closed == true }); | |
if (archiveActions.length == 0) { | |
throw "No archive actions!"; | |
} | |
var closedDate = new Date(archiveActions[0].date); | |
closed = closedDate.format('yyyy-mm-dd'); | |
} | |
if (card.due) { | |
due = new Date(card.due).format('yyyy-mm-dd'); | |
} | |
var output = []; | |
if (closed) { | |
output.push("x " + closed + " "); | |
} | |
if (pri) { | |
output.push("(" + pri + ") "); | |
} | |
output.push(card.name); | |
if (contexts && contexts.length > 0) { | |
_.each(_.map(contexts, function (c) { return "@" + c; }), function (c) { | |
output.push(" "); | |
output.push(c); | |
}); | |
} | |
if (projs && projs.length > 0) { | |
_.each(_.map(projs, function (p) { return "+" + p; }), function (p) { | |
output.push(" "); | |
output.push(p); | |
}); | |
} | |
if (due) { | |
output.push(" "); | |
output.push("due:" + due); | |
} | |
if (card.desc) { | |
output.push(" "); | |
output.push("desc:\"" + card.desc.replace(/\n/g, '\\n').replace(/"/g, "\"") + "\""); | |
} | |
return output.join(''); | |
} | |
require('fs').readFile('board.json', function (err, data) { | |
if (err) throw err; | |
var life = JSON.parse(data); | |
var lifeLists = _(life.lists).chain() | |
.filter(function (l) { return life.id == l.idBoard; }) | |
.reduce(function (m, l) { m[l.id] = l; return m; }, {}) | |
.value(); | |
var todotxtOutput = []; | |
var doneOutput = []; | |
var notDone = _.filter(life.cards, function (c) { return !c.closed; }); | |
_.each(notDone, function (c) { | |
todotxtOutput.push(todotxt(c, life, lifeLists)); | |
}); | |
var done = _.filter(life.cards, function (c) { return c.closed; }); | |
_.each(done, function (c) { | |
doneOutput.push(todotxt(c, life, lifeLists)); | |
}); | |
require('fs').writeFileSync('todo.txt', todotxtOutput.join('\n')); | |
require('fs').writeFileSync('done.txt', doneOutput.join('\n')); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment