Skip to content

Commit

Permalink
feat: add sciences
Browse files Browse the repository at this point in the history
  • Loading branch information
taskylizard committed Nov 1, 2024
1 parent 76dad0c commit ffa7f75
Show file tree
Hide file tree
Showing 9 changed files with 393 additions and 137 deletions.
13 changes: 0 additions & 13 deletions .prettierignore

This file was deleted.

15 changes: 0 additions & 15 deletions .prettierrc.json

This file was deleted.

16 changes: 16 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"extends": ["@taskylizard/biome-config"],
"linter": {
"rules": {
"style": {
"noDefaultExport": "off",
"useNamingConvention": "off"
},
"correctness": {
"useHookAtTopLevel": "off",
"noUnusedVariables": "off"
}
}
}
}
236 changes: 236 additions & 0 deletions docs/.vitepress/theme/composables/science.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
type EventName =
| "pageview"
| "Outbound Link: Click"
| "File Download"
| (string & {});

interface Options {
/**
* The domain to bind the event to.
*
* @default location.hostname
*/
readonly domain: Location["hostname"];
/**
* Hostnames to ignore. Useful for development environments.
*
* @default ['localhost']
*/
readonly ignoredHostnames: string[];
}

interface CallbackArgs {
readonly status: number | null;
}

interface EventOptions extends EventProps {
data?: Partial<EventData>;
/**
* Callback to be called after the event is sent.
*/
callback?: (args: CallbackArgs) => void;
}

/**
* Shape of the event options
*/
interface EventProps {
/**
* Properties to be bound to the event.
*/
readonly props?: { readonly [propName: string]: string | number | boolean };
}

/**
* Shape of the event data
*/
interface EventData {
/**
* The URL to bind the event to.
*
* @default location.href
*/
readonly url: Location["href"];
/**
* The referrer to bind the event to.
*
* @default document.referrer
*/
readonly referrer: Document["referrer"] | null;
/**
* The current device's width.
*
* @default window.innerWidth
*/
readonly deviceWidth: Window["innerWidth"];
}

/**
* Shape of the event payload
*
* @internal
*/
interface EventPayload {
readonly n: string;
readonly u: Location["href"];
readonly d: Location["hostname"];
readonly r: Document["referrer"] | null;
readonly w: Window["innerWidth"];
readonly h: 1 | 0;
readonly p?: string;
}

function createPayload(
eventName: string,
_opts: Required<Options>,
data: EventData,
options?: EventOptions,
): EventPayload {
const payload: EventPayload = {
n: eventName,
u: data.url,
d: _opts.domain,
r: data.referrer,
w: data.deviceWidth,
h: 0,
p: options && options.props ? JSON.stringify(options.props) : undefined,
};

return payload;
}

/**
* Check if the protocol is file
*
* @returns - If the protocol is file
*/
function isFile(
/** The current protocol */
protocol: string,
): boolean {
return protocol === "file:";
}
/**
* Check if the user has excluded themself using `localStorage`.
*
* @returns - If the user exclude themself
*/
function isUserSelfExcluded(): boolean {
// If localStorage is not available, return false
try {
return localStorage.getItem("plausible_ignore") === "true";
} catch (_error) {
console.error(_error);
return false;
}
}

/**
* Create the event data.
*
* @returns - The event data
*/
function createEventData(
/** The event data */
data: Partial<EventData> = {},
): EventData {
const { url, referrer, deviceWidth } = data;

return {
url: url ?? window.location.href,
referrer: referrer ?? document.referrer,
deviceWidth: deviceWidth ?? window.innerWidth,
};
}

/**
* Send an event to the API.
*/
function _sendEvent(
endpoint: string,
payload: EventPayload,
callback?: (args: CallbackArgs) => void,
) {
return fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "text/plain",
},
body: JSON.stringify(payload),
})
.then((response: Response) => {
callback?.({ status: response.status });
})
.catch(() => {});
}



export function createScienceProvider() {
if (import.meta.env.SSR && !import.meta.env.PROD) return;
const protocol = window.location.protocol;

const plausibleOptions: Options = {
domain: window.location.hostname,
ignoredHostnames: ["localhost"],
};

const sendEvent = (
payload: EventPayload,
callback?: (args: CallbackArgs) => void,
) => _sendEvent("https://wotaku.tasky.workers.dev/science", payload, callback);

/**
* Send a custom event.
*
* @param eventName - The event name
* @param options - The event options
*/
function trackEvent(eventName: EventName, options?: EventOptions) {
const data = createEventData(options?.data);
const payload = createPayload(eventName, plausibleOptions, data, options);

// Ignore events if the protocol is file, the hostname should be ignored or the user excluded themself.
if (isFile(protocol) || isUserSelfExcluded()) {
// Call the callback if it exists since we are not sending the event.
options?.callback?.({ status: null });
} else {
return sendEvent(payload, options?.callback);
}
}

/**
* Send a pageview event.
*/
function trackPageview(options?: EventOptions) {
return trackEvent("pageview", options);
}
/**
* Add Plausible script to the page to enable site verification.
*/
(window as unknown as any).plausible = trackEvent;

/**
* Encapsulate the pageview event to allow user to update the options at any time.
*/
function page() {
trackPageview();
}

const originalPushState = history.pushState;

function install() {
if (originalPushState) {
history.pushState = function (...args) {
originalPushState.apply(this, args);
page();
};
window.addEventListener("popstate", page);
}

// Initial pageview
page();
}

return install();
}
2 changes: 2 additions & 0 deletions docs/.vitepress/theme/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import "virtual:uno.css";
import Feedback from "./components/Feedback.vue";
import Layout from "./Layout.vue";
import PreferenceRadio from "./components/PreferenceRadio.vue";
import { createScienceProvider } from "./composables/science";

export default {
extends: DefaultTheme,
Expand All @@ -33,6 +34,7 @@ export default {
},
},
});
createScienceProvider()
// @ts-expect-error
enhanceAppWithTabs(app);
app.component("Button", Button);
Expand Down
15 changes: 7 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
"api:dev": "nitropack dev",
"api:build": "nitropack build",
"api:preview": "node .output/server/index.mjs",
"format": "prettier --cache -w .",
"format": "biome format . --write",
"lint": "biome lint .",
"lint:fix": "biome lint . --write",
"lint:fix:unsafe": "biome lint . --unsafe",
"og:dev": "x-satori -t ./docs/.vitepress/theme/components/OgImageTemplate.vue -c ./.vitepress/hooks/satoriConfig.ts --dev"
},
"dependencies": {
Expand All @@ -22,7 +25,6 @@
"@mdit/plugin-img-size": "^0.8.0",
"@resvg/resvg-js": "^2.6.2",
"@vueuse/core": "^10.11.1",
"fast-glob": "^3.3.2",
"floating-vue": "^5.2.2",
"itty-fetcher": "^0.9.4",
"markdown-it": "^14.1.0",
Expand All @@ -38,6 +40,7 @@
"zod": "^3.23.8"
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@iconify-json/fluent-emoji": "^1.2.1",
"@iconify-json/fxemoji": "^1.2.0",
"@iconify-json/gg": "^1.2.1",
Expand All @@ -57,18 +60,14 @@
"@nolebase/vitepress-plugin-enhanced-readabilities": "^2.6.1",
"@nolebase/vitepress-plugin-git-changelog": "^2.6.1",
"@nolebase/vitepress-plugin-page-properties": "^2.6.1",
"@taskylizard/biome-config": "^1.0.5",
"@types/node": "^22.8.2",
"prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.8",
"sass": "^1.80.4",
"wrangler": "^3.83.0"
},
"pnpm": {
"peerDependencyRules": {
"ignoreMissing": [
"@algolia/client-search",
"search-insights"
]
"ignoreMissing": ["@algolia/client-search", "search-insights"]
}
}
}
Loading

0 comments on commit ffa7f75

Please sign in to comment.