JSFiddle - React, Tailwind, and code Playground

by skirtle

HTML

<script src="https://unpkg.com/[email protected]/dist/vue.global.js"></script>
<div id="app">
  <h3>Vue 3</h3>
  <repo-table :repos="vue3"></repo-table>
  <h3>Vue 2</h3>
  <repo-table :repos="vue2"></repo-table>
</div>
<script type="text/x-template" id="repo-table">
	<table class="table">
    <thead>
      <tr>
        <th>Code</th>
        <th>Last commit</th> 
        <th>90 days</th> 
        <th>Org / User</th>
        <th>Live</th>
        <th>GitHub</th>
      </tr>
    </thead>
    <tbody>
    <tr v-for="repo in sorted" :class="{ inactive: (dates[repo.lang] || '').startsWith('201') }">
      <td>{{ repo.lang }}</td>
      <td>{{ dates[repo.lang] }}</td>
      <td>
        {{ counts[repo.lang] }}
        <template v-if="counts[repo.lang] === 100">
          +
        </template>
        commits
      </td>
      <td><a :href="`https://github.com/${repo.owner}/`" target="_blank">{{ repo.owner }}</a></td>
      <td>
        <template v-if="repo.live">
          &check;
        </template>
      </td>
      <td>
        <a :href="`https://github.com/${repo.owner}/${repo.repo}/`" target="_blank">View repo</a>
      </td>
    </tr>      
    </tbody>
  </table>
</script>

SCSS

* {
  font-family: Arial, sans-serif;
}

.table {
  border-collapse: collapse;
  
  td, th {
    border: 1px solid #777;
    padding: 5px;
  }
  
  .inactive {
    color: #777;
  }
  
  tr:hover td {
    background: #eee;
  }
}

JavaScript

function getRawCache() {
  let cache = {}

  try {
    cache = JSON.parse(localStorage.getItem('docs-stats'))
  } catch (ex) {
  }

  if (!cache || typeof cache !== 'object') {
    cache = {}
  }

  return cache
}

function getCache(...args) {
  const cache = getRawCache()
  const key = args.join('-')
  const entry = cache[key]
    
  if (entry && entry.date + 2 * 60 * 60 * 1000 > Date.now()) {
    return entry.value
  }
  
  return null
}

function setCache(...args) {
  const value = args.pop()
  const cache = getRawCache()
  const key = args.join('-')
  
  cache[key] = {
    date: Date.now(),
    value
  }
  
  localStorage.setItem('docs-stats', JSON.stringify(cache))
}

function withCache(cacheName, fn) {
  return async function (...args) {
    const cached = getCache(...args, cacheName)

    if (cached != null) {
      return cached
    }
    
    const value = await fn(...args)
    
    setCache(...args, cacheName, value)
    
    return value
  }
}

const getLastDate = withCache('date', async (owner, repo, branch) => {
  const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/branches/${branch}`)
  
  const data = await response.json()
  
  const date = data.commit.commit.committer.date.split('T')[0]
  
  return date
})

const getCommitDate = () => {
	return new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toJSON().replace(/\.\d*/, '')
}

const commitCount = withCache('commitCount', async (owner, repo) => {
  const dateString = getCommitDate()

  const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/commits?since=${dateString}&per_page=100`)
  
  const data = await response.json()
  
  return data.length
})

const RepoTable = {
  template: '#repo-table',
  props: ['repos'],
  
  data () {
    return {
      dates: {},
      counts: {}
    }
  },
  
  computed: {
    sorted () {
      const dates = this.dates
      
      return [...this.repos].sort((a, b) => {
        const dateA = dates[a.lang] || '2000'
       ...