const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; const monthsFull = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; const now = new Date(); let contributions; (() => { setRelativeTime(); const dom = document.querySelector('#contributions'); if (!dom) { return; } contributions = JSON.parse(dom.getAttribute('data')); let year = 0; for (const item of contributions) { item.publishDate = decodeURI(item.publishDate).replace(' ', 'T'); item.date = new Date(item.publishDate); if (item.date.getFullYear() > year) { year = item.date.getFullYear(); } item.title = decodeURI(item.title); } yearList(); switchYear(year.toString()); })(); function switchYear(year) { let startDate; let endDate; if (year !== now.getFullYear().toString()) { const date = new Date(Number(year), 0, 1, 0, 0, 0, 0); startDate = new Date(date.getFullYear(), 0, 1); endDate = new Date(date.getFullYear(), 11, 31); } else { endDate = now; startDate = new Date(endDate.getTime() - 364 * 24 * 60 * 60 * 1000 - endDate.getDay() * 24 * 60 * 60 * 1000); } startDate.setHours(0, 0, 0, 0); endDate.setHours(23, 59, 59, 999); const posts = []; const ms = []; for (const item of contributions) { if (item.date >= startDate && item.date <= endDate) { posts.push(item); if (!ms.includes(item.date.getMonth())) { ms.push(item.date.getMonth()); } } } posts.sort((a, b) => { return b - a }); document.querySelector('#posts-activity').innerHTML = ''; for (const month of ms) { const node = document.createElement('div'); node.innerHTML = monthly(year, month, posts); document.querySelector('#posts-activity').appendChild(node); } graph(year, posts, startDate, endDate); const yearList = document.querySelectorAll('.js-year-link'); for (const elem of yearList) { if (elem.innerText === year) { elem.classList.add('selected'); } else { elem.classList.remove('selected'); } } } function monthly(year, month, posts) { const monthPosts = posts.filter(post => post.date.getFullYear().toString() === year && post.date.getMonth() === month ); let liHtml = ''; for (const post of monthPosts) { liHtml += `
  • ${post.title}
  • `; } return `

    ${monthsFull[month]} ${monthPosts.length > 0 ? monthPosts[0].date.getFullYear() : year}

    Created ${monthPosts.length} post${monthPosts.length > 1 ? 's' : ''}
      ${liHtml}
    `; } function yearList() { const years = []; for (const item of contributions) { const year = item.date.getFullYear(); if (!years.includes(year)) { years.push(year); } } years.sort((a, b) => { return b - a }); for (let i = 0; i < years.length; i++) { const year = years[i]; const node = document.createElement('li'); node.innerHTML = `
  • ${year}
  • `; document.querySelector('#year-list').appendChild(node); } } function graph(year, posts, startDate, endDate) { const postsStr = posts.length === 1 ? "post" : "posts"; if (year === now.getFullYear().toString()) { document.querySelector('#posts-count').innerText = `${posts.length} ${postsStr} in the last year`; } else { document.querySelector('#posts-count').innerText = `${posts.length} ${postsStr} in ${year}`; } let html = ``; const count = {}; for (const post of posts) { const date = `${post.date.getFullYear()}-${(post.date.getMonth() + 1).toString().padStart(2, '0')}-${post.date.getDate().toString().padStart(2, '0')}`; if (count[date] === undefined) { count[date] = 1; } else { count[date]++; } } const monthPos = []; let startMonth = -1; for (let i = 0; i < 53; i++) { html += ``; for (let j = 0; j < 7; j++) { const date = new Date(startDate.getTime() + (i * 7 + j) * 24 * 60 * 60 * 1000); const dataDate = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`; if (date > endDate) { continue; } if (j === 0) { if (i <= 51) { if (startMonth !== date.getMonth()) { monthPos.push(i); startMonth = date.getMonth(); } } } let c; if (count[dataDate] === undefined) { c = 0; } else { c = count[dataDate]; } let color; switch (c) { case 0: color = "var(--color-calendar-graph-day-bg)"; break; case 1: color = "var(--color-calendar-graph-day-L1-bg)"; break; case 2: color = "var(--color-calendar-graph-day-L2-bg)"; break; case 3: color = "var(--color-calendar-graph-day-L3-bg)"; break; default: color = "var(--color-calendar-graph-day-L4-bg)"; } html += ``; } html += ''; } if (monthPos[1] - monthPos[0] < 2) { monthPos[0] = -1; } for (let i = 0; i < monthPos.length; i++) { const month = monthPos[i]; if (month === -1) { continue; } html += `${months[(i + startDate.getMonth()) % 12]}`; } html += ` Mon Wed Fri `; document.querySelector('#graph-svg').innerHTML = html; } let svgElem = document.createElement('div'); svgElem.style.cssText = 'pointer-events: none; display: none;'; svgElem.classList.add(...["svg-tip", "svg-tip-one-line"]); document.body.appendChild(svgElem); function svgTip(elem, count, dateStr) { if (window.screen.width < 768) { return; } const rect = getCoords(elem); const date = new Date(dateStr); const dateFmt = `${months[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`; if (count) { svgElem.innerHTML = `${count} posts on ${dateFmt}`; } else { svgElem.innerHTML = `No posts on ${dateFmt}`; } svgElem.style.display = 'block'; const tipRect = svgElem.getBoundingClientRect(); svgElem.style.top = `${rect.top - 50}px`; svgElem.style.left = `${rect.left - tipRect.width / 2 + rect.width / 2}px`; } function hideTip() { svgElem.style.display = 'none'; } function getCoords(elem) { const box = elem.getBoundingClientRect(); const body = document.body; const docEl = document.documentElement; const scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop; const scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft; const clientTop = docEl.clientTop || body.clientTop || 0; const clientLeft = docEl.clientLeft || body.clientLeft || 0; const top = box.top + scrollTop - clientTop; const left = box.left + scrollLeft - clientLeft; return { top, left, width: box.width, height: box.height }; } function relativeTime(dateStr) { const now = new Date(); const date = new Date(dateStr); const diff = Math.floor((now.getTime() - date.getTime()) / 1000); const seconds = Math.floor(diff); const minutes = Math.floor(diff / 60); const hours = Math.floor(diff / 60 / 60); const days = Math.floor(diff / 60 / 60 / 24); if (seconds < 60) { return `${seconds} seconds ago`; } if (minutes < 60) { return `${minutes} minutes ago`; } if (hours < 24) { return `${hours} hours ago`; } if (days < 30) { return `${days} days ago`; } if (date.getFullYear() === now.getFullYear()) { return `${date.getDate()} ${months[date.getMonth()]}`; } return `${date.getDate()} ${months[date.getMonth()]} ${date.getFullYear()}`; } function setRelativeTime() { document.querySelectorAll('relative-time').forEach(elem => { const dateStr = elem.getAttribute('datetime'); elem.innerHTML = relativeTime(dateStr); elem.setAttribute('title', new Date(dateStr).toLocaleString()); }); }