albertyw/reaction-pics

View on GitHub
server/static/js/app.js

Summary

Maintainability
A
2 hrs
Test Coverage
import axios from 'axios';
import LogFit from 'logfit';
import Rollbar from 'rollbar';
import LazyLoad from 'vanilla-lazyload';
import varsnap from 'varsnap';

import 'normalize.css/normalize.css';
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap/dist/js/bootstrap.js';
import '../css/global.css';

const rollbarConfig = {
  accessToken: process.env.ROLLBAR_CLIENT_TOKEN,
  captureUncaught: true,
  payload: {
    environment: process.env.ENVIRONMENT,
  },
};
if (process.env.ENVIRONMENT !== 'development') {
  Rollbar.init(rollbarConfig);
}

const logfit = new LogFit({
  source: process.env.LOGFIT_CLIENT_TOKEN,
});
logfit.report();

varsnap.updateConfig({
  varsnap: process.env.VARSNAP,
  env: process.env.ENVIRONMENT,
  producerToken: process.env.VARSNAP_PRODUCER_TOKEN,
  consumerToken: process.env.VARSNAP_CONSUMER_TOKEN,
});

const lazyLoadInstance = new LazyLoad({});
let searchCancel = undefined;

function getJSON(url, params, cancellable) {
  const options = {
    method: 'GET',
    url: url,
    responseType: 'json',
    params: params,
  };
  if (cancellable) {
    options.cancelToken = new axios.CancelToken((c) => { searchCancel = c; });
  }
  const ajaxPromise = axios(options).then(response => {
    return response.data;
  }).catch(error => {
    return {'status': error};
  });
  return ajaxPromise;
}

function showPost(postID) {
  const url = '/postdata/' + postID;
  getJSON(url, {}, false).then((data) => {
    setResults('');
    addResults(data);
  });
}

function getQuery() {
  const query = document.getElementById('query').value;
  return query;
}

function setResults(html) {
  document.getElementById('results').innerHTML = html;
}

function updateResults(query, offset) {
  if (searchCancel !== undefined) {
    searchCancel();
    searchCancel = undefined;
  }
  const params = {
    query: query,
    offset: offset,
  };
  getJSON('/search', params, true).then((data) => {
    searchCancel = undefined;
    setResults('');
    saveQuery(query, data);
    updateURL(query);
    addResults(data);
    window.scrollTo(0, 0);
  }).catch(() => {
    // no-op
  });
}
// Cannot serialize and compare jquery request
// updateResults = varsnap(updateResults);

function saveQuery(query, data) {
  let dataHTML = '';
  dataHTML += '<input type="hidden" id="query" value="' + query + '">';
  dataHTML += '<input type="hidden" id="paginateCount" value="' + data.data.length + '">';
  dataHTML += '<input type="hidden" id="offset" value="' + data.offset + '">';
  dataHTML += '<input type="hidden" id="totalResults" value="' + data.totalResults + '">';
  document.getElementById('data').innerHTML = dataHTML;
  return dataHTML;
}
saveQuery = varsnap(saveQuery); // eslint-disable-line no-func-assign

function updateURL(query) {
  let url = '/';
  if (query !== undefined && query !== '') {
    url += '?query=' + query;
  }
  const urlPath = window.location.pathname.split('/');
  if (urlPath[1] === 'post') {
    history.pushState({}, 'Reaction Pics', url);
  } else {
    history.replaceState({}, 'Reaction Pics', url);
  }
  return url;
}
// Security issue when running in headless browser
// updateURL = varsnap(updateURL);

function addResults(data) {
  let resultHTML = '';
  for (let x=0; x<data.data.length; x++) {
    const post = data.data[x];
    resultHTML += addResult(post);
  }
  if (data.data.length + data.offset < data.totalResults) {
    resultHTML += '<a class="btn btn-primary" href="#" id="paginateNext">';
    resultHTML += 'Next Page <span class="glyphicon glyphicon-menu-right" aria-hidden="true"></span>';
    resultHTML += '</a>';
  }
  setResults(resultHTML);
  const paginateNextElement = document.getElementById('paginateNext');
  if (paginateNextElement !== null) {
    paginateNextElement.addEventListener('click', paginateNext);
  }
  lazyLoadInstance.update();
  return resultHTML;
}
addResults = varsnap(addResults); // eslint-disable-line no-func-assign

function paginateNext() {
  let offset = parseInt(document.getElementById('offset').value, 10);
  offset += parseInt(document.getElementById('paginateCount').value, 10);
  updateResults(getQuery(), offset);
}

function addResult(postData) {
  let postHTML = '<div class="result">';
  postHTML += '<h2>';
  if (postData.url) postHTML += '<a href="' + postData.internalURL + '">';
  postHTML += postData.title;
  if (postData.url) postHTML += '</a></h2>';
  if (postData.image) {
    if (postData.image.endsWith('.mp4')) {
      postHTML += '<p><video class="result-img" autoplay loop muted><source src="' + postData.image + '" type="video/mp4" /></video></p>';
    } else {
      postHTML += '<p><img data-src="' + postData.image + '" class="result-img lazy" /></p>';
    }
  }
  if (postData.likes) {
    postHTML += '<p><a href="#" id="likes" class="btn btn-success disabled">';
    postHTML += postData.likes + ' <span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span>';
    postHTML += '</a></p>';
    postHTML += '<p><a href="' + postData.url + '">Original</a></p>';
  }
  postHTML += '</div>';
  return postHTML;
}
addResult = varsnap(addResult); // eslint-disable-line no-func-assign

function stats() {
  return getJSON('/stats.json', {}, false).then((data) => {
    const line = 'Currently indexing ' + data.postCount + ' posts';
    document.getElementById('indexStat').textContent = line;
    let suggestions = '<span class="suggestion">Try:</span>';
    for (const keyword of data.keywords.slice(0, 10)) {
      suggestions += '<span class="suggestion"><a href="/?query=' + keyword + '" class="btn btn-secondary btn-sm">' + keyword + '</a></span>';
    }
    document.getElementById('suggestions').innerHTML = suggestions;
  });
}
// Cannot serialize and compare jquery request
// stats = varsnap(stats);

function getParameterByName(url, name) {
  name = name.replace(/[[\]]/g, '\\$&');
  const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)');
  const results = regex.exec(url);
  if (!results) return null;
  if (!results[2]) return '';
  return decodeURIComponent(results[2].replace(/\+/g, ' '));
}
getParameterByName = varsnap(getParameterByName); // eslint-disable-line no-func-assign

document.addEventListener('DOMContentLoaded', function() {
  const query = getParameterByName(window.location.href, 'query');
  if (query !== undefined && query !== '') {
    document.getElementById('query').value = query;
  }
  document.getElementById('query').addEventListener('input', () => updateResults(getQuery()));
  const urlPath = window.location.pathname.split('/');
  if (urlPath[1] === 'post') {
    showPost(urlPath[2]);
  } else {
    updateResults(getQuery());
  }
  stats();
});