chore: merge blocksuite source code (#9213)

This commit is contained in:
Mirone
2024-12-20 15:38:06 +08:00
committed by GitHub
parent 2c9ef916f4
commit 30200ff86d
2031 changed files with 238888 additions and 229 deletions

View File

@@ -0,0 +1,55 @@
/// <reference types="@blocksuite/affine-shared/commands" />
import type {
BlockComponent,
Command,
InitCommandCtx,
} from '@blocksuite/block-std';
import { isPeekable, peek } from './peekable.js';
const getSelectedPeekableBlocks = (cmd: InitCommandCtx) => {
const [result, ctx] = cmd.std.command
.chain()
.tryAll(chain => [chain.getTextSelection(), chain.getBlockSelections()])
.getSelectedBlocks({ types: ['text', 'block'] })
.run();
return ((result ? ctx.selectedBlocks : []) || []).filter(isPeekable);
};
export const getSelectedPeekableBlocksCommand: Command<
'selectedBlocks',
'selectedPeekableBlocks'
> = (ctx, next) => {
const selectedPeekableBlocks = getSelectedPeekableBlocks(ctx);
if (selectedPeekableBlocks.length > 0) {
next({ selectedPeekableBlocks });
}
};
export const peekSelectedBlockCommand: Command<'selectedBlocks'> = (
ctx,
next
) => {
const peekableBlocks = getSelectedPeekableBlocks(ctx);
// if there are multiple blocks, peek the first one
const block = peekableBlocks.at(0);
if (block) {
peek(block);
next();
}
};
declare global {
namespace BlockSuite {
interface CommandContext {
selectedPeekableBlocks?: BlockComponent[];
}
interface Commands {
peekSelectedBlock: typeof peekSelectedBlockCommand;
getSelectedPeekableBlocks: typeof getSelectedPeekableBlocksCommand;
// todo: add command for peek an inline element?
}
}
}

View File

@@ -0,0 +1,31 @@
import type { TemplateResult } from 'lit';
import { PeekViewProvider } from './service.js';
import type { PeekableClass, PeekViewService } from './type.js';
export class PeekableController<T extends PeekableClass> {
private _getPeekViewService = (): PeekViewService | null => {
return this.target.std.getOptional(PeekViewProvider);
};
peek = (template?: TemplateResult) => {
return Promise.resolve<void>(
this._getPeekViewService()?.peek({
target: this.target,
template,
})
);
};
get peekable() {
return (
!!this._getPeekViewService() &&
(this.enable ? this.enable(this.target) : true)
);
}
constructor(
private target: T,
private enable?: (e: T) => boolean
) {}
}

View File

@@ -0,0 +1,8 @@
export {
getSelectedPeekableBlocksCommand,
peekSelectedBlockCommand,
} from './commands.js';
export { PeekableController } from './controller.js';
export { isPeekable, peek, Peekable } from './peekable.js';
export * from './service.js';
export type { PeekableOptions, PeekOptions, PeekViewService } from './type.js';

View File

@@ -0,0 +1,81 @@
import { isInsideEdgelessEditor } from '@blocksuite/affine-shared/utils';
import type { Constructor } from '@blocksuite/global/utils';
import type { LitElement, TemplateResult } from 'lit';
import { PeekableController } from './controller.js';
import type { PeekableClass, PeekableOptions } from './type.js';
const symbol = Symbol('peekable');
export const isPeekable = <Element extends LitElement>(e: Element): boolean => {
return Reflect.has(e, symbol) && (e as any)[symbol]?.peekable;
};
export const peek = <Element extends LitElement>(
e: Element,
template?: TemplateResult
): void => {
isPeekable(e) && (e as any)[symbol]?.peek(template);
};
/**
* Mark a class as peekable, which means the class can be peeked by the peek view service.
*
* Note: This class must be syntactically below the `@customElement` decorator (it will be applied before customElement).
*/
export const Peekable =
<T extends PeekableClass, C extends Constructor<PeekableClass>>(
options: PeekableOptions<T> = {
action: ['double-click', 'shift-click'],
}
) =>
(Class: C, context: ClassDecoratorContext) => {
if (context.kind !== 'class') {
console.error('@Peekable() can only be applied to a class');
return;
}
if (options.action === undefined)
options.action = ['double-click', 'shift-click'];
const actions = Array.isArray(options.action)
? options.action
: options.action
? [options.action]
: [];
const derivedClass = class extends Class {
[symbol] = new PeekableController(this as unknown as T, options.enableOn);
override connectedCallback() {
super.connectedCallback();
const target: HTMLElement =
(options.selector ? this.querySelector(options.selector) : this) ||
this;
if (actions.includes('double-click')) {
this.disposables.addFromEvent(target, 'dblclick', e => {
if (this[symbol].peekable) {
e.stopPropagation();
this[symbol].peek().catch(console.error);
}
});
}
if (
actions.includes('shift-click') &&
// shift click in edgeless should be selection
!isInsideEdgelessEditor(this.std.host)
) {
this.disposables.addFromEvent(target, 'click', e => {
if (e.shiftKey && this[symbol].peekable) {
e.stopPropagation();
e.stopImmediatePropagation();
this[symbol].peek().catch(console.error);
}
});
}
}
};
return derivedClass as unknown as C;
};

View File

@@ -0,0 +1,16 @@
import type { ExtensionType } from '@blocksuite/block-std';
import { createIdentifier } from '@blocksuite/global/di';
import type { PeekViewService } from './type.js';
export const PeekViewProvider = createIdentifier<PeekViewService>(
'AffinePeekViewProvider'
);
export function PeekViewExtension(service: PeekViewService): ExtensionType {
return {
setup: di => {
di.addImpl(PeekViewProvider, () => service);
},
};
}

View File

@@ -0,0 +1,70 @@
import type { BlockComponent, BlockStdScope } from '@blocksuite/block-std';
import type { DisposableClass } from '@blocksuite/global/utils';
import type { LitElement, TemplateResult } from 'lit';
export type PeekableClass = { std: BlockStdScope } & DisposableClass &
LitElement;
export interface PeekOptions {
/**
* Abort signal to abort the peek view
*/
abortSignal?: AbortSignal;
}
export interface PeekViewService {
/**
* Peek a target element page ref info
* @param pageRef The page ref info to peek.
* @returns A promise that resolves when the peek view is closed.
*/
peek(
pageRef: {
docId: string;
blockIds?: string[];
databaseId?: string;
databaseDocId?: string;
databaseRowId?: string;
elementIds?: string[];
target?: HTMLElement;
},
options?: PeekOptions
): Promise<void>;
/**
* Peek a target element with a optional template
* @param target The target element to peek. There are two use cases:
* 1. If the template is not given, peek view content rendering will be delegated to the implementation of peek view service.
* 2. To determine the origin of the peek view modal animation
* @param template Optional template to render in the peek view modal. If not given, the peek view service will render the content.
* @returns A promise that resolves when the peek view is closed.
*/
peek(
// eslint-disable-next-line @typescript-eslint/unified-signatures
element: { target: HTMLElement; template?: TemplateResult },
options?: PeekOptions
): Promise<void>;
peek<Element extends BlockComponent>(
element: { target: Element; template?: TemplateResult },
options?: PeekOptions
): Promise<void>;
}
type PeekableAction = 'double-click' | 'shift-click';
export type PeekableOptions<T extends PeekableClass> = {
/**
* Action to bind to the peekable element. default to ['double-click', 'shift-click']
* false means do not bind any action.
*/
action?: PeekableAction | PeekableAction[] | false;
/**
* It will check the block is enable to peek or not
*/
enableOn?: (block: T) => boolean;
/**
* Selector inside of the peekable element to bind the action
*/
selector?: string;
};