mirror of
https://github.com/Hackdex-App/hackdex-website.git
synced 2026-03-21 17:54:09 -05:00
Add anchor link support to markdown headers
This commit is contained in:
parent
bbd2f56797
commit
27f920b905
50
package-lock.json
generated
50
package-lock.json
generated
|
|
@ -22,6 +22,7 @@
|
|||
"react-dom": "19.1.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-markdown": "9.0.3",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"remark-gfm": "4.0.0",
|
||||
"rom-patcher-js": "github:marcrobledo/RomPatcher.js#91e522e247f709e894761157ccba3189004d0859"
|
||||
},
|
||||
|
|
@ -2115,6 +2116,12 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/github-slugger": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz",
|
||||
"integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
|
|
@ -2194,6 +2201,19 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-heading-rank": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-3.0.0.tgz",
|
||||
"integrity": "sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-to-jsx-runtime": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
|
||||
|
|
@ -2221,6 +2241,19 @@
|
|||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-to-string": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz",
|
||||
"integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-whitespace": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
|
||||
|
|
@ -4092,6 +4125,23 @@
|
|||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/rehype-slug": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-6.0.0.tgz",
|
||||
"integrity": "sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0",
|
||||
"github-slugger": "^2.0.0",
|
||||
"hast-util-heading-rank": "^3.0.0",
|
||||
"hast-util-to-string": "^3.0.0",
|
||||
"unist-util-visit": "^5.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/remark-gfm": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.0.tgz",
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
"react-dom": "19.1.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-markdown": "9.0.3",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"remark-gfm": "4.0.0",
|
||||
"rom-patcher-js": "github:marcrobledo/RomPatcher.js#91e522e247f709e894761157ccba3189004d0859"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -75,6 +75,22 @@ body {
|
|||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
/* Smooth scrolling and anchor offset for sticky header */
|
||||
html { scroll-behavior: smooth; }
|
||||
:root { --anchor-offset: 38px; }
|
||||
@media (min-width: 768px) { :root { --anchor-offset: 72px; } }
|
||||
/* Modern browsers honor scroll-padding-top on the scroll container */
|
||||
html { scroll-padding-top: var(--anchor-offset); }
|
||||
/* Fallback for elements targeted directly (especially headings) */
|
||||
.prose h1[id],
|
||||
.prose h2[id],
|
||||
.prose h3[id],
|
||||
.prose h4[id],
|
||||
.prose h5[id],
|
||||
.prose h6[id] {
|
||||
scroll-margin-top: var(--anchor-offset);
|
||||
}
|
||||
|
||||
.bg-grid { background-image: radial-gradient(var(--grid-dot-color) 1px, transparent 1px); background-size: 22px 22px; }
|
||||
|
||||
.card {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import Gallery from "@/components/Hack/Gallery";
|
|||
import HackActions from "@/components/Hack/HackActions";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import rehypeSlug from "rehype-slug";
|
||||
import Image from "next/image";
|
||||
import { FaDiscord, FaTwitter } from "react-icons/fa6";
|
||||
import PokeCommunityIcon from "@/components/Icons/PokeCommunityIcon";
|
||||
|
|
@ -127,7 +128,7 @@ export default async function HackDetail({ params }: HackDetailProps) {
|
|||
<div className="card p-5">
|
||||
<h2 className="text-xl font-semibold tracking-tight">About this hack</h2>
|
||||
<div className="prose prose-sm mt-3 max-w-none text-foreground/80">
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>{hack.description}</ReactMarkdown>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeSlug]}>{hack.description}</ReactMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import React from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import rehypeSlug from "rehype-slug";
|
||||
import TagSelector from "@/components/Submit/TagSelector";
|
||||
import { baseRoms } from "@/data/baseRoms";
|
||||
import Image from "next/image";
|
||||
|
|
@ -301,7 +302,7 @@ export default function HackEditForm({ slug, initial }: HackEditFormProps) {
|
|||
/>
|
||||
) : (
|
||||
<div className={`prose max-w-none rounded-md min-h-[14rem] px-3 py-2 ring-1 ring-inset ${descriptionChanged ? 'ring-[var(--ring)] bg-[var(--surface-2)]' : 'bg-[var(--surface-2)] ring-[var(--border)]'} ${description ? "" : "text-foreground/60 text-sm"}`}>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>{description || "Nothing to preview yet."}</ReactMarkdown>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeSlug]}>{description || "Nothing to preview yet."}</ReactMarkdown>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { SortableContext, arrayMove, useSortable, verticalListSortingStrategy }
|
|||
import { CSS } from "@dnd-kit/utilities";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import rehypeSlug from "rehype-slug";
|
||||
import { RxDragHandleDots2 } from "react-icons/rx";
|
||||
import { useAuthContext } from "@/contexts/AuthContext";
|
||||
import { useBaseRoms } from "@/contexts/BaseRomContext";
|
||||
|
|
@ -723,7 +724,7 @@ export default function HackSubmitForm({ dummy = false }: HackSubmitFormProps) {
|
|||
</div>
|
||||
{isDummy ? (
|
||||
<div className="prose max-w-none h-36 rounded-md bg-[var(--surface-2)] px-3 py-2 ring-1 ring-inset ring-[var(--border)] text-foreground/60 select-none">
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>{description || "Write a longer markdown description here."}</ReactMarkdown>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeSlug]}>{description || "Write a longer markdown description here."}</ReactMarkdown>
|
||||
</div>
|
||||
) : !showMdPreview ? (
|
||||
<textarea
|
||||
|
|
@ -735,7 +736,7 @@ export default function HackSubmitForm({ dummy = false }: HackSubmitFormProps) {
|
|||
/>
|
||||
) : (
|
||||
<div className={`prose max-w-none rounded-md bg-[var(--surface-2)] min-h-[14rem] px-3 py-2 ring-1 ring-inset ring-[var(--border)] ${description ? "" : "text-foreground/60 text-sm"}`}>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>{description || "Nothing to preview yet."}</ReactMarkdown>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeSlug]}>{description || "Nothing to preview yet."}</ReactMarkdown>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
6
src/types/markdown.d.ts
vendored
6
src/types/markdown.d.ts
vendored
|
|
@ -3,6 +3,7 @@ declare module "react-markdown" {
|
|||
export interface ReactMarkdownProps {
|
||||
children?: React.ReactNode;
|
||||
remarkPlugins?: any[];
|
||||
rehypePlugins?: any[];
|
||||
className?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
|
@ -15,4 +16,9 @@ declare module "remark-gfm" {
|
|||
export default remarkGfm;
|
||||
}
|
||||
|
||||
declare module "rehype-slug" {
|
||||
const rehypeSlug: any;
|
||||
export default rehypeSlug;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user