github emdash-cms/emdash emdash@1.1.0

latest releases: @emdash-cms/plugin-test@0.2.7, @emdash-cms/sandbox-workerd@0.9.2, @emdash-cms/plugin-embeds@0.1.54...
5 hours ago

Minor Changes

  • #3680 75de9a4 Thanks @khoinguyenpham04! - Adds a publishing calendar to the admin. It shows published entries on their publication date, and scheduled entries and scheduled updates on their scheduled date, across every visible collection and locale. Contributors and higher roles open it from Calendar in the sidebar, the command palette, or the dashboard's Scheduled count.

    Month shows a grid of days and Agenda lists entries by day; phones and other narrow screens open the agenda and show the month as a date picker. Both views place entries in the site's time zone, show browser-zone times when the viewer's zone differs, mark schedules that missed their time as Overdue, filter by collection, locale, and state, and keep the view, month, filters, and open entry in the URL. When a month's grid holds more than 1,000 entries, the calendar shows the first 1,000 and marks the days it didn't load.

    Selecting an entry opens a side panel with its details, a link to the editor, and a preview or live link. Users who can publish the entry can also reschedule it, remove its schedule, or publish an overdue entry immediately. Ctrl-click or Cmd-click opens the entry in the editor instead.

  • #3566 3c70ca5 Thanks @stephanedemotte! - Adds locale and translationOf to the content:beforeSave and content:afterSave hook events, for trusted and sandboxed plugins, so a hook can tell a new entry from a new translation of an existing one.

    locale is the locale the entry is saved in: the resolved requested locale (or the default locale) on a create, and the stored entry's locale on an update. translationOf is the ID of the source entry when a create comes from the translation flow, and is absent otherwise. Both fields are optional; existing hooks are unaffected.

  • #3687 76b06d4 Thanks @khoinguyenpham04! - Adds CSS and JavaScript to HTML blocks, and changes how new HTML blocks render on the site.

    Previously, every HTML block rendered inline, and the site sanitized its HTML, removing scripts and styles. HTML blocks created in the admin editor, or with /html in visual editing, now render in a sandboxed iframe that runs their HTML, CSS and JavaScript. Anyone who can edit content (Contributor and up, and sandboxed plugins with content:write) can add JavaScript that runs for visitors once the entry is published. The iframe can't read the site's cookies, storage or pages, and the site's styles don't apply inside it. Relative links in it resolve from the site's root. After a visitor clicks inside the iframe, it can open other pages in the visitor's tab or a new one. There is no site-wide setting for this; to render a block the previous way, choose Inline in its menu.

    Existing HTML blocks, and blocks created through imports, REST or MCP, keep rendering inline unless they set isolated: true.

    In the editor, an HTML block has HTML, CSS and JS tabs with a code editor, and a Preview tab that shows the block as the site renders it. A saved block that runs JavaScript waits for Run preview. The admin editor no longer nests HTML blocks in quotes, lists or table cells, where saving dropped them. HTML blocks pasted from another website or browser tab render inline.

    New fields

    htmlBlock gains optional css and js strings and an isolated flag. They're written only when set, so existing content is unchanged when it's opened and saved.

    What should I do?

    • If your site replaces the htmlBlock renderer, pass blocks with isolated: true to HtmlBlock from emdash/ui, or render them in an iframe whose sandbox omits allow-same-origin. Otherwise isolated blocks render without their CSS and JavaScript.
    • If you render Portable Text outside EmDash's components, handle isolated blocks the same way.
    • With Astro's content security policy turned on, the browser blocks the styles and scripts inside isolated blocks, so they render without their CSS, JavaScript or automatic height.
  • #3688 a880323 Thanks @khoinguyenpham04! - Adds an iframe block for embedding pages from other sites. In the editor, type /iframe and paste an embed code or a link into the Code tab; YouTube and Vimeo links become their players, and the Preview tab shows the embedded page. When an entry opens, a saved block waits for Load preview. Iframe blocks pasted from another website or browser tab arrive empty. On the site, Iframe from emdash/ui renders the iframe block as a responsive, lazy-loading iframe.

    The iframe is sandboxed and sends a strict-origin-when-cross-origin referrer. Only https sources render, pages on the site's own host lose same-origin access, and only the permissions video and map players need (such as autoplay, fullscreen and picture-in-picture) reach the embedded page.

    The admin's content security policy now allows https frames (frame-src 'self' https:), so previews can load embedded pages, including frames inside HTML block previews.

    What should I do?

    • If a plugin already defines an iframe block, the editor keeps using the plugin's block and doesn't offer the built-in one. On the site, the plugin's renderer still wins; a plugin without one gets Iframe for blocks that have only the built-in fields. In TypeScript, narrowing PortableTextBlock on _type === "iframe" now gives PortableTextIframeBlock | PortableTextUnknownBlock, so reading the plugin's own fields needs a check such as "theme" in block.
    • With Astro's content security policy turned on, allow the embedded hosts in frame-src. Iframe blocks also lose their custom size under that policy.
  • #3254 5de3bdc Thanks @danielmlr! - Adds a microsoft() login provider, so editors can sign in to the admin with a Microsoft Entra ID work or school account next to passkeys and the other providers.

    import { microsoft } from "emdash/auth/providers/microsoft";
    
    emdash({ authProviders: [microsoft()] });

    The provider reads EMDASH_OAUTH_MICROSOFT_CLIENT_ID, EMDASH_OAUTH_MICROSOFT_CLIENT_SECRET, and EMDASH_OAUTH_MICROSOFT_TENANT_ID (or the unprefixed names) and stays unconfigured until all three are set. The tenant is a directory (tenant) ID, or common, organizations, or consumers. With a directory ID, an account that signs in through that directory counts as verified when its address is in the domain of its sign-in name, or when the optional xms_edov claim confirms the address's domain, so it can link to an existing user, accept an invite, sign up through an allowed domain, and create the first admin account. Accounts that sign in through another directory or identity provider, such as guests, and sign-ins through common, organizations, or consumers do not count as verified. microsoft({ emailVerified }) overrides this either way.

  • #3632 728790b Thanks @swissky! - Adds a WebMcpSearch component (emdash/ui/webmcp-search) that lets AI agents in a visitor's browser search your published content. In browsers that support WebMCP, it registers a read-only search_site tool backed by the public search API and returns titles, absolute URLs, and plain-text excerpts. It accepts the same collections, locale, limit, and routeMap props as LiveSearch and does nothing in other browsers.

Patch Changes

  • #3526 2d84db5 Thanks @danielmlr! - Fixes byline translations dropping the source byline's linked user, which left the user's entries in the translation's locale without an author credit. Translations created with the admin's Translate action now keep the source's user, so those entries show the translated byline.

    When translationOf is set and the call omits userId, the byline_create MCP tool now links the source's user instead of creating an unlinked translation. Pass userId: null to keep the previous behavior.

    A user can have only one byline per locale. Creating a byline with a userId that already has a different byline in the target locale fails with CONFLICT naming that byline, instead of a server error. Translating a byline whose user already has a different byline in the target locale also fails with CONFLICT, where it previously created an unlinked translation.

    What should I do?

    Translations created before this release stay unlinked, and the admin's byline list marks them as unlinked. To restore author credits in a locale, open the translation in the admin and link the user.

    If Translate reports that the user is already linked to another byline in that locale, unlink that byline in the admin first, or create the translation with byline_create and userId: null.

  • #3711 2436119 Thanks @swissky! - Fixes the setup wizard failing with SITE_URL_REQUIRED on Cloudflare Workers since 0.40.1 when siteUrl, EMDASH_SITE_URL, or SITE_URL is not set, which blocked new sites created with the Deploy to Cloudflare button. On Cloudflare's network, setup now records https:// and the hostname it runs on as the origin for authentication emails, and passkeys created during setup only work on that hostname.

    • To use a custom domain, run setup on that domain or set EMDASH_SITE_URL first.
    • Node.js deployments still need a configured origin before production setup.
    • A Workers build served outside Cloudflare (wrangler dev, astro preview, or self-hosted workerd) on a reachable address should set siteUrl or EMDASH_SITE_URL before setup.
  • #3580 0cfa989 Thanks @emdashbot! - Adds image/jxl (JPEG XL) to the default media upload allowlist, so .jxl files can be uploaded without a field-specific MIME list. Upload routes now fall back to the filename extension when the browser reports an empty or generic MIME type, which is the common case for JPEG XL outside Safari.

  • #3652 2ef2d01 Thanks @danielmlr! - Speeds up the image endpoint EmDash installs (/_image by default): its requests no longer wait for the database setup check and runtime startup that delayed the first images on a fresh server instance, such as a new Cloudflare Worker isolate. Signed-in requests still get locals.user, but on D1 with session enabled their responses no longer set the D1 bookmark cookie, so an edge-cache route rule for /_image now also caches admin media thumbnails.

    On these requests locals.emdash holds only storage, plus db for a signed-in user; the page and admin helpers it carried before are not set. Playground requests are unchanged. While check or manual migration mode answers other requests with the 503 "Database migrations are required" response, image endpoint requests are still served.

  • #3662 02da2bd Thanks @khoinguyenpham04! - Fixes the MCP content_list tool suggesting created_at and updated_at for orderBy, which it rejects with a validation error. The tool description now lists only fields it accepts: createdAt, updatedAt, publishedAt, scheduledAt, slug, status, locale, and field slugs that are indexed or set as the collection's titleField or dateField.

  • #3579 ea3c927 Thanks @swissky! - Reduces database queries when rendering navigation menus with getMenu() and getMenuWithCacheHint(): a menu and its items now load in a single query, including when the menu falls back to another locale, saving at least one query per menu that isn't already served from the object cache. With an object cache configured, logged-out HTML page loads on Cloudflare D1 and Durable Object databases also skip one more query per request, and menus created or removed by seeding, WordPress import, or site transfer show up without waiting for the cache to expire.

  • #3662 02da2bd Thanks @khoinguyenpham04! - Fixes content lists skipping entries when paging with nextCursor while sorted by a field that can be empty, such as the publish date, scheduled date, title, slug, or a collection's dateField or titleField. This affected the admin content list for collections with more than 100 entries, including its default sort when the collection sets a dateField. It also affected GET /_emdash/api/content/{collection}, the MCP content_list tool, and plugin ctx.content.list calls that set orderBy.

    The order of entries is unchanged, and cursors issued before the upgrade are still accepted.

  • #3662 02da2bd Thanks @khoinguyenpham04! - Fixes getEmDashCollection skipping entries when paging with nextCursor while sorted by a field that can be empty, such as published_at, title, slug, or a custom date field, including lists filtered by taxonomy terms. Paging by a boolean field or by a system column such as version also returns every entry now. The order of entries is unchanged, and cursors issued before the upgrade are still accepted.

  • #3651 7583f9b Thanks @danielmlr! - Fixes sites using d1() or durableObjects() with session enabled, or hyperdrive(), reading the preview secret and IP salt from the database on every editor page view, preview link, and comment request. durableObjects() without session did the same on write requests, such as comment submissions and reactions. Both values are now read once per isolate, as with other database adapters, so rotating one by deleting its options row takes effect after a redeploy.

  • #3694 73304f4 Thanks @ascorbic! - Speeds up redirect matching on sites with many redirects. Public requests now load the enabled redirect rules in a single query and afterwards check one small row to see whether they changed, instead of reading the whole redirects table every 30 seconds in every Worker isolate. Redirect changes made in the admin, through the API, by automatic slug-change redirects, by seeding or by import take effect as before. A site that has just upgraded, or whose published redirect data is missing or damaged, keeps serving redirects from the redirects table while it rebuilds that data in the background.

  • #3692 2a0cb93 Thanks @ascorbic! - Fixes enabling a disabled redirect that closes a redirect loop. Enabling it from the admin, the API, or a plugin now fails with a loop validation error, as creating or editing the same redirect already did, and the database rejects it for writers that bypass the API. Previously such an enable succeeded and the loop was only flagged with an admin warning, so automation that toggled a loop-closing redirect on will now receive a validation error instead. Loops that already exist on a site are unchanged and can still be disabled.

  • #3693 d1b4402 Thanks @ascorbic! - Fixes redirect pattern precedence when several enabled pattern rules match the same path. The earliest-created rule now always wins. Previously the winner depended on database row order, which could change after a rule was edited or hit, particularly on PostgreSQL.

  • #3665 1d93e7c Thanks @danielmlr! - Fixes the settings page of a plugin installed from the registry or marketplace showing the plugin's internal ID, such as r_dsniqezhchh4zone, as its title instead of its name. The admin endpoint GET /_emdash/api/admin/plugins/:id returns these plugins with the same details as the plugin list instead of a 404.

  • #3625 0a5c604 Thanks @swissky! - Fixes partial seo and social settings updates through the MCP settings_update tool, POST /_emdash/api/settings, setSiteSettings(), and seeds applied with onConflict: "update" replacing the whole stored object. Sending one field, for example { seo: { googleVerification: "…" } }, used to remove the custom robots.txt, title separator, other verification code, and default social image that were not in the request. Fields you leave out now keep their stored values. To clear a text field inside seo or social, send it as an empty string, for example { social: { twitter: "" } }.

  • #3617 7d06f5d Thanks @enesismail! - Fixes publishing staged content after one of its fields is deleted. Existing drafts now publish against the current collection schema while their historical revision data remains available.

  • #3528 189c6b3 Thanks @danielmlr! - Fixes returning visitors seeing a stale page after a content publish until the next deploy. A cached page whose Astro.cache.set() hints carry no last-modified time no longer sends the build time as Last-Modified, so browsers download the page again instead of receiving a 304. The fix applies to pages that pass no hint and to pages that pass only tag hints, such as those from getSiteSettingsWithCacheHint() and getMenuWithCacheHint() or from a query that returned no entries. Pages whose hints carry a last-modified time, such as those from getEmDashEntry(), still revalidate against both the content and the build.

  • #3402 f925a89 Thanks @danielmlr! - Fixes taxonomy-filtered getEmDashCollection() listings sorted by published_at or created_at (the default) on D1 and SQLite reading up to the whole collection when the term has fewer than 200 entries or the listing filters by several terms. These listings now read only the term's entries. A single term with 200 or more entries is still read in date order and stops once the page is full.

  • #3608 a40b7ce Thanks @danielmlr! - Fixes image, feed and JSON responses for signed-in editors, such as admin media library thumbnails, waiting on the visual editing toolbar. Only HTML pages show the toolbar, so EmDash no longer renders it for other responses.

  • #3650 6940899 Thanks @swissky! - Fixes pages that answer a missing entry with Astro.rewrite("/404"): the visual editing toolbar (and the client toolbar script) now appears once instead of twice, and the 404 response is kept out of the route cache, so the URL starts working as soon as the entry is published.

  • #3622 0b9426e Thanks @swissky! - Fixes comment Turnstile verification ignoring EMDASH_TURNSTILE_SECRET_KEY and TURNSTILE_SECRET_KEY when they are set at runtime, for example with wrangler secret put or container environment variables. Comment submissions were accepted without a Turnstile check unless the key was also present when the site was built, and a key present at build time was written into the server bundle.

    On Node, the key must now be in the server's process environment at runtime. If you only set it in a .env file, load it when starting the server (for example node --env-file=.env ./dist/server/entry.mjs) or set it in your host's environment; otherwise comments are accepted without a Turnstile check. If your server build output was shared or stored, rotate a key that was present at build time.

  • Updated dependencies [75de9a4, 85ab50c, 3a00448, aa2f87e, 76b06d4, a880323, 5346dc8, 2288fa6, 90d71f9, 253b6f9, fc90e27, 5de3bdc, e811952, 7f4064c, 998ce63, fc019e4, 1942b3a]:

    • @emdash-cms/admin@1.1.0
    • @emdash-cms/blocks@1.1.0
    • @emdash-cms/auth@1.1.0
    • @emdash-cms/gutenberg-to-portable-text@1.1.0

Don't miss a new emdash release

NewReleases is sending notifications on new releases.