diff --git a/web_app/js/external/twitterFetcher.js b/web_app/js/external/twitterFetcher.js
new file mode 100644
index 0000000..79a4ee7
--- /dev/null
+++ b/web_app/js/external/twitterFetcher.js
@@ -0,0 +1,333 @@
+/*********************************************************************
+* #### Twitter Post Fetcher v13.0 ####
+* Coded by Jason Mayes 2015. A present to all the developers out there.
+* www.jasonmayes.com
+* Please keep this disclaimer with my code if you use it. Thanks. :-)
+* Got feedback or questions, ask here:
+* http://www.jasonmayes.com/projects/twitterApi/
+* Github: https://github.com/jasonmayes/Twitter-Post-Fetcher
+* Updates will be posted to this site.
+*********************************************************************/
+(function(root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define([], factory);
+ } else if (typeof exports === 'object') {
+ // Node. Does not work with strict CommonJS, but
+ // only CommonJS-like environments that support module.exports,
+ // like Node.
+ module.exports = factory();
+ } else {
+ // Browser globals.
+ factory();
+ }
+}(this, function() {
+ var domNode = '';
+ var maxTweets = 20;
+ var parseLinks = true;
+ var queue = [];
+ var inProgress = false;
+ var printTime = true;
+ var printUser = true;
+ var formatterFunction = null;
+ var supportsClassName = true;
+ var showRts = true;
+ var customCallbackFunction = null;
+ var showInteractionLinks = true;
+ var showImages = false;
+ var targetBlank = true;
+ var lang = 'en';
+ var permalinks = true;
+ var script = null;
+ var scriptAdded = false;
+
+ function handleTweets(tweets){
+ if (customCallbackFunction === null) {
+ var x = tweets.length;
+ var n = 0;
+ var element = document.getElementById(domNode);
+ var html = '
';
+ while(n < x) {
+ html += '- ' + tweets[n] + '
';
+ n++;
+ }
+ html += '
';
+ element.innerHTML = html;
+ } else {
+ customCallbackFunction(tweets);
+ }
+ }
+
+ function strip(data) {
+ return data.replace(/]*>(.*?)<\/b>/gi, function(a,s){return s;})
+ .replace(/class=".*?"|data-query-source=".*?"|dir=".*?"|rel=".*?"/gi,
+ '');
+ }
+
+ function targetLinksToNewWindow(el) {
+ var links = el.getElementsByTagName('a');
+ for (var i = links.length - 1; i >= 0; i--) {
+ links[i].setAttribute('target', '_blank');
+ }
+ }
+
+ function getElementsByClassName (node, classname) {
+ var a = [];
+ var regex = new RegExp('(^| )' + classname + '( |$)');
+ var elems = node.getElementsByTagName('*');
+ for (var i = 0, j = elems.length; i < j; i++) {
+ if(regex.test(elems[i].className)){
+ a.push(elems[i]);
+ }
+ }
+ return a;
+ }
+
+ function extractImageUrl(image_data) {
+ if (image_data !== undefined) {
+ var data_src = image_data.innerHTML.match(/data-srcset="([A-z0-9%_\.-]+)/i)[0];
+ return decodeURIComponent(data_src).split('"')[1];
+ }
+ }
+
+ var twitterFetcher = {
+ fetch: function(config) {
+ if (config.maxTweets === undefined) {
+ config.maxTweets = 20;
+ }
+ if (config.enableLinks === undefined) {
+ config.enableLinks = true;
+ }
+ if (config.showUser === undefined) {
+ config.showUser = true;
+ }
+ if (config.showTime === undefined) {
+ config.showTime = true;
+ }
+ if (config.dateFunction === undefined) {
+ config.dateFunction = 'default';
+ }
+ if (config.showRetweet === undefined) {
+ config.showRetweet = true;
+ }
+ if (config.customCallback === undefined) {
+ config.customCallback = null;
+ }
+ if (config.showInteraction === undefined) {
+ config.showInteraction = true;
+ }
+ if (config.showImages === undefined) {
+ config.showImages = false;
+ }
+ if (config.linksInNewWindow === undefined) {
+ config.linksInNewWindow = true;
+ }
+ if (config.showPermalinks === undefined) {
+ config.showPermalinks = true;
+ }
+
+ if (inProgress) {
+ queue.push(config);
+ } else {
+ inProgress = true;
+
+ domNode = config.domId;
+ maxTweets = config.maxTweets;
+ parseLinks = config.enableLinks;
+ printUser = config.showUser;
+ printTime = config.showTime;
+ showRts = config.showRetweet;
+ formatterFunction = config.dateFunction;
+ customCallbackFunction = config.customCallback;
+ showInteractionLinks = config.showInteraction;
+ showImages = config.showImages;
+ targetBlank = config.linksInNewWindow;
+ permalinks = config.showPermalinks;
+
+ var head = document.getElementsByTagName('head')[0];
+ if (script !== null) {
+ head.removeChild(script);
+ }
+ script = document.createElement('script');
+ script.type = 'text/javascript';
+ script.src = 'https://cdn.syndication.twimg.com/widgets/timelines/' +
+ config.id + '?&lang=' + (config.lang || lang) +
+ '&callback=twitterFetcher.callback&' +
+ 'suppress_response_codes=true&rnd=' + Math.random();
+ head.appendChild(script);
+ }
+ },
+ callback: function(data) {
+ var div = document.createElement('div');
+ div.innerHTML = data.body;
+ if (typeof(div.getElementsByClassName) === 'undefined') {
+ supportsClassName = false;
+ }
+
+ var tweets = [];
+ var authors = [];
+ var times = [];
+ var images = [];
+ var rts = [];
+ var tids = [];
+ var permalinksURL = [];
+ var x = 0;
+
+ if (supportsClassName) {
+ var tmp = div.getElementsByClassName('tweet');
+ while (x < tmp.length) {
+ if (tmp[x].getElementsByClassName('retweet-credit').length > 0) {
+ rts.push(true);
+ } else {
+ rts.push(false);
+ }
+ if (!rts[x] || rts[x] && showRts) {
+ tweets.push(tmp[x].getElementsByClassName('e-entry-title')[0]);
+ tids.push(tmp[x].getAttribute('data-tweet-id'));
+ authors.push(tmp[x].getElementsByClassName('p-author')[0]);
+ times.push(tmp[x].getElementsByClassName('dt-updated')[0]);
+ permalinksURL.push(tmp[x].getElementsByClassName('permalink')[0]);
+ if (tmp[x].getElementsByClassName('inline-media')[0] !== undefined) {
+ images.push(tmp[x].getElementsByClassName('inline-media')[0]);
+ } else {
+ images.push(undefined);
+ }
+ }
+ x++;
+ }
+ } else {
+ var tmp = getElementsByClassName(div, 'tweet');
+ while (x < tmp.length) {
+ tweets.push(getElementsByClassName(tmp[x], 'e-entry-title')[0]);
+ tids.push(tmp[x].getAttribute('data-tweet-id'));
+ authors.push(getElementsByClassName(tmp[x], 'p-author')[0]);
+ times.push(getElementsByClassName(tmp[x], 'dt-updated')[0]);
+ permalinksURL.push(getElementsByClassName(tmp[x], 'permalink')[0]);
+ if (getElementsByClassName(tmp[x], 'inline-media')[0] !== undefined) {
+ images.push(getElementsByClassName(tmp[x], 'inline-media')[0]);
+ } else {
+ images.push(undefined);
+ }
+
+ if (getElementsByClassName(tmp[x], 'retweet-credit').length > 0) {
+ rts.push(true);
+ } else {
+ rts.push(false);
+ }
+ x++;
+ }
+ }
+
+ if (tweets.length > maxTweets) {
+ tweets.splice(maxTweets, (tweets.length - maxTweets));
+ authors.splice(maxTweets, (authors.length - maxTweets));
+ times.splice(maxTweets, (times.length - maxTweets));
+ rts.splice(maxTweets, (rts.length - maxTweets));
+ images.splice(maxTweets, (images.length - maxTweets));
+ permalinksURL.splice(maxTweets, (permalinksURL.length - maxTweets));
+ }
+
+ var arrayTweets = [];
+ var x = tweets.length;
+ var n = 0;
+ while(n < x) {
+ if (typeof(formatterFunction) !== 'string') {
+ var datetimeText = times[n].getAttribute('datetime');
+ var newDate = new Date(times[n].getAttribute('datetime')
+ .replace(/-/g,'/').replace('T', ' ').split('+')[0]);
+ var dateString = formatterFunction(newDate, datetimeText);
+ times[n].setAttribute('aria-label', dateString);
+
+ if (tweets[n].innerText) {
+ // IE hack.
+ if (supportsClassName) {
+ times[n].innerText = dateString;
+ } else {
+ var h = document.createElement('p');
+ var t = document.createTextNode(dateString);
+ h.appendChild(t);
+ h.setAttribute('aria-label', dateString);
+ times[n] = h;
+ }
+ } else {
+ times[n].textContent = dateString;
+ }
+ }
+ var op = '';
+ if (parseLinks) {
+ if (targetBlank) {
+ targetLinksToNewWindow(tweets[n]);
+ if (printUser) {
+ targetLinksToNewWindow(authors[n]);
+ }
+ }
+ if (printUser) {
+ op += '' + strip(authors[n].innerHTML) +
+ '
';
+ }
+ op += '';
+ if (printTime) {
+ if (permalinks) {
+ op += '' + times[n].getAttribute('aria-label') + '
';
+ } else {
+ op += '' +
+ times[n].getAttribute('aria-label') + '
';
+ }
+ }
+ } else {
+ if (tweets[n].innerText) {
+ if (printUser) {
+ op += '' + authors[n].innerText + '
';
+ }
+ op += '';
+ if (printTime) {
+ op += '' + times[n].innerText + '
';
+ }
+
+ } else {
+ if (printUser) {
+ op += '' + authors[n].textContent + '
';
+ }
+ op += '';
+ if (printTime) {
+ op += '' + times[n].textContent + '
';
+ }
+ }
+ }
+ if (showInteractionLinks) {
+ op += '' +
+ '
';
+ }
+
+ if (showImages && images[n] !== undefined) {
+ op += '';
+ }
+
+ arrayTweets.push(op);
+ n++;
+ }
+ handleTweets(arrayTweets);
+ inProgress = false;
+
+ if (queue.length > 0) {
+ twitterFetcher.fetch(queue[0]);
+ queue.splice(0,1);
+ }
+ }
+ };
+
+ // It must be a global variable because it will be called by JSONP.
+ window.twitterFetcher = twitterFetcher;
+
+ return twitterFetcher;
+}));
diff --git a/web_app/js/external/twitterFetcher_min.js b/web_app/js/external/twitterFetcher_min.js
new file mode 100644
index 0000000..b347fc3
--- /dev/null
+++ b/web_app/js/external/twitterFetcher_min.js
@@ -0,0 +1,20 @@
+/*********************************************************************
+* #### Twitter Post Fetcher v13.1 ####
+* Coded by Jason Mayes 2015. A present to all the developers out there.
+* www.jasonmayes.com
+* Please keep this disclaimer with my code if you use it. Thanks. :-)
+* Got feedback or questions, ask here:
+* http://www.jasonmayes.com/projects/twitterApi/
+* Github: https://github.com/jasonmayes/Twitter-Post-Fetcher
+* Updates will be posted to this site.
+*********************************************************************/
+(function(w,p){"function"===typeof define&&define.amd?define([],p):"object"===typeof exports?module.exports=p():p()})(this,function(){function w(a){return a.replace(/]*>(.*?)<\/b>/gi,function(a,g){return g}).replace(/class=".*?"|data-query-source=".*?"|dir=".*?"|rel=".*?"/gi,"")}function p(a){a=a.getElementsByTagName("a");for(var c=a.length-1;0<=c;c--)a[c].setAttribute("target","_blank")}function n(a,c){for(var g=[],f=new RegExp("(^| )"+c+"( |$)"),h=a.getElementsByTagName("*"),b=0,k=h.length;b<
+k;b++)f.test(h[b].className)&&g.push(h[b]);return g}var B="",k=20,C=!0,u=[],x=!1,v=!0,q=!0,y=null,z=!0,D=!0,A=null,E=!0,F=!1,r=!0,G=!0,m=null,H={fetch:function(a){void 0===a.maxTweets&&(a.maxTweets=20);void 0===a.enableLinks&&(a.enableLinks=!0);void 0===a.showUser&&(a.showUser=!0);void 0===a.showTime&&(a.showTime=!0);void 0===a.dateFunction&&(a.dateFunction="default");void 0===a.showRetweet&&(a.showRetweet=!0);void 0===a.customCallback&&(a.customCallback=null);void 0===a.showInteraction&&(a.showInteraction=
+!0);void 0===a.showImages&&(a.showImages=!1);void 0===a.linksInNewWindow&&(a.linksInNewWindow=!0);void 0===a.showPermalinks&&(a.showPermalinks=!0);if(x)u.push(a);else{x=!0;B=a.domId;k=a.maxTweets;C=a.enableLinks;q=a.showUser;v=a.showTime;D=a.showRetweet;y=a.dateFunction;A=a.customCallback;E=a.showInteraction;F=a.showImages;r=a.linksInNewWindow;G=a.showPermalinks;var c=document.getElementsByTagName("head")[0];null!==m&&c.removeChild(m);m=document.createElement("script");m.type="text/javascript";m.src=
+"https://cdn.syndication.twimg.com/widgets/timelines/"+a.id+"?&lang="+(a.lang||"en")+"&callback=twitterFetcher.callback&suppress_response_codes=true&rnd="+Math.random();c.appendChild(m)}},callback:function(a){var c=document.createElement("div");c.innerHTML=a.body;"undefined"===typeof c.getElementsByClassName&&(z=!1);a=[];var g=[],f=[],h=[],b=[],m=[],t=[],e=0;if(z)for(c=c.getElementsByClassName("tweet");ek&&(a.splice(k,a.length-k),g.splice(k,g.length-k),f.splice(k,f.length-k),b.splice(k,b.length-k),h.splice(k,h.length-k),t.splice(k,t.length-k));c=[];e=a.length;for(b=0;b'+w(g[b].innerHTML)+""),d+='",v&&(d=G?d+(''+f[b].getAttribute("aria-label")+"
"):
+d+(''+f[b].getAttribute("aria-label")+"
"))):a[b].innerText?(q&&(d+=''+g[b].innerText+"
"),d+='",v&&(d+=''+f[b].innerText+"
")):(q&&(d+=''+g[b].textContent+"
"),d+='",v&&(d+=''+f[b].textContent+"
"));E&&(d+='");F&&void 0!==h[b]&&(l=h[b],void 0!==l?(l=l.innerHTML.match(/data-srcset="([A-z0-9%_\.-]+)/i)[0],l=decodeURIComponent(l).split('"')[1]):l=void 0,d+='');
+c.push(d);b++}if(null===A){a=c.length;g=0;f=document.getElementById(B);for(h=""}else A(c);x=!1;0