Infinite scroll version of Hacker News

An infinite scroll Hacker News demo, build with Vue.js and Vue-infinite-loading plugin

by PeachScript

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<script src="https://unpkg.com/vue-infinite-loading@%5E2/dist/vue-infinite-loading.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
<div id="app">
  <header class="hacker-news-header">
    <a target="_blank" href="http://www.ycombinator.com/">
      <img src="https://news.ycombinator.com/y18.gif">
    </a>
    <span>Hacker News</span>
  </header>

  <div
    class="hacker-news-item"
    v-for="(item, $index) in list"
    :key="$index"
    :data-num="$index + 1">
    <a target="_blank" :href="item.url" v-text="item.title"></a>
    <p>
      <span v-text="item.points"></span>
      points by
      <a
        target="_blank"
        :href="`https://news.ycombinator.com/user?id=${item.author}`"
        v-text="item.author"></a>
      |
      <a
        target="_blank" 
        :href="`https://news.ycombinator.com/item?id=${item.objectID}`"
        v-text="`${item.num_comments} comments`"></a>
    </p>
  </div>

  <infinite-loading @infinite="infiniteHandler"></infinite-loading>
</div>

SCSS

body {
  padding-top: 28px;
  background-color: #F6F6EF;
}

.hacker-news-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  padding: 4px 20px;
  line-height: 14px;
  background-color: #FF6600;

  img {
    border: 1px solid #fff;
    vertical-align: middle;
  }

  span {
    font-family: Verdana, Geneva, sans-serif;
    font-size: 14px;
    font-weight: bold;
    vertical-align: middle;
  }
}


.hacker-news-item {
  $gap: 40px;

  margin: 10px 0;
  padding: 0 10px 0 $gap;
  line-height: 16px;
  font-size: 14px;
  
  &::before {
    content: attr(data-num) '.';
    float: left;
    margin-left: -$gap;
    width: $gap - 8px;
    color: #888;
    text-align: right;
  }
  
  > a {
    color: #333;
    text-decoration: none;
    
    &:hover {
      color: #000;
    }
  }

  p {
    margin: 0;
    font-size: 12px;
    
    &,
    a {
      color: #888;
    }
    
    a:not(:hover) {
      text-decoration: none;
    }
  }
}

JavaScript

const api = '//hn.algolia.com/api/v1/search_by_date?tags=story';

new Vue({
  el: '#app',
  data() {
    return {
      page: 1,
      list: [],
    };
  },
  methods: {
    infiniteHandler($state) {
      axios.get(api, {
        params: {
          page: this.page,
        },
      }).then(({ data }) => {
        if (data.hits.length) {
          this.page += 1;
          this.list.push(...data.hits);
          $state.loaded();
        } else {
          $state.complete();
        }
      });
    },
  },
});