mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 04:48:53 +00:00
refactor(editor): move mini mindmap to ai module (#9497)
This commit is contained in:
@@ -1,106 +0,0 @@
|
||||
import { Container } from '@blocksuite/global/di';
|
||||
import { DocCollection, Schema } from '@blocksuite/store';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import { defaultBlockMarkdownAdapterMatchers } from '../../_common/adapters/index.js';
|
||||
import { inlineDeltaToMarkdownAdapterMatchers } from '../../_common/adapters/markdown/delta-converter/inline-delta.js';
|
||||
import { markdownInlineToDeltaMatchers } from '../../_common/adapters/markdown/delta-converter/markdown-inline.js';
|
||||
import { markdownToMindmap } from '../../surface-block/mini-mindmap/mindmap-preview.js';
|
||||
|
||||
const container = new Container();
|
||||
[
|
||||
...markdownInlineToDeltaMatchers,
|
||||
...defaultBlockMarkdownAdapterMatchers,
|
||||
...inlineDeltaToMarkdownAdapterMatchers,
|
||||
].forEach(ext => {
|
||||
ext.setup(container);
|
||||
});
|
||||
const provider = container.provider();
|
||||
|
||||
describe('markdownToMindmap: convert markdown list to a mind map tree', () => {
|
||||
test('basic case', () => {
|
||||
const markdown = `
|
||||
- Text A
|
||||
- Text B
|
||||
- Text C
|
||||
- Text D
|
||||
- Text E
|
||||
`;
|
||||
const collection = new DocCollection({ schema: new Schema() });
|
||||
collection.meta.initialize();
|
||||
const doc = collection.createDoc();
|
||||
const nodes = markdownToMindmap(markdown, doc, provider);
|
||||
|
||||
expect(nodes).toEqual({
|
||||
text: 'Text A',
|
||||
children: [
|
||||
{
|
||||
text: 'Text B',
|
||||
children: [
|
||||
{
|
||||
text: 'Text C',
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Text D',
|
||||
children: [
|
||||
{
|
||||
text: 'Text E',
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('basic case with different indent', () => {
|
||||
const markdown = `
|
||||
- Text A
|
||||
- Text B
|
||||
- Text C
|
||||
- Text D
|
||||
- Text E
|
||||
`;
|
||||
const collection = new DocCollection({ schema: new Schema() });
|
||||
collection.meta.initialize();
|
||||
const doc = collection.createDoc();
|
||||
const nodes = markdownToMindmap(markdown, doc, provider);
|
||||
|
||||
expect(nodes).toEqual({
|
||||
text: 'Text A',
|
||||
children: [
|
||||
{
|
||||
text: 'Text B',
|
||||
children: [
|
||||
{
|
||||
text: 'Text C',
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Text D',
|
||||
children: [
|
||||
{
|
||||
text: 'Text E',
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('empty case', () => {
|
||||
const markdown = '';
|
||||
const collection = new DocCollection({ schema: new Schema() });
|
||||
collection.meta.initialize();
|
||||
const doc = collection.createDoc();
|
||||
const nodes = markdownToMindmap(markdown, doc, provider);
|
||||
|
||||
expect(nodes).toEqual(null);
|
||||
});
|
||||
});
|
||||
@@ -181,11 +181,6 @@ import {
|
||||
AFFINE_VIEWPORT_OVERLAY_WIDGET,
|
||||
AffineViewportOverlayWidget,
|
||||
} from './root-block/widgets/viewport-overlay/viewport-overlay.js';
|
||||
import {
|
||||
MindmapRootBlock,
|
||||
MindmapSurfaceBlock,
|
||||
MiniMindmapPreview,
|
||||
} from './surface-block/mini-mindmap/index.js';
|
||||
|
||||
export function effects() {
|
||||
registerSpecs();
|
||||
@@ -232,8 +227,6 @@ export function effects() {
|
||||
|
||||
customElements.define('affine-page-root', PageRootBlockComponent);
|
||||
customElements.define('affine-preview-root', PreviewRootBlockComponent);
|
||||
customElements.define('mini-mindmap-preview', MiniMindmapPreview);
|
||||
customElements.define('mini-mindmap-surface-block', MindmapSurfaceBlock);
|
||||
customElements.define('affine-edgeless-root', EdgelessRootBlockComponent);
|
||||
customElements.define('edgeless-copilot-panel', EdgelessCopilotPanel);
|
||||
customElements.define(
|
||||
@@ -371,7 +364,6 @@ export function effects() {
|
||||
);
|
||||
customElements.define('edgeless-text-editor', EdgelessTextEditor);
|
||||
customElements.define('affine-image-toolbar', AffineImageToolbar);
|
||||
customElements.define('mini-mindmap-root-block', MindmapRootBlock);
|
||||
customElements.define('affine-block-selection', BlockSelection);
|
||||
customElements.define('edgeless-slide-menu', EdgelessSlideMenu);
|
||||
customElements.define(
|
||||
|
||||
@@ -33,11 +33,6 @@ export { EditPropsMiddlewareBuilder } from './root-block/edgeless/middlewares/ba
|
||||
export { EdgelessSnapManager } from './root-block/edgeless/utils/snap-manager.js';
|
||||
export * from './root-block/index.js';
|
||||
export * from './schemas.js';
|
||||
export {
|
||||
markdownToMindmap,
|
||||
MindmapSurfaceBlock,
|
||||
MiniMindmapPreview,
|
||||
} from './surface-block/mini-mindmap/index.js';
|
||||
export * from '@blocksuite/affine-block-attachment';
|
||||
export * from '@blocksuite/affine-block-bookmark';
|
||||
export * from '@blocksuite/affine-block-code';
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
export { markdownToMindmap, MiniMindmapPreview } from './mindmap-preview.js';
|
||||
export { MindmapRootBlock } from './mindmap-root-block.js';
|
||||
export { MindmapService } from './mindmap-service.js';
|
||||
export { MindmapSurfaceBlock } from './surface-block.js';
|
||||
@@ -1,310 +0,0 @@
|
||||
import type { SurfaceBlockModel } from '@blocksuite/affine-block-surface';
|
||||
import {
|
||||
MindmapStyleFour,
|
||||
MindmapStyleOne,
|
||||
MindmapStyleThree,
|
||||
MindmapStyleTwo,
|
||||
} from '@blocksuite/affine-components/icons';
|
||||
import {
|
||||
type MindmapElementModel,
|
||||
MindmapStyle,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { MarkdownAdapter } from '@blocksuite/affine-shared/adapters';
|
||||
import { BlockStdScope, type EditorHost } from '@blocksuite/block-std';
|
||||
import type { ServiceProvider } from '@blocksuite/global/di';
|
||||
import { WithDisposable } from '@blocksuite/global/utils';
|
||||
import {
|
||||
type Doc,
|
||||
DocCollection,
|
||||
type DocCollectionOptions,
|
||||
IdGeneratorType,
|
||||
Job,
|
||||
Schema,
|
||||
} from '@blocksuite/store';
|
||||
import { css, html, LitElement, nothing } from 'lit';
|
||||
import { property, query } from 'lit/decorators.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
import type { Root } from 'mdast';
|
||||
|
||||
import { MiniMindmapSchema, MiniMindmapSpecs } from './spec.js';
|
||||
|
||||
const mindmapStyles = [
|
||||
[MindmapStyle.ONE, MindmapStyleOne],
|
||||
[MindmapStyle.TWO, MindmapStyleTwo],
|
||||
[MindmapStyle.THREE, MindmapStyleThree],
|
||||
[MindmapStyle.FOUR, MindmapStyleFour],
|
||||
];
|
||||
|
||||
type Unpacked<T> = T extends (infer U)[] ? U : T;
|
||||
|
||||
export class MiniMindmapPreview extends WithDisposable(LitElement) {
|
||||
static override styles = css`
|
||||
mini-mindmap-root-block,
|
||||
mini-mindmap-surface-block,
|
||||
editor-host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.select-template-title {
|
||||
align-self: stretch;
|
||||
|
||||
color: var(
|
||||
--light-textColor-textSecondaryColor,
|
||||
var(--textColor-textSecondaryColor, #8e8d91)
|
||||
);
|
||||
|
||||
font-family: Inter;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.template {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.template-item {
|
||||
box-sizing: border-box;
|
||||
border: 2px solid var(--affine-border-color);
|
||||
border-radius: 4px;
|
||||
padding: 4px 6px;
|
||||
}
|
||||
|
||||
.template-item.active,
|
||||
.template-item:hover {
|
||||
border-color: var(--affine-brand-color);
|
||||
}
|
||||
|
||||
.template-item > svg {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
|
||||
doc?: Doc;
|
||||
|
||||
mindmapId?: string;
|
||||
|
||||
surface?: SurfaceBlockModel;
|
||||
|
||||
get _mindmap(): MindmapElementModel | null {
|
||||
return (
|
||||
(this.surface?.getElementById(
|
||||
this.mindmapId || ''
|
||||
) as MindmapElementModel) ?? null
|
||||
);
|
||||
}
|
||||
|
||||
private _createTemporaryDoc() {
|
||||
const schema = new Schema();
|
||||
schema.register(MiniMindmapSchema);
|
||||
const options: DocCollectionOptions = {
|
||||
id: 'MINI_MINDMAP_TEMPORARY',
|
||||
schema,
|
||||
idGenerator: IdGeneratorType.NanoID,
|
||||
awarenessSources: [],
|
||||
};
|
||||
|
||||
const collection = new DocCollection(options);
|
||||
collection.meta.initialize();
|
||||
collection.start();
|
||||
|
||||
const doc = collection.createDoc({ id: 'doc:home' }).load();
|
||||
const rootId = doc.addBlock('affine:page', {});
|
||||
const surfaceId = doc.addBlock('affine:surface', {}, rootId);
|
||||
const surface = doc.getBlockById(surfaceId) as SurfaceBlockModel;
|
||||
doc.resetHistory();
|
||||
|
||||
return {
|
||||
doc,
|
||||
surface,
|
||||
};
|
||||
}
|
||||
|
||||
private _switchStyle(style: MindmapStyle) {
|
||||
if (!this._mindmap || !this.doc) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.doc.transact(() => {
|
||||
this._mindmap!.style = style;
|
||||
});
|
||||
|
||||
this.ctx.set({ style });
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private _toMindmapNode(answer: string, doc: Doc) {
|
||||
return markdownToMindmap(answer, doc, this.host.std.provider);
|
||||
}
|
||||
|
||||
override connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
|
||||
const tempDoc = this._createTemporaryDoc();
|
||||
const mindmapNode = this._toMindmapNode(this.answer, tempDoc.doc);
|
||||
|
||||
if (!mindmapNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.doc = tempDoc.doc;
|
||||
this.surface = tempDoc.surface;
|
||||
this.mindmapId = this.surface.addElement({
|
||||
type: 'mindmap',
|
||||
children: mindmapNode,
|
||||
style: this.mindmapStyle ?? MindmapStyle.FOUR,
|
||||
});
|
||||
this.surface.getElementById(this.mindmapId) as MindmapElementModel;
|
||||
|
||||
const centerPosition = this._mindmap?.tree.element.xywh;
|
||||
|
||||
this.ctx.set({
|
||||
node: mindmapNode,
|
||||
style: MindmapStyle.FOUR,
|
||||
centerPosition,
|
||||
});
|
||||
}
|
||||
|
||||
override render() {
|
||||
if (!this.doc || !this.surface || !this._mindmap) return nothing;
|
||||
|
||||
const curStyle = this._mindmap.style;
|
||||
|
||||
return html` <div>
|
||||
<div
|
||||
style=${styleMap({
|
||||
height: this.height + 'px',
|
||||
border: '1px solid var(--affine-border-color)',
|
||||
borderRadius: '4px',
|
||||
})}
|
||||
>
|
||||
${new BlockStdScope({
|
||||
doc: this.doc,
|
||||
extensions: MiniMindmapSpecs,
|
||||
}).render()}
|
||||
</div>
|
||||
|
||||
${this.templateShow
|
||||
? html` <div class="select-template-title">Select template</div>
|
||||
<div class="template">
|
||||
${repeat(
|
||||
mindmapStyles,
|
||||
([style]) => style,
|
||||
([style, icon]) => {
|
||||
return html`<div
|
||||
class=${`template-item ${curStyle === style ? 'active' : ''}`}
|
||||
@click=${() => this._switchStyle(style as MindmapStyle)}
|
||||
>
|
||||
${icon}
|
||||
</div>`;
|
||||
}
|
||||
)}
|
||||
</div>`
|
||||
: nothing}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor answer!: string;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor ctx!: {
|
||||
get(): Record<string, unknown>;
|
||||
set(data: Record<string, unknown>): void;
|
||||
};
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor height = 400;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor host!: EditorHost;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor mindmapStyle: MindmapStyle | undefined = undefined;
|
||||
|
||||
@query('editor-host')
|
||||
accessor portalHost!: EditorHost;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor templateShow = true;
|
||||
}
|
||||
|
||||
type Node = {
|
||||
text: string;
|
||||
children: Node[];
|
||||
};
|
||||
|
||||
export const markdownToMindmap = (
|
||||
answer: string,
|
||||
doc: Doc,
|
||||
provider: ServiceProvider
|
||||
) => {
|
||||
let result: Node | null = null;
|
||||
const job = new Job({
|
||||
schema: doc.collection.schema,
|
||||
blobCRUD: doc.collection.blobSync,
|
||||
docCRUD: {
|
||||
create: (id: string) => doc.collection.createDoc({ id }),
|
||||
get: (id: string) => doc.collection.getDoc(id),
|
||||
delete: (id: string) => doc.collection.removeDoc(id),
|
||||
},
|
||||
});
|
||||
const markdown = new MarkdownAdapter(job, provider);
|
||||
const ast: Root = markdown['_markdownToAst'](answer);
|
||||
const traverse = (
|
||||
markdownNode: Unpacked<(typeof ast)['children']>,
|
||||
firstLevel = false
|
||||
): Node | null => {
|
||||
switch (markdownNode.type) {
|
||||
case 'list':
|
||||
{
|
||||
const listItems = markdownNode.children
|
||||
.map(child => traverse(child))
|
||||
.filter(val => val);
|
||||
|
||||
if (firstLevel) {
|
||||
return listItems[0];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'listItem': {
|
||||
const paragraph = markdownNode.children[0];
|
||||
const list = markdownNode.children[1];
|
||||
const node: Node = {
|
||||
text: '',
|
||||
children: [],
|
||||
};
|
||||
|
||||
if (
|
||||
paragraph?.type === 'paragraph' &&
|
||||
paragraph.children[0]?.type === 'text'
|
||||
) {
|
||||
node.text = paragraph.children[0].value;
|
||||
}
|
||||
|
||||
if (list?.type === 'list') {
|
||||
node.children = list.children
|
||||
.map(child => traverse(child))
|
||||
.filter(val => val) as Node[];
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
if (ast?.children?.[0]?.type === 'list') {
|
||||
result = traverse(ast.children[0], true);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
@@ -1,27 +0,0 @@
|
||||
import type { RootBlockModel } from '@blocksuite/affine-model';
|
||||
import { BlockComponent } from '@blocksuite/block-std';
|
||||
import { html } from 'lit';
|
||||
|
||||
export class MindmapRootBlock extends BlockComponent<RootBlockModel> {
|
||||
override render() {
|
||||
return html`
|
||||
<style>
|
||||
.affine-mini-mindmap-root {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
background-size: 20px 20px;
|
||||
background-color: var(--affine-background-primary-color);
|
||||
background-image: radial-gradient(
|
||||
var(--affine-edgeless-grid-color) 1px,
|
||||
var(--affine-background-primary-color) 1px
|
||||
);
|
||||
}
|
||||
</style>
|
||||
<div class="affine-mini-mindmap-root">
|
||||
${this.host.renderChildren(this.model)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { RootBlockSchema } from '@blocksuite/affine-model';
|
||||
import { BlockService } from '@blocksuite/block-std';
|
||||
import { Slot } from '@blocksuite/global/utils';
|
||||
|
||||
export class MindmapService extends BlockService {
|
||||
static override readonly flavour = RootBlockSchema.model.flavour;
|
||||
|
||||
requestCenter = new Slot();
|
||||
|
||||
center() {
|
||||
this.requestCenter.emit();
|
||||
}
|
||||
|
||||
override mounted(): void {}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import {
|
||||
MindMapView,
|
||||
SurfaceBlockSchema,
|
||||
} from '@blocksuite/affine-block-surface';
|
||||
import { RootBlockSchema } from '@blocksuite/affine-model';
|
||||
import {
|
||||
DocModeService,
|
||||
ThemeService,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import {
|
||||
BlockViewExtension,
|
||||
type ExtensionType,
|
||||
FlavourExtension,
|
||||
} from '@blocksuite/block-std';
|
||||
import type { BlockSchema } from '@blocksuite/store';
|
||||
import { literal } from 'lit/static-html.js';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { MindmapService } from './mindmap-service.js';
|
||||
import { MindmapSurfaceBlockService } from './surface-service.js';
|
||||
|
||||
export const MiniMindmapSpecs: ExtensionType[] = [
|
||||
DocModeService,
|
||||
ThemeService,
|
||||
FlavourExtension('affine:page'),
|
||||
MindmapService,
|
||||
BlockViewExtension('affine:page', literal`mini-mindmap-root-block`),
|
||||
FlavourExtension('affine:surface'),
|
||||
MindMapView,
|
||||
MindmapSurfaceBlockService,
|
||||
BlockViewExtension('affine:surface', literal`mini-mindmap-surface-block`),
|
||||
];
|
||||
|
||||
export const MiniMindmapSchema: z.infer<typeof BlockSchema>[] = [
|
||||
RootBlockSchema,
|
||||
SurfaceBlockSchema,
|
||||
];
|
||||
@@ -1,155 +0,0 @@
|
||||
/* oxlint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import type { SurfaceBlockModel } from '@blocksuite/affine-block-surface';
|
||||
import {
|
||||
CanvasRenderer,
|
||||
elementRenderers,
|
||||
fitContent,
|
||||
} from '@blocksuite/affine-block-surface';
|
||||
import type { Color, ShapeElementModel } from '@blocksuite/affine-model';
|
||||
import { ThemeProvider } from '@blocksuite/affine-shared/services';
|
||||
import { BlockComponent } from '@blocksuite/block-std';
|
||||
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
|
||||
import type { Bound } from '@blocksuite/global/utils';
|
||||
import { html } from 'lit';
|
||||
import { query } from 'lit/decorators.js';
|
||||
|
||||
import type { MindmapService } from './mindmap-service.js';
|
||||
|
||||
export class MindmapSurfaceBlock extends BlockComponent<SurfaceBlockModel> {
|
||||
renderer?: CanvasRenderer;
|
||||
|
||||
private get _grid() {
|
||||
return this.std.get(GfxControllerIdentifier).grid;
|
||||
}
|
||||
|
||||
private get _layer() {
|
||||
return this.std.get(GfxControllerIdentifier).layer;
|
||||
}
|
||||
|
||||
get mindmapService() {
|
||||
return this.std.getService('affine:page') as unknown as MindmapService;
|
||||
}
|
||||
|
||||
get viewport() {
|
||||
return this.std.get(GfxControllerIdentifier).viewport;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
private _adjustNodeWidth() {
|
||||
this.model.doc.transact(() => {
|
||||
this.model.elementModels.forEach(element => {
|
||||
if (element.type === 'shape') {
|
||||
fitContent(element as ShapeElementModel);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private _resizeEffect() {
|
||||
const observer = new ResizeObserver(() => {
|
||||
this.viewport.onResize();
|
||||
});
|
||||
|
||||
observer.observe(this.editorContainer);
|
||||
this._disposables.add(() => {
|
||||
observer.disconnect();
|
||||
});
|
||||
}
|
||||
|
||||
private _setupCenterEffect() {
|
||||
this._disposables.add(
|
||||
this.mindmapService.requestCenter.on(() => {
|
||||
let bound: Bound;
|
||||
|
||||
this.model.elementModels.forEach(el => {
|
||||
if (!bound) {
|
||||
bound = el.elementBound;
|
||||
} else {
|
||||
bound = bound.unite(el.elementBound);
|
||||
}
|
||||
});
|
||||
|
||||
if (bound!) {
|
||||
this.viewport.setViewportByBound(bound, [10, 10, 10, 10]);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private _setupRenderer() {
|
||||
this._disposables.add(
|
||||
this.model.elementUpdated.on(() => {
|
||||
this.mindmapService.center();
|
||||
})
|
||||
);
|
||||
|
||||
this.viewport.ZOOM_MIN = 0.01;
|
||||
}
|
||||
|
||||
override connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
|
||||
const themeService = this.std.get(ThemeProvider);
|
||||
this.renderer = new CanvasRenderer({
|
||||
viewport: this.viewport,
|
||||
layerManager: this._layer,
|
||||
gridManager: this._grid,
|
||||
enableStackingCanvas: true,
|
||||
provider: {
|
||||
selectedElements: () => [],
|
||||
getColorScheme: () => themeService.edgelessTheme,
|
||||
getColorValue: (color: Color, fallback?: Color, real?: boolean) =>
|
||||
themeService.getColorValue(
|
||||
color,
|
||||
fallback,
|
||||
real,
|
||||
themeService.edgelessTheme
|
||||
),
|
||||
generateColorProperty: (color: Color, fallback?: Color) =>
|
||||
themeService.generateColorProperty(
|
||||
color,
|
||||
fallback,
|
||||
themeService.edgelessTheme
|
||||
),
|
||||
getPropertyValue: (property: string) =>
|
||||
themeService.getCssVariableColor(
|
||||
property,
|
||||
themeService.edgelessTheme
|
||||
),
|
||||
},
|
||||
elementRenderers,
|
||||
surfaceModel: this.model,
|
||||
});
|
||||
this._disposables.add(this.renderer);
|
||||
}
|
||||
|
||||
override firstUpdated(_changedProperties: Map<PropertyKey, unknown>): void {
|
||||
this.renderer?.attach(this.editorContainer);
|
||||
|
||||
this._resizeEffect();
|
||||
this._setupCenterEffect();
|
||||
this._setupRenderer();
|
||||
this._adjustNodeWidth();
|
||||
this.mindmapService.center();
|
||||
}
|
||||
|
||||
override render() {
|
||||
return html`
|
||||
<style>
|
||||
.affine-mini-mindmap-surface {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
<div class="affine-mini-mindmap-surface">
|
||||
<!-- attach cavnas later in renderer -->
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@query('.affine-mini-mindmap-surface')
|
||||
accessor editorContainer!: HTMLDivElement;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import { SurfaceBlockSchema } from '@blocksuite/affine-block-surface';
|
||||
import { BlockService } from '@blocksuite/block-std';
|
||||
|
||||
export class MindmapSurfaceBlockService extends BlockService {
|
||||
static override readonly flavour = SurfaceBlockSchema.model.flavour;
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
import type { EditorHost } from '@blocksuite/block-std';
|
||||
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
|
||||
import {
|
||||
type EdgelessRootBlockComponent,
|
||||
type MindmapElementModel,
|
||||
type MindmapSurfaceBlock,
|
||||
MiniMindmapPreview,
|
||||
} from '@blocksuite/blocks';
|
||||
import { beforeEach, describe, expect, test } from 'vitest';
|
||||
|
||||
import { wait } from '../utils/common.js';
|
||||
import { getDocRootBlock } from '../utils/edgeless.js';
|
||||
import { setupEditor } from '../utils/setup.js';
|
||||
|
||||
describe('mini mindmap preview', () => {
|
||||
let edgeless!: EdgelessRootBlockComponent;
|
||||
|
||||
const createPreview = (host: EditorHost, answer: string) => {
|
||||
const mindmapPreview = new MiniMindmapPreview();
|
||||
|
||||
mindmapPreview.answer = answer;
|
||||
mindmapPreview.host = host;
|
||||
mindmapPreview.ctx = {
|
||||
get() {
|
||||
return {};
|
||||
},
|
||||
set() {},
|
||||
};
|
||||
|
||||
document.body.append(mindmapPreview);
|
||||
|
||||
return mindmapPreview;
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const cleanup = await setupEditor('edgeless');
|
||||
|
||||
edgeless = getDocRootBlock(doc, editor, 'edgeless');
|
||||
|
||||
edgeless.gfx.tool.setTool('default');
|
||||
|
||||
return cleanup;
|
||||
});
|
||||
|
||||
test('mini mindmap basic', async () => {
|
||||
const mindmapAnswer = `
|
||||
- Mindmap
|
||||
- Node 1
|
||||
- Node 1.1
|
||||
- Node 1.2
|
||||
- Node 2
|
||||
- Node 2.1
|
||||
- Node 2.2
|
||||
`;
|
||||
|
||||
const miniMindMapPreview = createPreview(
|
||||
window.editor.host!,
|
||||
mindmapAnswer
|
||||
);
|
||||
await wait(50);
|
||||
const miniMindMapSurface = miniMindMapPreview.renderRoot.querySelector(
|
||||
'mini-mindmap-surface-block'
|
||||
) as MindmapSurfaceBlock;
|
||||
|
||||
// model-related properties
|
||||
expect(miniMindMapPreview.mindmapId).toBeDefined();
|
||||
expect(miniMindMapPreview.portalHost).toBeDefined();
|
||||
expect(miniMindMapPreview.doc).toBeDefined();
|
||||
expect(miniMindMapPreview.surface).toBeDefined();
|
||||
expect(miniMindMapPreview.surface!.elementModels.length).toBe(8);
|
||||
|
||||
// renderer
|
||||
expect(miniMindMapSurface.renderer).toBeDefined();
|
||||
expect(miniMindMapSurface.renderer?.canvas.isConnected).toBe(true);
|
||||
expect(miniMindMapSurface.renderer?.canvas.width).toBeGreaterThan(0);
|
||||
expect(miniMindMapSurface.renderer?.canvas.height).toBeGreaterThan(0);
|
||||
|
||||
return () => {
|
||||
miniMindMapPreview.remove();
|
||||
};
|
||||
});
|
||||
|
||||
test('mini mindmap should layout automatically', async () => {
|
||||
const mindmapAnswer = `
|
||||
- Main node
|
||||
- Child node
|
||||
- Second child node
|
||||
- Third child node
|
||||
`;
|
||||
|
||||
const miniMindMapPreview = createPreview(
|
||||
window.editor.host!,
|
||||
mindmapAnswer
|
||||
);
|
||||
|
||||
await wait(50);
|
||||
const gfx = miniMindMapPreview.portalHost.std.get(GfxControllerIdentifier);
|
||||
const mindmap = gfx.surface!.elementModels.filter(
|
||||
model => model.type === 'mindmap'
|
||||
)[0] as MindmapElementModel;
|
||||
const [child1, child2, child3] = mindmap.tree.children;
|
||||
const root = mindmap.tree;
|
||||
|
||||
expect(mindmap).not.toBeUndefined();
|
||||
|
||||
expect(root.children.length).toBe(3);
|
||||
expect(root.element.x).toBeLessThan(child1.element.x);
|
||||
|
||||
// children should be aligned horizontally
|
||||
expect(child1.element.x).toBe(child2.element.x);
|
||||
expect(child2.element.x).toBe(child3.element.x);
|
||||
|
||||
// children
|
||||
expect(child1.element.y + child1.element.h).toBeLessThan(child2.element.y);
|
||||
expect(child2.element.y + child2.element.h).toBeLessThan(child3.element.y);
|
||||
});
|
||||
});
|
||||
16
blocksuite/presets/src/__tests__/env.d.ts
vendored
16
blocksuite/presets/src/__tests__/env.d.ts
vendored
@@ -1,16 +0,0 @@
|
||||
import type { Doc, DocCollection, Job } from '@blocksuite/store';
|
||||
|
||||
import type { AffineEditorContainer } from '../index.js';
|
||||
|
||||
declare global {
|
||||
const editor: AffineEditorContainer;
|
||||
const doc: Doc;
|
||||
const collection: DocCollection;
|
||||
const job: Job;
|
||||
interface Window {
|
||||
editor: AffineEditorContainer;
|
||||
doc: Doc;
|
||||
job: Job;
|
||||
collection: DocCollection;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { effects as blocksEffects } from '@blocksuite/blocks/effects';
|
||||
import type { BlockCollection } from '@blocksuite/store';
|
||||
import type { BlockCollection, Doc, Job } from '@blocksuite/store';
|
||||
|
||||
import { effects } from '../../effects.js';
|
||||
|
||||
@@ -109,3 +109,16 @@ export function cleanup() {
|
||||
|
||||
delete (window as any).doc;
|
||||
}
|
||||
|
||||
declare global {
|
||||
const editor: AffineEditorContainer;
|
||||
const doc: Doc;
|
||||
const collection: DocCollection;
|
||||
const job: Job;
|
||||
interface Window {
|
||||
editor: AffineEditorContainer;
|
||||
doc: Doc;
|
||||
job: Job;
|
||||
collection: DocCollection;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import '@blocksuite/affine-block-surface/effects';
|
||||
|
||||
export * from './editors/index.js';
|
||||
export * from './fragments/index.js';
|
||||
export * from './helpers/index.js';
|
||||
export * from './editors';
|
||||
export * from './fragments';
|
||||
export * from './helpers';
|
||||
|
||||
const env =
|
||||
typeof globalThis !== 'undefined'
|
||||
|
||||
Reference in New Issue
Block a user