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

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

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