A calm place to write long-form, and publish it to the open social web. skypress.blog/
0

Configure Feed

Select the types of activity you want to include in your feed.

at trunk 5.2 kB View raw
1/** 2 * Make `core/embed` resolve previews without a WordPress backend. `core/embed` 3 * fetches `/oembed/1.0/proxy?url=…` via `@wordpress/api-fetch`; we register a 4 * middleware that answers that path from our own embed registry: 5 * - atproto → a real card (CORS-friendly AppView), reusing the reader's 6 * `fetchAtprotoCard` + `renderEmbedCard` so editor and reader match exactly; 7 * - youtube/vimeo → a placeholder (their oEmbed isn't browser-CORS-reachable); 8 * - anything else → pass through (the URL stays a plain link). 9 */ 10import apiFetch from '@wordpress/api-fetch'; 11import { detectEmbed } from '../embeds/registry'; 12import { fetchAtprotoCard, type FetchLike } from '../embeds/resolve'; 13import { renderEmbedCard } from '../embeds/card'; 14 15interface EmbedPreview { 16 type: 'rich'; 17 version: '1.0'; 18 provider_name: string; 19 html: string; 20} 21 22const PROVIDER_LABEL: Record< 'youtube' | 'vimeo', string > = { 23 youtube: 'YouTube', 24 vimeo: 'Vimeo', 25}; 26 27/** 28 * Self-contained card styles for the editor preview. `core/embed` renders the 29 * preview html inside a sandboxed iframe (`<SandBox>`) that has no access to the 30 * editor's stylesheet OR the site's theme tokens, so these must be inlined with 31 * the html and use literal colors (not `var(--token)`). Kept roughly in sync with 32 * `src/styles/embeds.css` — the reader uses that external stylesheet (themed), 33 * this is only the in-editor preview approximation. 34 */ 35const PREVIEW_STYLES = 36 'body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;color:#1a1a1a}' + 37 '.skypress-embed{margin:0;border:1px solid #e6e3dd;border-radius:10px;overflow:hidden;background:#faf9f6}' + 38 '.skypress-embed--atproto .skypress-embed__link{display:block;padding:1rem 1.2rem;text-decoration:none;color:#1a1a1a}' + 39 '.skypress-embed__head{display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem}' + 40 '.skypress-embed__avatar{width:32px;height:32px;border-radius:50%;object-fit:cover}' + 41 '.skypress-embed__author{font-weight:600}' + 42 '.skypress-embed__handle{color:#6b6b6b;font-size:.92rem}' + 43 '.skypress-embed__text{display:block;white-space:pre-wrap;line-height:1.5}' + 44 '.skypress-embed__media{display:grid;gap:.4rem;margin-top:.75rem}' + 45 '.skypress-embed__image{width:100%;border-radius:8px}' + 46 '.skypress-embed__footer{display:block;margin-top:.75rem;font-size:.88rem;color:#6b6b6b}' + 47 '.skypress-embed--placeholder .skypress-embed__text{padding:1rem 1.2rem;color:#6b6b6b}'; 48 49/** Prepend the inline preview styles so the card renders styled inside the SandBox iframe. */ 50function withPreviewStyles( html: string ): string { 51 return `<style>${ PREVIEW_STYLES }</style>${ html }`; 52} 53 54function placeholder( label: string ): string { 55 return ( 56 `<figure class="wp-block-embed skypress-embed skypress-embed--placeholder">` + 57 `<span class="skypress-embed__text">▶ ${ label } — renders on your published page</span>` + 58 `</figure>` 59 ); 60} 61 62/** 63 * Resolve a candidate embed URL into an oEmbed-shaped preview for `core/embed`. 64 * 65 * @param url - The URL `core/embed` is trying to embed. 66 * @param fetchImpl - Fetch used for atproto card lookups (injectable for tests). 67 * @returns A rich oEmbed-shaped preview, or `null` when the URL is not embeddable. 68 */ 69export async function buildEmbedPreview( 70 url: string, 71 // Raw `fetch` (not `safeFetch`) is correct here: the editor is browser-only and 72 // runs post-OAuth against the fixed public AppView host, so there's no SSRF surface. 73 fetchImpl: FetchLike = ( u ) => fetch( u ) 74): Promise< EmbedPreview | null > { 75 const match = detectEmbed( url ); 76 if ( ! match ) { 77 return null; 78 } 79 if ( match.kind === 'atproto' ) { 80 const card = await fetchAtprotoCard( match, fetchImpl ); 81 if ( ! card ) { 82 return null; 83 } 84 return { type: 'rich', version: '1.0', provider_name: 'Bluesky', html: withPreviewStyles( renderEmbedCard( card ) ) }; 85 } 86 return { 87 type: 'rich', 88 version: '1.0', 89 provider_name: PROVIDER_LABEL[ match.kind ], 90 html: withPreviewStyles( placeholder( PROVIDER_LABEL[ match.kind ] ) ), 91 }; 92} 93 94/** Extract the `url` query arg from the embed-proxy apiFetch path. */ 95function proxyUrl( path?: string ): string | null { 96 if ( ! path || ! path.includes( '/oembed/1.0/proxy' ) ) { 97 return null; 98 } 99 const q = path.indexOf( '?' ); 100 if ( q === -1 ) { 101 return null; 102 } 103 return new URLSearchParams( path.slice( q + 1 ) ).get( 'url' ); 104} 105 106let registered = false; 107 108/** 109 * Register the `@wordpress/api-fetch` middleware that answers `core/embed`'s 110 * `/oembed/1.0/proxy` requests from our embed registry. Idempotent — registers once. 111 */ 112export function registerEmbedPreviewMiddleware(): void { 113 if ( registered ) { 114 return; 115 } 116 registered = true; 117 apiFetch.use( async ( options, next ) => { 118 const url = proxyUrl( ( options as { path?: string } ).path ); 119 if ( ! url ) { 120 return next( options ); 121 } 122 const preview = await buildEmbedPreview( url ); 123 // Resolve with our preview, or surface a 404 so core/embed shows "cannot 124 // embed" (a clean link) rather than spinning forever. 125 if ( preview ) { 126 return preview as unknown as ReturnType< typeof next >; 127 } 128 throw { code: 'oembed_invalid_url', message: 'Not an embeddable URL' }; 129 } ); 130}