Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import * as theme from '../theme/index.js';
import {
collectHtmlImages,
collectSourceAssets,
buildNavigationAnchorCompatCss,
localizeCssImages,
REVEAL_NEUTRALIZER_CSS,
rewriteHtmlImageSrcs,
Expand Down Expand Up @@ -110,39 +109,6 @@ describe('source assets DLA parity', () => {
expect(WP_COMPAT_CSS).toBe(DLA_WP_COMPAT_CSS);
});

it('replays source nav anchor rules against core/navigation wrapper markup', () => {
const css = buildNavigationAnchorCompatCss(`
.jumpnav a { padding: 0.35rem 0.85rem; color: var(--muted); text-decoration: none; }
.jumpnav a:first-child { padding-left: 0; }
.contact a { text-decoration: underline; }
`);

expect(css).toContain(
'.jumpnav.wp-block-navigation .wp-block-navigation-item__content, .jumpnav .wp-block-navigation .wp-block-navigation-item__content { padding: 0.35rem 0.85rem; color: var(--muted); text-decoration: none; }'
);
expect(css).toContain(
'.jumpnav.wp-block-navigation .wp-block-navigation-item:first-child > .wp-block-navigation-item__content, .jumpnav .wp-block-navigation .wp-block-navigation-item:first-child > .wp-block-navigation-item__content { padding-left: 0; }'
);
expect(css).toContain(
'.contact.wp-block-navigation .wp-block-navigation-item__content, .contact .wp-block-navigation .wp-block-navigation-item__content { text-decoration: underline; }'
);
});

it('replays nested source nav anchor color, decoration, and border rules through core/navigation wrappers', () => {
const css = buildNavigationAnchorCompatCss(`
.site-header .subnav a { color: #31251c; text-decoration: none; border-color: #31251c; }
.site-header .subnav a:hover { color: #8f5031; border-color: #8f5031; }
`);

expect(css).toContain(
'.site-header .subnav.wp-block-navigation .wp-block-navigation-item__content, .site-header .subnav .wp-block-navigation .wp-block-navigation-item__content { color: #31251c; text-decoration: none; border-color: #31251c; }'
);
expect(css).toContain(
'.site-header .subnav.wp-block-navigation .wp-block-navigation-item__content:hover, .site-header .subnav .wp-block-navigation .wp-block-navigation-item__content:hover { color: #8f5031; border-color: #8f5031; }'
);
expect(css).not.toContain('.site-header.wp-block-navigation .subnav');
});

it('rewrites exact quoted HTML image refs globally using the theme asset URL', () => {
const html =
'<main><img src="img/logo.png"><a href="img/logo.png">Logo</a><img src=\'img/logo.png\'><span data-copy=\'img/logo.png\' data-other="icons/logo.png"></span></main>';
Expand Down
1 change: 0 additions & 1 deletion packages/blocks-engine/src/theme/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ export { preserveDomStrategy } from './preserve-dom/strategy.js';
export {
collectHtmlImages,
collectSourceAssets,
buildNavigationAnchorCompatCss,
localizeCssImages,
rewriteHtmlImageSrcs,
WP_COMPAT_CSS,
Expand Down
85 changes: 1 addition & 84 deletions packages/blocks-engine/src/theme/source-assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,8 +409,7 @@ export function collectSourceAssets(
// dir. Then strip Google-Fonts @imports: WP_COMPAT_CSS contains no @imports so
// startsWith(WP_COMPAT_CSS) is preserved; Google imports only exist in cssParts.
const { parts: localizedParts, mediaAssets } = localizeCssImages(cssParts, dir);
const sourceCss = localizedParts.join('\n\n').replace(GOOGLE_IMPORT_RE, '');
const baseCss = WP_COMPAT_CSS + sourceCss + buildNavigationAnchorCompatCss(sourceCss);
const baseCss = (WP_COMPAT_CSS + localizedParts.join('\n\n')).replace(GOOGLE_IMPORT_RE, '');
// Append admin-bar accommodation LAST: scan the assembled source CSS for
// top-anchored fixed/sticky chrome and shift it below the WP admin bar for
// logged-in viewers (the bar otherwise overlays a `position:fixed; top:0`
Expand All @@ -430,85 +429,3 @@ export function collectSourceAssets(
imgRewritesByPage,
};
}

export function buildNavigationAnchorCompatCss(sourceCss: string): string {
const rules: string[] = [];
for (const match of sourceCss.matchAll(/([^{}@][^{}]*)\{([^{}]*)\}/g)) {
const body = match[2]?.trim();
if (!body) continue;

const mappedSelectors = splitSelectorList(match[1] ?? '').flatMap(mapNavigationAnchorSelector);
if (mappedSelectors.length === 0) continue;

rules.push(`${Array.from(new Set(mappedSelectors)).join(', ')} { ${body} }`);
}

if (rules.length === 0) return '';

return `\n\n/* wp-compat: replay source nav anchor selectors against core/navigation wrapper markup */\n${rules.join('\n')}`;
}

function splitSelectorList(selectorList: string): string[] {
const selectors: string[] = [];
let current = '';
let depth = 0;

for (const char of selectorList) {
if (char === '(' || char === '[') depth += 1;
if (char === ')' || char === ']') depth = Math.max(0, depth - 1);
if (char === ',' && depth === 0) {
selectors.push(current.trim());
current = '';
continue;
}
current += char;
}

if (current.trim()) selectors.push(current.trim());
return selectors;
}

function mapNavigationAnchorSelector(selector: string): string[] {
const anchorMatch = /(^|[\s>+~])a(?=$|[\s:.#\[])/.exec(selector);
if (!anchorMatch) return [];

const anchorStart = anchorMatch.index + (anchorMatch[1] ?? '').length;
const prefix = selector.slice(0, anchorStart);
if (!/[.#\[]/.test(prefix)) return [];

const mapped = selector
.replace(/(\s*[>+~]?\s*)a:first-child\b/g, '$1.wp-block-navigation-item:first-child > .wp-block-navigation-item__content')
.replace(/(\s*[>+~]?\s*)a:last-child\b/g, '$1.wp-block-navigation-item:last-child > .wp-block-navigation-item__content')
.replace(/(\s*[>+~]?\s*)a:nth-child\(([^)]*)\)/g, '$1.wp-block-navigation-item:nth-child($2) > .wp-block-navigation-item__content')
.replace(/(\s*[>+~]?\s*)a(?![A-Za-z0-9_-])/g, '$1.wp-block-navigation-item__content');

const directWrapper = addNavigationClassToLastPrefixCompound(mapped, anchorStart);
const descendantWrapper = insertNavigationDescendantWrapper(mapped, prefix);
return Array.from(new Set([directWrapper, descendantWrapper].filter((value): value is string => Boolean(value))));
}

function addNavigationClassToLastPrefixCompound(selector: string, anchorStart: number): string | undefined {
const prefix = selector.slice(0, anchorStart);
const match = /([^\s>+~]+)(\s*[>+~]?\s*)$/.exec(prefix);
if (!match) return undefined;

const compound = match[1] ?? '';
if (compound.includes('.wp-block-navigation')) return selector;

const insertAt = compound.search(/:{1,2}/);
const mappedCompound = insertAt >= 0
? `${compound.slice(0, insertAt)}.wp-block-navigation${compound.slice(insertAt)}`
: `${compound}.wp-block-navigation`;
const mappedPrefix = `${prefix.slice(0, match.index)}${mappedCompound}${match[2] ?? ''}`;
return `${mappedPrefix}${selector.slice(anchorStart)}`;
}

function insertNavigationDescendantWrapper(selector: string, prefix: string): string | undefined {
const parentPrefix = prefix.replace(/[\s>+~]+$/, '').trimEnd();
if (!parentPrefix || parentPrefix.includes('.wp-block-navigation')) return undefined;

const tail = selector.slice(prefix.length).replace(/^[\s>+~]+/, '');
if (!tail) return undefined;

return `${parentPrefix} .wp-block-navigation ${tail}`;
}
Loading