Add action buttons to flashNotification

Used by upgrade notification
This commit is contained in:
Zef Hemel
2026-03-10 16:07:25 +01:00
parent 73d657d652
commit ec40aa0838
8 changed files with 130 additions and 12 deletions
+5 -1
View File
@@ -822,8 +822,12 @@ export class Client {
) {
this.versionMismatchNotified = true;
this.ui.flashNotification(
"A new version of SilverBullet is available. Reload to update.",
"A new version of SilverBullet client is available. A reload or two is required to update.",
"warning",
{
timeout: 0,
actions: [{ name: "Reload", run: () => location.reload() }],
},
);
}
break;
+32 -1
View File
@@ -18,6 +18,7 @@ export function TopBar({
isLoading,
notifications,
onRename,
onDismissNotification,
actionButtons,
darkMode,
vimMode,
@@ -40,6 +41,7 @@ export function TopBar({
progressPercentage?: number;
progressType?: string;
onRename: (newName?: string) => Promise<void>;
onDismissNotification: (id: number) => void;
onClick: () => void;
actionButtons: ActionButton[];
lhs?: ComponentChildren;
@@ -97,7 +99,36 @@ export function TopBar({
key={notification.id}
className={`sb-notification-${notification.type}`}
>
{notification.message}
<span className="sb-notification-message">
{notification.message}
</span>
{notification.actions && notification.actions.length > 0 && (
<span className="sb-notification-actions">
{notification.actions.map((action, i) => (
<button
key={i}
className="sb-button"
onClick={(e) => {
e.stopPropagation();
action.run();
}}
>
{action.name}
</button>
))}
</span>
)}
{notification.persistent && (
<button
className="sb-notification-dismiss"
onClick={(e) => {
e.stopPropagation();
onDismissNotification(notification.id);
}}
>
&times;
</button>
)}
</div>
))}
</div>
+29 -7
View File
@@ -20,6 +20,7 @@ import { Panel } from "./components/panel.tsx";
import { safeRun } from "@silverbulletmd/silverbullet/lib/async";
import type {
FilterOption,
NotificationAction,
NotificationType,
} from "@silverbulletmd/silverbullet/type/client";
import { notificationDismissTimeouts } from "@silverbulletmd/silverbullet/type/client";
@@ -86,8 +87,26 @@ export class MainUI {
viewDispatch: (action: Action) => void = () => {};
flashNotification(message: string, type: NotificationType = "info") {
flashNotification(
message: string,
type: NotificationType = "info",
options?: {
timeout?: number;
actions?: NotificationAction[];
},
) {
const id = Math.floor(Math.random() * 1000000);
const dismiss = () => {
this.viewDispatch({ type: "dismiss-notification", id });
};
const persistent = options?.timeout === 0;
const actions = options?.actions?.map((action) => ({
name: action.name,
run: () => {
action.run();
dismiss();
},
}));
this.viewDispatch({
type: "show-notification",
notification: {
@@ -95,14 +114,14 @@ export class MainUI {
type,
message,
date: new Date(),
actions,
persistent,
},
});
setTimeout(() => {
this.viewDispatch({
type: "dismiss-notification",
id: id,
});
}, notificationDismissTimeouts[type]);
if (!persistent) {
const timeout = options?.timeout ?? notificationDismissTimeouts[type];
setTimeout(dismiss, timeout);
}
}
showProgress(progressPercentage?: number, progressType?: "sync" | "index") {
@@ -386,6 +405,9 @@ export class MainUI {
!viewState.current ? "" : getNameFromPath(viewState.current.path)
}
notifications={viewState.notifications}
onDismissNotification={(id) => {
dispatch({ type: "dismiss-notification", id });
}}
isOnline={viewState.isOnline}
unsavedChanges={viewState.unsavedChanges}
isLoading={viewState.isLoading}
+3 -1
View File
@@ -22,6 +22,7 @@ import { getCM as vimGetCm, Vim } from "@replit/codemirror-vim";
import type { SysCallMapping } from "../system.ts";
import type {
FilterOption,
NotificationAction,
NotificationType,
UploadFile,
} from "@silverbulletmd/silverbullet/type/client";
@@ -289,8 +290,9 @@ export function editorSyscalls(client: Client): SysCallMapping {
_ctx,
message: string,
type: NotificationType = "info",
options?: { timeout?: number; actions?: NotificationAction[] },
) => {
client.ui.flashNotification(message, type);
client.ui.flashNotification(message, type, options);
},
"editor.filterBox": (
_ctx,
+29 -1
View File
@@ -34,9 +34,37 @@
z-index: 100;
> div {
padding: 3px;
padding: 3px 8px;
margin-bottom: 3px;
border-radius: 5px;
display: flex;
align-items: center;
gap: 8px;
}
.sb-notification-actions {
display: flex;
gap: 4px;
flex-shrink: 0;
.sb-button {
font-size: 0.85em;
padding: 1px 6px;
white-space: nowrap;
}
}
.sb-notification-dismiss {
background: none;
border: none;
color: inherit;
cursor: pointer;
font-size: 1.1em;
padding: 0 2px;
opacity: 0.7;
&:hover {
opacity: 1;
}
}
}
}
+7
View File
@@ -19,11 +19,18 @@ export const notificationDismissTimeouts: Record<NotificationType, number> = {
warning: 8000,
};
export type NotificationAction = {
name: string;
run: () => void;
};
export type Notification = {
id: number;
message: string;
type: NotificationType;
date: Date;
actions?: NotificationAction[];
persistent?: boolean;
};
export type PanelMode = number;
+24 -1
View File
@@ -238,12 +238,35 @@ Example:
editor.hidePanel("rhs")
```
### editor.flashNotification(message, type)
### editor.flashNotification(message, type, options?)
Shows a flash notification.
Parameters:
- `message`: The message to display
- `type`: Notification type — `"info"` (default), `"error"`, or `"warning"`
- `options` (optional): Table with additional options:
- `timeout`: Dismiss timeout in milliseconds. Use `0` for persistent notifications (default: 4000/5000/8000 depending on type)
- `actions`: List of action buttons, each with:
- `name`: Button label
- `run`: Function to call when clicked (auto-dismisses the notification)
Example:
```lua
-- Simple notification
editor.flashNotification("Operation completed", "info")
-- Persistent notification with action button
editor.flashNotification("Update available", "warning", {
timeout = 0,
actions = {
{
name = "Reload",
run = function()
editor.reloadUI()
end
}
}
})
```
### editor.downloadFile(filename, dataUrl)
+1
View File
@@ -4,6 +4,7 @@ An attempt at documenting the changes/new features introduced in each release.
Whenever a commit is pushed to the `main` branch, within ~5 minutes, it will be released as a docker image with the `:v2` tag, and a binary in the [edge release](https://github.com/silverbulletmd/silverbullet/releases/tag/edge). If you want to live on the bleeding edge of SilverBullet goodness (or regression) this is where to do it.
* **Technical: Deno → Node.js migration**: The TypeScript/client codebase has been migrated from Deno to Node.js, now using vitest for tests. This _should_ purely be a tooling change, but may inadvertently have lead to regressions. Please report if you find anything.
* `editor.flashNotification` now supports an optional third argument with `timeout` (use `0` for persistent notifications) and `actions` (buttons with callbacks).
* [[Outlines]] commands have been thoroughly reworked. Should now be more robust and better tested outline move/indent operations. New features:
* Now also works with numbered items (and renumbers them)
* Now works with headers (moves around entire sections)