Compare commits

...

3 Commits

Author SHA1 Message Date
d8f40500bb
fix: make the website also work on mobile 2025-01-25 00:45:22 +01:00
1838a25f9a
fix: ignore mise.toml 2025-01-25 00:00:45 +01:00
00e7820462
feat: admin log page 2025-01-25 00:00:04 +01:00
13 changed files with 82 additions and 22 deletions

1
.gitignore vendored
View File

@ -9,3 +9,4 @@ data/
data-dev/ data-dev/
_fresh/ _fresh/
node_modules/ node_modules/
mise.toml

View File

@ -110,7 +110,7 @@ function Subline(
const ctx = useContext(HeroContext); const ctx = useContext(HeroContext);
return ( return (
<div <div
class={`relative flex items-center z-50 flex gap-5 font-sm text-light mt-3`} class={`relative flex items-center z-10 flex gap-5 font-sm text-light mt-3`}
style={{ color: ctx.image ? "#1F1F1F" : "white" }} style={{ color: ctx.image ? "#1F1F1F" : "white" }}
> >
{children} {children}

View File

@ -1,6 +1,9 @@
{ {
"lock": false, "lock": false,
"nodeModulesDir": "auto", "nodeModulesDir": "auto",
"unstable": [
"cron"
],
"tasks": { "tasks": {
"check": "deno fmt --check && deno lint && deno check **/*.ts && deno check **/*.tsx", "check": "deno fmt --check && deno lint && deno check **/*.ts && deno check **/*.tsx",
"start": "deno run --env-file -A --watch=static/,routes/ dev.ts", "start": "deno run --env-file -A --watch=static/,routes/ dev.ts",

View File

@ -6,6 +6,7 @@ import * as $_404 from "./routes/_404.tsx";
import * as $_app from "./routes/_app.tsx"; import * as $_app from "./routes/_app.tsx";
import * as $_layout from "./routes/_layout.tsx"; import * as $_layout from "./routes/_layout.tsx";
import * as $_middleware from "./routes/_middleware.ts"; import * as $_middleware from "./routes/_middleware.ts";
import * as $admin_cache_index from "./routes/admin/cache/index.tsx";
import * as $admin_log_index from "./routes/admin/log/index.tsx"; import * as $admin_log_index from "./routes/admin/log/index.tsx";
import * as $admin_performance_index from "./routes/admin/performance/index.tsx"; import * as $admin_performance_index from "./routes/admin/performance/index.tsx";
import * as $api_articles_name_ from "./routes/api/articles/[name].ts"; import * as $api_articles_name_ from "./routes/api/articles/[name].ts";
@ -68,6 +69,7 @@ const manifest = {
"./routes/_app.tsx": $_app, "./routes/_app.tsx": $_app,
"./routes/_layout.tsx": $_layout, "./routes/_layout.tsx": $_layout,
"./routes/_middleware.ts": $_middleware, "./routes/_middleware.ts": $_middleware,
"./routes/admin/cache/index.tsx": $admin_cache_index,
"./routes/admin/log/index.tsx": $admin_log_index, "./routes/admin/log/index.tsx": $admin_log_index,
"./routes/admin/performance/index.tsx": $admin_performance_index, "./routes/admin/performance/index.tsx": $admin_performance_index,
"./routes/api/articles/[name].ts": $api_articles_name_, "./routes/api/articles/[name].ts": $api_articles_name_,

View File

@ -191,7 +191,7 @@ export const KMenu = (
}} }}
style={{ outline: "none !important" }} style={{ outline: "none !important" }}
placeholder="Command" placeholder="Command"
class="bg-transparent color pl-4 outline outline outline-2 outline-offset-2" class="bg-transparent color pl-4 outline-none"
/> />
</> </>
)} )}

View File

@ -6,9 +6,9 @@ interface SetCacheOptions {
expires?: number; // Override expiration for individual cache entries expires?: number; // Override expiration for individual cache entries
} }
export const caches = new Map< const caches = new Map<
string, string,
{ info: () => { count: number; sizeInKB: number } } { info: () => { name: string; count: number; sizeInKB: number } }
>(); >();
export function createCache<T>( export function createCache<T>(
@ -48,7 +48,7 @@ export function createCache<T>(
} }
}, },
info(): { count: number; sizeInKB: number } { info() {
// Cleanup expired entries before calculating info // Cleanup expired entries before calculating info
this.cleanup(); this.cleanup();
@ -65,8 +65,8 @@ export function createCache<T>(
totalBytes += keySize + valueSize; totalBytes += keySize + valueSize;
} }
const sizeInKB = totalBytes / 1024; // Convert bytes to kilobytes const sizeInKB = Math.floor(totalBytes / 1024); // Convert bytes to kilobytes
return { count, sizeInKB }; return { name: cacheName, count, sizeInKB };
}, },
has(key: string): boolean { has(key: string): boolean {
@ -96,5 +96,10 @@ export function createCache<T>(
caches.set(cacheName, { caches.set(cacheName, {
info: api.info.bind(api), info: api.info.bind(api),
}); });
return api; return api;
} }
export function getCacheInfo() {
return [...caches.values().map((c) => c.info())];
}

View File

@ -89,10 +89,10 @@ export function createCrud<T extends GenericResource>(
return; return;
} }
const parsed = parse(content, id); let parsed = parse(content, id);
if (hasThumbnails) { if (hasThumbnails) {
return addThumbnailToResource(parsed); parsed = await addThumbnailToResource(parsed);
} }
const doc = { ...parsed, content }; const doc = { ...parsed, content };
cache.set(path, doc, { expires: 10 * 1000 }); cache.set(path, doc, { expires: 10 * 1000 });
@ -124,7 +124,6 @@ export function createCrud<T extends GenericResource>(
return; return;
} }
const newDoc = transformDocument(content, updater); const newDoc = transformDocument(content, updater);
cache.set("all", undefined);
await createDocument(path, newDoc); await createDocument(path, newDoc);
} }

View File

@ -172,3 +172,4 @@ export function getTextOfChild(child: DocumentChild): string | undefined {
} }
return; return;
} }

View File

@ -3,7 +3,7 @@ import { zodResponseFormat } from "https://deno.land/x/openai@v4.69.0/helpers/zo
import { OPENAI_API_KEY } from "@lib/env.ts"; import { OPENAI_API_KEY } from "@lib/env.ts";
import { hashString } from "@lib/helpers.ts"; import { hashString } from "@lib/helpers.ts";
import { createCache } from "@lib/cache.ts"; import { createCache } from "@lib/cache.ts";
import recipeSchema, { recipeResponseSchema } from "@lib/recipeSchema.ts"; import { recipeResponseSchema } from "@lib/recipeSchema.ts";
const openAI = OPENAI_API_KEY && new OpenAI({ apiKey: OPENAI_API_KEY }); const openAI = OPENAI_API_KEY && new OpenAI({ apiKey: OPENAI_API_KEY });

View File

@ -13,6 +13,15 @@ export function safeFileName(inputString: string): string {
return fileName; return fileName;
} }
export function toUrlSafeString(input: string): string {
return input
.trim() // Remove leading and trailing whitespace
.toLowerCase() // Convert to lowercase
.replace(/[^a-z0-9\s-]/g, "") // Remove non-alphanumeric characters except spaces and hyphens
.replace(/\s+/g, "-") // Replace spaces with hyphens
.replace(/-+/g, "-"); // Remove consecutive hyphens
}
export function extractHashTags(inputString: string) { export function extractHashTags(inputString: string) {
const hashtags = []; const hashtags = [];

View File

@ -19,6 +19,7 @@ export default function App({ Component }: PageProps) {
/> />
<link rel="manifest" href="/site.webmanifest" /> <link rel="manifest" href="/site.webmanifest" />
<meta name="msapplication-TileColor" content="#da532c" /> <meta name="msapplication-TileColor" content="#da532c" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#141218" /> <meta name="theme-color" content="#141218" />
<style>{globalCss}</style> <style>{globalCss}</style>
<title>Memorium</title> <title>Memorium</title>

32
routes/admin/cache/index.tsx vendored Normal file
View File

@ -0,0 +1,32 @@
import { MainLayout } from "@components/layouts/main.tsx";
import { Handlers, PageProps } from "$fresh/server.ts";
import { getCacheInfo } from "@lib/cache.ts";
export const handler: Handlers<
{ cacheInfo: ReturnType<typeof getCacheInfo> }
> = {
GET(_, ctx) {
return ctx.render({ cacheInfo: getCacheInfo() });
},
};
export default function Greet(
props: PageProps<
{ cacheInfo: ReturnType<typeof getCacheInfo> }
>,
) {
const { cacheInfo } = props.data;
return (
<MainLayout
url={props.url}
title="Recipes"
context={{ type: "recipe" }}
>
<code>
<pre class="text-white">
{JSON.stringify(cacheInfo, null, 2)}
</pre>
</code>
</MainLayout>
);
}

View File

@ -8,7 +8,11 @@ import * as openai from "@lib/openai.ts";
import tds from "https://cdn.skypack.dev/turndown@7.2.0"; import tds from "https://cdn.skypack.dev/turndown@7.2.0";
import { Article, createArticle } from "@lib/resource/articles.ts"; import { Article, createArticle } from "@lib/resource/articles.ts";
import { getYoutubeVideoDetails } from "@lib/youtube.ts"; import { getYoutubeVideoDetails } from "@lib/youtube.ts";
import { extractYoutubeId, isYoutubeLink } from "@lib/string.ts"; import {
extractYoutubeId,
isYoutubeLink,
toUrlSafeString,
} from "@lib/string.ts";
import { createLogger } from "@lib/log/index.ts"; import { createLogger } from "@lib/log/index.ts";
const parser = new DOMParser(); const parser = new DOMParser();
@ -69,6 +73,10 @@ async function processCreateArticle(
const url = new URL(fetchUrl); const url = new URL(fetchUrl);
function makeUrlAbsolute(src: string) { function makeUrlAbsolute(src: string) {
if (src.startsWith("//")) {
return "https:" + src;
}
if (src.startsWith("/")) { if (src.startsWith("/")) {
return `${url.origin}${src.replace(/$\//, "")}`; return `${url.origin}${src.replace(/$\//, "")}`;
} }
@ -98,20 +106,16 @@ async function processCreateArticle(
if (href.startsWith("/")) { if (href.startsWith("/")) {
return `[${content}](${url.origin}${href.replace(/$\//, "")})`; return `[${content}](${url.origin}${href.replace(/$\//, "")})`;
} } else if (href.startsWith("//")) {
return `[${content}](https:${href})`;
if (href.startsWith("#")) { } else if (href.startsWith("#")) {
if (content.length < 2) return ""; if (content.length < 2) return "";
return `[${content}](${url.href}#${href})`.replace("##", "#"); return `[${content}](${url.href}#${href})`.replace("##", "#");
} } else {
if (!href.startsWith("https://") && !href.startsWith("http://")) {
return `[${content}](${url.origin.replace(/\/$/, "")}/${ return `[${content}](${url.origin.replace(/\/$/, "")}/${
href.replace(/^\//, "") href.replace(/^\//, "")
})`; })`;
} }
return `[${content}](${href})`;
}, },
}); });
@ -125,7 +129,10 @@ async function processCreateArticle(
metaAuthor || openai.extractAuthorName(markdown), metaAuthor || openai.extractAuthorName(markdown),
]); ]);
const id = shortTitle || title || ""; console.log({ tags, shortTitle, author });
streamResponse.enqueue("postprocessing article");
const id = toUrlSafeString(shortTitle || title || "");
const meta: Article["meta"] = { const meta: Article["meta"] = {
author: (author || "").replace("@", "twitter:"), author: (author || "").replace("@", "twitter:"),
@ -161,7 +168,7 @@ async function processCreateArticle(
} }
} }
streamResponse.enqueue("finished processing"); streamResponse.enqueue("writing to disk");
await createArticle(newArticle.id, newArticle); await createArticle(newArticle.id, newArticle);