View Source

prefab-ui-fun-with-hacker-news-example.js [view raw]

function demoHackernews(runner) {
  const { $, _, prefab } = runner;
  let allCommentsCount, $commentsWithoutLinks;

  prefab.clearReport();
  prefab.userSettings.addInput('How many rows of stats would you like to display?', { name: 'maxRows', default: 8, type: 'number' });
  prefab.userSettings.addSelect('Modify the page to display highlights?', [ 'yes', 'no' ], { name: 'modifyDom', default: 'yes' });

  if (location.host !== 'news.ycombinator.com' || location.pathname !== '/item') {
    prefab.reportHtml('<h2>Waiting for HackerNews comments page.</h2><p>Tip: visit <a href="https://news.ycombinator.com/best">Hacker News /best</a></p>'); // or use prefab.sendAlertMessage
  } else {
    runOnDiscussionPage();
  }
  
  function runOnDiscussionPage() {
    allCommentsCount = $('tr.comtr').length; // Count total # comments on page load, when this script runs, before they are toggled/hidden
    $commentsWithoutLinks = $('.comment:visible > span:not(:has(a[rel=nofollow]))').closest('tr.comtr');

    prefab.mainMenu.addButton('Toggle Comments Without Links', () => {
      $commentsWithoutLinks.toggle();
      updateUi();
    });
    runner.on(prefab.Event.UserSettingsChanged, updateUi); // this event fires when the user updates their settings. The listener is passed the updated data (though it is ignored in this example for simplicity)
    updateUi();
  }

  function updateUi() {
    prefab.userSettings.snapshot().then(userSettings => {  // re-fetches userSettings each time, again for simplicity
      const stats = _commenterStats(userSettings.maxRows || 8);

      const areAllVisible = $('tr.comtr').length === allCommentsCount;
      prefab.reportTable(stats, { segmentId: 'commenterStatistics', sortScore: 20 }); // Set segmentId option to replace an existing report if present, rather than append a new report

      prefab.reportHtml('<h2>Commenter Stats</h2>' + // note: we could alternatively set "title" and "description" options on prefab.reportTable, but that would not show sorting of multiple report segments
        '<p>' + (allCommentsCount - $commentsWithoutLinks.length) + ' of ' + allCommentsCount + ' posts contain external links. ' +
        '<strong>' + (areAllVisible ? 'Not hiding' : 'Hiding ' + $commentsWithoutLinks.length) + ' posts.</strong></p>'
        , { segmentId: 'myCustomHtmlIntro', sortScore: 10 }); // Use sortScore to explicitly position rows in the report (else it uses order they are sent)

      _doSomethingWithTheDom(stats, userSettings.modifyDom === 'no');
    });
  }

  function _commenterStats(maxRows) {  // Does not use Wranggle APIs. Gathers post statistics from the discussion page.
    let res = {};
    $('a.hnuser:visible').each((i, el) => {
      const $el = $(el);
      const username = $el.text();
      res[username] = res[username] || { name: username, posts: 0, chars: 0 };
      res[username].posts += 1;
      res[username].chars += $el.closest('td').text().length;
    });
    return _.orderBy(Object.values(res), [ 'posts', 'chars' ], [ 'desc', 'desc' ]).slice(0, maxRows);
  }

  function _doSomethingWithTheDom(stats, skipDomModification) { // Does not use Wranggle APIs. Modifies the DOM to display post count and to auto-scroll on click to the user's next comment
    $('.inserted-posts-count').remove();
    if (skipDomModification) {
      return;
    }
    const gemStyle = 'padding: 3px 8px; margin: 0 4px 0 6px; color: #ffcb05; background-color: #00274c; border-radius: 2px;';
    stats.forEach((userInfo) => {
      const postCount = userInfo.posts;
      const userPosts = $(`a[href="user?id=${ userInfo.name }"]`);
      $(`<span class="inserted-posts-count" style="${ gemStyle }">${ postCount } comment${ postCount > 1 ? 's' : '' }</span>`)
        .click(evt => {
          const clickedEl = $(evt.target).prev('a')[0];
          const nextEl = userPosts[(_.findIndex(userPosts, (el) => el === clickedEl) + 1) % userPosts.length];
          $('body').animate({ scrollTop: $(nextEl).offset().top });
        })
        .css({ cursor: 'pointer' })
        .insertAfter(userPosts);
    });
  }
}