// ES2018 Hacker News client under 100 lines of code const baseUrl = 'https://hacker-news.firebaseio.com/v0/' const fetchAll = async (...endpoints) => { const responses = await Promise.all(endpoints.map(endpoint => fetch(`${ baseUrl }${ endpoint }.json`))) responses.forEach(response => { if (!response.ok) throw Error(`${ response.status } ${ response.statusText }`) }) return await Promise.all(responses.map(response => response.json())) } const formatter = new Intl.DateTimeFormat(undefined, { dateStyle: 'short', timeStyle: 'short' }) const list = async ids => { document.querySelector('.hn').innerHTML = `

Hacker News

    ` const $list = document.querySelector('.hn ol') const items = await fetchAll(...ids.map(id => `item/${ id }`)) // FIXME indeterminate order? for (const item of items) { // TODO generator, infinite scrolling const $item = document.createElement('li') $list.appendChild($item) const domain = item.url ? new URL(item.url).host : '' // ask, jobs and others $item.innerHTML = `${ item.title } ${ domain }
    ${ item.score } points by ${ item.by } ${ formatter.format(new Date(item.time * 1000)) } ${ item.descendants || 0 } comments` } } const renderComments = async ($article, kids) => { if (!kids) return const items = await fetchAll(...kids.map(kid => `item/${ kid }`)) for (const item of items) { const $comment = document.createElement('blockquote') $article.appendChild($comment) $comment.innerHTML = `
    ${ item.by || 'deleted'} ${ formatter.format(new Date(item.time * 1000)) } ${item.dead ? 'flagged' : ''} ${ item.text || '' }
    ` renderComments($comment.querySelector('details'), item.kids) } } const item = item => { if (!item) throw Error('no such item') document.querySelector('.hn').innerHTML = `

    ${ item.title || '' }

    by ${ item.by } ${ formatter.format(new Date(item.time * 1000)) } ${ item.parent ? 'parent' : '' } ${item.dead ? 'flagged' : ''}
    ${item.text || /* item.url || */ ''}
    ` const $article = document.querySelector('.hn article') renderComments($article, item.kids) } const profile = user => { if (!user) throw Error(`no such user`) document.querySelector('.hn').innerHTML = `

    ${ user.id }

    About
    ${ user.about || 'N/A' }
    Submitted
    ${ user.submitted.length }
    Karma
    ${ user.karma}
    Created
    ${ formatter.format(new Date(user.created * 1000)) }
    ` } const params = new URLSearchParams(document.location.search) const [id, user, type, query] = [params.get('id'), params.get('user'), params.get('page') || 'topstories', params.get('query')] const $nav = document.createElement('nav') document.querySelector('header').appendChild($nav) $nav.innerHTML = `` ;(async () => { try { if (!query) { // TODO ignore query, try to render what you can... let plugins handle the rest if (id) { item((await fetchAll(`item/${ id }`))[0]) } else if (user) { profile((await fetchAll(`user/${ user }`))[0]) } else if (type) { await list(((await fetchAll(type))[0]).slice(0, 150)) } } } catch (e) { console.error(e); document.querySelector('.hn').innerHTML = e } })()