Skip to content

Instantly share code, notes, and snippets.

@jschementi
Created April 29, 2012 23:56
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jschementi/2554173 to your computer and use it in GitHub Desktop.
Save jschementi/2554173 to your computer and use it in GitHub Desktop.
Trello to Todo.txt converter

Import Trello board to todo.txt

  1. 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

  2. Run trello2todotxt

    node trello2todotxt.js

  3. Done! You should have todo.txt and done.txt in the same directory as this README. Merge it into your existing todo.txt and done.txt as you see fit.

/*
* 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);
};
// 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