Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
382 lines (371 sloc) 10.1 KB
<!DOCTYPE html>
<meta charset="UTF-8">
<title>Life</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="format-detection" content="telephone=no">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:400,300">
<style>
body{
font-family: Open Sans, Helvetica, Arial, sans-serif;
color: #fff;
background-color: #384047;
margin: 0;
padding: 0;
font-size: 12px;
-webkit-text-size-adjust: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
cursor: default;
}
a{
color: #fff;
}
header{
z-index: 3;
background: linear-gradient(to bottom, rgba(56,64,71,0) 0%,rgba(56,64,71,.5) 20%,rgba(56,64,71,1) 50%,rgba(56,64,71,.5) 80%,rgba(56,64,71,0) 100%);
position: fixed;
bottom: 0;
left: 0;
margin: 0;
padding: 10px;
}
header a{
margin-left: 1em;
opacity: .3;
text-decoration: none;
}
header a:hover{
opacity: .7;
text-decoration: underline;
}
h1{
display: inline;
font-size: 20px;
line-height: 1em;
opacity: .5;
z-index: 3;
font-weight: 300;
white-space: nowrap;
}
#life{
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: auto;
-webkit-overflow-scrolling: touch;
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: grab;
}
#life-years{
position: absolute;
top: 0;
bottom: 0;
white-space: nowrap;
pointer-events: none;
}
#life-years .year{
display: inline-block;
box-sizing: border-box;
color: #fff;
font-weight: 300;
white-space: nowrap;
box-sizing: border-box;
height: 100%;
border-left: 1px dashed rgba(255,255,255,.2);
}
#life-years .year:first-child{
border-left: 0;
}
#life-years .year span{
background: linear-gradient(to bottom, rgba(56,64,71,1) 30%,rgba(56,64,71,0) 100%);
display: block;
padding: 10px;
position: -webkit-sticky;
position: sticky;
top: 0;
z-index: 3;
}
#life-years .year span i{
opacity: .5;
font-style: normal;
}
#life-events{
padding-top: 40px;
padding-bottom: 5em;
position: relative;
}
#life-events:after{
content: '';
display: block;
clear: left;
}
#life .event{
padding-right: 20px;
padding-bottom: 5px;
vertical-align: middle;
white-space: nowrap;
float: left;
clear: left;
}
#life .event b{
font-weight: normal;
color: rgba(255,255,255,.5);
}
#life .event .time{
display: inline-block;
overflow: hidden;
height: 0;
border: 4px solid #fff;
border-radius: 4px;
margin-right: 10px;
opacity: .3;
position: relative;
left: -2px;
}
#life .event:hover .time{
opacity: .5;
}
</style>
<header>
<h1 id="title">Life</h1>
<a href="https://github.com/cheeaun/life">Fork me</a>
</header>
<div id="life"></div>
<script>
(function(){
var life = {
$title: document.getElementById('title'),
$el: document.getElementById('life'),
utils: {
extend: function(object){
var args = Array.prototype.slice.call(arguments, 1);
for (var i=0, source; source=args[i]; i++){
if (!source) continue;
for (var property in source){
object[property] = source[property];
}
}
return object;
}
},
config: {
yearLength: 120, // 120px per year
hideAge: false, // Hide age from year axis
customStylesheetURL: null // Custom stylesheet
},
start: function(){
life.loadConfig(function(config){
life.config = life.utils.extend(life.config, config);
if (life.config.customStylesheetURL) life.injectStylesheet(life.config.customStylesheetURL);
life.fetch(function(response){
var data = life.parse(response);
var title = life.parseTitle(response);
life.render(title, data);
});
});
},
loadConfig: function(fn){
var xhr = new XMLHttpRequest();
xhr.open('GET', 'config.json', true);
xhr.onload = function(){
if (xhr.status == 200){
fn(JSON.parse(xhr.responseText));
} else {
fn({});
}
};
xhr.onerror = xhr.onabort = function(){
fn({});
};
xhr.send();
},
injectStylesheet: function(url){
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = url;
document.body.appendChild(link);
},
fetch: function(fn){
var xhr = new XMLHttpRequest();
xhr.open('GET', 'life.md', true);
xhr.onload = function(){
if (xhr.status == 200) fn(xhr.responseText);
};
xhr.send();
},
parse: function(response){
var list = response.match(/\-\s+[^\n\r]+/ig);
var data = [];
list.forEach(function(l){
var matches = l.match(/\-\s+([\d\/\-\~]+)\s(.*)/i);
var time = matches[1];
var text = matches[2];
data.push({
time: life.parseTime(time),
text: text
});
});
return data;
},
parseTitle: function(response){
return response.match(/[^\r\n]+/i)[0];
},
parseTime: function(time, point){
if (!point) point = 'start';
var data = {};
if (/^\~\d+$/.test(time)){ // ~YYYY
data = {
startYear: parseInt(time.slice(1), 10),
estimate: true
};
} else if (/^\d+$/.test(time)){ // YYYY
data[point + 'Year'] = parseInt(time, 10);
} else if (/^\d+\/\d+$/.test(time)){ // MM/YYYY
var t = time.split('/');
data[point + 'Month'] = parseInt(t[0], 10);
data[point + 'Year'] = parseInt(t[1], 10);
} else if (/^\d+\/\d+\/\d+$/.test(time)){ // DD/MM/YYYY
var t = time.split('/');
data[point + 'Date'] = parseInt(t[0], 10);
data[point + 'Month'] = parseInt(t[1], 10);
data[point + 'Year'] = parseInt(t[2], 10);
} else if (/\d\-/.test(time)){ // TIME-TIME
var splitTime = time.split('-');
var startTime = life.parseTime(splitTime[0]);
var endTime = life.parseTime(splitTime[1], 'end');
for (var k in startTime) { data[k] = startTime[k] }
for (var k in endTime) { data[k] = endTime[k] }
} else if (time == '~'){ // NOW
var now = new Date();
data.endYear = now.getFullYear();
data.endMonth = now.getMonth()+1;
data.endDate = now.getDate();
}
data.title = time;
return data;
},
firstYear: null,
renderEvent: function(d){
var firstYear = life.firstYear;
var yearLength = life.config.yearLength;
var monthLength = yearLength/12;
var dayLength = monthLength/30;
var time = d.time;
var estimate = time.estimate;
var startYear = time.startYear;
var startMonth = time.startMonth;
var startDate = time.startDate;
var endYear = time.endYear;
var endMonth = time.endMonth;
var endDate = time.endDate;
var width = 0;
// Calculate offset
var startTime = new Date(firstYear, 0, 1);
var endTime = new Date(startYear, startMonth ? startMonth-1 : 0, startDate || 1);
var daysDiff = (endTime - startTime)/(24*60*60*1000);
offset = daysDiff*dayLength;
// Calculate width
if (endYear){
var _endMonth = endMonth ? endMonth-1 : 11;
var _endDate = endDate || new Date(endYear, _endMonth+1, 0).getDate();
startTime = new Date(startYear, startMonth ? startMonth-1 : 0, startDate || 1);
endTime = new Date(endYear, _endMonth, _endDate);
daysDiff = (endTime - startTime)/(24*60*60*1000);
width = daysDiff*dayLength;
} else {
if (startDate){
width = dayLength;
} else if (startMonth){
width = monthLength;
} else {
width = yearLength;
}
}
// Parse Markdown links in the text
// credit: http://stackoverflow.com/a/9268827
var link = null;
while(link = d.text.match(/\[([^\]]+)\]\(([^)"]+)(?: \"([^\"]+)\")?\)/)) {
var link_attr = "";
if (link[3] !== undefined) {
link_attr = " title='" + link[3] + "'";
}
d.text = d.text.replace(link[0], "<a href='" + link[2] + "'" + link_attr + ">" + link[1] + "</a>");
}
return '<div class="event" style="margin-left: ' + offset.toFixed(2) + 'px">'
+ '<div class="time" style="width: ' + width.toFixed(2) + 'px"></div>'
+ '<b>' + d.time.title + '</b> ' + d.text
+ '</div>';
},
renderYears: function(firstYear, lastYear){
var dayLength = life.config.yearLength/12/30;
var html = '';
var days = 0;
var hideAge = life.config.hideAge;
for (var y=firstYear, age = 0; y<=lastYear+1; y++, age++){
var days = (y % 4 == 0) ? 366 : 365;
html += '<div class="year" style="width: ' + (days*dayLength).toFixed(2) + 'px"><span>'
+ y + (hideAge ? '' : (' <i>(' + age + ')</i>'))
+ '</span></div>';
}
return html;
},
render: function(title, data){
document.title = title;
life.$title.innerHTML = title;
// Get the first and last year for the year axis
var firstYear = new Date().getFullYear();
var lastYear = firstYear;
data.forEach(function(d){
var time = d.time;
var startYear = time.startYear;
var endYear = time.endYear;
if (startYear && startYear < firstYear) firstYear = startYear;
if (endYear && endYear > lastYear) lastYear = endYear;
});
life.firstYear = firstYear;
var html = '<div id="life-events">';
// 'comment_' class name is to hide it from Safari Reader
html += '<div id="life-years" class="comment_">' + life.renderYears(firstYear, lastYear) + '</div>';
data.forEach(function(d){
html += life.renderEvent(d);
});
html += '</div>';
life.$el.innerHTML = html;
}
};
var slider = {
startingMousePostition: {},
containerOffset: {},
init: function(){
window.addEventListener('mousedown', function(event){
slider.startingMousePostition = {
x: event.clientX,
y: event.clientY
};
slider.containerOffset = {
x: life.$el.scrollLeft,
y: life.$el.scrollTop
};
window.addEventListener('mousemove', slider.slide);
});
window.addEventListener('mouseup', function(event){
window.removeEventListener('mousemove', slider.slide);
});
},
slide: function(event){
event.preventDefault();
var x = slider.containerOffset.x + (slider.startingMousePostition.x - event.clientX);
var y = slider.containerOffset.y + (slider.startingMousePostition.y - event.clientY);
life.$el.scrollLeft = x;
life.$el.scrollTop = y;
}
};
life.start();
slider.init();
})();
</script>