Page.tsx
1 import { Link, Meta, MetaProvider, Title } from "@solidjs/meta"; 2 import Disclosure from '@corvu/disclosure' 3 import { Show, type ParentComponent, children, Suspense, For, Switch, createSignal, createEffect, createMemo } from "solid-js"; 4 import cfg from "../constant"; 5 import { A, createAsync, useLocation } from "@solidjs/router"; 6 import { TableOfContents } from "./Toc"; 7 import { docsData } from "solid:collection"; 8 import Spinner from "./Spinner"; 9 import { useTaxoTypeState } from "./PageState"; 10 import { Bi } from "./Taxo"; 11 import { twMerge } from "tailwind-merge"; 12 13 function formatDate(date: Date | undefined) { 14 if (date === undefined) { 15 return ""; 16 } 17 const year = date.getFullYear(); 18 19 const month = (date.getMonth() + 1).toString().padStart(2, "0"); 20 21 const day = date.getDate().toString().padStart(2, "0"); 22 23 return `${year}-${month}-${day}`; 24 } 25 const Page: ParentComponent<{ isError?: false }> = (props) => { 26 const resolved = children(() => props.children); 27 const location = useLocation(); 28 const [clientWidthGt900, setClientWidthGt900] = createSignal<boolean>(); 29 30 const [exptoc, setExptoc] = createSignal(false); 31 32 const handleWindowResize = () => { 33 setClientWidthGt900(window.innerWidth > 900); 34 } 35 36 createEffect(() => { 37 // component is mounted and window is available 38 handleWindowResize(); 39 40 window.addEventListener('resize', handleWindowResize); 41 42 // unsubscribe from the event on component unmount 43 return () => window.removeEventListener('resize', handleWindowResize); 44 }); 45 46 const ctx = createAsync( 47 async () => { 48 return docsData; 49 }, 50 { deferStream: true }, 51 ); 52 53 const currentUrl = `${cfg.base_url}${location.pathname}`; 54 const { setTaxoType } = useTaxoTypeState(); 55 56 const [toContainer, setToContainer] = createSignal<HTMLElement | undefined>(); 57 58 function handleSectionChange(evt: CustomEvent<string>) { 59 const id = "toc-" + evt.detail; 60 61 // console.log("cng to ", id) 62 const containerEl = toContainer(); 63 if (id && containerEl) { 64 const target = document.getElementById(id); 65 if (target) { 66 const scrollTarget = target.offsetTop; 67 68 containerEl.scrollTo({ 69 top: scrollTarget, 70 behavior: 'instant' 71 }); 72 } 73 } 74 } 75 76 77 return ( 78 <Suspense 79 fallback={ 80 <Spinner /> 81 } 82 > 83 <Show when={ctx()}> 84 {(articles) => { 85 const article = articles().find( 86 (i) => i.path == location.pathname.replaceAll("/", ""), 87 ); 88 89 if (article) { 90 const date = new Date(article.date); 91 return ( 92 <> 93 <MetaProvider> 94 <Title>{`${article?.title} - ${cfg.title}`}</Title> 95 <Link rel="canonical" href={currentUrl} /> 96 <Meta property="og:url" content={currentUrl} /> 97 <Meta 98 name="description" 99 content={article?.description || cfg.description} 100 /> 101 <Meta property="og:title" content={cfg.title} /> 102 <Meta property="og:description" content={cfg.description} /> 103 <Meta name="keywords" content={article?.tags?.join(",")} /> 104 <Meta 105 property="article:published_time" 106 content={formatDate(date)} 107 /> 108 </MetaProvider> 109 <div class="antialiased prose font-sans md:max-w-2/3 2xl:prose-lg dark:prose-invert mx-auto w-full mt-10 break-words"> 110 <div class="font-sans font-medium text-[22px] my-4 whitespace-nowrap"> 111 {article?.title} 112 <Show when={article?.draft}> 113 {" [draft]"} 114 </Show> 115 <span class="sr-only">title</span> 116 </div> 117 <div class="text-zinc-500 font-mono mb-2 font-light text-sm 2xl:text-lg"> 118 {formatDate(date)} 119 <span class="sr-only">date</span> 120 </div> 121 122 <div class="flex w-auto mb-10 justify-end items-end font-sans"> 123 <div class="text-pretty text-slate-500 text-start text-sm 2xl:text-lg font-mono leading-loose"> 124 {article?.description} 125 </div> 126 <span class="sr-only">description</span> 127 </div> 128 <Show when={article?.draft}> 129 <div class="flex w-auto justify-start items-end font-sans"> 130 <div class="text-pretty text-slate-500 text-start text-sm 2xl:text-lg font-mono leading-loose"> 131 恭喜你发现我的🌿稿一篇呀 🎉~ 132 </div> 133 <span class="sr-only">is draft</span> 134 </div> 135 </Show> 136 </div> 137 138 <Show when={article?.toc && clientWidthGt900()} fallback={ 139 <article class="antialiased prose font-sans md:max-w-2/3 2xl:prose-lg mx-auto w-full break-words"> 140 <Show when={article?.toc}> 141 <TableOfContents 142 children={resolved()} /> 143 </Show> 144 145 <div class="prose font-sans lg:prose-lg 2xl:prose-xl mb-16 mx-auto max-w-[80ch]"> 146 {resolved()} 147 </div> 148 </article> 149 }> 150 <article class="relative justify-between flex grow mx-16 mx-2 xl:mx-1/6"> 151 <div 152 ref={setToContainer} 153 onMouseEnter={() => setExptoc(true)} 154 class={twMerge("fixed backdrop-blur-4 left-4 px-2 py-1 hover:w-auto hover:min-w-1/8 rounded-lg bottom-10 overflow-y-auto bg-sprout-100/30 z-10 scrollbar-none")}> 155 {/* 156 TODO: waitting for resolve 157 */} 158 <Disclosure collapseBehavior="hide" expanded={exptoc()}> 159 {(_props) => ( 160 <> 161 <Show when={!exptoc()}> 162 <div class="w-[10vw] h-[10vh]"> 163 <TableOfContents 164 onCurrentSectionChanged={handleSectionChange} 165 children={resolved()} /> 166 </div> 167 </Show> 168 <Disclosure.Content 169 class="overflow-visible data-[expanded]:animate-expand min-w-[10vw] min-h-[10vh]" 170 onmouseleave={() => setExptoc(false)}> 171 <TableOfContents 172 onCurrentSectionChanged={handleSectionChange} 173 children={resolved()} /> 174 </Disclosure.Content> 175 </> 176 )} 177 </Disclosure> 178 </div> 179 <div class="antialiased prose lg:prose-lg 2xl:prose-xl 2xl:max-w-[80ch] font-sans mx-auto break-words"> 180 {resolved()} 181 </div> 182 </article> 183 </Show > 184 185 <div class="antialiased prose font-sans md:max-w-2/3 2xl:prose-lg dark:prose-invert mb-16 w-full mt-8 break-words mx-auto"> 186 <div class="flex w-auto mb-10 justify-end items-end font-sans mt-18"> 187 <div class="flex gap-1 text-pretty text-start text-sm 2xl:text-lg font-sans overflow-x-scroll scrollbar-none"> 188 <For each={article?.tags}> 189 {(i) => <div class="flex items-center"> 190 <A 191 class="text-sm 2xl:text-base no-underline text-slate-600 font-sans dark:text-chill-100 justify-self-end text-nowrap whitespace-nowrap group leading-snug" 192 href="/taxonomy" 193 onClick={() => { 194 setTaxoType({ type: Bi.tag, extra: i }); 195 }} 196 > 197 {i} 198 <span class="block max-w-0 group-hover:max-w-full transition-all duration-350 h-px bg-sprout-500" /> 199 </A> 200 <div class="w-1 h-4 mx-1 rounded-sm bg-sprout-300" /> 201 </div> 202 } 203 </For> 204 <span class="sr-only">keywords</span> 205 </div> 206 </div> 207 </div> 208 <div class="h-[30vh]" /> 209 </> 210 ); 211 } 212 // No frontmatter here 213 return resolved(); 214 }} 215 </Show> 216 </Suspense > 217 ); 218 }; 219 export default Page;