| ClipArtMag Science Blog |

Free Cliparts

Hacker News Reader For No Reason - в 5K из Vanilla JS, HTML и CSS

Перевод статьи - A Hacker News Reader For No Reason - In 5K of Vanilla JS, HTML, & CSS

Автор - Mike Elliott

Источник оригинальной статьи:

https://elliotec.com/a-hacker-news-reader-for-no-reason/

Только наполовину названия верно.

Прошлой ночью я думал о том, как мой опыт полностью закрепился в React и современной экосистеме JavaScript в последнее время, возможно, повлиял на то, как я буду писать приложение вручную в vanilla HTML, CSS и JS без библиотек или чего-либо еще.

В тот же час я смотрел Hacker News и некоторые из его читателей, отмечая, как все они трудились до UX (в том числе HN - только в последнее время добавление комментария рушится и поддержка мобильных устройств), и обнаружил Hacker News API.

Ясно, что это дало возможность построить мой собственный HN reader самым легким способом я мог - полностью клиентская сторона, без библиотек, полностью vanilla.

Так что я сделал это в 2 файлы, 99 строк кода JavaScript и 99 строк HTML и CSS.

Файлы будут автоматически проходить через мой скрипт, так как они размещены на этом сайте (как описано здесь). В первую очередь JS составляет всего 4,4 КБ, но его сокращение и архивирование (gzipping) снизило его до 1,5 КБ, что довольно важно. HTML-файла составляет 3.5 КБ, минимизировано и архивированная.

Я сэкономлю вам арифметику, это в общей сложности 5 КБ на начальной нагрузке.

Перейти к A Hacker News Reader For No Reason!

HN App Image

Особенности

Ограниченные возможности

Поскольку я использую современные функции ECMAScript, только более современные браузеры будут поддерживать его, но большая часть моей аудитории, вероятно, в порядке с этим.

Это только показывает лучшие рассказов - нет поддержки New, Ask, Jobs и т. д. если они на первой странице. Это не ограничение для меня лично, так как я трачу 99% своего времени HN только на первой странице.

Нет входа, проголосовать за/против, или возможности публикации. К сожалению, HN API еще не поддерживает это, но если/когда это произойдет, возможно, я попытаюсь получить все это с помощью еще 99 строк: D

Я не дизайнер. Это было введено в моду с моим типичным астетическим минимальной темной темы с зеленым цветом повсюду. Наверное, не все любимые, но, конечно, мои, и это мои, так что неважно.

Код

Я написал стили в теге <style> в index.html. Поскольку одна из целей, которые я имел с этим, состояла в том, чтобы держать его супер маленьким и легким, я характерно установил произвольный предел для себя в числе, которое, как я считал, было разумным закончить его.

Я подделал некоторые из моих обычных правил стиля кода, и несколько раз я неуважительно относился к правилу линии 80-символов, которое я предпочитаю соблюдать, чтобы поразить метку строки 99 в обоих файлах, но ничего слишком сумасшедшего, и я не пересекаю никаких жестких строк плохой практики.

Вы можете проверить РЕПО здесь, но поскольку это всего 99 строк, я разместил содержимое файла JS ниже.

Обратите внимание на использование нескольких функций ES6+ и сходство с React и JSX с шаблонизацией. Конечно, здесь нет причудливого Virtual DOM или чего-то еще, и он просто манипулирует DOM по мере необходимости. Я использую функциональные и неизменяемые шаблоны, где это применимо, на языке нашего времени.



const hnBaseUrl = 'https://hacker-news.firebaseio.com/v0'
const state = {}
function fetchTopStories() {
  const topStoriesUrl = `${hnBaseUrl}/topstories.json`
  return fetch(topStoriesUrl).then(response => response.json())
    .then((data) => fetchStories(data))
}
function fetchStories(data) {
  const topStories = data.slice(0, 29)
  const storyIds = topStories.map((storyId) => {
    const storyUrl = `${hnBaseUrl}/item/${storyId}.json`
    return fetch(storyUrl).then((response) => response.json())
      .then((story) => story)
  })
  return Promise.all(storyIds).then((stories) => {
    state.stories = stories
    renderStories(stories)
  })
}
function renderStories(stories) {
  return stories.map((story) => {
    const userUrl = `https://news.ycombinator.com/user?id=${story.by}`
    const storyItemUrl = `https://news.ycombinator.com/item?id=${story.id}`
    const html = `
      <div class='story' id='${story.id}'>
        <h3 class='title'>
          ${story.url ? `<a href='${story.url}' target='_blank'>${story.title}</a>`
            : `<a href='javascript:void(0)' onclick="toggleStoryText('${story.id}')" >${story.title}</a>`}
        </h3>
        <span class='score'> ${story.score} </span> points by
        <a href='${userUrl}' target='_blank' class='story-by'> ${story.by}</a>
        <div class='toggle-view'>
          ${story.kids ? `
            <span
              onclick="fetchOrToggleComments('${story.kids}','${story.id}')"
              class='comments'
            > [toggle ${story.descendants} comments] </span>`
          : '' }
          <a href='${storyItemUrl}' target='_blank' class='hnLink'>[view on HN]</a>
        </div>
        ${story.text ?
          `<div class='storyText' id='storyText-${story.id}' style='display:none;'>
            ${story.text} </div>` : '' }
        <div id='comments-${story.id}' style='display: block;'></div>
      </div> `
    document.getElementById('hn').insertAdjacentHTML('beforeend', html)
  })
}
function toggleStoryText(storyId) {
  const storyText = document.getElementById(`storyText-${storyId}`)
  storyText.style.display = (storyText.style.display === 'block') ? 'none' : 'block'
}
function fetchComments(kids, storyId) {
  const commentIds = kids.split(',')
  const allComments = commentIds.map((commentId) => {
    const commentUrl = `${hnBaseUrl}/item/${commentId}.json`
    return fetch(commentUrl).then((response) => response.json()).then((comment) => comment)
  })
  return Promise.all(allComments).then((comments) => {
    state[storyId] = comments
    renderComments(comments, storyId)
  })
}
function fetchOrToggleComments(kids, storyId) {
  function toggleAllComments(storyId) {
    const allComments = document.getElementById(`comments-${storyId}`)
    allComments.style.display = (allComments.style.display === 'block') ? 'none' : 'block'
  }
  state[storyId] ? toggleAllComments(storyId) : fetchComments(kids, storyId)
}
function toggleComment(commentId) {
  const comment = document.getElementById(commentId)
  const toggle = document.getElementById(`toggle-${commentId}`)
  comment.style.display = (comment.style.display === 'block') ? 'none' : 'block'
  toggle.innerHTML = (toggle.innerHTML === '[ - ]') ? '[ + ]' : '[ - ]'
}
function renderComments(comments, storyId) {
  return comments.map((comment) => {
    const userUrl = `https://news.ycombinator.com/user?id=${comment.by}`
    const html = comment.deleted || comment.dead ? '' : `
      <div class='comment'>
        <span
          onclick='toggleComment("${comment.id}")'
          href='javascript:void(0)'
          id='toggle-${comment.id}'
          class='toggle-comment'
        >[ - ]</span>
        <a href='${userUrl}' class='comment-by'> ${comment.by}</a>
        <div id=${comment.id} class='comment-text' style='display:block;'>
          ${comment.text}
        </div>
      </div> `
    comment.parent == storyId ?
      document.getElementById(`comments-${storyId}`).insertAdjacentHTML('beforeend', html)
      : document.getElementById(comment.parent).insertAdjacentHTML('beforeend', html)
    if (comment.kids) return fetchComments(comment.kids.toString(), storyId)
  })
}
fetchTopStories()

Вот оно у вас есть. Может быть, кто-то еще также найдет причину использовать A Hacker News Reader For No Reason.