HEX
Server: LiteSpeed
System: Linux server315.web-hosting.com 4.18.0-553.54.1.lve.el8.x86_64 #1 SMP Wed Jun 4 13:01:13 UTC 2025 x86_64
User: globfdxw (6114)
PHP: 8.1.34
Disabled: NONE
Upload Files
File: /home/globfdxw/diasporameetsafrica.com/wp-content/plugins/extendify/src/AutoLaunch/functions/nav.js
import { pageNames } from '@shared/lib/pages';
import apiFetch from '@wordpress/api-fetch';
import { __ } from '@wordpress/i18n';
import { addQueryArgs } from '@wordpress/url';

export const createNavigation = async ({ content = '', title, slug }) => {
	const existing = await apiFetch({
		path: addQueryArgs('extendify/v1/auto-launch/get-navigation', { slug }),
	}).catch(() => undefined);

	if (existing?.id) return existing;

	return await apiFetch({
		path: 'extendify/v1/auto-launch/create-navigation',
		method: 'POST',
		data: { title, slug, content },
	});
};

export const updateNavigation = (id, content) =>
	apiFetch({
		path: `wp/v2/navigation/${id}`,
		method: 'POST',
		data: { content },
	});

export const addSectionLinksToNav = async (
	navigationId,
	homePatterns = [],
	pluginPages = [],
	createdPages = [],
	{ orderedSlugs = [] } = {},
) => {
	// Extract plugin page slugs for comparison
	const pluginPageTitles = pluginPages.map(({ title }) =>
		title?.rendered?.toLowerCase(),
	);

	const pages =
		createdPages
			?.filter((page) => page?.slug !== 'home')
			?.map((page) => page.slug)
			?.filter(Boolean) ?? [];

	const resolve = (pattern) => {
		const patternType = pattern.patternTypes?.[0];
		const lookup =
			Object.values(pageNames).find(({ alias }) =>
				alias.includes(patternType),
			) || {};
		return {
			label: pattern.navLabel ?? lookup.title,
			slug: pattern.navSlug ?? lookup.slug,
		};
	};

	const sectionPatterns = homePatterns.filter((pattern) => {
		const { slug } = resolve(pattern);
		return slug && !pluginPageTitles.includes(slug);
	});

	const seen = new Set();

	const sectionsNavigationLinks = sectionPatterns.map((pattern) => {
		const { label, slug } = resolve(pattern);
		if (!slug) return '';
		if (seen.has(slug)) return '';
		seen.add(slug);

		const url = pages.includes(slug)
			? `${window.extSharedData.homeUrl}/${slug}`
			: `${window.extSharedData.homeUrl}/#${slug}`;

		const attributes = JSON.stringify({
			label,
			type: 'custom',
			url,
			kind: 'custom',
			isTopLevelLink: true,
		});

		return `<!-- wp:navigation-link ${attributes} /-->`;
	});

	const pluginPagesNavigationLinks = pluginPages.map(
		({ title, id, type, link }) => {
			const attributes = JSON.stringify({
				label: title.rendered,
				id,
				type,
				url: link,
				kind: id ? 'post-type' : 'custom',
				isTopLevelLink: true,
			});

			return `<!-- wp:navigation-link ${attributes} /-->`;
		},
	);

	// When an ordered slug list is provided, interleave plugin pages by slug
	// so e.g. "shop" lands where the design preview placed it.
	let navigationLinks;
	if (orderedSlugs.length) {
		const bySlug = new Map();
		sectionsNavigationLinks.forEach((link, i) => {
			const slug = resolve(sectionPatterns[i]).slug;
			if (slug) bySlug.set(slug, link);
		});
		pluginPages.forEach((page, i) => {
			if (page.slug) bySlug.set(page.slug, pluginPagesNavigationLinks[i]);
		});
		const ordered = orderedSlugs
			.map((slug) => bySlug.get(slug))
			.filter(Boolean);
		const placed = new Set(orderedSlugs.filter((s) => bySlug.has(s)));
		const extras = [
			...sectionsNavigationLinks.filter(
				(_, i) => !placed.has(resolve(sectionPatterns[i]).slug),
			),
			...pluginPagesNavigationLinks.filter(
				(_, i) => !placed.has(pluginPages[i].slug),
			),
		];
		navigationLinks = [...ordered, ...extras].join('');
	} else {
		navigationLinks = sectionsNavigationLinks
			.concat(pluginPagesNavigationLinks)
			.join('');
	}

	await updateNavigation(navigationId, navigationLinks);
};

export const addPageLinksToNav = async (
	navigationId,
	allPages,
	createdPages,
	pluginPages = [],
	{ orderedSlugs = [] } = {},
) => {
	// Because WP may have changed the slug and permalink (i.e., because of different languages),
	// we are using the `originalSlug` property to match the original pages with the updated ones.
	const findCreatedPage = ({ slug }) =>
		createdPages.find(({ originalSlug: s }) => s === slug) || {};

	const filteredCreatedPages = allPages
		.filter((p) => findCreatedPage(p)?.id) // make sure its a page
		.filter(({ slug }) => slug !== 'home') // exclude home page
		.map((page) => findCreatedPage(page));

	// Plugin pages use `slug`, created pages use `originalSlug`
	const getSlug = (page) => page.originalSlug ?? page.slug;
	const getOrder = (page) => {
		const slug = getSlug(page);
		return (
			pageNames[slug]?.navOrder ??
			Object.values(pageNames).find((p) => p.alias?.includes(slug))?.navOrder ??
			Object.keys(pageNames).length + 1
		);
	};
	const mergedPages = [...filteredCreatedPages, ...pluginPages];

	let finalPages;
	if (orderedSlugs.length) {
		const indexOf = (p) => orderedSlugs.indexOf(getSlug(p));
		const ordered = mergedPages
			.filter((p) => indexOf(p) !== -1)
			.sort((a, b) => indexOf(a) - indexOf(b));
		const extras = mergedPages.filter((p) => indexOf(p) === -1);
		finalPages = [...ordered, ...extras];
	} else {
		const contactPage = mergedPages.find((page) => {
			const slug = getSlug(page);
			return slug === 'contact' || pageNames.contact?.alias?.includes(slug);
		});

		const sortedPages = mergedPages
			.filter((page) => page !== contactPage)
			.sort((a, b) => getOrder(a) - getOrder(b));

		finalPages = contactPage
			? (() => {
					const index =
						sortedPages.length === 5 ? 5 : Math.min(4, sortedPages.length);
					return [
						...sortedPages.slice(0, index),
						contactPage,
						...sortedPages.slice(index),
					];
				})()
			: sortedPages;
	}

	const pageLinks = finalPages.map(({ id, title, link, type }) => {
		const attributes = JSON.stringify({
			label: title.rendered,
			id,
			type,
			url: link,
			kind: id ? 'post-type' : 'custom',
			isTopLevelLink: true,
		});

		return `<!-- wp:navigation-link ${attributes} /-->`;
	});

	const topLevelLinks = pageLinks.slice(0, 5).join('');
	const submenuLinks = pageLinks.slice(5);
	// We want a max of 6 top-level links, but if 7+, then move the last
	// two+ to a submenu.
	const additionalLinks =
		submenuLinks.length > 1
			? ` <!-- wp:navigation-submenu ${JSON.stringify({
					// translators: "More" here is used for a navigation menu item that contains additional links.
					label: __('More', 'extendify-local'),
					url: '#',
					kind: 'custom',
				})} --> ${submenuLinks.join('')} <!-- /wp:navigation-submenu -->`
			: submenuLinks.join(''); // only 1 link here

	await updateNavigation(navigationId, topLevelLinks + additionalLinks);
};

const getNavAttributes = (headerCode) => {
	try {
		return JSON.parse(headerCode.match(/<!-- wp:navigation([\s\S]*?)-->/)[1]);
	} catch (_e) {
		return {};
	}
};

export const updateNavAttributes = (headerCode, attributes) => {
	const newAttributes = JSON.stringify({
		...getNavAttributes(headerCode),
		...attributes,
	});
	return headerCode.replace(
		// biome-ignore lint: don't want to refactor and test this regex now
		/(<!--\s*wp:navigation\b[^>]*>)([^]*?)(<!--\s*\/wp:navigation\s*-->)/gi,
		`<!-- wp:navigation ${newAttributes} /-->`,
	);
};

const getNavExtrasBlock = (launchDecisions) => {
	switch (launchDecisions?.navExtras) {
		case 'button': {
			const label =
				launchDecisions?.navButtonLabel || __('Get Started', 'extendify-local');
			return `<!-- wp:buttons {"className":"ext-nav-extras-btn"} -->
<div class="wp-block-buttons ext-nav-extras-btn"><!-- wp:button {"className":"is-style-ext-preset\u002d\u002dbutton\u002d\u002dnatural-1\u002d\u002dbutton-1","style":{"spacing":{"padding":{"left":"20px","right":"20px","top":"8px","bottom":"8px"}},"typography":{"lineHeight":1.6}},"fontSize":"small"} -->
<div class="wp-block-button is-style-ext-preset--button--natural-1--button-1"><a class="wp-block-button__link has-small-font-size has-custom-font-size wp-element-button" href="#extendify-navbar-cta" style="padding-top:8px;padding-right:20px;padding-bottom:8px;padding-left:20px;line-height:1.6">${label}</a></div>
<!-- /wp:button --></div>
<!-- /wp:buttons -->`;
		}
		case 'phone-number':
			return `<!-- wp:group {"className":"ext-nav-extras-phone","style":{"spacing":{"blockGap":"6px"}},"layout":{"type":"flex","flexWrap":"nowrap","verticalAlignment":"center"}} -->
<div class="wp-block-group ext-nav-extras-phone"><!-- wp:image {"sizeSlug":"large","style":{"layout":{"selfStretch":"fixed","flexSize":"21px"},"color":{"duotone":"var:preset|duotone|primary-foreground"},"spacing":{"margin":{"bottom":"6px"}}}} -->
<figure class="wp-block-image size-large" style="margin-bottom:6px"><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgZmlsbD0iIzAwMDAwMCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxwYXRoIGQ9Ik0yMjQsMTU0LjhsLTQ3LjA5LTIxLjExLS4xOC0uMDhhMTkuOTQsMTkuOTQsMCwwLDAtMTksMS43NSwxMy4wOCwxMy4wOCwwLDAsMC0xLjEyLjg0bC0yMi4zMSwxOWMtMTMtNy4wNS0yNi40My0yMC4zNy0zMy40OS0zMy4yMWwxOS4wNi0yMi42NmExMS43NiwxMS43NiwwLDAsMCwuODUtMS4xNSwyMCwyMCwwLDAsMCwxLjY2LTE4LjgzLDEuNDIsMS40MiwwLDAsMS0uMDgtLjE4TDEwMS4yLDMyQTIwLjA2LDIwLjA2LDAsMCwwLDgwLjQyLDIwLjE1LDYwLjI3LDYwLjI3LDAsMCwwLDI4LDgwYzAsODEuNjEsNjYuMzksMTQ4LDE0OCwxNDhhNjAuMjcsNjAuMjcsMCwwLDAsNTkuODUtNTIuNDJBMjAuMDYsMjAuMDYsMCwwLDAsMjI0LDE1NC44Wk0xNzYsMjA0QTEyNC4xNSwxMjQuMTUsMCwwLDEsNTIsODAsMzYuMjksMzYuMjksMCwwLDEsODAuNDgsNDQuNDZsMTguODIsNDJMODAuMTQsMTA5LjI4YTEyLDEyLDAsMCwwLS44NiwxLjE2QTIwLDIwLDAsMCwwLDc4LDEzMC4wOGM5LjQyLDE5LjI4LDI4LjgzLDM4LjU2LDQ4LjMxLDQ4QTIwLDIwLDAsMCwwLDE0NiwxNzYuNjNhMTEuNjMsMTEuNjMsMCwwLDAsMS4xMS0uODVsMjIuNDMtMTkuMDcsNDIsMTguODFBMzYuMjksMzYuMjksMCwwLDEsMTc2LDIwNFoiPjwvcGF0aD48L3N2Zz4=" alt=""/></figure>
<!-- /wp:image -->

<!-- wp:paragraph {"className":"no-underline","style":{"elements":{"link":{"color":{"text":"var:preset|color|primary"}}},"typography":{"fontSize":"18px","fontStyle":"normal","fontWeight":"700","textDecoration":"none"}},"textColor":"primary"} -->
<p class="no-underline has-primary-color has-text-color has-link-color" style="font-size:18px;font-style:normal;font-weight:700;text-decoration:none"><a href="tel:206-555-0100" data-type="tel" data-id="tel:206-555-0100">206-555-0100</a></p>
<!-- /wp:paragraph --></div>
<!-- /wp:group -->`;
		case 'social-icons':
			return `<!-- wp:social-links {"iconColor":"foreground","iconColorValue":"var(--wp--preset--color--foreground)","size":"has-small-icon-size","className":"is-style-logos-only ext-hidden tablet:ext-flex ext-nav-extras-social","style":{"spacing":{"blockGap":"1rem"}},"layout":{"type":"flex","flexWrap":"nowrap","justifyContent":"right"}} -->
<ul class="wp-block-social-links has-small-icon-size has-icon-color is-style-logos-only ext-hidden tablet:ext-flex ext-nav-extras-social"><!-- wp:social-link {"url":"https://www.instagram.com/","service":"instagram"} /-->

<!-- wp:social-link {"url":"https://www.facebook.com/","service":"facebook"} /-->

<!-- wp:social-link {"url":"https://x.com/","service":"x"} /--></ul>
<!-- /wp:social-links -->`;
		default:
			return null;
	}
};

export const injectNavExtras = (headerCode, launchDecisions) => {
	const navExtras = launchDecisions?.navExtras;
	if (!navExtras || navExtras === 'none') return headerCode;

	const block = getNavExtrasBlock(launchDecisions);
	if (!block) return headerCode;

	// Strip any existing ext-nav-extras-* block so we don't stack with what the
	// header may already ship with.
	const stripped = headerCode.replace(
		/<!--\s*wp:([\w-]+)\b[^>]*ext-nav-extras-[\w-]+[^>]*-->[\s\S]*?<!--\s*\/wp:\1\s*-->\s*/g,
		'',
	);

	const markerIdx = stripped.indexOf('ext-nav-extras');
	if (markerIdx === -1) return stripped;

	const groupCloseMatch = stripped
		.slice(markerIdx)
		.match(/<!--\s*\/wp:group\s*-->/);
	if (!groupCloseMatch) return stripped;

	const insertAt = markerIdx + groupCloseMatch.index;
	return `${stripped.slice(0, insertAt)}${block}\n${stripped.slice(insertAt)}`;
};