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