diff --git a/src/assets/styles/global.css b/src/assets/styles/global.css index b03009a..ab73d35 100644 --- a/src/assets/styles/global.css +++ b/src/assets/styles/global.css @@ -1437,6 +1437,84 @@ body { border-color: rgba(160, 160, 170, 0.32); } +/* ── GitHub issue/PR — Green ────────────────────────── */ + +.prose .ref-gh-issue { + color: #1a6d2e; + background-color: rgba(34, 140, 60, 0.08); + border-color: rgba(34, 140, 60, 0.2); +} + +.prose .ref-gh-issue:hover { + color: #145524; + background-color: rgba(34, 140, 60, 0.14); + border-color: rgba(34, 140, 60, 0.35); +} + +.dark .prose .ref-gh-issue { + color: #6ddb8a; + background-color: rgba(50, 180, 80, 0.12); + border-color: rgba(50, 180, 80, 0.25); +} + +.dark .prose .ref-gh-issue:hover { + color: #8de8a4; + background-color: rgba(50, 180, 80, 0.2); + border-color: rgba(50, 180, 80, 0.4); +} + +/* ── CVE — Red/rose ────────────────────────────────── */ + +.prose .ref-cve { + color: #9f1239; + background-color: rgba(190, 30, 70, 0.07); + border-color: rgba(190, 30, 70, 0.18); +} + +.prose .ref-cve:hover { + color: #7f0f2e; + background-color: rgba(190, 30, 70, 0.13); + border-color: rgba(190, 30, 70, 0.32); +} + +.dark .prose .ref-cve { + color: #f0758e; + background-color: rgba(220, 60, 90, 0.12); + border-color: rgba(220, 60, 90, 0.25); +} + +.dark .prose .ref-cve:hover { + color: #f5a0b2; + background-color: rgba(220, 60, 90, 0.2); + border-color: rgba(220, 60, 90, 0.4); +} + +/* ── Python release — Python blue (distinct) ────────── */ + +.prose .ref-py-release { + color: #1a5276; + background-color: rgba(30, 100, 160, 0.08); + border-color: rgba(30, 100, 160, 0.2); +} + +.prose .ref-py-release:hover { + color: #144060; + background-color: rgba(30, 100, 160, 0.14); + border-color: rgba(30, 100, 160, 0.35); +} + +.dark .prose .ref-py-release { + color: #68b5e8; + background-color: rgba(50, 130, 200, 0.12); + border-color: rgba(50, 130, 200, 0.25); +} + +.dark .prose .ref-py-release:hover { + color: #90caf0; + background-color: rgba(50, 130, 200, 0.2); + border-color: rgba(50, 130, 200, 0.4); +} + /* ── Post footer references panel ── */ .post-refs-header { diff --git a/src/components/references/_icons.ts b/src/components/references/_icons.ts index d78fedb..c8c5a20 100644 --- a/src/components/references/_icons.ts +++ b/src/components/references/_icons.ts @@ -33,6 +33,27 @@ export const docsIcon = lucide( '', ); +/** Lucide `circle-dot` — GitHub issue/PR */ +export const issueIcon = lucide( + 14, + 14, + '', +); + +/** Lucide `shield-alert` — CVE / security vulnerability */ +export const shieldIcon = lucide( + 14, + 14, + '', +); + +/** Lucide `download` — Python release download */ +export const downloadIcon = lucide( + 14, + 14, + '', +); + /** Lucide `arrow-up-right` — external link indicator */ export const externalIcon = lucide( 11, diff --git a/src/plugins/remark-python-refs.ts b/src/plugins/remark-python-refs.ts index d3fecd0..094dbdb 100644 --- a/src/plugins/remark-python-refs.ts +++ b/src/plugins/remark-python-refs.ts @@ -8,8 +8,11 @@ * - PEP links (by URL pattern OR link text matching "PEP NNN") * - CPython docs (docs.python.org/...) * - PyPI links (pypi.org/project/NAME/) + * - GitHub issues/PRs (github.com/OWNER/REPO/issues|pull/NNN) * - GitHub repos (github.com/OWNER/REPO — exactly 2 path segments) * - GitHub users/orgs (github.com/NAME — exactly 1 segment, not reserved) + * - CVE references (nvd.nist.gov/vuln/detail/CVE-YYYY-NNNNN) + * - Python releases (python.org/downloads/release/python-XXXX/) */ import type { Root, Link, Paragraph, PhrasingContent } from "mdast"; import { visit } from "unist-util-visit"; @@ -18,6 +21,9 @@ import { docsIcon, githubIcon, packageIcon, + issueIcon, + shieldIcon, + downloadIcon, externalIcon, } from "../components/references/_icons.js"; @@ -27,6 +33,9 @@ const PEP_OLD = /^https?:\/\/(?:www\.)?python\.org\/dev\/peps\/pep-(\d+)\/?/i; const PEP_NEW = /^https?:\/\/peps\.python\.org\/pep-(\d+)\/?/i; const DOCS = /^https?:\/\/docs\.python\.org\//i; const PYPI = /^https?:\/\/pypi\.org\/project\/([^/]+)\/?/i; +const GH_ISSUE = /^https?:\/\/github\.com\/([\w.-]+)\/([\w.-]+)\/(issues|pull)\/(\d+)\/?/i; +const CVE = /^https?:\/\/nvd\.nist\.gov\/vuln\/detail\/(CVE-[\d-]+)\/?/i; +const PY_RELEASE = /^https?:\/\/(?:www\.)?python\.org\/downloads\/release\/(python-[\w.]+)\/?/i; const GITHUB = /^https?:\/\/github\.com\/([\w.-]+)(?:\/([\w.-]+))?\/?$/i; @@ -64,7 +73,7 @@ const GH_RESERVED = new Set([ // ── Helpers ───────────────────────────────────────────── -type RefType = "pep" | "docs" | "pypi" | "gh-repo" | "gh-user"; +type RefType = "pep" | "docs" | "pypi" | "gh-repo" | "gh-user" | "gh-issue" | "cve" | "py-release"; interface Match { type: RefType; @@ -108,6 +117,18 @@ function classify(url: string, linkText?: string): Omit return { type: "pypi", icon: packageIcon }; } + if ((m = url.match(GH_ISSUE))) { + return { type: "gh-issue", icon: issueIcon }; + } + + if ((m = url.match(CVE))) { + return { type: "cve", icon: shieldIcon }; + } + + if ((m = url.match(PY_RELEASE))) { + return { type: "py-release", icon: downloadIcon }; + } + if ((m = url.match(GITHUB))) { const [, owner, repo] = m; if (repo) {