Add plausible

This commit is contained in:
Andras Schmelczer 2026-06-01 07:33:05 +01:00
parent 3193c509a7
commit 0e3067db19
5 changed files with 90 additions and 3 deletions

53
src/analytics.ts Normal file
View file

@ -0,0 +1,53 @@
import {
init as plausibleInit,
track as plausibleTrack,
type PlausibleEventOptions,
} from '@plausible-analytics/tracker';
const ANALYTICS_AUTO_CAPTURE_PAGEVIEWS = true;
const ANALYTICS_DOMAIN = 'schmelczer.dev/photos';
const ANALYTICS_ENDPOINT = 'https://stats.schmelczer.dev/status';
const ANALYTICS_LOGGING = import.meta.env.DEV;
let isInitialized = false;
const track = (eventName: string, options: PlausibleEventOptions = {}) => {
try {
plausibleTrack(eventName, options);
} catch (error) {
console.warn(`Could not track analytics event "${eventName}".`, error);
}
};
export const initAnalytics = () => {
if (isInitialized) {
return;
}
try {
plausibleInit({
domain: ANALYTICS_DOMAIN,
endpoint: ANALYTICS_ENDPOINT,
autoCapturePageviews: ANALYTICS_AUTO_CAPTURE_PAGEVIEWS,
logging: ANALYTICS_LOGGING,
});
isInitialized = true;
} catch (error) {
console.warn('Could not initialize analytics.', error);
}
};
export const trackPhotoView = ({
photoId,
source,
}: {
photoId: string;
source: string;
}) => {
track('Photo View', {
props: {
photoId,
source,
},
});
};

View file

@ -1,8 +1,11 @@
import './index.scss';
import { initAnalytics, trackPhotoView } from './analytics';
import { PhotoGallery, requiredElement } from './photos';
document.documentElement.classList.add('js');
initAnalytics();
const gallery = requiredElement<HTMLElement>('#gallery', HTMLElement);
const frame = requiredElement<HTMLElement>('#frame-content', HTMLElement);
const toggle = requiredElement<HTMLButtonElement>(
@ -14,4 +17,5 @@ new PhotoGallery({
gallery,
frame,
toggle,
onSelect: trackPhotoView,
});

View file

@ -5,11 +5,13 @@ type GalleryOptions = {
readonly frame: HTMLElement;
readonly toggle: HTMLButtonElement;
readonly autoAdvance?: boolean;
readonly onSelect?: (info: { photoId: string; source: string }) => void;
};
type SelectOptions = {
readonly focusThumbnail?: boolean;
readonly pause?: boolean;
readonly source?: string;
};
export const requiredElement = <T extends Element>(
@ -118,7 +120,10 @@ export class PhotoGallery {
}
event.preventDefault();
this.selectById(thumbnail.dataset.photoId, { pause: true });
this.selectById(thumbnail.dataset.photoId, {
pause: true,
source: 'thumbnail',
});
});
this.options.gallery.addEventListener('keydown', (event) => {
@ -128,6 +133,7 @@ export class PhotoGallery {
this.select(this.selectedIndex - 1, {
focusThumbnail: true,
pause: true,
source: 'keyboard',
});
break;
case 'ArrowRight':
@ -135,17 +141,23 @@ export class PhotoGallery {
this.select(this.selectedIndex + 1, {
focusThumbnail: true,
pause: true,
source: 'keyboard',
});
break;
case 'Home':
event.preventDefault();
this.select(0, { focusThumbnail: true, pause: true });
this.select(0, {
focusThumbnail: true,
pause: true,
source: 'keyboard',
});
break;
case 'End':
event.preventDefault();
this.select(this.thumbnails.length - 1, {
focusThumbnail: true,
pause: true,
source: 'keyboard',
});
break;
}
@ -202,6 +214,15 @@ export class PhotoGallery {
if (options.pause) {
this.pauseAutoAdvance();
}
// Only user-initiated selections carry a source; auto-advance stays
// untracked so the slideshow doesn't flood analytics every few seconds.
if (options.source) {
this.options.onSelect?.({
photoId: thumbnail.dataset.photoId ?? '',
source: options.source,
});
}
}
private updateSelectedThumbnail(index: number, shouldFocus: boolean): void {