mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 21:27:20 +00:00
refactor(editor): extract surface ref block (#9433)
This commit is contained in:
@@ -17,6 +17,10 @@ import {
|
||||
EdgelessSurfaceBlockSpec,
|
||||
PageSurfaceBlockSpec,
|
||||
} from '@blocksuite/affine-block-surface';
|
||||
import {
|
||||
EdgelessSurfaceRefBlockSpec,
|
||||
PageSurfaceRefBlockSpec,
|
||||
} from '@blocksuite/affine-block-surface-ref';
|
||||
import {
|
||||
RefNodeSlotsExtension,
|
||||
RichTextExtensions,
|
||||
@@ -31,10 +35,6 @@ import type { ExtensionType } from '@blocksuite/block-std';
|
||||
import { AdapterFactoryExtensions } from '../_common/adapters/extension.js';
|
||||
import { DataViewBlockSpec } from '../data-view-block/data-view-spec.js';
|
||||
import { DatabaseBlockSpec } from '../database-block/database-spec.js';
|
||||
import {
|
||||
EdgelessSurfaceRefBlockSpec,
|
||||
PageSurfaceRefBlockSpec,
|
||||
} from '../surface-ref-block/surface-ref-spec.js';
|
||||
|
||||
export const CommonBlockSpecs: ExtensionType[] = [
|
||||
DocDisplayMetaService,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { NoteBlockComponent } from '@blocksuite/affine-block-note';
|
||||
import { CaptionedBlockComponent } from '@blocksuite/affine-components/caption';
|
||||
import {
|
||||
menu,
|
||||
@@ -17,7 +16,10 @@ import {
|
||||
TelemetryProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { getDropResult } from '@blocksuite/affine-widget-drag-handle';
|
||||
import { RANGE_SYNC_EXCLUDE_ATTR } from '@blocksuite/block-std';
|
||||
import {
|
||||
type BlockComponent,
|
||||
RANGE_SYNC_EXCLUDE_ATTR,
|
||||
} from '@blocksuite/block-std';
|
||||
import {
|
||||
createRecordDetail,
|
||||
createUniComponentFromWebComponent,
|
||||
@@ -46,7 +48,6 @@ import { autoUpdate } from '@floating-ui/dom';
|
||||
import { computed, signal } from '@preact/signals-core';
|
||||
import { css, html, nothing, unsafeCSS } from 'lit';
|
||||
|
||||
import { EdgelessRootBlockComponent } from '../root-block/index.js';
|
||||
import { popSideDetail } from './components/layout.js';
|
||||
import type { DatabaseOptionsConfig } from './config.js';
|
||||
import { HostContextKey } from './context/host-context.js';
|
||||
@@ -351,9 +352,8 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent<
|
||||
}
|
||||
|
||||
override get topContenteditableElement() {
|
||||
if (this.rootComponent instanceof EdgelessRootBlockComponent) {
|
||||
const note = this.closest<NoteBlockComponent>(NOTE_SELECTOR);
|
||||
return note;
|
||||
if (this.std.get(DocModeProvider).getEditorMode() === 'edgeless') {
|
||||
return this.closest<BlockComponent>(NOTE_SELECTOR);
|
||||
}
|
||||
return this.rootComponent;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { effects as blockListEffects } from '@blocksuite/affine-block-list/effec
|
||||
import { effects as blockNoteEffects } from '@blocksuite/affine-block-note/effects';
|
||||
import { effects as blockParagraphEffects } from '@blocksuite/affine-block-paragraph/effects';
|
||||
import { effects as blockSurfaceEffects } from '@blocksuite/affine-block-surface/effects';
|
||||
import { effects as blockSurfaceRefEffects } from '@blocksuite/affine-block-surface-ref/effects';
|
||||
import { effects as componentAiItemEffects } from '@blocksuite/affine-components/ai-item';
|
||||
import { BlockSelection } from '@blocksuite/affine-components/block-selection';
|
||||
import { BlockZeroWidth } from '@blocksuite/affine-components/block-zero-width';
|
||||
@@ -209,13 +210,6 @@ import {
|
||||
MindmapSurfaceBlock,
|
||||
MiniMindmapPreview,
|
||||
} from './surface-block/mini-mindmap/index.js';
|
||||
import { effects as blockSurfaceRefEffects } from './surface-ref-block/effects.js';
|
||||
import {
|
||||
EdgelessSurfaceRefBlockComponent,
|
||||
SurfaceRefBlockComponent,
|
||||
} from './surface-ref-block/index.js';
|
||||
import { SurfaceRefGenericBlockPortal } from './surface-ref-block/portal/generic-block.js';
|
||||
import { SurfaceRefNotePortal } from './surface-ref-block/portal/note.js';
|
||||
|
||||
export function effects() {
|
||||
registerSpecs();
|
||||
@@ -286,10 +280,6 @@ export function effects() {
|
||||
'edgeless-copilot-toolbar-entry',
|
||||
EdgelessCopilotToolbarEntry
|
||||
);
|
||||
customElements.define(
|
||||
'affine-edgeless-surface-ref',
|
||||
EdgelessSurfaceRefBlockComponent
|
||||
);
|
||||
customElements.define(
|
||||
'edgeless-color-custom-button',
|
||||
EdgelessColorCustomButton
|
||||
@@ -302,7 +292,6 @@ export function effects() {
|
||||
);
|
||||
customElements.define('affine-custom-modal', AffineCustomModal);
|
||||
customElements.define('affine-database', DatabaseBlockComponent);
|
||||
customElements.define('affine-surface-ref', SurfaceRefBlockComponent);
|
||||
customElements.define('affine-slash-menu', SlashMenu);
|
||||
customElements.define('inner-slash-menu', InnerSlashMenu);
|
||||
customElements.define('generating-placeholder', GeneratingPlaceholder);
|
||||
@@ -321,10 +310,6 @@ export function effects() {
|
||||
customElements.define('icon-button', IconButton);
|
||||
customElements.define('loader-element', Loader);
|
||||
customElements.define('edgeless-brush-menu', EdgelessBrushMenu);
|
||||
customElements.define(
|
||||
'surface-ref-generic-block-portal',
|
||||
SurfaceRefGenericBlockPortal
|
||||
);
|
||||
customElements.define('edgeless-brush-tool-button', EdgelessBrushToolButton);
|
||||
customElements.define(
|
||||
'edgeless-connector-tool-button',
|
||||
@@ -334,7 +319,6 @@ export function effects() {
|
||||
'edgeless-default-tool-button',
|
||||
EdgelessDefaultToolButton
|
||||
);
|
||||
customElements.define('surface-ref-note-portal', SurfaceRefNotePortal);
|
||||
customElements.define('edgeless-connector-menu', EdgelessConnectorMenu);
|
||||
customElements.define('smooth-corner', SmoothCorner);
|
||||
customElements.define('toggle-switch', ToggleSwitch);
|
||||
|
||||
@@ -39,7 +39,6 @@ export {
|
||||
MindmapSurfaceBlock,
|
||||
MiniMindmapPreview,
|
||||
} from './surface-block/mini-mindmap/index.js';
|
||||
export * from './surface-ref-block/index.js';
|
||||
export * from '@blocksuite/affine-block-attachment';
|
||||
export * from '@blocksuite/affine-block-bookmark';
|
||||
export * from '@blocksuite/affine-block-code';
|
||||
@@ -53,6 +52,7 @@ export * from '@blocksuite/affine-block-list';
|
||||
export * from '@blocksuite/affine-block-note';
|
||||
export * from '@blocksuite/affine-block-paragraph';
|
||||
export * from '@blocksuite/affine-block-surface';
|
||||
export * from '@blocksuite/affine-block-surface-ref';
|
||||
export {
|
||||
type AIError,
|
||||
type AIItemConfig,
|
||||
|
||||
@@ -2,6 +2,7 @@ import type {
|
||||
SurfaceBlockComponent,
|
||||
SurfaceBlockModel,
|
||||
} from '@blocksuite/affine-block-surface';
|
||||
import type { EdgelessPreviewer } from '@blocksuite/affine-block-surface-ref';
|
||||
import type { RootBlockModel } from '@blocksuite/affine-model';
|
||||
import {
|
||||
FontLoaderService,
|
||||
@@ -22,11 +23,14 @@ import type { EdgelessRootBlockWidgetName } from '../types.js';
|
||||
import type { EdgelessRootService } from './edgeless-root-service.js';
|
||||
import { getBackgroundGrid, isCanvasElement } from './utils/query.js';
|
||||
|
||||
export class EdgelessRootPreviewBlockComponent extends BlockComponent<
|
||||
RootBlockModel,
|
||||
EdgelessRootService,
|
||||
EdgelessRootBlockWidgetName
|
||||
> {
|
||||
export class EdgelessRootPreviewBlockComponent
|
||||
extends BlockComponent<
|
||||
RootBlockModel,
|
||||
EdgelessRootService,
|
||||
EdgelessRootBlockWidgetName
|
||||
>
|
||||
implements EdgelessPreviewer
|
||||
{
|
||||
static override styles = css`
|
||||
affine-edgeless-root-preview {
|
||||
pointer-events: none;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { SurfaceRefBlockComponent } from '@blocksuite/affine-block-surface-ref';
|
||||
import { MenuContext } from '@blocksuite/affine-components/toolbar';
|
||||
|
||||
import type { SurfaceRefBlockComponent } from '../../../surface-ref-block/surface-ref-block.js';
|
||||
|
||||
export class SurfaceRefToolbarContext extends MenuContext {
|
||||
override close = () => {
|
||||
this.abortController.abort();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { SurfaceRefBlockComponent } from '@blocksuite/affine-block-surface-ref';
|
||||
import { HoverController } from '@blocksuite/affine-components/hover';
|
||||
import {
|
||||
CaptionIcon,
|
||||
@@ -25,7 +26,6 @@ import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
import { join } from 'lit/directives/join.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
|
||||
import type { SurfaceRefBlockComponent } from '../../../surface-ref-block/index.js';
|
||||
import { BUILT_IN_GROUPS } from './config.js';
|
||||
import { SurfaceRefToolbarContext } from './context.js';
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { CanvasRenderer } from '@blocksuite/affine-block-surface';
|
||||
import type { SurfaceRefBlockComponent } from '@blocksuite/affine-block-surface-ref';
|
||||
import { isTopLevelBlock } from '@blocksuite/affine-shared/utils';
|
||||
import type { EditorHost } from '@blocksuite/block-std';
|
||||
import { assertExists, Bound } from '@blocksuite/global/utils';
|
||||
|
||||
import { ExportManager } from '../../../_common/export-manager/export-manager.js';
|
||||
import type { SurfaceRefBlockComponent } from '../../../surface-ref-block/surface-ref-block.js';
|
||||
|
||||
export const edgelessToBlob = async (
|
||||
host: EditorHost,
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
import { getSurfaceBlock } from '@blocksuite/affine-block-surface';
|
||||
import type { SurfaceRefProps } from '@blocksuite/affine-model';
|
||||
import { matchFlavours } from '@blocksuite/affine-shared/utils';
|
||||
import type { BlockCommands, Command } from '@blocksuite/block-std';
|
||||
|
||||
export const insertSurfaceRefBlockCommand: Command<
|
||||
'selectedModels',
|
||||
'insertedSurfaceRefBlockId',
|
||||
{
|
||||
reference: string;
|
||||
place: 'after' | 'before';
|
||||
removeEmptyLine?: boolean;
|
||||
}
|
||||
> = (ctx, next) => {
|
||||
const { selectedModels, reference, place, removeEmptyLine, std } = ctx;
|
||||
if (!selectedModels?.length) return;
|
||||
|
||||
const targetModel =
|
||||
place === 'before'
|
||||
? selectedModels[0]
|
||||
: selectedModels[selectedModels.length - 1];
|
||||
|
||||
const surfaceRefProps: Partial<SurfaceRefProps> & {
|
||||
flavour: 'affine:surface-ref';
|
||||
} = {
|
||||
flavour: 'affine:surface-ref',
|
||||
reference,
|
||||
};
|
||||
|
||||
const surface = getSurfaceBlock(std.doc);
|
||||
if (!surface) return;
|
||||
|
||||
const element = surface.getElementById(reference);
|
||||
const blockModel = std.doc.getBlock(reference)?.model ?? null;
|
||||
|
||||
if (element?.type === 'group') {
|
||||
surfaceRefProps.refFlavour = 'group';
|
||||
} else if (matchFlavours(blockModel, ['affine:frame'])) {
|
||||
surfaceRefProps.refFlavour = 'frame';
|
||||
} else {
|
||||
console.error(`reference not found ${reference}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = std.doc.addSiblingBlocks(
|
||||
targetModel,
|
||||
[surfaceRefProps],
|
||||
place
|
||||
);
|
||||
if (result.length === 0) return;
|
||||
|
||||
if (removeEmptyLine && targetModel.text?.length === 0) {
|
||||
std.doc.deleteBlock(targetModel);
|
||||
}
|
||||
|
||||
next({
|
||||
insertedSurfaceRefBlockId: result[0],
|
||||
});
|
||||
};
|
||||
|
||||
export const commands: BlockCommands = {
|
||||
insertSurfaceRefBlock: insertSurfaceRefBlockCommand,
|
||||
};
|
||||
@@ -1,24 +0,0 @@
|
||||
import type { insertSurfaceRefBlockCommand } from './commands.js';
|
||||
|
||||
export function effects() {
|
||||
// TODO(@L-Sun): move other effects to this file
|
||||
}
|
||||
|
||||
declare global {
|
||||
namespace BlockSuite {
|
||||
interface CommandContext {
|
||||
insertedSurfaceRefBlockId?: string;
|
||||
}
|
||||
|
||||
interface Commands {
|
||||
/**
|
||||
* insert a SurfaceRef block after or before the current block selection
|
||||
* @param reference the reference block id. The block should be group or frame
|
||||
* @param place where to insert the LaTeX block
|
||||
* @param removeEmptyLine remove the current block if it is empty
|
||||
* @returns the id of the inserted SurfaceRef block
|
||||
*/
|
||||
insertSurfaceRefBlock: typeof insertSurfaceRefBlockCommand;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
export * from './surface-ref-block.js';
|
||||
export * from './surface-ref-block-edgeless.js';
|
||||
export {
|
||||
EdgelessSurfaceRefBlockSpec,
|
||||
PageSurfaceRefBlockSpec,
|
||||
} from './surface-ref-spec.js';
|
||||
export * from './utils.js';
|
||||
@@ -1,81 +0,0 @@
|
||||
import type {
|
||||
AttachmentBlockModel,
|
||||
BookmarkBlockModel,
|
||||
EmbedFigmaModel,
|
||||
EmbedGithubModel,
|
||||
EmbedHtmlModel,
|
||||
EmbedLinkedDocModel,
|
||||
EmbedLoomModel,
|
||||
EmbedSyncedDocModel,
|
||||
EmbedYoutubeModel,
|
||||
ImageBlockModel,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { ShadowlessElement } from '@blocksuite/block-std';
|
||||
import { Bound, WithDisposable } from '@blocksuite/global/utils';
|
||||
import type { BlockModel } from '@blocksuite/store';
|
||||
import { css, type TemplateResult } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
import { html } from 'lit/static-html.js';
|
||||
|
||||
export class SurfaceRefGenericBlockPortal extends WithDisposable(
|
||||
ShadowlessElement
|
||||
) {
|
||||
static override styles = css`
|
||||
surface-ref-generic-block-portal {
|
||||
position: relative;
|
||||
}
|
||||
`;
|
||||
|
||||
override firstUpdated() {
|
||||
this.disposables.add(
|
||||
this.model.propsUpdated.on(() => this.requestUpdate())
|
||||
);
|
||||
}
|
||||
|
||||
override render() {
|
||||
const { model, index } = this;
|
||||
const bound = Bound.deserialize(model.xywh);
|
||||
const style = {
|
||||
position: 'absolute',
|
||||
zIndex: `${index}`,
|
||||
width: `${bound.w}px`,
|
||||
height: `${bound.h}px`,
|
||||
transform: `translate(${bound.x}px, ${bound.y}px)`,
|
||||
};
|
||||
|
||||
return html`
|
||||
<div
|
||||
style=${styleMap(style)}
|
||||
data-portal-reference-block-id="${model.id}"
|
||||
>
|
||||
${this.renderModel(model)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor index!: number;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor model!:
|
||||
| ImageBlockModel
|
||||
| AttachmentBlockModel
|
||||
| BookmarkBlockModel
|
||||
| EmbedGithubModel
|
||||
| EmbedYoutubeModel
|
||||
| EmbedFigmaModel
|
||||
| EmbedLinkedDocModel
|
||||
| EmbedSyncedDocModel
|
||||
| EmbedHtmlModel
|
||||
| EmbedLoomModel;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor renderModel!: (model: BlockModel) => TemplateResult;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'surface-ref-generic-block-portal': SurfaceRefGenericBlockPortal;
|
||||
}
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
import type { CanvasRenderer } from '@blocksuite/affine-block-surface';
|
||||
import type { NoteBlockModel } from '@blocksuite/affine-model';
|
||||
import {
|
||||
DefaultTheme,
|
||||
NoteDisplayMode,
|
||||
NoteShadow,
|
||||
} from '@blocksuite/affine-model';
|
||||
import {
|
||||
EDGELESS_BLOCK_CHILD_BORDER_WIDTH,
|
||||
EDGELESS_BLOCK_CHILD_PADDING,
|
||||
} from '@blocksuite/affine-shared/consts';
|
||||
import { ThemeProvider } from '@blocksuite/affine-shared/services';
|
||||
import { SpecProvider } from '@blocksuite/affine-shared/utils';
|
||||
import {
|
||||
BlockStdScope,
|
||||
type EditorHost,
|
||||
RANGE_QUERY_EXCLUDE_ATTR,
|
||||
ShadowlessElement,
|
||||
} from '@blocksuite/block-std';
|
||||
import { deserializeXYWH, WithDisposable } from '@blocksuite/global/utils';
|
||||
import { type BlockModel, BlockViewType, type Query } from '@blocksuite/store';
|
||||
import { css, nothing } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
import { html } from 'lit/static-html.js';
|
||||
|
||||
export class SurfaceRefNotePortal extends WithDisposable(ShadowlessElement) {
|
||||
static override styles = css`
|
||||
surface-ref-note-portal {
|
||||
position: relative;
|
||||
}
|
||||
`;
|
||||
|
||||
ancestors = new Set<string>();
|
||||
|
||||
query: Query | null = null;
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
const ancestors = new Set<string>();
|
||||
let parent: BlockModel | null = this.model;
|
||||
while (parent) {
|
||||
this.ancestors.add(parent.id);
|
||||
parent = this.model.doc.getParent(parent.id);
|
||||
}
|
||||
const query: Query = {
|
||||
mode: 'include',
|
||||
match: Array.from(ancestors).map(id => ({
|
||||
id,
|
||||
viewType: BlockViewType.Display,
|
||||
})),
|
||||
};
|
||||
this.query = query;
|
||||
|
||||
const doc = this.model.doc;
|
||||
this._disposables.add(() => {
|
||||
doc.blockCollection.clearQuery(query, true);
|
||||
});
|
||||
}
|
||||
|
||||
override firstUpdated() {
|
||||
this.disposables.add(
|
||||
this.model.propsUpdated.on(() => this.requestUpdate())
|
||||
);
|
||||
}
|
||||
|
||||
override render() {
|
||||
const { model, index } = this;
|
||||
const { displayMode, edgeless } = model;
|
||||
if (!!displayMode && displayMode === NoteDisplayMode.DocOnly)
|
||||
return nothing;
|
||||
|
||||
const backgroundColor = this.host.std
|
||||
.get(ThemeProvider)
|
||||
.generateColorProperty(model.background, DefaultTheme.noteBackgrounColor);
|
||||
|
||||
const [modelX, modelY, modelW, modelH] = deserializeXYWH(model.xywh);
|
||||
const style = {
|
||||
zIndex: `${index}`,
|
||||
width: modelW + 'px',
|
||||
height:
|
||||
edgeless.collapse && edgeless.collapsedHeight
|
||||
? edgeless.collapsedHeight + 'px'
|
||||
: undefined,
|
||||
transform: `translate(${modelX}px, ${modelY}px)`,
|
||||
padding: `${EDGELESS_BLOCK_CHILD_PADDING}px`,
|
||||
border: `${EDGELESS_BLOCK_CHILD_BORDER_WIDTH}px none var(--affine-black-10)`,
|
||||
backgroundColor,
|
||||
boxShadow: `var(${NoteShadow.Sticker})`,
|
||||
position: 'absolute',
|
||||
borderRadius: '0px',
|
||||
boxSizing: 'border-box',
|
||||
pointerEvents: 'none',
|
||||
overflow: 'hidden',
|
||||
transformOrigin: '0 0',
|
||||
userSelect: 'none',
|
||||
};
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="surface-ref-note-portal"
|
||||
style=${styleMap(style)}
|
||||
data-model-height="${modelH}"
|
||||
data-portal-reference-block-id="${model.id}"
|
||||
>
|
||||
${this.renderPreview()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
renderPreview() {
|
||||
if (!this.query) {
|
||||
console.error('Query is not set before rendering note preview');
|
||||
return nothing;
|
||||
}
|
||||
const doc = this.model.doc.blockCollection.getDoc({
|
||||
query: this.query,
|
||||
readonly: true,
|
||||
});
|
||||
const previewSpec = SpecProvider.getInstance().getSpec('page:preview');
|
||||
return new BlockStdScope({
|
||||
doc,
|
||||
extensions: previewSpec.value.slice(),
|
||||
}).render();
|
||||
}
|
||||
|
||||
override updated() {
|
||||
setTimeout(() => {
|
||||
const editableElements = Array.from<HTMLDivElement>(
|
||||
this.querySelectorAll('[contenteditable]')
|
||||
);
|
||||
const blocks = Array.from(this.querySelectorAll(`[data-block-id]`));
|
||||
|
||||
editableElements.forEach(element => {
|
||||
if (element.contentEditable === 'true')
|
||||
element.contentEditable = 'false';
|
||||
});
|
||||
|
||||
blocks.forEach(element => {
|
||||
element.setAttribute(RANGE_QUERY_EXCLUDE_ATTR, 'true');
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor host!: EditorHost;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor index!: number;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor model!: NoteBlockModel;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor renderer!: CanvasRenderer;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'surface-ref-note-portal': SurfaceRefNotePortal;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import type { SurfaceRefBlockModel } from '@blocksuite/affine-model';
|
||||
import { BlockComponent } from '@blocksuite/block-std';
|
||||
import { nothing } from 'lit';
|
||||
|
||||
export class EdgelessSurfaceRefBlockComponent extends BlockComponent<SurfaceRefBlockModel> {
|
||||
override render() {
|
||||
return nothing;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'affine-edgeless-surface-ref': EdgelessSurfaceRefBlockComponent;
|
||||
}
|
||||
}
|
||||
@@ -1,661 +0,0 @@
|
||||
import {
|
||||
getSurfaceBlock,
|
||||
type SurfaceBlockModel,
|
||||
SurfaceElementModel,
|
||||
} from '@blocksuite/affine-block-surface';
|
||||
import type { BlockCaptionEditor } from '@blocksuite/affine-components/caption';
|
||||
import {
|
||||
EdgelessModeIcon,
|
||||
FrameIcon,
|
||||
MoreDeleteIcon,
|
||||
} from '@blocksuite/affine-components/icons';
|
||||
import { Peekable } from '@blocksuite/affine-components/peek';
|
||||
import {
|
||||
FrameBlockModel,
|
||||
GroupElementModel,
|
||||
type SurfaceRefBlockModel,
|
||||
} from '@blocksuite/affine-model';
|
||||
import {
|
||||
DocModeProvider,
|
||||
EditPropsStore,
|
||||
ThemeProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { requestConnectedFrame } from '@blocksuite/affine-shared/utils';
|
||||
import {
|
||||
type BaseSelection,
|
||||
BlockComponent,
|
||||
BlockServiceWatcher,
|
||||
BlockStdScope,
|
||||
type EditorHost,
|
||||
LifeCycleWatcher,
|
||||
} from '@blocksuite/block-std';
|
||||
import { GfxBlockElementModel } from '@blocksuite/block-std/gfx';
|
||||
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||
import {
|
||||
assertExists,
|
||||
Bound,
|
||||
deserializeXYWH,
|
||||
DisposableGroup,
|
||||
type SerializedXYWH,
|
||||
} from '@blocksuite/global/utils';
|
||||
import type { Doc } from '@blocksuite/store';
|
||||
import { css, html, nothing, type TemplateResult } from 'lit';
|
||||
import { query, state } from 'lit/decorators.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import { SpecProvider } from '../_specs/index.js';
|
||||
import type { EdgelessRootPreviewBlockComponent } from '../root-block/edgeless/edgeless-root-preview-block.js';
|
||||
import { EdgelessRootService } from '../root-block/index.js';
|
||||
import { noContentPlaceholder } from './utils.js';
|
||||
|
||||
const REF_LABEL_ICON = {
|
||||
'affine:frame': FrameIcon,
|
||||
DEFAULT_NOTE_HEIGHT: EdgelessModeIcon,
|
||||
} as Record<string, TemplateResult>;
|
||||
|
||||
const NO_CONTENT_TITLE = {
|
||||
'affine:frame': 'Frame',
|
||||
group: 'Group',
|
||||
DEFAULT: 'Content',
|
||||
} as Record<string, string>;
|
||||
|
||||
const NO_CONTENT_REASON = {
|
||||
group: 'This content was ungrouped or deleted on edgeless mode',
|
||||
DEFAULT: 'This content was deleted on edgeless mode',
|
||||
} as Record<string, string>;
|
||||
|
||||
@Peekable()
|
||||
export class SurfaceRefBlockComponent extends BlockComponent<SurfaceRefBlockModel> {
|
||||
static override styles = css`
|
||||
.affine-surface-ref {
|
||||
position: relative;
|
||||
user-select: none;
|
||||
margin: 10px 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.affine-surface-ref {
|
||||
outline: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ref-placeholder {
|
||||
padding: 26px 0px 0px;
|
||||
}
|
||||
|
||||
.placeholder-image {
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.placeholder-text {
|
||||
margin: 12px auto 0;
|
||||
text-align: center;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
line-height: 36px;
|
||||
font-family: var(--affine-font-family);
|
||||
}
|
||||
|
||||
.placeholder-action {
|
||||
margin: 32px auto 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
width: 204px;
|
||||
padding: 4px 18px;
|
||||
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--affine-border-color);
|
||||
|
||||
font-family: var(--affine-font-family);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.delete-button > .icon > svg {
|
||||
color: var(--affine-icon-color);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.placeholder-reason {
|
||||
margin: 72px auto 0;
|
||||
padding: 10px;
|
||||
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
font-family: var(--affine-font-family);
|
||||
line-height: 20px;
|
||||
|
||||
color: var(--affine-warning-color);
|
||||
background-color: var(--affine-background-error-color);
|
||||
}
|
||||
|
||||
.ref-content {
|
||||
position: relative;
|
||||
padding: 20px;
|
||||
background-color: var(--affine-background-primary-color);
|
||||
background: radial-gradient(
|
||||
var(--affine-edgeless-grid-color) 1px,
|
||||
var(--affine-background-primary-color) 1px
|
||||
);
|
||||
}
|
||||
|
||||
.ref-viewport {
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.ref-viewport.frame {
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--affine-black-30);
|
||||
}
|
||||
|
||||
.surface-ref-mask {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
.surface-ref-mask:hover {
|
||||
background-color: rgba(211, 211, 211, 0.1);
|
||||
}
|
||||
|
||||
.surface-ref-mask:hover .ref-label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ref-label {
|
||||
display: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.ref-label {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
|
||||
width: 100%;
|
||||
padding: 8px 16px;
|
||||
border: 1px solid var(--affine-border-color);
|
||||
gap: 14px;
|
||||
|
||||
background: var(--affine-background-primary-color);
|
||||
|
||||
font-size: 12px;
|
||||
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.ref-label .title {
|
||||
display: inline-block;
|
||||
font-weight: 600;
|
||||
font-family: var(--affine-font-family);
|
||||
line-height: 20px;
|
||||
|
||||
color: var(--affine-text-secondary-color);
|
||||
}
|
||||
|
||||
.ref-label .title > svg {
|
||||
color: var(--affine-icon-secondary);
|
||||
display: inline-block;
|
||||
vertical-align: baseline;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.ref-label .suffix {
|
||||
display: inline-block;
|
||||
font-weight: 400;
|
||||
color: var(--affine-text-disable-color);
|
||||
line-height: 20px;
|
||||
}
|
||||
`;
|
||||
|
||||
private _previewDoc: Doc | null = null;
|
||||
|
||||
private readonly _previewSpec =
|
||||
SpecProvider.getInstance().getSpec('edgeless:preview');
|
||||
|
||||
private _referencedModel: BlockSuite.EdgelessModel | null = null;
|
||||
|
||||
private _referenceXYWH: SerializedXYWH | null = null;
|
||||
|
||||
private _viewportEditor: EditorHost | null = null;
|
||||
|
||||
private get _shouldRender() {
|
||||
return (
|
||||
this.isConnected &&
|
||||
// prevent surface-ref from render itself in loop
|
||||
!this.parentComponent?.closest('affine-surface-ref')
|
||||
);
|
||||
}
|
||||
|
||||
get referenceModel() {
|
||||
return this._referencedModel;
|
||||
}
|
||||
|
||||
private _deleteThis() {
|
||||
this.doc.deleteBlock(this.model);
|
||||
}
|
||||
|
||||
private _focusBlock() {
|
||||
this.selection.update(() => {
|
||||
return [this.selection.create('block', { blockId: this.blockId })];
|
||||
});
|
||||
}
|
||||
|
||||
private _initHotkey() {
|
||||
const selection = this.host.selection;
|
||||
const addParagraph = () => {
|
||||
if (!this.doc.getParent(this.model)) return;
|
||||
|
||||
const [paragraphId] = this.doc.addSiblingBlocks(this.model, [
|
||||
{
|
||||
flavour: 'affine:paragraph',
|
||||
},
|
||||
]);
|
||||
const model = this.doc.getBlockById(paragraphId);
|
||||
assertExists(model, `Failed to add paragraph block.`);
|
||||
|
||||
requestConnectedFrame(() => {
|
||||
selection.update(selList => {
|
||||
return selList
|
||||
.filter<BaseSelection>(sel => !sel.is('block'))
|
||||
.concat(
|
||||
selection.create('text', {
|
||||
from: {
|
||||
blockId: model.id,
|
||||
index: 0,
|
||||
length: 0,
|
||||
},
|
||||
to: null,
|
||||
})
|
||||
);
|
||||
});
|
||||
}, this);
|
||||
};
|
||||
|
||||
this.bindHotKey({
|
||||
Enter: () => {
|
||||
if (!this._focused) return;
|
||||
addParagraph();
|
||||
return true;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _initReferencedModel() {
|
||||
const surfaceModel = getSurfaceBlock(this.doc);
|
||||
this._surfaceModel = surfaceModel;
|
||||
|
||||
const findReferencedModel = (): [
|
||||
BlockSuite.EdgelessModel | null,
|
||||
string,
|
||||
] => {
|
||||
if (!this.model.reference) return [null, this.doc.id];
|
||||
|
||||
if (this.doc.getBlock(this.model.reference)) {
|
||||
return [
|
||||
this.doc.getBlock(this.model.reference)
|
||||
?.model as GfxBlockElementModel,
|
||||
this.doc.id,
|
||||
];
|
||||
}
|
||||
|
||||
if (this._surfaceModel?.getElementById(this.model.reference)) {
|
||||
return [
|
||||
this._surfaceModel.getElementById(this.model.reference),
|
||||
this.doc.id,
|
||||
];
|
||||
}
|
||||
|
||||
const doc = [...this.std.collection.docs.values()]
|
||||
.map(doc => doc.getDoc())
|
||||
.find(
|
||||
doc =>
|
||||
doc.getBlock(this.model.reference) ||
|
||||
getSurfaceBlock(doc)!.getElementById(this.model.reference)
|
||||
);
|
||||
|
||||
if (doc) {
|
||||
this._surfaceModel = getSurfaceBlock(doc);
|
||||
}
|
||||
|
||||
if (doc && doc.getBlock(this.model.reference)) {
|
||||
return [
|
||||
doc.getBlock(this.model.reference)?.model as GfxBlockElementModel,
|
||||
doc.id,
|
||||
];
|
||||
}
|
||||
|
||||
if (doc && getSurfaceBlock(doc)) {
|
||||
return [
|
||||
getSurfaceBlock(doc)!.getElementById(this.model.reference),
|
||||
doc.id,
|
||||
];
|
||||
}
|
||||
|
||||
return [null, this.doc.id];
|
||||
};
|
||||
|
||||
const init = () => {
|
||||
const [referencedModel, docId] = findReferencedModel();
|
||||
|
||||
this._referencedModel =
|
||||
referencedModel && referencedModel.xywh ? referencedModel : null;
|
||||
this._previewDoc = this.doc.collection.getDoc(docId, {
|
||||
readonly: true,
|
||||
});
|
||||
this._referenceXYWH = this._referencedModel?.xywh ?? null;
|
||||
};
|
||||
|
||||
init();
|
||||
|
||||
this._disposables.add(
|
||||
this.model.propsUpdated.on(payload => {
|
||||
if (
|
||||
payload.key === 'reference' &&
|
||||
this.model.reference !== this._referencedModel?.id
|
||||
) {
|
||||
init();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
if (surfaceModel && this._referencedModel instanceof SurfaceElementModel) {
|
||||
this._disposables.add(
|
||||
surfaceModel.elementRemoved.on(({ id }) => {
|
||||
if (this.model.reference === id) {
|
||||
init();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (this._referencedModel instanceof GfxBlockElementModel) {
|
||||
this._disposables.add(
|
||||
this.doc.slots.blockUpdated.on(({ type, id }) => {
|
||||
if (type === 'delete' && id === this.model.reference) {
|
||||
init();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private _initSelection() {
|
||||
const selection = this.host.selection;
|
||||
this._disposables.add(
|
||||
selection.slots.changed.on(selList => {
|
||||
this._focused = selList.some(
|
||||
sel => sel.blockId === this.blockId && sel.is('block')
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private _initSpec() {
|
||||
const refreshViewport = this._refreshViewport.bind(this);
|
||||
class PageViewWatcher extends BlockServiceWatcher {
|
||||
static override readonly flavour = 'affine:page';
|
||||
|
||||
override mounted() {
|
||||
this.blockService.disposables.add(
|
||||
this.blockService.specSlots.viewConnected.once(({ component }) => {
|
||||
const edgelessBlock =
|
||||
component as EdgelessRootPreviewBlockComponent;
|
||||
|
||||
edgelessBlock.editorViewportSelector = 'ref-viewport';
|
||||
refreshViewport();
|
||||
edgelessBlock.service.viewport.sizeUpdated.once(() => {
|
||||
refreshViewport();
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
this._previewSpec.extend([PageViewWatcher]);
|
||||
|
||||
const referenceId = this.model.reference;
|
||||
const setReferenceXYWH = (xywh: typeof this._referenceXYWH) => {
|
||||
this._referenceXYWH = xywh;
|
||||
};
|
||||
|
||||
class FrameGroupViewWatcher extends LifeCycleWatcher {
|
||||
static override readonly key = 'surface-ref-group-view-watcher';
|
||||
|
||||
private readonly _disposable = new DisposableGroup();
|
||||
|
||||
override mounted() {
|
||||
const edgelessService = this.std.get(EdgelessRootService);
|
||||
const { _disposable } = this;
|
||||
|
||||
const referenceElement =
|
||||
edgelessService.crud.getElementById(referenceId);
|
||||
if (!referenceElement) {
|
||||
throw new BlockSuiteError(
|
||||
ErrorCode.MissingViewModelError,
|
||||
`can not find element(id:${referenceElement})`
|
||||
);
|
||||
}
|
||||
|
||||
if (referenceElement instanceof FrameBlockModel) {
|
||||
_disposable.add(
|
||||
referenceElement.xywh$.subscribe(xywh => {
|
||||
setReferenceXYWH(xywh);
|
||||
refreshViewport();
|
||||
})
|
||||
);
|
||||
} else if (referenceElement instanceof GroupElementModel) {
|
||||
_disposable.add(
|
||||
edgelessService.surface.elementUpdated.on(({ id, oldValues }) => {
|
||||
if (
|
||||
id === referenceId &&
|
||||
oldValues.xywh !== referenceElement.xywh
|
||||
) {
|
||||
setReferenceXYWH(referenceElement.xywh);
|
||||
refreshViewport();
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
console.warn('Unsupported reference element type');
|
||||
}
|
||||
}
|
||||
|
||||
override unmounted() {
|
||||
this._disposable.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
this._previewSpec.extend([FrameGroupViewWatcher]);
|
||||
}
|
||||
|
||||
private _refreshViewport() {
|
||||
if (!this._referenceXYWH) return;
|
||||
|
||||
const previewEditorHost = this.previewEditor;
|
||||
|
||||
if (!previewEditorHost) return;
|
||||
|
||||
const edgelessService = previewEditorHost.std.getService(
|
||||
'affine:page'
|
||||
) as EdgelessRootService;
|
||||
|
||||
edgelessService.viewport.setViewportByBound(
|
||||
Bound.deserialize(this._referenceXYWH)
|
||||
);
|
||||
}
|
||||
|
||||
private _renderMask(
|
||||
referencedModel: BlockSuite.EdgelessModel,
|
||||
flavourOrType: string
|
||||
) {
|
||||
const title = 'title' in referencedModel ? referencedModel.title : '';
|
||||
|
||||
return html`
|
||||
<div class="surface-ref-mask">
|
||||
<div class="ref-label">
|
||||
<div class="title">
|
||||
${REF_LABEL_ICON[flavourOrType ?? 'DEFAULT'] ??
|
||||
REF_LABEL_ICON.DEFAULT}
|
||||
<span>${title}</span>
|
||||
</div>
|
||||
<div class="suffix">from edgeless mode</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderRefContent(referencedModel: BlockSuite.EdgelessModel) {
|
||||
const [, , w, h] = deserializeXYWH(referencedModel.xywh);
|
||||
const flavourOrType =
|
||||
'flavour' in referencedModel
|
||||
? referencedModel.flavour
|
||||
: referencedModel.type;
|
||||
const _previewSpec = this._previewSpec.value;
|
||||
|
||||
if (!this._viewportEditor) {
|
||||
this._viewportEditor = new BlockStdScope({
|
||||
doc: this._previewDoc!,
|
||||
extensions: _previewSpec,
|
||||
}).render();
|
||||
}
|
||||
|
||||
return html`<div class="ref-content">
|
||||
<div
|
||||
class="ref-viewport ${flavourOrType === 'affine:frame' ? 'frame' : ''}"
|
||||
style=${styleMap({
|
||||
width: `${w}px`,
|
||||
aspectRatio: `${w} / ${h}`,
|
||||
})}
|
||||
>
|
||||
${this._viewportEditor}
|
||||
</div>
|
||||
${this._renderMask(referencedModel, flavourOrType)}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
private _renderRefPlaceholder(model: SurfaceRefBlockModel) {
|
||||
return html`<div class="ref-placeholder">
|
||||
<div class="placeholder-image">${noContentPlaceholder}</div>
|
||||
<div class="placeholder-text">
|
||||
No Such
|
||||
${NO_CONTENT_TITLE[model.refFlavour ?? 'DEFAULT'] ??
|
||||
NO_CONTENT_TITLE.DEFAULT}
|
||||
</div>
|
||||
<div class="placeholder-action">
|
||||
<button class="delete-button" type="button" @click=${this._deleteThis}>
|
||||
<span class="icon">${MoreDeleteIcon}</span
|
||||
><span>Delete this block</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="placeholder-reason">
|
||||
${NO_CONTENT_REASON[model.refFlavour ?? 'DEFAULT'] ??
|
||||
NO_CONTENT_REASON.DEFAULT}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
this.contentEditable = 'false';
|
||||
|
||||
if (!this._shouldRender) return;
|
||||
|
||||
this._initHotkey();
|
||||
this._initSpec();
|
||||
this._initReferencedModel();
|
||||
this._initSelection();
|
||||
}
|
||||
|
||||
override render() {
|
||||
if (!this._shouldRender) return nothing;
|
||||
|
||||
const { _surfaceModel, _referencedModel, model } = this;
|
||||
const isEmpty =
|
||||
!_surfaceModel || !_referencedModel || !_referencedModel.xywh;
|
||||
const content = isEmpty
|
||||
? this._renderRefPlaceholder(model)
|
||||
: this._renderRefContent(_referencedModel);
|
||||
const edgelessTheme = this.std.get(ThemeProvider).edgeless$.value;
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="affine-surface-ref"
|
||||
data-theme=${edgelessTheme}
|
||||
@click=${this._focusBlock}
|
||||
style=${styleMap({
|
||||
outline: this._focused
|
||||
? '2px solid var(--affine-primary-color)'
|
||||
: undefined,
|
||||
})}
|
||||
>
|
||||
${content}
|
||||
</div>
|
||||
|
||||
<block-caption-editor></block-caption-editor>
|
||||
|
||||
${Object.values(this.widgets)}
|
||||
`;
|
||||
}
|
||||
|
||||
viewInEdgeless() {
|
||||
if (!this._referenceXYWH) return;
|
||||
|
||||
const viewport = {
|
||||
xywh: this._referenceXYWH,
|
||||
padding: [60, 20, 20, 20] as [number, number, number, number],
|
||||
};
|
||||
|
||||
this.std.get(EditPropsStore).setStorage('viewport', viewport);
|
||||
this.std.get(DocModeProvider).setEditorMode('edgeless');
|
||||
}
|
||||
|
||||
override willUpdate(_changedProperties: Map<PropertyKey, unknown>): void {
|
||||
if (_changedProperties.has('_referencedModel')) {
|
||||
this._refreshViewport();
|
||||
}
|
||||
}
|
||||
|
||||
@state()
|
||||
private accessor _focused: boolean = false;
|
||||
|
||||
@state()
|
||||
private accessor _surfaceModel: SurfaceBlockModel | null = null;
|
||||
|
||||
@query('affine-surface-ref > block-caption-editor')
|
||||
accessor captionElement!: BlockCaptionEditor;
|
||||
|
||||
@query('editor-host')
|
||||
accessor previewEditor!: EditorHost | null;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'affine-surface-ref': SurfaceRefBlockComponent;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import {
|
||||
BlockViewExtension,
|
||||
CommandExtension,
|
||||
type ExtensionType,
|
||||
FlavourExtension,
|
||||
WidgetViewMapExtension,
|
||||
} from '@blocksuite/block-std';
|
||||
import { literal } from 'lit/static-html.js';
|
||||
|
||||
import { commands } from './commands.js';
|
||||
|
||||
export const PageSurfaceRefBlockSpec: ExtensionType[] = [
|
||||
FlavourExtension('affine:surface-ref'),
|
||||
CommandExtension(commands),
|
||||
BlockViewExtension('affine:surface-ref', literal`affine-surface-ref`),
|
||||
WidgetViewMapExtension('affine:surface-ref', {
|
||||
surfaceToolbar: literal`affine-surface-ref-toolbar`,
|
||||
}),
|
||||
];
|
||||
|
||||
export const EdgelessSurfaceRefBlockSpec: ExtensionType[] = [
|
||||
FlavourExtension('affine:surface-ref'),
|
||||
BlockViewExtension(
|
||||
'affine:surface-ref',
|
||||
literal`affine-edgeless-surface-ref`
|
||||
),
|
||||
];
|
||||
@@ -1,99 +0,0 @@
|
||||
import { html } from 'lit';
|
||||
|
||||
export const noContentPlaceholder = html`
|
||||
<svg
|
||||
width="182"
|
||||
height="182"
|
||||
viewBox="0 0 182 182"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect
|
||||
x="37.645"
|
||||
y="37.6452"
|
||||
width="106.71"
|
||||
height="106.71"
|
||||
stroke="#D2D2D2"
|
||||
stroke-width="0.586319"
|
||||
/>
|
||||
<path
|
||||
d="M91 144.234L37.7664 91.0003L91 37.7666L144.234 91.0003L91 144.234Z"
|
||||
stroke="#D2D2D2"
|
||||
stroke-width="0.586319"
|
||||
/>
|
||||
<path
|
||||
d="M90.564 37.352C99.4686 32.1345 109.836 29.1436 120.902 29.1436C154.093 29.1436 181 56.0502 181 89.2413C181 113.999 166.03 135.259 144.648 144.466"
|
||||
stroke="#D2D2D2"
|
||||
stroke-width="0.586319"
|
||||
/>
|
||||
<path
|
||||
d="M144.465 90.707C149.683 99.6117 152.674 109.979 152.674 121.045C152.674 154.236 125.767 181.143 92.5759 181.143C67.8187 181.143 46.5579 166.173 37.3516 144.791"
|
||||
stroke="#D2D2D2"
|
||||
stroke-width="0.586319"
|
||||
/>
|
||||
<path
|
||||
d="M91.436 144.465C82.5314 149.683 72.1639 152.674 61.0978 152.674C27.9068 152.674 1.0001 125.767 1.0001 92.576C1.00011 67.8188 15.9701 46.558 37.3519 37.3518"
|
||||
stroke="#D2D2D2"
|
||||
stroke-width="0.586319"
|
||||
/>
|
||||
<path
|
||||
d="M37.3518 91.436C32.1342 82.5314 29.1433 72.1639 29.1433 61.0978C29.1433 27.9067 56.05 1.00002 89.241 1.00001C113.998 1.00001 135.259 15.97 144.465 37.3518"
|
||||
stroke="#D2D2D2"
|
||||
stroke-width="0.586319"
|
||||
/>
|
||||
<path
|
||||
d="M37.3518 37.3521L144.648 144.649"
|
||||
stroke="#D2D2D2"
|
||||
stroke-width="0.586319"
|
||||
/>
|
||||
<path
|
||||
d="M144.648 37.3521L37.3518 144.649"
|
||||
stroke="#D2D2D2"
|
||||
stroke-width="0.586319"
|
||||
/>
|
||||
<path d="M91 37.3521V144.649" stroke="#D2D2D2" stroke-width="0.586319" />
|
||||
<path d="M144.648 91L37.3518 91" stroke="#D2D2D2" stroke-width="0.586319" />
|
||||
<ellipse cx="144.355" cy="37.645" rx="4.39739" ry="4.3974" fill="#5B5B5B" />
|
||||
<ellipse
|
||||
cx="144.355"
|
||||
cy="144.355"
|
||||
rx="4.39739"
|
||||
ry="4.3974"
|
||||
fill="#5B5B5B"
|
||||
/>
|
||||
<ellipse
|
||||
cx="144.355"
|
||||
cy="90.9999"
|
||||
rx="4.39739"
|
||||
ry="4.3974"
|
||||
fill="#5B5B5B"
|
||||
/>
|
||||
<ellipse cx="37.645" cy="37.645" rx="4.39739" ry="4.3974" fill="#5B5B5B" />
|
||||
<ellipse cx="37.645" cy="144.355" rx="4.39739" ry="4.3974" fill="#5B5B5B" />
|
||||
<ellipse cx="37.645" cy="90.9999" rx="4.39739" ry="4.3974" fill="#5B5B5B" />
|
||||
<ellipse
|
||||
cx="90.9999"
|
||||
cy="37.6451"
|
||||
rx="4.3974"
|
||||
ry="4.39739"
|
||||
transform="rotate(-90 90.9999 37.6451)"
|
||||
fill="#5B5B5B"
|
||||
/>
|
||||
<ellipse
|
||||
cx="90.9999"
|
||||
cy="90.4136"
|
||||
rx="4.3974"
|
||||
ry="4.39739"
|
||||
transform="rotate(-90 90.9999 90.4136)"
|
||||
fill="#5B5B5B"
|
||||
/>
|
||||
<ellipse
|
||||
cx="90.9999"
|
||||
cy="144.356"
|
||||
rx="4.3974"
|
||||
ry="4.39739"
|
||||
transform="rotate(-90 90.9999 144.356)"
|
||||
fill="#5B5B5B"
|
||||
/>
|
||||
</svg>
|
||||
`;
|
||||
Reference in New Issue
Block a user