1+ import hljs from "highlight.js" ;
2+ import 'highlight.js/styles/github-dark.css' ;
3+ import './custom-highlight.scss'
4+ import { useEffect , useState , type JSX } from "react" ;
5+ import { Header } from "~/components/Header/Header" ;
6+ import styles from "./$id.module.scss"
7+ import { useParams } from "react-router" ;
8+ import { useIsMobile } from "~/hooks/useIsMobile" ;
9+ import { custom_markdown_convert } from "~/utils/convert" ;
10+
11+ export default function Blog ( ) : JSX . Element {
12+ const { id } = useParams ( ) ;
13+ const [ thumbnail ] = useState < string | null > ( null ) ;
14+ const [ markdownSource , setMarkdownSource ] = useState < string > ( "" ) ;
15+ const [ table , setTable ] = useState < JSX . Element [ ] > ( [ ] ) ;
16+ const isMobile = useIsMobile ( ) ;
17+
18+ const handleAnchorClick = ( id : string ) => ( e : React . MouseEvent ) => {
19+ e . preventDefault ( ) ;
20+ const target = document . getElementById ( id ) ;
21+ if ( target ) {
22+ window . scrollTo ( {
23+ top : target . getBoundingClientRect ( ) . top + window . scrollY - 80 ,
24+ behavior : 'smooth' ,
25+ } ) ;
26+ }
27+ } ;
28+
29+ useEffect ( ( ) => {
30+ const script = document . createElement ( "script" ) ;
31+ script . src = "https://cdn.jsdelivr.net/gh/rsms/[email protected] /dist/markdown.js" ; 32+ script . async = true ;
33+
34+ document . body . appendChild ( script ) ;
35+
36+ script . onload = async ( ) => {
37+ await markdown . ready ;
38+ await fetch ( `https://raw.githubusercontent.com/object-t/object-t-blog/refs/heads/main/articles/${ id } .md` )
39+ . then ( ( res ) => res . text ( ) )
40+ . then ( ( md ) => {
41+ const html = markdown . parse ( custom_markdown_convert ( md ) ) ;
42+ const container = document . createElement ( "div" ) ;
43+ container . innerHTML = html ;
44+
45+ const headings = container . querySelectorAll ( "h1, h2" ) ;
46+ headings . forEach ( ( heading , i ) => {
47+ const text = heading . textContent ?? `heading-${ i } ` ;
48+ const id = text . replace ( / \s + / g, "-" ) . toLowerCase ( ) ;
49+ heading . id = id ;
50+ } ) ;
51+ setTable (
52+ Array . from ( headings ) . map ( ( d , i ) => {
53+ const id = ( d . textContent ?? `heading-${ i } ` ) . replace ( / \s + / g, "-" ) . toLowerCase ( ) ;
54+ return (
55+ < a
56+ key = { i }
57+ className = { styles [ `${ d . tagName . toLowerCase ( ) } -table` ] }
58+ href = { "#" + id }
59+ onClick = { handleAnchorClick ( id ) }
60+ >
61+ < li > { d . textContent } </ li >
62+ </ a >
63+ )
64+ }
65+ )
66+ ) ;
67+
68+ container . querySelectorAll ( "pre code" ) . forEach ( ( code ) => {
69+ hljs . highlightElement ( code as HTMLElement ) ;
70+ } ) ;
71+
72+ setMarkdownSource ( container . innerHTML ) ;
73+ } )
74+ . catch ( ( err ) => console . error ( "読み込みエラー:" , err ) ) ;
75+ } ;
76+ } , [ id ] ) ;
77+
78+ return (
79+ < div className = { styles . container } >
80+ < Header />
81+ {
82+ ! isMobile &&
83+ < div className = { styles . side } >
84+ < ul >
85+ {
86+ table
87+ }
88+ </ ul >
89+ </ div >
90+ }
91+ < div className = { styles [ "article-container" ] } >
92+ < img src = { thumbnail ? thumbnail : "/assets/images/headers/blog.webp" } className = { styles [ "header-img" ] } />
93+ < div
94+ className = { [ styles . article , isMobile && styles . mobile ] . join ( " " ) }
95+ dangerouslySetInnerHTML = {
96+ {
97+ __html : markdownSource
98+ }
99+ }
100+ />
101+ </ div >
102+ </ div >
103+ )
104+ }
0 commit comments