PluginProbe
Extendify / 2.2.0
Extendify v2.2.0
3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 0.7.0 All 126 releases
extendify / src / HelpCenter / components / knowledge-base / Article.jsx

Article.jsx in Extendify 2.2.0, at src/HelpCenter/components/knowledge-base/Article.jsx

168 lines 5.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import apiFetch from '@wordpress/api-fetch';
2 import { Spinner } from '@wordpress/components';
3 import { useState, useEffect, useRef } from '@wordpress/element';
4 import { __ } from '@wordpress/i18n';
5 import parse from 'html-react-parser';
6 import { useSupportArticle } from '@help-center/hooks/useSupportArticles';
7 import { useKnowledgeBaseStore } from '@help-center/state/knowledge-base';
8
9 export const Article = () => {
10 const { articles, pushArticle, popArticle, updateTitle } =
11 useKnowledgeBaseStore();
12 const [openedErrorUrl, setOpenedErrorUrl] = useState();
13 const [fetching, setFetching] = useState(false);
14 const articleRef = useRef();
15 const slug = articles?.[0]?.slug;
16 const { data: article, error, loading } = useSupportArticle(slug);
17 const title = article?.title;
18
19 const getArticleRedirect = (url) =>
20 apiFetch({
21 path: `/extendify/v1/help-center/get-redirect?path=${url}`,
22 });
23
24 useEffect(() => {
25 if (!error) return setOpenedErrorUrl(false);
26 // If there's an error, then open the url once
27 if (openedErrorUrl) return;
28 setOpenedErrorUrl(true);
29 popArticle();
30 window.open(
31 `https://wordpress.org/documentation/article/${slug}`,
32 '_blank',
33 );
34 }, [error, slug, openedErrorUrl, popArticle]);
35
36 useEffect(() => {
37 if (!slug || !title) return;
38 updateTitle(slug, title);
39 }, [title, updateTitle, slug]);
40
41 useEffect(() => {
42 if (!articleRef.current) return;
43 const links = articleRef.current?.querySelectorAll('a');
44
45 // a small fix to make sure the images fit within the modal
46 const figures = articleRef.current?.querySelectorAll('figure');
47 const images = articleRef.current?.querySelectorAll('img');
48
49 figures.forEach((figure) => {
50 figure.classList.add('mx-auto');
51 figure.classList.add('my-4');
52 figure.classList.add('block');
53 figure.classList.add('w-full');
54 figure.classList.remove('wp-block-image');
55 });
56
57 images.forEach((image) => {
58 image.classList.add('object-contain');
59 image.classList.add('max-w-[400px]');
60 image.classList.add('max-h-[250px]');
61 });
62
63 const handleInternal = async (e) => {
64 e.preventDefault();
65
66 // In that case, event.ctrlKey does the trick.
67 if (e.ctrlKey || e.metaKey) {
68 e.stopPropagation();
69 return window.open(e.target.href, '_blank');
70 }
71
72 // Could be the parent element so check both
73 const link = e.target?.href ?? e.target?.closest('a')?.href;
74 const { pathname } = new URL(link);
75 const slug = pathname.split('/').filter(Boolean)?.at(-1);
76
77 // Both the new docs site and the old may have redirects
78 setFetching(true);
79 const data = await getArticleRedirect(pathname);
80 setFetching(false);
81 if (!data) {
82 // If nothing useful was returned, it could be the new docs site
83 if (pathname.startsWith('/documentation/article/')) {
84 return pushArticle({ slug, title: undefined });
85 }
86 // But if not then just open the link in a new tab
87 return window.open(`https://wordpress.org${pathname}`, '_blank');
88 }
89 // Finally load the article
90 pushArticle({ slug: data.split('/').filter(Boolean)?.at(-1) });
91 };
92
93 const handleExternal = (e) => {
94 e.preventDefault();
95 window.open(e.target.href, '_blank');
96 };
97
98 const handleNoOp = (e) => e.preventDefault();
99
100 links.forEach((link) => {
101 const { hash, host, pathname } = new URL(link.href);
102 // Hash links should be disabled since they don't work properly
103 if (
104 (hash && host === window.location.host) ||
105 pathname.startsWith('/support/category')
106 ) {
107 link.addEventListener('click', handleNoOp);
108 link.setAttribute('aria-disabled', 'true');
109 link.classList.add('link-disabled');
110 return;
111 }
112 // if link is to an image or a file, remove it
113 const pattern = /\.(jpg|jpeg|png|gif|pdf|doc|docx|xls|xlsx|ppt|pptx)$/;
114 if (pathname.match(pattern)) {
115 link.addEventListener('click', handleNoOp);
116 return;
117 }
118 if (
119 pathname.startsWith('/documentation/article') ||
120 pathname.startsWith('/support/article')
121 ) {
122 link.addEventListener('click', handleInternal);
123 return;
124 }
125 // If the link is something else, then open in a new tab
126 link.addEventListener('click', handleExternal);
127 const svg =
128 '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" class="components-external-link__icon css-rvs7bx esh4a730" aria-hidden="true" focusable="false"><path d="M18.2 17c0 .7-.6 1.2-1.2 1.2H7c-.7 0-1.2-.6-1.2-1.2V7c0-.7.6-1.2 1.2-1.2h3.2V4.2H7C5.5 4.2 4.2 5.5 4.2 7v10c0 1.5 1.2 2.8 2.8 2.8h10c1.5 0 2.8-1.2 2.8-2.8v-3.6h-1.5V17zM14.9 3v1.5h3.7l-6.4 6.4 1.1 1.1 6.4-6.4v3.7h1.5V3h-6.3z"></path></svg>';
129 const svgEl = document.createElement('span');
130 svgEl.innerHTML = svg;
131 link.appendChild(svgEl);
132 });
133 return () => {
134 links.forEach((link) => {
135 link?.removeEventListener('click', handleInternal);
136 link?.removeEventListener('click', handleExternal);
137 link?.removeEventListener('click', handleNoOp);
138 });
139 };
140 }, [article, pushArticle]);
141
142 if (loading || fetching) {
143 return (
144 <div className="p-8 text-center text-base">
145 <Spinner />
146 </div>
147 );
148 }
149
150 if (error) {
151 return (
152 <div className="p-8 text-center text-base">
153 {__('There was an error loading this article', 'extendify-local')}
154 </div>
155 );
156 }
157
158 return (
159 <article
160 ref={articleRef}
161 className="extendify-documentation w-full"
162 data-test="kb-article-content">
163 <h1 className="m-0 text-3xl">{title}</h1>
164 {article?.content && parse(article?.content)}
165 </article>
166 );
167 };
168