xref: /plugin/statistics/script.js (revision 41d1fffc4a3b58bed7f96d983ba8317fe9e225a5)
1/* DOKUWIKI:include_once lib/chart.js */
2/* DOKUWIKI:include_once lib/chartjs-plugin-datalabels.js */
3
4/* globals JSINFO, DOKU_BASE, DokuCookie */
5
6/**
7 * Modern Statistics Plugin
8 */
9class StatisticsPlugin {
10    constructor() {
11        this.data = {};
12    }
13
14    /**
15     * Initialize the statistics plugin
16     */
17    async init() {
18        try {
19            this.buildTrackingData();
20            await this.logPageView();
21            this.attachEventListeners();
22        } catch (error) {
23            console.error('Statistics plugin initialization failed:', error);
24        }
25    }
26
27    /**
28     * Build tracking data object
29     */
30    buildTrackingData() {
31        const now = Date.now();
32        this.data = {
33            p: JSINFO.id,
34            r: document.referrer,
35            sx: screen.width,
36            sy: screen.height,
37            vx: window.innerWidth,
38            vy: window.innerHeight,
39            rnd: now
40        };
41    }
42
43    /**
44     * Log page view based on action
45     */
46    async logPageView() {
47        const action = JSINFO.act === 'show' ? 'v' : 's';
48        await this.logView(action);
49    }
50
51    /**
52     * Attach event listeners for tracking
53     */
54    attachEventListeners() {
55        // Track external link clicks
56        document.querySelectorAll('a.urlextern').forEach(link => {
57            link.addEventListener('click', this.logExternal.bind(this));
58        });
59
60        // Track page unload
61        window.addEventListener('beforeunload', this.logExit.bind(this));
62    }
63
64    /**
65     * Log a view or session
66     * @param {string} action 'v' = view, 's' = session
67     */
68    async logView(action) {
69        const params = new URLSearchParams(this.data);
70        const url = `${DOKU_BASE}lib/plugins/statistics/log.php?do=${action}&${params}`;
71
72        try {
73            // Use fetch with keepalive for better reliability
74            await fetch(url, {
75                method: 'GET',
76                keepalive: true,
77                cache: 'no-cache'
78            });
79        } catch (error) {
80            // Fallback to image beacon for older browsers
81            const img = new Image();
82            img.src = url;
83        }
84    }
85
86    /**
87     * Log clicks to external URLs
88     * @param {Event} event Click event
89     */
90    logExternal(event) {
91        const params = new URLSearchParams(this.data);
92        const url = `${DOKU_BASE}lib/plugins/statistics/log.php?do=o&ol=${encodeURIComponent(event.target.href)}&${params}`;
93
94        // Use sendBeacon for reliable tracking
95        if (navigator.sendBeacon) {
96            navigator.sendBeacon(url);
97        } else {
98            // Fallback for older browsers
99            const img = new Image();
100            img.src = url;
101        }
102
103        return true;
104    }
105
106    /**
107     * Log page exit as session info
108     */
109    logExit() {
110        const params = new URLSearchParams(this.data);
111        const url = `${DOKU_BASE}lib/plugins/statistics/log.php?do=s&${params}`;
112
113        if (navigator.sendBeacon) {
114            navigator.sendBeacon(url);
115        }
116    }
117}
118
119// Initialize when DOM is ready
120if (document.readyState === 'loading') {
121    document.addEventListener('DOMContentLoaded', () => {
122        new StatisticsPlugin().init();
123    });
124} else {
125    // DOM already loaded
126    new StatisticsPlugin().init();
127}
128
129class ChartComponent extends HTMLElement {
130    connectedCallback() {
131        this.renderChart();
132    }
133
134    renderChart() {
135        const chartType = this.getAttribute('type');
136        const data = JSON.parse(this.getAttribute('data'));
137
138        console.log('data', data);
139
140        const canvas = document.createElement("canvas");
141        canvas.height = this.getAttribute('height') || 300;
142        canvas.width = this.getAttribute('width') || 300;
143
144        this.appendChild(canvas);
145
146        const ctx = canvas.getContext('2d');
147
148        // basic config
149        const config = {
150            type: chartType,
151            data: data,
152            options: {
153                responsive: false,
154            },
155        };
156
157        // percentage labels and tooltips for pie charts
158        if (chartType === "pie") {
159            // chartjs-plugin-datalabels needs to be registered
160            Chart.register(ChartDataLabels);
161
162            config.options.plugins = {
163                datalabels: {
164                    formatter: (value, context) => {
165                        const total = context.chart.data.datasets[0].data.reduce((a, b) => a + b, 0);
166                        return ((value / total) * 100).toFixed(2) + '%'; // percentage
167                    },
168                    color: '#fff',
169                }
170            };
171        }
172
173        new Chart(ctx, config);
174    }
175}
176
177customElements.define('chart-component', ChartComponent);
178