Emdash source with visual editor image upload fix

Fixes:
1. media.ts: wrap placeholder generation in try-catch
2. toolbar.ts: check r.ok, display error message in popover
This commit is contained in:
2026-05-03 10:44:54 +07:00
parent 78f81bebb6
commit 2d1be52177
2352 changed files with 662964 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
---
/**
* Bluesky post embed component for Portable Text
*
* Wraps astro-embed's BlueskyPost component, extracting props from the PT block node.
* astro-portabletext passes `node` (not `value`) for custom type components.
*
* Accepts either `id` or `url` field for compatibility with different content sources.
*/
import { BlueskyPost } from "astro-embed";
import type { BlueskyBlock } from "../schemas.js";
interface Props {
node: BlueskyBlock & { url?: string };
}
const { node } = Astro.props;
// Support both 'id' (schema) and 'url' (admin editor) field names
const postId = node.id || node.url;
---
{postId && <BlueskyPost id={postId} />}

View File

@@ -0,0 +1,19 @@
---
/**
* GitHub Gist embed component for Portable Text
*
* Wraps astro-embed's Gist component, extracting props from the PT block node.
* astro-portabletext passes `node` (not `value`) for custom type components.
*/
import { Gist as AstroGist } from "astro-embed";
import type { GistBlock } from "../schemas.js";
interface Props {
node: GistBlock;
}
const { node } = Astro.props;
const { id, file } = node;
---
<AstroGist id={id} file={file} />

View File

@@ -0,0 +1,19 @@
---
/**
* Link preview (Open Graph) embed component for Portable Text
*
* Wraps astro-embed's LinkPreview component, extracting props from the PT block node.
* astro-portabletext passes `node` (not `value`) for custom type components.
*/
import { LinkPreview as AstroLinkPreview } from "astro-embed";
import type { LinkPreviewBlock } from "../schemas.js";
interface Props {
node: LinkPreviewBlock;
}
const { node } = Astro.props;
const { id, hideMedia } = node;
---
<AstroLinkPreview id={id} hideMedia={hideMedia} />

View File

@@ -0,0 +1,19 @@
---
/**
* Mastodon post embed component for Portable Text
*
* Wraps astro-embed's MastodonPost component, extracting props from the PT block node.
* astro-portabletext passes `node` (not `value`) for custom type components.
*/
import { MastodonPost } from "astro-embed";
import type { MastodonBlock } from "../schemas.js";
interface Props {
node: MastodonBlock;
}
const { node } = Astro.props;
const { id } = node;
---
<MastodonPost id={id} />

View File

@@ -0,0 +1,19 @@
---
/**
* Tweet embed component for Portable Text
*
* Wraps astro-embed's Tweet component, extracting props from the PT block node.
* astro-portabletext passes `node` (not `value`) for custom type components.
*/
import { Tweet as AstroTweet } from "astro-embed";
import type { TweetBlock } from "../schemas.js";
interface Props {
node: TweetBlock;
}
const { node } = Astro.props;
const { id, theme } = node;
---
<AstroTweet id={id} theme={theme} />

View File

@@ -0,0 +1,25 @@
---
/**
* Vimeo embed component for Portable Text
*
* Wraps astro-embed's Vimeo component, extracting props from the PT block node.
* astro-portabletext passes `node` (not `value`) for custom type components.
*/
import { Vimeo as AstroVimeo } from "astro-embed";
import type { VimeoBlock } from "../schemas.js";
interface Props {
node: VimeoBlock;
}
const { node } = Astro.props;
const { id, poster, posterQuality, params, playlabel } = node;
---
<AstroVimeo
id={id}
poster={poster}
posterQuality={posterQuality}
params={params}
playlabel={playlabel}
/>

View File

@@ -0,0 +1,26 @@
---
/**
* YouTube embed component for Portable Text
*
* Wraps astro-embed's YouTube component, extracting props from the PT block node.
* astro-portabletext passes `node` (not `value`) for custom type components.
*/
import { YouTube as AstroYouTube } from "astro-embed";
import type { YouTubeBlock } from "../schemas.js";
interface Props {
node: YouTubeBlock;
}
const { node } = Astro.props;
const { id, poster, posterQuality, params, playlabel, title } = node;
---
<AstroYouTube
id={id}
poster={poster}
posterQuality={posterQuality}
params={params}
playlabel={playlabel}
title={title}
/>

View File

@@ -0,0 +1,66 @@
/**
* Astro components for rendering embed blocks in Portable Text
*
* These components are automatically registered with PortableText when
* the embeds plugin is enabled. Manual wiring is no longer needed!
*
* The components are exported with lowercase names matching their block types
* for auto-registration, plus PascalCase aliases for direct usage.
*
* @example Direct usage (if you need to customize)
* ```astro
* ---
* import { YouTube } from "@emdash-cms/plugin-embeds/astro";
* ---
* <YouTube value={{ id: "dQw4w9WgXcQ", _type: "youtube", _key: "1" }} />
* ```
*/
import BlueskyComponent from "./Bluesky.astro";
import GistComponent from "./Gist.astro";
import LinkPreviewComponent from "./LinkPreview.astro";
import MastodonComponent from "./Mastodon.astro";
import TweetComponent from "./Tweet.astro";
import VimeoComponent from "./Vimeo.astro";
// Import all components
import YouTubeComponent from "./YouTube.astro";
// Export with lowercase names (for auto-registration via virtual module)
// These names MUST match the block type names in EMBED_BLOCK_TYPES
export {
YouTubeComponent as youtube,
VimeoComponent as vimeo,
TweetComponent as tweet,
BlueskyComponent as bluesky,
MastodonComponent as mastodon,
LinkPreviewComponent as linkPreview,
GistComponent as gist,
};
// Also export with PascalCase for direct usage
export {
YouTubeComponent as YouTube,
VimeoComponent as Vimeo,
TweetComponent as Tweet,
BlueskyComponent as Bluesky,
MastodonComponent as Mastodon,
LinkPreviewComponent as LinkPreview,
GistComponent as Gist,
};
/**
* All embed components keyed by their Portable Text block type.
* Exported as `blockComponents` for auto-registration via the virtual module,
* and as `embedComponents` for direct usage.
*/
export const blockComponents = {
youtube: YouTubeComponent,
vimeo: VimeoComponent,
tweet: TweetComponent,
bluesky: BlueskyComponent,
mastodon: MastodonComponent,
linkPreview: LinkPreviewComponent,
gist: GistComponent,
} as const;
export { blockComponents as embedComponents };