Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/post-save-publish-notice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystatic/core': minor
---

Add `ui.publishNotice` and `ui.onCommit` configuration options to display a post-save publishing notice or trigger a custom callback when changes are committed to a remote branch.
28 changes: 28 additions & 0 deletions packages/keystatic/src/app/updating.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { expect, test } from '@jest/globals';
import { formatPublishNoticeMessage } from './updating';

test('formatPublishNoticeMessage interpolates branch, sha, and slug placeholders', () => {
const formatted = formatPublishNoticeMessage(
'Saved to {branch} ({sha}) for {slug} — deployment takes ~2 minutes.',
{
branch: 'main',
sha: 'abc1234',
slug: 'hello-world',
}
);
expect(formatted).toBe(
'Saved to main (abc1234) for hello-world — deployment takes ~2 minutes.'
);
});

test('formatPublishNoticeMessage leaves static messages unchanged and handles omitted optional fields', () => {
const formatted = formatPublishNoticeMessage(
'Changes committed to {branch}. Site rebuilds automatically ({slug}).',
{
branch: 'staging',
}
);
expect(formatted).toBe(
'Changes committed to staging. Site rebuilds automatically ().'
);
});
26 changes: 26 additions & 0 deletions packages/keystatic/src/app/updating.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { toastQueue } from '@keystar/ui/toast';
import { gql } from '@ts-gql/tag/no-transform';
import { assert } from 'emery';
import { useContext, useState } from 'react';
Expand Down Expand Up @@ -305,6 +306,21 @@ export function useUpsertItem(args: {
const target = result.data?.createCommitOnBranch?.ref?.target;
if (target) {
setState({ kind: 'updated' });
const publishMessage = args.config.ui?.publishNotice?.message;
if (publishMessage) {
toastQueue.positive(
formatPublishNoticeMessage(publishMessage, {
branch: branch.branchName,
sha: target.oid,
slug: args.slug?.value,
})
);
}
args.config.ui?.onCommit?.({
branch: branch.branchName,
sha: target.oid,
slug: args.slug?.value,
});
return true;
}
throw new Error('Failed to update');
Expand Down Expand Up @@ -492,3 +508,13 @@ const FetchRef = gql`
}
}
` as import('../../__generated__/ts-gql/FetchRef').type;

export function formatPublishNoticeMessage(
message: string,
info: { branch: string; sha?: string; slug?: string }
): string {
return message
.replace(/\{branch\}/g, info.branch)
.replace(/\{sha\}/g, info.sha ?? '')
.replace(/\{slug\}/g, info.slug ?? '');
}
12 changes: 12 additions & 0 deletions packages/keystatic/src/config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ type BrandMark = (props: {
colorScheme: Exclude<ColorScheme, 'auto'>; // we resolve "auto" to "light" or "dark" on the client
}) => ReactElement;
export const NAVIGATION_DIVIDER_KEY = '---';
export type CommitInfo = {
branch: string;
sha: string;
slug?: string;
};

export type PublishNoticeConfig = {
message: string;
};

type UserInterface<Collections, Singletons> = {
brand?: {
mark?: BrandMark;
Expand All @@ -70,6 +80,8 @@ type UserInterface<Collections, Singletons> = {
| (keyof Singletons & string)
| typeof NAVIGATION_DIVIDER_KEY
>;
publishNotice?: PublishNoticeConfig;
onCommit?: (info: CommitInfo) => void;
};

type Navigation<K> = K[] | { [section: string]: K[] };
Expand Down