| 1 |
import { importImage, updateOption } from '@auto-launch/functions/wp'; |
| 2 |
import { PATTERNS_HOST } from '@constants'; |
| 3 |
import { reqDataBasics } from '@shared/lib/data'; |
| 4 |
import { pageNames } from '@shared/lib/pages'; |
| 5 |
import apiFetch from '@wordpress/api-fetch'; |
| 6 |
import { createBlock, parse, serialize } from '@wordpress/blocks'; |
| 7 |
import { __, sprintf } from '@wordpress/i18n'; |
| 8 |
import { setStatus } from './helpers'; |
| 9 |
|
| 10 |
// Slugs that plugins own — skip creating design-build pages for these. |
| 11 |
export const PLUGIN_OWNED_PAGES = [ |
| 12 |
{ slug: 'shop', plugin: 'woocommerce' }, |
| 13 |
{ slug: 'events', plugin: 'the-events-calendar' }, |
| 14 |
]; |
| 15 |
|
| 16 |
export const isBlogPage = ({ originalSlug }) => originalSlug === 'blog'; |
| 17 |
|
| 18 |
export const getPagesToCreate = (data) => { |
| 19 |
const { home, pages, siteProfile } = data; |
| 20 |
const homepage = { |
| 21 |
id: 'home', |
| 22 |
name: pageNames.home.title, |
| 23 |
slug: 'home', |
| 24 |
patterns: home.patterns, |
| 25 |
}; |
| 26 |
const hasBlog = pages.some(({ slug }) => slug === 'blog'); |
| 27 |
const blogPage = |
| 28 |
siteProfile.blog && !hasBlog |
| 29 |
? { |
| 30 |
name: pageNames.blog.title, |
| 31 |
id: 'blog', |
| 32 |
patterns: [], |
| 33 |
slug: 'blog', |
| 34 |
} |
| 35 |
: null; |
| 36 |
|
| 37 |
// Remove the page title pattern from all pages |
| 38 |
const patternHasTitle = (pattern) => |
| 39 |
!pattern.patternTypes?.includes('page-title'); |
| 40 |
const p = pages.map((page) => ({ |
| 41 |
...page, |
| 42 |
patterns: page.patterns.filter(patternHasTitle), |
| 43 |
})); |
| 44 |
return [homepage, ...p, blogPage].filter(Boolean); |
| 45 |
}; |
| 46 |
|
| 47 |
// Replace the page-title pattern in “page-with-title” template with the incoming page-title pattern |
| 48 |
export const updatePageTitlePattern = async (pageTitlePattern) => { |
| 49 |
const updatedPattern = transformHeadingToPostTitle(pageTitlePattern); |
| 50 |
|
| 51 |
const templateContent = ` |
| 52 |
<!-- wp:template-part {"slug":"header","tagName":"header"} /--> |
| 53 |
<!-- wp:group {"tagName":"main","style":{"spacing":{"margin":{"top":"0px","bottom":"0px"},"blockGap":"0"}}} --> |
| 54 |
<main class="wp-block-group" style="margin-top:0px;margin-bottom:0px"> |
| 55 |
${updatedPattern} |
| 56 |
<!-- wp:post-content {"layout":{"type":"constrained"}} /--> |
| 57 |
</main> |
| 58 |
<!-- /wp:group --> |
| 59 |
<!-- wp:template-part {"slug":"footer","tagName":"footer"} /--> |
| 60 |
`; |
| 61 |
|
| 62 |
try { |
| 63 |
await apiFetch({ |
| 64 |
path: '/wp/v2/templates/extendable/page-with-title', |
| 65 |
method: 'POST', |
| 66 |
data: { |
| 67 |
slug: 'page-with-title', |
| 68 |
theme: 'extendable', |
| 69 |
type: 'wp_template', |
| 70 |
status: 'publish', |
| 71 |
description: __('Added by Launch', 'extendify-local'), |
| 72 |
content: templateContent, |
| 73 |
}, |
| 74 |
}); |
| 75 |
} catch { |
| 76 |
// do nothing |
| 77 |
} |
| 78 |
}; |
| 79 |
|
| 80 |
// finds the core/heading in the pattern and replaces it with a core/post-title block |
| 81 |
const transformHeadingToPostTitle = (rawHTML) => { |
| 82 |
let done = false; |
| 83 |
|
| 84 |
const walk = (block) => { |
| 85 |
if (done) return block; |
| 86 |
|
| 87 |
if (block.name === 'core/heading') { |
| 88 |
done = true; |
| 89 |
const attrs = { |
| 90 |
level: block.attributes.level, |
| 91 |
textAlign: block.attributes.textAlign, |
| 92 |
textColor: block.attributes.textColor, |
| 93 |
backgroundColor: block.attributes.backgroundColor, |
| 94 |
isLink: block.attributes.isLink, |
| 95 |
linkTarget: block.attributes.linkTarget, |
| 96 |
rel: block.attributes.rel, |
| 97 |
}; |
| 98 |
|
| 99 |
if (block.attributes.fontSize) { |
| 100 |
attrs.fontSize = block.attributes.fontSize; |
| 101 |
} |
| 102 |
|
| 103 |
const customSize = block.attributes.style?.typography?.fontSize; |
| 104 |
const linkStyle = block.attributes.style?.elements?.link; |
| 105 |
|
| 106 |
if (customSize || linkStyle) { |
| 107 |
attrs.style = {}; |
| 108 |
|
| 109 |
if (customSize) { |
| 110 |
attrs.style.typography = { fontSize: customSize }; |
| 111 |
} |
| 112 |
if (linkStyle) { |
| 113 |
attrs.style.elements = { link: linkStyle }; |
| 114 |
} |
| 115 |
} |
| 116 |
|
| 117 |
return createBlock('core/post-title', attrs); |
| 118 |
} |
| 119 |
|
| 120 |
if (block.innerBlocks?.length) { |
| 121 |
block.innerBlocks = block.innerBlocks.map(walk); |
| 122 |
} |
| 123 |
return block; |
| 124 |
}; |
| 125 |
|
| 126 |
return serialize(parse(rawHTML).map(walk)); |
| 127 |
}; |
| 128 |
|
| 129 |
// navSlug: the design menu entry this section was picked for (applyDesignBuildNav). |
| 130 |
export const sectionSlug = (pattern) => |
| 131 |
pattern.navSlug ?? |
| 132 |
Object.values(pageNames).find(({ alias }) => |
| 133 |
alias.includes(pattern.patternTypes?.[0]), |
| 134 |
)?.slug; |
| 135 |
|
| 136 |
export const createWpPages = async ( |
| 137 |
pagesRaw, |
| 138 |
{ skipSectionIds = false } = {}, |
| 139 |
) => { |
| 140 |
const pages = []; |
| 141 |
|
| 142 |
for (const page of pagesRaw) { |
| 143 |
const content = []; |
| 144 |
const seenPatternTypes = new Set(); |
| 145 |
|
| 146 |
setStatus(sprintf(__('Adding page: %s', 'extendify-local'), page.name)); |
| 147 |
|
| 148 |
for (const [_, pattern] of page.patterns.entries()) { |
| 149 |
const code = pattern.code; |
| 150 |
const slug = sectionSlug(pattern); |
| 151 |
|
| 152 |
if (skipSectionIds || seenPatternTypes.has(slug) || !slug) { |
| 153 |
content.push(code); |
| 154 |
continue; |
| 155 |
} |
| 156 |
|
| 157 |
seenPatternTypes.add(slug); |
| 158 |
content.push(addIdAttributeToBlock(code, slug)); |
| 159 |
} |
| 160 |
|
| 161 |
const pageData = { |
| 162 |
title: page.name, |
| 163 |
status: 'publish', |
| 164 |
content: content.join(''), |
| 165 |
template: page.slug === 'home' ? 'no-title' : 'page-with-title', |
| 166 |
meta: { made_with_extendify_launch: true }, |
| 167 |
}; |
| 168 |
|
| 169 |
let newPage; |
| 170 |
try { |
| 171 |
newPage = await createPage(pageData); |
| 172 |
} catch (_e) { |
| 173 |
pageData.template = 'no-title'; |
| 174 |
newPage = await createPage(pageData); |
| 175 |
} |
| 176 |
|
| 177 |
pages.push({ ...newPage, originalSlug: page.slug }); |
| 178 |
} |
| 179 |
|
| 180 |
const maybeHome = pages.find(({ originalSlug }) => originalSlug === 'home'); |
| 181 |
if (maybeHome) { |
| 182 |
await updateOption('show_on_front', 'page'); |
| 183 |
await updateOption('page_on_front', maybeHome.id); |
| 184 |
} |
| 185 |
|
| 186 |
const maybeBlog = pages.find(isBlogPage); |
| 187 |
if (maybeBlog) { |
| 188 |
await updateOption('page_for_posts', maybeBlog.id); |
| 189 |
} |
| 190 |
|
| 191 |
return pages; |
| 192 |
}; |
| 193 |
|
| 194 |
export const addIdAttributeToBlock = (blockCode, id) => |
| 195 |
blockCode.replace( |
| 196 |
/(<div\s[^>]*class="[^"]*\bwp-block-group\b[^"]*")/, |
| 197 |
`$1 id="${id}"`, |
| 198 |
); |
| 199 |
|
| 200 |
export const createPage = (data) => |
| 201 |
apiFetch({ path: 'wp/v2/pages', data, method: 'POST' }); |
| 202 |
export const updatePage = (data) => |
| 203 |
apiFetch({ path: `wp/v2/pages/${data.id}`, data, method: 'POST' }); |
| 204 |
|
| 205 |
export const setHelloWorldFeaturedImage = async (imageUrls) => { |
| 206 |
try { |
| 207 |
const translatedSlug = window.extLaunchData?.helloWorldPostSlug; |
| 208 |
let posts = await apiFetch({ path: `wp/v2/posts?slug=${translatedSlug}` }); |
| 209 |
if (!posts.length) { |
| 210 |
posts = await apiFetch({ path: 'wp/v2/posts?slug=hello-world' }); |
| 211 |
} |
| 212 |
if (!posts.length) return; |
| 213 |
const helloPost = posts[0]; |
| 214 |
if (helloPost.featured_media && parseInt(helloPost.featured_media, 10) > 0) |
| 215 |
return; |
| 216 |
if (!Array.isArray(imageUrls) || imageUrls.length === 0) { |
| 217 |
console.error('No image URLs provided.'); |
| 218 |
return; |
| 219 |
} |
| 220 |
const lastImageUrl = imageUrls[imageUrls.length - 1]; |
| 221 |
const mediaResponse = await importImage(lastImageUrl, { |
| 222 |
alt: __('Hello World Featured Image', 'extendify-local'), |
| 223 |
filename: 'hello-world-featured.jpg', |
| 224 |
caption: '', |
| 225 |
}); |
| 226 |
if (!mediaResponse || !mediaResponse.id) { |
| 227 |
console.error('Image upload failed.'); |
| 228 |
return; |
| 229 |
} |
| 230 |
await apiFetch({ |
| 231 |
path: `wp/v2/posts/${helloPost.id}`, |
| 232 |
method: 'POST', |
| 233 |
data: { featured_media: mediaResponse.id }, |
| 234 |
}); |
| 235 |
} catch (error) { |
| 236 |
console.error('Failed to set Hello World featured image:', error); |
| 237 |
} |
| 238 |
}; |
| 239 |
|
| 240 |
export const addImprintPage = async ({ siteStyle }) => { |
| 241 |
try { |
| 242 |
// Get the imprint page template |
| 243 |
const imprintPage = await getImprintPageTemplate({ siteStyle }); |
| 244 |
// Create the page in WordPress with the fetched template |
| 245 |
const [createdImprintPage] = await createWpPages([imprintPage]); |
| 246 |
return createdImprintPage; |
| 247 |
} catch (error) { |
| 248 |
console.error('Failed to add imprint page:', error); |
| 249 |
return null; |
| 250 |
} |
| 251 |
}; |
| 252 |
|
| 253 |
export const getImprintPageTemplate = async ({ siteStyle }) => { |
| 254 |
const res = await fetch(`${PATTERNS_HOST}/api/page-imprint`, { |
| 255 |
method: 'POST', |
| 256 |
headers: { 'Content-Type': 'application/json' }, |
| 257 |
body: JSON.stringify({ ...reqDataBasics, siteStyle }), |
| 258 |
}); |
| 259 |
const response = await res.json(); |
| 260 |
return { ...response.template }; |
| 261 |
}; |
| 262 |
|