Commit d4f9448a authored by Pooya Parsa's avatar Pooya Parsa

feat: rewrite project with hnpwa api

parent 2c64c349
import { initializeApp, database } from "firebase/app"
export async function createAPI({ config, version }) {
await import(/* webpackChunkName: "firebase" */ "firebase/database")
initializeApp(config)
return database().ref(version)
}
import Firebase from "firebase"
import LRU from "lru-cache"
export async function createAPI({ config, version }) {
let api
// this piece of code may run multiple times in development mode,
// so we attach the instantiated API to `process` to avoid duplications
if (process.__API__) {
api = process.__API__
} else {
Firebase.initializeApp(config)
api = process.__API__ = Firebase.database().ref(version)
api.onServer = true
// fetched item cache
api.cachedItems = LRU({
max: 1000,
maxAge: 1000 * 60 * 15 // 15 min cache
})
// cache the latest story ids
api.cachedIds = {}
;["top", "new", "show", "ask", "job"].forEach(type => {
api.child(`${type}stories`).on("value", snapshot => {
api.cachedIds[type] = snapshot.val()
})
})
}
return api
}
// This is aliased in webpack config based on server/client build
import { createAPI } from "create-api"
const logRequests = !!process.env.DEBUG_API
let api = {}
let _api = createAPI({
version: "/v0",
config: {
databaseURL: "https://hacker-news.firebaseio.com"
}
}).then(_api => {
api = _api
// warm the front page cache every 15 min
// make sure to do this only once across all requests
if (api.onServer) {
warmCache()
}
})
function warmCache() {
if (!api.cachedIds) return
fetchItems((api.cachedIds.top || []).slice(0, 30))
setTimeout(warmCache, 1000 * 60 * 15)
}
async function fetch(child) {
logRequests && console.log(`fetching ${child}...`)
await _api
const cache = api.cachedItems
if (cache && cache.has(child)) {
logRequests && console.log(`cache hit for ${child}.`)
return cache.get(child)
} else {
return new Promise((resolve, reject) => {
api.child(child).once(
"value",
snapshot => {
const val = snapshot.val()
// mark the timestamp when this item is cached
if (val) val.__lastUpdated = Date.now()
cache && cache.set(child, val)
logRequests && console.log(`fetched ${child}.`)
resolve(val)
},
reject
)
})
}
}
export function fetchIdsByType(type) {
return api.cachedIds && api.cachedIds[type]
? Promise.resolve(api.cachedIds[type])
: fetch(`${type}stories`)
}
export function fetchItem(id) {
return fetch(`item/${id}`)
}
export function fetchItems(ids) {
return Promise.all(ids.map(id => fetchItem(id)))
}
export function fetchUser(id) {
return fetch(`user/${id}`)
}
export async function watchList(type, cb) {
let first = true
await _api
const ref = api.child(`${type}stories`)
const handler = snapshot => {
if (first) {
first = false
} else {
cb(snapshot.val())
}
}
ref.on("value", handler)
return () => {
ref.off("value", handler)
}
}
<!DOCTYPE html>
<html {{ HTML_ATTRS }}>
<head>
{{ HEAD }}
<style>
#skip a { position:absolute; left:-10000px; top:auto; width:1px; height:1px; overflow:hidden; }
#skip a:focus { position:static; width:auto; height:auto; }
</style>
</head>
<body {{ BODY_ATTRS }}>
<div id="skip"><a href="#app">skip to content</a></div>
{{ APP }}
</body>
</html>
export const feeds = {
news: { title: "News", pages: 10 },
newest: { title: "Newest", pages: 12 },
ask: { title: "Ask", pages: 2 },
show: { title: "Show", pages: 2 },
jobs: { title: "Jobs", pages: 1 }
}
export const validFeeds = Object.keys(feeds)
export const lazy = (commit, task, optimistic, enabled) => {
// By default, do lazy operations only in client
if (enabled === undefined) {
enabled = process.client
}
// Non lazy mode
if (!enabled) {
return task().then(commit)
}
// Do real task in background
Promise.resolve(task(optimistic))
.then(commit)
.catch(console.error)
// Commit optimistic value and resolve
return Promise.resolve(commit(optimistic))
}
<template>
<li v-if="comment" class="comment">
<div class="by">
<router-link :to="'/user/' + comment.by">{{ comment.by }}</router-link>
<router-link :to="'/user/' + comment.user">{{ comment.user }}</router-link>
{{ comment.time | timeAgo }} ago
</div>
<div class="text" v-html="comment.text" />
<div v-if="comment.kids && comment.kids.length" :class="{ open }" class="toggle">
<a @click="open = !open">{{ open ? '[-]' : '[+] ' + pluralize(comment.kids.length) + ' collapsed' }}
<div class="text" v-html="comment.content" />
<div v-if="comment.comments && comment.comments.length" :class="{ open }" class="toggle">
<a @click="open = !open">{{ open ? '[-]' : '[+] ' + pluralize(comment.comments.length) + ' collapsed' }}
</a>
</div>
<ul v-show="open" class="comment-children">
<comment v-for="id in comment.kids" :key="id" :id="id" />
<comment v-for="childComment in comment.comments" :key="childComment.id" :comment="childComment" />
</ul>
</li>
</template>
......@@ -19,8 +19,8 @@
export default {
name: "Comment",
props: {
id: {
type: String,
comment: {
type: Object,
required: true
}
},
......@@ -29,11 +29,6 @@ export default {
open: true
}
},
computed: {
comment() {
return this.$store.state.items[this.id]
}
},
methods: {
pluralize: n => n + (n === 1 ? " reply" : " replies")
}
......
<template>
<li class="news-item">
<span class="score">{{ item.score }}</span>
<span class="score">{{ item.points }}</span>
<span class="title">
<template v-if="item.url">
<a :href="item.url" target="_blank" rel="noopener">{{ item.title }}</a>
......@@ -14,7 +14,7 @@
<span class="meta">
<span v-if="item.type !== 'job'" class="by">
by
<router-link :to="'/user/' + item.by">{{ item.by }}</router-link>
<router-link :to="'/user/' + item.user">{{ item.user }}</router-link>
</span>
<span class="time">
{{ item.time | timeAgo }} ago
......
import ItemList from "./ItemList.vue"
const camelize = str => str.charAt(0).toUpperCase() + str.slice(1)
// This is a factory function for dynamically creating root-level list views,
// since they share most of the logic except for the type of items to display.
// They are essentially higher order components wrapping ItemList.vue.
export default function createListView(type) {
return {
name: `${type}-stories-view`,
fetch({ store }) {
return store.dispatch("FETCH_LIST_DATA", { type })
},
head: {
title: camelize(type)
},
render(h) {
return h(ItemList, { props: { type } })
}
}
}
<template>
<div class="news-list-nav">
<router-link v-if="page > 1" :to="`/${type}/${page - 1}`">&lt; prev</router-link>
<router-link v-if="page > 1" :to="`/${feed}/${page - 1}`">&lt; prev</router-link>
<a v-else class="disabled">&lt; prev</a>
<span>{{ page }}/{{ maxPage }}</span>
<router-link v-if="hasMore" :to="`/${type}/${page + 1}`">more &gt;</router-link>
<router-link v-if="hasMore" :to="`/${feed}/${page + 1}`">more &gt;</router-link>
<a v-else class="disabled">more &gt;</a>
</div>
</template>
......@@ -11,7 +11,7 @@
<script>
export default {
props: {
type: {
feed: {
type: String,
required: true
},
......
<script>
import Spinner from "./spinner"
export default {
functional: true,
props: {
loading: {
type: Boolean,
default: false
}
},
render(h, { props, children }) {
return props.loading
? h("div", { style: { "text-align": "center" } }, [
h(Spinner, { props: { show: true } })
])
: children
}
}
</script>
......@@ -5,11 +5,9 @@
<router-link to="/" exact>
<img class="logo" src="~assets/logo.png" alt="logo">
</router-link>
<router-link to="/top">Top</router-link>
<router-link to="/new">New</router-link>
<router-link to="/show">Show</router-link>
<router-link to="/ask">Ask</router-link>
<router-link to="/job">Jobs</router-link>
<router-link v-for="(list, key) in feeds" :key="key" :to="`/${key}`">
{{ list.title }}
</router-link>
<a class="github" href="https://github.com/nuxt/hackernews" target="_blank" rel="noopener">
Built with Nuxt.js
</a>
......@@ -20,6 +18,8 @@
</template>
<script>
import { feeds } from "~/common/api"
export default {
head() {
return {
......@@ -28,6 +28,9 @@ export default {
{ rel: "canonical", href: `https://hn.nuxtjs.org${this.$route.path}` }
]
}
},
computed: {
feeds: () => feeds
}
}
</script>
......
export default ({ isDev, req, redirect }) => {
// Redirect to https
if (!isDev && req) {
const protocol =
req.headers["x-forwarded-proto"] ||
(req.connection.encrypted ? "https" : "http")
if (protocol === "http") {
return redirect(301, `https://${req.headers.host}${req.url}`)
}
}
}
module.exports = {
mode: "universal",
build: {
extend(config, { isClient, isDev }) {
// Run ESLint on save
if (isDev && isClient) {
config.module.rules.push({
enforce: "pre",
test: /\.(js|vue)$/,
loader: "eslint-loader",
exclude: /(node_modules)/
})
}
config.resolve.alias["create-api"] = `./create-api-${
isClient ? "client" : "server"
}.js`
},
vendor: ["firebase"]
},
head: {
titleTemplate: "Nuxt HN | %s",
meta: [
......@@ -28,10 +10,7 @@ module.exports = {
{ property: "twitter:card", content: "summary_large_image" },
{ property: "twitter:site", content: "@nuxt_js" }
],
link: [
{ rel: "icon", type: "image/x-icon", href: "/favicon.ico" },
{ rel: "dns-prefetch", href: "https://hacker-news.firebaseio.com" }
]
link: [{ rel: "icon", type: "image/x-icon", href: "/favicon.ico" }]
},
loading: {
color: "#59cc93"
......@@ -46,15 +25,17 @@ module.exports = {
description: "HackerNews clone built with Nuxt.js",
theme_color: "#188269"
},
modules: ["@nuxtjs/pwa", "@nuxtjs/component-cache"],
plugins: [
"~/plugins/vuex-router-sync",
"~/plugins/filters",
"~/plugins/components"
],
router: {
middleware: ["https"]
modules: ["@nuxtjs/pwa", "@nuxtjs/component-cache", "@nuxtjs/axios"],
axios: {
proxy: true
},
proxy: {
"/api": {
target: "https://api.hnpwa.com/v0/",
pathRewrite: { "^/api/": "" }
}
},
plugins: ["~/plugins/filters"],
render: {
static: {
maxAge: "1y",
......
......@@ -32,6 +32,7 @@
"node": ">=8.0"
},
"dependencies": {
"@nuxtjs/axios": "^5.1.1",
"@nuxtjs/component-cache": "^1.1.1",
"@nuxtjs/pwa": "2.0.8",
"nuxt-edge": "^2.0.0-25364965.06067bf",
......
<template>
<div class="news-view view">
<item-list-nav :type="type" :page="page" :max-page="maxPage" />
<div class="view">
<item-list-nav :feed="feed" :page="page" :max-page="maxPage" />
<lazy-wrapper :loading="loading">
<transition :name="transition" mode="out-in">
<div v-if="displayedPage > 0" :key="displayedPage" class="news-list">
<div :key="displayedPage" class="news-list">
<transition-group tag="ul" name="item">
<item v-for="item in displayedItems" :key="item.id" :item="item" />
</transition-group>
</div>
</transition>
<item-list-nav :type="type" :page="page" :max-page="maxPage" />
<item-list-nav :feed="feed" :page="page" :max-page="maxPage" />
</lazy-wrapper>
</div>
</template>
<script>
import { watchList } from "../api"
import Item from "./Item.vue"
import ItemListNav from "./ItemListNav.vue"
import Item from "~/components/item.vue"
import ItemListNav from "~/components/item-list-nav.vue"
import LazyWrapper from "~/components/lazy-wrapper"
import { feeds, validFeeds } from "~/common/api"
export default {
name: "ItemList",
components: {
Item,
ItemListNav
ItemListNav,
LazyWrapper
},
validate({ params: { feed } }) {
return validFeeds.includes(feed)
},
props: {
type: {
type: String,
required: true
fetch({ store, params: { feed, page = 1 } }) {
return store.dispatch("FETCH_FEED", { feed, page })
},
head() {
return {
title: feeds[this.$route.params.feed].title
}
},
data() {
return {
transition: "slide-right",
displayedPage: Number(this.$route.params.page) || 1,
displayedItems: this.$store.getters.activeItems
displayedPage: Number(this.page) || 1
}
},
computed: {
feed() {
return this.$route.params.feed
},
page() {
return Number(this.$route.params.page) || 1
},
maxPage() {
const { itemsPerPage, lists } = this.$store.state
return Math.ceil(lists[this.type].length / itemsPerPage)
}
return feeds[this.feed].pages
},
watch: {
page(to, from) {
console.log("Page changed", to, from)
this.loadItems(to, from)
pageData() {
return this.$store.state.feeds[this.feed][this.page]
},
displayedItems() {
return this.pageData.map(id => this.$store.state.items[id])
},
loading() {
return this.displayedItems.length === 0
}
},
async beforeMount() {
if (this.$root._isMounted) {
this.loadItems(this.page)
}
// watch the current list for realtime updates
this.unwatchList = await watchList(this.type, ids => {
this.$store.commit("SET_LIST", { type: this.type, ids })
this.$store.dispatch("ENSURE_ACTIVE_ITEMS").then(() => {
this.displayedItems = this.$store.getters.activeItems
})
})
watch: {
page: "pageChanged"
},
beforeDestroy() {
this.unwatchList()
mounted() {
this.pageChanged(this.page)
},
methods: {
loadItems(to = this.page, from = -1) {
this.$nuxt.$loading.start()
this.$store
.dispatch("FETCH_LIST_DATA", {
type: this.type
})
.then(() => {
if (this.page < 0 || this.page > this.maxPage) {
this.$router.replace(`/${this.type}/1`)
pageChanged(to, from = -1) {
if (to < 0 || to > this.maxPage) {
this.$router.replace(`/${this.feed}/1`)
return
}
// Prefetch next page
this.$store
.dispatch("FETCH_FEED", {
feed: this.feed,
page: this.page + 1,
prefetch: true
})
.catch(() => {})
this.transition =
from === -1 ? null : to > from ? "slide-left" : "slide-right"
this.displayedPage = to
this.displayedItems = this.$store.getters.activeItems
this.$nuxt.$loading.finish()
})
}
}
}
......
<script>
import createListView from "~/components/createListView"
export default createListView("ask")
</script>
<script>
import { validFeeds } from "~/common/api"
export default {
fetch({ redirect }) {
redirect("/top")
redirect("/" + validFeeds[0])
}
}
</script>
<template>
<div v-if="item" class="item-view view" >
<template v-if="item">
<div class="item-view view" >
<div class="item-view-header">
<a :href="item.url" target="_blank">
<h1 v-html="item.title" />
......@@ -9,35 +8,31 @@
({{ item.url | host }})
</span>
<p class="meta">
{{ item.score }} points | by
<router-link :to="'/user/' + item.by">{{ item.by }}</router-link>
{{ item.points }} points | by
<router-link :to="'/user/' + item.user">{{ item.user }}</router-link>
{{ item.time | timeAgo }} ago
</p>
</div>
<div class="item-view-comments">
<lazy-wrapper :loading="item.loading">
<p class="item-view-comments-header">
{{ item.kids ? item.descendants + ' comments' : 'No comments yet.' }}
<spinner :show="loading"/>
{{ item.comments ? item.comments.length + ' comments' : 'No comments yet.' }}
</p>
<ul v-if="!loading" class="comment-children">
<comment v-for="id in item.kids" :key="id" :id="id"/>
<ul class="comment-children">
<comment v-for="comment in item.comments" :key="comment.id" :comment="comment"/>
</ul>
</lazy-wrapper>
</div>
</template>
</div>
</template>
<script>
import Spinner from "~/components/Spinner.vue"
import Comment from "~/components/Comment.vue"
import Comment from "~/components/comment.vue"
import LazyWrapper from "~/components/lazy-wrapper"
export default {
name: "ItemView",
components: { Spinner, Comment },
data: () => ({
loading: true
}),
components: { Comment, LazyWrapper },
head() {
return {
......@@ -46,54 +41,17 @@ export default {
},
computed: {
id() {
return this.$route.params.id
},
item() {
return this.$store.state.items[this.$route.params.id]
return this.$store.state.items[this.id]
}
},
// We only fetch the item itself before entering the view, because
// it might take a long time to load threads with hundreds of comments
// due to how the HN Firebase API works.
asyncData({ store, route: { params: { id } } }) {
return store.dispatch("FETCH_ITEMS", { ids: [id] })
},
// refetch comments if item changed
watch: {
item: "fetchComments"
},
// Fetch comments when mounted on the client
beforeMount() {
this.fetchComments()
},
methods: {
fetchComments() {
this.loading = true
fetchComments(this.$store, this.item).then(() => {
this.loading = false
})
}
}
}
// recursively fetch all descendent comments
function fetchComments(store, item) {
if (item && item.kids) {
return store
.dispatch("FETCH_ITEMS", {
ids: item.kids
})
.then(() =>
Promise.all(
item.kids.map(id => {
return fetchComments(store, store.state.items[id])
})
)
)
fetch({ store, params: { id } }) {
return store.dispatch("FETCH_ITEM", { id })
}
return Promise.resolve()
}
</script>
......
<script>
import createListView from "~/components/createListView"
export default createListView("job")
</script>
<script>
import createListView from "~/components/createListView"
export default createListView("new")
</script>
<script>
import createListView from "~/components/createListView"
export default createListView("show")
</script>
<script>
import createListView from "~/components/createListView"
export default createListView("top")
</script>
......@@ -2,28 +2,34 @@
<div class="user-view view">
<template v-if="user">
<h1>User : {{ user.id }}</h1>
<lazy-wrapper :loading="user.loading">
<ul class="meta">
<li>
<span class="label">Created:</span> {{ user.created | timeAgo }} ago</li>
<span class="label">Created:</span> {{ user.created_time | timeAgo }} ago</li>
<li>
<span class="label">Karma:</span> {{ user.karma }}</li>
<span class="label">Karma:</span> {{ user.karma || '-' }}</li>
<li v-if="user.about" class="about" v-html="user.about" />
</ul>
</lazy-wrapper>
<p class="links">
<a :href="'https://news.ycombinator.com/submitted?id=' + user.id">submissions</a> |
<a :href="'https://news.ycombinator.com/threads?id=' + user.id">comments</a>
</p>
</template>
<template v-else-if="user === false">
<template v-else>
<h1>User not found.</h1>
</template>
</div>
</template>
<script>
import LazyWrapper from "~/components/lazy-wrapper"
export default {
name: "UserView",
components: { LazyWrapper },
computed: {
user() {
return this.$store.state.users[this.$route.params.id]
......@@ -31,9 +37,7 @@ export default {
},
head() {
return {
title: this.user.id || "User not found"
}
return this.user ? this.user.id : "User not found"
},
fetch({ store, route: { params: { id } } }) {
......
// Import common components to optimize chunks size
import "~/components/createListView"
import { sync } from "vuex-router-sync"
export default function({ app, store }) {
sync(store, app.router)
}
import { fetchUser, fetchItems, fetchIdsByType } from "../api"
export default {
// ensure data for rendering given list type
FETCH_LIST_DATA: ({ commit, dispatch }, { type }) => {
commit("SET_ACTIVE_TYPE", { type })
return fetchIdsByType(type)
.then(ids => commit("SET_LIST", { type, ids }))
.then(() => dispatch("ENSURE_ACTIVE_ITEMS"))
},
// ensure all active items are fetched
ENSURE_ACTIVE_ITEMS: ({ dispatch, getters }) => {
return dispatch("FETCH_ITEMS", {
ids: getters.activeIds
})
},
FETCH_ITEMS: ({ commit, state }, { ids }) => {
// on the client, the store itself serves as a cache.
// only fetch items that we do not already have, or has expired (3 minutes)
const now = Date.now()
ids = ids.filter(id => {
const item = state.items[id]
if (!item) {
return true
}
if (now - item.__lastUpdated > 1000 * 60 * 3) {
return true
}
return false
})
if (ids.length) {
return fetchItems(ids).then(items => commit("SET_ITEMS", { items }))
} else {
return Promise.resolve()
}
},
FETCH_USER: ({ commit, state }, { id }) => {
return state.users[id]
? Promise.resolve(state.users[id])
: fetchUser(id).then(user => commit("SET_USER", { id, user }))
}
}
export default {
// ids of the items that should be currently displayed based on
// current list type and current pagination
activeIds(state) {
const { activeType, itemsPerPage, lists } = state
if (!activeType) {
return []
}
const page = Number(state.route.params.page) || 1
const start = (page - 1) * itemsPerPage
const end = page * itemsPerPage
return lists[activeType].slice(start, end)
},
// items that should be currently displayed.
// this Array may not be fully fetched.
activeItems(state, getters) {
return getters.activeIds.map(id => state.items[id]).filter(_ => _)
}
}
import Vuex from "vuex"
import Vue from "vue"
import actions from "./actions"
import mutations from "./mutations"
import getters from "./getters"
import { validFeeds } from "~/common/api"
import { lazy } from "~/common/utils"
import { CancelToken } from "axios"
export default () => {
return new Vuex.Store({
state: {
activeType: null,
itemsPerPage: 20,
export default {
// =================================================
// State
// =================================================
state: () => {
const state = {
items: {
/* [id: number]: Item */
},
users: {
/* [id: string]: User */
},
lists: {
top: [
/* number */
],
new: [],
show: [],
ask: [],
job: []
feeds: {
/* [page: number] : [ [id: number] ] */
}
}
validFeeds.forEach(feed => {
state.feeds[feed] = {}
})
return state
},
// =================================================
// Actions
// =================================================
actions: {
FETCH_FEED({ commit, state }, { feed, page, prefetch }) {
if (!prefetch) {
if (this.feedCancelSource) {
this.feedCancelSource.cancel(
"priorotize feed: " + feed + " page: " + page
)
}
this.feedCancelSource = CancelToken.source()
}
return lazy(
items => {
const ids = items.map(item => item.id)
commit("SET_FEED", { feed, ids, page })
commit("SET_ITEMS", { items })
},
() =>
this.$axios.$get(`/api/${feed}/${page}.json`, {
cancelToken: this.feedCancelSource && this.feedCancelSource.token
}),
(state.feeds[feed][page] || []).map(id => state.items[id])
)
},
FETCH_ITEM({ commit, state }, { id }) {
return lazy(
item => commit("SET_ITEM", { item }),
() => this.$axios.$get(`/api/item/${id}.json`),
Object.assign({ id, loading: true, comments: [] }, state.items[id])
)
},
actions,
mutations,
getters
FETCH_USER({ state, commit }, { id }) {
return lazy(
user => commit("SET_USER", { id, user }),
() => this.$axios.$get(`/api/user/${id}.json`),
Object.assign({ id, loading: true }, state.users[id])
)
}
},
// =================================================
// Mutations
// =================================================
mutations: {
SET_FEED: (state, { feed, ids, page }) => {
Vue.set(state.feeds[feed], page, ids)
},
SET_ITEM: (state, { item }) => {
if (item) {
Vue.set(state.items, item.id, item)
}
},
SET_ITEMS: (state, { items }) => {
items.forEach(item => {
if (item) {
Vue.set(state.items, item.id, item)
}
})
},
SET_USER: (state, { id, user }) => {
Vue.set(state.users, id, user || false) /* false means user not found */
}
}
}
import Vue from "vue"
export default {
SET_ACTIVE_TYPE: (state, { type }) => {
state.activeType = type
},
SET_LIST: (state, { type, ids }) => {
state.lists[type] = ids
},
SET_ITEMS: (state, { items }) => {
items.forEach(item => {
if (item) {
Vue.set(state.items, item.id, item)
}
})
},
SET_USER: (state, { id, user }) => {
Vue.set(state.users, id, user || false) /* false means user not found */
}
}
......@@ -192,6 +192,16 @@
version "0.2.6"
resolved "https://registry.yarnpkg.com/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.2.6.tgz#a4b81ca8cdeb1acbc7923289a4a514f61b59db86"
"@nuxtjs/axios@^5.1.1":
version "5.1.1"
resolved "https://registry.yarnpkg.com/@nuxtjs/axios/-/axios-5.1.1.tgz#2aec0bfc6cee8dc682b05402af25c0c6bc72a70c"
dependencies:
"@nuxtjs/proxy" "^1.1.4"
axios "^0.18.0"
axios-retry "^3.0.2"
chalk "^2.3.2"
debug "^3.1.0"
"@nuxtjs/component-cache@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@nuxtjs/component-cache/-/component-cache-1.1.1.tgz#d94aa450e1b0df0451a2e938262234b534df9dfd"
......@@ -225,6 +235,12 @@
version "2.2.1"
resolved "https://registry.yarnpkg.com/@nuxtjs/meta/-/meta-2.2.1.tgz#ad7c8658ef0357cdef56856a9ff21d595102e089"
"@nuxtjs/proxy@^1.1.4":
version "1.1.4"
resolved "https://registry.yarnpkg.com/@nuxtjs/proxy/-/proxy-1.1.4.tgz#71056d402d57c825a098c1651138a878ee6c0d23"
dependencies:
http-proxy-middleware "^0.17.4"
"@nuxtjs/pwa@2.0.8":
version "2.0.8"
resolved "https://registry.yarnpkg.com/@nuxtjs/pwa/-/pwa-2.0.8.tgz#0e36ea1e3a63c1531f4e9e6b1d03a9390b09f8b5"
......@@ -386,11 +402,17 @@ argparse@^1.0.7:
dependencies:
sprintf-js "~1.0.2"
arr-diff@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf"
dependencies:
arr-flatten "^1.0.1"
arr-diff@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
arr-flatten@^1.1.0:
arr-flatten@^1.0.1, arr-flatten@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1"
......@@ -428,6 +450,10 @@ array-uniq@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
array-unique@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53"
array-unique@^0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
......@@ -540,6 +566,19 @@ aws4@^1.2.1, aws4@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
axios-retry@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/axios-retry/-/axios-retry-3.0.2.tgz#d644db74184858e4a5cf10f5ea88dd657bb4ba61"
dependencies:
is-retry-allowed "^1.1.0"
axios@^0.18.0:
version "0.18.0"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.0.tgz#32d53e4851efdc0a11993b6cd000789d70c05102"
dependencies:
follow-redirects "^1.3.0"
is-buffer "^1.1.5"
babel-code-frame@^6.22.0, babel-code-frame@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
......@@ -1213,6 +1252,14 @@ brace-expansion@^1.1.7:
balanced-match "^1.0.0"
concat-map "0.0.1"
braces@^1.8.2:
version "1.8.5"
resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7"
dependencies:
expand-range "^1.8.1"
preserve "^0.2.0"
repeat-element "^1.1.2"
braces@^2.3.0, braces@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.1.tgz#7086c913b4e5a08dbe37ac0ee6a2500c4ba691bb"
......@@ -2425,6 +2472,10 @@ etag@^1.8.1, etag@~1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
eventemitter3@1.x.x:
version "1.2.0"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508"
events@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
......@@ -2440,6 +2491,12 @@ exif-parser@^0.1.9:
version "0.1.12"
resolved "https://registry.yarnpkg.com/exif-parser/-/exif-parser-0.1.12.tgz#58a9d2d72c02c1f6f02a0ef4a9166272b7760922"
expand-brackets@^0.1.4:
version "0.1.5"
resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b"
dependencies:
is-posix-bracket "^0.1.0"
expand-brackets@^2.1.4:
version "2.1.4"
resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622"
......@@ -2452,6 +2509,12 @@ expand-brackets@^2.1.4:
snapdragon "^0.8.1"
to-regex "^3.0.1"
expand-range@^1.8.1:
version "1.8.2"
resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337"
dependencies:
fill-range "^2.1.0"
express@^4.16.2:
version "4.16.3"
resolved "https://registry.yarnpkg.com/express/-/express-4.16.3.tgz#6af8a502350db3246ecc4becf6b5a34d22f7ed53"
......@@ -2512,6 +2575,12 @@ external-editor@^2.0.1, external-editor@^2.0.4:
iconv-lite "^0.4.17"
tmp "^0.0.33"
extglob@^0.3.1:
version "0.3.2"
resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1"
dependencies:
is-extglob "^1.0.0"
extglob@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543"
......@@ -2583,10 +2652,24 @@ file-type@^3.1.0:
version "3.9.0"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9"
filename-regex@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26"
filesize@^3.5.11:
version "3.6.1"
resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317"
fill-range@^2.1.0:
version "2.2.3"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723"
dependencies:
is-number "^2.1.0"
isobject "^2.0.0"
randomatic "^1.1.3"
repeat-element "^1.1.2"
repeat-string "^1.5.2"
fill-range@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7"
......@@ -2684,16 +2767,28 @@ flush-write-stream@^1.0.0:
inherits "^2.0.1"
readable-stream "^2.0.4"
follow-redirects@^1.3.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.4.1.tgz#d8120f4518190f55aac65bb6fc7b85fcd666d6aa"
dependencies:
debug "^3.1.0"
for-each@^0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.2.tgz#2c40450b9348e97f281322593ba96704b9abd4d4"
dependencies:
is-function "~1.0.0"
for-in@^1.0.2:
for-in@^1.0.1, for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
for-own@^0.1.4:
version "0.1.5"
resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce"
dependencies:
for-in "^1.0.1"
foreach@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
......@@ -2827,6 +2922,19 @@ getpass@^0.1.1:
dependencies:
assert-plus "^1.0.0"
glob-base@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
dependencies:
glob-parent "^2.0.0"
is-glob "^2.0.0"
glob-parent@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28"
dependencies:
is-glob "^2.0.0"
glob-parent@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae"
......@@ -3103,6 +3211,22 @@ http-parser-js@>=0.4.0:
version "0.4.11"
resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.11.tgz#5b720849c650903c27e521633d94696ee95f3529"
http-proxy-middleware@^0.17.4:
version "0.17.4"
resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.17.4.tgz#642e8848851d66f09d4f124912846dbaeb41b833"
dependencies:
http-proxy "^1.16.2"
is-glob "^3.1.0"
lodash "^4.17.2"
micromatch "^2.3.11"
http-proxy@^1.16.2:
version "1.16.2"
resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.16.2.tgz#06dff292952bf64dbe8471fa9df73066d4f37742"
dependencies:
eventemitter3 "1.x.x"
requires-port "1.x.x"
http-signature@~1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf"
......@@ -3315,6 +3439,16 @@ is-directory@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1"
is-dotfile@^1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1"
is-equal-shallow@^0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534"
dependencies:
is-primitive "^2.0.0"
is-extendable@^0.1.0, is-extendable@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
......@@ -3325,6 +3459,10 @@ is-extendable@^1.0.1:
dependencies:
is-plain-object "^2.0.4"
is-extglob@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0"
is-extglob@^2.1.0, is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
......@@ -3349,6 +3487,12 @@ is-function@^1.0.1, is-function@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.1.tgz#12cfb98b65b57dd3d193a3121f5f6e2f437602b5"
is-glob@^2.0.0, is-glob@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
dependencies:
is-extglob "^1.0.0"
is-glob@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a"
......@@ -3361,6 +3505,12 @@ is-glob@^4.0.0:
dependencies:
is-extglob "^2.1.1"
is-number@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
dependencies:
kind-of "^3.0.2"
is-number@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195"
......@@ -3411,6 +3561,14 @@ is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4:
dependencies:
isobject "^3.0.1"
is-posix-bracket@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4"
is-primitive@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575"
is-promise@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
......@@ -3425,6 +3583,10 @@ is-resolvable@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88"
is-retry-allowed@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34"
is-stream@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
......@@ -3732,7 +3894,7 @@ lodash.uniq@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.3.0:
lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.3.0:
version "4.17.5"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"
......@@ -3832,6 +3994,24 @@ methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
micromatch@^2.3.11:
version "2.3.11"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
dependencies:
arr-diff "^2.0.0"
array-unique "^0.2.1"
braces "^1.8.2"
expand-brackets "^0.1.4"
extglob "^0.3.1"
filename-regex "^2.0.0"
is-extglob "^1.0.0"
is-glob "^2.0.1"
kind-of "^3.0.2"
normalize-path "^2.0.1"
object.omit "^2.0.0"
parse-glob "^3.0.4"
regex-cache "^0.4.2"
micromatch@^3.1.4, micromatch@^3.1.8:
version "3.1.10"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
......@@ -4083,7 +4263,7 @@ nopt@^4.0.1:
abbrev "1"
osenv "^0.1.4"
normalize-path@^2.1.1:
normalize-path@^2.0.1, normalize-path@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
dependencies:
......@@ -4232,6 +4412,13 @@ object.getownpropertydescriptors@^2.0.3:
define-properties "^1.1.2"
es-abstract "^1.5.1"
object.omit@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa"
dependencies:
for-own "^0.1.4"
is-extendable "^0.1.1"
object.pick@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747"
......@@ -4396,6 +4583,15 @@ parse-bmfont-xml@^1.1.0:
xml-parse-from-string "^1.0.0"
xml2js "^0.4.5"
parse-glob@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c"
dependencies:
glob-base "^0.3.0"
is-dotfile "^1.0.0"
is-extglob "^1.0.0"
is-glob "^2.0.0"
parse-headers@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/parse-headers/-/parse-headers-2.0.1.tgz#6ae83a7aa25a9d9b700acc28698cd1f1ed7e9536"
......@@ -5071,6 +5267,10 @@ prepend-http@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
preserve@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
prettier@^1.11.1:
version "1.11.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.11.1.tgz#61e43fc4cd44e68f2b0dfc2c38cd4bb0fccdcc75"
......@@ -5201,6 +5401,13 @@ querystring@0.2.0, querystring@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
randomatic@^1.1.3:
version "1.1.7"
resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c"
dependencies:
is-number "^3.0.0"
kind-of "^4.0.0"
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
version "2.0.6"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.6.tgz#d302c522948588848a8d300c932b44c24231da80"
......@@ -5317,6 +5524,12 @@ regenerator-transform@^0.10.0:
babel-types "^6.19.0"
private "^0.1.6"
regex-cache@^0.4.2:
version "0.4.4"
resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd"
dependencies:
is-equal-shallow "^0.1.3"
regex-not@^1.0.0, regex-not@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c"
......@@ -5376,7 +5589,7 @@ repeat-element@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a"
repeat-string@^1.6.1:
repeat-string@^1.5.2, repeat-string@^1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
......@@ -5451,6 +5664,10 @@ require-uncached@^1.0.3:
caller-path "^0.1.0"
resolve-from "^1.0.0"
requires-port@1.x.x:
version "1.0.0"
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
resolve-from@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment