Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/components/markdown-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,20 @@ type MarkdownContentProps = Readonly<{
html: SanitizedHtml
}>

const DEFAULT_KATEX_CSS_PATH = "/katex/katex.min.css"

/**
* GitHub Pages 서브경로(e.g. /repo/) 배포 환경을 지원하기 위해
* 환경변수 NEXT_PUBLIC_BASE_PATH 가 지정된 경우 KaTeX 스타일시트 경로 앞에 접두사로 조합합니다.
*/
export function getKatexStylesheetUrl(): string {
// biome-ignore lint/complexity/useLiteralKeys: tsconfig noPropertyAccessFromIndexSignature requires bracket access
const basePath = process.env["NEXT_PUBLIC_BASE_PATH"]?.trim() ?? ""
const normalizedBasePath = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath

return `${normalizedBasePath}${DEFAULT_KATEX_CSS_PATH}`
}

const markdownContentClassName = cn(
"prose prose-neutral max-w-none text-foreground dark:prose-invert",
"prose-a:text-foreground prose-a:decoration-border prose-a:underline-offset-4",
Expand All @@ -30,7 +44,9 @@ export function MarkdownContent({ contentKey, hasDiagram, hasMath, html }: Markd

return (
<>
{hasMath ? <link href="/katex/katex.min.css" precedence="default" rel="stylesheet" /> : null}
{hasMath ? (
<link href={getKatexStylesheetUrl()} precedence="default" rel="stylesheet" />
) : null}
<div
id={markdownContentId}
className={markdownContentClassName}
Expand Down
27 changes: 26 additions & 1 deletion tests/katex-assets.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import { existsSync, readFileSync } from "node:fs"
import { join } from "node:path"
import { describe, expect, it } from "vitest"
import { afterEach, beforeEach, describe, expect, it } from "vitest"
import { getKatexStylesheetUrl } from "../src/components/markdown-content"

const katexDirectory = join(process.cwd(), "public", "katex")
const stylesheetPath = join(katexDirectory, "katex.min.css")
const licensePath = join(katexDirectory, "LICENSE")

describe("local KaTeX assets", () => {
const originalEnv = process.env

beforeEach(() => {
process.env = { ...originalEnv }
})

afterEach(() => {
process.env = originalEnv
})

it("Given repository-owned KaTeX assets When checking the public directory Then includes CSS and its license", () => {
expect(existsSync(stylesheetPath)).toBe(true)
expect(existsSync(licensePath)).toBe(true)
Expand Down Expand Up @@ -34,4 +45,18 @@ describe("local KaTeX assets", () => {
expect(packageJson).not.toMatch(/^\s*"katex":/m)
expect(packageJson).not.toContain("copy-katex-assets")
})

describe("getKatexStylesheetUrl", () => {
it("Given no NEXT_PUBLIC_BASE_PATH When resolving Then returns root stylesheet path", () => {
// biome-ignore lint/complexity/useLiteralKeys: tsconfig noPropertyAccessFromIndexSignature requires bracket access
delete process.env["NEXT_PUBLIC_BASE_PATH"]
expect(getKatexStylesheetUrl()).toBe("/katex/katex.min.css")
})

it("Given NEXT_PUBLIC_BASE_PATH When resolving Then prepends base path with normalized slashes", () => {
// biome-ignore lint/complexity/useLiteralKeys: tsconfig noPropertyAccessFromIndexSignature requires bracket access
process.env["NEXT_PUBLIC_BASE_PATH"] = "/my-blog/"
expect(getKatexStylesheetUrl()).toBe("/my-blog/katex/katex.min.css")
})
})
})