2024-07-14 20:28:44 +08:00
|
|
|
import { type mastodon } from "masto";
|
2024-08-22 15:31:15 +08:00
|
|
|
import { Accessor, createEffect, createResource } from "solid-js";
|
2024-08-06 20:27:30 +08:00
|
|
|
import { createStore } from "solid-js/store";
|
2024-07-14 20:28:44 +08:00
|
|
|
|
|
|
|
type TimelineFetchTips = {
|
|
|
|
direction?: "new" | "old";
|
|
|
|
};
|
|
|
|
|
|
|
|
type Timeline = {
|
|
|
|
list(params: {
|
2024-08-22 15:31:15 +08:00
|
|
|
readonly limit?: number;
|
2024-07-14 20:28:44 +08:00
|
|
|
}): mastodon.Paginator<mastodon.v1.Status[], unknown>;
|
|
|
|
};
|
|
|
|
|
2024-08-22 15:31:15 +08:00
|
|
|
export function useTimeline(
|
|
|
|
timeline: Accessor<Timeline>,
|
|
|
|
cfg?: {
|
|
|
|
/**
|
|
|
|
* Use full refresh mode. This mode ignores paging, it will refetch the specified number
|
|
|
|
* of toots at every refetch().
|
|
|
|
*/
|
|
|
|
fullRefresh?: number;
|
|
|
|
},
|
|
|
|
) {
|
2024-07-14 20:28:44 +08:00
|
|
|
let otl: Timeline | undefined;
|
2024-08-12 22:29:55 +08:00
|
|
|
let npager: mastodon.Paginator<mastodon.v1.Status[], unknown> | undefined;
|
|
|
|
let opager: mastodon.Paginator<mastodon.v1.Status[], unknown> | undefined;
|
2024-08-06 20:27:30 +08:00
|
|
|
const [snapshot, { refetch }] = createResource<
|
2024-08-12 22:29:55 +08:00
|
|
|
{
|
|
|
|
records: mastodon.v1.Status[];
|
2024-08-22 15:31:15 +08:00
|
|
|
direction: "new" | "old" | "items";
|
2024-08-12 22:29:55 +08:00
|
|
|
tlChanged: boolean;
|
|
|
|
},
|
2024-07-14 20:28:44 +08:00
|
|
|
[Timeline],
|
|
|
|
TimelineFetchTips | undefined
|
|
|
|
>(
|
|
|
|
() => [timeline()] as const,
|
|
|
|
async ([tl], info) => {
|
2024-08-12 22:29:55 +08:00
|
|
|
let tlChanged = false;
|
2024-07-14 20:28:44 +08:00
|
|
|
if (otl !== tl) {
|
2024-08-12 22:29:55 +08:00
|
|
|
npager = opager = undefined;
|
2024-07-14 20:28:44 +08:00
|
|
|
otl = tl;
|
2024-08-12 22:29:55 +08:00
|
|
|
tlChanged = true;
|
2024-07-14 20:28:44 +08:00
|
|
|
}
|
2024-08-22 15:31:15 +08:00
|
|
|
const fullRefresh = cfg?.fullRefresh;
|
|
|
|
if (typeof fullRefresh !== "undefined") {
|
|
|
|
const records = await tl
|
|
|
|
.list({
|
|
|
|
limit: fullRefresh,
|
|
|
|
})
|
|
|
|
.next();
|
|
|
|
return {
|
|
|
|
direction: "items",
|
|
|
|
records: records.value ?? [],
|
|
|
|
end: records.done,
|
|
|
|
tlChanged,
|
|
|
|
};
|
|
|
|
}
|
2024-07-14 20:28:44 +08:00
|
|
|
const direction =
|
|
|
|
typeof info.refetching !== "boolean"
|
2024-08-12 22:29:55 +08:00
|
|
|
? (info.refetching?.direction ?? "old")
|
2024-07-14 20:28:44 +08:00
|
|
|
: "old";
|
|
|
|
if (direction === "old") {
|
2024-08-12 22:29:55 +08:00
|
|
|
if (!opager) {
|
|
|
|
opager = tl.list({}).setDirection("next");
|
2024-07-14 20:28:44 +08:00
|
|
|
}
|
2024-08-12 22:29:55 +08:00
|
|
|
const next = await opager.next();
|
2024-08-06 20:27:30 +08:00
|
|
|
return {
|
2024-08-12 22:29:55 +08:00
|
|
|
direction,
|
|
|
|
records: next.value ?? [],
|
|
|
|
end: next.done,
|
|
|
|
tlChanged,
|
2024-08-06 20:27:30 +08:00
|
|
|
};
|
2024-07-14 20:28:44 +08:00
|
|
|
} else {
|
2024-08-12 22:29:55 +08:00
|
|
|
if (!npager) {
|
|
|
|
npager = tl.list({}).setDirection("prev");
|
2024-07-14 20:28:44 +08:00
|
|
|
}
|
2024-08-12 22:29:55 +08:00
|
|
|
const next = await npager.next();
|
|
|
|
const page = next.value ?? [];
|
|
|
|
return { direction, records: page, end: next.done, tlChanged };
|
2024-07-14 20:28:44 +08:00
|
|
|
}
|
|
|
|
},
|
2024-08-06 20:27:30 +08:00
|
|
|
);
|
|
|
|
|
|
|
|
const [store, setStore] = createStore([] as mastodon.v1.Status[]);
|
|
|
|
|
|
|
|
createEffect(() => {
|
|
|
|
const shot = snapshot();
|
|
|
|
if (!shot) return;
|
2024-08-12 22:29:55 +08:00
|
|
|
const { direction, records, tlChanged } = shot;
|
|
|
|
if (tlChanged) {
|
|
|
|
setStore(() => []);
|
|
|
|
}
|
2024-08-22 15:31:15 +08:00
|
|
|
if (direction === "new") {
|
2024-08-06 20:27:30 +08:00
|
|
|
setStore((x) => [...records, ...x]);
|
2024-08-22 15:31:15 +08:00
|
|
|
} else if (direction === "old") {
|
2024-08-06 20:27:30 +08:00
|
|
|
setStore((x) => [...x, ...records]);
|
2024-08-22 15:31:15 +08:00
|
|
|
} else if (direction === "items") {
|
|
|
|
setStore(() => records);
|
2024-08-06 20:27:30 +08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return [
|
|
|
|
store,
|
|
|
|
snapshot,
|
2024-07-14 20:28:44 +08:00
|
|
|
{
|
2024-08-06 20:27:30 +08:00
|
|
|
refetch,
|
|
|
|
mutate: setStore,
|
2024-07-14 20:28:44 +08:00
|
|
|
},
|
2024-08-06 20:27:30 +08:00
|
|
|
] as const;
|
2024-07-14 20:28:44 +08:00
|
|
|
}
|
2024-10-10 16:24:06 +08:00
|
|
|
|
|
|
|
export function createTimelineSnapshot(
|
|
|
|
timeline: Accessor<Timeline>,
|
|
|
|
limit: Accessor<number>,
|
|
|
|
) {
|
|
|
|
const [shot, {refetch}] = createResource(
|
|
|
|
() => [timeline(), limit()] as const,
|
|
|
|
async ([tl, limit]) => {
|
|
|
|
const ls = await tl.list({ limit }).next();
|
|
|
|
return ls.value?.map((x) => [x]) ?? [];
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
const [snapshot, setSnapshot] = createStore([] as mastodon.v1.Status[][]);
|
|
|
|
|
|
|
|
createEffect(() => {
|
|
|
|
const nls = shot();
|
|
|
|
if (!nls) return;
|
|
|
|
const ols = Array.from(snapshot);
|
|
|
|
// The algorithm below assumes the snapshot is not changing
|
|
|
|
for (let i = 0; i < nls.length; i++) {
|
|
|
|
if (i >= ols.length) {
|
|
|
|
setSnapshot(i, nls[i]);
|
|
|
|
} else {
|
|
|
|
if (nls[i].length !== ols[i].length) {
|
|
|
|
setSnapshot(i, nls[i]);
|
|
|
|
} else {
|
|
|
|
const oth = ols[i],
|
|
|
|
nth = nls[i];
|
|
|
|
for (let j = 0; j < oth.length; j++) {
|
|
|
|
const ost = oth[j],
|
|
|
|
nst = nth[j];
|
|
|
|
for (const key of Object.keys(
|
|
|
|
nst,
|
|
|
|
) as unknown as (keyof mastodon.v1.Status)[]) {
|
|
|
|
if (ost[key] !== nst[key]) {
|
|
|
|
setSnapshot(i, j, key, nst[key]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return [snapshot, shot, {
|
|
|
|
refetch,
|
|
|
|
mutate: setSnapshot
|
|
|
|
}] as const;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function createTimeline(timeline: Accessor<Timeline>) {
|
|
|
|
// TODO
|
|
|
|
}
|