Move to astro
All checks were successful
Build & Depoly / Depoly blog (push) Successful in 1m52s

This commit is contained in:
thislight 2025-04-11 21:50:48 +08:00
parent 729704984c
commit 4bd975796e
148 changed files with 4865 additions and 27561 deletions

12
src/utils/dom.ts Normal file
View file

@ -0,0 +1,12 @@
export function wrapElementsInClass(
elements: Iterable<Element>,
classes: string[],
) {
for (const e of elements) {
const wrapper = document.createElement("div");
wrapper.dataset.buckBaked = "false";
wrapper.classList.add(...classes);
e.replaceWith(wrapper);
wrapper.appendChild(e);
}
}

79
src/utils/posts.ts Normal file
View file

@ -0,0 +1,79 @@
import type { CollectionEntry } from "astro:content";
function toPadLeft(n: number) {
if (n < 10) {
return `0${n}`;
} else {
return n.toString();
}
}
export function getPostParam(post: CollectionEntry<"posts">) {
const meta = post.data;
return {
year: meta.date.getUTCFullYear(),
month: toPadLeft(meta.date.getUTCMonth() + 1),
date: toPadLeft(meta.date.getUTCDate() + 1),
postid: post.id,
};
}
export function postParamlink(post: CollectionEntry<"posts">) {
const meta = post.data;
return `/${meta.date.getUTCFullYear()}/${toPadLeft(meta.date.getUTCMonth() + 1)}/${toPadLeft(meta.date.getUTCDate() + 1)}/${post.id}/`;
}
export function getAllTags(posts: CollectionEntry<"posts">[]) {
const tags = new Set(posts.filter(shouldBeVisible).flatMap((post) => post.data.tags || []));
return tags.values().toArray().sort();
}
function p50Of(histogram: number[]) {
if (histogram.length === 0) {
return undefined;
} else if (histogram.length === 1) {
return histogram[0];
}
const idx = histogram.length / 2;
if (Math.trunc(idx) !== idx) {
const leftIdx = Math.trunc(idx);
const rightIdx = Math.ceil(idx);
return (histogram[leftIdx] + histogram[rightIdx]) / 2;
} else {
return histogram[idx];
}
}
export function getHotTags(posts: CollectionEntry<"posts">[], tags: string[]) {
const freqs = new Map(tags.map((x) => [x, 0]));
for (const post of posts) {
for (const tag of post.data.tags || []) {
const count = freqs.get(tag)!;
freqs.set(tag, count + 1);
}
}
const histogram = freqs.values().toArray().sort();
const p50 = p50Of(histogram);
if (p50 === undefined) {
return [];
}
const results = [] as string[];
for (const [tag, freq] of freqs.entries()) {
if (freq > p50) {
results.push(tag);
}
}
return results;
}
export function shouldBeVisible(post: CollectionEntry<"posts">) {
return !post.data.visibility || post.data.visibility === "visible"
}

51
src/utils/table.ts Normal file
View file

@ -0,0 +1,51 @@
import { createRenderEffect, onCleanup } from "solid-js";
function findCorrespondingTableHeadOfCell(
target: HTMLTableCellElement,
): HTMLTableCellElement | null {
const parentElement = target.parentElement as HTMLTableRowElement;
const collection = parentElement.cells;
for (let i = 0; i < collection.length; i++) {
if (collection.item(i) === target) {
const tbody = parentElement.parentElement!;
const table = tbody.parentElement!;
const allHeads = table.querySelectorAll("thead > tr > th");
return allHeads.item(i) as HTMLTableCellElement;
}
}
return null;
}
const TABLE_COLUMN_HOVER_CLASSNAME = "table-column-hover";
export function renderAdvancedTablesOn(elements: Iterable<Element>) {
for (const e of elements) {
if (!(e instanceof HTMLTableElement)) {
console.warn(e, "is unable to be rendered as advanced table");
continue;
}
const onMouseEnter = (ev: MouseEvent) => {
const head = findCorrespondingTableHeadOfCell(
ev.target as HTMLTableCellElement,
)!;
if (!head.classList.contains(TABLE_COLUMN_HOVER_CLASSNAME)) {
head.classList.add(TABLE_COLUMN_HOVER_CLASSNAME);
}
};
const onMouseLeave = (ev: MouseEvent) => {
const head = findCorrespondingTableHeadOfCell(
ev.target as HTMLTableCellElement,
)!;
if (head.classList.contains(TABLE_COLUMN_HOVER_CLASSNAME)) {
head.classList.remove(TABLE_COLUMN_HOVER_CLASSNAME);
}
};
for (const td of e.querySelectorAll("td")) {
td.addEventListener("mouseenter", onMouseEnter);
td.addEventListener("mouseleave", onMouseLeave);
}
}
}

View file

@ -0,0 +1,67 @@
import { differenceInMilliseconds } from "date-fns";
import { createRenderEffect, createSignal, onCleanup, untrack } from "solid-js";
import { isServer } from "solid-js/web";
export enum DateRefreshPercision {
seconds = 0,
minutes = 1,
hours,
days,
}
function useCurrentDate(
percision:
| (() => DateRefreshPercision)
| DateRefreshPercision = DateRefreshPercision.minutes,
) {
const [current, setCurrent] = createSignal(new Date());
const updateInterval = () => {
switch (typeof percision === "function" ? percision() : percision) {
case DateRefreshPercision.seconds:
return 1000;
case DateRefreshPercision.minutes:
return 15 * 1000;
case DateRefreshPercision.hours:
return 60 * 1000;
case DateRefreshPercision.days:
return 60 * 60 * 1000;
}
};
let id: undefined | number;
const update = () => setCurrent(new Date());
createRenderEffect(() => {
if (typeof id !== "undefined") {
window.clearInterval(id);
id = undefined;
}
const interval = updateInterval();
const now = new Date();
if (differenceInMilliseconds(now, untrack(current)) <= interval) {
update();
}
id = window.setInterval(update, interval);
});
onCleanup(() => {
if (typeof id !== "undefined") {
window.clearInterval(id);
id = undefined;
}
});
return current;
}
function useCurrentDateServer() {
const now = new Date();
return () => now;
}
export default isServer ? useCurrentDateServer : useCurrentDate;

82
src/utils/wikipedia.ts Normal file
View file

@ -0,0 +1,82 @@
import { match } from "@formatjs/intl-localematcher";
async function selectWikipediaInstance(
instance: string,
escapedTitle: string,
langs: readonly string[],
userAgent: string,
) {
const url = `https://${instance}.wikipedia.org/w/rest.php/v1/page/${escapedTitle}/links/language`;
const response = await fetch(url, {
headers: {
"api-user-agent": userAgent,
},
});
if (!(response.ok && response.status === 200)) {
throw new TypeError("resource not found");
}
const languages = (await response.json()) as {
code: string;
key: string;
}[];
const code = match(
langs,
languages.map((x) => x.code),
instance,
);
return languages.find((x) => x.code === code)!;
}
export async function fetchWikipediaSummary(
instance: string,
title: string,
opts: {
langs?: readonly string[];
userAgent: string;
},
) {
const escapedTitle = encodeURIComponent(title);
const { key, code } = opts.langs
? await selectWikipediaInstance(
instance,
escapedTitle,
opts.langs,
opts.userAgent,
)
: {
key: title,
code: instance,
};
const baseUrl = `https://${code}.wikipedia.org`;
const summaryUrl = new URL(
`${baseUrl}/api/rest_v1/page/summary/${key}?redirect=false`,
);
const response = await fetch(summaryUrl, {
headers: {
accept:
'application/json; charset=utf-8; profile="https://www.mediawiki.org/wiki/Specs/Summary/1.4.2"',
"api-user-agent": `Rubicon's Rubicon (on Server, https://rubicon.lightstands.xyz/external-resource-usage/)`,
},
});
const data = (await response.json()) as {
extract: string;
titles: { normalized: string };
lang: string;
content_urls: {
desktop: {
page: string;
};
};
};
return data;
}