PluginProbe
Extendify / 3.1.0
Extendify v3.1.0
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 / AutoLaunch / functions / links.js

links.js in Extendify 3.1.0, at src/AutoLaunch/functions/links.js

271 lines 7.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { updatePage } from '@auto-launch/functions/pages';
2 import { alreadyActive } from '@auto-launch/functions/plugins';
3 import { getOption, getPageById } from '@auto-launch/functions/wp';
4 import { AI_HOST } from '@constants';
5 import { reqDataBasics } from '@shared/lib/data';
6 import { pageNames } from '@shared/lib/pages';
7 import { getBlockContent, parse } from '@wordpress/blocks';
8
9 const { homeUrl } = window.extSharedData;
10 const buttonRegex = /href="(#extendify-[\w-]+)"/gi;
11 const pagesWithButtons = (p) => p?.content?.raw?.match(buttonRegex);
12
13 export const updateButtonLinks = async (
14 wpPages,
15 pluginPages,
16 headerCode = '',
17 ) => {
18 // Fetch active plugins after installing plugins
19 const contactPageSlug = wpPages.find(({ originalSlug }) =>
20 originalSlug?.startsWith('contact'),
21 )?.slug;
22
23 const patternsToProcess = wpPages
24 // Look for pages with links
25 .filter(pagesWithButtons)
26 .map(({ content }) => {
27 // 1. Convert to individual blocks
28 return (
29 parse(content.raw || '')
30 // 2. Convert back to HTML
31 .map((b) => getBlockContent(b))
32 // 3. Filter only blocks with links
33 .filter((b) => b.match(buttonRegex))
34 .join('')
35 // TODO: Filter out patterns from pages that have identical buttons?
36 );
37 });
38
39 if (headerCode?.match(buttonRegex)) {
40 patternsToProcess.push(headerCode);
41 }
42
43 // Collect the page slugs to share with the server
44 const availablePages = wpPages
45 .concat(pluginPages)
46 .filter(({ slug }) => !slug.startsWith('home'))
47 .map(({ slug }) => `/${slug}`);
48
49 // Fetch the links from the server. If a request fails, ignore it.
50 const suggestedLinks = (
51 await Promise.allSettled(
52 patternsToProcess.map(
53 (pageContent) =>
54 getLinkSuggestions({ pageContent, availablePages }) || {},
55 ),
56 )
57 )
58 .filter((r) => r.status === 'fulfilled')
59 .map((r) => r.value?.suggestedLinks || [])
60 // Combine all suggested links
61 .reduce((acc, link) => {
62 for (const key in link) {
63 acc[key] = link[key];
64 }
65 return acc;
66 }, {});
67
68 const linkKeys = Object.keys(suggestedLinks)
69 .filter((k) =>
70 // Remove links sent back that aren't in the availablePages
71 availablePages.includes(`/${suggestedLinks[k].replace(/^\//, '')}`),
72 )
73 .map((v) => `\\"${v}\\"`)
74 .join('|');
75
76 // Replace links and update the pages. Failed pages get ignored.
77 const newPages = (
78 await Promise.allSettled(
79 wpPages.filter(pagesWithButtons).map((p) => {
80 // We want to match \"extendify-cta\" exactly inside the href
81 // So we need to look for the quotes, then replace with the quotes
82 const content = linkKeys
83 ? p.content.raw.replace(new RegExp(linkKeys, 'g'), (match) => {
84 if (!match || suggestedLinks.length === 0) return '';
85
86 const link = suggestedLinks[match.replace(/"/g, '')];
87 // if the link points to the current page or '/'
88 // we should link to the contact page (or default to '/')
89 if ([p.slug, `/${p.slug}`, '/'].includes(link))
90 return `"${homeUrl}/${contactPageSlug ?? ''}"`;
91
92 // The server once sent back slugs without the /
93 // so we need to check
94 return `"${homeUrl}/${link.replace(/^\//, '')}"`;
95 })
96 : p.content.raw.replace(new RegExp(buttonRegex, 'g'), (match) => {
97 return match ? 'href="#"' : '';
98 });
99 return updatePage({ id: p.id, content });
100 }),
101 )
102 )
103 .filter((r) => r.status === 'fulfilled')
104 .map((r) => r.value);
105
106 const updatedHeaderCode = linkKeys
107 ? headerCode.replace(new RegExp(linkKeys, 'g'), (match) => {
108 const link = suggestedLinks[match.replace(/"/g, '')];
109 if (link === '/') return `"${homeUrl}/${contactPageSlug ?? ''}"`;
110 return `"${homeUrl}/${link.replace(/^\//, '')}"`;
111 })
112 : headerCode.replace(new RegExp(buttonRegex, 'g'), 'href="#"');
113
114 const updatedPages = wpPages
115 // Add the new pages into the wpPages array
116 .map((p) => newPages.find(({ id }) => id === p.id) || p)
117 // Also include the originalSlug from wpPages
118 .map((p) => {
119 const { originalSlug } = wpPages.find(({ id }) => id === p.id) || {};
120 return { ...p, originalSlug };
121 });
122
123 return { wpPages: updatedPages, headerCode: updatedHeaderCode };
124 };
125
126 export const updateSinglePageLinksToSections = async (
127 wpPages,
128 pages,
129 options,
130 headerCode = '',
131 ) => {
132 let homePageContent = wpPages?.[0]?.content?.raw;
133 if (!homePageContent) return { wpPages, headerCode };
134 const { objective, activePlugins, landingPageCTALink } = options;
135
136 /**
137 * Special case handling for landing page sites.
138 *
139 * For landing pages, all internal navigation links are either replaced with
140 * a single, provided CTA link, or set to '#' if no CTA link is provided.
141 * This function updates the home page content and returns the updated pages array.
142 */
143 if (objective === 'landing-page') {
144 const ctaHref =
145 landingPageCTALink && typeof landingPageCTALink === 'string'
146 ? landingPageCTALink
147 : '#';
148 wpPages[0] = updatePage({
149 id: wpPages[0].id,
150 content: homePageContent.replaceAll(
151 /href="(#extendify-[\w|-]+)"/gi,
152 `href="${ctaHref}"`,
153 ),
154 });
155
156 return {
157 wpPages,
158 headerCode: headerCode.replaceAll(
159 /href="(#extendify-[\w|-]+)"/gi,
160 `href="${ctaHref}"`,
161 ),
162 };
163 }
164
165 // get all the patterns that we have in the home page
166 const patternTypes = pages?.[0]?.patterns
167 ?.map((pattern) => pattern?.patternTypes?.[0])
168 ?.filter((patternType) => patternType !== 'hero-header')
169 ?.map((patternType) => {
170 const { slug } =
171 Object.values(pageNames).find(({ alias }) =>
172 alias.includes(patternType),
173 ) || {};
174 return slug;
175 })
176 ?.filter(Boolean)
177 ?.flat();
178
179 const createdPages =
180 pages
181 ?.filter((page) => page.slug !== 'home')
182 ?.map((page) => page.slug)
183 ?.filter(Boolean) ?? [];
184
185 // get the active plugins
186 const pluginPages = [];
187
188 // check if woocommerce is active, if so we add it to the list of pages
189 if (alreadyActive(activePlugins, 'woocommerce')) {
190 const page = await getPageById(
191 await getOption('woocommerce_shop_page_id'),
192 ).catch(() => null);
193
194 page?.slug && pluginPages.push(page.slug);
195 }
196
197 // check if events calendar is active, if so we add it to the list of pages
198 if (alreadyActive(activePlugins, 'the-events-calendar')) {
199 pluginPages.push('events');
200 }
201
202 const allAvailablePages = (patternTypes ?? []).concat(pluginPages);
203 if (!allAvailablePages.length) {
204 wpPages[0] = updatePage({
205 id: wpPages[0].id,
206 content: homePageContent.replaceAll(
207 /href="(#extendify-[\w|-]+)"/gi,
208 'href="#"',
209 ),
210 });
211 return {
212 wpPages,
213 headerCode: headerCode.replaceAll(
214 /href="(#extendify-[\w|-]+)"/gi,
215 'href="#"',
216 ),
217 };
218 }
219
220 // get the suggested links from the AI and send both the patterns and the plugin pages.
221 const { suggestedLinks } =
222 (await getLinkSuggestions({
223 pageContent: headerCode
224 ? `${homePageContent}\n${headerCode}`
225 : homePageContent,
226 availablePages: allAvailablePages,
227 })) || {};
228
229 // replace the links
230 let updatedHeaderCode = headerCode;
231 homePageContent = Object.keys(suggestedLinks ?? {}).reduce((content, key) => {
232 const slug = suggestedLinks[key];
233
234 if (!slug) return content;
235
236 const newLink = pluginPages.concat(createdPages).includes(slug)
237 ? `"${homeUrl}/${slug}"`
238 : `"${homeUrl}/#${slug}"`;
239
240 updatedHeaderCode = updatedHeaderCode.replaceAll(`"${key}"`, newLink);
241 return content.replaceAll(`"${key}"`, newLink);
242 }, homePageContent);
243
244 // Update the first page by replacing the buttons urls with the new slug
245 wpPages[0] = updatePage({
246 id: wpPages[0].id,
247 content: homePageContent,
248 });
249
250 return { wpPages, headerCode: updatedHeaderCode };
251 };
252
253 const getLinkSuggestions = async ({ pageContent, availablePages }) => {
254 try {
255 const response = await fetch(`${AI_HOST}/api/link-pages`, {
256 method: 'POST',
257 headers: { 'Content-Type': 'application/json' },
258 body: JSON.stringify({
259 ...reqDataBasics,
260 pageContent,
261 availablePages,
262 }),
263 });
264 if (!response.ok) throw new Error('Bad response from server');
265 return await response.json();
266 } catch (_error) {
267 // fail gracefully
268 return {};
269 }
270 };
271