🌿 Collaborative wiki on ATProto lichen.wiki
atproto
14
fork

Configure Feed

Select the types of activity you want to include in your feed.

Move some constants to limits

juprodh 3e5e6f0b c5b1aa05

+47 -40
+1
scripts/dev-full.ts
··· 76 76 DEV_PDS_URL: pdsUrl, 77 77 DEV_PLC_URL: plcUrl, 78 78 DEV_ACCOUNTS: JSON.stringify(accounts), 79 + SEED_DB: "1", 79 80 DB_PATH: "lichen-dev.db", 80 81 }; 81 82
+8
src/lib/limits.ts
··· 38 38 timeoutMs: 10_000, 39 39 maxBytes: 10 * 1024 * 1024, 40 40 }, 41 + page: { 42 + home: 6, 43 + explore: 12, 44 + searchDefault: 6, 45 + searchMax: 12, 46 + }, 47 + snippetRadius: 60, 48 + profileCacheHours: 24, 41 49 } as const;
+1 -1
src/server/db/index.ts
··· 10 10 if (!db) { 11 11 db = new Database(DB_PATH); 12 12 initSchema(db); 13 - if (process.env["DEV_PDS_URL"]) seedIfEmpty(db); 13 + if (process.env["SEED_DB"]) seedIfEmpty(db); 14 14 } 15 15 return db; 16 16 }
+6 -4
src/server/db/queries/note.ts
··· 1 1 import type { RevisionBlob } from "../../../atproto/pds.ts"; 2 2 import { createDiff } from "../../../lib/diff.ts"; 3 3 import { escapeLikePattern } from "../../../lib/html.ts"; 4 + import { LIMITS } from "../../../lib/limits.ts"; 4 5 import { getDb } from "../index.ts"; 5 6 import type { CurrentNoteRow, NoteRow } from "../types.ts"; 6 7 import { appendRevisionTx, capSnapshots } from "./revision.ts"; 7 - 8 - const SNIPPET_RADIUS = 60; 9 8 10 9 function extractSnippet(content: string | null, query: string): string | null { 11 10 if (!content || !query) return null; ··· 13 12 const idx = lower.indexOf(query.toLowerCase()); 14 13 if (idx === -1) return null; 15 14 16 - const start = Math.max(0, idx - SNIPPET_RADIUS); 17 - const end = Math.min(content.length, idx + query.length + SNIPPET_RADIUS); 15 + const start = Math.max(0, idx - LIMITS.snippetRadius); 16 + const end = Math.min( 17 + content.length, 18 + idx + query.length + LIMITS.snippetRadius, 19 + ); 18 20 const prefix = start > 0 ? "..." : ""; 19 21 const suffix = end < content.length ? "..." : ""; 20 22 return prefix + content.slice(start, end).replace(/\n/g, " ") + suffix;
+2 -3
src/server/db/queries/profile-cache.ts
··· 1 + import { LIMITS } from "../../../lib/limits.ts"; 1 2 import { getDb } from "../index.ts"; 2 3 3 4 interface CachedProfile { ··· 8 9 updated_at: string; 9 10 } 10 11 11 - const CACHE_MAX_AGE_HOURS = 24; 12 - 13 12 export function getCachedProfile(did: string): CachedProfile | null { 14 13 const db = getDb(); 15 14 const row = db ··· 17 16 `SELECT * FROM profile_cache 18 17 WHERE did = ? AND updated_at > datetime('now', ?)`, 19 18 ) 20 - .get(did, `-${CACHE_MAX_AGE_HOURS} hours`) as CachedProfile | null; 19 + .get(did, `-${LIMITS.profileCacheHours} hours`) as CachedProfile | null; 21 20 return row; 22 21 } 23 22
+3 -4
src/server/routes/explore.ts
··· 1 1 import { Elysia } from "elysia"; 2 2 import { resolveRequestContext } from "../../lib/access.ts"; 3 + import { LIMITS } from "../../lib/limits.ts"; 3 4 import { parsePage, parseSort } from "../../lib/query-params.ts"; 4 5 import { htmlResponse } from "../../lib/response.ts"; 5 6 import { explorePage } from "../../views/explore.ts"; ··· 7 8 getWikiLanguages, 8 9 listPublicWikisPaginated, 9 10 } from "../db/queries/index.ts"; 10 - 11 - const EXPLORE_LIMIT = 12; 12 11 13 12 export const exploreRoutes = new Elysia() 14 13 .get("/explore", async ({ query, request }) => { ··· 19 18 20 19 const { wikis, total } = listPublicWikisPaginated({ 21 20 sort, 22 - limit: EXPLORE_LIMIT, 23 - offset: (page - 1) * EXPLORE_LIMIT, 21 + limit: LIMITS.page.explore, 22 + offset: (page - 1) * LIMITS.page.explore, 24 23 ...(lang && { language: lang }), 25 24 }); 26 25
+2 -3
src/server/routes/home.ts
··· 1 1 import { Elysia } from "elysia"; 2 2 import { resolveRequestContext } from "../../lib/access.ts"; 3 + import { LIMITS } from "../../lib/limits.ts"; 3 4 import { htmlResponse } from "../../lib/response.ts"; 4 5 import { homePage } from "../../views/home.ts"; 5 6 import { ··· 7 8 listPublicWikisPaginated, 8 9 } from "../db/queries/index.ts"; 9 10 10 - const HOME_LIMIT = 6; 11 - 12 11 export const homeRoute = new Elysia().get("/", async ({ request }) => { 13 12 const ctx = await resolveRequestContext(request); 14 13 const { wikis } = listPublicWikisPaginated({ 15 14 sort: "updated", 16 - limit: HOME_LIMIT, 15 + limit: LIMITS.page.home, 17 16 offset: 0, 18 17 }); 19 18 const languages = getWikiLanguages();
+3 -8
src/server/routes/note.ts
··· 14 14 } from "../../lib/orchestrators/note.ts"; 15 15 import { htmlResponse } from "../../lib/response.ts"; 16 16 import { noteUrl, redirect, wikiUrl } from "../../lib/urls.ts"; 17 - import { bookmarkButton } from "../../views/bookmark.ts"; 17 + import { resolveBookmarkHtml } from "../../views/bookmark.ts"; 18 18 import { editNotePage } from "../../views/edit-note.ts"; 19 19 import { newNotePage } from "../../views/new-note.ts"; 20 20 import { notePage } from "../../views/note.ts"; 21 21 import { shareOnBlueskyButton } from "../../views/share.ts"; 22 - import { 23 - getNoteWithCurrent, 24 - getSidebarNotes, 25 - isBookmarked, 26 - } from "../db/queries/index.ts"; 22 + import { getNoteWithCurrent, getSidebarNotes } from "../db/queries/index.ts"; 27 23 28 24 export const noteRoutes = new Elysia({ prefix: "/wiki" }) 29 25 .get("/:wikiSlug/new", async ({ params, request }) => { ··· 135 131 const sidebarNotes = getSidebarNotes(params.wikiSlug); 136 132 137 133 const msg = t(ctx.locale); 138 - const bookmarked = ctx.did ? isBookmarked(ctx.did, ctx.wiki.at_uri) : false; 139 - const bmHtml = bookmarkButton(ctx.wiki.at_uri, bookmarked, msg); 134 + const bmHtml = resolveBookmarkHtml(ctx.did, ctx.wiki.at_uri, msg); 140 135 const publicUrl = getAtprotoEnv()?.publicUrl ?? new URL(request.url).origin; 141 136 const canonicalUrl = `${publicUrl}${noteUrl(params.wikiSlug, params.noteSlug)}`; 142 137 const shareHtml = shareOnBlueskyButton(data.note.title, canonicalUrl, msg);
+4 -6
src/server/routes/search.ts
··· 1 1 import { Elysia } from "elysia"; 2 2 import { canRead, resolveRequestContext } from "../../lib/access.ts"; 3 + import { LIMITS } from "../../lib/limits.ts"; 3 4 import { parsePage, parseSort } from "../../lib/query-params.ts"; 4 5 import { htmlResponse } from "../../lib/response.ts"; 5 6 import { renderNoteResults } from "../../views/search-results.ts"; ··· 9 10 } from "../../views/wiki-list.ts"; 10 11 import { listPublicWikisPaginated, searchNotes } from "../db/queries/index.ts"; 11 12 12 - const MAX_LIMIT = 12; 13 - const DEFAULT_LIMIT = 6; 14 - 15 13 function parseLimit(value: unknown): number { 16 14 const n = Number(value); 17 - if (Number.isInteger(n) && n >= 1 && n <= MAX_LIMIT) return n; 18 - return DEFAULT_LIMIT; 15 + if (Number.isInteger(n) && n >= 1 && n <= LIMITS.page.searchMax) return n; 16 + return LIMITS.page.searchDefault; 19 17 } 20 18 21 19 export const searchRoutes = new Elysia().get( ··· 55 53 }); 56 54 57 55 // Explore page gets pagination, home page gets explore link 58 - if (limit > DEFAULT_LIMIT) { 56 + if (limit > LIMITS.page.searchDefault) { 59 57 return htmlResponse( 60 58 wikiGridWithPagination(wikis, ctx.locale, { 61 59 sort,
+2 -4
src/server/routes/wiki.ts
··· 25 25 import { htmlResponse } from "../../lib/response.ts"; 26 26 import { noteUrl, redirect, wikiUrl } from "../../lib/urls.ts"; 27 27 import { accessDeniedPage } from "../../views/access-denied.ts"; 28 - import { bookmarkButton } from "../../views/bookmark.ts"; 28 + import { resolveBookmarkHtml } from "../../views/bookmark.ts"; 29 29 import { newWikiPage } from "../../views/new-wiki.ts"; 30 30 import { notePage } from "../../views/note.ts"; 31 31 import { settingsPage } from "../../views/settings.ts"; ··· 34 34 import { 35 35 getNoteWithCurrent, 36 36 getSidebarNotes, 37 - isBookmarked, 38 37 listMembers, 39 38 listRequests, 40 39 } from "../db/queries/index.ts"; ··· 231 230 232 231 const cacheable = !ctx.session && ctx.wiki.visibility === "public" ? 60 : 0; 233 232 const msg = t(ctx.locale); 234 - const bookmarked = ctx.did ? isBookmarked(ctx.did, ctx.wiki.at_uri) : false; 235 - const bmHtml = bookmarkButton(ctx.wiki.at_uri, bookmarked, msg); 233 + const bmHtml = resolveBookmarkHtml(ctx.did, ctx.wiki.at_uri, msg); 236 234 237 235 if (homeData) { 238 236 const { html, hasViz } = renderMarkdown(
+10
src/views/bookmark.ts
··· 1 1 import { escapeHtml } from "../lib/html.ts"; 2 2 import type { Messages } from "../lib/i18n/index.ts"; 3 + import { isBookmarked } from "../server/db/queries/index.ts"; 3 4 import { THEME } from "./theme.ts"; 5 + 6 + export function resolveBookmarkHtml( 7 + did: string | null, 8 + wikiAtUri: string, 9 + msg: Messages, 10 + ): string { 11 + const bookmarked = did ? isBookmarked(did, wikiAtUri) : false; 12 + return bookmarkButton(wikiAtUri, bookmarked, msg); 13 + } 4 14 5 15 export function bookmarkButton( 6 16 wikiAtUri: string,
+3 -4
src/views/explore.ts
··· 1 1 import { t } from "../lib/i18n/index.ts"; 2 + import { LIMITS } from "../lib/limits.ts"; 2 3 import type { 3 4 WikiSort, 4 5 WikiWithNoteCount, ··· 6 7 import { type LayoutOptions, layout } from "./layout.ts"; 7 8 import { THEME } from "./theme.ts"; 8 9 import { wikiGridWithPagination, wikiToolbar } from "./wiki-list.ts"; 9 - 10 - const EXPLORE_LIMIT = 12; 11 10 12 11 export function explorePage( 13 12 wikis: WikiWithNoteCount[], ··· 21 20 const toolbar = wikiToolbar(languages, locale, { 22 21 sort: options.sort, 23 22 gridTargetId: "wiki-grid", 24 - limit: EXPLORE_LIMIT, 23 + limit: LIMITS.page.explore, 25 24 }); 26 25 27 26 const gridHtml = wikiGridWithPagination(wikis, locale, { 28 27 sort: options.sort, 29 28 page: options.page, 30 29 total, 31 - limit: EXPLORE_LIMIT, 30 + limit: LIMITS.page.explore, 32 31 }); 33 32 34 33 return layout(
+2 -3
src/views/home.ts
··· 1 1 import { t } from "../lib/i18n/index.ts"; 2 + import { LIMITS } from "../lib/limits.ts"; 2 3 import { profileUrl } from "../lib/urls.ts"; 3 4 import type { WikiWithNoteCount } from "../server/db/queries/index.ts"; 4 5 import { type LayoutOptions, layout } from "./layout.ts"; 5 6 import { outlineSmallButtonClass, primaryButtonClass, THEME } from "./theme.ts"; 6 7 import { wikiGridWithExploreLink, wikiToolbar } from "./wiki-list.ts"; 7 - 8 - const HOME_LIMIT = 6; 9 8 10 9 export function homePage( 11 10 wikis: WikiWithNoteCount[], ··· 18 17 const toolbar = wikiToolbar(languages, locale, { 19 18 sort: "updated", 20 19 gridTargetId: "wiki-grid", 21 - limit: HOME_LIMIT, 20 + limit: LIMITS.page.home, 22 21 }); 23 22 24 23 const gridHtml = wikiGridWithExploreLink(wikis, locale);