File manager - Edit - /var/www/payraty/helpdesk/resources/client/help-center/articles/article-page/article-page.tsx
Back
import { ArticlePageNavItem, GetArticleResponse, useArticle, } from '@app/help-center/articles/use-article'; import React, {Fragment, useEffect, useRef, useState} from 'react'; import {PageMetaTags} from '@common/http/page-meta-tags'; import {PageStatus} from '@common/http/page-status'; import {Trans} from '@common/i18n/trans'; import {HcSidenav} from '@app/help-center/articles/hc-sidenav'; import {UseQueryResult} from '@tanstack/react-query'; import {Link, useLocation} from 'react-router-dom'; import clsx from 'clsx'; import {ArticleAttachments} from '@app/help-center/articles/article-attachments'; import {ArticlePageLayout} from '@app/help-center/articles/article-page/article-page-layout'; import {Skeleton} from '@common/ui/skeleton/skeleton'; import {AnimatePresence, m} from 'framer-motion'; import {opacityAnimation} from '@common/ui/animation/opacity-animation'; import {LinkStyle} from '@common/ui/buttons/external-link'; import {useSettings} from '@common/core/settings/use-settings'; import {ArticlePageBreadcrumb} from '@app/help-center/articles/article-page/article-page-breadcrumb'; import {ArticlePageFeedback} from '@app/help-center/articles/article-page/article-page-feedback'; import {highlightAllCode} from '@common/text-editor/highlight/highlight-code'; import {useAuth} from '@common/auth/use-auth'; import {IconButton} from '@common/ui/buttons/icon-button'; import {EditIcon} from '@common/icons/material/Edit'; import {getEditArticleLink} from '@app/help-center/articles/article-link'; export function ArticlePage() { const query = useArticle('articlePage'); const {hash} = useLocation(); useEffect(() => { if (hash && query.data?.article) { setTimeout(() => { if (document.documentElement.scrollTop === 0) { const el = document.getElementById(hash.slice(1)); if (el) { el.scrollIntoView({behavior: 'smooth'}); } } }); } }, [query.data, hash]); return ( <ArticlePageLayout leftSidenav={<HcSidenav sections={query.data?.categoryNav} />} rightSidenav={<RightSidenav nav={query.data?.pageNav} />} categoryId={query.data?.article.path?.[0].id} > <AnimatePresence initial={false} mode="sync"> <PageContent query={query} /> </AnimatePresence> </ArticlePageLayout> ); } interface RightSidenavProps { nav?: ArticlePageNavItem[]; } function RightSidenav({nav}: RightSidenavProps) { const [activeItem, setActiveItem] = useState<string | null>(null); useEffect(() => { if (!nav) return; const observer = new IntersectionObserver( entries => { for (const entry of entries) { const el = document.getElementById(`to-${entry.target.id}`); if (el && entry.isIntersecting) { setActiveItem(el.id.replace(/^to-/, '')); } } }, { rootMargin: '0px 0px -50% 0px', }, ); const headings = nav .map(item => document.getElementById(item.slug)) .filter(Boolean) as HTMLElement[]; headings.forEach(heading => { observer.observe(heading); }); return () => { headings.forEach(heading => observer.unobserve(heading)); }; }, [nav]); return ( <div className="dashboard-grid-sidenav-right compact-scrollbar hidden w-224 xl:flex xl:w-288"> <nav className="sticky top-64 h-[calc(100dvh-64px)] w-full py-64 pr-32 xl:pr-64"> <h2 className="font-display text-sm font-medium"> <Trans message="On this page" /> </h2> <ol role="list" className="mt-16 space-y-12 text-sm text-muted"> {nav?.map(item => { const isActive = item.slug === activeItem; return ( <li key={item.slug} className={item.indent ? 'pl-20' : 'mt-8'}> <Link className={clsx( 'cursor-pointer', isActive ? 'text-primary' : 'hover:text-main', )} to={`#${item.slug}`} id={`to-${item.slug}`} onClick={e => { const el = document.getElementById(item.slug); if (el) { setActiveItem(item.slug); el.scrollIntoView({behavior: 'smooth'}); } }} > {item.display_name} </Link> </li> ); })} </ol> </nav> </div> ); } interface PageContentProps { query: UseQueryResult<GetArticleResponse>; } function PageContent({query}: PageContentProps) { const settings = useSettings(); const {hasPermission, hasRole} = useAuth(); if (query.data && !query.isPlaceholderData) { const canEdit = hasPermission('articles.update') || (query.data.article.managed_by_role && hasRole(query.data.article.managed_by_role)); return ( <Fragment> <PageMetaTags query={query} /> <m.article key="article" {...opacityAnimation}> <header className="mb-36"> <ArticlePageBreadcrumb data={query.data} className="-ml-6" /> <div className="mt-4 flex items-center gap-4"> <h1 className="font-display text-slate-900 text-3xl tracking-tight"> {query.data.article.title} </h1> {canEdit && ( <IconButton className="text-muted" elementType={Link} to={getEditArticleLink(query.data.article)} > <EditIcon /> </IconButton> )} </div> </header> <ArticleBody body={query.data.article.body} /> <ArticleAttachments article={query.data.article} /> </m.article> {!settings.article?.hide_new_ticket_link && ( <div className="my-50 border-y py-50"> <Trans message="Have more questions? <a>Submit a request</a>" values={{ a: label => ( <Link className={LinkStyle} to="/hc/tickets/new"> {label} </Link> ), }} /> </div> )} <ArticlePageFeedback /> </Fragment> ); } return ( <PageStatus query={query} show404={false} delayedSpinner={false} loader={ <m.div key="skeletons" {...opacityAnimation}> <Skeleton variant="rect" className="h-20 max-w-580" /> <Skeleton variant="rect" className="mb-30 mt-10 h-34 max-w-440" /> <Skeleton className="max-w-[95%]" /> <Skeleton /> <Skeleton className="mb-30 max-w-[70%]" /> <Skeleton className="max-w-[90%]" /> <Skeleton className="max-w-[80%]" /> <Skeleton className="max-w-[30%]" /> </m.div> } /> ); } interface ArticleBodyProps { body: string; } function ArticleBody({body}: ArticleBodyProps) { const bodyRef = useRef<HTMLDivElement>(null); useEffect(() => { if (bodyRef.current) { highlightAllCode(bodyRef.current); } }, []); return ( <div className="prose-headings:font-display prose-pre:bg-slate-900 prose max-w-none dark:prose-invert prose-headings:scroll-mt-112 prose-headings:font-normal prose-a:font-normal prose-a:text-primary prose-pre:rounded-xl prose-pre:shadow-lg dark:prose-pre:shadow-none dark:prose-pre:ring-1 dark:prose-pre:ring-divider lg:prose-headings:scroll-mt-136"> <div ref={bodyRef} className="article-body whitespace-pre-wrap break-words" dangerouslySetInnerHTML={{__html: body}} /> </div> ); }
| ver. 1.4 |
Github
|
.
| PHP 8.3.30 | Generation time: 1.65 |
proxy
|
phpinfo
|
Settings