web/_javascript/modules/components/pageviews.js
Cotes Chung b69d3d7edd
refactor(build): modularize JS code
- replace gulp with rollup
- remove JS output from repo
2023-03-15 21:51:37 +08:00

254 lines
5.7 KiB
JavaScript

/**
* Count page views form GA or local cache file.
*
* Dependencies:
* - jQuery
* - countUp.js <https://github.com/inorganik/countUp.js>
*/
const getInitStatus = (function () {
let hasInit = false;
return () => {
let ret = hasInit;
if (!hasInit) {
hasInit = true;
}
return ret;
};
})();
const PvOpts = (function () {
function getContent(selector) {
return $(selector).attr('content');
}
function hasContent(selector) {
let content = getContent(selector);
return typeof content !== 'undefined' && content !== false;
}
return {
getProxyMeta() {
return getContent('meta[name=pv-proxy-endpoint]');
},
getLocalMeta() {
return getContent('meta[name=pv-cache-path]');
},
hasProxyMeta() {
return hasContent('meta[name=pv-proxy-endpoint]');
},
hasLocalMeta() {
return hasContent('meta[name=pv-cache-path]');
}
};
})();
const PvStorage = (function () {
const Keys = {
KEY_PV: 'pv',
KEY_PV_SRC: 'pv_src',
KEY_CREATION: 'pv_created_date'
};
const Source = {
LOCAL: 'same-origin',
PROXY: 'cors'
};
function get(key) {
return localStorage.getItem(key);
}
function set(key, val) {
localStorage.setItem(key, val);
}
function saveCache(pv, src) {
set(Keys.KEY_PV, pv);
set(Keys.KEY_PV_SRC, src);
set(Keys.KEY_CREATION, new Date().toJSON());
}
return {
keysCount() {
return Object.keys(Keys).length;
},
hasCache() {
return localStorage.getItem(Keys.KEY_PV) !== null;
},
getCache() {
return JSON.parse(localStorage.getItem(Keys.KEY_PV));
},
saveLocalCache(pv) {
saveCache(pv, Source.LOCAL);
},
saveProxyCache(pv) {
saveCache(pv, Source.PROXY);
},
isExpired() {
let date = new Date(get(Keys.KEY_CREATION));
date.setHours(date.getHours() + 1); // per hour
return Date.now() >= date.getTime();
},
isFromLocal() {
return get(Keys.KEY_PV_SRC) === Source.LOCAL;
},
isFromProxy() {
return get(Keys.KEY_PV_SRC) === Source.PROXY;
},
newerThan(pv) {
return (
PvStorage.getCache().totalsForAllResults['ga:pageviews'] >
pv.totalsForAllResults['ga:pageviews']
);
},
inspectKeys() {
if (localStorage.length !== PvStorage.keysCount()) {
localStorage.clear();
return;
}
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
switch (key) {
case Keys.KEY_PV:
case Keys.KEY_PV_SRC:
case Keys.KEY_CREATION:
break;
default:
localStorage.clear();
return;
}
}
}
};
})(); /* PvStorage */
function countUp(min, max, destId) {
if (min < max) {
let numAnim = new CountUp(destId, min, max);
if (!numAnim.error) {
numAnim.start();
} else {
console.error(numAnim.error);
}
}
}
function countPV(path, rows) {
let count = 0;
if (typeof rows !== 'undefined') {
for (let i = 0; i < rows.length; ++i) {
const gaPath = rows[parseInt(i, 10)][0];
if (gaPath === path) {
/* path format see: site.permalink */
count += parseInt(rows[parseInt(i, 10)][1], 10);
break;
}
}
}
return count;
}
function tacklePV(rows, path, elem, hasInit) {
let count = countPV(path, rows);
count = count === 0 ? 1 : count;
if (!hasInit) {
elem.text(new Intl.NumberFormat().format(count));
} else {
const initCount = parseInt(elem.text().replace(/,/g, ''), 10);
if (count > initCount) {
countUp(initCount, count, elem.attr('id'));
}
}
}
function displayPageviews(data) {
if (typeof data === 'undefined') {
return;
}
let hasInit = getInitStatus();
const rows = data.rows; /* could be undefined */
if ($('#post-list').length > 0) {
/* the Home page */
$('.post-preview').each(function () {
const path = $(this).find('a').attr('href');
tacklePV(rows, path, $(this).find('.pageviews'), hasInit);
});
} else if ($('.post').length > 0) {
/* the post */
const path = window.location.pathname;
tacklePV(rows, path, $('#pv'), hasInit);
}
}
function fetchProxyPageviews() {
if (PvOpts.hasProxyMeta()) {
$.ajax({
type: 'GET',
url: PvOpts.getProxyMeta(),
dataType: 'jsonp',
jsonpCallback: 'displayPageviews',
success: (data) => {
PvStorage.saveProxyCache(JSON.stringify(data));
},
error: (jqXHR, textStatus, errorThrown) => {
console.log(
'Failed to load pageviews from proxy server: ' + errorThrown
);
}
});
}
}
function fetchLocalPageviews(hasCache = false) {
return fetch(PvOpts.getLocalMeta())
.then((response) => response.json())
.then((data) => {
if (hasCache) {
// The cache from the proxy will sometimes be more recent than the local one
if (PvStorage.isFromProxy() && PvStorage.newerThan(data)) {
return;
}
}
displayPageviews(data);
PvStorage.saveLocalCache(JSON.stringify(data));
});
}
export function initPageviews() {
if ($('.pageviews').length <= 0) {
return;
}
PvStorage.inspectKeys();
if (PvStorage.hasCache()) {
displayPageviews(PvStorage.getCache());
if (PvStorage.isExpired()) {
if (PvOpts.hasLocalMeta()) {
fetchLocalPageviews(true).then(fetchProxyPageviews);
} else {
fetchProxyPageviews();
}
} else {
if (PvStorage.isFromLocal()) {
fetchProxyPageviews();
}
}
} else {
// no cached
if (PvOpts.hasLocalMeta()) {
fetchLocalPageviews().then(fetchProxyPageviews);
} else {
fetchProxyPageviews();
}
}
}