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 / Agent / workflows / content / components / PageContentShell.jsx

PageContentShell.jsx in Extendify 2.2.0, at src/Agent/workflows/content/components/PageContentShell.jsx

186 lines 5.5 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 { registerCoreBlocks } from '@wordpress/block-library';
3 import { rawHandler, serialize } from '@wordpress/blocks';
4 import { useDispatch, useSelect } from '@wordpress/data';
5 import { store as editorStore } from '@wordpress/editor';
6 import { useEffect, useRef, useState } from '@wordpress/element';
7 import { usePageCustomContent } from '@page-creator/hooks/usePageCustomContent';
8 import { processPatterns } from '@page-creator/lib/processPatterns.js';
9 import { usePageDescriptionStore } from '@page-creator/state/cache';
10 import { installBlocks } from '@page-creator/util/installBlocks.js';
11 import { syncPageTitleTemplate } from '@page-creator/util/syncPageTitleTemplate.js';
12 import { render } from '@shared/lib/dom';
13 import { pageNames } from '@shared/lib/pages';
14 import { addIdAttributeToBlock } from '@launch/lib/blocks';
15
16 const { pageTitlePattern } = window.extPageCreator ?? {};
17
18 const PageContentShell = ({ pageDescription, onComplete }) => {
19 const { page, loading } = usePageCustomContent();
20 const { setDescription } = usePageDescriptionStore();
21 const { editPost } = useDispatch(editorStore);
22 const [patterns, setPatterns] = useState([]);
23 const once = useRef(false);
24 const { theme, templates } = useSelect((select) => {
25 const core = select('core');
26 const current = core.getCurrentTheme();
27
28 return {
29 theme: current,
30 templates: core.getEntityRecords('postType', 'wp_template', {
31 per_page: -1,
32 context: 'edit',
33 theme: current?.stylesheet,
34 }),
35 };
36 }, []);
37
38 // `rawHandler` does not work on the frontend, so we need to register the
39 // core blocks again to get it working.
40 useEffect(() => {
41 registerCoreBlocks();
42 }, []);
43
44 useEffect(() => {
45 setDescription(pageDescription);
46 }, [pageDescription, setDescription]);
47
48 useEffect(() => {
49 if (!page && loading) return;
50 if (once.current) return;
51 once.current = true;
52 (async () => {
53 // If page-with-title template isn’t customized and a page-title pattern is stashed, update the template with it.
54 await syncPageTitleTemplate(pageTitlePattern);
55
56 const patterns = await processPatterns(page?.patterns);
57 await installBlocks({ patterns });
58 setPatterns(patterns);
59 })();
60 }, [loading, page, setPatterns]);
61
62 useEffect(() => {
63 if (!patterns?.length || !once.current) return;
64 if (!theme || !Array.isArray(templates)) return;
65
66 const isExtendable = theme.textdomain === 'extendable';
67 const hasPageWithTitle =
68 isExtendable && templates.some((t) => t.slug === 'page-with-title');
69
70 const id = setTimeout(async () => {
71 const result = await insertPage({
72 hasPageWithTitle,
73 patterns,
74 title: page.title,
75 });
76 onComplete(result);
77 }, 1000);
78
79 return () => clearTimeout(id);
80 }, [patterns, editPost, page, theme, templates, onComplete]);
81
82 return null; // Renders nothing
83 };
84
85 const insertPage = async ({ hasPageWithTitle, patterns, title }) => {
86 const patternsToInsert = hasPageWithTitle
87 ? patterns.filter((p) => !p.patternTypes?.includes('page-title'))
88 : patterns;
89
90 const pagePatterns = patternsToInsert.map(({ code, ...prop }) => {
91 // find links with #extendify- like href="#extendify-hero-cta"
92 const linksRegex = /href="#extendify-([^"]+)"/g;
93 return {
94 ...prop,
95 // replaceAll() is an ES2021 method. Since the regex already has the global flag (/g),
96 // we can use the older replace() method for broader browser compatibility.
97 code: code.replace(linksRegex, 'href="#"'),
98 };
99 });
100
101 const HTML = pagePatterns.map(({ code }) => code).join('');
102 const blocks = rawHandler({ HTML });
103
104 const content = [];
105 // Use this to avoid adding duplicate Ids to patterns
106 const seenPatternTypes = new Set();
107
108 for (const [i, pattern] of blocks.entries()) {
109 const patternType = pagePatterns[i].patternTypes?.[0];
110 const serializedBlock = serialize(pattern);
111 // Get the translated slug
112 const { slug } =
113 Object.values(pageNames).find(({ alias }) =>
114 alias.includes(patternType),
115 ) || {};
116
117 // If we've already seen this slug, or no slug found, return the pattern unchanged
118 if (seenPatternTypes.has(slug) || !slug) {
119 content.push(serializedBlock);
120 continue;
121 }
122 // Add the slug to the seen list so we don't add it again
123 seenPatternTypes.add(slug);
124
125 content.push(addIdAttributeToBlock(serializedBlock, slug));
126 }
127
128 const data = {
129 title,
130 status: 'draft',
131 content: content.join(''),
132 template: !hasPageWithTitle ? 'no-title-sticky-header' : 'page-with-title',
133 meta: { made_with_extendify_launch: true },
134 };
135
136 return await apiFetch({ path: '/wp/v2/pages', method: 'POST', data });
137 };
138
139 // This is used as a bridge between this hidden component and the tool.
140 export const generatePage = (pageDescription) => {
141 return new Promise((resolve, reject) => {
142 const container = document.createElement('div');
143 container.style.display = 'none';
144 document.body.appendChild(container);
145 let isCompleted = false;
146 let timeout;
147 const cleanup = () => {
148 if (timeout) clearTimeout(timeout);
149 if (container.parentNode) container.remove();
150 };
151
152 const handleComplete = (data) => {
153 if (isCompleted) return;
154 isCompleted = true;
155 cleanup();
156
157 !data
158 ? reject(new Error('Something went wrong while creating the page'))
159 : resolve(data);
160 };
161
162 timeout = setTimeout(() => {
163 if (!isCompleted) {
164 handleComplete(null);
165 }
166 }, 30000);
167
168 try {
169 render(
170 <PageContentShell
171 onComplete={(data) => {
172 clearTimeout(timeout);
173 handleComplete(data);
174 }}
175 pageDescription={pageDescription}
176 />,
177 container,
178 );
179 } catch (error) {
180 clearTimeout(timeout);
181 cleanup();
182 reject(error);
183 }
184 });
185 };
186