1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2025-04-26 04:45:22 -04:00

a bit more cleanup

This commit is contained in:
Jake Jarvis 2025-03-07 14:56:49 -05:00
parent 354dade9aa
commit 9229f92c0c
Signed by: jake
SSH Key Fingerprint: SHA256:nCkvAjYA6XaSPUqc4TfbBQTpzr8Xj7ritg/sGInCdkc
23 changed files with 50 additions and 58 deletions

View File

@ -1,25 +1,32 @@
# required. storage for hit counter endpoints at /api/count and /api/hits.
# currently uses Postgres, but this can be changed in prisma/schema.prisma.
# required. storage for hit counter's server component at app/notes/[slug]/counter.tsx and API endpoint at /api/hits.
# currently set automatically by Vercel's Neon integration, but this can be changed in prisma/schema.prisma.
# https://www.prisma.io/docs/postgres/overview
# https://vercel.com/marketplace/neon
DATABASE_URL=
# requred. used for /projects grid, built with SSG. only needs the "public_repo" scope since we don't need/want to
# required. used for /projects grid, built with ISR. only needs the "public_repo" scope since we don't need/want to
# showcase any private repositories, obviously.
# https://github.com/settings/tokens/new?scopes=public_repo
GH_PUBLIC_TOKEN=
GITHUB_TOKEN=
# required for production. sends contact form submissions via /api/contact.
# optional. enables comments on blog posts via GitHub discussions.
# https://giscus.app/
NEXT_PUBLIC_GISCUS_REPO_ID=
NEXT_PUBLIC_GISCUS_CATEGORY_ID=
# required for production. sends contact form submissions via a server action (see app/contact/actions.ts).
# https://resend.com/api-keys
# currently set automatically by Vercel's Resend integration.
# https://vercel.com/integrations/resend
RESEND_API_KEY=
# optional. send submissions from noreply@{RESEND_DOMAIN}; defaults to onboarding@resend.dev. sender's real email is
# passed via a Reply-To header. setting this makes zero difference to the user.
# https://resend.com/docs/send-with-nodemailer-smtp
RESEND_DOMAIN=
# required for production. falls back to testing keys if not set or in dev environment:
# required for production. site key must be prefixed with NEXT_PUBLIC_ since it is used to embed the captcha widget.
# falls back to testing keys if not set or in dev environment:
# https://developers.cloudflare.com/turnstile/troubleshooting/testing/
# site key must be prefixed with NEXT_PUBLIC_ since it is used to embed the actual captcha widget.
# https://developers.cloudflare.com/turnstile/
NEXT_PUBLIC_TURNSTILE_SITE_KEY=
# used only for backend validation of contact form submissions on /api/contact.
# used for backend validation of turnstile result.
TURNSTILE_SECRET_KEY=

View File

@ -3,7 +3,7 @@
import { headers } from "next/headers";
import { z } from "zod";
import { Resend } from "resend";
import config from "../../lib/config";
import config from "../../lib/config/constants";
const schema = z.object({
name: z.string().min(1, { message: "Name is required" }),

View File

@ -3,7 +3,7 @@ import { buildFeed } from "../../lib/helpers/build-feed";
export const dynamic = "force-static";
export const GET = async () => {
return new Response(await buildFeed({ type: "atom" }), {
return new Response((await buildFeed()).atom1(), {
headers: {
"content-type": "application/atom+xml; charset=utf-8",
},

View File

@ -3,7 +3,7 @@ import { buildFeed } from "../../lib/helpers/build-feed";
export const dynamic = "force-static";
export const GET = async () => {
return new Response(await buildFeed({ type: "rss" }), {
return new Response((await buildFeed()).rss2(), {
headers: {
"content-type": "application/rss+xml; charset=utf-8",
},

View File

@ -4,7 +4,7 @@ import { ThemeProvider } from "../contexts/ThemeContext";
import Header from "../components/Header";
import Footer from "../components/Footer";
import { SkipToContentLink, SkipToContentTarget } from "../components/SkipToContent";
import config from "../lib/config";
import config from "../lib/config/constants";
import type { Metadata } from "next";
import type { Person, WithContext } from "schema-dts";

View File

@ -1,4 +1,4 @@
import config from "../lib/config";
import config from "../lib/config/constants";
import type { MetadataRoute } from "next";
const manifest = (): MetadataRoute.Manifest => {

View File

@ -8,7 +8,7 @@ import Loading from "../../../components/Loading";
import HitCounter from "./counter";
import { getPostSlugs, getFrontMatter } from "../../../lib/helpers/posts";
import { metadata as defaultMetadata } from "../../layout";
import config from "../../../lib/config";
import config from "../../../lib/config/constants";
import { FiCalendar, FiTag, FiEdit, FiEye } from "react-icons/fi";
import type { Metadata, Route } from "next";
import type { Article, WithContext } from "schema-dts";

View File

@ -2,7 +2,7 @@ import Content from "../../components/Content";
import Link from "../../components/Link";
import Time from "../../components/Time";
import { getAllPosts } from "../../lib/helpers/posts";
import config from "../../lib/config";
import config from "../../lib/config/constants";
import { metadata as defaultMetadata } from "../layout";
import type { ReactElement } from "react";
import type { Metadata, Route } from "next";

View File

@ -4,7 +4,7 @@ import PageTitle from "../../components/PageTitle";
import Link from "../../components/Link";
import RelativeTime from "../../components/RelativeTime";
import commaNumber from "comma-number";
import config from "../../lib/config";
import config from "../../lib/config/constants";
import { metadata as defaultMetadata } from "../layout";
import { GoStar, GoRepoForked } from "react-icons/go";
import { SiGithub } from "react-icons/si";
@ -43,8 +43,8 @@ type Project = {
async function getRepos(): Promise<Project[] | null> {
// don't fail the entire site build if the required API key for this page is missing
if (!process.env.GH_PUBLIC_TOKEN || process.env.GH_PUBLIC_TOKEN === "") {
console.warn(`ERROR: I can't fetch any GitHub projects without "GH_PUBLIC_TOKEN" set! Skipping for now...`);
if (!process.env.GITHUB_TOKEN || process.env.GITHUB_TOKEN === "") {
console.warn(`ERROR: I can't fetch any GitHub projects without "GITHUB_TOKEN" set! Skipping for now...`);
return null;
}
@ -86,7 +86,7 @@ async function getRepos(): Promise<Project[] | null> {
limit: 12,
headers: {
accept: "application/vnd.github.v3+json",
authorization: `token ${process.env.GH_PUBLIC_TOKEN}`,
authorization: `token ${process.env.GITHUB_TOKEN}`,
},
}
);

View File

@ -1,4 +1,4 @@
import config from "../lib/config";
import config from "../lib/config/constants";
import type { MetadataRoute } from "next";
export const dynamic = "force-static";

View File

@ -1,7 +1,7 @@
import path from "path";
import glob from "fast-glob";
import { getAllPosts } from "../lib/helpers/posts";
import config from "../lib/config";
import config from "../lib/config/constants";
import type { MetadataRoute } from "next";
export const dynamic = "force-static";

View File

@ -1,7 +1,7 @@
"use client";
import Giscus from "@giscus/react";
import config from "../../lib/config";
import config from "../../lib/config/constants";
import type { GiscusProps } from "@giscus/react";
export type CommentsProps = {
@ -10,9 +10,9 @@ export type CommentsProps = {
const Comments = ({ title }: CommentsProps) => {
// fail silently if giscus isn't configured
if (!config.giscusConfig) {
if (!process.env.NEXT_PUBLIC_GISCUS_REPO_ID || !process.env.NEXT_PUBLIC_GISCUS_CATEGORY_ID) {
console.warn(
"[giscus] not configured, ensure giscusConfig.repoId and giscusConfig.categoryId are set in lib/config/index.js"
"[giscus] not configured, ensure 'NEXT_PUBLIC_GISCUS_REPO_ID' and 'NEXT_PUBLIC_GISCUS_CATEGORY_ID' environment variables are set."
);
return null;
@ -22,14 +22,15 @@ const Comments = ({ title }: CommentsProps) => {
return (
<Giscus
repo={config.githubRepo as GiscusProps["repo"]}
repoId={config.giscusConfig.repoId}
repoId={process.env.NEXT_PUBLIC_GISCUS_REPO_ID}
term={title}
category="Comments"
categoryId={config.giscusConfig.categoryId}
categoryId={process.env.NEXT_PUBLIC_GISCUS_CATEGORY_ID}
mapping="specific"
reactionsEnabled="1"
emitMetadata="0"
inputPosition="top"
theme="preferred_color_scheme"
loading="lazy"
/>
);

View File

@ -2,7 +2,7 @@ import clsx from "clsx";
import Link from "../Link";
import { GoHeartFill } from "react-icons/go";
import { SiNextdotjs } from "react-icons/si";
import config from "../../lib/config";
import config from "../../lib/config/constants";
import type { ComponentPropsWithoutRef } from "react";
import styles from "./Footer.module.css";

View File

@ -2,7 +2,7 @@ import clsx from "clsx";
import Link from "../Link";
import Image from "../Image";
import Menu from "../Menu";
import config from "../../lib/config";
import config from "../../lib/config/constants";
import type { ComponentPropsWithoutRef } from "react";
import styles from "./Header.module.css";

View File

@ -1,7 +1,7 @@
import NextLink from "next/link";
import clsx from "clsx";
import objStr from "obj-str";
import config from "../../lib/config";
import config from "../../lib/config/constants";
import type { ComponentPropsWithoutRef } from "react";
import styles from "./Link.module.css";

View File

@ -35,10 +35,11 @@ const Video = ({ src, poster, autoplay = false, responsive = true, className, ..
>
{src.map((file) => {
const extension = file.split(".").pop();
if (extension === "vtt") {
return <track key={file} kind="subtitles" src={file} srcLang="en" label="English" default />;
} else {
return <source key={file} src={file} type={`video/${file.split(".").pop()}`} />;
return <source key={file} src={file} type={`video/${extension}`} />;
}
})}
</video>

View File

@ -1,6 +1,4 @@
// @ts-check
const config = {
const constants = {
// Site info
siteName: "Jake Jarvis",
siteLocale: "en-US",
@ -20,11 +18,6 @@ const config = {
licenseUrl: "https://creativecommons.org/licenses/by/4.0/",
copyrightYearStart: 2001,
githubRepo: "jakejarvis/jarv.is",
giscusConfig: {
// https://github.com/giscus/giscus-component/tree/main/packages/react#readme
repoId: "MDEwOlJlcG9zaXRvcnk1MzM0MDgxMQ==",
categoryId: "DIC_kwDOAy3qi84CAsjS",
},
// Me info
authorName: "Jake Jarvis",
@ -42,4 +35,4 @@ const config = {
},
};
export default config;
export default constants;

View File

@ -1,10 +1,10 @@
import { Feed } from "feed";
import { getAllPosts } from "./posts";
import config from "../config";
import config from "../config/constants";
import meJpg from "../../app/me.jpg";
export const buildFeed = async (options: { type: "rss" | "atom" | "json" }): Promise<string> => {
export const buildFeed = async (): Promise<Feed> => {
// https://github.com/jpmonette/feed#example
const feed = new Feed({
id: config.baseUrl,
@ -43,15 +43,5 @@ export const buildFeed = async (options: { type: "rss" | "atom" | "json" }): Pro
});
});
if (options.type === "rss") {
return feed.rss2();
} else if (options.type === "atom") {
return feed.atom1();
} else if (options.type === "json") {
// rare but including as an option because why not...
// https://www.jsonfeed.org/
return feed.json1();
} else {
throw new TypeError(`Invalid feed type "${options.type}", must be "rss", "atom", or "json".`);
}
return feed;
};

View File

@ -5,7 +5,7 @@ import dayjsRelativeTime from "dayjs/plugin/relativeTime";
import dayjsLocalizedFormat from "dayjs/plugin/localizedFormat";
import dayjsAdvancedFormat from "dayjs/plugin/advancedFormat";
import "dayjs/locale/en";
import config from "../config";
import config from "../config/constants";
const IsomorphicDayJs = (date?: dayjs.ConfigType): dayjs.Dayjs => {
// plugins

View File

@ -3,7 +3,7 @@ import glob from "fast-glob";
import pMap from "p-map";
import pMemoize from "p-memoize";
import { formatDate } from "./format-date";
import config from "../config";
import config from "../config/constants";
// path to directory with .mdx files, relative to project root
const POSTS_DIR = "notes";

View File

@ -1,7 +1,7 @@
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import siteConfig from "./lib/config";
import siteConfig from "./lib/config/constants";
export function middleware(request: NextRequest) {
const response = NextResponse.next();

View File

@ -28,7 +28,7 @@ const nextConfig: NextConfig = {
},
eslint: {
// https://nextjs.org/docs/basic-features/eslint#linting-custom-directories-and-files
dirs: ["app", "components", "contexts", "hooks", "lib"],
dirs: ["app", "components", "contexts", "hooks", "lib", "notes"],
},
headers: async () => [
{

View File

@ -101,7 +101,7 @@
"engines": {
"node": ">=20.x"
},
"packageManager": "pnpm@10.5.2+sha512.da9dc28cd3ff40d0592188235ab25d3202add8a207afbedc682220e4a0029ffbff4562102b9e6e46b4e3f9e8bd53e6d05de48544b0c57d4b0179e22c76d1199b",
"packageManager": "pnpm@10.6.1+sha512.40ee09af407fa9fbb5fbfb8e1cb40fbb74c0af0c3e10e9224d7b53c7658528615b2c92450e74cfad91e3a2dcafe3ce4050d80bda71d757756d2ce2b66213e9a3",
"cacheDirectories": [
"node_modules",
".next/cache"