/ src / components / Page.tsx
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;