half of the project
This commit is contained in:
parent
e9e003c55f
commit
133e9aa73e
44
index.html
44
index.html
|
@ -0,0 +1,44 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>CloneWars</title>
|
||||
<link rel="stylesheet" href="static/bulma.css">
|
||||
<link rel="stylesheet" href="static/fontawesome.css"/>
|
||||
<link rel="stylesheet" href="static/solid.css"/>
|
||||
<script src="static/index.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="navbar breadcumb" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-item">
|
||||
<a href="index.html">
|
||||
<h1 class="title">CloneWars</h1>
|
||||
</a>
|
||||
</div>
|
||||
<div class="navbar-item">
|
||||
<p class='subtitle' id='header-sub'>The latest on <strong>Hackernews</strong>!</p>
|
||||
</div>
|
||||
<div class="navbar-end">
|
||||
<div class="navbar-item">
|
||||
<div class="buttons">
|
||||
<button class="button is-info" id='notification-btn'>Notification</button>
|
||||
<button class="button is-warning" id='switch-btn'>New</button>
|
||||
|
||||
<p class="navbar-item"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<section class='section' style="height: 80%">
|
||||
<div class='container'>
|
||||
<div id='item-box'>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,394 @@
|
|||
"use strict";
|
||||
|
||||
const pageItemBox = document.getElementById('item-box')
|
||||
const headerSub = document.getElementById('header-sub')
|
||||
const params = new URLSearchParams(document.location.search)
|
||||
|
||||
// for top listing
|
||||
let items = [] // shuld be an array of items id
|
||||
let currentItem = -1
|
||||
|
||||
// for newest listing
|
||||
let newMode = params.get('order') == 'new'
|
||||
let floorItem // for the later new items
|
||||
let maxItem
|
||||
|
||||
// for the api requests
|
||||
let loadedItems = 0
|
||||
let loader_id
|
||||
let timer = 100 // how many request pre minute
|
||||
|
||||
// for item query
|
||||
let isQuery = params.get('id') != null
|
||||
|
||||
const itemType = {
|
||||
job: 'job', // list
|
||||
story: 'story', // list
|
||||
comment: 'comment',
|
||||
poll: 'poll', // list
|
||||
pollopt: 'pollopt'
|
||||
}
|
||||
|
||||
async function fetchStories() {
|
||||
const req = await fetch('https://hacker-news.firebaseio.com/v0/topstories.json')
|
||||
|
||||
req.json().then(resolve => {
|
||||
items = resolve
|
||||
currentItem = 0
|
||||
|
||||
loadItems()
|
||||
})
|
||||
}
|
||||
|
||||
async function fetchMaxItem() {
|
||||
const req = await fetch('https://hacker-news.firebaseio.com/v0/maxitem.json')
|
||||
|
||||
req.json().then(resolve => {
|
||||
maxItem = resolve
|
||||
floorItem = resolve
|
||||
|
||||
loadItems()
|
||||
})
|
||||
}
|
||||
|
||||
function loadItems() {
|
||||
|
||||
const shouldLoad = () => {
|
||||
const maxScroll = document.documentElement.scrollHeight - document.documentElement.clientHeight
|
||||
return (currentItem < items.length) && window.scrollY > maxScroll - 100 || maxScroll == 0
|
||||
}
|
||||
|
||||
if (loader_id || !shouldLoad()) {
|
||||
return
|
||||
}
|
||||
|
||||
loader_id = setInterval(async () => {
|
||||
|
||||
if (!shouldLoad()) {
|
||||
loader_id = clearInterval(loader_id)
|
||||
return
|
||||
}
|
||||
|
||||
const itemId = newMode ? maxItem-- : items[currentItem++]
|
||||
|
||||
try {
|
||||
const item = await getItem(itemId)
|
||||
makeItemListing(item)
|
||||
|
||||
} catch (e) {
|
||||
console.log(`[loadItems] got error ${e}`)
|
||||
}
|
||||
|
||||
}, timer)
|
||||
}
|
||||
|
||||
|
||||
function getItem(id) {
|
||||
const url = `https://hacker-news.firebaseio.com/v0/item/${id}.json`
|
||||
|
||||
try {
|
||||
return fetch(url).then(response => response.json().then(obj => obj))
|
||||
} catch (e) {
|
||||
console.log(`[getItem]: failed to fetch item ${id}`)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function makeItemListing(itemObj) {
|
||||
|
||||
if (itemObj === null || itemObj === undefined) {
|
||||
console.log(`[makeItemListing]: null or undefined object`)
|
||||
clearInterval(loader_id)
|
||||
return
|
||||
}
|
||||
|
||||
if (itemObj.deleted || itemObj.dead || itemObj.title == "[deleted]") {
|
||||
console.log(`[makeItemListing]: bad item: ${itemObj.id}`)
|
||||
return
|
||||
}
|
||||
|
||||
if (itemObj.type == itemType.comment || itemObj.type == itemType.pollopt) {
|
||||
return
|
||||
}
|
||||
|
||||
const id = itemObj.id
|
||||
const title = itemObj.title
|
||||
const url = itemObj.url
|
||||
|
||||
const points = itemObj.score
|
||||
const author = itemObj.by
|
||||
const time = itemObj.time
|
||||
const date = timeSince(itemObj.time)
|
||||
const comments = itemObj.descendants
|
||||
|
||||
const match = url && url.match(/https?:\/\/([^\/:]+)/)
|
||||
const hostname = match && match[1]
|
||||
|
||||
let subtitle = `${points} points by ${author} ${date} ago`
|
||||
|
||||
if (itemObj.type != itemType.job) {
|
||||
subtitle += ` | ${comments} comments`
|
||||
} else {
|
||||
console.log(`[makeItemListing]: job item ${title}`)
|
||||
}
|
||||
|
||||
const element_string =`
|
||||
<div class="block">
|
||||
<div class="columns">
|
||||
<div class="column is-narrow">
|
||||
<h4 class="title is-4">${title} <a href="${url}">(${hostname})</a></h4>
|
||||
<p class="subtitle is-5">${subtitle}</p>
|
||||
</div>
|
||||
<div class="column is-narrow is-center">
|
||||
<span class="icon is-small is-right" style="height: 100%">
|
||||
<i class="fas fa-arrow-right"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
const item = document.createElement('a')
|
||||
item.href = (itemObj.type != itemType.job) ? "index.html?id=" + id : url
|
||||
item.time = time
|
||||
item.innerHTML = element_string
|
||||
|
||||
if (url === undefined || hostname === undefined) {
|
||||
const header = item.getElementsByClassName('title')[0]
|
||||
header.innerHTML = title
|
||||
}
|
||||
|
||||
const children = Array.from(pageItemBox.childNodes)
|
||||
|
||||
for (const c of children) {
|
||||
if (typeof c == 'object' && c.time < item.time) {
|
||||
try {
|
||||
pageItemBox.insertBefore(item, c)
|
||||
return
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
console.log(c)
|
||||
clearInterval(loader_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pageItemBox.appendChild(item)
|
||||
}
|
||||
|
||||
function timeSince(date) {
|
||||
|
||||
const now = Date.now() / 1000
|
||||
const seconds = Math.floor(now - date)
|
||||
|
||||
if (seconds > 31536000) {
|
||||
return Math.floor(seconds / 31536000) + " years"
|
||||
}
|
||||
|
||||
if (seconds > 2592000) {
|
||||
return Math.floor(seconds / 2592000) + " months"
|
||||
}
|
||||
|
||||
if (seconds > 86400) {
|
||||
return Math.floor(seconds / 86400) + " days"
|
||||
}
|
||||
|
||||
if (seconds > 3600) {
|
||||
return Math.floor(seconds / 3600) + " hours"
|
||||
}
|
||||
|
||||
if (seconds > 60) {
|
||||
return Math.floor(seconds / 60) + " minutes"
|
||||
}
|
||||
|
||||
return Math.floor(seconds) + " seconds"
|
||||
}
|
||||
|
||||
|
||||
async function itemPage() {
|
||||
|
||||
headerSub.innerHTML = ''
|
||||
|
||||
const item = await getItem(params.get('id'))
|
||||
|
||||
if (item == null || item == undefined) {
|
||||
const element_string =`
|
||||
<div class="block">
|
||||
<div class="columns">
|
||||
<div class="column is-narrow">
|
||||
<h4 class="title is-4">Something went Bad :(</a></h4>
|
||||
<p class="subtitle is-5">Couldn't get your result from the api</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<sep>
|
||||
`
|
||||
const item = document.createElement('div')
|
||||
item.innerHTML = element_string
|
||||
|
||||
pageItemBox.appendChild(item)
|
||||
}
|
||||
|
||||
const type = item.type
|
||||
let target = pageItemBox
|
||||
|
||||
if (type != itemType.comment && type != itemType.pollopt) {
|
||||
if (item.url) {
|
||||
headerSub.innerHTML = `<a href=${item.url}>${item.title}</a> | ${item.score} points`
|
||||
} else {
|
||||
headerSub.innerHTML = `${item.title} | ${item.score} points`
|
||||
}
|
||||
}
|
||||
|
||||
if (item.type == itemType.poll) {
|
||||
await loadPollOpt(item.parts)
|
||||
} else if (type == itemType.pollopt){
|
||||
return loadPollOpt([item.id])
|
||||
}
|
||||
|
||||
if (!item.kids) {
|
||||
console.log(type)
|
||||
return
|
||||
}
|
||||
|
||||
if (type == itemType.comment) {
|
||||
loadCommnets([item.id], target)
|
||||
} else {
|
||||
loadCommnets(item.kids, target)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function loadPollOpt(opts) {
|
||||
|
||||
for (const id of opts) {
|
||||
|
||||
const item = await getItem(id)
|
||||
|
||||
if (!item || !item.text || !item.score) {
|
||||
continue
|
||||
}
|
||||
|
||||
const element_string =`
|
||||
<div class="columns">
|
||||
<div class="column is-narrow is-center">
|
||||
<span class="icon is-small is-left" style="height: 100%">
|
||||
<i class="fas fa-arrow-up"> ${item.score}</i>
|
||||
</span>
|
||||
</div>
|
||||
<div class="column is-narrow">
|
||||
<h4 class="title is-4">${item.text}</h4>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
const element = document.createElement('div')
|
||||
element.className = 'block'
|
||||
element.innerHTML = element_string
|
||||
|
||||
pageItemBox.appendChild(element)
|
||||
}
|
||||
|
||||
pageItemBox.innerHTML += '<hr>'
|
||||
}
|
||||
|
||||
|
||||
const sleep = (ms) => new Promise(resolve => {
|
||||
const reachedEnd = () => {
|
||||
const maxScroll = document.documentElement.scrollHeight - document.documentElement.clientHeight
|
||||
return window.scrollY >= maxScroll - 100
|
||||
}
|
||||
|
||||
const waitForScroll = () => {
|
||||
if (!reachedEnd()) {
|
||||
return
|
||||
}
|
||||
|
||||
resolve()
|
||||
document.removeEventListener('scroll', waitForScroll)
|
||||
}
|
||||
|
||||
if (reachedEnd()) {
|
||||
setTimeout(resolve, ms)
|
||||
} else {
|
||||
document.addEventListener('scroll', waitForScroll)
|
||||
}
|
||||
})
|
||||
|
||||
async function loadCommnets(ids, target) {
|
||||
|
||||
const message = (msg) => `
|
||||
<div class="card-content">
|
||||
<div class="content">
|
||||
${msg}
|
||||
</div>
|
||||
`
|
||||
|
||||
for (const id of ids) {
|
||||
await sleep(timer)
|
||||
|
||||
try {
|
||||
|
||||
const itemObj = await getItem(id)
|
||||
if (itemObj == undefined || itemObj.type != itemType.comment || !itemObj.text) {
|
||||
continue
|
||||
}
|
||||
|
||||
const item = document.createElement('div')
|
||||
item.className = 'card'
|
||||
item.innerHTML = message(itemObj.text)
|
||||
|
||||
target.appendChild(item)
|
||||
|
||||
if (itemObj.kids && itemObj.kids.length > 0) {
|
||||
const mainBox = item.getElementsByClassName('content')[0]
|
||||
const subBox = document.createElement('div')
|
||||
|
||||
subBox.className = 'card'
|
||||
|
||||
mainBox.innerHTML += '<br>'
|
||||
mainBox.appendChild(subBox)
|
||||
|
||||
await loadCommnets(itemObj.kids, subBox)
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
console.log(`[loadCommnets] failed to load comment ${id}, error ${e}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isQuery) {
|
||||
newMode ? fetchMaxItem() : fetchStories()
|
||||
document.addEventListener('scroll', loadItems)
|
||||
} else {
|
||||
itemPage()
|
||||
}
|
||||
|
||||
// ================ new items section stuff ===================
|
||||
let permission = 'denied'
|
||||
let notificationBtn = document.getElementById('notification-btn')
|
||||
|
||||
notificationBtn.addEventListener("click", updatePermission)
|
||||
function updatePermission() {
|
||||
Notification.requestPermission().then(result => {
|
||||
permission = result
|
||||
notificationBtn.disabled = permission == 'denied'
|
||||
})
|
||||
}
|
||||
updatePermission()
|
||||
|
||||
let switchBtn = document.getElementById('switch-btn')
|
||||
if (newMode) {
|
||||
switchBtn.innerText = 'Top'
|
||||
switchBtn.className = 'button is-primary'
|
||||
}
|
||||
|
||||
switchBtn.addEventListener("click", () => {
|
||||
if (newMode) {
|
||||
window.location.assign('index.html')
|
||||
} else {
|
||||
window.location.assign('index.html?order=new')
|
||||
}
|
||||
})
|
||||
|
Loading…
Reference in New Issue