forgor lol

This commit is contained in:
laura 2025-11-13 01:49:35 -03:00
parent 869f3dfbe3
commit 862b11b9ef
Signed by: w
GPG key ID: BCD2117C99E69817
34 changed files with 516 additions and 179 deletions

View file

@ -1,8 +1,9 @@
/**
* Copyright (c) 2025 favewa
* Copyright (c) 2025 miwa
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { calculateReadingTime, ReadingTimeResult } from "./blog.ts";
import { withInterval } from "./temp.ts";
export interface Report {
@ -14,6 +15,7 @@ export interface Report {
createdAt: string;
tags?: string[];
visibility: string;
readingTime: ReadingTimeResult;
date: Date;
}
@ -59,6 +61,7 @@ export async function getPosts(cursor?: string): Promise<Report[]> {
...value,
rkey: rkey(uri),
date: new Date(value.createdAt),
readingTime: value.content ? calculateReadingTime(value.content) : 1,
createdAt: new Date(value.createdAt).toISOString().slice(0, 10).replace(
/-/g,
".",
@ -82,6 +85,9 @@ export async function retrieveReport(rkey: string): Promise<Report | null> {
return {
...response.value,
readingTime: response.value.content
? calculateReadingTime(response.value.content)
: 1,
cid: response.cid,
rkey,
} as Report;

68
utils/blog.ts Normal file
View file

@ -0,0 +1,68 @@
/**
* Copyright (c) 2025 miwa
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
export interface ReadingTimeOptions {
wordsPerMinute?: number;
imageTime?: number;
codeBlockTime?: number;
chineseKoreanReadingSpeed?: number;
}
export interface ReadingTimeResult {
minutes: number;
words: number;
images: number;
codeBlocks: number;
}
export function formatReadingTime(input: ReadingTimeResult) {
return `${input.words} words · ${
input.images ? `${input.images} images · ` : ""
}${
input.codeBlocks ? `${input.codeBlocks} code blocks · ` : ""
}${input.minutes} minutes`;
}
export function calculateReadingTime(
input: string,
options: ReadingTimeOptions = {},
): ReadingTimeResult {
const {
wordsPerMinute = 200,
imageTime = 12,
codeBlockTime = 15,
chineseKoreanReadingSpeed = 260,
} = options;
let images = 0, preBlocks = 0, codeBlocks = 0;
const text = input
.replace(/<img[^>]*>/gi, () => (images++, ""))
.replace(/<pre[^>]*>[\s\S]*?<\/pre>/gi, () => (preBlocks++, ""))
.replace(/<code[^>]*>[\s\S]*?<\/code>/gi, () => (codeBlocks++, ""));
const cjkRegex = /[\u4E00-\u9FFF\u3040-\u309F\u30A0-\u30FF\uAC00-\uD7AF]/g;
const cjkMatches = text.match(cjkRegex);
const cjkCharacters = cjkMatches?.length ?? 0;
const textWithoutCJK = cjkCharacters ? text.replace(cjkRegex, " ") : text;
const words = textWithoutCJK.trim().split(/\s+/).reduce(
(n, w) => n + (w && /\w/.test(w) ? 1 : 0),
0,
);
const totalMinutes = Math.ceil(
words / wordsPerMinute +
cjkCharacters / chineseKoreanReadingSpeed +
(images * imageTime + (preBlocks + codeBlocks) * codeBlockTime) / 60,
);
return {
minutes: Math.max(totalMinutes || 2, 2),
words: words + cjkCharacters,
images,
codeBlocks: preBlocks + codeBlocks,
};
}

View file

@ -1,5 +1,5 @@
/**
* Copyright (c) 2025 favewa
* Copyright (c) 2025 miwa
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

View file

@ -1,5 +1,5 @@
/**
* Copyright (c) 2025 favewa
* Copyright (c) 2025 miwa
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

View file

@ -1,5 +1,5 @@
/**
* Copyright (c) 2025 favewa
* Copyright (c) 2025 miwa
* SPDX-License-Identifier: AGPL-3.0-or-later
*/