Skip to content

Commit

Permalink
Add timesince/until and urlize/urlizetrunc filters
Browse files Browse the repository at this point in the history
  • Loading branch information
Anders Hellerup Madsen committed Jan 21, 2010
1 parent ece9831 commit 87c7718
Show file tree
Hide file tree
Showing 7 changed files with 267 additions and 8 deletions.
5 changes: 5 additions & 0 deletions template/template.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ testcase('Filter Expression tests');
var expr = new FilterExpression("test|escape|upper");
assertEquals('<SCRIPT>', expr.resolve(context));
});
test('filterexpression should work with variable as arg', function () {
var context = new Context({test: 4, arg: 38 });
var expr = new FilterExpression("test|add:arg");
assertEquals(42, expr.resolve(context));
});

testcase('Context test');
setup( function () {
Expand Down
32 changes: 26 additions & 6 deletions template/template_defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,7 @@ var utils = require('utils/utils');
iriencode
Not implemented (yet):
timesince
timeuntil
unordered_list
urlize
urlizetrunc
wordcount
wordwrap
yesno
Expand Down Expand Up @@ -246,6 +242,16 @@ var filters = exports.filters = {
// TODO: this filter may not be safe
return (value instanceof Date) ? utils.date.format_time(value, arg) : '';
},
timesince: function (value, arg) {
value = new Date(value), arg = new Date(arg);
if (isNaN(value) || isNaN(arg)) { return ''; }
return utils.date.timesince(value, arg);
},
timeuntil: function (value, arg) {
value = new Date(value), arg = new Date(arg);
if (isNaN(value) || isNaN(arg)) { return ''; }
return utils.date.timeuntil(value, arg);
},
truncatewords: function (value, arg) {
return String(value).split(/\s+/g).slice(0, arg).join(' ') + ' ...';
},
Expand All @@ -258,6 +264,22 @@ var filters = exports.filters = {
},
urlencode: function (value, arg) {
return escape(value);
},
urlize: function (value, arg, safety) {
if (!safety.is_safe && safety.must_escape) {
var out = utils.html.urlize(value + "", { escape: true });
safety.is_safe = true;
return out;
}
return utils.html.urlize(value + "");
},
urlizetrunc: function (value, arg, safety) {
if (!safety.is_safe && safety.must_escape) {
var out = utils.html.urlize(value + "", { escape: true, limit: arg });
safety.is_safe = true;
return out;
}
return utils.html.urlize(value + "", { limit: arg });
}
};

Expand Down Expand Up @@ -600,5 +622,3 @@ var callbacks = exports.callbacks = {





46 changes: 46 additions & 0 deletions template/template_defaults.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -396,5 +396,51 @@ testcase('time');
assertEquals('18:12:14', filters.time(t, 'H:i:s'));
assertEquals('', filters.date('hest', 'H:i:s'));
});
testcase('timesince');
test('should return time since', function () {
var blog_date = new Date("1 June 2006 00:00:00");
var comment_date = new Date("1 June 2006 08:00:00");
assertEquals('8 hours', filters.timesince(blog_date, comment_date));
});
testcase('timeuntil');
test('should return time since', function () {
var today = new Date("1 June 2006");
var from_date = new Date("22 June 2006");
var conference_date = new Date("29 June 2006");
assertEquals('4 weeks', filters.timeuntil(conference_date, today));
assertEquals('1 week', filters.timeuntil(conference_date, from_date));
});
testcase('urlize');
test('should urlize text', function () {
assertEquals(
'Check out <a href="http://www.djangoproject.com">www.djangoproject.com</a>',
filters.urlize('Check out www.djangoproject.com', null, {})
);
});
test('should escape if required', function () {
var safety = { must_escape: true };
assertEquals('hest &amp; giraf', filters.urlize('hest & giraf', null, safety));
});
test('should mark output as safe if escaped', function () {
var safety = { must_escape: true };
filters.urlize('hest', null, safety);
assertEquals(true, safety.is_safe);
});
testcase('urlizetrunc');
test('should urlize text and truncate', function () {
assertEquals(
'Check out <a href="http://www.djangoproject.com">www.djangopr...</a>',
filters.urlizetrunc('Check out www.djangoproject.com', 15, {})
);
});
test('should escape if required', function () {
var safety = { must_escape: true };
assertEquals('hest &amp; giraf', filters.urlizetrunc('hest & giraf', 15, safety));
});
test('should mark output as safe if escaped', function () {
var safety = { must_escape: true };
filters.urlizetrunc('hest', 15, safety);
assertEquals(true, safety.is_safe);
});
run();

59 changes: 57 additions & 2 deletions utils/date.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ var i18n = {
"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
months_ap: ['Jan.', 'Feb.', 'March', 'April', 'May', 'June', 'July', 'Aug.', 'Sept.', 'Oct.', 'Nov.', 'Dec.'],
ordinal_suffix: { 1: 'st', 2: 'nd', 3: 'rd', 21: 'st', 22: 'nd', 23: 'rd', 31: 'st', 'default': 'th' },
special_times: ['midnight', 'noon']
special_times: ['midnight', 'noon'],
timespan: ['year', 'years', 'month', 'months', 'week', 'weeks', 'day', 'days', 'hour', 'hours', 'minute', 'minutes']
}
};

Expand Down Expand Up @@ -149,6 +150,8 @@ function format_date(date, format) {
});
}

exports.format_date = format_date;

function format_time(time, format) {
return format.replace(time_flags_re, function (s, escape_val) {
if (escape_val) { return escape_val.replace(/\\/g, ''); }
Expand All @@ -160,8 +163,60 @@ function format_time(time, format) {
});
}

exports.format_date = format_date;
exports.format_time = format_time;

/*************** TIMESPAN ********************************************/


function timespan_to_str(timespan) {

function map_chunk(cnt, idx) {
return cnt + ' ' + cur_i18n.timespan[ cnt === 1 ? idx * 2 : idx * 2 + 1]
}

var chunks = [
// years
1000 * 60 * 60 * 24 * 365,
// months
1000 * 60 * 60 * 24 * 30,
// weeks
1000 * 60 * 60 * 24 * 7,
// days
1000 * 60 * 60 * 24,
// hours
1000 * 60 * 60,
// minutes
1000 * 60,
];

chunks.forEach(function (x, idx) {
chunks[idx] = Math.floor(timespan / x);
timespan -= chunks[idx] * x;
});

for (var i = 0; i < chunks.length; i++) {
if (chunks[i]) {
return map_chunk(chunks[i], i) + (chunks[i+1] ? ', ' + map_chunk(chunks[i+1], i+1) : '');
}
}
return map_chunk(0, chunks.length - 1);
}

function timesince(date, now) {
if (!now) { now = new Date(); }
var timespan = now - date;
if (timespan < 0) { timespan = 0; }
return timespan_to_str(timespan);
}
exports.timesince = timesince;

function timeuntil(date, now) {
if (!now) { now = new Date(); }
var timespan = date - now;
if (timespan < 0) { timespan = 0; }
return timespan_to_str(timespan);
}
exports.timeuntil = timeuntil;



26 changes: 26 additions & 0 deletions utils/date.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,30 @@ testcase('longer formats');
assertEquals('Wednesday 2nd of December 1981 06:31:45 PM', format_date(d, 'l jS \\o\\f F Y h:i:s A'));
});

testcase('timesince');
test('correct results for known values', function () {
var now = new Date("Wed Dec 02 1981 18:31:45 GMT+0100 (CET)"); // Random time on Britney Spears birthday :-)

var date = new Date("Wed Dec 02 1981 15:15:45 GMT+0100 (CET)");
assertEquals('3 hours, 16 minutes', timesince(date, now));

date = new Date("Wed Nov 22 1981 15:15:45 GMT+0100 (CET)");
assertEquals('1 week, 3 days', timesince(date, now));

date = new Date("Sun Oct 19 1981 18:10:53 GMT+0100 (CET)");
assertEquals('1 month, 2 weeks', timesince(date, now));

date = new Date("Sat Dec 29 1970 04:52:13 GMT+0100 (CET)");
assertEquals('10 years, 11 months', timesince(date, now));

date = new Date("Wed Nov 13 1980 10:36:13 GMT+0100 (CET)");
assertEquals('1 year', timesince(date, now));

date = new Date("Wed Dec 02 1981 18:29:40 GMT+0100 (CET)"); // Random time on Britney Spears birthday :-)
assertEquals('2 minutes', timesince(date, now));

date = new Date("Wed Dec 02 1983 18:29:40 GMT+0100 (CET)"); // Random time on Britney Spears birthday :-)
assertEquals('0 minutes', timesince(date, now));
});

run();
70 changes: 70 additions & 0 deletions utils/html.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,73 @@ var truncate_html_words = exports.truncate_html_words = function (input, cnt) {



var punctuation_re = /^((?:\(|<|&lt;)*)(.*?)((?:\.|,|\)|>|\n|&gt;)*)$/;
var simple_email_re = /^\S+@[a-zA-Z0-9._\-]+\.[a-zA-Z0-9._\-]+$/;

function trim_url(url, limit) {
if (limit === undefined || limit > url.length) { return url; }
return url.substr(0, limit - 3 > 0 ? limit - 3 : 0) + '...';
}

/* Function: urlize(text, options)
Converts all urls found in text into links (<a href="URL">URL</a>).
Arguments:
text - string, the text to convert.
options - optional, see options
Options:
escape - boolean, if true pass the string through escape()
limit - number, if defined the shown urls will be truncated with '...' at this length
nofollow - boolean, if true add rel="nofollow" to <a> tags
*/
function urlize(text, options) {
options = options || {};

var words = text.split(/(\s+)/g);
var nofollow = options.nofollow ? ' rel="nofollow"' : '';

words.forEach( function (word, i, words) {
var match;
if (word.indexOf('.') > -1 || word.indexOf('@') > -1 || word.indexOf(':') > -1) {
match = punctuation_re(word);
}

if (match) {
var url, lead = match[1], middle = match[2], trail = match[3];
if (middle.substr(0,7) === 'http://' || middle.substr(0,8) === 'https://') {
url = encodeURI(middle);
} else if (middle.substr(0,4) === 'www.' || (
middle.indexOf('@') === -1 && middle && middle[0].match(/[a-z0-9]/i) &&
(middle.substr(-4) === '.org' || middle.substr(-4) === '.net' || middle.substr(-4) === '.com'))) {
url = encodeURI('http://' + middle);
} else if (middle.indexOf('@') > -1 && middle.indexOf(':') === -1 && simple_email_re(middle)) {
url = 'mailto:' + middle;
nofollow = '';
}

if (url) {
var trimmed = trim_url(middle, options.limit);
if (options.escape) {
lead = escape(lead);
trail = escape(trail);
url = escape(url);
trimmed = escape(trimmed);
}
middle = '<a href="' + url + '"' + nofollow + '>' + trimmed + '</a>';
words[i] = lead + middle + trail;
}
} else if (options.escape) {
words[i] = escape(word);
}
});
return words.join('');
}

exports.urlize = urlize;








37 changes: 37 additions & 0 deletions utils/html.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,42 @@ testcase('truncate_html_words');
test('should close tags on truncate', function () {
assertEquals('<p>Joel is ...</p>', truncate_html_words('<p>Joel is a slug</p>', 2));
});
testcase('urlize')
test('should urlize urls in text', function () {
assertEquals(
'Check out <a href="http://www.djangoproject.com">www.djangoproject.com</a>',
urlize('Check out www.djangoproject.com')
);
assertEquals(
'Check out (<a href="http://www.djangoproject.com">www.djangoproject.com</a>)',
urlize('Check out (www.djangoproject.com)')
);
assertEquals(
'Skriv til <a href="mailto:[email protected]">[email protected]</a>',
urlize('Skriv til [email protected]')
);
assertEquals(
'Check out (<a href="http://www.djangoproject.com">www.djangoproject.com</a>)\n' +
'Skriv til <a href="mailto:[email protected]">[email protected]</a>',
urlize('Check out (www.djangoproject.com)\nSkriv til [email protected]')
);
assertEquals(
'Check out <a href="http://www.djangoproject.com">www.djangopr...</a>',
urlize('Check out www.djangoproject.com', {limit: 15})
);
assertEquals(
'Se her: (<a href="http://www.dr.dk">www.dr.dk</a> &amp; ' +
'<a href="http://www.djangoproject.com">http://www.djangoproject.com</a>)',
urlize('Se her: (www.dr.dk & http://www.djangoproject.com)', { escape: true })
);
assertEquals(
'Se her: <a href="http://www.dr.dk?hest=4&amp;test=tolv">www.dr.dk?hest=4&amp;test=tolv</a>.',
urlize('Se her: www.dr.dk?hest=4&test=tolv.', { escape: true })
);
assertEquals(
'Check out (<a href="http://www.djangoproject.com" rel="nofollow">www.djangoproject.com</a>)',
urlize('Check out (www.djangoproject.com)', { nofollow: true })
);
});

run();

0 comments on commit 87c7718

Please sign in to comment.