diff --git a/.eslintrc.json b/.eslintrc.json index 47064a84..92ff2f3c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,5 +1,4 @@ { - "extends": "plugin:@wordpress/eslint-plugin/es5", "env": { "browser": true, "jquery": true @@ -9,5 +8,22 @@ "statifyAjax": "readonly", "statifyDashboard": "readonly", "wp": "readonly" - } + }, + "overrides": [ + { + "files": "js/snippet.js", + "extends": "plugin:@wordpress/eslint-plugin/es5" + }, + { + "files": "*.test.js", + "env": { + "jest": true + } + }, + { + "files": "*.js", + "excludedFiles": "js/snippet.js", + "extends": "plugin:@wordpress/eslint-plugin/recommended" + } + ] } diff --git a/js/dashboard.js b/js/dashboard.js index 39c6e117..020e31c2 100644 --- a/js/dashboard.js +++ b/js/dashboard.js @@ -1,67 +1,103 @@ -( function() { - // Initialize. - var chartElem = document.getElementById( 'statify_chart' ); - var referrerTable = document.querySelector( '#statify_dashboard .table.referrer table tbody' ); - var targetTable = document.querySelector( '#statify_dashboard .table.target table tbody' ); - var totalsTable = document.querySelector( '#statify_dashboard .table.total table tbody' ); - var refreshBtn = document.getElementById( 'statify_refresh' ); - - // Abort if config or target element is not present. - if ( typeof statifyDashboard === 'undefined' || typeof chartElem === 'undefined' ) { - return; - } +{ + // Initialize + const chartElem = document.getElementById('statify_chart'); + const referrerTable = document.querySelector( + '#statify_dashboard .table.referrer table tbody' + ); + const targetTable = document.querySelector( + '#statify_dashboard .table.target table tbody' + ); + const totalsTable = document.querySelector( + '#statify_dashboard .table.total table tbody' + ); + const refreshBtn = document.getElementById('statify_refresh'); /** * Update the dashboard widget * * @param {boolean} refresh Force refresh. */ - function updateDashboard( refresh ) { + function updateDashboard(refresh) { // Disable refresh button. - if ( refreshBtn ) { + if (refreshBtn) { refreshBtn.disabled = true; } // Load data from API. - wp.apiFetch( { path: '/statify/v1/stats' + ( refresh ? '?refresh=1' : '' ) } ).then( function( data ) { - var labels = Object.keys( data.visits ); - var values = Object.values( data.visits ); - // Determine maximum value for scaling. - var maxValue = Math.max.apply( Math, values ); - var fullWidth = true; - var pointRadius = 4; - var chart; - var rows; - var row; - var i; - - // Remove the loading content. - chartElem.innerHTML = ''; - - // Adjust display according if there are too many values to display readable. - if ( labels.length === 0 ) { - chartElem.innerHTML = '
' + statifyDashboard.i18n.nodata + '
'; - return; - } else if ( chartElem.clientWidth < labels.length * 4 ) { - // Make chart scrollable, if 2px points are overlapping. - fullWidth = false; - pointRadius = 3; - } else if ( chartElem.clientWidth < labels.length * 8 ) { - // Shrink datapoints if 4px is overlapping, but 2 is not. - pointRadius = 2; - } + wp.apiFetch({ + path: '/statify/v1/stats' + (refresh ? '?refresh=1' : ''), + }) + .then((data) => { + const labels = Object.keys(data.visits); + const values = Object.values(data.visits); + + render(chartElem, labels, values); + + // Render top lists. + if (referrerTable) { + renderTopList(referrerTable, data.referrer); + } + if (targetTable) { + renderTopList(targetTable, data.target); + } - // Draw chart. - chart = new Chartist.LineChart( '#statify_chart', { - labels: labels, - series: [ - values, - ], - }, { + if (totalsTable) { + renderTotals(totalsTable, data.totals); + } + + // Re-enable refresh button. + if (refreshBtn) { + refreshBtn.disabled = false; + } + }) + .catch(() => { + // Failed to load. + chartElem.innerHTML = + '' + statifyDashboard.i18n.error + '
'; + }); + } + + /** + * Render statistics chart. + * + * @param {HTMLElement} root Root element. + * @param {string[]} labels Labels. + * @param {number[]} values Values. + */ + function render(root, labels, values) { + // Remove the loading content. + root.innerHTML = ''; + + // Adjust display according if there are too many values to display readable. + let fullWidth = true; + let pointRadius = 4; + if (labels.length === 0) { + root.innerHTML = '' + statifyDashboard.i18n.nodata + '
'; + return; + } else if (root.clientWidth < labels.length * 4) { + // Make chart scrollable, if 2px points are overlapping. + fullWidth = false; + pointRadius = 3; + } else if (root.clientWidth < labels.length * 8) { + // Shrink datapoints if 4px is overlapping, but 2 is not. + pointRadius = 2; + } + + // Determine maximum value for scaling. + const maxValue = Math.max(...values); + + // Draw chart. + const chart = new Chartist.LineChart( + root, + { + labels, + series: [values], + }, + { low: 0, showArea: true, - fullWidth: fullWidth, - width: ( fullWidth ? undefined : 5 * data.length ), + fullWidth, + width: fullWidth ? undefined : 5 * labels.length, axisX: { showGrid: false, showLabel: false, @@ -75,120 +111,136 @@ high: maxValue + 1, ticks: [ 0, - Math.round( maxValue * 1 / 4 ), - Math.round( maxValue * 2 / 4 ), - Math.round( maxValue * 3 / 4 ), + Math.round((maxValue * 1) / 4), + Math.round((maxValue * 2) / 4), + Math.round((maxValue * 3) / 4), maxValue, ], offset: 30, }, plugins: [ - Chartist.plugins.tooltip( { + Chartist.plugins.tooltip({ appendToBody: true, class: 'statify-chartist-tooltip', - } ), + }), ], - } ); - - // Replace default points with hollow circles, add "pageview(s) to value and append date (label) as metadata. - chart.on( 'draw', function( d ) { - var circle; - if ( 'point' === d.type ) { - circle = new Chartist.Svg( 'circle', { - cx: [ d.x ], - cy: [ d.y ], - r: [ pointRadius ], - 'ct:value': d.value.y + ' ' + ( d.value.y > 1 ? statifyDashboard.i18n.pageviews : statifyDashboard.i18n.pageview ), + } + ); + + // Replace default points with hollow circles, add "pageview(s) to value and append date (label) as metadata. + chart.on('draw', (d) => { + let circle; + if ('point' === d.type) { + circle = new Chartist.Svg( + 'circle', + { + cx: [d.x], + cy: [d.y], + r: [pointRadius], + 'ct:value': + d.value.y + + ' ' + + (d.value.y > 1 + ? statifyDashboard.i18n.pageviews + : statifyDashboard.i18n.pageview), 'ct:meta': labels[d.index], - }, 'ct-point' ); - d.element.replace( circle ); - } - } ); - - // Render top lists. - if ( referrerTable ) { - // Get pre-existing rows. - rows = referrerTable.querySelectorAll( 'tr' ); - - // Update or append rows. - data.referrer.forEach( function( r, idx ) { - row = document.createElement( 'TR' ); - row.innerHTML = '' + statifyDashboard.i18n.error + '
'; - } ); + /** + * Render totals table. + * + * @param {HTMLTableElement} table Table element. + * @param {{alltime: number, since: string, today: number}} data Totals data. + */ + function renderTotals(table, data) { + const rows = table.querySelectorAll('tr'); + let row = document.createElement('TR'); + row.innerHTML = + '