half of the project

This commit is contained in:
kapypara 2023-12-16 17:28:27 +03:00
parent e9e003c55f
commit 133e9aa73e
3 changed files with 20843 additions and 0 deletions

View File

@ -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>

20405
static/bulma.css vendored Normal file

File diff suppressed because it is too large Load Diff

394
static/index.js Normal file
View File

@ -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">&nbsp;${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')
}
})