mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-23 01:07:12 +08:00
refactor(editor): add helper function for undo notification (#11903)
### What Changes - Refactors the `notify` function call with undo button. It is called `notifyWithUndo`, which can associate the visibility of the `NotificationCard` with the top undo item in history stack, such as undo by shortcut `Cmd+Z`. - change icon of the "Insert into page" button. Close [BS-3267](https://linear.app/affine-design/issue/BS-3267/frame和group的insert-into-page图标也更换一下)
This commit is contained in:
@@ -11,7 +11,7 @@ import {
|
||||
import { EMBED_CARD_HEIGHT } from '@blocksuite/affine-shared/consts';
|
||||
import { NotificationProvider } from '@blocksuite/affine-shared/services';
|
||||
import { matchModels } from '@blocksuite/affine-shared/utils';
|
||||
import { BlockStdScope, EditorLifeCycleExtension } from '@blocksuite/std';
|
||||
import { BlockStdScope } from '@blocksuite/std';
|
||||
import {
|
||||
type BlockModel,
|
||||
type BlockSnapshot,
|
||||
@@ -337,45 +337,12 @@ export function promptDocTitle(std: BlockStdScope, autofill?: string) {
|
||||
});
|
||||
}
|
||||
|
||||
export function notifyDocCreated(std: BlockStdScope, doc: Store) {
|
||||
const notification = std.getOptional(NotificationProvider);
|
||||
if (!notification) return;
|
||||
|
||||
const abortController = new AbortController();
|
||||
const clear = () => {
|
||||
doc.history.off('stack-item-added', addHandler);
|
||||
doc.history.off('stack-item-popped', popHandler);
|
||||
disposable.unsubscribe();
|
||||
};
|
||||
const closeNotify = () => {
|
||||
abortController.abort();
|
||||
clear();
|
||||
};
|
||||
|
||||
// edit or undo or switch doc, close notify toast
|
||||
const addHandler = doc.history.on('stack-item-added', closeNotify);
|
||||
const popHandler = doc.history.on('stack-item-popped', closeNotify);
|
||||
const disposable = std
|
||||
.get(EditorLifeCycleExtension)
|
||||
.slots.unmounted.subscribe(closeNotify);
|
||||
|
||||
notification.notify({
|
||||
export function notifyDocCreated(std: BlockStdScope) {
|
||||
std.getOptional(NotificationProvider)?.notifyWithUndoAction({
|
||||
title: 'Linked doc created',
|
||||
message: 'You can click undo to recovery block content',
|
||||
accent: 'info',
|
||||
duration: 10 * 1000,
|
||||
actions: [
|
||||
{
|
||||
key: 'undo',
|
||||
label: 'Undo',
|
||||
onClick: () => {
|
||||
doc.undo();
|
||||
clear();
|
||||
},
|
||||
},
|
||||
],
|
||||
abort: abortController.signal,
|
||||
onClose: clear,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -230,7 +230,7 @@ export const builtinToolbarConfig = {
|
||||
draftedModels,
|
||||
title
|
||||
);
|
||||
notifyDocCreated(std, store);
|
||||
notifyDocCreated(std);
|
||||
|
||||
track('DocCreated', {
|
||||
segment: 'doc',
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
SurfaceRefBlockSchema,
|
||||
} from '@blocksuite/affine-model';
|
||||
import {
|
||||
NotificationProvider,
|
||||
type ToolbarContext,
|
||||
type ToolbarModuleConfig,
|
||||
ToolbarModuleExtension,
|
||||
@@ -26,7 +27,11 @@ import {
|
||||
} from '@blocksuite/affine-shared/utils';
|
||||
import { mountFrameTitleEditor } from '@blocksuite/affine-widget-frame-title';
|
||||
import { Bound } from '@blocksuite/global/gfx';
|
||||
import { EditIcon, PageIcon, UngroupIcon } from '@blocksuite/icons/lit';
|
||||
import {
|
||||
EditIcon,
|
||||
InsertIntoPageIcon,
|
||||
UngroupIcon,
|
||||
} from '@blocksuite/icons/lit';
|
||||
import { type BlockComponent, BlockFlavourIdentifier } from '@blocksuite/std';
|
||||
import { GfxControllerIdentifier } from '@blocksuite/std/gfx';
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
@@ -46,9 +51,8 @@ const builtinSurfaceToolbarConfig = {
|
||||
{
|
||||
id: 'a.insert-into-page',
|
||||
label: 'Insert into Page',
|
||||
showLabel: true,
|
||||
tooltip: 'Insert into Page',
|
||||
icon: PageIcon(),
|
||||
icon: InsertIntoPageIcon(),
|
||||
when: ctx => ctx.getSurfaceModelsByType(FrameBlockModel).length === 1,
|
||||
run(ctx) {
|
||||
const model = ctx.getCurrentModelByType(FrameBlockModel);
|
||||
@@ -78,13 +82,23 @@ const builtinSurfaceToolbarConfig = {
|
||||
);
|
||||
}
|
||||
|
||||
ctx.store.captureSync();
|
||||
ctx.store.addBlock(
|
||||
SurfaceRefBlockSchema.model.flavour,
|
||||
{ reference: frameId, refFlavour: FrameBlockSchema.model.flavour },
|
||||
lastNoteId
|
||||
);
|
||||
|
||||
toast(ctx.host, 'Frame has been inserted into doc');
|
||||
const notification = ctx.std.getOptional(NotificationProvider);
|
||||
if (notification) {
|
||||
notification.notifyWithUndoAction({
|
||||
title: 'Frame inserted into Page.',
|
||||
message: 'Frame has been inserted into doc',
|
||||
accent: 'success',
|
||||
});
|
||||
} else {
|
||||
toast(ctx.host, 'Frame has been inserted into doc');
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -31,10 +31,7 @@ import {
|
||||
LinkedPageIcon,
|
||||
ScissorsIcon,
|
||||
} from '@blocksuite/icons/lit';
|
||||
import {
|
||||
BlockFlavourIdentifier,
|
||||
EditorLifeCycleExtension,
|
||||
} from '@blocksuite/std';
|
||||
import { BlockFlavourIdentifier } from '@blocksuite/std';
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
import { computed } from '@preact/signals-core';
|
||||
import { html } from 'lit';
|
||||
@@ -504,34 +501,6 @@ function setDisplayMode(
|
||||
ctx.selection.clear();
|
||||
}
|
||||
|
||||
const abortController = new AbortController();
|
||||
const clear = () => {
|
||||
ctx.history.off('stack-item-added', addHandler);
|
||||
ctx.history.off('stack-item-popped', popHandler);
|
||||
disposable.unsubscribe();
|
||||
};
|
||||
const closeNotify = () => {
|
||||
abortController.abort();
|
||||
clear();
|
||||
};
|
||||
|
||||
const addHandler = ctx.history.on('stack-item-added', closeNotify);
|
||||
const popHandler = ctx.history.on('stack-item-popped', closeNotify);
|
||||
const disposable = ctx.std
|
||||
.get(EditorLifeCycleExtension)
|
||||
.slots.unmounted.subscribe(closeNotify);
|
||||
|
||||
const undo = () => {
|
||||
ctx.store.undo();
|
||||
closeNotify();
|
||||
};
|
||||
|
||||
const viewInToc = () => {
|
||||
const sidebar = ctx.std.getOptional(SidebarExtensionIdentifier);
|
||||
sidebar?.open('outline');
|
||||
closeNotify();
|
||||
};
|
||||
|
||||
const data =
|
||||
newMode === NoteDisplayMode.EdgelessOnly
|
||||
? {
|
||||
@@ -544,27 +513,21 @@ function setDisplayMode(
|
||||
};
|
||||
|
||||
const notification = ctx.std.getOptional(NotificationProvider);
|
||||
notification?.notify({
|
||||
notification?.notifyWithUndoAction({
|
||||
title: data.title,
|
||||
message: `${data.message} Find it in the TOC for quick navigation.`,
|
||||
accent: 'success',
|
||||
duration: 5 * 1000,
|
||||
actions: [
|
||||
{
|
||||
key: 'undo-display-in-page',
|
||||
label: 'Undo',
|
||||
onClick: () => undo(),
|
||||
},
|
||||
{
|
||||
key: 'view-in-toc',
|
||||
label: 'View in Toc',
|
||||
onClick: () => viewInToc(),
|
||||
onClick: () => {
|
||||
const sidebar = ctx.std.getOptional(SidebarExtensionIdentifier);
|
||||
sidebar?.open('outline');
|
||||
},
|
||||
},
|
||||
],
|
||||
abort: abortController.signal,
|
||||
onClose: () => {
|
||||
clear();
|
||||
},
|
||||
});
|
||||
|
||||
ctx.track('NoteDisplayModeChanged', {
|
||||
|
||||
@@ -58,7 +58,7 @@ export const quickActionConfig: QuickActionConfig[] = [
|
||||
draftedModels,
|
||||
title
|
||||
).catch(console.error);
|
||||
notifyDocCreated(std, doc);
|
||||
notifyDocCreated(std);
|
||||
})
|
||||
.catch(console.error);
|
||||
},
|
||||
|
||||
@@ -244,7 +244,7 @@ const turnIntoLinkedDoc = {
|
||||
draftedModels,
|
||||
title
|
||||
);
|
||||
notifyDocCreated(std, store);
|
||||
notifyDocCreated(std);
|
||||
|
||||
track('DocCreated', {
|
||||
segment: 'doc',
|
||||
|
||||
@@ -347,7 +347,7 @@ export const moreActions = [
|
||||
other: 'new doc',
|
||||
});
|
||||
|
||||
notifyDocCreated(ctx.std, ctx.store);
|
||||
notifyDocCreated(ctx.std);
|
||||
};
|
||||
|
||||
create().catch(console.error);
|
||||
|
||||
@@ -156,7 +156,7 @@ export class PageKeyboardManager {
|
||||
draftedModels,
|
||||
title
|
||||
).catch(console.error);
|
||||
notifyDocCreated(rootComponent.std, doc);
|
||||
notifyDocCreated(rootComponent.std);
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
@@ -1,52 +1,22 @@
|
||||
import { NotificationProvider } from '@blocksuite/affine-shared/services';
|
||||
import { type BlockStdScope, EditorLifeCycleExtension } from '@blocksuite/std';
|
||||
import { type BlockStdScope } from '@blocksuite/std';
|
||||
|
||||
import { toast } from '../toast/toast.js';
|
||||
|
||||
function notify(std: BlockStdScope, title: string, message: string) {
|
||||
const notification = std.getOptional(NotificationProvider);
|
||||
const { store: doc, host } = std;
|
||||
const { host } = std;
|
||||
|
||||
if (!notification) {
|
||||
toast(host, title);
|
||||
return;
|
||||
}
|
||||
|
||||
const abortController = new AbortController();
|
||||
const clear = () => {
|
||||
doc.history.off('stack-item-added', addHandler);
|
||||
doc.history.off('stack-item-popped', popHandler);
|
||||
disposable.unsubscribe();
|
||||
};
|
||||
const closeNotify = () => {
|
||||
abortController.abort();
|
||||
clear();
|
||||
};
|
||||
|
||||
// edit or undo or switch doc, close notify toast
|
||||
const addHandler = doc.history.on('stack-item-added', closeNotify);
|
||||
const popHandler = doc.history.on('stack-item-popped', closeNotify);
|
||||
const disposable = host.std
|
||||
.get(EditorLifeCycleExtension)
|
||||
.slots.unmounted.subscribe(closeNotify);
|
||||
|
||||
notification.notify({
|
||||
notification.notifyWithUndoAction({
|
||||
title,
|
||||
message,
|
||||
accent: 'info',
|
||||
duration: 10 * 1000,
|
||||
actions: [
|
||||
{
|
||||
key: 'undo',
|
||||
label: 'Undo',
|
||||
onClick: () => {
|
||||
doc.undo();
|
||||
clear();
|
||||
},
|
||||
},
|
||||
],
|
||||
abort: abortController.signal,
|
||||
onClose: clear,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,11 @@ import {
|
||||
import { matchModels } from '@blocksuite/affine-shared/utils';
|
||||
import { getRootBlock } from '@blocksuite/affine-widget-edgeless-toolbar';
|
||||
import { Bound } from '@blocksuite/global/gfx';
|
||||
import { EditIcon, PageIcon, UngroupIcon } from '@blocksuite/icons/lit';
|
||||
import {
|
||||
EditIcon,
|
||||
InsertIntoPageIcon,
|
||||
UngroupIcon,
|
||||
} from '@blocksuite/icons/lit';
|
||||
import { BlockFlavourIdentifier } from '@blocksuite/std';
|
||||
|
||||
import { ungroupCommand } from '../command';
|
||||
@@ -25,9 +29,8 @@ export const groupToolbarConfig = {
|
||||
{
|
||||
id: 'a.insert-into-page',
|
||||
label: 'Insert into Page',
|
||||
showLabel: true,
|
||||
tooltip: 'Insert into Page',
|
||||
icon: PageIcon(),
|
||||
icon: InsertIntoPageIcon(),
|
||||
when: ctx => ctx.getSurfaceModelsByType(GroupElementModel).length === 1,
|
||||
run(ctx) {
|
||||
const model = ctx.getCurrentModelByType(GroupElementModel);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createIdentifier } from '@blocksuite/global/di';
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
import { createIdentifier, type ServiceProvider } from '@blocksuite/global/di';
|
||||
import { EditorLifeCycleExtension } from '@blocksuite/std';
|
||||
import { type ExtensionType, StoreIdentifier } from '@blocksuite/store';
|
||||
import type { TemplateResult } from 'lit';
|
||||
|
||||
export interface NotificationService {
|
||||
@@ -37,8 +38,16 @@ export interface NotificationService {
|
||||
label: string | TemplateResult;
|
||||
onClick: () => void;
|
||||
}[];
|
||||
onClose: () => void;
|
||||
onClose?: () => void;
|
||||
}): void;
|
||||
|
||||
/**
|
||||
* Notify with undo action, it is a helper function to notify with undo action.
|
||||
* And the notification card will be closed when undo action is triggered by shortcut key or other ways.
|
||||
*/
|
||||
notifyWithUndoAction: (
|
||||
options: Parameters<NotificationService['notify']>[0]
|
||||
) => void;
|
||||
}
|
||||
|
||||
export const NotificationProvider = createIdentifier<NotificationService>(
|
||||
@@ -46,11 +55,69 @@ export const NotificationProvider = createIdentifier<NotificationService>(
|
||||
);
|
||||
|
||||
export function NotificationExtension(
|
||||
notificationService: NotificationService
|
||||
notificationService: Omit<NotificationService, 'notifyWithUndoAction'>
|
||||
): ExtensionType {
|
||||
return {
|
||||
setup: di => {
|
||||
di.addImpl(NotificationProvider, notificationService);
|
||||
di.addImpl(NotificationProvider, provider => {
|
||||
return {
|
||||
...notificationService,
|
||||
notifyWithUndoAction: options => {
|
||||
notifyWithUndoActionImpl(
|
||||
provider,
|
||||
notificationService.notify,
|
||||
options
|
||||
);
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function notifyWithUndoActionImpl(
|
||||
provider: ServiceProvider,
|
||||
notify: NotificationService['notify'],
|
||||
options: Parameters<NotificationService['notifyWithUndoAction']>[0]
|
||||
) {
|
||||
const store = provider.get(StoreIdentifier);
|
||||
|
||||
const abortController = new AbortController();
|
||||
const abort = () => {
|
||||
abortController.abort();
|
||||
};
|
||||
options.abort?.addEventListener('abort', abort);
|
||||
|
||||
const clearOnClose = () => {
|
||||
store.history.off('stack-item-added', addHandler);
|
||||
store.history.off('stack-item-popped', popHandler);
|
||||
disposable.unsubscribe();
|
||||
options.abort?.removeEventListener('abort', abort);
|
||||
};
|
||||
|
||||
const addHandler = store.history.on('stack-item-added', abort);
|
||||
const popHandler = store.history.on('stack-item-popped', abort);
|
||||
const disposable = provider
|
||||
.get(EditorLifeCycleExtension)
|
||||
.slots.unmounted.subscribe(() => abort());
|
||||
|
||||
notify({
|
||||
...options,
|
||||
actions: [
|
||||
{
|
||||
key: 'notification-card-undo',
|
||||
label: 'Undo',
|
||||
onClick: () => {
|
||||
store.undo();
|
||||
abortController.abort();
|
||||
},
|
||||
},
|
||||
...(options.actions ?? []),
|
||||
],
|
||||
abort: abortController.signal,
|
||||
onClose: () => {
|
||||
options.onClose?.();
|
||||
clearOnClose();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -97,6 +97,10 @@ export function mockNotificationService(editor: TestAffineEditorContainer) {
|
||||
// todo: implement in playground
|
||||
console.log(notification);
|
||||
},
|
||||
notifyWithUndoAction: notification => {
|
||||
// todo: implement in playground
|
||||
console.log(notification);
|
||||
},
|
||||
};
|
||||
return notificationService;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user