first commit

This commit is contained in:
Matt Kane
2026-04-01 10:44:22 +01:00
commit 43fcb9a131
1789 changed files with 395041 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,142 @@
/**
* Transformers for WordPress embed blocks
*/
import type { BlockTransformer } from "../types.js";
import { attrString } from "../types.js";
// Regex patterns for embed parsing
const IFRAME_SRC_PATTERN = /<iframe[^>]+src=["']([^"']+)["']/i;
const VIDEO_SRC_PATTERN = /<video[^>]+src=["']([^"']+)["']/i;
const VIDEO_SOURCE_PATTERN = /<source[^>]+src=["']([^"']+)["']/i;
const AUDIO_SRC_PATTERN = /<audio[^>]+src=["']([^"']+)["']/i;
const AUDIO_SOURCE_PATTERN = /<source[^>]+src=["']([^"']+)["']/i;
/**
* core/embed and variants → embed block
*/
export const embed: BlockTransformer = (block, _options, context) => {
const url = attrString(block.attrs, "url");
const providerSlug = attrString(block.attrs, "providerNameSlug");
// Extract iframe src if present
const iframeMatch = block.innerHTML.match(IFRAME_SRC_PATTERN);
const iframeSrc = iframeMatch?.[1];
return [
{
_type: "embed",
_key: context.generateKey(),
url: url || iframeSrc || "",
provider: providerSlug || detectProvider(url || iframeSrc || ""),
html: block.innerHTML.trim() || undefined,
},
];
};
/**
* core-embed/youtube → embed block
*/
export const youtube: BlockTransformer = (block, options, context) => {
return embed(block, options, context);
};
/**
* core-embed/twitter → embed block
*/
export const twitter: BlockTransformer = (block, options, context) => {
return embed(block, options, context);
};
/**
* core-embed/vimeo → embed block
*/
export const vimeo: BlockTransformer = (block, options, context) => {
return embed(block, options, context);
};
/**
* core/video → embed block (self-hosted video)
*/
export const video: BlockTransformer = (block, _options, context) => {
const src = attrString(block.attrs, "src");
// Extract from video tag if not in attrs
const videoMatch = block.innerHTML.match(VIDEO_SRC_PATTERN);
const sourceMatch = block.innerHTML.match(VIDEO_SOURCE_PATTERN);
const videoSrc = src || videoMatch?.[1] || sourceMatch?.[1];
return [
{
_type: "embed",
_key: context.generateKey(),
url: videoSrc || "",
provider: "video",
html: block.innerHTML.trim() || undefined,
},
];
};
/**
* core/audio → embed block (self-hosted audio)
*/
export const audio: BlockTransformer = (block, _options, context) => {
const src = attrString(block.attrs, "src");
// Extract from audio tag if not in attrs
const audioMatch = block.innerHTML.match(AUDIO_SRC_PATTERN);
const sourceMatch = block.innerHTML.match(AUDIO_SOURCE_PATTERN);
const audioSrc = src || audioMatch?.[1] || sourceMatch?.[1];
return [
{
_type: "embed",
_key: context.generateKey(),
url: audioSrc || "",
provider: "audio",
html: block.innerHTML.trim() || undefined,
},
];
};
/**
* Detect embed provider from URL
*/
function detectProvider(url: string): string | undefined {
if (!url) return undefined;
const urlLower = url.toLowerCase();
if (urlLower.includes("youtube.com") || urlLower.includes("youtu.be")) {
return "youtube";
}
if (urlLower.includes("vimeo.com")) {
return "vimeo";
}
if (urlLower.includes("twitter.com") || urlLower.includes("x.com")) {
return "twitter";
}
if (urlLower.includes("instagram.com")) {
return "instagram";
}
if (urlLower.includes("facebook.com")) {
return "facebook";
}
if (urlLower.includes("tiktok.com")) {
return "tiktok";
}
if (urlLower.includes("spotify.com")) {
return "spotify";
}
if (urlLower.includes("soundcloud.com")) {
return "soundcloud";
}
if (urlLower.includes("codepen.io")) {
return "codepen";
}
if (urlLower.includes("gist.github.com")) {
return "gist";
}
return undefined;
}

View File

@@ -0,0 +1,115 @@
/**
* Block transformers registry
*/
import type { BlockTransformer, PortableTextBlock } from "../types.js";
import * as core from "./core.js";
import * as embed from "./embed.js";
/**
* Default block transformers for core WordPress blocks
*/
export const defaultTransformers: Record<string, BlockTransformer> = {
// Text blocks
"core/paragraph": core.paragraph,
"core/heading": core.heading,
"core/list": core.list,
"core/quote": core.quote,
"core/code": core.code,
"core/preformatted": core.preformatted,
"core/pullquote": core.pullquote,
"core/verse": core.verse,
// Media blocks
"core/image": core.image,
"core/gallery": core.gallery,
"core/file": core.file,
"core/media-text": core.mediaText,
"core/cover": core.cover,
// Layout blocks
"core/columns": core.columns,
"core/group": core.group,
"core/separator": core.separator,
"core/spacer": core.separator,
"core/table": core.table,
"core/buttons": core.buttons,
"core/button": core.button,
// Structural blocks
"core/more": core.more,
"core/nextpage": core.nextpage,
// Pass-through blocks (preserve as HTML)
"core/html": core.html,
"core/shortcode": core.shortcode,
// Embed blocks
"core/embed": embed.embed,
"core/video": embed.video,
"core/audio": embed.audio,
// Legacy embed block names (WP < 5.6)
"core-embed/youtube": embed.youtube,
"core-embed/twitter": embed.twitter,
"core-embed/vimeo": embed.vimeo,
"core-embed/facebook": embed.embed,
"core-embed/instagram": embed.embed,
"core-embed/soundcloud": embed.embed,
"core-embed/spotify": embed.embed,
};
/**
* Fallback transformer for unknown blocks
* Stores the original HTML for manual review
*/
export const fallbackTransformer: BlockTransformer = (
block,
_options,
context,
): PortableTextBlock[] => {
// Skip completely empty blocks
if (!block.innerHTML.trim() && block.innerBlocks.length === 0) {
return [];
}
// If it has inner blocks, try to transform those
if (block.innerBlocks.length > 0) {
return context.transformBlocks(block.innerBlocks);
}
// Store as HTML fallback
return [
{
_type: "htmlBlock",
_key: context.generateKey(),
html: block.innerHTML,
originalBlockName: block.blockName,
originalAttrs: Object.keys(block.attrs).length > 0 ? block.attrs : undefined,
},
];
};
/**
* Get transformer for a block
*/
export function getTransformer(
blockName: string | null,
customTransformers?: Record<string, BlockTransformer>,
): BlockTransformer {
if (!blockName) {
return fallbackTransformer;
}
// Check custom transformers first
if (customTransformers?.[blockName]) {
return customTransformers[blockName];
}
// Check default transformers
if (defaultTransformers[blockName]) {
return defaultTransformers[blockName];
}
return fallbackTransformer;
}