PluginProbe
Extendify / 3.2.1
Extendify v3.2.1
3.2.1 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 All 127 releases
extendify / src / Agent / workflows / content / components / PageContentShell.jsx

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

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